npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lilac-wysiwyg/core

v0.7.1

Published

Lilac - a smooth, modern WYSIWYG text editor built with pure TypeScript. Zero dependencies, framework-agnostic, with a clean interface and elegant typography.

Readme

Lilac Editor

A smooth, modern WYSIWYG text editor built with TypeScript. Framework-agnostic core with adapters for React, Preact, Vue, Svelte, Solid, Angular, Lit, Qwik, Alpine.js, Ember, Astro, and Vanilla JS.

TypeScript Zero Dependencies Framework Agnostic

Architecture

Lilac follows an adapter pattern for maximum framework compatibility. The core functionality is isolated from framework-specific implementations, ensuring consistent behavior across all platforms.

lilac/
├── core/                    # Framework-agnostic core
│   ├── types/               # TypeScript type definitions
│   ├── plugins/              # Plugin system (emoji, table, word count)
│   ├── utils/                # Utility functions (formatting, icons)
│   ├── components/           # Core components (Editor, Toolbar)
│   └── index.ts              # Core exports
├── adapter/                  # Framework-specific adapters
│   ├── react/                # React component wrapper
│   ├── preact/               # Preact component wrapper
│   ├── vue/                  # Vue component
│   ├── svelte/               # Svelte action
│   ├── solid/                # SolidJS component + use:lilac directive
│   ├── angular/              # Angular component/directive
│   ├── lit/                  # <lilac-editor> Web Component
│   ├── qwik/                 # Qwik component
│   ├── alpine/               # Alpine.js x-lilac directive
│   ├── ember/                # Ember {{lilac}} modifier
│   ├── astro/                # Astro mount helper + integration
│   └── vanilla/              # Vanilla JS wrapper
└── docs/                     # Documentation & demos

Core Design Principles

  1. UI Instructions in Core: The core package contains all UI styling instructions to ensure consistent appearance across all framework adapters
  2. Framework-Agnostic Logic: Business logic and state management are kept framework-independent
  3. Adapter Pattern: Each framework gets its own adapter that bridges the core with framework-specific patterns

Features

  • Framework-Agnostic Core: Pure TypeScript implementation with no dependencies
  • Consistent UI: Centralized UI instructions ensure identical styling across all adapters
  • Plugin System: Extensible architecture with built-in plugins for emojis, tables, and word count
  • Rich Text Formatting: Bold, italic, underline, strikethrough, headings, lists, blockquotes, code blocks
  • Media Support: Link and image insertion with keyboard shortcuts
  • Keyboard Shortcuts: Ctrl/Cmd + B for bold, Ctrl/Cmd + I for italic, etc.
  • Undo/Redo: Full history support with 50-step undo stack
  • Theme Support: Light and dark themes with CSS custom properties
  • Accessibility: ARIA labels on the toolbar buttons and editable region, plus full keyboard navigation
  • Mobile-friendly: 44px touch targets on coarse pointers, a scrollable toolbar, and a 16px editable font so iOS doesn't zoom on focus
  • Plugin API: Create custom plugins with toolbar buttons, keyboard shortcuts, and lifecycle hooks

Installation

Core (Vanilla JS / TypeScript)

npm install @lilac-wysiwyg/core
pnpm add @lilac-wysiwyg/core
yarn add @lilac-wysiwyg/core

Framework Adapters

