SDK for visualizing and tracking contenteditable events, ranges, and DOM changes
npm install contenteditable-visualizerSDK for visualizing and tracking contenteditable events, ranges, and DOM changes. Works with any editor (ProseMirror, Slate.js, Editor.js, Lexical, etc.) without React dependencies.
- 🎯 Range Visualization - Visualize selection, composition, beforeinput, and input ranges with SVG overlays
- 📊 DOM Change Tracking - Track text node changes (added, deleted, modified, moved) in real-time
- 📝 Event Logging - Capture all contenteditable events with detailed information (beforeinput, input, composition events, selection changes)
- 📸 Snapshot Management - Capture and store snapshots with IndexedDB for debugging and analysis
- 🤖 AI Prompt Generation - Automatically generate structured prompts from snapshots for AI analysis
- 🎨 Floating UI Panel - Built-in event viewer and snapshot history viewer with toggle button and resizing support
- 🔍 Invisible Character Visualization - Visualize zero-width spaces, line feeds, tabs, and other invisible characters
- 📍 Boundary Markers - Visual indicators when selection is at text node or element boundaries
- 🔌 Framework Agnostic - Pure TypeScript/DOM API, no React or other framework dependencies
- 🔌 Plugin System - Extensible plugin system for editor-specific integrations (ProseMirror, Slate.js, etc.)
- ⚡ Performance Optimized - Throttled selection changes, configurable log limits, efficient DOM tracking
- 🎨 Customizable - Custom color schemes, panel sizing, container options, and error handling callbacks
``bash`
npm install contenteditable-visualizeror
pnpm add contenteditable-visualizeror
yarn add contenteditable-visualizer
`typescript
import { createVisualizer } from 'contenteditable-visualizer';
const editorElement = document.querySelector('[contenteditable]');
const visualizer = createVisualizer(editorElement, {
visualize: true,
logEvents: true,
snapshots: true,
panel: true,
});
// Capture a snapshot manually
await visualizer.captureSnapshot('manual', 'User triggered snapshot');
// Get event logs
const events = visualizer.getEventLogs();
// Export all data
const data = await visualizer.exportData();
console.log(data);
// Clean up when done
visualizer.destroy();
`
Creates a new visualizer instance and attaches it to the specified element.
Parameters:
- element (HTMLElement) - The contenteditable element to attach the visualizer tooptions
- (ContentEditableVisualizerOptions, optional) - Configuration options
Returns: ContentEditableVisualizer instance
Throws: Error if element is not a valid HTMLElement
#### Core Options
- visualize (boolean, default: true) - Enable range visualization overlaylogEvents
- (boolean, default: true) - Enable event loggingsnapshots
- (boolean, default: true) - Enable snapshot management functionalitypanel
- (boolean, default: true) - Show floating UI panel with event viewer and snapshot history
#### Panel Options
- panel (boolean | FloatingPanelConfig, default: true) - Show floating panel or panel configurationboolean
- - Enable/disable panelFloatingPanelConfig
- object:position
- ('top-right' | 'top-left' | 'bottom-right' | 'bottom-left', default: 'bottom-right') - Panel positiontheme
- ('light' | 'dark' | 'auto', default: 'auto') - Panel theme (auto detects system preference)container
- (HTMLElement, optional) - Container to append the panel to (default: document.body)resizable
- (boolean, default: true) - Enable panel resizingtoggleButtonSize
- (number, default: 48) - Toggle button size in pixelspanelWidth
- (number, default: 500) - Initial panel width in pixelspanelHeight
- (number, default: 600) - Initial panel height in pixelspanelMinWidth
- (number, default: 300) - Minimum panel width in pixelspanelMinHeight
- (number, default: 200) - Minimum panel height in pixelspanelMaxWidth
- (number | string, default: '90vw') - Maximum panel widthpanelMaxHeight
- (number | string, default: '90vh') - Maximum panel height
#### Snapshot Options
- autoSnapshot (boolean, default: false) - Automatically capture snapshots on input events
#### Performance Options
- maxLogs (number, default: 1000) - Maximum number of event logs to keep (0 = unlimited)throttleSelection
- (number, default: 100) - Throttle delay for selectionchange events in milliseconds
#### Customization Options
- container (HTMLElement, optional) - Container for the visualization overlay (default: element itself). Use document.body for fixed positioning.colors
- (VisualizerColorScheme, optional) - Custom color scheme for visualizations`
typescript`
{
selection?: { fill?: string; stroke?: string };
composition?: { fill?: string; stroke?: string };
beforeinput?: { fill?: string; stroke?: string };
input?: { fill?: string; stroke?: string };
deleted?: { fill?: string; stroke?: string };
added?: { fill?: string; stroke?: string };
}
sizes
- (VisualizerSizeOptions, optional) - Custom size options (for future use)onError
- ((error: Error, context: string) => void, optional) - Error callback function
#### Event Logging
##### getEventLogs(): EventLog[]
Get all logged events.
Returns: Array of event logs
##### clearEventLogs(): void
Clear all event logs.
##### onEvent(callback: (log: EventLog) => void): () => void
Register a callback for new events. The callback is called whenever a new event is logged.
Parameters:
- callback - Function to call when a new event is logged
Returns: Unsubscribe function to remove the callback
Example:
`typescript
const unsubscribe = visualizer.onEvent((log) => {
console.log('New event:', log);
});
// Later, to unsubscribe:
unsubscribe();
`
#### Snapshot Management
##### captureSnapshot(trigger?, triggerDetail?): Promise
Manually capture a snapshot of the current editor state.
Parameters:
- trigger (SnapshotTrigger, optional) - Trigger type (e.g., 'manual', 'auto', 'custom')triggerDetail
- (string, optional) - Description of what triggered the snapshot
Returns: Promise that resolves to the snapshot ID
Throws: Error if snapshots are not enabled
Example:
`typescript`
const snapshotId = await visualizer.captureSnapshot('manual', 'User clicked save button');
console.log('Snapshot ID:', snapshotId);
##### getSnapshots(): Promise
Get all stored snapshots.
Returns: Promise that resolves to an array of snapshots
##### getSnapshot(id: number): Promise
Get a specific snapshot by ID.
Parameters:
- id - The snapshot ID
Returns: Promise that resolves to the snapshot or null if not found
##### deleteSnapshot(id: number): Promise
Delete a snapshot by ID.
Parameters:
- id - The snapshot ID to delete
Throws: Error if deletion fails
##### clearSnapshots(): Promise
Clear all stored snapshots.
Throws: Error if clearing fails
#### Visualization
##### showVisualization(enabled: boolean): void
Enable or disable visualization dynamically.
Parameters:
- enabled - Whether to enable visualization
Example:
`typescript
// Disable visualization
visualizer.showVisualization(false);
// Re-enable visualization
visualizer.showVisualization(true);
`
#### Data Export
##### exportData(): Promise
Export all events and snapshots as JSON.
Returns: Promise that resolves to export data object containing:
- events - Serialized event logssnapshots
- - All stored snapshotsenvironment
- - Environment information (OS, browser, device)
Example:
`typescript
const data = await visualizer.exportData();
const json = JSON.stringify(data, null, 2);
// Download as file
const blob = new Blob([json], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = visualizer-export-${Date.now()}.json;`
a.click();
URL.revokeObjectURL(url);
#### Lifecycle
##### detach(): void
Detach event listeners and clean up resources. The visualizer can be reattached later by calling methods that require attachment.
Note: This does not remove DOM elements. Use destroy() for complete cleanup.
##### destroy(): void
Completely remove the visualizer and all UI elements. This:
- Removes all event listeners
- Destroys the range visualizer
- Destroys the floating panel
- Removes the overlay element
- Cleans up ResizeObserver and scroll handlers
Example:
`typescript`
// When unmounting or cleaning up
visualizer.destroy();
`typescript
import { createVisualizer } from 'contenteditable-visualizer';
const editorElement = document.getElementById('editor');
const visualizer = createVisualizer(editorElement);
`
`typescriptVisualizer error in ${context}:
const visualizer = createVisualizer(editorElement, {
visualize: true,
logEvents: true,
snapshots: true,
panel: {
position: 'top-right',
theme: 'dark',
resizable: true,
panelWidth: 600,
panelHeight: 700,
},
autoSnapshot: false,
maxLogs: 500,
throttleSelection: 150,
colors: {
selection: {
fill: 'rgba(59, 130, 246, 0.2)',
stroke: 'rgba(59, 130, 246, 0.8)',
},
input: {
fill: 'rgba(16, 185, 129, 0.2)',
stroke: 'rgba(16, 185, 129, 0.8)',
},
},
onError: (error, context) => {
console.error(, error);`
},
});
`typescript
const visualizer = createVisualizer(editorElement, {
panel: false,
});
// Use your own UI to display events
visualizer.onEvent((log) => {
console.log('New event:', log);
// Update your custom UI
});
`
`typescript`
// Attach overlay to document.body for fixed positioning
const visualizer = createVisualizer(editorElement, {
container: document.body,
});
`typescript
import { EditorView } from 'prosemirror-view';
import { createVisualizer } from 'contenteditable-visualizer';
const view = new EditorView(dom, {
state,
// ... other options
});
// Attach visualizer to the editor's DOM
const visualizer = createVisualizer(view.dom, {
visualize: true,
logEvents: true,
container: document.body, // Use fixed positioning for ProseMirror
});
// Register a ProseMirror plugin (from separate package)
// import { ProseMirrorPlugin } from '@contenteditable/prosemirror';
// const plugin = new ProseMirrorPlugin();
// visualizer.registerPlugin(plugin, view);
`
`typescript
import { createEditor } from 'slate';
import { createVisualizer } from 'contenteditable-visualizer';
const editor = createEditor();
const editorElement = document.querySelector('[data-slate-editor]');
const visualizer = createVisualizer(editorElement, {
visualize: true,
logEvents: true,
});
// Register a Slate plugin (from separate package)
// import { SlatePlugin } from '@contenteditable/slate';
// const plugin = new SlatePlugin();
// visualizer.registerPlugin(plugin, editor);
`
`typescript
import EditorJS from '@editorjs/editorjs';
import { createVisualizer } from 'contenteditable-visualizer';
const editor = new EditorJS({
holder: 'editorjs',
});
editor.isReady.then(() => {
const contentEditable = document.querySelector('[contenteditable]');
const visualizer = createVisualizer(contentEditable, {
visualize: true,
logEvents: true,
});
});
`
`typescript
const visualizer = createVisualizer(editorElement);
// Monitor all events
visualizer.onEvent((log) => {
switch (log.type) {
case 'beforeinput':
console.log('Before input:', log.event.inputType);
break;
case 'input':
console.log('Input:', log.event.inputType);
break;
case 'compositionstart':
console.log('IME composition started');
break;
case 'selectionchange':
console.log('Selection changed');
break;
}
});
`
`typescript
const visualizer = createVisualizer(editorElement, {
snapshots: true,
autoSnapshot: false, // Manual snapshots only
});
// Capture snapshot on specific condition
if (shouldCaptureSnapshot) {
const id = await visualizer.captureSnapshot('custom', 'Important state change');
console.log('Captured snapshot:', id);
}
// Get all snapshots
const snapshots = await visualizer.getSnapshots();
console.log('Total snapshots:', snapshots.length);
// Export for analysis
const data = await visualizer.exportData();
`
The visualizer captures the following events:
- beforeinput - Fired before input is processed. Includes getTargetRanges() information.input
- - Fired after input is processed. Includes DOM change detection results.compositionstart
- - IME composition started (for languages like Japanese, Chinese, Korean).compositionupdate
- - IME composition updated.compositionend
- - IME composition ended.selectionchange
- - Selection changed (throttled for performance).
Each snapshot includes:
- id - Unique snapshot IDtimestamp
- - When the snapshot was captured (ISO 8601 string)trigger
- - What triggered the snapshot ('manual', 'auto', or custom string)triggerDetail
- - Optional description of the triggerenvironment
- - Environment information:os
- - Operating system nameosVersion
- - OS versionbrowser
- - Browser namebrowserVersion
- - Browser versiondevice
- - Device typeisMobile
- - Whether running on mobile deviceeventLogs
- - All events captured up to that pointdomBefore
- - DOM state before (if captured)domAfter
- - DOM state afterranges
- - Selection and composition ranges at snapshot timedomChangeResult
- - Detected DOM changes (if available)
`typescript`
type EventLog = {
type: 'beforeinput' | 'input' | 'compositionstart' | 'compositionupdate' | 'compositionend' | 'selectionchange';
timestamp: number;
event: Event; // Original event object
range: Range | null; // Selection range at event time
// Additional properties depending on event type
};
`typescript`
type Snapshot = {
id: number;
timestamp: string;
trigger: string;
triggerDetail?: string;
environment: {
os: string;
osVersion: string;
browser: string;
browserVersion: string;
device: string;
isMobile: boolean;
};
eventLogs: EventLog[];
domBefore?: any;
domAfter: any;
ranges?: any;
domChangeResult?: DomChangeResult;
};
`typescript`
type ExportData = {
events: any[];
snapshots: Snapshot[];
environment: {
os: string;
osVersion: string;
browser: string;
browserVersion: string;
device: string;
isMobile: boolean;
};
};
- Chrome/Edge (latest)
- Firefox (latest)
- Safari (latest)
- Mobile browsers (iOS Safari, Chrome Mobile)
Note: Requires modern browser features:
- IndexedDB (for snapshot storage)
- ResizeObserver (for editor resize handling)
- getTargetRanges() API (for beforeinput events)
ContentEditable behavior, especially for contenteditable="false" elements, may vary between browsers:
- Chrome/Edge: Generally consistent behavior. contenteditable="false" elements can be startContainer/endContainer in Range API.
- Firefox: Similar to Chrome, but may handle edge cases differently.
- Safari: May have different behavior, especially on iOS devices.
- Mobile browsers: Additional variations due to different input methods and touch handling.
Important: The SDK captures environment information (OS, browser, device) in snapshots to help identify browser-specific issues. Always test your implementation across different browsers and devices.
- Selection Change Throttling: Selection change events are throttled by default (100ms) to avoid performance issues. Adjust with throttleSelection option.maxLogs
- Log Limits: Event logs are limited to 1000 by default. Set to 0 for unlimited (not recommended for long sessions).
- DOM Tracking: Text node tracking uses efficient TreeWalker API and WeakMap for memory management.
- Overlay Rendering: SVG overlay is efficiently updated only when ranges change.
The visualizer automatically detects and visualizes invisible characters in the selection:
- ZWNBSP (Zero-Width Non-Breaking Space, \uFEFF) - Red diamond\n
- LF (Line Feed, ) - Blue diamond\r
- CR (Carriage Return, ) - Cyan diamond\t
- TAB (Tab, ) - Purple diamond\u200B
- ZWSP (Zero-Width Space, ) - Pink diamond\u200C
- ZWNJ (Zero-Width Non-Joiner, ) - Rose diamond\u200D
- ZWJ (Zero-Width Joiner, ) - Fuchsia diamond
Each invisible character is marked with a colored diamond at the top of its bounding box, with a dashed line extending downward.
When a selection (collapsed or non-collapsed) is at a text node or element boundary, visual markers are displayed:
- Start boundary: Orange triangle pointing downward (above the text)
- End boundary: Orange triangle pointing upward (below the text)
This helps identify when the cursor or selection is at the edge of a text node or element.
Snapshots automatically include AI prompts that can be used for debugging and analysis. The prompt includes:
- HTML structure of the editor
- Event logs leading up to the snapshot
- DOM changes detected
- Range information
- Environment details
Access the AI prompt from the snapshot detail view in the floating panel, or via the aiPrompt field in the snapshot object.
Example AI Prompt:
`markdownContentEditable Event Analysis Request
- Description: Auto capture (on input event)
- Detail: User typed 'hello'
- Timestamp: 2024-01-15T10:30:45.123ZEvent Logs
\\\[1] beforeinput (t=1705315845125, Δ=2ms)
type: insertText
parent: P #editor
node: #text
offset: start=5, end=5
data: " "
selection: "hello |"
[2] input (t=1705315845127, Δ=2ms)
type: insertText
parent: P #editor
node: #text
offset: start=6, end=6
data: " "
selection: "hello |"
\\\
\Input Range:
startContainer: #text
startOffset: 6
endContainer: #text
endOffset: 6
collapsed: true
\\\
\Modified:
- Text node: "hello" → "hello " (offset: 0)
\\\
\htmlhello
\\$3
\\\htmlhello
\\Analysis Request
$3
- Analyze event occurrence order and time intervals
- Relationship between Selection, Composition, BeforeInput, and Input events
- Track Range position changes between events$3
- Track Before DOM → After DOM changes
- Text node addition/deletion/modification patterns
- Timing and content of browser DOM changes$3
- Compare Ranges at each point: Selection, Composition, BeforeInput, Input
- Range position changes (offset, container)
- Consistency between Range and actual DOM changes$3
- Analyze root causes if abnormal behavior exists
- Mismatch points between browser behavior and expected behavior
- Areas in editor implementation that need improvement$3
- Propose specific solutions
- Code-level improvement suggestions (if possible)
- Consider browser-specific issuesNotes
$3
- Selection Range: Text range selected by the user
- Composition Range: Range during IME input (Japanese, Chinese, Korean, etc.)
- BeforeInput Range: Range at beforeinput event point
- Input Range: Range at input event point
- DOM Change Result: Text node level change tracking results$3
Typical input event flow:
1. \selectionchange\ - Selection area change
2. \compositionstart\ (for IME input) - IME input start
3. \compositionupdate\ (for IME input) - IME input update
4. \beforeinput\ - Before input (before browser DOM change)
5. \input\ - After input (after browser DOM change)
6. \compositionend\ (for IME input) - IME input end
\\\Usage:
`typescript
// Capture a snapshot
const snapshotId = await visualizer.captureSnapshot('manual', 'Debugging issue');// Get the snapshot with AI prompt
const snapshot = await visualizer.getSnapshot(snapshotId);
if (snapshot?.aiPrompt) {
// Copy to clipboard or send to AI service
await navigator.clipboard.writeText(snapshot.aiPrompt);
// Or use with AI API
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization':
Bearer ${apiKey},
},
body: JSON.stringify({
model: 'gpt-4',
messages: [
{
role: 'system',
content: 'You are an expert in contenteditable behavior and browser input events.',
},
{
role: 'user',
content: snapshot.aiPrompt,
},
],
}),
});
}
`$3
The floating panel supports drag-to-resize functionality:
- Drag the resize handle (bottom-right corner) to adjust panel size
- Minimum and maximum sizes are configurable via
FloatingPanelConfig
- Panel size is maintained across sessionsTroubleshooting
$3
- Ensure the editor element has
position: relative or use container: document.body option
- Check that visualize: true is set
- Verify the element is actually contenteditable$3
- Check that
logEvents: true is set
- Verify the element is receiving events (check browser console)
- Ensure the element is properly attached to the DOM$3
- Check that
snapshots: true is set
- Verify IndexedDB is available in your browser
- Check browser console for errors$3
- Reduce
maxLogs to limit memory usage
- Increase throttleSelection delay
- Disable visualization if not needed: visualize: false
- Disable floating panel if not needed: panel: falsePlugin System
The SDK includes an extensible plugin system for editor-specific integrations. Editor-specific plugins are provided as separate packages (e.g.,
@contenteditable-visualizer/prosemirror, @contenteditable-visualizer/slate).> 📖 For detailed plugin documentation, see PLUGINS.md
$3
Plugins allow you to monitor editor-specific state and events beyond the standard DOM events. They integrate seamlessly with the visualizer's lifecycle and can provide additional context for debugging and analysis.
Key Features:
- Monitor editor-specific state changes (e.g., ProseMirror transactions, Slate operations)
- Track editor events that aren't captured by standard DOM events
- Provide editor state snapshots for AI prompt generation
- Integrate with visualizer lifecycle (attach/detach/destroy)
- Access visualizer instance for advanced integrations
$3
All plugins must implement the
VisualizerPlugin interface:`typescript
interface VisualizerPlugin {
readonly metadata: PluginMetadata;
initialize(editor: any, visualizer: ContentEditableVisualizer): void;
attach(): void;
detach(): void;
getState?(): any;
getEvents?(): any[];
destroy(): void;
}
`Plugin Metadata:
`typescript
interface PluginMetadata {
id: string; // Unique plugin identifier
name: string; // Human-readable plugin name
version: string; // Plugin version
editor: string; // Editor framework (e.g., 'prosemirror', 'slate')
description?: string; // Optional description
}
`$3
The
BasePlugin class provides a foundation for creating plugins with built-in lifecycle management:`typescript
import { BasePlugin } from 'contenteditable-visualizer';
import type { PluginMetadata, PluginOptions } from 'contenteditable-visualizer';class MyCustomPlugin extends BasePlugin {
readonly metadata: PluginMetadata = {
id: 'my-plugin',
name: 'My Custom Plugin',
version: '1.0.0',
editor: 'my-editor',
description: 'Custom plugin for my editor',
};
constructor(options: PluginOptions = {}) {
super(options);
}
// Called when plugin is initialized with editor and visualizer
protected onInitialize(): void {
// Access this.editor and this.visualizer here
}
// Called when plugin is attached (visualizer is active)
protected onAttach(): void {
// Set up event listeners, observers, etc.
}
// Called when plugin is detached (visualizer is paused)
protected onDetach(): void {
// Clean up event listeners, observers, etc.
}
// Called when plugin is destroyed
protected onDestroy(): void {
// Final cleanup
}
// Optional: Return current editor state
getState(): any {
return {
// Editor-specific state
};
}
// Optional: Return editor events since last snapshot
getEvents(): any[] {
return [
// Array of editor events
];
}
}
`$3
Plugins follow a specific lifecycle that aligns with the visualizer:
1. Initialization (
initialize)
- Called when registerPlugin() is invoked
- Receives editor instance and visualizer instance
- Sets up internal references2. Attachment (
attach)
- Called when visualizer is attached (or immediately if already attached)
- Set up event listeners, observers, etc.
- Start monitoring editor state3. Detachment (
detach)
- Called when visualizer is detached
- Clean up event listeners, observers, etc.
- Stop monitoring (but keep plugin registered)4. Destruction (
destroy)
- Called when plugin is unregistered or visualizer is destroyed
- Final cleanup of all resources
- Plugin cannot be reused after destruction$3
Here's a complete example of a custom plugin:
`typescript
import { BasePlugin } from 'contenteditable-visualizer';
import type { PluginMetadata, PluginOptions } from 'contenteditable-visualizer';
import type { ContentEditableVisualizer } from 'contenteditable-visualizer';interface MyEditor {
on(event: string, handler: Function): void;
off(event: string, handler: Function): void;
getState(): any;
getHistory(): any[];
}
interface MyPluginOptions extends PluginOptions {
config?: {
trackHistory?: boolean;
maxHistorySize?: number;
};
}
class MyEditorPlugin extends BasePlugin {
readonly metadata: PluginMetadata = {
id: 'my-editor',
name: 'My Editor Plugin',
version: '1.0.0',
editor: 'my-editor',
description: 'Monitors My Editor state and events',
};
private editor: MyEditor | null = null;
private visualizer: ContentEditableVisualizer | null = null;
private eventHistory: any[] = [];
private handlers: Map = new Map();
constructor(options: MyPluginOptions = {}) {
super(options);
}
protected onInitialize(): void {
this.editor = this.editor as MyEditor;
this.visualizer = this.visualizer;
// Initialize based on options
const config = this.options.config || {};
// ... setup based on config
}
protected onAttach(): void {
if (!this.editor) return;
// Set up event listeners
const changeHandler = (event: any) => {
this.eventHistory.push({
timestamp: Date.now(),
type: 'change',
data: event,
});
// Keep history size limited
const maxSize = this.options.config?.maxHistorySize ?? 100;
if (this.eventHistory.length > maxSize) {
this.eventHistory.shift();
}
};
this.editor.on('change', changeHandler);
this.handlers.set('change', changeHandler);
}
protected onDetach(): void {
if (!this.editor) return;
// Remove event listeners
this.handlers.forEach((handler, event) => {
this.editor!.off(event, handler);
});
this.handlers.clear();
}
protected onDestroy(): void {
this.onDetach();
this.eventHistory = [];
this.editor = null;
this.visualizer = null;
}
getState(): any {
if (!this.editor) return null;
return {
editorState: this.editor.getState(),
eventCount: this.eventHistory.length,
};
}
getEvents(): any[] {
return this.eventHistory.slice();
}
}
// Usage
const plugin = new MyEditorPlugin({
enabled: true,
config: {
trackHistory: true,
maxHistorySize: 50,
},
});
visualizer.registerPlugin(plugin, myEditorInstance);
// Later, get plugin state
const state = plugin.getState();
const events = plugin.getEvents();
// Or access via visualizer
const retrievedPlugin = visualizer.getPlugin('my-editor');
if (retrievedPlugin) {
const pluginState = retrievedPlugin.getState?.();
}
`$3
Plugins are registered with the visualizer instance:
`typescript
// Register a plugin
visualizer.registerPlugin(plugin, editorInstance);// Get a specific plugin
const plugin = visualizer.getPlugin('plugin-id');
// Get all registered plugins
const plugins = visualizer.getPlugins();
// Unregister a plugin
visualizer.unregisterPlugin('plugin-id');
`Important Notes:
- Plugins are automatically attached when the visualizer is attached
- Plugins are automatically detached when the visualizer is detached
- Plugins are automatically destroyed when the visualizer is destroyed
- You can manually unregister plugins before visualizer destruction
$3
1. Error Handling
`typescript
protected onAttach(): void {
try {
// Your attachment logic
} catch (error) {
console.error([${this.metadata.id}] Failed to attach:, error);
}
}
`2. Resource Cleanup
- Always clean up event listeners in
onDetach()
- Clear timers, observers, and subscriptions
- Release references to prevent memory leaks3. State Management
- Keep plugin state separate from editor state
- Limit history/event arrays to prevent memory issues
- Use
getState() and getEvents() for snapshot integration4. Plugin Options
- Use TypeScript interfaces for type-safe options
- Provide sensible defaults
- Validate options in constructor or
onInitialize()5. Visualizer Integration
- Access visualizer methods when needed (e.g.,
visualizer.captureSnapshot())
- Don't modify visualizer state directly
- Use visualizer's error handling if available$3
Plugins can accept configuration options:
`typescript
interface PluginOptions {
enabled?: boolean; // Enable/disable plugin (default: true)
config?: Record; // Plugin-specific configuration
}// Usage
const plugin = new MyPlugin({
enabled: true,
config: {
trackHistory: true,
maxSize: 100,
},
});
`$3
Plugins have access to the visualizer instance:
`typescript
protected onAttach(): void {
if (!this.visualizer) return;
// Access visualizer methods
const logs = this.visualizer.getEventLogs();
const snapshots = await this.visualizer.getSnapshots();
// Trigger snapshots programmatically
await this.visualizer.captureSnapshot('plugin', 'Plugin triggered snapshot');
}
`$3
Editor-specific plugins are distributed as separate packages:
-
@contenteditable/prosemirror - ProseMirror integration
- @contenteditable/slate - Slate.js integration
- @contenteditable/lexical - Lexical integration
- @contenteditable/editorjs` - Editor.js integrationThese packages provide pre-built plugins with editor-specific monitoring capabilities.
MIT