Lexical Integration
WriteTrack includes a Lexical integration, compatible with Lexical v0.12+.
Installation
Section titled “Installation”Install WriteTrack alongside Lexical:
npm i writetrack lexicalpnpm add writetrack lexicalyarn add writetrack lexicalbun add writetrack lexicalBasic Usage
Section titled “Basic Usage”import { createEditor, $getSelection } from 'lexical';import { createWriteTrackLexical } from 'writetrack/lexical';
const editor = createEditor({ namespace: 'MyEditor', onError: console.error });editor.setRootElement(document.getElementById('editor'));
const handle = createWriteTrackLexical(editor, $getSelection, { license: 'your-license-key',});
// Access WriteTrack data when neededconst data = handle.tracker?.getData();With @lexical/react
Section titled “With @lexical/react”Most Lexical apps are built from plugin components under a <LexicalComposer>
rather than createEditor(). WriteTrack slots in as one more plugin: grab the
editor instance from the composer context and hand it to
createWriteTrackLexical. @lexical/react’s <ContentEditable> calls
editor.setRootElement() internally, so no manual root wiring is needed.
'use client'; // Next.js App Router
import { useEffect, useRef } from 'react';import { $getSelection } from 'lexical';import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';import { createWriteTrackLexical } from 'writetrack/lexical';import type { WriteTrackLexicalHandle } from 'writetrack/lexical';
export function WriteTrackPlugin({ onReady,}: { onReady?: (handle: WriteTrackLexicalHandle) => void;}) { const [editor] = useLexicalComposerContext(); const handleRef = useRef<WriteTrackLexicalHandle | null>(null);
useEffect(() => { const handle = createWriteTrackLexical(editor, $getSelection, { contentId: 'article-body', }); handleRef.current = handle; onReady?.(handle); return () => handle.destroy(); }, [editor]);
return null;}Render it inside the composer alongside your other plugins:
<LexicalComposer initialConfig={config}> <RichTextPlugin contentEditable={<ContentEditable />} ... /> <WriteTrackPlugin onReady={(handle) => (trackerHandle = handle)} /></LexicalComposer>Configuration
Section titled “Configuration”Pass $getSelection as the second argument and options as the third:
const handle = createWriteTrackLexical(editor, $getSelection, { license: 'your-license-key', userId: 'user-123', contentId: 'document-456', metadata: { formName: 'signup' }, autoStart: true, // default});| Option | Type | Default | Description |
|---|---|---|---|
license | string | undefined | WriteTrack license key |
userId | string | undefined | User identifier included in metadata |
contentId | string | undefined | Content identifier included in metadata |
metadata | Record<string, unknown> | undefined | Additional metadata |
autoStart | boolean | true | Start tracking when root element is available |
wasmUrl | string | undefined | URL to WASM binary for analysis |
persist | boolean | false | Enable IndexedDB session persistence (requires contentId) |
onTick | (data: { activeTime: number; totalTime: number; tracker: WriteTrack }) => void | undefined | Callback fired every ~1s with active session time and tracker instance |
onReady | (tracker: WriteTrack) => void | undefined | Callback fired when tracker is ready (persistence loaded if applicable) |
Accessing Data
Section titled “Accessing Data”// Get the tracker instance (null until root element is available)const tracker = handle.tracker;
// Get typing dataconst data = tracker?.getData();
// Check tracking statusconst isTracking = handle.isTracking;Manual DOM Attachment
Section titled “Manual DOM Attachment”If you prefer not to use the integration, you can attach WriteTrack directly to the root element:
import WriteTrack from 'writetrack';import { createEditor } from 'lexical';
const editor = createEditor({ namespace: 'MyEditor', onError: console.error });const rootElement = document.getElementById('editor')!;editor.setRootElement(rootElement);
const tracker = new WriteTrack({ target: rootElement, license: 'your-license-key',});tracker.start();Cleanup
Section titled “Cleanup”Call destroy() to stop tracking and release resources:
handle.destroy();The integration also automatically cleans up if the root element is removed from the editor.
TypeScript
Section titled “TypeScript”import type { WriteTrackLexicalOptions, WriteTrackLexicalHandle,} from 'writetrack/lexical';