Programmatic edits
Drive editor changes from application code with intents, commands, and runtime actions.
Programmatic edits enter through the same semantic pipeline as browser input:
engine.dispatch(intent)— direct semantic editengine.dispatchCommand(commandId, args?)— named action by id
Both routes produce an intent, compile a plan, lower a transaction, apply steps, normalize, commit, and record history when the transaction is meaningful. Treat engine.state as a committed snapshot; the DOM runtime renders the next snapshot after the engine commits.
Examples assume an engine from Quickstart. Browser UI examples also assume a mounted runtime.
Choose intent or command
| Use | When |
|---|---|
dispatch(intent) | The application already knows the semantic edit to request. |
dispatchCommand(id, args?) | The same action is shared by a toolbar, shortcut, menu, or plugin. |
runtime.invokeAction(id, args?) | Browser UI wants the DOM runtime's readonly and action-state checks. |
Insert text at the current selection
engine.dispatch({
kind: "typeText",
text: "Hello, SpineEditor",
});
A selected range is replaced by the inserted text.
Move the selection explicitly
import { pos, textSelection } from "@spine-editor/core";
engine.dispatch({
kind: "setSelection",
selection: textSelection(pos(0), pos(0)),
});
Selection changes are semantic state changes. In a mounted browser runtime, the runtime projects the committed selection back into DOM after render.
Insert a block at a known position
import {
branchNode,
fragment,
textNode,
type NodeTypeName,
} from "@spine-editor/core";
engine.dispatch({
kind: "insertFragment",
parentPath: [],
index: 1,
fragment: fragment([
branchNode("paragraph" as NodeTypeName, [
textNode("Inserted from app code"),
]),
]),
});
parentPath: [] is the document root. index: 1 inserts after the first root child.
Structural paths are semantic NodePath arrays instead of DOM indexes. They describe child indexes from the root, so [0, 2] means the third child of the first root child.
Replace the current selection with multi-line content
engine.dispatch({
kind: "paste",
payload: { kind: "text", text: "alpha\nbeta" },
});
For pre-shaped structural content, pass { kind: "fragment", fragment } instead.
Apply common command actions
import { CoreCommandIds } from "@spine-editor/core";
engine.dispatchCommand(CoreCommandIds.setBlockType, {
toType: "heading",
attrs: { level: 2 },
});
engine.dispatchCommand(CoreCommandIds.setBlockAlignment, "center");
engine.dispatchCommand(CoreCommandIds.setListType, "bullet");
engine.dispatchCommand(CoreCommandIds.adjustBlockIndent, 1);
Commands are useful when app code and UI controls should share one named behavior. They inspect the current semantic selection, produce an intent, and let the engine complete the rest of the pipeline.
Retag or format selected content
import type { MarkTypeName, NodeTypeName } from "@spine-editor/core";
engine.dispatch({
kind: "setSelectedBlockType",
toType: "heading" as NodeTypeName,
attrs: { level: 2 },
});
engine.dispatch({
kind: "toggleMark",
markType: "strong" as MarkTypeName,
});
Block retagging follows the current semantic selection. Inline marks apply to selected text or become stored marks for later typing.
Invoke built-in commands
import { CoreCommandIds } from "@spine-editor/core";
engine.dispatchCommand(CoreCommandIds.pasteText, "alpha\nbeta");
engine.dispatchCommand(CoreCommandIds.insertParagraphBreak);
engine.dispatchCommand(CoreCommandIds.undo);
dispatchCommand returns DispatchResult | null (null when no handler is registered).
Use DOM runtime actions from browser UI
function invokeToolbarAction(
actionId: string,
args?: Parameters<typeof runtime.invokeAction>[1],
) {
const action = runtime.getActions().find((item) => item.id === actionId);
if (action?.enabled !== true) return false;
return runtime.invokeAction(actionId, args);
}
invokeToolbarAction("inline.bold");
invokeToolbarAction("block.heading.2");
invokeToolbarAction("inline.textColor.set", "#1f4e79");
The runtime action catalog is the ergonomic browser-facing layer for toolbars and menus. It exposes labels, groups, active state, values, argument requirements, and enabled state from the current engine state.
Read the result
const result = engine.dispatch({ kind: "typeText", text: "!" });
if (result.applied) {
console.log(result.stateAfter);
console.log(result.transaction);
}
A no-op dispatch returns applied: false with stoppedAt: "noOp". A successful edit includes the transaction, mapping, normalized state, and optional history entry.
Respect the layer boundary
- Application code may dispatch intents and commands.
- Planner rules may patch plan operations.
- Normalizer rules may repair the applied document before commit.
- DOM code should route edits through runtime actions, commands, or intents.
- Browser evidence should become semantic input before the engine commits.