A modern, powerful rich text editor built for compatibility and extensibility
npx @tessl/cli install tessl/npm-quill@2.0.0Quill is a modern, powerful rich text editor built for compatibility and extensibility. It provides a comprehensive WYSIWYG editing experience with support for operational transformation, custom formatting, modular architecture, and extensive theming capabilities. The library offers both programmatic API access and user-friendly toolbar interfaces, supports multiple output formats including Delta (its native format), HTML, and plain text, and includes built-in modules for clipboard handling, keyboard shortcuts, history/undo-redo functionality, and toolbar management.
npm install quillimport Quill from "quill";For CommonJS:
const Quill = require("quill");import Quill from "quill";
// Initialize Quill editor
const quill = new Quill('#editor', {
theme: 'snow',
modules: {
toolbar: [
['bold', 'italic', 'underline'],
['link', 'image'],
[{ 'header': [1, 2, 3, false] }],
[{ 'list': 'ordered'}, { 'list': 'bullet' }]
]
}
});
// Get/set content
const content = quill.getContents();
quill.setContents(content);
// Listen for changes
quill.on('text-change', (delta, oldDelta, source) => {
console.log('Text changed:', delta);
});Quill is built around several key components:
Core editor functionality for creating and managing rich text editor instances. The main Quill class provides all essential editing operations.
class Quill {
constructor(container: HTMLElement | string, options?: QuillOptions);
// Content operations
getContents(index?: number, length?: number): Delta;
setContents(delta: Delta | Op[], source?: EmitterSource): Delta;
updateContents(delta: Delta | Op[], source?: EmitterSource): Delta;
getText(index?: number, length?: number): string;
setText(text: string, source?: EmitterSource): Delta;
getSemanticHTML(range: Range): string;
getSemanticHTML(index?: number, length?: number): string;
// Selection operations
getSelection(focus?: boolean): Range | null;
setSelection(range: Range | null, source?: EmitterSource): void;
setSelection(index: number, length?: number, source?: EmitterSource): void;
getBounds(index: number | Range, length?: number): Bounds | null;
// Formatting operations
format(name: string, value: unknown, source?: EmitterSource): Delta;
formatText(index: number, length: number, formats: Record<string, unknown>, source?: EmitterSource): Delta;
formatLine(index: number, length: number, formats: Record<string, unknown>, source?: EmitterSource): Delta;
removeFormat(index: number, length: number, source?: EmitterSource): Delta;
getFormat(index?: number, length?: number): { [format: string]: unknown };
// Text operations
insertText(index: number, text: string, formats?: Record<string, unknown>, source?: EmitterSource): Delta;
insertEmbed(index: number, embed: string, value: unknown, source?: EmitterSource): Delta;
deleteText(index: number, length: number, source?: EmitterSource): Delta;
// Editor state
focus(options?: { preventScroll?: boolean }): void;
blur(): void;
enable(enabled?: boolean): void;
disable(): void;
isEnabled(): boolean;
hasFocus(): boolean;
// Event handling
on(event: string, handler: (...args: any[]) => void): Emitter;
off(event: string, handler?: (...args: any[]) => void): Emitter;
once(event: string, handler: (...args: any[]) => void): Emitter;
// Additional content methods
getIndex(blot: Parchment.Blot): number;
getLeaf(index: number): [Parchment.Leaf | null, number];
getLine(index: number): [Parchment.Block | Parchment.BlockEmbed | null, number];
getLines(index?: number, length?: number): (Parchment.Block | Parchment.BlockEmbed)[];
getLines(range: Range): (Parchment.Block | Parchment.BlockEmbed)[];
// Container management
addContainer(container: string, refNode?: Node | null): HTMLDivElement;
addContainer(container: HTMLElement, refNode?: Node | null): HTMLElement;
// Advanced editing
editReadOnly<T>(modifier: () => T): T;
scrollRectIntoView(rect: { top: number; left: number; right: number; bottom: number }): void;
// Deprecated methods
scrollIntoView(): void; // @deprecated Use scrollSelectionIntoView()
// Utility methods
getLength(): number;
getModule(name: string): Module;
scrollSelectionIntoView(): void;
update(source?: EmitterSource): Delta | undefined;
// Static methods
static debug(level: DebugLevel | boolean): void;
static find(node: Node, bubble?: boolean): Parchment.Blot | null;
static import(name: string): unknown;
static register(path: string, target: any, overwrite?: boolean): void;
static register(target: Record<string, any>, overwrite?: boolean): void;
static register(target: Parchment.RegistryDefinition, overwrite?: boolean): void;
// Static properties
static DEFAULTS: Partial<QuillOptions>;
static events: typeof Emitter.events;
static sources: typeof Emitter.sources;
static version: string;
}
interface QuillOptions {
theme?: string;
debug?: DebugLevel | boolean;
registry?: Parchment.Registry;
readOnly?: boolean;
placeholder?: string;
bounds?: HTMLElement | string | null;
modules?: Record<string, unknown>;
formats?: string[] | null;
}
interface Range {
index: number;
length: number;
}
interface Bounds {
bottom: number;
height: number;
left: number;
right: number;
top: number;
width: number;
}
type EmitterSource = 'api' | 'user' | 'silent';
type DebugLevel = 'error' | 'warn' | 'log' | 'info';
interface Emitter {
on(event: string, handler: (...args: any[]) => void): Emitter;
off(event: string, handler?: (...args: any[]) => void): Emitter;
once(event: string, handler: (...args: any[]) => void): Emitter;
emit(event: string, ...args: any[]): void;
}
namespace Emitter {
const events = {
TEXT_CHANGE: 'text-change',
SELECTION_CHANGE: 'selection-change',
EDITOR_CHANGE: 'editor-change'
};
const sources = {
API: 'api' as EmitterSource,
USER: 'user' as EmitterSource,
SILENT: 'silent' as EmitterSource
};
}Delta-based document representation and operational transform system for tracking and applying changes to rich text content.
class Delta {
constructor(ops?: Op[] | Delta);
// Core operations
insert(text: string, attributes?: AttributeMap): Delta;
insert(embed: object, attributes?: AttributeMap): Delta;
delete(length: number): Delta;
retain(length: number, attributes?: AttributeMap): Delta;
// Delta manipulation
compose(other: Delta): Delta;
transform(other: Delta, priority?: boolean): Delta;
transformPosition(index: number, priority?: boolean): number;
diff(other: Delta, cursor?: number): Delta;
// Utility methods
length(): number;
slice(start?: number, end?: number): Delta;
partition(predicate: (op: Op) => boolean): [Delta, Delta];
filter(predicate: (op: Op, index: number) => boolean): Op[];
forEach(predicate: (op: Op, index: number) => void): void;
map<T>(predicate: (op: Op, index: number) => T): T[];
reduce<T>(predicate: (acc: T, curr: Op, index: number) => T, initialValue: T): T;
}
interface Op {
insert?: string | object;
delete?: number;
retain?: number;
attributes?: AttributeMap;
}
interface AttributeMap {
[key: string]: unknown;
}Comprehensive formatting system supporting inline formats (bold, italic, links), block formats (headers, lists, blockquotes), and custom attributors.
// Built-in inline formats
class Bold extends Inline {
static blotName: 'bold';
static tagName: ['STRONG', 'B'];
}
class Italic extends Inline {
static blotName: 'italic';
static tagName: ['EM', 'I'];
}
class Link extends Inline {
static blotName: 'link';
static tagName: 'A';
static create(value: string): HTMLAnchorElement;
static formats(domNode: HTMLAnchorElement): string;
static sanitize(url: string): string;
}
// Built-in block formats
class Header extends Block {
static blotName: 'header';
static tagName: ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'];
static formats(domNode: HTMLElement): number;
}
class List extends Block {
static blotName: 'list';
static tagName: 'LI';
static create(value: 'ordered' | 'bullet'): HTMLLIElement;
static formats(domNode: HTMLLIElement): string;
}
// Embed formats
class Image extends EmbedBlot {
static blotName: 'image';
static tagName: 'IMG';
static create(value: string): HTMLImageElement;
static formats(domNode: HTMLImageElement): Record<string, string>;
static value(domNode: HTMLImageElement): string;
}Extensible module architecture with core modules for history, keyboard, clipboard, toolbar, and upload functionality.
abstract class Module {
static DEFAULTS: Record<string, unknown>;
constructor(quill: Quill, options?: Record<string, unknown>);
}
class History extends Module {
static DEFAULTS: {
delay: number;
maxStack: number;
userOnly: boolean;
};
clear(): void;
cutoff(): void;
undo(): void;
redo(): void;
}
class Toolbar extends Module {
static DEFAULTS: {
container?: string | HTMLElement | (string | Record<string, unknown>)[];
handlers?: Record<string, ToolbarHandler>;
};
addHandler(format: string, handler: ToolbarHandler): void;
attach(input: HTMLElement): void;
update(range: Range | null): void;
}
class Clipboard extends Module {
addMatcher(selector: string, matcher: ClipboardMatcher): void;
dangerouslyPasteHTML(html: string, source?: EmitterSource): void;
convert(clipboard: { html?: string; text?: string }): Delta;
}
type ToolbarHandler = (value: unknown) => void;
type ClipboardMatcher = (node: Node, delta: Delta) => Delta;Theme system providing Snow and Bubble themes with customizable UI components and toolbar layouts.
abstract class Theme {
static DEFAULTS: Record<string, unknown>;
constructor(quill: Quill, options: Record<string, unknown>);
init(): void;
addModule(name: string): Module;
}
class SnowTheme extends Theme {
// Snow theme with toolbar above editor
}
class BubbleTheme extends Theme {
// Bubble theme with floating toolbar
}Dynamic registration system for formats, modules, themes, and blots enabling extensibility and customization.
class Quill {
static register(path: string, target: any, overwrite?: boolean): void;
static register(target: Record<string, any>, overwrite?: boolean): void;
static register(target: RegistryDefinition, overwrite?: boolean): void;
static import(name: string): unknown;
static find(node: Node, bubble?: boolean): Blot | null;
static debug(level: DebugLevel | boolean): void;
}
// Registration paths
// 'formats/bold' - Register Bold format
// 'modules/toolbar' - Register Toolbar module
// 'themes/snow' - Register Snow theme
// 'blots/block' - Register Block blot
// 'attributors/style/color' - Register style-based color attributor// Re-exported from quill-delta
interface Delta {
ops: Op[];
}
interface Op {
insert?: string | object;
delete?: number;
retain?: number;
attributes?: AttributeMap;
}
interface AttributeMap {
[key: string]: unknown;
}
class OpIterator {
constructor(ops: Op[]);
hasNext(): boolean;
next(length?: number): Op;
peek(): Op;
peekLength(): number;
peekType(): 'insert' | 'delete' | 'retain';
rest(): Op[];
}
// Re-exported from parchment
namespace Parchment {
abstract class Blot {
static blotName: string;
static className?: string;
static tagName?: string | string[];
static scope: Scope;
domNode: Node;
parent: Blot | null;
prev: Blot | null;
next: Blot | null;
constructor(scroll: ScrollBlot, domNode?: Node);
attach(): void;
clone(): Blot;
detach(): void;
deleteAt(index: number, length: number): void;
formatAt(index: number, length: number, name: string, value: any): void;
insertAt(index: number, value: string, def?: any): void;
isolate(index: number, length: number): Blot;
length(): number;
offset(root?: Blot): number;
optimize(context: any): void;
remove(): void;
replaceWith(name: string | Blot, value?: any): Blot;
split(index: number, force?: boolean): Blot | null;
update(mutations: MutationRecord[], context: any): void;
wrap(name: string | Blot, value?: any): Blot;
}
enum Scope {
TYPE = 3,
LEVEL = 12,
ATTRIBUTE = 13,
BLOT = 14,
INLINE = 7,
BLOCK = 11,
BLOCK_BLOT = 10,
INLINE_BLOT = 6,
LEAF = 4,
PARENT = 8,
ANY = 15
}
class Registry {
create(scroll: ScrollBlot, input: Node | string | Scope, value?: any): Blot;
find(domNode: Node, bubble?: boolean): Blot | null;
query(query: string | Node | Scope, scope?: Scope): BlotConstructor | null;
register(...definitions: RegistryDefinition[]): void;
}
}