@molecule/app-rich-text-quill
Provider bond · rich-text · App (browser) · v1.0.5 · Apache-2.0
Quill v2 rich text editor provider for molecule.dev
npm install @molecule/app-rich-text-quillnpm · Source on GitHub · Implements @molecule/app-rich-text
How it works
@molecule/app-rich-text-quill is a provider bond on the app (browser) side: it implements the rich-text core interface (@molecule/app-rich-text) with a concrete vendor or library behind it.
Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.
import { setProvider } from '@molecule/app-rich-text'
import { createQuillProvider } from '@molecule/app-rich-text-quill'
setProvider(createQuillProvider())Works with: @molecule/app-rich-text
Live demos
Running apps built with Molecule that install this package — open the app or study the template.
Reference
Quill v2 rich text editor provider for molecule.dev.
Implements the RichTextProvider interface using Quill v2.
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
import { setProvider } from '@molecule/app-rich-text'
import { createQuillProvider } from '@molecule/app-rich-text-quill'
setProvider(createQuillProvider())
Type
provider
Installation
npm install @molecule/app-rich-text-quill @molecule/app-rich-text dompurify quill quill-delta
API
Interfaces
QuillOptions
Quill-specific configuration options.
interface QuillOptions extends EditorOptions {
/**
* Quill theme ('snow' or 'bubble').
*/
theme?: 'snow' | 'bubble'
/**
* Enable/disable specific formats.
*/
formats?: FormatType[]
/**
* Quill module configurations.
*/
modules?: {
toolbar?: boolean | string | unknown[] | Record<string, unknown>
history?: { delay?: number; maxStack?: number; userOnly?: boolean }
clipboard?: { matchVisual?: boolean }
keyboard?: Record<string, unknown>
[key: string]: unknown
}
/**
* Scroll container element.
* (Quill v1 option — ignored by the pinned Quill v2.)
*/
scrollingContainer?: HTMLElement | string | null
/**
* Strict mode (limit user input to editor's capabilities).
* (Quill v1 option — ignored by the pinned Quill v2.)
*/
strict?: boolean
/**
* Debug mode.
*/
debug?: 'error' | 'warn' | 'log' | false
}
RichTextValue
Rich text content value.
interface RichTextValue {
/**
* Plain text content.
*/
text: string
/**
* HTML content.
*/
html: string
/**
* Delta/JSON representation (if supported by the editor).
*/
delta?: unknown
}
ToolbarConfig
Rich text editor toolbar layout (named preset with grouped format buttons).
interface ToolbarConfig {
/**
* Toolbar name/key.
*/
name: string
/**
* Toolbar groups - each group is an array of format buttons.
*/
groups: ToolbarGroup[]
}
SelectionRange
Selection range in the editor.
interface SelectionRange {
/**
* Start index.
*/
index: number
/**
* Length of selection.
*/
length: number
}
TextChangeData
Data emitted when the editor content changes.
interface TextChangeData {
/**
* New value.
*/
value: RichTextValue
/**
* Previous value.
*/
previousValue: RichTextValue
/**
* Source of the change.
*/
source: 'user' | 'api' | 'silent'
}
SelectionChangeData
Data emitted when the editor selection changes.
interface SelectionChangeData {
/**
* New selection range.
*/
range: SelectionRange | null
/**
* Previous selection range.
*/
previousRange: SelectionRange | null
/**
* Source of the change.
*/
source: 'user' | 'api' | 'silent'
}
RichTextEditor
Rich text editor instance with methods for content manipulation, formatting, and event handling.
interface RichTextEditor {
/**
* Gets the current content value.
*/
getValue(): RichTextValue
/**
* Sets the content value.
*/
setValue(value: RichTextValue): void
/**
* Gets the plain text content.
*/
getText(): string
/**
* Gets the HTML content.
*/
getHTML(): string
/**
* Gets the content length.
*/
getLength(): number
/**
* Inserts text at the current cursor position.
*/
insertText(text: string, index?: number): void
/**
* Inserts HTML at the current cursor position.
*/
insertHTML(html: string, index?: number): void
/**
* Inserts an embed (image, video, etc.).
*/
insertEmbed(type: string, value: unknown, index?: number): void
/**
* Deletes content.
*/
deleteText(index: number, length: number): void
/**
* Formats text.
*/
format(format: FormatType, value?: unknown): void
/**
* Formats text at a specific range.
*/
formatText(index: number, length: number, format: FormatType, value?: unknown): void
/**
* Removes formatting.
*/
removeFormat(index: number, length: number): void
/**
* Gets the current selection.
*/
getSelection(): SelectionRange | null
/**
* Sets the selection.
*/
setSelection(index: number, length?: number): void
/**
* Focuses the editor.
*/
focus(): void
/**
* Blurs the editor.
*/
blur(): void
/**
* Checks if the editor has focus.
*/
hasFocus(): boolean
/**
* Enables the editor.
*/
enable(): void
/**
* Disables the editor.
*/
disable(): void
/**
* Checks if the editor is enabled.
*/
isEnabled(): boolean
/**
* Subscribes to editor events.
*/
on<T>(event: EditorEvent, handler: EditorEventHandler<T>): () => void
/**
* Removes event handler.
*/
off<T>(event: EditorEvent, handler: EditorEventHandler<T>): void
/**
* Gets the underlying editor instance (for advanced usage).
*/
getEditorInstance(): unknown
/**
* Destroys the editor.
*/
destroy(): void
}
EditorOptions
Configuration options for creating a new rich text editor instance.
interface EditorOptions {
/**
* Container element.
*/
container: HTMLElement
/**
* Initial value.
*/
value?: RichTextValue
/**
* Placeholder text.
*/
placeholder?: string
/**
* Toolbar configuration key or custom config.
*/
toolbar?: string | ToolbarConfig
/**
* Read-only mode.
*/
readOnly?: boolean
/**
* Custom formats to register.
*/
formats?: FormatType[]
/**
* Theme name.
*/
theme?: string
/**
* Additional modules/plugins configuration.
*/
modules?: Record<string, unknown>
}
RichTextProvider
Rich text editor provider interface for creating editors and converting content formats.
interface RichTextProvider {
/**
* Create a new rich text editor instance attached to a container element.
* @returns A RichTextEditor instance for controlling the editor.
*/
createEditor(options: EditorOptions): RichTextEditor
/**
* Get the name of this rich text provider (e.g., 'quill', 'tiptap', 'slate').
* @returns The provider name string.
*/
getName(): string
/**
* Get the available toolbar configuration presets (e.g., 'minimal', 'standard', 'full').
* @returns A map of preset names to their ToolbarConfig definitions.
*/
getToolbarPresets(): Record<string, ToolbarConfig>
/**
* Convert an HTML string to the editor's internal rich text format.
* @returns A RichTextValue in the provider's internal format.
*/
htmlToValue(html: string): RichTextValue
/**
* Convert a plain text string to the editor's internal rich text format.
* @returns A RichTextValue in the provider's internal format.
*/
textToValue(text: string): RichTextValue
/**
* Create an empty rich text value suitable for initializing an editor.
* @returns An empty RichTextValue.
*/
createEmptyValue(): RichTextValue
}
Types
FormatType
Editor format types.
type FormatType =
| 'bold'
| 'italic'
| 'underline'
| 'strike'
| 'link'
| 'image'
| 'video'
| 'blockquote'
| 'code-block'
| 'header'
| 'list'
| 'indent'
| 'align'
| 'color'
| 'background'
| 'font'
| 'size'
| 'script'
| 'direction'
| 'clean'
EditorEvent
Editor event types.
type EditorEvent = 'text-change' | 'selection-change' | 'focus' | 'blur'
EditorEventHandler
Event handler for editor events.
type EditorEventHandler<T = unknown> = (data: T) => void
data— The data.
Classes
Quill
declare class Quill {
static DEFAULTS: {
bounds: null
modules: {
clipboard: boolean
keyboard: boolean
history: boolean
uploader: boolean
}
placeholder: string
readOnly: false
registry: Parchment.Registry
theme: string
}
static events: {
readonly EDITOR_CHANGE: 'editor-change'
readonly SCROLL_BEFORE_UPDATE: 'scroll-before-update'
readonly SCROLL_BLOT_MOUNT: 'scroll-blot-mount'
readonly SCROLL_BLOT_UNMOUNT: 'scroll-blot-unmount'
readonly SCROLL_OPTIMIZE: 'scroll-optimize'
readonly SCROLL_UPDATE: 'scroll-update'
readonly SCROLL_EMBED_UPDATE: 'scroll-embed-update'
readonly SELECTION_CHANGE: 'selection-change'
readonly TEXT_CHANGE: 'text-change'
readonly COMPOSITION_BEFORE_START: 'composition-before-start'
readonly COMPOSITION_START: 'composition-start'
readonly COMPOSITION_BEFORE_END: 'composition-before-end'
readonly COMPOSITION_END: 'composition-end'
}
static sources: {
readonly API: 'api'
readonly SILENT: 'silent'
readonly USER: 'user'
}
static version: string
static imports: Record<string, unknown>
static debug(limit: DebugLevel | boolean): void
static find(node: Node, bubble?: boolean): Parchment.Blot | Quill | null
static import(name: 'core/module'): typeof Module
static import(name: `themes/${string}`): typeof Theme
static import(name: 'parchment'): typeof Parchment
static import(name: 'delta'): typeof Delta
static import(name: string): unknown
static register(
targets: Record<
string,
Parchment.RegistryDefinition | Record<string, unknown> | Theme | Module | Function
>,
overwrite?: boolean,
): void
static register(target: Parchment.RegistryDefinition, overwrite?: boolean): void
static register(path: string, target: any, overwrite?: boolean): void
container: HTMLElement
root: HTMLDivElement
scroll: Scroll
emitter: Emitter
protected allowReadOnlyEdits: boolean
editor: Editor
composition: Composition
selection: Selection
theme: Theme
keyboard: Keyboard
clipboard: Clipboard
history: History
uploader: Uploader
options: ExpandedQuillOptions
constructor(container: HTMLElement | string, options?: QuillOptions)
addContainer(container: string, refNode?: Node | null): HTMLDivElement
addContainer(container: HTMLElement, refNode?: Node | null): HTMLElement
blur(): void
deleteText(range: Range, source?: EmitterSource): Delta
deleteText(index: number, length: number, source?: EmitterSource): Delta
disable(): void
editReadOnly<T>(modifier: () => T): T
enable(enabled?: boolean): void
focus(options?: { preventScroll?: boolean }): void
format(name: string, value: unknown, source?: EmitterSource): Delta
formatLine(
index: number,
length: number,
formats: Record<string, unknown>,
source?: EmitterSource,
): Delta
formatLine(
index: number,
length: number,
name: string,
value?: unknown,
source?: EmitterSource,
): Delta
formatText(range: Range, name: string, value: unknown, source?: EmitterSource): Delta
formatText(
index: number,
length: number,
name: string,
value: unknown,
source?: EmitterSource,
): Delta
formatText(
index: number,
length: number,
formats: Record<string, unknown>,
source?: EmitterSource,
): Delta
getBounds(index: number | Range, length?: number): Bounds | null
getContents(index?: number, length?: number): Delta
getFormat(
index?: number,
length?: number,
): {
[format: string]: unknown
}
getFormat(range?: Range): {
[format: string]: unknown
}
getIndex(blot: Parchment.Blot): number
getLength(): number
getLeaf(index: number): [Parchment.LeafBlot | null, number]
getLine(index: number): [Block | BlockEmbed | null, number]
getLines(range: Range): (Block | BlockEmbed)[]
getLines(index?: number, length?: number): (Block | BlockEmbed)[]
getModule(name: string): unknown
getSelection(focus: true): Range
getSelection(focus?: boolean): Range | null
getSemanticHTML(range: Range): string
getSemanticHTML(index?: number, length?: number): string
getText(range?: Range): string
getText(index?: number, length?: number): string
hasFocus(): boolean
insertEmbed(index: number, embed: string, value: unknown, source?: EmitterSource): Delta
insertText(index: number, text: string, source?: EmitterSource): Delta
insertText(
index: number,
text: string,
formats: Record<string, unknown>,
source?: EmitterSource,
): Delta
insertText(
index: number,
text: string,
name: string,
value: unknown,
source?: EmitterSource,
): Delta
isEnabled(): boolean
off(...args: Parameters<(typeof Emitter)['prototype']['off']>): Emitter
on(
event: (typeof Emitter)['events']['TEXT_CHANGE'],
handler: (delta: Delta, oldContent: Delta, source: EmitterSource) => void,
): Emitter
on(
event: (typeof Emitter)['events']['SELECTION_CHANGE'],
handler: (range: Range, oldRange: Range, source: EmitterSource) => void,
): Emitter
on(
event: (typeof Emitter)['events']['EDITOR_CHANGE'],
handler: (
...args:
| [(typeof Emitter)['events']['TEXT_CHANGE'], Delta, Delta, EmitterSource]
| [(typeof Emitter)['events']['SELECTION_CHANGE'], Range, Range, EmitterSource]
) => void,
): Emitter
on(event: string, ...args: unknown[]): Emitter
once(...args: Parameters<(typeof Emitter)['prototype']['once']>): Emitter
removeFormat(index: number, length: number, source?: EmitterSource): Delta
scrollRectIntoView(rect: Rect): void
/**
* @deprecated Use Quill#scrollSelectionIntoView() instead.
*/
scrollIntoView(): void
/**
* Scroll the current selection into the visible area.
* If the selection is already visible, no scrolling will occur.
*/
scrollSelectionIntoView(): void
setContents(delta: Delta | Op[], source?: EmitterSource): Delta
setSelection(range: Range | null, source?: EmitterSource): void
setSelection(index: number, source?: EmitterSource): void
setSelection(index: number, length?: number, source?: EmitterSource): void
setSelection(index: number, source?: EmitterSource): void
setText(text: string, source?: EmitterSource): Delta
update(source?: EmitterSource): void
updateContents(delta: Delta | Op[], source?: EmitterSource): Delta
}
Functions
createQuillEditor(quill, container)
Wraps a Quill instance in a molecule RichTextEditor interface. Sets up event listeners
for text changes, selection changes, focus, and blur, and exposes a unified API for
content manipulation, formatting, and selection management.
function createQuillEditor(quill: Quill, container: HTMLElement): RichTextEditor
quill— The initialized Quill editor instance.container— The DOM element containing the Quill editor (used for cleanup on destroy).
Returns: A RichTextEditor with getValue/setValue, formatting, selection, and event methods.
createQuillProvider(defaultOptions)
Creates a Quill-based rich text provider implementing the molecule RichTextProvider interface.
Supports toolbar presets (minimal, standard, full), HTML/text/delta value conversion,
and Quill themes (snow, bubble).
function createQuillProvider(defaultOptions?: Partial<QuillOptions>): RichTextProvider
defaultOptions— Default Quill options applied to every editor created by this provider.
Returns: A RichTextProvider backed by Quill.
toolbarConfigToQuill(config)
Converts a molecule ToolbarConfig to Quill's native toolbar array format.
Each group becomes a sub-array; string items pass through, objects become { type: options } entries.
function toolbarConfigToQuill(config: ToolbarConfig): unknown[][]
config— A molecule toolbar config with named groups of toolbar items.
Returns: A nested array in Quill's toolbar module format.
Constants
provider
Default Quill rich text provider instance.
const provider: RichTextProvider
quillToolbars
Default toolbar presets for Quill.
const quillToolbars: Record<string, ToolbarConfig>
Core Interface
Implements @molecule/app-rich-text interface.
Bond Wiring
Setup function to register this provider with the core interface:
import { setProvider } from '@molecule/app-rich-text'
import { provider } from '@molecule/app-rich-text-quill'
export function setupRichTextQuill(): void {
setProvider(provider)
}
Injection Notes
Requirements
Peer dependencies:
@molecule/app-rich-text^1.0.2
Runtime Dependencies
-
@molecule/app-rich-text -
dompurify -
quill -
quill-delta -
Import Quill's theme stylesheet yourself — this package does not:
import 'quill/dist/quill.snow.css'(orquill.bubble.cssfortheme: 'bubble'). Without it the editor renders unstyled and the toolbar is a broken list. -
Call
setProvider(...)explicitly. If you forget,@molecule/app-rich-textsilently falls back to its built-in contentEditable provider — the app appears to work but without Quill's toolbar presets, delta support, or theming. No error is thrown. -
Browser-only.
createEditor({ container })requires a mounted DOM element andhtmlToValue()touchesdocument— in SSR frameworks construct editors in a client-only effect. -
htmlToValue()returnsdelta: undefined(delta conversion needs a live editor); initialize editors fromvalue.htmlor convert via an editor instance. -
Untrusted HTML is sanitized on the way IN (DOMPurify in
htmlToValue, Quill's clipboard allowlist for stored HTML) — buteditor.getHTML()is raw editor DOM: still sanitize server-side before persisting/re-serving user content. -
Toolbar: pass a preset name (
'minimal' | 'standard' | 'full'), a customToolbarConfig, ortoolbar: false; presets live inquillToolbars. -
Custom
modulesmerge with the toolbar preset. Amodulesobject you pass (history/clipboard/keyboard/…) is merged UNDER the derived toolbar, so it never drops the toolbar; setmodules.toolbaronly when you want to override the preset. -
editor.destroy()fully tears down — it also removes the snow theme's sibling.ql-toolbarelement, so re-mounting on the same node won't stack duplicate toolbars.