Skip to content

Commands

Commands are pure functions that take an EditorState and return a Transaction (or null if the command cannot be applied). They never mutate state or touch the DOM directly.

import { insertTextCommand, toggleBold } from '@notectl/core';
const tr = insertTextCommand(state, 'Hello');
if (tr) editor.dispatch(tr);

Inserts text at the current cursor position. Replaces any selected text.

function insertTextCommand(
state: EditorState,
text: string,
origin?: 'input' | 'paste',
): Transaction

Inserts a hard line break (<br>) at the cursor position.

function insertHardBreakCommand(state: EditorState): Transaction | null

Splits the current block at the cursor, creating a new block below.

function splitBlockCommand(state: EditorState): Transaction | null

Merges the current block with the previous one (e.g. pressing Backspace at the start of a block).

function mergeBlockBackward(state: EditorState): Transaction | null

Merges the current block with the next one (e.g. pressing Delete at the end of a block).

function mergeBlockForward(state: EditorState): Transaction | null

Deletes the currently selected text, producing a collapsed cursor.

function deleteSelectionCommand(state: EditorState): Transaction | null

Selects all content in the document.

function selectAll(state: EditorState): Transaction

Toggles an inline mark on the current selection or stored marks.

function toggleMark(
state: EditorState,
markType: MarkTypeName,
features?: FeatureConfig,
): Transaction | null
function toggleBold(state: EditorState, features?: FeatureConfig): Transaction | null
function toggleItalic(state: EditorState, features?: FeatureConfig): Transaction | null
function toggleUnderline(state: EditorState, features?: FeatureConfig): Transaction | null

Returns true if the given mark is active at the current selection.

function isMarkActive(state: EditorState, markType: MarkTypeName): boolean

Controls which formatting features are available:

interface FeatureConfig {
readonly bold: boolean;
readonly italic: boolean;
readonly underline: boolean;
}

For marks that carry attributes (e.g. link with href, textColor with color).

Applies a mark with attributes to the current selection.

function applyAttributedMark(state: EditorState, mark: Mark): Transaction | null

Removes all instances of a mark type from the current selection.

function removeAttributedMark(
state: EditorState,
markTypeName: MarkTypeName,
): Transaction | null

isAttributedMarkActive(state, markTypeName)

Section titled “isAttributedMarkActive(state, markTypeName)”

Returns true if the given attributed mark type is active at the selection.

function isAttributedMarkActive<K extends keyof MarkAttrRegistry>(
state: EditorState,
markTypeName: K,
): boolean

getMarkAttrAtSelection(state, markTypeName, extractFn)

Section titled “getMarkAttrAtSelection(state, markTypeName, extractFn)”

Extracts a value from the mark attributes at the current selection.

function getMarkAttrAtSelection<K extends keyof MarkAttrRegistry, V>(
state: EditorState,
markTypeName: K,
extractFn: (mark: Mark & { readonly type: K; readonly attrs: MarkAttrRegistry[K] }) => V | null,
): V | null

Example:

const href = getMarkAttrAtSelection(state, markType('link'), (m) => m.attrs.href);

All delete commands return null when there is nothing to delete.

CommandDescription
deleteBackward(state)Delete one character backward (Backspace)
deleteForward(state)Delete one character forward (Delete)
deleteWordBackward(state)Deletes the word before the cursor
deleteWordForward(state)Deletes the word after the cursor
deleteSoftLineBackward(state)Deletes to the beginning of the soft line
deleteSoftLineForward(state)Deletes to the end of the soft line
function deleteBackward(state: EditorState): Transaction | null
function deleteForward(state: EditorState): Transaction | null
function deleteWordBackward(state: EditorState): Transaction | null
function deleteWordForward(state: EditorState): Transaction | null
function deleteSoftLineBackward(state: EditorState): Transaction | null
function deleteSoftLineForward(state: EditorState): Transaction | null

Commands for handling void/node-selected blocks.

Deletes the node-selected block.

function deleteNodeSelection(
state: EditorState,
sel: NodeSelection,
): Transaction | null
Section titled “navigateArrowIntoVoid(state, direction, isRtl?)”

Moves the cursor into a void block via arrow keys, creating a NodeSelection.

function navigateArrowIntoVoid(
state: EditorState,
direction: 'left' | 'right' | 'up' | 'down',
isRtl?: boolean,
): Transaction | null

Finds the first leaf block ID in a node tree (depth-first).

function findFirstLeafBlockId(node: BlockNode): BlockId

Finds the last leaf block ID in a node tree (depth-first).

