Skip to content
notectl is live on DevHunt's tool of the week contest. Vote for notectl on DevHunt

EditorState

EditorState is the immutable container for all editor state. Every change produces a new EditorState instance.

import { EditorState } from '@notectl/core';
const state = EditorState.create({
doc: myDocument, // Optional Document
schema: mySchema, // Optional Schema
selection: mySel, // Optional EditorSelection
});
Property Type Description
doc Document The document tree
selection EditorSelection Current cursor/selection (text or node)
schema Schema Active schema (node + mark types)
storedMarks readonly Mark[] | null Marks to apply on next input

getBlock(blockId: BlockId): BlockNode | undefined

Section titled “getBlock(blockId: BlockId): BlockNode | undefined”

Look up a block by its ID (O(1) via internal index).

Returns leaf-block IDs in depth-first document order.

getNodePath(nodeId: BlockId): BlockId[] | undefined

Section titled “getNodePath(nodeId: BlockId): BlockId[] | undefined”

Returns the path (array of block IDs) from root to the given node.

getParent(nodeId: BlockId): BlockNode | undefined

Section titled “getParent(nodeId: BlockId): BlockNode | undefined”

Returns the parent BlockNode of a node, or undefined for top-level blocks.

transaction(origin?: TransactionOrigin): TransactionBuilder

Section titled “transaction(origin?: TransactionOrigin): TransactionBuilder”

Creates a TransactionBuilder for this state. The origin defaults to 'api'.

const tr = state.transaction('command')
.insertText(blockId, offset, 'hello', [])
.addMark(blockId, 0, 5, { type: markType('bold') })
.build();

Applies a transaction to produce a new state. This is a pure function — the original state is unchanged.

toJSON(): { readonly doc: Document; readonly selection: EditorSelection }

Section titled “toJSON(): { readonly doc: Document; readonly selection: EditorSelection }”

Serializes the state (document and selection) to a JSON-compatible object.

static fromJSON(json: { doc: Document; selection: EditorSelection }, schema?: Schema): EditorState

Section titled “static fromJSON(json: { doc: Document; selection: EditorSelection }, schema?: Schema): EditorState”

Deserializes a state from a JSON object. Optionally accepts a schema.

const json = state.toJSON();
const restored = EditorState.fromJSON(json);

EditorState is deeply immutable:

const state1 = editor.getState();
const tr = state1.transaction('command')
.insertText(blockId, 0, 'hello', [])
.build();
const state2 = state1.apply(tr);
// state1 is unchanged
assert(state1 !== state2);
assert(state1.doc !== state2.doc);

This enables:

  • Undo/redo via state snapshots
  • Safe comparison between old and new states
  • Predictable plugin behavior — plugins always see consistent state

Manages undo/redo stacks with automatic transaction grouping.

import { HistoryManager } from '@notectl/core';
const history = new HistoryManager({ groupTimeoutMs: 500, maxDepth: 100 });
Option Type Default Description
groupTimeoutMs number 500 Time window for grouping consecutive input transactions
maxDepth number 100 Maximum number of undo groups to retain

Pushes a transaction onto the undo stack. Consecutive input transactions within groupTimeoutMs are grouped together — undoing the group reverts all of them at once.

history.push(transaction);

Records the mapping of an external transaction applied without push, such as a collaboration update that should not be undoable. Both undo and redo adjust their steps and restored selections through these external changes.

A non-empty mapping ends the current input group. The next input starts a new group, and subsequent rapid inputs can group normally. Empty mappings are a no-op and leave input grouping unchanged.

state = state.apply(externalTransaction);
history.recordIntervening(externalTransaction.mapping);

Undoes the last group. Returns a HistoryResult with the new state and the inverse transaction, or null if nothing to undo:

const result = history.undo(state);
if (result) {
// result.state — new EditorState after undo
// result.transaction — the inverse transaction that was applied
}

Redoes the last undone group. Returns a HistoryResult or null:

const result = history.redo(state);

Returns true if there are groups available to undo or redo:

if (history.canUndo()) { /* show undo button */ }
if (history.canRedo()) { /* show redo button */ }

Clears both undo and redo stacks:

history.clear();
interface HistoryResult {
readonly state: EditorState;
readonly transaction: Transaction;
}

The HistoryManager groups consecutive transactions of the same input type (e.g. typing characters) into a single undo group when they arrive within groupTimeoutMs of each other. This means pressing undo after typing “hello” undoes the entire word, not individual characters.

Groups are broken when:

  • The timeout between transactions exceeds groupTimeoutMs
  • The transaction origin changes (e.g. from 'input' to 'command')
  • A non-input transaction is pushed