Every adapter is a thin wrapper over the same core, published under the @lilac-wysiwyg/* scope and built with plain tsc. Install one with your package manager of choice (npm install, pnpm add, or yarn add).

| Framework | Package | Supported versions | Integration | Docs | | --- | --- | --- | --- | --- | | React | @lilac-wysiwyg/react | React ≥ 16.8 (hooks) | <LilacEditor> component + imperative ref | react.html | | Preact | @lilac-wysiwyg/preact | Preact ≥ 10 | <LilacEditor> component + imperative ref | preact.html | | Vue | @lilac-wysiwyg/vue | Vue ≥ 3 | <LilacEditor> component with v-model | vue.html | | Svelte | @lilac-wysiwyg/svelte | Svelte ≥ 4 (works in 4 & 5) | use:lilac action | svelte.html | | Solid | @lilac-wysiwyg/solid | solid-js ≥ 1.6 | <LilacEditor> component + use:lilac directive | solid.html | | Angular | @lilac-wysiwyg/angular | @angular/core ≥ 14 | Component + directive (module) | angular.html | | Lit | @lilac-wysiwyg/lit | lit ≥ 2 | <lilac-editor> custom element | lit.html | | Qwik | @lilac-wysiwyg/qwik | @builder.io/qwik ≥ 1.5 | <LilacEditor> component$ (QRL handlers) | qwik.html | | Alpine.js | @lilac-wysiwyg/alpine | alpinejs ≥ 3 | x-lilac directive (plugin) | alpine.html | | Ember | @lilac-wysiwyg/ember | ember-source ≥ 4, ember-modifier ≥ 4 | {{lilac}} element modifier | ember.html | | Astro | @lilac-wysiwyg/astro | astro ≥ 3 (optional peer) | mountLilacEditor() + lilac() integration | astro.html | | Vanilla JS | @lilac-wysiwyg/vanilla | — (no framework) | new LilacEditor() / factory | vanilla.html |

All version ranges are declared as peer dependencies, so the adapter uses the copy of the framework already in your app. React also peers on react-dom (≥ 16.8).

Script tag (UMD / CDN)

For no-bundler pages, load the UMD build and use the Lilac global:

<script src="https://cdn.jsdelivr.net/npm/@lilac-wysiwyg/core/dist/lilac.umd.js"></script>
<div id="editor"></div>
<script>
  new Lilac.LilacEditor({ container: document.getElementById('editor'), toolbar: { show: true } });
</script>

PHP & Laravel

A Composer package renders the editor server-side (it emits the container + the UMD loader script). See the PHP docs.

composer require lilac-wysiwyg/lilac
use Lilac\Editor;

echo Editor::render(['toolbar' => ['show' => true], 'placeholder' => 'Write…']);

In Laravel the service provider is auto-discovered, so a Blade view can use the component directly:

<x-lilac :value="old('body', $post->body)" placeholder="Write…" />

Quick Start

Vanilla JS / TypeScript (Core)

import { LilacEditor } from '@lilac-wysiwyg/core';

// Styles are injected automatically on construction. To supply your own
// stylesheet instead, pass `injectStyles: false` below.

// Create editor instance
const editor = new LilacEditor({
  container: document.getElementById('editor')!,
  toolbar: { show: true },
  placeholder: 'Start writing...',
  onChange: (content) => {
    console.log('Content:', content);
  }
});

// Get content
const content = editor.getContent();

// Set content
editor.setContent('<p>Hello World!</p>');

React

import { useState } from 'react';
import { LilacEditor } from '@lilac-wysiwyg/react';

function App() {
  const [content, setContent] = useState('<p>Hello!</p>');

  return (
    <LilacEditor
      value={content}
      onChange={setContent}
      toolbar={{ show: true }}
      placeholder="Start writing..."
    />
  );
}

Vue 3

<template>
  <LilacEditor
    v-model="content"
    :toolbar="{ show: true }"
    placeholder="Start writing..."
  />
</template>

<script setup>
import { ref } from 'vue';
import { LilacEditor } from '@lilac-wysiwyg/vue';

const content = ref('<p>Hello!</p>');
</script>

Svelte

The Svelte adapter ships an action, not a component, so it works on both Svelte 4 and 5 without a compiler step:

<script>
  import { lilac } from '@lilac-wysiwyg/svelte';

  let content = '<p>Hello!</p>';
</script>

<div use:lilac={{ value: content, onChange: (c) => (content = c), toolbar: true }} />

Solid

The Solid adapter offers both a component and a use:lilac directive:

import { createSignal } from 'solid-js';
import { LilacEditor } from '@lilac-wysiwyg/solid';

function App() {
  const [content, setContent] = createSignal('<p>Hello!</p>');

  return <LilacEditor value={content()} onChange={setContent} toolbar />;
}

Lit / Web Components

Importing the package registers a <lilac-editor> custom element, usable in plain HTML or any framework:

<script type="module">
  import '@lilac-wysiwyg/lit';
</script>

<lilac-editor value="<p>Hello!</p>" toolbar></lilac-editor>
<script>
  document
    .querySelector('lilac-editor')
    .addEventListener('change', (e) => console.log(e.detail));
</script>

Using Built-in Plugins

Pass plugins through the plugins option — the editor installs them into its own manager on construction:

import {
  LilacEditor,
  wordCountPlugin,
  emojiPlugin,
  tablePlugin
} from '@lilac-wysiwyg/core';

const editor = new LilacEditor({
  container: document.getElementById('editor')!,
  toolbar: { show: true },
  plugins: [wordCountPlugin, emojiPlugin, tablePlugin],
  onChange: (content) => console.log('Content:', content)
});

The exported pluginManager singleton is a standalone registry; installing into it does not affect an editor instance. Use the plugins option above.

API Reference

LilacEditor Constructor Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | container | HTMLElement | required | DOM element to mount the editor | | initialContent | string | '' | Initial HTML content of the editor | | placeholder | string | 'Start writing...' | Placeholder text when editor is empty | | readOnly | boolean | false | Whether the editor is read-only | | autoFocus | boolean | false | Auto-focus editor on mount | | theme | 'light' \| 'dark' \| 'auto' | 'light' | Editor theme ('auto' follows the OS preference) | | sanitize | boolean | true | Sanitize HTML from initialContent/setContent (stored-XSS guard) | | injectStyles | boolean | true | Inject Lilac's stylesheet into document.head on construction | | minHeight | number | undefined | Minimum editor height in pixels | | maxHeight | number | undefined | Maximum editor height in pixels | | maxLength | number | undefined | Maximum content length | | className | string | undefined | Extra class name on the editor root | | onChange | (content: string) => void | undefined | Content change callback | | onSelectionChange | (selection: SelectionRange \| null) => void | undefined | Selection change callback | | onFocus | () => void | undefined | Focus event callback | | onBlur | () => void | undefined | Blur event callback | | toolbar | ToolbarConfig | undefined | Toolbar configuration | | plugins | EditorPlugin[] | [] | Plugins to install |

LilacEditor Methods

| Method | Description | |--------|-------------| | getContent() | Get current editor HTML content | | setContent(content) | Set editor content | | focus() | Focus the editor | | blur() | Blur the editor | | undo() | Undo last change | | redo() | Redo last undone change | | setReadOnly(readOnly) | Enable/disable read-only mode | | destroy() | Clean up and remove the editor |

Toolbar Tools

| Tool | Keyboard Shortcut | |------|-----------------| | Bold | Ctrl/Cmd + B | | Italic | Ctrl/Cmd + I | | Underline | Ctrl/Cmd + U | | Strikethrough | - | | Heading 1-3 | - | | Paragraph | - | | Bullet List | - | | Ordered List | - | | Blockquote | - | | Code Block | - | | Link | Ctrl/Cmd + K | | Image | - |

Plugin System

Lilac features a powerful plugin system that allows extending the editor with custom functionality.

Built-in Plugins

  • Word Count Plugin: Real-time document statistics in a side panel, toggled from a toolbar button
  • Emoji Picker Plugin: Insert emojis with an easy-to-use picker (Ctrl+Shift+E)
  • Table Inserter Plugin: Insert and manage HTML tables (Ctrl+Shift+T)

Creating Custom Plugins

import type { EditorPlugin } from '@lilac-wysiwyg/core';

export const myCustomPlugin: EditorPlugin = {
  id: 'my-custom-plugin',
  name: 'My Custom Plugin',
  version: '1.0.0',

  // Toolbar buttons
  toolbarButtons: [{
    id: 'my-button',
    icon: '<svg>...</svg>',
    label: 'My Tool',
    tooltip: 'My custom tool',
    onClick: (context) => {
      context.insertContent('<strong>Custom!</strong>');
    },
  }],

  // Keyboard shortcuts
  keyboardShortcuts: [{
    key: 'm',
    ctrlKey: true,
    action: (context) => {
      context.insertContent('<em>Shortcut!</em>');
    },
  }],

  // Lifecycle hooks
  onInstall: (context) => console.log('Installed'),
  onEditorMount: (context) => console.log('Ready'),
  onContentChange: (content, context) => {
    // React to content changes
  },
};

Images

Drop an image file onto the editor, or paste one from the clipboard, and it is embedded at the caret. By default the image is inlined as a base64 data: URL — fully client-side, no server required:

const editor = new LilacEditor({
  container,
  toolbar: { show: true },
  maxImageSize: 5 * 1024 * 1024, // optional: reject images over 5 MB
});

To upload to your own storage instead of inlining, provide onImageUpload and return the URL to embed:

const editor = new LilacEditor({
  container,
  toolbar: { show: true },
  onImageUpload: async (file) => {
    const url = await myUploader(file); // your storage
    return url;
  },
});

Non-image drops and pastes fall through to the editor's normal handling.

Markdown Export / Import

Round-trip editor content to and from Markdown. The conversion is hand-written and zero-dependency, covering the subset the editor produces (headings, bold/italic/strikethrough, inline code and code blocks, links, images, blockquotes, lists, rules, paragraphs):

const md = editor.getMarkdown();      // serialize current content to Markdown
editor.setMarkdown('# Hello\n\nWorld'); // replace content from Markdown

The converters are also exported directly:

import { toMarkdown, fromMarkdown } from '@lilac-wysiwyg/core';

toMarkdown('<h1>Hi</h1><p>a <strong>b</strong></p>'); // "# Hi\n\na **b**"
fromMarkdown('- one\n- two');                          // "<ul><li>one</li><li>two</li></ul>"

Constructs outside that subset (tables, nested lists, HTML passthrough, underline — which has no Markdown equivalent) are not converted.

Find & Replace

Press Ctrl/Cmd + F inside the editor to open the find bar — match count, next/previous, replace, replace-all, and case-sensitive / whole-word toggles. Esc closes it, and because the shortcut is bound to the editable region, the browser's own find still works when the editor isn't focused.

editor.openFind();  // open the panel programmatically
editor.closeFind(); // close it

The matching engine is also exported for non-DOM use (e.g. searching content before it is mounted):

import { findMatches, replaceAll } from '@lilac-wysiwyg/core';

findMatches('the cat sat', 'at');                 // [{ start: 5, end: 7 }, { start: 9, end: 11 }]
replaceAll('cat cat', 'cat', 'dog', { wholeWord: true }); // { text: 'dog dog', count: 2 }

Customization

Themes

Override CSS custom properties to create custom themes:

.lilac-editor {
  --lilac-color-primary: #your-color;
  --lilac-color-background: #your-bg;
  --lilac-border-radius: 8px;
}

Development

# Install dependencies
pnpm install

# Build core library
pnpm build

# Watch for changes
pnpm dev

# Type check
pnpm typecheck

Documentation

Visit our GitHub Pages for per-framework documentation, with a live interactive demo on the Vanilla JS page.

Roadmap

  • [x] Rich text toolbar with all formatting options
  • [x] Plugin system with built-in plugins
  • [x] Emoji picker
  • [x] Table inserter
  • [x] Word count plugin
  • [x] React adapter
  • [x] Preact adapter
  • [x] Svelte adapter
  • [x] Solid adapter
  • [x] Angular adapter
  • [x] Vue adapter
  • [x] Lit adapter
  • [x] Qwik adapter
  • [x] Alpine.js adapter
  • [x] Ember adapter
  • [x] Astro adapter
  • [x] Vanilla JS adapter
  • [x] UMD / CDN (script-tag) build
  • [x] PHP & Laravel integration
  • [x] Markdown export/import
  • [x] Image upload and embedding
  • [x] Find and replace
  • [x] Mobile optimizations

Contributing

We welcome contributions! Please see our Contributing Guide for details.

License

MIT License - See LICENSE for details.

Author

Maifee Ul Asad [email protected]

Preview

preview of lilac