function findLastLeafBlockId(node: BlockNode): BlockId

insertParagraphAfterNodeSelection(state, sel)

Section titled “insertParagraphAfterNodeSelection(state, sel)”

Inserts a new paragraph after the node-selected block and moves the cursor into it.

function insertParagraphAfterNodeSelection(
state: EditorState,
sel: NodeSelection,
): Transaction | null

insertTextAfterNodeSelection(state, sel, text, origin)

Section titled “insertTextAfterNodeSelection(state, sel, text, origin)”

Inserts text into a new paragraph after the node-selected block.

function insertTextAfterNodeSelection(
state: EditorState,
sel: NodeSelection,
text: string,
origin: 'input' | 'paste',
): Transaction

Commands for editing when the cursor is in a gap position (between void blocks).

function deleteBackwardAtGap(state: EditorState, sel: GapCursorSelection): Transaction | null
function deleteForwardAtGap(state: EditorState, sel: GapCursorSelection): Transaction | null

Inserts a new paragraph at the gap cursor position.

function insertParagraphAtGap(
state: EditorState,
sel: GapCursorSelection,
): Transaction | null

Inserts text into a new paragraph at the gap cursor position.

function insertTextAtGap(
state: EditorState,
sel: GapCursorSelection,
text: string,
origin: 'input' | 'paste',
): Transaction

Pure state-level cursor movement — no DOM access required.

function moveCharacterForward(state: EditorState): Transaction | null
function moveCharacterBackward(state: EditorState): Transaction | null
function moveToDocumentStart(state: EditorState): Transaction | null
function moveToDocumentEnd(state: EditorState): Transaction | null
function moveToBlockStart(state: EditorState): Transaction | null
function moveToBlockEnd(state: EditorState): Transaction | null
function extendCharacterForward(state: EditorState): Transaction | null
function extendCharacterBackward(state: EditorState): Transaction | null
function extendToBlockStart(state: EditorState): Transaction | null
function extendToBlockEnd(state: EditorState): Transaction | null
function extendToDocumentStart(state: EditorState): Transaction | null
function extendToDocumentEnd(state: EditorState): Transaction | null

DOM-aware cursor movement using the browser’s caret positioning. These require an HTMLElement container (the editor’s content element).

function viewMove(
container: HTMLElement,
state: EditorState,
direction: 'forward' | 'backward',
granularity: 'word' | 'lineboundary' | 'line',
): Transaction | null
function viewExtend(
container: HTMLElement,
state: EditorState,
direction: 'forward' | 'backward',
granularity: 'word' | 'lineboundary' | 'line',
): Transaction | null

All take (container: HTMLElement, state: EditorState) => Transaction | null:

Movement:

FunctionDescription
moveWordForwardMove cursor one word forward
moveWordBackwardMove cursor one word backward
moveToLineStartMove cursor to start of visual line
moveToLineEndMove cursor to end of visual line
moveLineUpMove cursor one line up
moveLineDownMove cursor one line down

Selection extension:

FunctionDescription
extendWordForwardExtend selection one word forward
extendWordBackwardExtend selection one word backward
extendToLineStartExtend selection to start of visual line
extendToLineEndExtend selection to end of visual line
extendLineUpExtend selection one line up
extendLineDownExtend selection one line down

forEachBlockInRange(state, range, callback)

Section titled “forEachBlockInRange(state, range, callback)”

Iterates over every block that overlaps with a selection range, providing the block ID and the from/to offsets within each block.

function forEachBlockInRange(
state: EditorState,
range: SelectionRange,
callback: (blockId: BlockId, from: number, to: number) => void,
): void

Utility functions for querying block properties.

Returns true if the block is a void node (no editable text).

Returns true if the block is isolating (selection cannot cross its boundary).

Returns true if the block is nested inside an isolating parent.

Returns true if two blocks share the same parent node.

canCrossBlockBoundary(state, fromBlockId, toBlockId)

Section titled “canCrossBlockBoundary(state, fromBlockId, toBlockId)”

Returns true if the cursor can cross the boundary between two blocks (both are non-isolating or share a parent).


Finds the next word boundary position after the given offset.

function findWordBoundaryForward(block: BlockNode, offset: number): number

Finds the previous word boundary position before the given offset.

function findWordBoundaryBackward(block: BlockNode, offset: number): number

  • Transaction — the Transaction type that commands produce
  • EditorState — the EditorState that commands consume
  • Selection — selection types used by movement commands
  • Input System — how keymaps dispatch to commands