Transaction
Transactions represent atomic state changes. They contain a sequence of Steps that transform the document.
Creating Transactions
Section titled “Creating Transactions”Use the TransactionBuilder from an EditorState:
const state = editor.getState();const tr = state.transaction('command') .insertText(blockId, offset, 'hello', []) .build();
editor.dispatch(tr);The origin parameter is optional and defaults to 'api':
// Equivalent — origin defaults to 'api'const tr = state.transaction() .setBlockType(blockId, nodeType('heading'), { level: 1 }) .build();TransactionBuilder Methods
Section titled “TransactionBuilder Methods”Text Operations
Section titled “Text Operations”// Insert text with marks at a positionbuilder.insertText(blockId, offset, text, marks, segments?)
// Delete text with explicit undo databuilder.deleteText(blockId, from, to, deletedText, deletedMarks, deletedSegments?)
// Delete text — auto-derives undo data from working documentbuilder.deleteTextAt(blockId, from, to)Mark Operations
Section titled “Mark Operations”builder.addMark(blockId, from, to, mark)builder.removeMark(blockId, from, to, mark)Block Operations
Section titled “Block Operations”// Split a block at offset, creating a new block with the given IDbuilder.splitBlock(blockId, offset, newBlockId)
// Merge two blocks with explicit target lengthbuilder.mergeBlocks(targetBlockId, sourceBlockId, targetLengthBefore)
// Merge two blocks — auto-derives target length from working documentbuilder.mergeBlocksAt(targetBlockId, sourceBlockId)
// Change a block's type and optionally its attributesbuilder.setBlockType(blockId, nodeType, attrs?)Merging requires both blocks to exist under the same immediate parent. Builders
created with state.transaction() check this against their working document,
including all preceding steps, and throw before recording an invalid merge or
position map. Container boundaries must be handled by the calling command.
Manual TransactionBuilder instances created without a document cannot check
this precondition; callers of mergeBlocks remain responsible for it.
mergeBlocksAt always requires a document.
Structural Operations (Nested Documents)
Section titled “Structural Operations (Nested Documents)”// Insert a child node at index under the parent pathbuilder.insertNode(parentPath, index, node)
// Remove a child node at index under the parent pathbuilder.removeNode(parentPath, index)
// Set attributes on a node at the given pathbuilder.setNodeAttr(path, attrs)InlineNode Operations
Section titled “InlineNode Operations”// Insert an InlineNode at offset within a blockbuilder.insertInlineNode(blockId, offset, node)
// Remove an InlineNode at offset (auto-derives from working document)builder.removeInlineNode(blockId, offset)
// Set attributes on an InlineNode at offset// attrs: Readonly<Record<string, string | number | boolean>>builder.setInlineNodeAttr(blockId, offset, attrs)Selection
Section titled “Selection”builder.setSelection(selection)builder.setNodeSelection(nodeId, path)builder.setStoredMarks(marks, previousMarks)Readonly
Section titled “Readonly”// Marks this transaction as allowed in readonly mode. Returns `this` for method chaining.builder.readonlyAllowed()const transaction = builder.build();Transaction Origins
Section titled “Transaction Origins”Each transaction has an origin that describes where it came from:
type TransactionOrigin = 'input' | 'paste' | 'command' | 'history' | 'api';| Origin | Description |
|---|---|
input |
User typing / input events |
paste |
Paste operations |
command |
Programmatic command execution |
history |
Undo/redo operations |
api |
External API calls (setContentHTML, setJSON) — this is the default |
Transaction Metadata
Section titled “Transaction Metadata”Each transaction carries a metadata object:
interface TransactionMetadata { readonly origin: TransactionOrigin; readonly timestamp: number; readonly historyDirection?: 'undo' | 'redo'; readonly readonlyAllowed?: boolean;}| Property | Type | Description |
|---|---|---|
origin |
TransactionOrigin |
Where the transaction came from (see origins above) |
timestamp |
number |
When the transaction was created (Date.now()) |
historyDirection |
'undo' | 'redo' |
Set when the transaction comes from undo/redo |
readonlyAllowed |
boolean |
If true, the transaction bypasses the readonly guard |
Step Types
Section titled “Step Types”Every step is invertible for undo support:
| Step | Description |
|---|---|
InsertTextStep |
Insert text at position |
DeleteTextStep |
Delete text range |
SplitBlockStep |
Split block at offset |
MergeBlocksStep |
Merge two adjacent blocks |
AddMarkStep |
Add inline mark to range |
RemoveMarkStep |
Remove inline mark from range |
SetBlockTypeStep |
Change block type |
SetStoredMarksStep |
Set stored marks (for mark continuity) — internal, not exported |
InsertNodeStep |
Insert a block node into a parent |
RemoveNodeStep |
Remove a block node from a parent |
SetNodeAttrStep |
Change a node’s attributes |
InsertInlineNodeStep |
Insert an inline node at offset |
RemoveInlineNodeStep |
Remove an inline node at offset |
SetInlineNodeAttrStep |
Change an inline node’s attributes |
Inverting Transactions
Section titled “Inverting Transactions”Every transaction can be inverted for undo:
import { invertTransaction } from '@notectl/core';
const inverse = invertTransaction(transaction);// Applying inverse undoes the original transactionIndividual steps can also be inverted:
import { invertStep } from '@notectl/core';
const invertedStep = invertStep(step);Middleware
Section titled “Middleware”Transactions pass through a middleware chain before being applied:
context.registerMiddleware((tr, state, next) => { // Inspect or modify the transaction console.log(`${tr.steps.length} steps`); next(tr); // Call next to continue, or skip to cancel}, { priority: 100 });Step Application
Section titled “Step Application”Pure functions for applying and inverting individual steps.
applyStep(doc, step)
Section titled “applyStep(doc, step)”Applies a single step to a document, returning a new Document:
import { applyStep } from '@notectl/core';
const newDoc: Document = applyStep(doc, step);This is the low-level primitive used by EditorState.apply(). It handles all step types: insertText, deleteText, splitBlock, mergeBlocks, addMark, removeMark, setBlockType, setStoredMarks, insertNode, removeNode, setNodeAttr, insertInlineNode, removeInlineNode, and setInlineNodeAttr.
invertStep(step)
Section titled “invertStep(step)”Returns a step that undoes the given step:
import { invertStep } from '@notectl/core';
const undo = invertStep(step);// applyStep(applyStep(doc, step), undo) ≈ docinvertTransaction(tr)
Section titled “invertTransaction(tr)”Returns a transaction whose steps undo all steps of the original, in reverse order:
import { invertTransaction } from '@notectl/core';
const undo = invertTransaction(tr);Readonly Guards
Section titled “Readonly Guards”Pure functions to determine whether a transaction is allowed in readonly mode.
isSelectionOnlyTransaction(tr)
Section titled “isSelectionOnlyTransaction(tr)”Returns true if the transaction contains no document-mutating steps (i.e. all steps are setStoredMarks, or there are no steps at all):
import { isSelectionOnlyTransaction } from '@notectl/core';
if (isSelectionOnlyTransaction(tr)) { // Safe to apply in readonly mode}isAllowedInReadonly(tr)
Section titled “isAllowedInReadonly(tr)”Returns true if the transaction may proceed in readonly mode — either it has metadata.readonlyAllowed set, or it is selection-only:
import { isAllowedInReadonly } from '@notectl/core';
const allowed: boolean = isAllowedInReadonly(tr);Selection Transaction Helpers
Section titled “Selection Transaction Helpers”Convenience functions for building selection-only transactions.
moveTx(state, blockId, offset)
Section titled “moveTx(state, blockId, offset)”Builds a collapsed-cursor transaction and clears stored marks:
import { moveTx } from '@notectl/core';
const tr = moveTx(state, blockId('b1'), 5);extendTx(state, anchorBlockId, anchorOffset, headBlockId, headOffset)
Section titled “extendTx(state, anchorBlockId, anchorOffset, headBlockId, headOffset)”Builds a range-selection transaction and clears stored marks:
import { extendTx } from '@notectl/core';
const tr = extendTx(state, blockId('b1'), 0, blockId('b1'), 10);nodeSelTx(state, targetId)
Section titled “nodeSelTx(state, targetId)”Builds a node-selection transaction and clears stored marks:
import { nodeSelTx } from '@notectl/core';
const tr = nodeSelTx(state, blockId('img-1'));Block Boundary Movement
Section titled “Block Boundary Movement”State-level functions for moving the cursor to block boundaries.
moveToBlockStart(state)
Section titled “moveToBlockStart(state)”Moves the cursor to offset 0 of the current block. Returns null if already at the start or if the selection is a node/gap cursor:
import { moveToBlockStart } from '@notectl/core';
const tr = moveToBlockStart(state);moveToBlockEnd(state)
Section titled “moveToBlockEnd(state)”Moves the cursor to the end of the current block. Returns null if already at the end:
import { moveToBlockEnd } from '@notectl/core';
const tr = moveToBlockEnd(state);