Editor API
Complete reference for the Editor class — methods, properties, and lifecycle.
The Editor class is the central orchestrator of every Scrivr instance. It owns the document
state, layout engine, and extension system. Consumers interact with it directly (headless) or
via the useScrivrEditor React hook.
import { Editor, StarterKit } from '@scrivr/core';
const editor = new Editor({
extensions: [StarterKit],
onChange: (state) => console.log('changed', state),
});
editor.mount(containerElement);Constructor
new Editor(options: EditorOptions)Accepts an EditorOptions object. Builds the ProseMirror schema
from extensions, runs the initial layout, and starts the cursor blink timer.
Properties
schema
get schema(): SchemaThe merged ProseMirror schema built from all active extensions. Use this instead of importing a schema directly — it reflects exactly which node and mark types are loaded.
pageConfig
readonly pageConfig: PageConfigThe active page dimensions. Set once at construction from EditorOptions.pageConfig.
See PageConfig.
charMap
get charMap(): CharacterMapThe spatial index mapping doc positions to (x, y, page) canvas coordinates. Owned by the
layout engine and updated after every layout pass.
cursorManager
readonly cursorManager: CursorManagerManages the cursor blink timer. Read cursorManager.isVisible to know whether to draw or clear
the cursor in a custom overlay renderer.
toolbarItems
readonly toolbarItems: ToolbarItemSpec[]Toolbar item specs contributed by all extensions, in registration order. Data-only — no framework dependency. Use this to build a data-driven toolbar without hard-coding button lists.
commands
readonly commands: Record<string, (...args: unknown[]) => void>All bound extension commands. Closures that always read the latest state at call time.
editor.commands.toggleBold();
editor.commands.setHeading2();
editor.commands.undo();isFocused
get isFocused(): booleanWhether the editor's hidden textarea currently has focus.
loadingState
get loadingState(): 'syncing' | 'rendering' | 'ready'Current loading phase:
| Value | Meaning |
|---|---|
'syncing' | Waiting for collaborative sync (only when startReady: false). |
'rendering' | Layout engine is running for the first time. |
'ready' | Document is fully laid out and visible. |
cursorPage
get cursorPage(): numberThe 1-based page number of the current cursor position.
Lifecycle
mount(container)
mount(container: HTMLElement): voidAttaches the keyboard, IME, and paste event listeners to the given container. Called
automatically by <Scrivr />.
unmount()
unmount(): voidDetaches event listeners. The editor state is preserved — the editor can be re-mounted.
Called automatically by <Scrivr /> on unmount.
destroy()
destroy(): voidFull teardown: runs extension cleanup, stops the cursor blink timer, and calls unmount().
After destroy() the instance must not be used.
focus()
focus(): voidProgrammatically focuses the editor.
setReady(ready)
setReady(ready: boolean): voidUnblocks layout flushing after a collaborative sync. Managed automatically by the
Collaboration extension — you do not need to call this manually.
State
getState()
getState(): EditorStateReturns the current ProseMirror EditorState. States are immutable — reference equality
detects changes.
getSnapshot()
getSnapshot(): EditorStateReturns the current state. Used as the getSnapshot argument for React's
useSyncExternalStore.
isActive(name, attrs?)
isActive(name: string, attrs?: Record<string, unknown>): booleanReturns true if the named mark or block type is active at the current cursor or selection.
editor.isActive('bold');
editor.isActive('heading', { level: 1 });getSelectionSnapshot()
getSelectionSnapshot(): SelectionSnapshotReturns a lightweight snapshot with everything a toolbar or floating menu needs — active marks,
block type, cursor position — without requiring ProseMirror imports.
See SelectionSnapshot.
getActiveMarks()
getActiveMarks(): string[]Names of marks active at the cursor. For a range selection, only marks present on every character in the range are returned.
getActiveMarkAttrs()
getActiveMarkAttrs(): Record<string, Record<string, unknown>>Attributes of each active mark, keyed by mark name.
const attrs = editor.getActiveMarkAttrs();
attrs.color?.color; // '#dc2626'
attrs.font_size?.size; // 18getBlockInfo()
getBlockInfo(): { blockType: string; blockAttrs: Record<string, unknown> }Type name and attributes of the block node containing the cursor.
getMarkdown()
getMarkdown(): stringSerializes the full document to Markdown using extension-contributed serializer rules.
getMarkdownSerializer()
getMarkdownSerializer(): MarkdownSerializerReturns a configured MarkdownSerializer built from all extension rules. Used internally by
getMarkdown() and exportToMarkdown() from @scrivr/export.
Layout
layout
get layout(): DocumentLayoutThe current DocumentLayout. Calls ensureLayout() internally so the result always reflects
the latest state.
ensureLayout()
ensureLayout(): voidForces the layout engine to run synchronously if the current layout is stale. Normally
triggered implicitly by editor.layout and movement methods.
ensurePagePopulated(pageNumber)
ensurePagePopulated(pageNumber: number): voidEnsures the given page (1-based) has been laid out and its character positions are indexed.
Viewport & Position
getViewportRect(from, to)
getViewportRect(from: number, to: number): DOMRect | nullConverts a doc position range to a viewport-relative DOMRect. Returns null if the
positions are not in the character map or no rendering adapter is mounted.
const rect = editor.getViewportRect(snap.from, snap.to);
if (rect) {
menuEl.style.top = `${rect.bottom + 8}px`;
menuEl.style.left = `${rect.left}px`;
}getNodeViewportRect(docPos)
getNodeViewportRect(docPos: number): DOMRect | nullConverts a single node's doc position to a viewport-relative DOMRect covering the full
visual bounds of that node. Used by createImageMenu to position the image toolbar below
the selected image.
scrollCursorIntoView()
scrollCursorIntoView(): voidScrolls the viewport so the current cursor position is visible.
Cursor & Selection
moveCursorTo(pos)
moveCursorTo(pos: number): voidCollapses the cursor to a given doc position.
setSelection(anchor, head)
setSelection(anchor: number, head: number): voidSets an explicit selection range. Both values are clamped to valid doc bounds.
selectNode(docPos)
selectNode(docPos: number): voidCreates a NodeSelection at the given doc position. Falls back to moveCursorTo if the node
is not selectable. Use this to programmatically select images and other inline objects.
setNodeAttrs(docPos, attrs)
setNodeAttrs(docPos: number, attrs: Record<string, unknown>): voidMerges attributes into the node at docPos. Use this to update image dimensions, alignment,
or any other node-level attribute without writing a custom command.
moveLeft / moveRight / moveUp / moveDown
moveLeft(extend?: boolean): void
moveRight(extend?: boolean): void
moveUp(extend?: boolean): void
moveDown(extend?: boolean): voidArrow-key cursor movement. Pass extend: true to grow the selection (Shift+arrow).
Subscription
subscribe(listener)
subscribe(listener: () => void): () => voidRegisters a listener called on every editor notification: state changes, focus changes, and cursor blink ticks. Returns an unsubscribe function.
const unsubscribe = editor.subscribe(() => forceUpdate());redraw()
redraw(): voidTriggers a repaint without a document or selection change. Use when external state (e.g. collaborative awareness data) changes and the overlay needs refreshing.
Extension Points
addOverlayRenderHandler(handler)
addOverlayRenderHandler(handler: OverlayRenderHandler): () => voidRegisters a canvas draw function on the overlay layer. Called after the built-in cursor and selection are painted. Returns an unregister function.
const unregister = editor.addOverlayRenderHandler(
(ctx, pageNumber, pageConfig, charMap) => {
// draw custom overlay for this page
}
);setPageElementLookup(fn)
setPageElementLookup(fn: ((page: number) => HTMLElement | null) | null): voidRegisters the function used to resolve a 1-based page number to its DOM element. Called
automatically by <Scrivr />. Only needed when building a custom rendering adapter.
_applyTransaction(tr)
_applyTransaction(tr: Transaction): voidApplies a ProseMirror transaction directly, bypassing input positioning. Used by extensions and the AI toolkit to dispatch transactions that originate outside normal user input (e.g. Yjs remote sync, ghost text updates).
Prefixed with _ to signal it is infrastructure-facing. Prefer editor.commands.* for
document mutations from application code.