edytor
Every name edytor exports, with its declaration and documentation (generated from the published types).
Every name edytor exports, generated from the package’s published type declarations, with the documentation its JSDoc carries. The guides explain how the pieces fit; this page is the exhaustive list. 381 names.
ActorId
The durable per-block attribution record a replica sees.
export type ActorId = string;
ActorProfile
Replicated actor profile record payload (u/ values).
export type ActorProfile = {
name?: string;
color?: string;
};
AfterOperationPayload
What onAfterOperation receives: a ChangePayload without
prevent (the operation already ran). The Omit distributes over the
union, so operation still narrows payload (and text).
export type AfterOperationPayload = ChangePayload extends infer C ? C extends unknown ? Omit<C, 'prevent'> : never : never;
AnchorAffinity
Selection-endpoint affinity — which side of a position the anchor binds
to (see T.atomAnchorAt):
'left'— bound to the atom BEFORE the position; resolves right after it, so a concurrent insert exactly at the position lands to the anchor’s right. Used for carets and range ENDS.'right'— bound to the atom AT the position; resolves right before it, so a concurrent insert lands to the anchor’s left — outside a range starting here. Used for range STARTS.
export type AnchorAffinity = 'left' | 'right';
Announcer
export declare class Announcer {
#private;
private edytor;
message: string;
serial: number;
constructor(edytor: Edytor);
say: (message: string) => void;
what: (blocks: readonly Block[]) => string;
moved: (request: BlockMoveRequest, moved: readonly Block[]) => void;
deleted: (what: string) => void;
}
message: The last announcement.serial: Bumped by each announcement: the live region renders it as a new node.say: Announcemessage(politely: after what the screen reader is saying).what:blocks(one or several) as words: a kind’s label for one, a count for several.moved:request’s blocks moved (moveBlocksanswered them).deleted: Blocks were deleted:whatnames them, read before they went.
attachDocument
See DocumentBinding.attachDocument.
declare const attachDocument: (doc: YDoc, options?: DocumentOptions) => EdytorDocument;
AttributionActor
The stable application-level author identity (mirrors DocumentActor).
export type AttributionActor = {
id: string;
name?: string;
color?: string;
};
Awareness
Simple shared state protocol for non-persistent data (cursor, username,
status…). Semantics are verbatim upstream: clock-ordered LWW per client,
remote null states mark peers offline, local state is re-published when
outdated, and remote states expire after outdatedTimeout.
export declare class Awareness extends IsolatedObservable<AwarenessEvents> {
#private;
doc: AwarenessDoc;
clientID: number;
states: AwarenessStates;
meta: Map<number, MetaClientState>;
private _destroyed;
constructor(doc: AwarenessDoc);
destroy(): void;
getLocalState(): Record<string, unknown> | null;
setLocalState(state: Record<string, unknown> | null): void;
setLocalStateField(field: string, value: unknown): void;
getStates(): AwarenessStates;
}
states: Maps from client id to client state
AwarenessLike
Structural awareness surface used by publishPresence — the real
Awareness satisfies it; tests may substitute stubs.
export type AwarenessLike = {
getLocalState: () => Record<string, unknown> | null;
setLocalState: (state: Record<string, unknown> | null) => void;
};
AwarenessStates
export type AwarenessStates = Map<number, Record<string, unknown>>;
AwarenessUpdate
export type AwarenessUpdate = {
added: number[];
updated: number[];
removed: number[];
};
Block
An id-only, read-only block handle: every getter reads the document index
(transaction-aware). Writes are the view’s operations, with the block’s id
(edytor.splitBlock(block.id, …)). The view keeps one handle per id
(edytor.idToBlock); node is the element the core renders for it.
A reactive reader (a template, a $derived, an $effect) of a getter
depends on the cells the answer comes from (this block’s, its parent’s,
the root’s list), so it re-runs when a commit changes them, as data
does; a read anywhere else is a plain read of the document.
export declare class Block {
#private;
readonly: boolean;
readonly edytor: Edytor;
readonly id: string;
node?: HTMLElement;
constructor(edytor: Edytor, id: string);
get isRoot(): boolean;
get parent(): Block | undefined;
get children(): Block[];
get content(): (Text | InlineBlock)[];
get type(): string;
get data(): Readonly<Record<string, any>>;
get definition(): BlockDefinition;
get selected(): boolean;
get focused(): boolean;
get movable(): boolean;
get convertible(): boolean;
get index(): number;
get depth(): number;
get path(): number[];
get isInTree(): boolean;
get nextBlock(): Block | null;
get previousBlock(): Block | null;
get closestPreviousBlock(): Block | null;
get closestNextBlock(): Block | null;
get hasChildren(): boolean;
get hasContent(): boolean;
get isEmpty(): boolean;
get isNested(): boolean;
get value(): JSONBlock;
isChildOf(block: Block): boolean;
get rendersContent(): boolean;
get isContainer(): boolean;
get isListItem(): boolean;
get list(): Block | undefined;
get firstText(): Text | undefined;
get lastText(): Text | undefined;
void: (node: HTMLElement) => void;
textAtOffset: (offset: number) => {
text: Text;
offset: number;
} | null;
}
parent: The display parent (the root for a top-level block); none for the root or a dead block.content: Text segments and inline atoms, alternating, starting and ending with a text.data: The block’sdata, read-your-writes; a reactive reader depends on the block’s cell.movable: May this block move at all:canPlacewithout a destination (drag handles).convertible: May text-level structural commands (convert, markdown shortcut, slash menu) apply: the block is movable — or the virtual paragraph of an emptied document, which a conversion creates with its kind — has no role of its own and renders its own content — a container (a list) is never converted, its items are.isInTree: The block is visible in the document (a dead handle answers false).hasContent: It shows content: text or an inline atom (a block holding only a mention is not empty).isEmpty: It holds nothing: no content and no children (the emptiness Turn into reads,holdsNothing).value: This block’s JSON — the document’s one serializer (facade.blockJSON).rendersContent: Whether this kind renders its own content slot — the adopted capability.isContainer: A container: it shows only its children (a list), neither void nor an island — the document’s container rule.isListItem: An item of a list: its parent is a container whose items are a kind of their own (alist-itemin anunordered-list), not the document’s default — a column of paragraphs is no list.list: The list this block shows in: its container when it is an item (isListItem), or — an item outdented out of a nested list into the item holding it — the list of the items of its kind it sits under.firstText: The first text of this block’s own content — none for a kind that does not render its content (a list container, a divider): its slot is never displayed, so no caret or endpoint may land there.lastText: The last text of this block’s own content (seefirstText).void: Mark an element inside the block’s markup as non-editable chrome (a header, a caption bar).textAtOffset: The text segment that displays block offsetoffset, and the offset in it — the one offset → segment mapper over the committed parts (a boundary before an atom reads the text before it; past the end, the last text’s end).
BLOCK_ACTIVATE_EVENT
The DOM event a grip click dispatches on the editor (the one way to
answer it: the block menu’s, or your own). A menu that takes the keyboard answers it with preventDefault()
(the block menu does); unanswered, the editor takes the focus, so the keys
act on the selected block (Backspace deletes it, Shift+arrows extend it).
declare const BLOCK_ACTIVATE_EVENT = "edytor-block-activate";
BLOCK_ADD_EVENT
The DOM event the + dispatches on the editor. A menu answers it with
preventDefault() (the slash menu does), adds nothing until a row is
picked, then calls insert(then); unanswered, the + inserts at once.
declare const BLOCK_ADD_EVENT = "edytor-block-add";
BLOCK_COLORS
Notion’s palette: the colour names a block may hold, in menu order (“Default” is none).
declare const BLOCK_COLORS: readonly [
"gray",
"brown",
"orange",
"yellow",
"green",
"blue",
"purple",
"pink",
"red"
];
BlockActivation
export type BlockActivation = {
block: Block;
anchor: HTMLElement;
};
BlockAddition
export type BlockAddition = {
block: Block;
anchor: HTMLElement | null;
insert: (then?: () => unknown) => unknown;
};
anchor: The+clicked, else the block’s element.insert: Add an empty block of the parent’s default kind belowblock(above with Alt; an empty block of that kind is reused), put the caret in it and runthen, as one undo step.
BlockAttribution
export type BlockAttribution = {
createdBy?: ActorId;
contributors: ReadonlySet<ActorId>;
lastChangedBy?: ActorId;
};
createdBy: The actor that created the block — assigned at creation, never rewritten.contributors: Union set of actors that committed an edit addressing this block id, plus the pre-split snapshot a split/merge copies to siblings — block-identity LINEAGE, add-only.lastChangedBy: The actor whose state change currently wins the LWW resolution.
BlockColorField
A block’s colour fields: its text colour and its background.
export type BlockColorField = 'color' | 'background';
BlockDefinition
Defines the structure and behavior of a block type.
export type BlockDefinition = {
snippet?: Snippet<[
BlockSnippetPayload<any>
]>;
element?: BlockElement | ((data: Record<string, any>, id?: string) => BlockElement);
contentElement?: BlockElement | ((data: Record<string, any>, id?: string) => BlockElement);
viewState?: string[];
void?: boolean;
island?: boolean;
lines?: boolean;
layout?: boolean;
table?: boolean;
menu?: (block: Block) => KindMenuAction[];
atomic?: readonly (string | readonly string[])[];
rendersContent?: boolean;
upload?: KindUpload;
continues?: boolean;
container?: boolean;
body?: boolean;
defaultChild?: string;
itemKind?: string;
presets?: KindPreset[];
empty?: Pick<JSONBlock, 'content' | 'children'>;
html?: string | ((block: JSONBlock, content: string, children: string) => string);
plain?: (block: JSONBlock, content: string, children: string) => string;
parse?: (element: HTMLElement) => Record<string, SerializableContent> | undefined;
transformText?: TextTransform;
onFocus?: (payload: {
block: Block;
}) => void;
onBlur?: (payload: {
block: Block;
}) => void;
onSelect?: (payload: {
block: Block;
}) => void;
onDeselect?: (payload: {
block: Block;
}) => void;
normalizeContent?: (payload: {
block: Block;
}) => (() => void) | void;
normalizeChildren?: (payload: {
block: Block;
}) => (() => void) | void;
};
snippet: The markup inside the block element; a kind whose element takes no content (hr) has none.element: The element the core renders for the block: a tag, or tag and attributes, possibly from the block’s data and id (the second argument: view state the kind keeps per block, such as a column’s width while a resize drags; reactive state it reads re-renders the element; absent when HTML import reads a kind’s tag for data alone).contentElement: The element the core wraps around the block’s own text (content()), possibly from the block’s data and id: a heading’sh1–h3, a quote’sblockquote.viewState: Attributes of the block element the browser or the user own (openon adetails): declared view state, never inverted.void: A void kind (an image, a divider): one unit the editor never edits as text and never merges.island: An island kind (a code block, a table): editable inside, structurally sealed.lines: An island of lines (the code block): each direct child shows as this kind’sdefaultChildand holds no children, even when an undo or a peer’s edit puts another kind or a nested block there.layout: A layout (the columns plugin’scolumns): itsdefaultChildis its item kind (a column, a container that holds any block), shown side by side.table: A table (the table plugin’stable): itsdefaultChildis its row kind, a container whose owndefaultChildis the cell kind (a text island).menu: Rows the block menu adds for a block of this kind, when it is opened on that block alone (a table’s Header row switch), after Turn into and Color.atomic: Data paths written as one value (data.atomic): a top-level key, or an array of keys for a nested one.rendersContent: Whether the snippet renders the block’s own content (content()).upload: The files the kind takes (the image’simage/*, the file block’s any): with the uploads plugin listed, an empty block’s Upload sends one and a pasted or dropped file becomes a block of this kind (media.files).continues: A list-like kind (Notion’s bullets, numbers, to-dos, toggles): Enter at the start or end of a non-empty block opens another block of this kind (with its first preset’s data), and Enter in an empty one ends the run — it outdents under a parent of a continuing kind, else turns into the parent’s default child in place (at the top level, in a callout).container: A container kind (Notion’s toggles, callouts, quotes): its content is a header over its children.body: A container whose children are a body that shows even while it is empty (Notion’s callout: a title over its content): Enter at the end of its header opens its first child, as an open toggle’s does, and a paste there leads that body.defaultChild: The type a new child of this block takes by default (Enter inside a child, a split, an island merged out into it).itemKind: A list container’s flat counterpart (numbered-list-itemfor anordered-list): its items show as this kind, so the menus name them by it and turning one into it keeps it in the list.presets: Catalogue rows: one per way to create this kind — the slash menu, markdown shortcuts and block menus are generated from them (edytor.kinds).empty: The content and children a conversion into this kind creates (a void has none; an island starts with a first child).html: Clipboard HTML form: a tag wrapping content then children, or a function of the block and its serialized content and children.plain: Clipboard plain-text form; default content then children, one per line.parse: HTML import: the block’s data whenelementis this kind, elseundefined.transformText: Transform text content within the blockonFocus: Called when the block receives focusonBlur: Called when the block loses focusonSelect: Called when the block is selectedonDeselect: Called when the block is deselectednormalizeContent: Normalize block content Called at the end of the transaction of each operation that wrote the block’s content: the block is an id-only handle whose reads show the operation’s writes.normalizeChildren: Normalize block children Called at the end of the transaction of each operation that changed the block’s children: the block is an id-only handle whose reads show the operation’s writes.
BlockElement
The block element a kind declares: a tag, or a tag and attributes.
export type BlockElement = string | {
tag: string;
attributes?: Record<string, string | undefined>;
};
BlockHandleController
export declare class BlockHandleController {
#private;
private edytor;
private options;
private readonly owner;
private readonly targets;
private readonly registered;
dragging: string | null;
private scrolling;
private group;
private readonly indicator;
private held;
private handle;
private gripped;
constructor(edytor: Edytor, options?: BlockHandleControllerOptions);
get readonly(): boolean;
get draggable(): boolean;
labelOf: (block: Block) => string;
opened: (block: Block, control: PopupOpener["control"]) => Popup | undefined;
grip: (block: Block) => Attachment<HTMLElement>;
plus: (block: Block) => Attachment<HTMLElement>;
addsColumn(block: Block): boolean;
addBlock(block: Block, alt?: boolean, anchor?: HTMLElement | null): void;
registerHandle(element: HTMLElement, block: Block): () => void;
addDropTarget(node: HTMLElement, target: Block): () => void;
private gutter;
private margin;
private halves;
private pick;
private reorder;
private moving;
private covered;
dragBlocks(source: Block): Block[];
private select;
private fits;
private rowAt;
private get layouts();
private besideOf;
private layoutOf;
private beside;
private inside;
private shown;
private emptied;
private zones;
private placement;
private carrier;
}
dragging: The block whose handle is the source of the drag in progress (its handle stays mounted).labelOf: The block’s kind as people read it (kindLabel: “Heading 1”), its handle’s name; reactive through the block’s cell (a Turn into renames it).opened: The popup the+or the grip ofblockopened, while it is open (edytor.popups).grip: The block’s grip ({@attach handle.grip}): dragging (when draggable), a click that selects the block and opens its menu, the Alt+arrow moves (aria-keyshortcuts), and the ARIA of the menu it opened.plus: The block’s+({@attach handle.plus}): a click offers what to add below (Alt+click above, or a column beside:addBlock) under it; a press keeps the editor’s focus; the ARIA of the menu it opened.addsColumn: Whether the+’s Alt+click onblockadds a column right of its column (a block directly in a layout’s column, as Notion) rather than a block above it.addBlock: The+: offers the new block to a menu (BLOCK_ADD_EVENT), which adds it once the user picks what to insert; with none answering, adds it now (BlockAddition.insert).addDropTarget: A candidate drop target; registered at once when our drag is in progress.dragBlocks: The blocks a drag (or an Alt+arrow) fromsource’s handle moves (Notion): the outermost of those the selection covers (covered), in document order — a block’s children and a closed toggle’s hidden body travel with it; elsesourcealone.
BlockHandleSnippetPayload
What a handle snippet receives.
export type BlockHandleSnippetPayload = {
block: Block;
label: string;
grip: Attachment<HTMLElement>;
plus: Attachment<HTMLElement>;
add: (alt?: boolean, anchor?: HTMLElement | null) => void;
beside: boolean;
readonly: boolean;
draggable: boolean;
};
label: The block’s kind as people read it (“Heading 1”, its preset’s label): name your controls with it.grip: The grip’s attachment ({@attach handle.grip}): the drag source, a click that opens the block’s menu, the Alt+arrow moves (aria-keyshortcuts), and the ARIA of the menu it opened (aria-expanded,aria-controls,aria-haspopup).plus: The+’s attachment ({@attach handle.plus}): a click callsadd(Alt+click withtrue) with the+as the anchor, a press keeps the editor’s focus, and the ARIA of the menu it opened.add: The+: the slash menu offers what to add below; withtrue(Alt+click) above, or, for a block directly in a layout’s column, in a new column right of its column.beside: Alt+click on the+adds a column right of the block’s (a block directly in a layout’s column): name your+with it.
BlockHandlesOptions
export type BlockHandlesOptions = {
draggable?: boolean;
handle?: Snippet<[
BlockHandleSnippetPayload
]>;
};
draggable: Keep the handle and its keyboard actions, but omit pointer dragging and drop targets.handle: The+and ⋮⋮ beside each block, and their words (without it, no handle shows and no block drags); placement and hover are the plugin’s.
BlockId
Logical block identifier — caller-assigned, immutable per block.
export type BlockId = string;
BlockMenuAction
export type BlockMenuAction = {
id: string;
label: string;
icon: string;
hint?: string;
danger?: boolean;
checked?: boolean;
submenu?: 'turn' | 'color';
isEnabled?: () => boolean;
run?: () => unknown;
};
checked: A switch’s state (a kind’s row,BlockDefinition.menu): the row is amenuitemcheckbox.submenu: Opens a flyout instead of running: the kinds (turn) or the colours (color).
BlockMenuColor
A row of the Color flyout: a text colour or a background, value null for the default.
export type BlockMenuColor = {
id: string;
label: string;
field: BlockColorField;
value: (typeof BLOCK_COLORS)[number] | null;
};
id:color.<name>orbackground.<name>(color.default,background.default).label: Its words in the view’s labels (colorText,colorBackground): “Red text”, “Red background”, “Default text”.
BlockMenuController
export declare class BlockMenuController {
#private;
private edytor;
private options;
block: Block | null;
blocks: Block[];
anchor: HTMLElement | null;
query: string;
index: number;
flyout: false | "color" | "turn";
flyoutIndex: number;
constructor(edytor: Edytor, options?: BlockMenuOptions);
get isOpen(): boolean;
get readonly(): boolean;
get menuId(): string;
get flyoutId(): string;
rowId: (row: BlockMenuAction | KindRow | BlockMenuColor, flyout?: boolean) => string;
popup: Attachment<HTMLElement>;
flyoutMenu: Attachment<HTMLElement>;
keys: Attachment<HTMLElement>;
option: (index: number, flyout?: boolean) => Attachment<HTMLElement>;
search(query: string): void;
get members(): Block[];
get kinds(): KindRow[];
get colorable(): Block[];
get colors(): BlockMenuColor[];
isCurrentColor: (row: BlockMenuColor) => boolean;
get flyoutRows(): Array<KindRow | BlockMenuColor>;
get currentKind(): KindRow | undefined;
get actions(): BlockMenuAction[];
get matchingColors(): BlockMenuColor[];
get rows(): Array<BlockMenuAction | KindRow | BlockMenuColor>;
open(block: Block, anchor: HTMLElement): void;
close(restoreCaret?: boolean): void;
move(direction: 'up' | 'down'): void;
turnInto(kind: KindRow): void;
paint(row: BlockMenuColor): void;
duplicate(blocks: Block[], select: boolean): void;
remove(): void;
get linked(): Block | undefined;
copyLink(): Promise<void>;
run(row: BlockMenuAction | KindRow | BlockMenuColor | undefined): void;
openFlyout(which?: 'turn' | 'color'): void;
private restore;
private caret;
private focus;
}
block: The block whose grip opened the menu.blocks: The blocks the actions apply to, in document order: the block selection when it holdsblock(Notion), elseblockalone — as clicked, so a grip-selected list is one block (Move, Duplicate, Copy link, the caret after).index: The keyboard’s row.flyout: The open flyout: the kinds (turn), the colours (color), or none.flyoutIndex: The keyboard’s row in the open flyout.readonly: The editor is readonly: the menu closes.menuId: The menu’s and the Turn into flyout’s element ids (page-unique), foraria-controls.rowId: A row’s element id (page-unique; a flyout’s withflyout), foraria-activedescendant.popup: The menu element ({@attach controller.popup}, the one with the rows): it takes the menu’s id, itsmenurole and name unless it has its own, keeps the focus where it is on a press (a field takes its own), and is published to the view’s root (edytor.popups) while it is in the page, the grip that opened it told so.flyoutMenu: An open flyout ({@attach controller.flyoutMenu}, named by your markup): its id,menurole, the overlay’s placement beside the menu, and presses that keep the focus.keys: The element that holds the keyboard ({@attach controller.keys}: the search field): focused when it mounts, it takes every key of the menu (the arrows, Home and End walk the rows, → opens a row’s flyout and ← closes it, Enter runs the row, Delete removes the blocks while the query is empty, Escape closes the flyout, then the menu), and names the open menu and its highlighted row (aria-controls,aria-activedescendant, once that row is in the page).option: The attachment of the row atindex(of the open flyout’s rows withflyout):{@attach controller.option(index)}on its element.search: The search field typed in: the rows matchquery, the first one highlighted, no flyout open.members: What Delete and Turn into act on:blocks, a list or a code block with its subtree (selectedMembers).kinds: The Turn into rows: the kinds, then the commands that turnblocksinto something (turnCommands).colorable: The blocks the Color flyout paints: themembersthat take a colour (colorable).colors: The Color flyout’s rows: Notion’s text colours, then its backgrounds.isCurrentColor: Whether every block the flyout paints holdsrow’s colour (its ✓).flyoutRows: The rows of the open flyout.currentKind: The row naming the open block.matchingColors: With a query, the colours it names join the list after the kinds (Notion’s “Color” results:redlists “Red text” and “Red background”), when the blocks take one (colorable).rows: Every keyboard row: actions, then the matching kinds, then the matching colours.turnInto: Convert the open block, or every member (a list’s items) as one undo step (they stay selected); one block’s conversion places the caret (refused: the caret returns).paint: Paint the open blocks (colorable, a list’s items too) withrow’s colour or background as one undo step (setBlockColor); the menu closes and the blocks stay selected, as in Notion.duplicate: Duplicateblocksas one undo step, each copy after its block (a block inside another of them is copied with it; a vetoed one is skipped,dispatcher.each); the copies are selected (select), else the first copy takes the caret (refused: its block keeps it).remove: Delete the open blocks’members(a list with its items) as the keyboard’s block delete does (deleteSelectedBlocks):onDeleteSelectedBlocksmay keep them, then one command (deleteBlocks, one plan, so a veto keeps them all), unselected children taking their parent’s place, the caret where that delete puts it (kept or refused: the caret or the selection returns).linked: The one block “Copy link” names: the open block, or the layout its block selection covers whole (liftLayouts); none for several.run: Run a row of the menu or of its open flyout (a click; Enter runs the keyboard’s): paint a colour, convert to a kind, open an action’s flyout or run it.openFlyout: Open a flyout on its first row (keyboard or mouse): the “Turn into” kinds (default) or the colours; that flyout already open stays as it is.
BlockMenuLabels
The block menu’s actions (its search matches them) and the block colours’ commands.
export type BlockMenuLabels = {
turnInto: string;
color: string;
colors: Record<string, string>;
colorText: (color: string) => string;
colorBackground: (color: string) => string;
copyLink: string;
duplicate: string;
moveUp: string;
moveDown: string;
delete: string;
ctrl: string;
shift: string;
deleteKey: string;
};
color: The Color row.colors: A color’s name, by itsBLOCK_COLORSname (red),defaultfor none.colorText: A text color row and command, from the color’s name (“Red text”).colorBackground: A background row and command, from the color’s name (“Red background”).ctrl: Key names in the shortcuts shown off a Mac.
BlockMenuOptions
export type BlockMenuOptions = {
linkTo?: (block: Block) => string;
menu?: Snippet<[
BlockMenuController
]>;
};
linkTo: A link to the block, for “Copy link to block” (the row is hidden without it).menu: The menu’s markup and its words; it renders whilecontroller.isOpen, placed beside the handle (mark your paneldata-edytor-block-menufor placement; without it, nothing shows).
BlockMoveDirection
One relative step: up/down pass the previous/next sibling, never
entering its children, and past the first/last sibling leave the parent
(before/after it; a layout’s column: the layout, layout.fits); in =
last child of the previous sibling; out =
after the parent, the siblings after the last moved block becoming its
children (the document’s outdent, unNestBlocks).
export type BlockMoveDirection = 'up' | 'down' | 'in' | 'out';
BlockMovePosition
Where a move places the blocks relative to its target: before, after,
inside (its last child), or left/right of it, side by side
(prepare.placeBeside, layout.place-beside: a new column of the layout
the target is in, else a new layout wrapping the target).
export type BlockMovePosition = 'before' | 'after' | 'inside' | 'left' | 'right';
BlockMoveRequest
export type BlockMoveRequest = {
blocks: Block[];
} & ({
target: Block;
position: BlockMovePosition;
} | {
direction: BlockMoveDirection;
});
BlockRole
Structural role of a block type (plugin-side policy, resolved per op).
export type BlockRole = {
void?: boolean;
island?: boolean;
lines?: boolean;
layout?: boolean;
table?: boolean;
atomic?: readonly (string | readonly string[])[];
};
void: Not editable through normal structural flow; no children, no merges.island: Editable, but its subtree is structurally sealed from outside blocks.lines: An island of lines (code): each direct child displays as itsdefaultChildkind and holds no children.layout: A layout (columns): it displays only its items — itsdefaultChildkind, a container that renders no content — side by side, and only while it shows two or more (layout.*in the delete contract).table: A table: it displays only its rows — itsdefaultChildkind, a container whose owndefaultChildis the cell kind (a text island) — and each row only its cells, in the order of the table’sdata.columns(table.*in the delete contract).atomic: Data paths written as one leaf (data.atomic): a top-level key, or an array of keys for a nested one (['link', ['media', 'source']]).
BlockSnippetPayload
Represents the payload for block snippets.
export type BlockSnippetPayload<D = Record<string, any>> = {
block: BlockView<D>;
content: Snippet;
children: Snippet | null;
};
BlockSpec
Declarative block description used for inserts (and by seeders).
export type BlockSpec = {
id: BlockId;
type: string;
data?: Record<string, unknown>;
content?: ContentItem[];
children?: BlockSpec[];
};
BlockView
What a block snippet receives: declared values read from the block’s cell and the selection — reactive, never the block’s handle itself — plus the handle for document reads and commands.
export type BlockView<D = Record<string, any>> = {
readonly id: string;
readonly type: string;
readonly data: Readonly<D>;
readonly selected: boolean;
readonly focused: boolean;
readonly handle: Block | undefined;
void: Block['void'];
};
handle: The block’s id-only, read-only handle (its getters, reactive in a template);undefinedin a suggestion’s preview, which is no block of the document.void: Marks inner chrome (a header, a toolbar) non-editable; the block element is the core’s.
BookmarkCardPayload
What a bookmark’s card snippet receives: the page it links to, as unfurl described it.
export type BookmarkCardPayload = {
block: Block | undefined;
url: string;
title: string | undefined;
description: string | undefined;
image: string | undefined;
icon: string | undefined;
link: Attachment<HTMLElement>;
};
block: The bookmark;undefinedin a suggestion’s preview.url: The page’s URL (http(s)).title: Its title, description, cover image and icon, whenunfurlanswered them.link: The card’s link (<a href={card.url} {@attach card.link}>): a new tab without the opener, never dragged out of the editor.
BookmarkPluginOptions
export type BookmarkPluginOptions = {
unfurl?: (url: string) => Promise<BookmarkPreview | null | undefined>;
menu?: Snippet<[
UrlPasteController
]>;
card?: Snippet<[
BookmarkCardPayload
]>;
};
unfurl: Read a page’s title, description and images, usually through your server (a browser cannot fetch most pages).menu: The menu a URL pasted on an empty line opens (Link, Embed, Bookmark); it renders whilecontroller.open, under the line (without it, the link is kept and nothing shows).card: The card of a bookmark with a link (default: Notion’s, the title, description and URL beside the cover):<a href={card.url} {@attach card.link}>.
BookmarkPreview
What an unfurl answers about a page; every field optional.
export type BookmarkPreview = {
title?: string;
description?: string;
image?: string;
icon?: string;
};
image: The page’s cover image (og:image): anhttp(s)URL.icon: The page’s icon: anhttp(s)URL.
CALLOUT_ICONS
The picker’s choices unless createCalloutIconsPlugin({ icons }) names others.
declare const CALLOUT_ICONS: readonly string[];
CalloutIconPayload
What a callout’s calloutIcon snippet receives.
export type CalloutIconPayload = {
readonly block: Block | undefined;
readonly icon: string;
readonly editable: boolean;
readonly expanded: boolean;
readonly label: string;
toggle: () => void;
readonly control: Attachment<HTMLElement>;
};
block: The callout;undefinedin a suggestion’s preview.icon: Its icon: itsdata.icon, the view’s default without one,''for none (reactive).editable: The icon opens the view’s picker: an editable view listing a picker, not a suggestion’s preview.expanded: The picker is open on this callout (reactive).label: The button’s accessible name: Change icon, or Add icon over an empty slot.toggle: Open the view’s picker on this callout, or close it.control: The icon’s attachment ({@attach icon.control}): its marker (data-edytor-callout-icon, which the picker opens under),data-emptywithout an icon,contenteditable="false"; editable, itsaria-label,aria-haspopup,aria-expanded,aria-controls, a press that keeps the editor’s selection and a click that toggles the picker; elsearia-hidden.
CalloutIconPicker
A view’s callout icon picker: what a callout.picker snippet receives.
The callout it is open on (block), the choices (icons) and the icon
that callout shows (current); set(icon) and remove() write it and
close, close() closes with nothing written. Put {@attach picker.keys}
and {@attach picker.popup} on the picker’s element (it takes the focus
and the keys, names the keyboard’s row and is published to the view’s
root), attach picker.option(i) to each row (i in icons,
icons.length for Remove icon) and run picker.pick(i) on its click.
export declare class CalloutIconPicker extends ChromeList {
#private;
readonly icons: readonly string[];
target: string | null;
readonly columns = 8;
constructor(edytor: Edytor, icons?: readonly string[]);
get isOpen(): boolean;
get block(): Block | undefined;
get current(): string | undefined;
protected get count(): number;
get readonly(): boolean;
toggle: (block: Block) => void;
open: (block: Block) => void;
close: (keys?: boolean) => void;
set: (icon: string) => void;
remove: () => void;
pick: (index: number) => void;
option(index: number): Attachment<HTMLElement>;
keys: Attachment<HTMLElement>;
}
icons: The picker’s choices.target: The callout the picker is open on (its id),nullwhen closed.columns: The choices on a row: ArrowUp and ArrowDown move by a row, so lay the icons out in as many columns.isOpen: Whether the picker is open (reactive).block: The callout it is open on (reactive).current: The icon that callout shows ('': none; reactive).count: The keyboard’s rows: the icons, then Remove icon.readonly: The view is readonly (reactive): the picker closes.toggle: Open the picker onblock, or close it when it is open there.open: Open the picker on the calloutblock, its icon the keyboard’s row (refused in a view that may not write).close: Close the picker; withkeys(by default), the editor takes the keys back, with its selection.set: Writeiconon the callout (one undo step), then close.remove: Leave the callout without an icon (data.iconis''), then close.pick: Pick rowindex: its icon, or Remove icon (icons.length).option: Rowindex’s attachment (icons.length: Remove icon), as every menu’s rows: its id,menuitemradiorole andaria-checked(menuitemfor Remove icon); its highlight is the markup’s (index).keys: The picker’s element ({@attach picker.keys}): it takes the focus as it opens and names the keyboard’s row (aria-activedescendant).
CalloutIconsOptions
export type CalloutIconsOptions = {
picker?: Snippet<[
CalloutIconPicker
]>;
icons?: readonly string[];
};
picker: The icon picker’s markup and its words: it renders while the picker is open, placed under the icon (without it, the icon opens nothing).icons: The picker’s choices (CALLOUT_ICONSby default).
Caret
A caret in block offsets: the block and the offset in its content, the
offsets edytor.document.facade takes (an inline atom counts 1). The
public form of a collapsed text selection (selection.caret), and what
selection.setCaret and dispatcher.caret take.
export type Caret = {
block: Block;
offset: number;
};
Cell
export type Cell = {
readonly id: BlockId;
readonly type: string;
readonly data: Data;
readonly childIds: readonly BlockId[];
readonly runs: readonly ContentRun[];
};
Cells
export type Cells = {
readonly rootIds: readonly BlockId[];
readonly size: number;
get: (id: BlockId) => Cell | undefined;
apply: (report: CellReport) => Patched;
remount: (id: BlockId) => void;
epoch: (id: BlockId) => number;
dispose: () => void;
};
rootIds: The root’s visible child ids.apply: Patch from one change report.remount: Re-create a block’s text elements from its cell (the observer’s repair of foreign damage).epoch: Bumped byremount: part of the block’s text element keys.
ChangePayload
What onBeforeOperation receives: a document write under its name with
its arguments (payload: Parameters<DocumentWrites[K]>), or a view
command with its payload (Commands); operation narrows payload.
export type ChangePayload = {
[K in keyof DocumentWrites]: Change<K, Parameters<DocumentWrites[K]>>;
}[keyof DocumentWrites] | {
[K in keyof Commands]: Change<K, Commands[K]>;
}[keyof Commands];
clearDocument
declare const clearDocument: ProviderStack['clearDocument'];
CODE_LANGUAGES
The default languages, after Notion’s list, sorted by label (Plain text first).
declare const CODE_LANGUAGES: readonly CodeLanguage[];
CodeHeader
What a code plugin’s header snippet receives: the code block’s
language (language, its id; label, as shown), whether the language may be changed here
(editable: an editable view, not a suggestion’s preview), the list’s
state (expanded), the actions (toggle, the list’s own pick, copy, and
copied for a moment after a copy) and the plugin’s labels. Put
{@attach header.button} on the element that opens the language list:
it opens it on a click, ArrowDown or ArrowUp, Escape gives the keys back
to the editor, the toolbar’s shortcut at a caret in the block reaches it, and it carries
the ARIA (aria-haspopup, aria-expanded, aria-controls, its name).
export declare class CodeHeader {
#private;
copied: boolean;
get block(): Block | undefined;
get language(): string;
get label(): string;
get readonly(): boolean;
get editable(): boolean;
get expanded(): boolean;
toggle: (keys?: boolean) => void;
copy: () => Promise<void>;
button: Attachment<HTMLElement>;
}
copied: Copy just copied: the button may read Copied (reactive, for a moment).block: The code block;undefinedin a suggestion’s preview.language: The block’s language id: itsdata.language, else the plugin’s default (reactive).label: The language’s label; a language the plugin does not list shows its id (reactive).readonly: The view is readonly (reactive).editable: The language may be changed here: an editable view, not a suggestion’s preview (reactive).expanded: The language list is open on this block (reactive).toggle: Open the language list under the header’s button, or close it (keys: the keyboard opened it, so the keys go back to the button after).copy: Copy the block’s code to the clipboard;copiedreads true for a moment.button: The element that opens the language list ({@attach header.button}, abutton): a click opens or closes it (from Enter or Space, the keys opened it and get it back), ArrowDown or ArrowUp opens it, Escape gives the keys back to the editor; a press keeps the editor’s selection.
CodeLabels
export type CodeLabels = {
code: string;
};
code: The preset (a language’s name is itsCodePluginOptions.languagesrow’s).
CodeLanguage
A language a code block may be in.
export type CodeLanguage = {
id: string;
label: string;
grammar?: string;
load?: () => Promise<readonly LanguageDefinition[]>;
};
id: Stored in the code block’sdata.language.label: Shown in the header and its picker.grammar: The name of the grammar its lines are tokenized with; absent, its lines are plain text (no tokens).load: Loads the grammar, then the grammars it embeds (HTML’s styles and scripts).
CodePluginOptions
export type CodePluginOptions = {
languages?: readonly CodeLanguage[];
defaultLanguage?: string;
header?: Snippet<[
CodeHeader
]>;
menu?: Snippet<[
LanguageMenu
]>;
};
languages: The languages a code block may be in, in the header’s order, with their names.defaultLanguage: The language of a code block whosedata.languageis unset.header: A code block’s header (the language’s button and Copy) and its words: it renders above the code, in the block’s non-editable part (without it, no header).menu: The language list: it renders under the header’s button whilemenu.openis set (without it, nothing shows).
ColumnsLabels
export type ColumnsLabels = {
columns: (count: number) => string;
resize: string;
};
columns: Thecolumns.<n>command.resize: A resize band’s accessible name.
ColumnsPluginOptions
export type ColumnsPluginOptions = {
minWidth?: number;
};
minWidth: The narrowest a column may be resized to, as a fraction of its layout’s width (default0.1).
CommandResult
The result of the last command a view ran (edytor.dispatcher.last): its
operation and its status.
export type CommandResult = {
operation: string;
status: 'refused' | 'noop' | 'applied' | 'failed';
error?: unknown;
selection?: SelectionValue;
};
selection: The result selection the command authored, selected once.
Commands
The view’s commands that are no single document write, each one prepared plan of them, with their payloads: a hook sees each by its name, then its planned steps as the writes they perform.
export interface Commands {
acceptSuggestion: {
suggestion: {
id: string;
at: ResolvedAt;
content: readonly JSONBlock[];
};
};
replaceMatches: {
matches: {
block: string;
offset: number;
length: number;
}[];
replacement: string;
};
addComment: {
thread: string;
runs: {
block: string;
offset: number;
length: number;
}[];
};
moveText: {
from: {
start: {
block: string;
offset: number;
};
end: {
block: string;
offset: number;
};
};
to: {
block: string;
offset: number;
};
copy: boolean;
};
inlineMarkdown: {
value: string;
start: number;
end: number;
};
insertDivider: Record<string, never>;
insertTableRow: {
index: number;
};
deleteTableRows: {
rows: Block[];
};
insertTableColumn: {
index: number;
width?: number;
};
deleteTableColumn: {
column: string | number;
};
moveTableColumn: {
column: string | number;
to: number;
};
moveTableRows: {
rows: Block[];
to: number;
};
fillTableCell: {
row: string;
column: string | number;
};
}
acceptSuggestion: A suggestion placed in the document (edytor.suggestions,suggestion.accept()).replaceMatches: Find and replace (plugins/find): each match’s text replaced byreplacement, one plan, one undo step.addComment: A comment thread’s anchor (plugins/comments): itscomment:<thread>mark over each run of the text it was made on, one plan, one undo step.moveText: A text range dragged inside the editor and dropped atto(block offsets): its content placed atto, then the range deleted — or, withcopy(Alt), only placed.inlineMarkdown: A closing marker typed after marked-up text (**bold**): the mark set, the markers removed;valuetyped atstartof the text.insertDivider: A divider at the caret: its steps are the conversion, insertion or split it plans.insertTableRow: A table’s row atindex, one empty cell per column (table.insert-row).deleteTableRows: A table’s rows, with their cells (table.delete-row).insertTableColumn: A table’s column atindex, an empty cell in every row (table.insert-column).deleteTableColumn: A table’s column, by id or position, with its cells (table.delete-column).moveTableColumn: A table’s column moved to positionto(table.move-column).moveTableRows: A table’s rows moved to positiontoamong its rows (table.move-row).fillTableCell: The cell a row shows none of incolumn, created (table.pad).
commentAnchors
Every thread’s anchor in doc: the runs of text its comment:<id> mark
covers, in document order (adjacent runs of one block joined). A split
or a merge moves the marked text, never copies it, so a thread keeps its
runs through both; a thread whose marked text was deleted has none.
declare const commentAnchors: (doc: CommentAnchorSource) => Map<string, CommentRun[]>;
CommentCardPayload
What a card snippet receives: one thread (null: the comment being
written, comments.draft), the view’s comments controller, and whether
it is the active one.
export type CommentCardPayload = {
thread: PlacedThread | null;
comments: CommentsController;
active: boolean;
};
thread: The thread, placed beside its text (its comments, its anchor’s text,resolved);nullfor the comment being written (its textcomments.draft.quote: post it withcomments.submit(body), give it up withcomments.cancel()).comments: The view’s comments: the actions (reply, resolve, delete,activate), the user.active: It is the active thread (its text is selected, or its card was pressed).
CommentChange
What one request changed (the room broadcasts it, onComment receives it).
export type CommentChange = {
type: 'added' | 'replied' | 'resolved' | 'reopened' | 'deleted' | 'removed';
thread: CommentThread;
comment: ThreadComment | null;
user: string;
at: number;
seq: number;
};
type:addeda thread,repliedto one,resolvedorreopenedone,deleteda reply, orremoveda whole thread (its anchor marks go with it).thread: The thread after the change; forremoved, as it was.comment: The comment added (added,replied) or deleted (deleted), elsenull.user: Who made the change.seq: The change’s sequence number in its document (the thread’s newrev).
CommentDocument
What a client reads and writes of a document: its blocks’ content, and its marks (a removal).
export type CommentDocument = CommentAnchorSource & {
formatRange(block: string, offset: number, length: number, marks: Record<string, unknown>): unknown;
transact<T>(fn: () => T): T;
};
CommentDraft
The thread being written: the text it will anchor, held by anchors so edits move it.
export type CommentDraft = {
id: string;
quote: string;
block: string;
spans: {
start: DocAnchor;
end: DocAnchor;
}[];
};
id: The new thread’s id (its anchor will becomment:<id>).quote: The text it was started on (a preview: the server keeps it as the thread’s quote).block: The block it starts in.spans: Each run of selected text, by anchors.
CommentFeed
What a client’s subscribe delivers: every thread (at a (re)connect), or one change.
export type CommentFeed = Exclude<CommentMessage, {
type: 'subscribe';
}>;
CommentMessage
A comment message, decoded.
export type CommentMessage = {
type: 'subscribe';
on: boolean;
} | ({
type: 'snapshot';
} & CommentSnapshot) | {
type: 'change';
change: CommentChange;
};
CommentNotice
What onComment receives with each change: whether the current user made it (here or elsewhere).
export type CommentNotice = {
own: boolean;
};
CommentRequest
A request to a document’s comments, by the user a store verified.
export type CommentRequest = {
op: 'add';
thread: string;
body: string;
quote?: string;
block?: string | null;
comment?: string;
} | {
op: 'reply';
thread: string;
body: string;
comment?: string;
} | {
op: 'resolve';
thread: string;
} | {
op: 'reopen';
thread: string;
} | {
op: 'delete';
thread: string;
comment?: string;
};
CommentRequestError
A comments request the server refused: status is its HTTP status (400, 403, 404, 409…).
export declare class CommentRequestError extends Error {
readonly status: number;
constructor(status: number, message: string);
}
CommentResult
What a request did: applied (the change) or noop (the thread, unchanged).
export type CommentResult = {
status: 'applied';
change: CommentChange;
} | {
status: 'noop';
thread: CommentThread;
};
CommentRun
One run of text a thread’s anchor mark covers: length units from offset of block.
export type CommentRun = {
block: string;
offset: number;
length: number;
};
CommentsClient
A document’s comment threads, as the comments plugin reads and writes them.
export type CommentsClient = {
list(): Promise<CommentSnapshot>;
send(request: CommentRequest): Promise<CommentResult>;
subscribe?(listener: (message: CommentFeed) => void): () => void;
attach?(document: CommentDocument): (() => void) | void;
};
list: Every thread, with the document’s last sequence number.send: One request (add, reply, resolve, reopen, delete) as the client’s user.subscribe: Hear the changes anyone makes (and every thread again after a reconnect).attach: The document a view shows, given by the comments plugin when it mounts: a client whose store has no document of its own (the memory client) removes a removed thread’s anchor marks from it.
CommentsClientOptions
Options of createCommentsClient.
export type CommentsClientOptions = {
server: string;
room: string;
socket?: string;
params?: Record<string, string> | (() => Record<string, string>);
fetch?: typeof fetch;
};
server: The base URL of the comments route: requests go to<server>/<room>, the room percent-encoded as one path segment (ws:/wss:read ashttp:/https:).room: The document’s id.socket: The websocket sync’s server URL (theserverof<Edytor>orcreateWebsocketSync), whose socket hears the room’s changes.params: Query parameters sent with each request (a token): an object, or a function read at every request.fetch: The fetch to use (default the global one).
commentsController
The comments plugin’s controller of edytor; undefined when the plugin is not listed.
declare const commentsController: (edytor: Edytor) => CommentsController | undefined;
CommentsController
The comments plugin’s state and actions (plugins/comments): the
threads the client hears (merged by their newest change), each thread’s
anchor read from the document (its comment:<id> mark), the thread being
written, the active one (the caret’s, or the one clicked), and the
requests. The anchor mark is written by one dispatcher command
(addComment) once the server accepted the thread; a removed thread’s
marks are its store’s to remove (the room, or the memory client).
export declare class CommentsController {
#private;
private edytor;
private options;
active: string | null;
draft: CommentDraft | null;
showResolved: boolean;
error: string | null;
readonly client: CommentsClient;
readonly user: CommentUser;
constructor(edytor: Edytor, options: CommentsControllerOptions);
readonly anchors: Map<string, CommentRun[]>;
readonly threads: PlacedThread[];
readonly open: PlacedThread[];
readonly resolved: PlacedThread[];
readonly shown: PlacedThread[];
get canComment(): boolean;
thread(id: string): PlacedThread | undefined;
userOf: (id: string) => CommentUser;
mayDelete: (thread: CommentThread, comment: {
author: string;
}) => boolean;
connect: () => void;
destroy: () => void;
start: () => boolean;
cancel: () => void;
draftRuns: (draft?: CommentDraft | null) => CommentRun[];
submit: (body: string) => Promise<boolean>;
reply: (thread: string, body: string) => Promise<boolean>;
resolve: (thread: string) => Promise<boolean>;
reopen: (thread: string) => Promise<boolean>;
remove: (thread: string, comment?: string) => Promise<boolean>;
focus: (thread: string | null) => void;
follow: () => void;
readonly css: string;
}
active: The thread the caret is in, or the one picked in the sidebar.draft: The thread being written, before it is posted.showResolved: The sidebar also shows the resolved threads.error: The last request that failed (its message), until the next one succeeds.anchors: Every thread’s anchor runs, in document order (maintained per commit).threads: Every thread, in the order of its anchor in the document (threads with none last).open: The open threads whose text shows (what the sidebar places by default).resolved: The resolved threads.shown: The threads the sidebar shows: the open ones, the resolved ones on demand, the active one.canComment: The view may write: it may start, answer, resolve and delete comments.thread: One thread, by id.userOf: A user as the sidebar shows them: their name (else their id), avatar and color.mayDelete: Whether the current user may deletecommentofthread(its author).connect: Start hearing the threads: the client’s list, its changes, the document’s commits.destroy: Stop hearing them (the editor is going away).start: Start a thread on the selected text (the toolbar’s Comment button, Mod+Shift+M): each selected run, held by anchors while it is written; at a caret, its block’s whole text.cancel: Drop the thread being written; the editor takes the focus back.draftRuns: The runs the draft’s anchors cover now.submit: Post the thread being written: the server keeps it, then its anchor mark is written over the draft’s text (oneaddCommentcommand, one undo step).remove: Delete one comment; withoutcomment(or its first), the whole thread.focus: Makethreadthe active one (a click on its card); its text scrolls into view.follow: The caret moved (onSelectionChange): the thread whose text holds it becomes the active one (an open one first), or none.css: The highlight of the threads’ text: open threads, the active one stronger.
CommentsLabels
The comments plugin (createCommentsPlugin): its toolbar button and its sidebar.
export type CommentsLabels = {
comment: string;
sidebar: string;
};
comment: The toolbar button, and a thread’s card’s accessible name.sidebar: The sidebar’s accessible name.
CommentSnapshot
The document’s threads at sequence number seq.
export type CommentSnapshot = {
seq: number;
threads: CommentThread[];
};
CommentsPluginOptions
export type CommentsPluginOptions = {
client: CommentsClient;
user: string | CommentUser;
users?: (id: string) => Omit<CommentUser, 'id'> | undefined;
onComment?: (change: CommentChange, notice: CommentNotice) => void;
card?: Snippet<[
CommentCardPayload
]>;
header?: Snippet<[
CommentsController
]>;
};
client: Where the threads live:createCommentsClient({ server, room })for the room’s (routeDocumentComments),createMemoryCommentsClientfor threads in memory, or any object withlistandsend.user: The current user: their id (the identity your server verifies; it decides who may delete a comment) or{ id, name, avatar, color }.users: How another user shows: their name, avatar and color, by id (default: the id).onComment: Notifications: each change this view hears (a thread added, a reply, a resolve or reopen, a delete), once;ownwhen the current user made it (in this view or another).card: A card’s markup: a thread’s (its comments, a reply field, Resolve) or the comment being written (thread: null).header: The top of the sidebar’s margin (Notion’s “Show resolved”): the cards stack under it.
CommentThread
A comment thread: its first comment starts it, the others reply.
export type CommentThread = {
id: string;
block: string | null;
quote: string;
createdBy: string;
createdAt: number;
resolved: {
by: string;
at: number;
} | null;
comments: ThreadComment[];
rev: number;
};
id: The thread’s id, which its anchor mark names (comment:<id>).block: The block it was started in (null: unknown).quote: The text it was started on, as it read then (at mostMAX_QUOTE_LENGTH).resolved: Who resolved it and when;nullwhile it is open.comments: Oldest first; never empty.rev: The sequence number of the last change to it (a later change has a greater one).
CommentUser
Who a comment’s author is, as the sidebar shows them.
export type CommentUser = {
id: string;
name?: string;
avatar?: string;
color?: string;
};
name: Shown in place of the id.avatar: An avatar image URL; without one, the name’s initial oncolor.
ContentItem
One inline content element in a block spec / projection.
export type ContentItem = {
kind: 'text';
text: string;
marks?: Record<string, unknown>;
} | {
kind: 'inline';
id: string;
type: string;
data?: Record<string, unknown>;
};
ContentRun
One visible run of a block — the maintained form of ContentItem.
marks/data are interned: equal payloads share one frozen instance,
so === compares formatting without deep walks. A run carries no
authorship: runs never split at author boundaries, and durable
attribution is the compact per-block record (blockattr).
export type ContentRun = {
kind: 'text';
text: string;
marks?: Record<string, unknown>;
} | {
kind: 'inline';
id: string;
type: string;
data?: Record<string, unknown>;
};
convertToKind
Convert block to a row’s kind as one command. With caret (by default
when the conversion replaces the content) the caret lands at the start of
the converted block, or of its first child when the conversion creates one.
A replacing kind (a divider, a code block) converts in place only a block
that holds nothing (holdsNothing); after text, atoms or children it is
inserted after the block instead, which stays intact. The kind lands where
it fits (placing): a list’s item turned into another kind leaves the
list — out of every list it sits in directly (a list nested right in a
list) — where Shift+Tab lifts it, as a bullet turned into
a heading stops being a bullet in Notion; a block’s own kind (only its
data changes) never moves it. Each is one plan: one refusal or veto keeps
everything, one undo step. Answers whether it applied.
declare const convertToKind: (edytor: Edytor, block: Block | null | undefined, row: Pick<KindRow, "value" | "replaces">, caret?: boolean) => boolean;
createArrowMovePlugin
Block moves from the keyboard (Mod+Up/Down, Mod+Shift+Up/Down): a default plugin.
declare const createArrowMovePlugin: () => Plugin;
createAudioPlugin
Notion’s audio block: an audio file (an http(s) link, or an upload
through upload) in the browser’s player, with an editable caption.
Void: its only text is the caption.
declare const createAudioPlugin: () => Plugin;
createBlockHandlesPlugin
Block handles in the overlay, created lazily: a handle mounts
for a block near the viewport (the overlay’s near top-level blocks and
the blocks inside them, not hidden; every block without layout),
under the pointer, selected, focused or dragged; drop targets exist only
during our own drag. Hover is one delegated listener on the editor and
one on the overlay (a block’s handle keeps it hovered).
declare const createBlockHandlesPlugin: (options?: BlockHandlesOptions) => Plugin;
createBlockMenuPlugin
Notion’s block menu: a handle click
opens it beside the handle — search, Turn into, Color, Duplicate, Move,
Delete, and Copy link when linkTo is given. Also binds Mod+D (duplicate
the selected blocks, or the caret’s block), and adds the colours to the
slash menu by name (/red: “Red text”, “Red background”).
declare const createBlockMenuPlugin: (options?: BlockMenuOptions) => Plugin;
createBookmarkPlugin
Notion’s web bookmark: a card linking to a page, with its title,
description and images when your unfurl answers them, and an
editable caption. Void: its only text is the caption. Pasting an
http(s) link on an empty line offers “Bookmark”.
declare const createBookmarkPlugin: (options?: BookmarkPluginOptions) => Plugin;
createCalloutIconsPlugin
Callout icons (Notion’s): a callout’s icon opens the view’s picker, and its block menu has a Change icon row (rich text’s, while a picker is listed), the keyboard’s way to it. The first plugin listed claims the view’s picker.
declare const createCalloutIconsPlugin: ({ picker: menu, icons }?: CalloutIconsOptions) => Plugin;
createCodePlugin
Code blocks, in the languages options lists (code.language): the
header names the block’s language and picks another; each line is
highlighted with its block’s language, its grammar loaded the first
time a line of it renders.
declare const createCodePlugin: (options?: CodePluginOptions) => Plugin;
createColumnsPlugin
Notion’s columns (docs/columns-plan.md): a columns layout shows its
columns side by side, each holding any blocks; a column grows by its
weight (data.width). The roles are the document’s (layoutKinds,
layout.* in the delete contract): a layout showing one column shows
as its blocks, an empty column does not show. columns.css lays them
out and stacks them below a 480px wide layout.
declare const createColumnsPlugin: (options?: ColumnsPluginOptions) => Plugin;
createCommentsClient
The default CommentsClient: the requests routeDocumentComments
answers (GET <url> lists, POST <url> sends a request as JSON), and
the room’s changes heard on the socket of the document’s websocket sync
(subscribe: while a listener is subscribed, that socket asks the room
for them). Throws a TypeError at once for a room id no request can
carry.
declare const createCommentsClient: (options: CommentsClientOptions) => CommentsClient;
createCommentsPlugin
Comments (Notion’s): select text and press Comment in the toolbar (or
Mod+Shift+M) to start a thread on it. The thread’s text carries its
anchor, a comment:<id> mark the document keeps through splits and
merges and that copy and cut leave behind; its comments live in the
client’s store (the room’s threads and comments tables). Open threads
are highlighted, and a sidebar beside the text (under it, on a narrow
screen) places a card for each one.
declare const createCommentsPlugin: (options: CommentsPluginOptions) => Plugin;
createDocument
See DocumentBinding.createDocument.
declare const createDocument: (options?: CreateDocumentOptions) => EdytorDocument;
CreateDocumentOptions
export type CreateDocumentOptions = DocumentOptions & {
value?: JSONDoc;
};
createEmbedPlugin
Notion’s embed block: an “Embed a link” panel until it has a link, then
the provider’s player in a sandboxed frame, with an editable caption.
Only an allowlisted provider (providers) plays: the frame’s src is
the player URL the provider derives from the stored link, never the
stored value, and it has a sandbox and no srcdoc. Void: its only
text is the caption. Pasting a link a provider plays on an empty line
offers “Embed”.
declare const createEmbedPlugin: (options?: EmbedPluginOptions) => Plugin;
createEquationPlugin
Equations (Notion’s): a block equation (equation, void, its TeX in
data.expression) and an inline equation atom (inlineEquation), both
drawn by KaTeX, loaded the first time an equation shows in a browser:
from jsDelivr by default, or as katex says (a URL, your loader). A
click on one, or Enter on a selected one, opens its TeX source in an
editor under it; the equation is the live preview. “Block equation”
and “Inline equation” are slash commands, $$…$$ typed in text is an
inline equation, and Mod+Shift+E turns the selected text into one.
Copied, an equation is MathML carrying its TeX; pasted HTML with KaTeX
or MathML math becomes equations again.
declare const createEquationPlugin: (options?: EquationPluginOptions) => Plugin;
createFilePlugin
Notion’s file block: an attachment, uploaded through upload (or
linked), shown as its name and size, linking to the file, with an
editable caption. Void: its only text is the caption.
declare const createFilePlugin: (options?: FilePluginOptions) => Plugin;
createFindPlugin
Find and replace (Notion’s find in page), opt-in: Mod+F opens a bar over
the editor’s top right. It searches the document’s text in reading order,
a closed toggle’s body included (the toggle opens when a match there
becomes the current one), highlights the matches in the overlay, and
replaces one or all as one command (replaceMatches) and one undo step.
declare const createFindPlugin: (options?: FindPluginOptions) => Plugin;
createHistoryClient
The default HistoryClient: the requests routeDocumentHistory
answers. GET <url> lists, GET <url>?key= reads (404: null),
POST <url>?restore= restores (404: refused), POST <url>?undo
undoes; any other refusal throws a HistoryRequestError. Throws
a TypeError at once for a room id no request can carry.
declare const createHistoryClient: (options: HistoryClientOptions) => HistoryClient;
createImagePlugin
Notion’s image block: an “Add an image” panel until it has a source (a
pasted link, or an upload when upload is given), then the image with
an editable caption, its alt text, width and alignment set from the
chrome the plugin shows over it. Void: its only text is the caption.
declare const createImagePlugin: () => Plugin;
createImageToolbarPlugin
The image toolbar: your toolbar over the image the chrome is on. The first listed claims the view.
declare const createImageToolbarPlugin: ({ toolbar }: ImageToolbarOptions) => Plugin;
createIndexeddbSync
declare const createIndexeddbSync: (name: string) => EdytorSync;
createMarkdownShortcutsPlugin
Markdown prefixes come from the kind catalogue: a row whose shortcut the typed character completes.
declare const createMarkdownShortcutsPlugin: () => Plugin;
createMarqueePlugin
Block selection with a rectangle (Notion’s rubber band), opt-in: a press
in the editor’s empty area — the margins beside the blocks (the handle
gutter, not a handle), the space below the last block, an optional
container around the editor — then a drag draws a rectangle and selects,
live, the blocks it meets. Shift or Mod adds to the selection there was;
Escape gives it back. On release the block selection stays and the editor
has the keys. A click without a drag keeps what a press there did (below
the last block, the trailing paragraph).
declare const createMarqueePlugin: (options?: MarqueePluginOptions) => Plugin;
createMediaEmptyPlugin
The empty image and media blocks’ chrome: your empty snippet for each
of them (without the plugin, an empty block shows nothing). The first
listed claims the view.
declare const createMediaEmptyPlugin: ({ empty }: MediaEmptyOptions) => Plugin;
createMemoryCommentsClient
Comment threads kept in memory, under the rules the room applies
(decideComment): for tests, demos and a document without a room. Its
siblings (as(user)) share the threads; each change reaches every
subscriber, asynchronously as over a network. A removed thread’s anchor
marks leave the last document a plugin attached.
declare const createMemoryCommentsClient: (options: MemoryCommentsClientOptions) => MemoryCommentsClient;
createMentionPlugin
People mentions, as in Notion: @ opens a menu of the people your app’s
items(query) answers; picking one replaces @query with a mention
atom ({ id, label }) and puts the caret after it, in one undo step.
declare const createMentionPlugin: (options: MentionPluginOptions) => Plugin;
createPageLinkPlugin
Links to your app’s pages, as Notion’s [[: it opens a menu of the pages
your app’s search(query) answers; picking one replaces [[query with
a pageLink atom ({ id, title, href, icon }), an <a> to the page,
and puts the caret after it, in one undo step.
declare const createPageLinkPlugin: (options: PageLinkPluginOptions) => Plugin;
createPagePlugin
Notion’s subpage block: a page block links to another document by
id (data.pageId), and shows its title (data.title, the cache your
title lookup refreshes). Each page is a document of its own (a room
per page): moving a subpage is moving this block, and the page itself,
its content and history, never moves. A click opens it (open), and so
does Enter over it selected alone; a modified or middle click opens it
in a new tab ({ newTab: true }, the browser’s own on an href). With create, the “Page”
command (slash menu, +) creates a page, inserts a block linking to it
where the caret is (an empty line becomes it) and opens it.
createPagePlugin({
open: (pageId) => goto(`/p/$\{pageId\}`),
href: (pageId) => `/p/$\{pageId\}`,
title: (pageId) => pages.get(pageId)?.title,
create: () => crypto.randomUUID()
});
declare const createPagePlugin: (options?: PagePluginOptions) => Plugin;
createRichTextPlugin
The rich text kinds and marks, their words and slash keywords the
view’s (labels, English by default).
declare const createRichTextPlugin: (options?: RichTextPluginOptions) => Plugin;
createSlashMenuPlugin
The slash menu, drawn by your menu snippet.
declare const createSlashMenuPlugin: (options?: SlashMenuOptions) => Plugin;
createSuggestionBarPlugin
The suggestion bar (Notion AI’s): your bar under each block
suggestion (Accept, Discard, and Try again when the app passed
onRetry); its keys are the suggestions plugin’s.
declare const createSuggestionBarPlugin: ({ bar }: SuggestionBarOptions) => Plugin;
createSuggestionsPlugin
The keys of edytor.suggestions (Notion AI’s), a default plugin: for the
latest one, Mod+Enter accepts and Escape discards; Tab accepts an end
suggestion at the caret. Its bar is createSuggestionBarPlugin’s.
declare const createSuggestionsPlugin: () => Plugin;
createTablePlugin
Notion’s simple table (table.* in the delete contract): a table of
tableRows of tableCells, each cell a text island whose lines are line
breaks (Enter is a line break in a cell). The columns are the table’s
data.columns (ids and widths), each cell names its column, and the
header flags are data.headerRow and data.headerColumn. The table is a
CSS grid (table.css): a row shows its cells in its table’s column
order, a padded cell (a peer’s row inserted while a column was) as an
empty placeholder whose first press creates it. Tab and Shift+Tab walk
the cells (Tab in the last one adds a row), ArrowUp and ArrowDown go to
the cell above and below. On hover, a + under and beside the table adds
a row or a column, the grips of a row and a column open their menus
(insert, move, delete), and a band on each column’s edge resizes it. The
block menu of a table has its Header row and Header column switches.
Not a default plugin; its roles are in defaultSemantics.
declare const createTablePlugin: (options?: TablePluginOptions) => Plugin;
createTocPlugin
Notion’s table of contents: a toc block lists the document’s
headings, live, indented by level; a click scrolls to one (opening the
closed toggles it sits in). Void: it holds no text, and its list is
never stored, only drawn. Its preset in the slash menu turns an empty
line into one, or inserts it after the line; its words are the view’s
toc labels (English by default).
declare const createTocPlugin: (options?: TocPluginOptions) => Plugin;
createToolbarPlugin
The selection toolbar, drawn by your toolbar snippet, and the link card by your card.
declare const createToolbarPlugin: (options?: ToolbarOptions) => Plugin;
createUploadsPlugin
File uploads (Notion’s): the view’s one uploader, for every kind whose
record takes files (upload: the image, file, video and audio kinds).
Without it, only links are embedded. The first listed claims the view.
declare const createUploadsPlugin: ({ upload }: UploadsOptions) => Plugin;
createVideoPlugin
Notion’s video block: a video file (an http(s) link, or an upload
through upload) in the browser’s player, with an editable caption.
A YouTube or Vimeo link is an embed (createEmbedPlugin()). Void: its only
text is the caption.
declare const createVideoPlugin: () => Plugin;
createWebsocketSync
declare const createWebsocketSync: (options: WebsocketSyncOptions) => WebsocketSync;
DataPatch
One data edit at path (object keys and array indexes from the data’s
root): splice takes Array.prototype.splice’s arguments and order
the array’s current indexes in their new order; else it sets value, or
deletes the key or removes the item when there is none.
export type DataPatch = {
readonly path: readonly string[];
readonly value?: unknown;
readonly splice?: readonly [
start: number,
deleteCount?: number,
...items: unknown[]
];
readonly order?: readonly number[];
};
DataTarget
What a data patch edits: a block, one of its inline atoms, or the document (null).
export type DataTarget = BlockId | null | {
block: BlockId;
atom: string;
};
DEFAULT_PRESENCE_THROTTLE
The least ms between two presence writes of a view by default: a caret held down or a drag publishes about 20 writes a second instead of one per selection change, which every peer and the room relay.
declare const DEFAULT_PRESENCE_THROTTLE = 50;
defaultSemantics
The rich-text, code, image, media, columns, page, table of contents,
table and equation plugins’ block roles together (and the rich-text and
comment mark edges) — what the room and a headless
createDocument/loadDocument adopt by default (semantics: {} checks
none). The media, columns, page, table of contents, table and equation
plugins are not default plugins of <Edytor>, but their roles are here so their
blocks read the same on every replica.
declare const defaultSemantics: {
roles: Record<string, BlockRole>;
rendersContent: Record<string, boolean>;
defaultChild: Record<string, string>;
} & {
marks: Record<string, {
edge: MarkEdge;
}>;
};
deleteTableColumn
Delete a column (table.delete-column, by id or position among the
grid’s): it leaves data.columns (every entry naming it) and every
shown cell of it is deleted, in one plan; a table left with no column
goes with it. A column the table shows but no longer lists
(table.cell) loses its cells. ids: the table.
declare const deleteTableColumn: (doc: Omit<DocumentOperations, "transact">, table: BlockId, column: string | number) => Prepared;
deleteTableRows
Delete rows with their cells (table.delete-row): one plan; a table
left with no row goes with them. ids: the rows.
declare const deleteTableRows: (doc: Omit<DocumentOperations, "transact">, rows: readonly BlockId[]) => Prepared;
Destination
Where a block goes: parent = logical block id or null for root.
export type Destination = {
parent: BlockId | null;
index: number;
};
Dispatcher
export declare class Dispatcher {
#private;
private edytor;
get last(): CommandResult | null;
private active;
private running;
private kind;
private applied;
private depth;
private replacing;
private queue;
private current;
private leading;
private patched;
private untracked;
outside: <T>(body: () => T) => T;
permits: () => boolean;
run: <T>(kind: string, body: () => T) => T | undefined;
each: <T, R>(kind: string, parts: readonly T[], part: (item: T) => R) => (R | undefined)[];
dispatch: <P, R>(operation: string, payload: P, context: {
block: unknown;
text?: Text;
}, body: (payload: P, plan?: Prepared) => R, prepare?: (payload: P) => Prepared) => R | undefined;
plan: <P>(operation: string, payload: P, prepare: (payload: P) => Prepared, block?: Block, text?: Text) => OpResult;
lead: <T>(lead: Prepared, body: () => T) => {
out: T | undefined;
taken: boolean;
};
caret: {
(text: Text | null | undefined, offset: number, ops?: () => OpResult): OpResult | undefined;
(caret: Caret | DocPosition | null | undefined, ops?: () => OpResult): OpResult | undefined;
};
private policy;
private decide;
private settle;
private replace;
private steps;
}
last: The last operation’s result (read-only: the dispatcher records it).outside: Runbody’s commands outside the undo history (one transaction of their own, under an origin no history tracks): admitted, shown to hooks and recorded inlastas any command, but they cut no step and enter none.permits: Whether this view accepts writes now: not readonly, its document writable, and decided when it requires hydration.run: A user command: admission, the undo policy around it, and one prevention scope.each: A user command over several parts (Turn into over several blocks, Tab over several runs of siblings): each part is its own prevention scope, so a part the document refuses or an extension vetoes is skipped and the others still run, as one undo step.dispatch: Dispatch one operation.plan: Dispatchoperationas the planpreparemakes ofpayload(a hook’s replacement payload is prepared again), aboutblock(the root by default): hooks see it and each planned step before any write; one transaction; the blocks it touched normalized and the blocks it moved shown (closed toggles open); the caret it decides (plan.at: a range deletion, a placed flow) selected.lead: Runbodywithlead(a plan prepared now) composed into the first operation it dispatches: one plan, so a veto of any step, or a refusal, refuses both (a slash or markdown trigger removal with its conversion).caret: A command’s result caret: selected once and recorded on the result; the projector displays it after the flush.
DocAnchor
A selection endpoint: b is the home block of the backing text the
position lives in (NOT necessarily the block that displays it — merges and
splits reroute display while the anchor stays on the same items), a an
engine relative position whose a carries the side (< 0 left, >= 0
right). The containing stream and the side are two facts in two fields.
JSON-serializable — the presence and history wire shape.
export type DocAnchor = {
b: BlockId;
a: Anchor;
};
DocChange
One committed transaction’s semantic diff — the payload EdytorDoc.onChange
subscribers receive. Every collection names the affected ids; order
carries the NEW child-id list per changed parent so a mirror can apply the
diff without re-reading the doc. The report is shared: every subscriber
of the document (each view over it, its cells included) receives the same
object, so it is read-only.
export type DocChange = {
origin: unknown;
local: boolean;
version: number;
added: ReadonlyMap<BlockId, ProjectedBlock>;
removed: ReadonlySet<BlockId>;
moved: ReadonlySet<BlockId>;
meta: ReadonlyMap<BlockId, {
type: string;
data?: Record<string, unknown>;
}>;
content: ReadonlyMap<BlockId, readonly ContentRun[]>;
order: ReadonlyMap<BlockId | null, readonly BlockId[]>;
data?: Record<string, unknown>;
};
origin: Transaction origin (local origin object, remote marker, …).local:transaction.local— false for remote-applied updates.version: Monotonic event counter per facade.added: Newly visible blocks → full projected subtree (incl.removed: Ids no longer visible (deleted, merged-away, or hidden with subtree).moved: Ids whose display parent or sibling index changed.meta: Ids whosetype/datapayload changed → new values.content: Ids whose visible content changed → the new maintained runs.order: Parents (null= root) whose visible child list changed → new order (frozen — shared with the retained snapshot baseline).data: The document’s own data (docData()), when this commit changed it.
DocPosition
A position in one document version: a block and a display offset.
export type DocPosition = {
block: BlockId;
offset: number;
};
DocumentActor
Local actor identity — durable across replicas, independent of presence profiles.
export type DocumentActor = {
id: string;
name?: string;
color?: string;
};
id: Stable author identifier (used by awareness and, later, native attribution).
DocumentAttribution
The document-level attribution surface. actors/actorOf/block
read replicated doc state directly — they resolve remote-applied
records identically and stay correct as old replicas append records.
export type DocumentAttribution = {
readonly actors: ReadonlyMap<string, ActorProfile>;
actorOf(client: number): string | undefined;
setProfile(profile: ActorProfile): void;
block(blockId: string): BlockAttribution | undefined;
};
actors: actorId → profile — the replicated actor dictionary (u/records).actorOf: Resolve an item’s inserting replica (clientID) to an actor id (c/records).setProfile: Re-publish the local actor’s profile record (u/<actorId>— LWW).block: Compact per-block attribution forblockId({createdBy, contributors, lastChangedBy}— durable actor ids, never replica clientIDs), orundefinedfor system/foreign blocks that carry no record.
DocumentDestroyedError
Raised when an operation runs against a torn-down document — every
attach reference has been released and the facade/history/awareness
teardown already ran. Distinct from DocumentNotReadyError:
destroyed is terminal, pending may still resolve.
export declare class DocumentDestroyedError extends Error {
constructor(service?: string);
}
DocumentNotReadyError
Raised when a readiness-gated service is used on a pending document.
export declare class DocumentNotReadyError extends Error {
constructor(service: string);
}
DocumentOperations
The document’s public operations: the writes, each also prepared
(prepare.<op>(…), pure, then apply(plan); compose joins plans),
the reads, transact, onChange and version. See the document API
reference.
export interface DocumentOperations extends DocumentWrites, DocumentReads {
readonly prepare: {
readonly [K in keyof DocumentWrites]: (...args: Parameters<DocumentWrites[K]>) => Prepared;
};
apply(plan: Prepared): OpResult;
compose(...parts: Prepared[]): Prepared;
transact<R>(fn: () => R, origin?: unknown): R;
onChange(listener: (change: DocChange) => void): () => void;
readonly version: number;
}
prepare: Every write, prepared: a plan of named steps and its effect, orrefused.apply: Write a prepared plan exactly (a refusal passes through).compose: One plan ofparts, applied in order.transact: Group writes in one transaction and one change event (no rollback on a throw).onChange: Hear each transaction’s change; answers the unsubscribe.version: Changes on every write that changed the tree.
DocumentOptions
export type DocumentOptions = {
actor?: DocumentActor;
semantics?: DocumentSemanticsConfig;
awareness?: Awareness;
history?: {
captureTimeout?: number;
limit?: number;
};
requireHydration?: boolean;
};
actor: Local actor — anonymous opaque id when absent.semantics: Up-front semantic configuration (views may also seed viaadoptSemantics).awareness: Borrow an existing awareness instance instead of creating one —destroy()will NOT destroy it (same owned/borrowed rule as the doc).history: History tuning — applied when the document’s undo manager attaches.requireHydration: Decide an EMPTY document only from a provider that synced: the room answered, or a local store hydrated.
DocumentReadiness
How the document’s content state was decided — the distinction the readiness contract requires:
pending— undecided (created without a value or attached beforesync(); a provider may still hydrate — nothing bootstrapped, nothing broadcast). Reads/attaches/sync()legal,historyrefuses;local— seeded locally (createDocument({value}), orsync()on a still-fresh doc — seed-if-empty bootstrap included). Full surface legal;hydrated— the doc was already initialized whensync()ran: provider-applied remote state or aloadDocumentrestore (schema asserted, content untouched). Full surface legal.
sync() is the only transition out of pending, and it is the
document-side admission re-check: a doc that arrived in a problem
state (unversioned/unsupported/foreign — e.g. via raw
applyUpdate bypass writes) refuses there with a typed error, stays
pending, and preserves the state — never silently becoming local.
export type DocumentReadiness = 'pending' | 'local' | 'hydrated';
DocumentReads
The document’s reads (transaction-aware where the reference says so).
export interface DocumentReads {
toJSON(): JSONDoc;
docData(): JsonObj;
dataItemIds(target: DataTarget, path: readonly string[]): string[];
project(): ProjectedDoc;
blockJSON(id: BlockId): JSONBlock;
childrenIds(parent: BlockId | null): BlockId[];
parentOf(id: BlockId): BlockId | null;
ancestorsOf(id: BlockId): BlockId[];
positionOf(id: BlockId): Destination | null;
pathOf(id: BlockId): number[] | null;
blockTypeOf(id: BlockId): string | undefined;
blockDataOf(id: BlockId): Record<string, unknown> | undefined;
blockText(id: BlockId): string | null;
contentItems(id: BlockId): ContentItem[];
displayLength(id: BlockId): number;
runs(id: BlockId): readonly ContentRun[];
hasBlock(id: BlockId): boolean;
isVisibleBlock(id: BlockId): boolean;
listBlockIds(): BlockId[];
blockAttribution(id: BlockId): BlockAttribution | undefined;
isVoid(id: BlockId): boolean;
isIsland(id: BlockId): boolean;
isLines(id: BlockId): boolean;
islandOf(id: BlockId): BlockId | null;
insideIsland(id: BlockId): boolean;
isLayout(id: BlockId): boolean;
isLayoutItem(id: BlockId): boolean;
besideAt(id: BlockId, kind?: string): BlockId;
isTable(id: BlockId): boolean;
isTableRow(id: BlockId): boolean;
isTableCell(id: BlockId): boolean;
tableOf(id: BlockId): BlockId | null;
tableColumns(id: BlockId): {
id: string;
width?: number;
}[] | null;
tableGrid(id: BlockId): {
columns: string[];
rows: {
id: BlockId;
cells: (BlockId | null)[];
}[];
} | null;
order(): readonly BlockId[];
compare(a: BlockId, b: BlockId): number;
next(id: BlockId, policy?: OrderPolicy): BlockId | null;
previous(id: BlockId, policy?: OrderPolicy): BlockId | null;
canPlace(ids: readonly BlockId[], parent?: BlockId | null): boolean;
canMerge(from: BlockId, into: BlockId): boolean;
fits(parent: BlockId | null, kind: string): boolean;
nestParent(ids: readonly BlockId[], parent: BlockId): BlockId;
landingOf(id: BlockId, kind: string, after?: boolean): {
parent: BlockId | null;
levels: BlockId[];
};
anchorAt(id: BlockId, offset: number, affinity?: AnchorAffinity): DocAnchor | null;
resolveAnchor(anchor: DocAnchor): {
blockId: BlockId;
offset: number;
} | null;
}
DocumentSemanticsConfig
Document-level semantic configuration — the structural subset of what
plugin BlockDefinitions carry (roles, rendersContent, defaultChild)
plus the default block type. Snippets/DOM hooks are deliberately NOT
part of this: they are view-side rendering policy.
export type DocumentSemanticsConfig = {
roles?: Record<string, BlockRole>;
rendersContent?: Record<string, boolean>;
defaultChild?: Record<string, string>;
defaultType?: string;
marks?: Record<string, {
edge?: MarkEdge;
}>;
};
roles: Structural role per block type ({void?, island?, lines?, layout?}— absent flags mean false).rendersContent: Whether a kind renders its own content slot (undeclared kinds do).defaultChild: Default child type per parent type (undeclared parents takedefaultType).defaultType: Default block type — the root’s default child and the bootstrap block.marks: Per mark, its edge (its record’sedge) — where a concurrent insert at each end of a mark lands.
documentSnapshot
A room’s document as JSON (one authorized HTTP request), for <Edytor snapshot>.
declare const documentSnapshot: typeof snapshotOf;
DocumentWrites
The document’s writes: each one transaction, answering what it did (OpResult).
export interface DocumentWrites {
insertBlocks(dest: Destination, specs: readonly (JSONBlock | BlockSpec)[]): OpResult;
moveBlocks(ids: readonly BlockId[], dest: Destination): OpResult;
nestBlock(id: BlockId, parent: BlockId): OpResult;
unNestBlock(id: BlockId): OpResult;
unNestBlocks(ids: readonly BlockId[]): OpResult;
liftOut(id: BlockId, kind: string, options?: {
keep?: boolean;
after?: readonly BlockSpec[];
}): OpResult;
wrapInLayout(ids: readonly BlockId[], kind?: string, columns?: number): OpResult;
placeBeside(ids: readonly BlockId[], target: BlockId, side: 'left' | 'right', kind?: string): OpResult;
splitBlock(id: BlockId, offset: number, newId: BlockId, tail?: SplitTail): OpResult;
mergeBlocks(fromId: BlockId, intoId: BlockId): OpResult;
mergeBackward(id: BlockId): OpResult;
mergeForward(id: BlockId): OpResult;
deleteBlock(id: BlockId, options?: {
keepChildren?: boolean;
}): OpResult;
deleteBlocks(ids: readonly BlockId[]): OpResult;
setBlock(id: BlockId, value: {
type?: string;
data?: Record<string, unknown>;
content?: (ContentItem | JSONText | JSONInlineBlock)[];
children?: (BlockSpec | JSONBlock)[];
}): OpResult;
setBlockType(id: BlockId, type: string): OpResult;
patchData(target: DataTarget, patches: readonly DataPatch[]): OpResult;
duplicateBlock(id: BlockId, freshId?: (oldId: string, kind: 'block' | 'inline') => string): OpResult;
insertText(id: BlockId, offset: number, text: string, marks?: Record<string, unknown>): OpResult;
deleteText(id: BlockId, offset: number, length: number): OpResult;
formatRange(id: BlockId, offset: number, length: number, marks: Record<string, unknown>): OpResult;
clearMarks(id: BlockId, offset: number, length: number): OpResult;
insertInline(id: BlockId, offset: number, atom: NewInline): OpResult;
removeInline(id: BlockId, inlineId: string): OpResult;
deleteRange(from: DocPosition, to: DocPosition, view?: RangeView): OpResult;
replaceRange(from: DocPosition, to: DocPosition, view?: RangeView): OpResult;
insertFlow(target: FlowTarget, flow: Flow, view?: FlowView): OpResult;
}
duplicateBlock:freshIdnames each copy (fresh ids by default).insertInline: An atom without an id gets a fresh one.
EditorCommand
export type EditorCommand = {
id: string;
label: string;
icon?: string;
keywords?: string[];
group?: string;
hint?: string;
isEnabled?: (edytor: Edytor) => boolean;
run: (edytor: Edytor) => unknown | Promise<unknown>;
turnsInto?: (blocks: Block[]) => boolean;
searchOnly?: boolean;
};
hint: A shortcut shown beside the label (a markdown prefix such as##).turnsInto: Whether the block menu’s Turn into offers the command overblocks(the block selection as clicked); the row runs it, over that selection (the columns plugin’scolumns.<n>overnsibling blocks wraps them).searchOnly: Listed by the slash and+menus only once a query names it (the block colours:/red), never in the bare list.
EditorLabels
What the view itself says: its live announcements and a suggestion’s default name.
export type EditorLabels = {
suggestion: string;
block: (kind: string) => string;
blocks: (count: number) => string;
movedUp: (what: string) => string;
movedDown: (what: string) => string;
indented: (what: string) => string;
outdented: (what: string) => string;
moved: (what: string) => string;
deleted: (what: string) => string;
};
suggestion: A block suggestion’s accessible name when it has nolabel.block: One block in an announcement, by its kind’s label (“Heading 1 block”).blocks: Several blocks in an announcement (“3 blocks”).moved: A move to a place (a drop).
Edytor
declare const Edytor: import("svelte").Component<EdytorProps, {}, "edytor">;
type Edytor = ReturnType<typeof Edytor>;
EdytorDocData
The document’s properties (edytor.data). Augment it to type yours:
declare module 'edytor' { interface EdytorDocData { title?: string } }.
export interface EdytorDocData extends Record<string, any> {
}
EdytorDocument
The document’s operations and reads are its own members
(document.insertBlocks(…), document.toJSON()): see DocumentOperations.
transact is the document’s (its local origin by default).
export interface EdytorDocument extends Omit<DocumentOperations, 'transact'> {
}
export declare class EdytorDocument {
#private;
readonly doc: YDoc;
readonly awareness: Awareness;
readonly actor: DocumentActor;
get attribution(): DocumentAttribution;
readonly transaction: TRANSACTION;
private _destroyed;
readonly requireHydration: boolean;
get clientID(): number;
get ready(): boolean;
get readiness(): DocumentReadiness;
get destroyed(): boolean;
get writable(): boolean;
onWritableChange: (listener: (writable: boolean) => void) => (() => void);
get historyLimit(): number;
get semantics(): {
defaultType: string;
roles: Map<string, BlockRole>;
rendersContent: Map<string, boolean>;
defaultChild: Map<string, string>;
marks: Map<string, MarkEdge>;
};
defaultChild: (parentType: string | null) => string;
rendersContent: (type: string) => boolean;
adoptSemantics: (config: DocumentSemanticsConfig) => void;
sync: (value?: JSONDoc) => void;
get history(): YUndoManager;
trackOrigin: (origin: unknown) => void;
untrackOrigin: (origin: unknown) => void;
clearHistory: () => void;
transact: <T>(cb: () => T, origin?: unknown) => T;
onReady: (listener: () => void) => (() => void);
get syncPending(): boolean;
get syncRefusal(): SyncRefusedError | undefined;
onSyncRefused: (listener: (refusal: SyncRefusedError) => void) => (() => void);
private _decide;
attachSync: (sync: EdytorSync, opts?: {
value?: JSONDoc;
}) => EdytorSyncCleanup | void;
encode: () => Uint8Array;
retain: () => (() => void);
destroy: () => void;
}
doc: The underlying engine doc — raw reads/providers borrow it; they never own it.awareness: One shared awareness instance for every view/provider of this document.actor: The local actor identity (anonymous opaque id when none was supplied).attribution: The attribution surface — compact per-block records and the replicated actor dictionary (u/profiles,c/replica bindings).transaction: The document’s own local-edit transaction origin — headless edits viatransactcarry it so history capture attributes them.requireHydration:DocumentOptions.requireHydration: an empty document waits for a provider’ssynced.clientID: The replica id (doc.clientID) — identifies the replica, not the actor.ready:trueonce the document’s content state is decided — seeded locally or hydrated then synced.readiness:pending|local|hydrated— distinguishes a locally-seeded ready from a provider-hydrated/restored ready (the contract consumers need to tell “I decided this content” from “sync brought it”).writable:falsewhile the document carries a schema stamp this build cannot own (a foreign stamp got in despite the transport’s inbound refusal): every write refuses with aSchemaMismatchError, and the providers neither persist nor broadcast it.onWritableChange: Subscribe towritabletransitions — the visible signal that the document turned read-only (once per transition, not per refused edit).historyLimit: The undo steps this document’s history keeps: the configuredhistory.limit, elseDEFAULT_HISTORY_LIMIT.semantics: The document-level semantic configuration (snapshot).defaultChild: The default child type under a parent ofparentType(null= the root) — the one answer split, paragraph insert, merge-unnest, clear and root normalization apply against the new block’s actual parent.rendersContent: Whether kindtyperenders its own content slot (undeclared kinds do).adoptSemantics: Contribute document-level semantics — the seam views use to seed the structural roles implied by their plugin block definitions.sync: The readiness transition — decide the document’s content state.history: The default local history (the registry-scoped undo manager —undo/redo/stopCapturing/stacks).trackOrigin: Track a local-edit transaction origin for history capture — the seam views use to enroll their ownTRANSACTIONinstance in this document’s history.untrackOrigin: Release a previously-tracked origin — the view-teardown counterpart oftrackOrigin.clearHistory: Drop the undo/redo stacks.transact: Local document transaction — headless edits, one undo step per call (origin: another origin, which the document’s history tracks only when it is tracked).onReady: Subscribe to the readiness transition — fires once, synchronously, insidesyncwhen_readinessleaves'pending'(every decision path goes throughsync(): providersynced, local seed, explicit call).syncPending: Whether an attached provider has neither settled nor reached its bound.syncRefusal: The server’s refusal of a provider (a1008/4xxxclose — seeSyncRefusedError), the latest still standing: that provider stopped.onSyncRefused: Subscribe to provider refusals (syncRefusal).attachSync: Attach a provider sync factory to this document (headlessEdytorSyncpath — the same contract views use).encode: Full replicated state as a v14 update —loadDocumentrestores it on a fresh replica.retain: Acquire one more attach reference and return its ONE-SHOT release capability.destroy: Release ONE attach reference.
EdytorInstance
The view writes with the document’s operations (DocumentWrites), under
the same names and arguments as a bare EdytorDocument, dispatched:
readonly and document.writable admit them, every extension hook sees
them and their planned steps first, each is one transaction and the undo
policy’s step, the blocks it touched are normalized, and the caret it
decides (a range deletion, a placed flow) is selected. Each answers its
OpResult (refused when the view, a hook or the document refused it);
dispatcher.last records it.
export interface Edytor extends DocumentWrites {
}
export declare class Edytor {
#private;
node?: HTMLElement;
marks: Map<string, MarkDefinition>;
blocks: Map<string, BlockDefinition>;
inlineBlocks: Map<string, InlineBlockDefinition>;
commands: Map<string, EditorCommand>;
kinds: KindRow[];
idToBlock: Handles;
readonly: boolean;
root: Block | undefined;
get synced(): boolean;
selection: EdytorSelection;
cells: Cells | undefined;
get data(): Readonly<EdytorDocData>;
readonly overlay: Overlay;
readonly popups: Popups;
readonly announcer: AnnouncerState;
readonly labels: PartialLabels;
private off;
private onChange?;
private onDocChange?;
placeholder?: Placeholder;
get isComposing(): boolean;
readonly suggestions: Suggestions;
document: EdytorDocument;
get doc(): YDoc;
private lens?;
blockAfter: (block: Block, policy?: OrderPolicy) => Block | null;
blockBefore: (block: Block, policy?: OrderPolicy) => Block | null;
compareBlocks: (a: Block, b: Block) => number;
get awareness(): Awareness;
readonly dispatcher: Dispatcher;
transact: <T>(cb: () => T) => T;
canMove: (request: BlockMoveRequest) => boolean;
move: (request: BlockMoveRequest) => Block[];
historyUndo: () => void;
historyRedo: () => void;
constructor(options: EdytorOptions);
definitionOf: (type: string) => BlockDefinition;
private _valueCache;
get value(): JSONDoc;
runCommand: (id: string) => Promise<boolean>;
defaultChild: (parent: Block) => string;
focus: () => void;
clear: () => boolean;
presence: PresenceWriter;
destroy: () => void;
}
kinds: The kind catalogue: one row per preset of each kind record (slash, markdown, block menus).idToBlock: The view’s handles: one id-onlyBlockper live id, texts and atoms by position and id.synced: The view is bound to its decided document (its root is built).cells: What the components render: one cell per visible block, patched from change reports.data: The document’s properties, read-your-writes and reactive.overlay: The chrome layer outside the host: handles, menus, remote carets.popups: The chrome popups open on this view (surface/popups): what the root’s ARIA names.announcer: What this view announces to assistive technology (session/announcer): its block moves and deletes.labels: The words the view was given (<Edytor labels>); its plugins read their section throughlabelsOf.isComposing: A composition session is live.suggestions: The view’s suggestions (session/suggestions): proposed content, shown here until accepted.dispatcher: The view’s command dispatcher: every mutation this view makes goes through it.transact: One transaction of this view.canMove: Whether a block move (relative step or beside/inside a target) is structurally allowed.move: Move blocks one relative step, or before, after, inside or beside a live target block: resolved to the document’s move (moveBlocks, an outdent’sunNestBlocks,placeBeside) and announced.historyUndo: Undo/redo through THIS view: the only history entry points that restore a view’s selection (its recordedbefore/after); sibling views and a headlessdocument.history.undo()restore none (their carets ride the change, as for a remote undo).definitionOf: The definition of block kindtype— the one lookup (render, surface, handles).value: Full-document JSON export — memoized onfacade.version(the model-state token the facade bumps on every write and every committed update) + the root wrapper identity, so repeated reads inside one version share the one computed tree.defaultChild: The adopted default type for a new child ofparent— its actual parent.focus: Focus the editor, as a click in it would, without scrolling: the selection it has stays, and with none the caret goes to the start of the first text.clear: Replace the whole document with one empty block and put the caret in it, as one undo step.presence: This view’s presence writer: what it shares (share) and how often (throttle, ms); both settable.destroy: Edytor-lifetime teardown.
EdytorOptions
export type EdytorOptions = {
readonly?: boolean;
hotkeys?: Partial<Record<HotKeyCombination, HotKey>>;
plugins?: Plugin[];
document?: EdytorDocument;
doc?: YDoc;
awareness?: Awareness;
actor?: DocumentActor;
requireHydration?: boolean;
presence?: PresenceOptions;
sync?: boolean;
value?: JSONDoc;
onChange?: (value: JSONDoc) => void;
onDocChange?: (change: DocChange) => void;
onSelectionChange?: (selection: EdytorSelection) => void;
labels?: PartialLabels;
};
hotkeys: Chords (mod+s,shift+alt+enter) the view binds before plugins and built-ins.document: The assembled document this view renders — shared facade, history, awareness and semantics live on it (seecrdt/document.ts).actor: The local author of a view-owned document (withdocument, set it there).requireHydration: A view-owned document’srequireHydration(withdocument, set it there).presence: What this view shares of its selection with peers, and how often (edytor.presence).onChange: After every commit that changed the visible document: the value, in the shapevaluetakes (a whole-document export per commit).onDocChange: After every commit that changed the visible document: what it changed (blocks added, removed, moved, retyped, edited), with no export.labels: The words the package shows and says in this view, over the English ones: any entry of any section (richText,table,editor…), and the commands’ slashkeywordsby id.
EdytorProps
export type EdytorProps = {
plugins?: Plugin[];
defaultPlugins?: boolean;
class?: string;
'aria-label'?: string;
'aria-labelledby'?: string;
'aria-describedby'?: string;
id?: string;
edytor?: EdytorClass;
document?: EdytorDocument;
doc?: YDoc;
awareness?: Awareness;
actor?: DocumentActor;
presence?: PresenceOptions;
readonly?: boolean;
hotkeys?: Partial<Record<HotKeyCombination, HotKey>>;
onChange?: (value: JSONDoc) => void;
onDocChange?: (change: DocChange) => void;
onSelectionChange?: (selection: EdytorSelection) => void;
value?: JSONDoc;
labels?: PartialLabels;
translate?: 'yes' | 'no';
spellcheck?: boolean;
autocorrect?: 'on' | 'off';
autocomplete?: 'on' | 'off';
autocapitalize?: 'off' | 'none' | 'on' | 'sentences' | 'words' | 'characters';
inputmode?: 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url';
enterkeyhint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send';
room?: string;
server?: string;
params?: Record<string, string>;
onSyncExpired?: WebsocketSyncOptions['onExpired'];
onSyncRefused?: (refusal: SyncRefusedError) => void;
sync?: EdytorSync;
requireHydration?: boolean;
snapshot?: JSONDoc;
};
defaultPlugins: Add rich text, arrow moves, the suggestion UI and images unlesspluginslists them (defaulttrue).'aria-label': The root textbox’s accessible name (or name it witharia-labelledby).'aria-labelledby': The id(s) of the element(s) naming the root textbox (a page title, a field label).'aria-describedby': The id(s) of the element(s) describing the root textbox (a hint, an error).id: The root textbox’s id (a<label for>, a skip link).document: The assembled document this view renders (seecrdt/document.ts).actor: The local author (id, name, color) of the document this view owns: attribution and presence.presence: What this view shares of its selection with peers (share:'caret','block'or'none') and how often (throttle, ms).readonly: Refuse this view’s edits.hotkeys: Chords (mod+s,shift+alt+enter) the view binds before plugins and built-ins.onChange: After every commit that changed the visible document: aJSONDoc, the typevaluetakes.onDocChange: After every commit that changed the visible document: the change report (DocChange), with no whole-document export.value: The initial content, read once (not bindable): follow edits withonChangeoredytor.value.labels: The words the package shows and says in this view, over the English ones: any entry of any section (editor,richText,table…) and the commands’ slashkeywordsby id.inputmode: Virtual-keyboard hint — forwarded to the rootinputmodeattribute; omitted from the DOM when unset (browser default).enterkeyhint: Virtual-keyboard action-key label — forwarded to the rootenterkeyhintattribute; omitted from the DOM when unset.room: The document’s id.server: The sync server’s base URL (wss://…/rooms); the view dials<server>/<room>.params: Query parameters sent with each dial (auth token…).onSyncExpired: Withserver: the room closed the connection with4401(expired credentials).onSyncRefused: The server refused a provider of this view’s document for good (seedocument.syncRefusal): a refusal standing at mount, then each new one.sync: Advanced: a custom sync factory.requireHydration: Keep the view read-only, and seed nothing, until its document was fetched once (a provider synced): a first visit offline shows novaluethat the room’s content would later duplicate.snapshot: The document as JSON, shown read-only until this view’s document is ready: a first visit to a room-backed document paints at once — server-side too — instead of waiting for the room.
EdytorSelection
export declare class EdytorSelection {
#private;
private edytorOnSelectionChange?;
edytor: Edytor;
get focusedBlocks(): ReadonlySet<Block>;
get selectedBlocks(): ReadonlySet<Block>;
get selectedMembers(): Block[];
selectedInlineBlock: SvelteSet<InlineBlock>;
get value(): SelectionValue;
constructor(edytor: Edytor, edytorOnSelectionChange?: ((selection: EdytorSelection) => void) | undefined);
get projection(): SelectionProjection;
select: (next: SelectionValue) => void;
get caret(): Caret | null;
setCaret: (caret: Caret | null | undefined) => void;
get pending(): PendingMarks | undefined;
stage: (pending: Marks | undefined) => void;
textValue: (startText: Text, yStart: number, endText?: Text, yEnd?: number, isReversed?: boolean, startAffinity?: "left" | "right", mint?: (text: Text, offset: number, affinity?: "left" | "right") => TextAnchor | null) => SelectionValue;
get dragging(): boolean;
clearInlineBlockSelection: () => void;
selectInlineBlock: (inlineBlock: InlineBlock, from?: AtomSide) => void;
createTextAnchor: (text: Text, offset: number, affinity?: "left" | "right") => TextAnchor | null;
resolveTextAnchor: (anchor: TextAnchor) => {
text: Text;
offset: number;
} | null;
displayable: (id: string) => boolean;
hidden: (block: Block, removed?: ReadonlySet<Block>) => boolean;
shown: (block: Block, step: "blockBefore" | "blockAfter", { sealed, removed }?: {
sealed?: boolean;
removed?: ReadonlySet<Block>;
}) => Block | null;
rtl: (text: Text) => boolean;
}
focusedBlocks: The blocks the selection touches (read-only:select()keeps it).selectedBlocks: The selected blocks as clicked: a grip-selected list is one block (read-only:select()keeps it).selectedMembers: The blocks a command over the block selection acts on, in document order: the selected blocks, a selected list or code block with its whole subtree (sel.blocks.exact).value: The selection: a value — none, a text range of two anchors, one inline atom, or a set of block ids.projection: The projection of the current value at the current document version.select: Replace the selection withnext(none, a text range of two anchors, one inline atom, or a set of block ids), keeping the selected and focused blocks, presence andonSelectionChangein step.caret: The collapsed text caret in block offsets ({ block, offset }, an inline atom counting 1), ornullwhen the selection is not a caret (a range, an atom, blocks, none).setCaret: Select a caret atcaret.offsetofcaret.block(block offsets, an inline atom counting 1; clamped to the block’s content).pending: The marks the next insertion at the caret takes (values kept).stage: Stage (or, withundefined, clear) the caret’s pending marks: a new value, same anchors.textValue: A text value from wrapper endpoints (offsets inside text segments): the start binds right (boundary inserts stay outside), the end and a caret bind left;nonewhen the start text is not bound to the document.dragging: A pointer drag is in progress (a press in the host, until its release): the projector does not display under it, and the overlay chrome (block handles, column resize bands) takes no pointer, so the native selection is computed against the host.clearInlineBlockSelection: Leave an atom selection: a caret at the atom’s start (the end of the text before it), else none.createTextAnchor: Anchor a text position into the BACKING text that owns the atom at that display position — the v14 replacement forY.createRelativePositionFromTypeIndex(yText, offset, -1).resolveTextAnchor: Resolve aTextAnchor→{text, offset}(offset within the text segment).displayable: Displayable (a fact of the rendering layer): the block’s own content is mounted and not hidden by view state — a collapsed toggle’s body, ahiddensubtree.hidden: The view’s visibility rule (selection/visibility.ts), for extensions: hidden by view state, and the shown neighbour.rtl: The text’s computed direction (a Surface fact): arrow and word keys are visual.
EdytorSync
export type EdytorSync = ((payload: EdytorSyncPayload) => void | EdytorSyncCleanup) & {
bound?: number;
target?: string;
};
EdytorSyncCleanup
export type EdytorSyncCleanup = () => void | Promise<void>;
EdytorSyncPayload
Payload a sync factory receives — the v13 contract plus failed:
the terminal half of the sync lifecycle. A provider that can never reach
synced (destroyed before syncing, persistence load failure, refused
hydration, denied auth) reports it here exactly once; a transient
disconnect or a provider that already synced never does. A
SyncRefusedError is the exception: the server refused this
client for good, reported synced or not, and an empty document does not
seed over it.
export type EdytorSyncPayload = {
doc: YDoc;
awareness: Awareness;
synced: (provider?: unknown) => void;
failed?: (error: unknown, provider: unknown) => void;
armBound?: () => void;
holdBound?: () => void;
attach?: (sync: EdytorSync) => EdytorSyncCleanup | void;
};
armBound: (Re)start this provider’s readiness bound (DEFAULT_READINESS_BOUNDms from now) — for a factory whoseboundisInfinitybecause only its transport knows when waiting starts to count (the websocket sync: from the socket’s open, or its first failed dial).holdBound: Stop this provider’s readiness bound: an empty document waits for it to settle (or for the nextarmBound()), as the websocket sync does after expired credentials on a first visit.attach: Attach another sync to the same document as its own provider (its own target, bound and settle): how a sync brings a companion, e.g.
EMBED_ALLOW
The frame’s permissions (allow): what a player needs, nothing that reads the viewer.
declare const EMBED_ALLOW = "fullscreen; picture-in-picture; encrypted-media; clipboard-write";
EMBED_PROVIDERS
The default allowlist: YouTube (through youtube-nocookie.com), Vimeo,
Loom, Figma, CodePen and Spotify. A link no provider plays renders no
frame. Extend it with createEmbedPlugin({ providers: [...EMBED_PROVIDERS, mine] }).
declare const EMBED_PROVIDERS: readonly EmbedProvider[];
EMBED_SANDBOX
The frame’s sandbox: scripts and the provider’s own origin, popups that leave it; no top navigation, modals, downloads or forms.
declare const EMBED_SANDBOX = "allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-presentation";
EmbedFallbackPayload
What an embed’s fallback snippet receives: a link no allowed provider plays.
export type EmbedFallbackPayload = {
block: Block | undefined;
url: string;
link: Attachment<HTMLElement>;
};
block: The embed;undefinedin a suggestion’s preview.url: The link (http(s)), shown as a link, never in a frame.link: Its link (<a href={fallback.url} {@attach fallback.link}>): a new tab without the opener.
EmbedPluginOptions
export type EmbedPluginOptions = {
providers?: readonly EmbedProvider[];
menu?: Snippet<[
UrlPasteController
]>;
fallback?: Snippet<[
EmbedFallbackPayload
]>;
};
providers: The allowlist (defaultEMBED_PROVIDERS); a link none of them plays shows no frame.menu: The menu a URL pasted on an empty line opens (Link, Embed, Bookmark); it renders whilecontroller.open, under the line (without it, the link is kept and nothing shows).fallback: An embed whose link no allowed provider plays (a peer may store any), or a suggestion’s preview: a plain link, never a frame (default: the URL as a link):<a href={fallback.url} {@attach fallback.link}>.
EmbedProvider
One allowlisted embed provider: which links it plays, and its player.
export type EmbedProvider = {
name: string;
embed: (url: URL) => string | null;
aspectRatio?: string;
height?: number;
};
name: The provider’s name: the frame’stitle.embed: The player’s URL for a link of this provider, elsenull.aspectRatio: The frame’s CSSaspect-ratio(default16 / 9); ignored withheight.height: A fixed frame height in pixels (a music player).
embedSourceOf
The player for value (a stored or pasted link): the first provider of
providers that plays it, and its https: player URL. null when the
link is not a safeWebUrl, no provider plays it, or the player URL
a provider answers is not https: or is on the page’s own origin. The frame’s src is always this
answer, never the stored value.
declare const embedSourceOf: (value: unknown, providers?: readonly EmbedProvider[]) => {
src: string;
provider: EmbedProvider;
} | null;
EmptyBodyPayload
What the emptyBody snippet of an open toggle’s or a callout’s empty body receives.
export type EmptyBodyPayload = {
readonly block: Block;
readonly kind: 'toggle' | 'callout';
readonly label: string;
open: () => void;
readonly control: Attachment<HTMLElement>;
};
block: The toggle or callout.kind: Which kind’s body.label: The hint (the view’srichText.emptyBody).open: Start the body: a first child, the caret in it (one command, one undo step).control: The hint’s attachment ({@attach hint.control}): its marker (data-edytor-empty-body, where a dragged block drops inside),contenteditable="false",aria-hidden(Enter at the end of the header is the keyboard’s way in) and a click that starts the body.
englishLabels
The English dictionary: the default of every section. Frozen: start
yours from a copy ({ ...englishLabels.richText, checkbox: '…' }). Each
plugin reads only its own section, so an app ships the words of the
plugins it lists.
declare const englishLabels: Labels;
EquationData
The data of a block equation and of an inline one: its TeX source.
export type EquationData = {
expression?: string;
};
EquationEditor
One view’s equation editor (Notion’s): a click on an equation of an
editable view (Enter on a selected one, or a new empty one) opens its
TeX source in a field in the overlay, under it. Each keystroke writes
data.expression (one patchData command; the dispatcher groups the
typing into one undo step), so the equation itself is the live preview.
Enter (Shift+Enter: a newline in a block equation), Escape, Done, a
press or a focus outside close it; an inline equation closed empty is
removed. The editor then gives the keys back: a block equation stays
selected, the caret goes after an inline one.
What a panel snippet receives: the equation being edited (target,
display for a block one), its source (expression, written by
set), KaTeX’s error (error) and close().
Put {@attach editor.field} on the source’s field: it takes the focus
as the editor opens, Enter (Shift+Enter: a newline in a block equation)
and Escape close the editor, and focus leaving the panel closes it.
export declare class EquationEditor {
#private;
private edytor;
target: EquationTarget | null;
get readonly(): boolean;
get display(): boolean;
get expression(): string;
get error(): string | null;
open: (target: EquationTarget) => void;
editing: (target: EquationTarget) => boolean;
set: (expression: string) => void;
close: (keys?: boolean) => void;
field: Attachment<HTMLTextAreaElement | HTMLInputElement>;
}
target: The equation being edited (reactive).readonly: The view is readonly (reactive): the editor closes.display: A block equation is edited (a field of several lines).expression: The edited equation’s TeX source (reactive through its block’s cell).error: What the source draws as: KaTeX’s error, if it cannot read it.open: Open the editor ontarget: nothing in a readonly view.editing: Whethertargetis the one being edited.set: Write the source as it is typed: onepatchDatacommand per keystroke, one undo step.close: Close the editor.field: The source’s field ({@attach editor.field}, atextareaor aninput): it takes the focus as the editor opens, the caret at the end of the source.
EquationErrorPayload
What an equation’s error snippet receives.
export type EquationErrorPayload = {
display: boolean;
expression: string;
error: string;
label: string;
};
display: A block equation (true) or an inline one.expression: Its TeX source.error: KaTeX’s message.label: The view’s word for it (equation.invalid).
EquationLabels
The block and inline equations (createEquationPlugin).
export type EquationLabels = {
block: string;
inline: string;
blockPlaceholder: string;
inlinePlaceholder: string;
invalid: string;
};
block: The block preset, and the block’s accessible name.inline: The inline command.blockPlaceholder: An empty block equation.inlinePlaceholder: An empty inline equation.invalid: TeX that does not render: shown as the equation’s title, with KaTeX’s message.
EquationPlaceholderPayload
What an equation’s placeholder snippet receives.
export type EquationPlaceholderPayload = {
display: boolean;
label: string;
};
display: A block equation (true) or an inline one.label: The view’s placeholder for it (equation.blockPlaceholder,inlinePlaceholder).
EquationPluginOptions
export type EquationPluginOptions = {
katex?: KatexLoader | string | false;
macros?: Record<string, string>;
panel?: Snippet<[
EquationEditor
]>;
placeholder?: Snippet<[
EquationPlaceholderPayload
]>;
error?: Snippet<[
EquationErrorPayload
]>;
};
katex: Where KaTeX comes from, loaded the first time an equation is drawn in a browser (a server render shows the TeX source and loads nothing):macros: TeX macros every equation reads ({ '\\RR': '\\mathbb{R}' }).panel: The editor’s panel and its words (the source’s field, KaTeX’s error, Done): it renders under the edited equation whileeditor.targetis set (centered under a block equation; without it, nothing shows).placeholder: An empty equation’s placeholder (default:√xand the view’sequation.blockPlaceholderorinlinePlaceholder).error: An equation KaTeX cannot read (default: its source, marked invalid, KaTeX’s message in its title).
EquationTarget
The equation being edited: a block equation, or an inline one (atom) in block block.
export type EquationTarget = {
block: string;
atom?: string;
};
FilePluginOptions
export type FilePluginOptions = {
row?: Snippet<[
FileRowPayload
]>;
};
row: A file’s row once it has a source (default: Notion’s, a paperclip, its name and size):<a href={row.src} {@attach row.link}>, which downloads the file.
FileRowPayload
What a file block’s row snippet receives: the file it links to.
export type FileRowPayload = {
block: Block | undefined;
src: string;
name: string;
size: number | undefined;
sizeLabel: string;
link: Attachment<HTMLElement>;
};
block: The file block;undefinedin a suggestion’s preview.src: The file’s URL.name: Its name: the uploaded file’s, else its URL’s last segment.size: Its size in bytes, when it was uploaded.sizeLabel: That size in the view’s words (media.fileSize),''without one.link: The row’s link (<a href={row.src} {@attach row.link}>): it downloads the file under its name, in a new tab without the opener, never dragged out of the editor.
fillTableCell
Create the cell row shows none of for column (an id, or a
position among the grid’s; table.pad: the first edit in a padded
cell), empty, in one plan. A column the table shows but no longer
lists (table.cell) is listed again, last. ids: the new cell.
declare const fillTableCell: (doc: Omit<DocumentOperations, "transact">, row: BlockId, column: string | number) => Prepared;
findController
The find plugin’s controller of edytor; undefined when the plugin is not listed.
declare const findController: (edytor: Edytor) => FindController | undefined;
FindController
The find bar’s state and actions (plugins/find): the query, the matches
in reading order, the current one and the replacements. It reads the
document through the facade and the handles and writes it through one
dispatcher command; the bar and the highlights render from it.
export declare class FindController {
#private;
private edytor;
isOpen: boolean;
replacement: string;
constructor(edytor: Edytor);
get query(): string;
set query(value: string);
get caseSensitive(): boolean;
set caseSensitive(value: boolean);
readonly matches: FindMatch[];
readonly current: number;
get canReplace(): boolean;
open: (query?: string) => void;
close: ({ select }?: {
select?: boolean;
}) => void;
next: () => void;
previous: () => void;
replace: () => CommandResult["status"];
replaceAll: () => CommandResult["status"];
field: Attachment<HTMLInputElement>;
replaceField: Attachment<HTMLInputElement>;
destroy: () => void;
}
replacement: The replacement text.query: What the bar searches for; setting it shows the current match.query: What the bar searches for; setting it shows the current match.caseSensitive: Match case exactly (default: ignore it).caseSensitive: Match case exactly (default: ignore it).matches: Every match while the bar is open, in reading order (none while closed).current: The current match’s index inmatches;-1when there is none.canReplace: Replacing is possible: an editable view of a writable document.open: Open the bar (or focus it again) and search from the caret.close: Close the bar.next: The next match becomes the current one (after the last, the first).previous: The previous match becomes the current one (before the first, the last).replace: Replace the current match, then go to the next one.replaceAll: Replace every match: one command, one undo step.field: The query field ({@attach find.field}): focused on each Mod+F; Enter, Shift+Enter, Escape, Mod+F.replaceField: The replacement field ({@attach find.replaceField}): Enter replaces, Escape closes.destroy: Stop following the document (the editor is going away).
FindMatch
One occurrence: length characters of block block from display offset offset (atoms count 1).
export type FindMatch = {
block: string;
offset: number;
length: number;
};
findMatches
Every occurrence of query in the document’s text, in reading order
(pre-order, a closed toggle’s body included: what hides it is view state,
not the document). A match lies in one text segment: an inline atom
(a mention) ends it, and marks never do. Matches do not overlap.
declare const findMatches: (edytor: Edytor, query: string, { caseSensitive }?: FindOptions) => FindMatch[];
FindOptions
export type FindOptions = {
caseSensitive?: boolean;
};
caseSensitive: Match case exactly (default: ignore it, as Notion and the browsers do).
FindPluginOptions
export type FindPluginOptions = {
bar?: Snippet<[
FindController
]>;
};
bar: The bar’s markup and its words; it renders whilefind.isOpen, placed at the editor’s top right (without it, Mod+F searches and highlights, and nothing else shows).
Flow
An admitted flow: lines in order, ids fresh; whole: a block-selection copy.
export type Flow = {
lines: FlowLine[];
whole?: boolean;
};
FlowLine
One line of a flow: a kinded block, or an inline run when it has no type.
export type FlowLine = Omit<BlockSpec, 'type'> & {
type?: string;
};
FlowTarget
A position, the blocks the flow replaces (flow.slot), or a slot (flow.place).
export type FlowTarget = DocPosition | {
replace: readonly BlockId[];
} | {
slot: Destination;
};
FlowView
What the view tells flow placement beyond range deletion’s view: a
container’s flat item kind (numbered-list-item for an ordered-list).
export type FlowView = RangeView & {
itemKind?: (parent: BlockId) => string | undefined;
header?: (id: BlockId) => boolean | 'closed';
};
Handles
export declare class Handles {
#private;
private edytor;
readonly root: Block;
constructor(edytor: Edytor);
block: (id: string) => Block;
get: (id: string) => Block | undefined;
has: (id: string) => boolean;
text: (blockId: string, ordinal: number) => Text;
textById: (id: string) => Text | undefined;
atom: (blockId: string, atomId: string) => InlineBlock;
parts: (id: string) => ContentPart[];
prune: (change: DocChange) => void;
clear: () => void;
}
block: The handle of blockid, live or not (the one a caller held stays the same object).get: The handle of live blockid; none for a dead or unknown id.text: Theordinal-th text segment of blockblockId.textById: The text whose id isid(t:<block>:<ordinal>), while its segment exists.atom: The handle of atomatomId, shown in blockblockId.parts: Blockid’s content parts — from its runs, which the index keeps per block and reads-your-writes (a write elsewhere re-derives nothing here).prune: Forget the handles of the blocks a commit removed.
HistoryClient
A room’s version history, as the history panel reads and restores it.
export type HistoryClient = {
list(): Promise<HistoryVersion[]>;
read(key: string): Promise<JSONDoc | null>;
restore(key: string): Promise<HistoryRestoreResult>;
undo(): Promise<HistoryUndoResult>;
};
list: The versions, newest first.read: A version as JSON, ornullwhen the room holds no such version.restore: Restore a version as a forward edit every client receives.undo: Undo the last restore (one level).
HistoryClientOptions
Options of createHistoryClient.
export type HistoryClientOptions = {
server: string;
room: string;
params?: Record<string, string> | (() => Record<string, string>);
fetch?: typeof fetch;
};
server: The base URL of the history route: requests go to<server>/<room>, the room percent-encoded as one path segment (ws:/wss:read ashttp:/https:).room: The document’s id.params: Query parameters sent with each request (a token): an object, or a function read at every request.fetch: The fetch to use (default the global one).
HistoryRequestError
A history request the server refused: status is its HTTP status (401, 403, 404, 503…).
export declare class HistoryRequestError extends Error {
readonly status: number;
constructor(status: number, message: string);
}
HistoryRestoreResult
What a restore did: refused when the room holds no such version.
export type HistoryRestoreResult = {
status: 'applied' | 'noop' | 'refused';
key: string;
revived?: number;
moved?: number;
rewritten?: number;
created?: number;
deleted?: number;
};
HistoryUndoResult
What undoing the last restore did: noop when there is none to undo.
export type HistoryUndoResult = {
status: 'applied' | 'noop';
};
HistoryVersion
One stored version, as the room lists it (edytor/cloudflare’s HistoryEntry).
export type HistoryVersion = {
key: string;
date: string;
slot: 'am' | 'pm';
bytes: number;
blocks: number;
editors: string[];
more: number;
at: number;
expiresAt: number | null;
};
key: The version’s key, whichreadandrestoretake.date: The local date of its slot,YYYY-MM-DD, in the room’s time zone.slot: Its half of the day:ambefore local noon,pmuntil midnight.bytes: Stored (compressed) bytes.blocks: Visible blocks.editors: The users whose edits the room stored during the slot.more: Editors left out ofeditorsto keep the metadata small.at: When the version was written (ms since the epoch).expiresAt: When it expires (ms since the epoch),nullwhen its store does not say.
HotKey
export type HotKey = (payload: {
event?: KeyboardEvent;
edytor: Edytor;
prevent: (cb?: () => void) => void;
}) => void;
HotKeyCombination
A chord: a key, after up to three modifiers in any order (matched case-insensitively).
export type HotKeyCombination = Key | `${Modifiers}+${Key}`;
ImageBox
The hovered image, layer-relative: the img box, and its frame’s width (the most it may take).
export type ImageBox = {
id: string;
x: number;
y: number;
width: number;
height: number;
align: ImageAlign;
};
ImageControls
The image chrome (Notion’s), in the overlay: on the image under
the pointer of an editable view, a resize handle on each side and a
toolbar (alignment, alt text). Dragging a handle resizes the image live
and writes nothing: the width is a view-only preview the kind’s snippet
reads (width), so only this view sees it and no peer gets a frame; the
release writes data.width (CSS px, one patchData command, one undo
step). A centered image grows on both sides (twice the pointer’s move),
an image aligned left or right from the side the handle is on. It is
held between MIN_IMAGE_WIDTH and its block’s width. Escape, a cancel or
the view turning readonly drop the preview at once. Positions are read in
the overlay’s measure, once per frame.
export declare class ImageControls {
#private;
hovered: string | null;
box: ImageBox | null;
constructor(edytor: Edytor);
get drag(): Drag | null;
get readonly(): boolean;
get selecting(): boolean;
get selected(): string | null;
get target(): string | null;
get shown(): boolean;
}
hovered: The image block under the pointer.box: The measured box of the image the chrome is on.drag: The handle being dragged.readonly: The view is readonly (reactive).selecting: A text selection is in progress: the chrome takes no pointer.selected: The image block that is the whole block selection, if any (reactive): the keyboard’s image.target: The image the chrome is on: the dragged one, the one whose alt is edited, the hovered one, else the selected one (no pointer needed).shown: Whether the chrome shows: over an image of an editable view, or while a handle drags.
ImageLabels
export type ImageLabels = {
image: string;
};
image: The preset.
ImageToolbar
What a toolbar snippet receives: the image the chrome is on (box),
its alignment (align) and alt text (alt, setAlt), the alt field’s
state (editing, toggleAlt, closeAlt) and the attachments that give
your markup its behavior (bar, altField).
export declare class ImageToolbar {
#private;
constructor(controls: ImageControls);
get box(): import("./controls.svelte.js").ImageBox | null;
get readonly(): boolean;
get editing(): string | null;
set editing(id: string | null);
align: (align: ImageAlign) => void;
get alt(): string;
setAlt: (alt: string) => void;
toggleAlt: () => void;
bar: Attachment<HTMLElement>;
altField: Attachment<HTMLInputElement>;
closeAlt: (keys?: boolean) => void;
}
box: The measured image the toolbar is on (null: none).readonly: The view is readonly (reactive).editing: The image whose alt field is open: the chrome stays on it.editing: The image whose alt field is open: the chrome stays on it.align: Set the target image’s alignment: onepatchDatacommand.alt: The target image’s alt text.setAlt: Write the alt text as it is typed: the dispatcher groups the keystrokes into one step.toggleAlt: Open (or close) the alt field of the target image.bar: The image’s toolbar ({@attach controls.bar}, named by your markup): itstoolbarrole unless it has its own, the mark a press outside the alt field reads (a press on the toolbar keeps the field open, so its Alt button toggles it), and presses that keep the editor’s focus.altField: The alt text field ({@attach controls.altField}): focused when it opens; Enter or Escape closes it and gives the editor its keys back, focus moving out of it (or of the element markeddata-edytor-image-alt-panelaround it) closes it, and a press on it keeps it open.closeAlt: Close the alt field; withkeys, the editor takes the keys back.
ImageToolbarOptions
export type ImageToolbarOptions = {
toolbar: Snippet<[
ImageToolbar
]>;
};
toolbar: The toolbar over the hovered image (the alignments and the alt field); it renders while the image chrome shows and no handle drags, at the image’s top right.
IndexeddbSyncOptions
export type IndexeddbSyncOptions = CrdtIndexeddbSyncOptions;
InitializedPlugin
export type InitializedPlugin = ReturnType<Plugin>;
InlineBlock
An id-only, read-only inline-atom handle: atom id, shown in block
blockId. Getters read the document index; writes are the view’s
operations (edytor.patchData({ block: atom.blockId, atom: atom.id }, …)).
node is the element that renders it (a Surface fact). A reactive reader
of a getter depends on the block’s cell, so it re-runs when a commit
changes the block’s content.
export declare class InlineBlock {
#private;
readonly: boolean;
readonly edytor: Edytor;
readonly id: string;
blockId: string;
node: HTMLElement | undefined;
constructor(edytor: Edytor, blockId: string, id: string);
get parent(): Block;
get selected(): boolean;
get type(): string;
get data(): Readonly<Record<string, any>>;
get definition(): InlineBlockDefinition;
get index(): number;
get isInDocument(): boolean;
get value(): JSONInlineBlock;
}
blockId: The block that shows the atom (the view updates it when the atom moves).data: The atom’sdata; a reactive reader depends on its block’s cell.index: Position among the block’s content parts.isInDocument: The atom is shown by a live block.
InlineBlockDefinition
export type InlineBlockDefinition = {
snippet: Snippet<[
InlineBlockSnippetPayload<any>
]>;
plain?: (data: JSONInlineBlock['data']) => string;
html?: (data: JSONInlineBlock['data']) => string;
parse?: (element: HTMLElement) => Record<string, SerializableContent> | undefined;
};
plain: Clipboard plain-text form of the atom; default none.html: Clipboard HTML form of the atom (inline markup, escaped by you); default an empty<span data-edytor-inline-block>.parse: HTML import: the atom’s data whenelement, met in a line’s inline content, is this atom, elseundefined.
InlineBlockSnippetPayload
export type InlineBlockSnippetPayload<D = Record<string, any>> = {
block: InlineBlockView<D>;
};
InlineBlockView
What an inline-atom snippet receives: declared values; a suggestion’s atom is never selected.
export type InlineBlockView<D = Record<string, any>> = {
readonly id: string;
readonly type: string;
readonly data: Readonly<D>;
readonly selected: boolean;
readonly handle: InlineBlock | undefined;
};
handle: The atom’s id-only handle; a suggested atom has none.
InlineSpec
export type InlineSpec = {
id: string;
type: string;
data?: Record<string, unknown>;
};
InputRule
Text typed at a caret that matches find is replaced (:smile: → 😄,
## → a heading): Notion’s markdown and autoformat. Rules run for text
typed at a collapsed caret (one insertText at it, an IME’s commit
excluded), in plugin order then list order; the first that writes wins.
They do not run in the lines of a code block.
export type InputRule = {
find: RegExp;
replace: (match: RegExpExecArray, ctx: InputRuleContext) => string | boolean | null | void;
};
find: Tested on the caret’s text segment up to the caret, followed by the typed text; a match must end at the typed text’s end (end it with$).replace: The replacement.
InputRuleContext
What an input rule’s replace receives besides the match.
export type InputRuleContext = TextRuleContext & {
typed: string;
remove: (then?: () => unknown) => boolean;
};
insertTableColumn
Insert a column at index among table’s listed columns
(table.insert-column): a new id in data.columns and an empty cell
for it in every row the table shows, in one plan. width: the new
column’s. ids: the table.
declare const insertTableColumn: (doc: Omit<DocumentOperations, "transact">, table: BlockId, index: number, width?: number) => Prepared;
insertTableRow
Insert a row at index among table’s rows (table.insert-row): one
empty cell per column, in one plan. ids: the new row.
declare const insertTableRow: (doc: Omit<DocumentOperations, "transact">, table: BlockId, index: number) => Prepared;
isPrevention
Whether error is a veto (a PreventionError).
declare const isPrevention: (error: unknown) => error is PreventionError;
JSONBlock
export type JSONBlock = {
type: string;
id?: string;
data?: Record<string, SerializableContent>;
children?: JSONBlock[];
content?: (JSONText | JSONInlineBlock)[];
};
JSONDoc
export type JSONDoc = {
data?: Record<string, SerializableContent>;
children: JSONBlock[];
} & Partial<Record<string, unknown>>;
JSONInlineBlock
export type JSONInlineBlock = {
id?: string;
data?: any;
type: string;
};
JSONText
export type JSONText = {
text: string;
marks?: Record<string, SerializableContent>;
};
KATEX_CDN
Where the default loader fetches KaTeX: jsDelivr’s copy of the npm
package’s dist, at KATEX_VERSION exactly (katex.mjs,
katex.min.css, and the fonts the stylesheet names).
declare const KATEX_CDN = "https://cdn.jsdelivr.net/npm/katex@0.18.7/dist/";
KATEX_VERSION
The KaTeX version the default loader fetches: the one edytor is tested with.
declare const KATEX_VERSION = "0.18.7";
KatexLike
The part of KaTeX the plugin uses: katex.renderToString.
export type KatexLike = {
renderToString: (tex: string, options?: Record<string, unknown>) => string;
};
KatexLoader
Loads KaTeX when an equation first shows: () => import('katex') from your bundle.
export type KatexLoader = () => Promise<KatexLike | {
default: KatexLike;
}>;
Key
A key a chord names: a one-character key or a named one, lower case.
export type Key = Chars<typeof CHARS> | (typeof NAMED)[number];
KindMenuAction
A row a kind adds to the block menu (BlockDefinition.menu).
export type KindMenuAction = {
id: string;
label: string;
icon?: string;
checked?: boolean;
isEnabled?: () => boolean;
run: () => unknown;
};
icon: An icon id (iconOf), else none.checked: A switch’s state: the row is amenuitemcheckbox, and the menu stays open when it runs.run: The row’s action.
KindPreset
A way to create a block kind: one slash command, markdown prefixes, one block-menu row.
export type KindPreset = {
label: string;
icon?: string;
keywords?: string[];
data?: Record<string, SerializableContent>;
markdown?: string[];
markdownFrom?: Record<string, string[]>;
group?: string;
};
data: The new block’s data.markdown: Typed at the start of a block’s first text, each converts it (the last character triggers).markdownFrom: Shortcuts that convert a block of another kind into this preset, by that kind, typed asmarkdownis:{ toggle: ['# '] }makes a toggle list a toggle heading.group: The slash menu section (defaultBasic blocks).
KindRecord
A kind record as a plugin declares it (plugin.blocks[type]): its
structural row with whatever else it holds (a snippet, an element,
presets), or a bare snippet, a plain kind.
export type KindRecord = KindSemantics | ((...args: never[]) => unknown);
KindRow
The kind catalogue: one row per preset of each registered kind record, in registration order. The slash menu, markdown shortcuts and block menus read it; nothing else names a kind.
export type KindRow = KindPreset & {
id: string;
value: JSONBlock;
replaces: boolean;
command?: EditorCommand;
};
KindSemantics
One kind’s structural row — the fields of its BlockDefinition a document adopts.
export type KindSemantics = BlockRole & {
rendersContent?: boolean;
defaultChild?: string;
};
KindUpload
What files a kind takes (its record’s upload, BlockDefinition): which,
and the data an upload’s answer writes. The view’s uploader, if the
uploads plugin is listed, sends them.
export type KindUpload = {
accepts: (file: File) => boolean;
fallback?: boolean;
data: (file: File, answer: string) => Record<string, unknown> | null;
filled: (data: Record<string, unknown>) => boolean;
preview?: boolean;
};
accepts: Whether it takesfile.fallback: It takes any file no other kind takes (the file block): asked last.data: The data the answered URL writes (null: no source this kind stores).filled: Whether the block has a source already (a peer’s, a link’s): its upload then writes nothing.preview: Show the file while it uploads (an image’sblob:preview).
Labels
Every section of the dictionary.
export type Labels = {
editor: EditorLabels;
richText: RichTextLabels;
blockMenu: BlockMenuLabels;
image: ImageLabels;
media: MediaLabels;
code: CodeLabels;
columns: ColumnsLabels;
page: PageLabels;
toc: TocLabels;
pageLink: PageLinkLabels;
comments: CommentsLabels;
table: TableLabels;
equation: EquationLabels;
};
LanguageMenu
One view’s code language list (Notion’s): the header’s language button
opens it in the overlay under the button, a search field over the
languages (by label or id), the block’s own marked. The field holds the
keys: the arrows move the highlighted row, Enter picks it, Escape
closes. A pick writes the block’s data.language (one patchData
command, one undo step). Opened by the keys, the list gives them back to
its button; opened by a press, to the editor. A press or a focus outside
closes it, as readonly does.
What a menu snippet receives: the open list (open), the query
and its rows (query, items, each a MenuItemPayload), the keyboard’s
row (index), search, pick and close, and
the attachments that give it the keys and the ARIA: keys on the
search field, popup on the list, option(index) on each row.
export declare class LanguageMenu extends ChromeList {
#private;
open: {
block: string;
keys: boolean;
} | null;
query: string;
get readonly(): boolean;
get current(): string | null;
get rows(): readonly LanguageRow[];
get items(): LanguageMenuItem[];
protected get count(): number;
isOpenFor: (block: string) => boolean;
toggle: (block: string, keys: boolean) => void;
search: (query: string) => void;
pick: (row?: LanguageRow | undefined) => void;
close: (keys?: "button" | "editor" | null) => void;
keys: Attachment<HTMLElement>;
}
open: The code block whose list is open, and whether the keys opened it (reactive).query: The search field’s text.readonly: The view is readonly (reactive): the list closes.current: The open block’s language id.rows: The rows the query keeps: a language the plugin does not list first (the block’s own), then the list.items: The rows for yourmenu: each the row shape every menu shares (runpicks it,selecthighlights it,optionits attributes), and whether it is the block’s language (current).count: How many rows the keyboard walks.isOpenFor: Whetherblock’s list is open.toggle: Openblock’s list (keys: the keyboard opened it), or close it when it is open.search: A new query: the first row it keeps is highlighted.pick: Pickrow(the highlighted one): the block’s language, one undo step; the list closes.close: Close the list; the keys go back to its button (button), to the editor (editor: its caret where it was), or stay where a press or a focus put them (null).keys: The search field ({@attach menu.keys}): it takes the focus as the list opens and is itscombobox(unless it has a role), naming the list and the highlighted row (aria-controls,aria-activedescendant).
LanguageMenuItem
One row of the language list, for your menu: the row shape every
menu shares (item is the language, run picks it), and whether it is
the block’s own language (current, the demo’s ✓).
export type LanguageMenuItem = MenuItemPayload<LanguageRow> & {
current: boolean;
};
LanguageRow
A row of the language list: a language id and its label as shown.
export type LanguageRow = {
id: string;
label: string;
};
lastUpdated
When a room last stored a change: one authorized HTTP request, no document opened.
declare const lastUpdated: typeof lastUpdatedOf;
LastUpdatedOptions
What lastUpdated takes: the room it asks, and the params (a token) its dial would carry.
export type LastUpdatedOptions = WebsocketTarget & {
params?: Record<string, string>;
fetch?: typeof fetch;
};
loadCodeLanguage
Load language’s grammar (and those it embeds); resolves once its lines highlight.
declare const loadCodeLanguage: (language: CodeLanguage) => Promise<void>;
loadDocument
See DocumentBinding.loadDocument.
declare const loadDocument: (update: Uint8Array, options?: LoadDocumentOptions) => EdytorDocument;
LoadDocumentOptions
export type LoadDocumentOptions = DocumentOptions;
MarkDefinition
export type MarkDefinition = {
snippet?: Snippet<[
MarkSnippetPayload<any>
]>;
tag?: string;
attributes?: (value: any) => Record<string, string | undefined>;
parse?: (element: HTMLElement) => SerializableContent | undefined;
void?: boolean;
edge?: MarkEdge;
copy?: boolean;
toolbar?: {
label: string;
icon: string;
run?: (edytor: Edytor) => void;
};
};
snippet: Custom markup inside a core<span data-edytor-mark>; without one the core renderstag.tag: The mark’s element: rendered by the core (<tag data-edytor-mark>), written by the clipboard’s HTML export and read back by HTML import — one tag, so render and export cannot disagree.attributes: The element’s attributes from the mark’s value (sanitized here);undefinedomits one.parse: HTML import: the mark’s value whenelementcarries it, elseundefined.edge: Whether typing at the mark’s edges extends it (marksForInsertion); defaultinclusive.copy: Whether copy and cut carry the mark (defaulttrue):falsecopies the text without it (a comment’s anchor: the copy is not commented).toolbar: A selection-toolbar button: it toggles the mark, or runsruninstead (the comments plugin’s Comment button opens its composer).
MarkEdge
Whether a mark grows at its edges — always, never, or at its start only.
export type MarkEdge = 'inclusive' | 'exclusive' | 'side-dependent';
MarkSnippetPayload
Represents the payload for mark snippets with generic serializable content.
export type MarkSnippetPayload<D extends SerializableContent = SerializableContent> = {
content: Snippet;
mark: D;
text: Text;
};
MarqueeBoxPayload
What a box snippet receives: the rectangle and what it selects (reactive).
export type MarqueeBoxPayload = {
rect: DOMRectReadOnly;
count: number;
ids: readonly string[];
adding: boolean;
};
rect: The rectangle, in viewport (client) coordinates.count: How many blocks are selected (the ones Shift or Mod kept included).ids: The selected blocks’ ids, in the order the selection lists them.adding: Shift or Mod was held at the press: the rectangle adds to the selection there was.
MarqueePluginOptions
export type MarqueePluginOptions = {
container?: string | ((editor: HTMLElement) => Element | null | undefined);
threshold?: number;
box?: Snippet<[
MarqueeBoxPayload
]>;
};
container: An element around the editor whose empty area starts a marquee too (the page, as in Notion): a CSS selector the editor’s element is matched against withclosest(), or a function of the editor’s element.threshold: How far (px) the pointer moves before the rectangle starts: a shorter press is a click.box: The rectangle’s markup; it renders in a host placed and sized to the rectangle (without it, the blocks are selected and no rectangle shows).
MAX_INLINE_IMAGE_BYTES
The largest inline image (data: URL) an image block takes: 1 MiB of its
URL. An inline image is stored in the document and sent in every
update and catch-up that carries the block, so a larger one would bloat
the room’s frames and storage: host the file instead (the plugin’s
upload option) and embed its URL.
declare const MAX_INLINE_IMAGE_BYTES: number;
Measure
Reads layout with the layer’s origin; returns the writes to apply after every read.
export type Measure = (origin: DOMRect) => (() => void) | void;
MediaEmptyController
An empty image or media block’s state (an image, an embed, a bookmark, a
file, a video, an audio track: Notion’s “Add …”): the panel’s open
state, the link field’s draft, why the last link or upload was not
embedded (failed), and the upload of a pasted or dropped file
(pending); embed and upload set the source. What an image or media
plugin’s empty snippet receives (one per empty block shown).
export declare class MediaEmptyController {
#private;
readonly block: Block | undefined;
readonly kind: MediaKind | 'image';
private view;
open: boolean;
draft: string;
failed: MediaEmptyFailure | null;
constructor(block: Block | undefined, kind: MediaKind | 'image', view: EmptyView | undefined);
get icon(): string;
get accept(): string | undefined;
get readonly(): boolean;
get canUpload(): boolean;
get pending(): FileUpload | undefined;
embed: (value?: string) => void;
upload: (picked: File) => Promise<void>;
field: Attachment<HTMLInputElement>;
}
block: The empty block; none in a suggestion’s preview.kind: Which kind it is (your words for it: its button, placeholder, errors).open: The link panel is open.draft: The link field’s text.failed: Why the last link or upload was not embedded (null: it was, or none was tried).icon: The kind’s icon (an emoji).accept: The file picker’saccept:image/*,video/*,audio/*, else any file.readonly: No control may write: a readonly view, or a suggestion’s preview (no block).canUpload: The view lists the uploads plugin and the kind takes files: offer Upload.pending: The upload of a pasted or dropped file into this block, if any (itsstatus,progress,preview).embed: Embedvalue(default thedraft): the block takes the link, orfailedsays why not.upload: Uploadfilewith the view’s uploader (createUploadsPlugin) and store the URL it answers, only on the block as it was picked from: still in the tree, of its kind and empty (a peer may have set a source, or deleted it, meanwhile).field: The link field ({@attach empty.field}): Enter embeds itsdraft.
MediaEmptyFailure
Why the last link or upload was not embedded: no source the kind takes
(invalid), an inline image over the cap (inline), a failed upload.
export type MediaEmptyFailure = 'invalid' | 'inline' | 'upload';
MediaEmptyOptions
export type MediaEmptyOptions = {
empty: Snippet<[
MediaEmptyController
]>;
};
empty: An empty image’s or media block’s markup and its words (Notion’s “Add an image”, “Embed a video”… and their link panel, a pasted or dropped file’s upload, or a readonly view’s placeholder): it receives the block’sMediaEmptyController(kindsays which: image, embed, bookmark, file, video, audio).
MediaKind
The media kinds.
export type MediaKind = 'embed' | 'bookmark' | 'file' | 'video' | 'audio';
MediaLabels
The media kinds’ presets, and a file’s size.
export type MediaLabels = {
embed: string;
bookmark: string;
file: string;
video: string;
audio: string;
fileSize: (bytes: number) => string;
};
fileSize: A file’s size, in bytes.
MemoryCommentsClient
A memory client, and a sibling sharing its threads as another user (as).
export type MemoryCommentsClient = CommentsClient & {
as(user: string, options?: {
moderator?: boolean;
}): MemoryCommentsClient;
};
MemoryCommentsClientOptions
Options of createMemoryCommentsClient.
export type MemoryCommentsClientOptions = {
user: string;
moderator?: boolean;
now?: () => number;
};
user: The user its requests are made as.moderator: May delete anyone’s comment.now: The clock (defaultDate.now).
MentionData
What a mention atom stores.
export type MentionData = {
id: string;
label: string;
};
MentionItem
A person the mention menu offers: what your app’s directory answers.
export type MentionItem = {
id: string;
label: string;
description?: string;
avatar?: string;
};
id: The person’s id in your app: stored in the atom.label: The name shown in the menu and in the atom.description: A line under the name in the menu (an email, a role).avatar: An avatar image URL (http, https or data).
MentionPluginOptions
export type MentionPluginOptions = {
items: (query: string, ctx: TriggerContext) => readonly MentionItem[] | Promise<readonly MentionItem[]>;
char?: string;
menu?: Snippet<[
TriggerMenuController<MentionItem>
]>;
};
items: The people matchingquery(the text typed after@), at once or as a promise.char: The character that opens the menu (default@).menu: The menu’s markup and its words; it renders whilecontroller.isOpen, at the caret (without it, nothing shows).
MenuItemPayload
One row of a chrome list a controller builds for the app’s markup: the same shape in every menu (a table grip’s, the code language list’s).
export type MenuItemPayload<T> = {
item: T;
id: string;
selected: boolean;
run: () => void;
select: () => void;
option: Attachment<HTMLElement>;
};
item: What the row stands for: a command, a trigger’s row, a version.id: The row’s element id (page-unique): the keyboard’s owner names the highlighted row by it.selected: It is the keyboard’s row.run: Run it (pick the row).select: Make it the keyboard’s row (hover).option: The row’s attachment ({@attach item.option}): itsid,role,aria-selected(oraria-checked) andtabindex, and the keyboard’s row scrolled into view.
MergedSemantics
The merged config’s fields, each present (empty when no config named one).
export type MergedSemantics = {
roles: Record<string, BlockRole>;
rendersContent: Record<string, boolean>;
defaultChild: Record<string, string>;
marks: Record<string, {
edge?: MarkEdge;
}>;
defaultType?: string;
};
mergeSemantics
Several configs as one, deeply frozen, merged field by field (roles,
rendersContent, defaultChild, marks, defaultType): the room’s or
a headless document’s roles for an app’s own kinds beside the bundled
ones, mergeSemantics(defaultSemantics, semanticsOf(myKinds)). A
top-level spread of two configs would keep only the last one’s fields.
A kind (or mark, or the default type) two configs declare otherwise
throws SemanticConflictError, as a view’s conflicting plugin does
on a document; an equal declaration merges.
declare const mergeSemantics: (...configs: DocumentSemanticsConfig[]) => MergedSemantics;
MetaClientState
export type MetaClientState = {
clock: number;
lastUpdated: number;
};
lastUpdated: unix timestamp
Modifier
A chord’s modifier (mod: Cmd on Apple platforms, Ctrl elsewhere).
export type Modifier = 'mod' | 'alt' | 'ctrl' | 'shift';
Modifiers
One to three distinct modifiers, in any order.
export type Modifiers = {
[A in Modifier]: A | {
[B in Exclude<Modifier, A>]: `${A}+${B}` | `${A}+${B}+${Exclude<Modifier, A | B>}`;
}[Exclude<Modifier, A>];
}[Modifier];
moveTableColumn
Move a listed column to position to among them (table.move-column):
one order patch of data.columns; no cell moves, every row shows its
cells in the new order. ids: the table.
declare const moveTableColumn: (doc: Omit<DocumentOperations, "transact">, table: BlockId, column: string | number, to: number) => Prepared;
moveTableRows
Move rows to position to among their table’s rows
(table.move-row): a move. Refused unless every one is a row of the
same table. ids: the rows.
declare const moveTableRows: (doc: Omit<DocumentOperations, "transact">, rows: readonly BlockId[], to: number) => Prepared;
OpResult
The observed outcome of one document operation — the one result
shape every op returns, empty inputs included. refused: the op did not
apply and wrote nothing; noop: it applied and changed nothing; applied:
the transaction wrote. Read from the transaction’s effects, never predicted.
export type OpResult = {
readonly status: 'refused' | 'noop' | 'applied';
readonly ids: readonly BlockId[];
readonly reason?: string;
};
ids: What the op is about — created, moved, merge target, or its target; empty unless applied.reason: Why a refused op refused, when it names a reason ('id-collision').
OrderPolicy
Island-sealing policy for a walk in document order (see next).
export type OrderPolicy = {
sealed?: boolean;
};
Overlay
export declare class Overlay {
#private;
layer: HTMLElement | null;
observe: (node: Element) => () => void;
add: (measure: Measure) => () => undefined;
invalidate: () => void;
mount: <Props extends Record<string, unknown>>(component: Component<Props>, props: Props, name: string, zIndex: number, measure: (host: HTMLElement, origin: DOMRect) => (() => void) | void) => () => void;
attach: (host: HTMLElement) => () => void;
}
layer: The layer: absolutely positioned at its static place right after the host.observe: Invalidate whenevernode(a block’s element) changes size: what lays the blocks out again with no commit, no scroll and no resize of the host — a font or a stylesheet that loads, a kind’s markup drawn later (KaTeX, an image), a host whose height its page fixes — moves the blocks under the chrome.add: Runmeasureon every invalidated frame, starting with the next one.invalidate: Position the chrome in the next frame (once, however many invalidations).mount: Mountcomponentin a fixed host of the layer (data-<name>), placed bymeasureon every invalidated frame — a measure like any other: it reads (the host, the layer’s origin) and returns the host’s style writes.attach: Create the layer afterhost; invalidated by resizes (the host’s, its blocks’, the window’s) and by scrolls of any container.
PageBlockLinkPayload
What a page block’s link snippet receives: the page it links to.
export type PageBlockLinkPayload = {
block: Block | undefined;
pageId: string | null;
title: string | undefined;
label: string;
href: string | undefined;
openable: boolean;
open: (newTab?: boolean) => void;
control: Attachment<HTMLElement>;
};
block: The page block;undefinedin a suggestion’s preview (it opens nothing).pageId: The page’s id (null: the block names none).title: Its title: yourtitlelookup’s answer, else the one the block stores.label: What it reads: its title, else the view’spage.untitled.href: The page’s URL (href), if any: draw ana.openable: A click may open it: it names a page and the plugin hasopen.open: Open the page (open), in a new tab whennewTab.control: The link’s attachment ({@attach link.control}): its marker (data-edytor-page-link), the click and middle click that open it; on ana,draggable="false"; on abutton,disabledwhen it cannot open and a press that keeps the editor’s selection.
PageLabels
The page block (createPagePlugin).
export type PageLabels = {
page: string;
untitled: string;
};
page: The “Page” command.untitled: A page without a title: the block, its plain text and HTML export.
PageLinkData
What a pageLink atom stores.
export type PageLinkData = {
id: string;
title: string;
href?: string;
icon?: string;
};
PageLinkItem
A page the page-link menu offers: what your app’s search answers.
export type PageLinkItem = {
id: string;
title: string;
href?: string;
icon?: string;
};
id: The page’s id in your app: stored in the atom.title: The page’s title, shown in the menu and in the atom.href: Where the link goes (absolute, or relative to your app); defaulthref(page).icon: A short icon (an emoji) shown before the title.
PageLinkLabels
The [[ page link atom (createPageLinkPlugin).
export type PageLinkLabels = {
untitled: string;
};
untitled: An atom whose page has no title.
PageLinkPluginOptions
export type PageLinkPluginOptions = {
search: (query: string, ctx: TriggerContext) => readonly PageLinkItem[] | Promise<readonly PageLinkItem[]>;
href?: (page: PageLinkItem) => string | undefined;
char?: string;
menu?: Snippet<[
TriggerMenuController<PageLinkItem>
]>;
};
search: The pages matchingquery(the text typed after[[), at once or as a promise.href: A page’s URL when the search answers none (/pages/<id>, say).char: The text that opens the menu (default[[).menu: The menu’s markup and its words; it renders whilecontroller.isOpen, at the caret (without it, nothing shows).
PagePluginOptions
The page plugin’s options: how your app opens, names, links and creates pages.
export type PagePluginOptions = {
open?: (pageId: string, how: {
newTab: boolean;
}) => void;
title?: (pageId: string) => string | null | undefined | Promise<string | null | undefined>;
href?: (pageId: string) => string;
create?: () => string | {
pageId: string;
title?: string;
} | null | undefined;
link?: Snippet<[
PageBlockLinkPayload
]>;
};
open: Open the page (navigate to it): a click on a page block, or Enter over it selected alone;newTabfor a modified click (Mod, Shift) or a middle click, which a link (href) leaves to the browser.title: The page’s title now, or a promise of it;nullorundefinedwhile unknown (the block shows the title it stores).href: A URL for the page: the block is a link to it (a modified click opens a new tab), and its HTML export links it.create: Create a new page and answer its id (and its title): the “Page” command (slash menu,+) inserts a block linking to it, then opens it.link: The block’s link to its page (default: Notion’s row, a page icon and the title).
PartialLabels
A view’s words (<Edytor labels>): any entry of any section, and any
entry of a nested group (placeholders, kinds) without the others;
keywords replaces a command’s slash keywords by command id (list the
English ones in it to keep them).
export type PartialLabels = {
[K in keyof Labels]?: Overrides<Labels[K]>;
} & {
keywords?: Partial<Record<string, string[]>>;
};
PlacedThread
A thread as the sidebar places it: its anchor’s runs (none: its text was deleted).
export type PlacedThread = CommentThread & {
runs: CommentRun[];
};
Placeholder
An empty block’s placeholder text: one string, or one per block of the view (null: none).
export type Placeholder = string | ((view: PlaceholderView, edytor: Edytor) => string | null);
PlaceholderView
What a placeholder function receives: the empty block’s declared values.
export type PlaceholderView = {
type: string;
data: Readonly<Record<string, unknown>>;
focused: boolean;
empty: boolean;
};
Plan
A prepared operation: its steps, their effect, the ids the op is about, and the document version it was prepared against — valid only there, applied in the same synchronous turn.
export type Plan = {
readonly ids: readonly BlockId[];
readonly writes: readonly PlanStep[];
readonly effect: PlanEffect;
readonly version: number;
readonly at?: DocPosition;
};
at: Where the op leaves the caret, when it decides one (deleteRange).
PlanEffect
What applying a plan does: blocks created, removed (they leave the
document), merged ([from, into]), moved (a placement written), retyped
or given new data (meta), and the text ranges written. Derived from the
plan’s steps; the applied transaction changes exactly this.
export type PlanEffect = {
creates: BlockId[];
removes: BlockId[];
merges: [
from: BlockId,
into: BlockId
][];
moves: BlockId[];
meta: BlockId[];
textRanges: TextRange[];
};
PlanStep
One planned write, named by the document operation that performs it — the name a hook matches. Steps carry everything their write needs (ranks, marks, offsets), decided at prepare time.
export type PlanStep = {
op: 'insertBlocks';
parent: BlockId | null;
index: number;
specs: BlockSpec[];
ranks: string[];
} | {
op: 'moveBlocks';
ids: BlockId[];
parent: BlockId | null;
index: number;
ranks: string[];
} | {
op: 'deleteBlock';
id: BlockId;
marks: BlockId[];
removes: BlockId[];
} | {
op: 'splitBlock';
id: BlockId;
offset: number;
length: number;
newId: BlockId;
tail: SplitTail;
parent: BlockId | null;
rank: string;
} | {
op: 'mergeBlocks';
from: BlockId;
into: BlockId;
at: number;
length: number;
} | {
op: 'setBlockType';
id: BlockId;
type: string;
} | {
op: 'patchData';
id?: BlockId;
inlineId?: string;
offset?: number;
ops: DataPatch[];
leaves: LeafWrite[];
} | {
op: 'insertText';
id: BlockId;
offset: number;
text: string;
marks?: Record<string, unknown>;
} | {
op: 'insertInline';
id: BlockId;
offset: number;
atom: InlineSpec;
} | {
op: 'deleteText';
id: BlockId;
offset: number;
length: number;
} | {
op: 'removeInline';
id: BlockId;
offset: number;
inlineId: string;
} | {
op: 'formatRange';
id: BlockId;
offset: number;
length: number;
marks: Record<string, unknown>;
};
Plugin
Plugin factory function type that creates plugin definitions and operations.
export type Plugin = (editor: Edytor) => PluginDefinitions & PluginOperations;
PluginDefinitions
Defines the structure of plugin definitions including marks, blocks, inline blocks, and hotkeys.
export type PluginDefinitions = {
marks?: Record<string, MarkDefinition | Snippet<[
MarkSnippetPayload<any>
]>>;
blocks?: Record<string, BlockDefinition | Snippet<[
BlockSnippetPayload<any>
]>>;
inlineBlocks?: Record<string, InlineBlockDefinition | Snippet<[
InlineBlockSnippetPayload<any>
]>>;
hotkeys?: Partial<Record<HotKeyCombination, HotKey>>;
commands?: EditorCommand[];
};
PluginOperations
Defines the available operations and hooks for plugins.
export type PluginOperations = {
onBeforeOperation?: <C extends ChangePayload>(payload: C) => C['payload'] | void;
onAfterOperation?: (payload: AfterOperationPayload) => void;
onChange?: (value: JSONDoc) => void;
onDocChange?: (change: DocChange) => void;
onSelectionChange?: (selection: EdytorSelection) => void;
placeholder?: Placeholder;
onEdytorAttached?: (payload: {
node: HTMLElement;
}) => (() => void) | void;
onBlockAttached?: (payload: {
node: HTMLElement;
block: Block;
}) => (() => void) | void;
onTextAttached?: (payload: {
node: HTMLElement;
text: Text;
}) => (() => void) | void;
onDeleteSelectedBlocks?: (payload: {
prevent: Prevent;
selectedBlocks: Block[];
}) => void;
onBeforeInput?: (payload: {
prevent: Prevent;
e: InputEvent;
}) => void;
onCopy?: (payload: {
prevent: Prevent;
e: ClipboardEvent;
}) => void;
onCut?: (payload: {
prevent: Prevent;
e: ClipboardEvent;
}) => void;
onPaste?: (payload: {
prevent: Prevent;
e: ClipboardEvent;
}) => void;
};
onBeforeOperation: Called before an operation is executedonAfterOperation: Called after an operation is executed:dispatcher.last.statusis set;operationnarrowspayload.onChange: Called when the editor value changes: the sameJSONDocshape<Edytor value>takes.onDocChange: Called after every commit that changed the visible document, with what it changed (the change report), without exporting the document.onSelectionChange: Called when the selection changesplaceholder: The placeholder of an empty block: rendered by a::beforerule the library ships.onEdytorAttached: Called when the editor is attached to the DOM; may return a cleanup, run on detach.onBlockAttached: Called when a block element is attached to the DOM; may return a cleanup.onTextAttached: Called when a text element is attached to the DOM; may return a cleanup.onDeleteSelectedBlocks: Before a block selection is removed: Backspace or Delete (a word or line delete too), a cut, the block menu’s Delete, and typing, a composition or a paste over it.onBeforeInput: Called before input is processedonCopy: Called when a copy event is detectedonCut: Called when a cut event is detectedonPaste: Called when a paste event is detected
Popup
export type Popup = {
id: string;
haspopup?: 'listbox' | 'menu' | 'dialog';
active?: string;
keys?: string;
opener?: PopupOpener;
};
id: The popup element’s id.haspopup: What it is, foraria-haspopup(none for a toolbar).active: Its highlighted row’s id, when the root holds the keyboard.keys: The keys that move the focus from the root into it (aria-keyshortcuts).opener: The handle control that opened it.
PopupOpener
A handle control that opens a popup: its + or its grip.
export type PopupOpener = {
block: string;
control: 'add' | 'grip';
};
Popups
export declare class Popups {
#private;
idOf: (name: string) => string;
set: (owner: string, popup: Popup | null) => void;
get current(): Popup | undefined;
openedBy: (block: string, control: PopupOpener["control"]) => Popup | undefined;
}
idOf: This view’s element id forname(any string: what an IDREF cannot hold is replaced).set: Publishowner’s popup, or withdraw it withnull.current: The newest popup open, if any (reactive).openedBy: The popup the handlecontrolofblockopened, if one is open (reactive).
prefetch
Keep a document’s local copy fresh without opening it (sync once, then close).
declare const prefetch: typeof prefetchStack;
PrefetchOptions
What bindProviders’ prefetch takes: a websocket sync’s target and options.
export type PrefetchOptions = (WebsocketSyncOptions extends infer O ? O extends unknown ? Omit<O, 'onExpired' | 'persist' | 'disableBc'> : never : never) & {
timeout?: number;
};
PrefetchResult
What prefetch found: whether the room sent the store anything it lacked.
export type PrefetchResult = {
updated: boolean;
};
Prepared
prepare’s answer: a plan, or the op’s refusal.
export type Prepared = Plan | OpResult;
PresenceOptions
export type PresenceOptions = {
share?: PresenceShare;
throttle?: number;
};
share:'caret'(default): the selection;'block': the focused block only, so moving inside it publishes nothing;'none': nothing.throttle: Minimum ms between two presence writes (defaultDEFAULT_PRESENCE_THROTTLE, 50;0: every change).
PresenceSelection
The presence payload: anchors only. A text range publishes its
endpoints in document order with collapsed/reversed; a block set its ids;
an atom its block and id; none publishes nothing.
export type PresenceSelection = {
start: DocAnchor;
end: DocAnchor;
collapsed: boolean;
reversed: boolean;
} | {
blocks: BlockId[];
} | {
atom: string;
block: BlockId;
};
PresenceShare
What a view shares of its selection: the selection itself, the block holding its focus, or nothing.
export type PresenceShare = 'caret' | 'block' | 'none';
PresenceWriter
One view’s presence writer: the only writer of its key, under the
view’s share and throttle. select() hands it the full payload; the
view’s teardown calls clear().
export declare class PresenceWriter {
#private;
private readonly awareness;
private readonly key;
throttle: number;
constructor(awareness: AwarenessLike, key: string, { share, throttle }?: PresenceOptions);
get share(): PresenceShare;
set share(share: PresenceShare);
write(payload: PresenceSelection | null, focus?: string | null): void;
clear(): void;
}
write: Publishpayload(the full selection;focus: the block holding its focus) as this view shares it, within the throttle.clear: Remove this view’s entry and drop a held write (the view’s teardown).
Prevent
A hook’s prevent: veto the gesture, and optionally run cb in its place.
export type Prevent = (cb?: () => void) => void;
PreventionError
A hook’s veto as the dispatcher carries it: what prevent(cb?) recorded,
and the extension that recorded it. Hooks never see one thrown at them:
prevent records and returns. Throwing one from a hook (the
earlier form) still vetoes, for the transition.
export declare class PreventionError extends Error {
cb?: () => void;
by?: unknown;
constructor(cb?: () => void);
}
by: The extension whose hook prevented (set by the dispatcher).
ProjectedBlock
Canonical projected block — the comparison surface for convergence.
export type ProjectedBlock = {
id: BlockId;
type: string;
data?: Record<string, unknown>;
content: ContentItem[];
children: ProjectedBlock[];
malformed?: true;
};
malformed: Set when the registry node could not be read completely.
ProjectedDoc
export type ProjectedDoc = {
children: ProjectedBlock[];
};
ProtocolMismatch
export type ProtocolMismatch = {
expected: number;
found: number | null;
};
RangeView
What the view knows of a range (view state the document does not hold):
whether it hides a block, and, given removed, whether the block stays
hidden once those go (a removed closed toggle’s children take its place, shown).
export type RangeView = {
hidden?: (id: BlockId, removed?: ReadonlySet<BlockId>) => boolean;
};
ReplaceMatches
What replaceMatches (one command, one undo step) writes: each match replaced by replacement.
export type ReplaceMatches = Commands['replaceMatches'];
ResolvedAt
A position as resolved: block ids, or a text range’s anchors.
export type ResolvedAt = Exclude<SuggestionAt, {
replace: 'selection';
}>;
RichTextLabels
The rich text kinds and marks: their names, the document’s own words, placeholders.
export type RichTextLabels = {
kinds: {
paragraph: string;
heading1: string;
heading2: string;
heading3: string;
toggleHeading1: string;
toggleHeading2: string;
toggleHeading3: string;
bulletedList: string;
numberedList: string;
todoList: string;
toggleList: string;
callout: string;
quote: string;
divider: string;
};
marks: {
bold: string;
italic: string;
underline: string;
strike: string;
code: string;
};
checkbox: string;
emptyBody: {
toggle: string;
callout: string;
};
calloutIcon: {
change: string;
add: string;
};
placeholders: {
heading: (level: 1 | 2 | 3) => string;
toggleHeading: (level: 1 | 2 | 3) => string;
list: string;
todo: string;
toggle: string;
quote: string;
callout: string;
caption: string;
empty: string;
};
};
kinds: Each preset’s label: the slash menu, Turn into, the handles’ names.marks: The marks’ toolbar buttons (MarkDefinition.toolbar.label).checkbox: A to-do’s checkbox.emptyBody: The hint an empty body shows (an open toggle’s, a callout’s): a press there starts it.calloutIcon: A callout’s icon button:changeits icon,addone when it has none.placeholders:richTextPlaceholder’s texts.
RichTextLink
export type RichTextLink = {
href: string;
target?: string;
};
RichTextMark
export type RichTextMark = 'bold' | 'italic' | 'underline' | 'code' | 'link' | 'strike' | 'superscript' | 'subscript' | 'color' | 'highlight';
richTextOperations
declare const richTextOperations: (edytor: Edytor) => {
insertDividerAtSelection: () => import("../..").Block | null;
removeAllMarksAtRange: () => void;
setLinkAtRange: (link: RichTextLink) => void;
removeLinkAtRange: () => void;
linkText: (text: Text, start: number, end: number, href: string) => void;
unlinkText: (text: Text, start: number, end: number) => void;
removeMarkAtRange: (mark: RichTextMark) => void;
setMarkValueAtRange: (mark: RichTextMark, value: SerializableContent) => void;
setMarkAtRange: (mark: RichTextMark | (string & {}), value?: SerializableContent) => void;
};
richTextPlaceholder
Notion’s placeholders, in the view’s richText labels (English by
default): headings, lists, to-dos, toggles and quotes name their kind
while empty; a paragraph invites a command only while focused.
declare const richTextPlaceholder: ({ type, data, focused }: PlaceholderView, edytor?: Edytor) => string | null;
RichTextPluginOptions
export type RichTextPluginOptions = {
callout?: {
icon?: string;
};
checkbox?: Snippet<[
TodoCheckboxPayload
]>;
calloutIcon?: Snippet<[
CalloutIconPayload
]>;
emptyBody?: Snippet<[
EmptyBodyPayload
]>;
};
callout: Callouts’ icons (their picker iscreateCalloutIconsPlugin’s).checkbox: A to-do’s checkbox (default: a nativeinput type="checkbox").calloutIcon: A callout’s icon (default: abuttonshowing it, or the icon alone where it cannot be picked).emptyBody: The hint an open toggle’s or a callout’s empty body shows (default: adivwith the view’srichText.emptyBodywords).
safeImageSrc
An image source the block may render: http(s), blob, or an inline image.
declare const safeImageSrc: (value: unknown) => string | null;
safeMediaSrc
A media file’s source (a video, an audio track, an attachment): a
safeWebUrl, or a blob: URL an upload answered. Never data::
an inline file would be stored in the document and sent in every update.
declare const safeMediaSrc: (value: unknown) => string | null;
safeWebUrl
A web page’s URL a media block may store or render: the link sanitizer’s
reading of it (sanitizeLinkHref: ASCII control characters and spaces stripped,
tab/newline anywhere), then only an absolute http:/https: URL with a
host. null for anything else: a script or data: URL, a mailto:, a
relative path, a bare domain.
declare const safeWebUrl: (value: unknown) => string | null;
SchemaMismatchDetail
Inbound refusal detail — emitted on 'schema-mismatch' (and mirrored
through 'message-error' as a SchemaMismatchError) when a
received update would write a foreign schema stamp (unsupported
version, foreign manifest, or a deleted stamp — unversioned). The
update is never integrated, persisted, or rebroadcast.
export type SchemaMismatchDetail = {
docName: string;
problem: SchemaProblem;
};
SchemaMismatchError
Error raised by assertSchema — carries the detected problem.
export declare class SchemaMismatchError extends Error {
readonly docName: string;
readonly problem: SchemaProblem;
constructor(docName: string, problem: SchemaProblem);
}
SchemaProblem
A document’s relationship to this build’s schema:
unversioned— registry state exists butmeta.vis absent. Someone wrote replicated content without running the schema path (a rogue or v13-era write). This state must NEVER be persisted/broadcast as a document update by a provider.unsupported—meta.vnames a version this build does not speak (e.g.99written by a future build). Content still applies (a replica cannot refuse structs it already shares a protocol with), but providers surface aschema-mismatchsignal so operators can detect skew.foreign—meta.vis the supported version butmeta.schemais missing or names a DIFFERENT manifest ('not-edytor', a next-gen name, an app-local doc kind). A valid version number alone does not make the payload an edytor document — the manifest name is part of the contract (the version-only check let foreignmeta.schemavalues cross the staging boundary).
export type SchemaProblem = {
kind: 'unversioned' | 'unsupported' | 'foreign';
version?: number;
schema?: unknown;
};
version: The observedmeta.v(undefined forunversioned).schema: The observedmeta.schema(undefined when absent).
SelectionPoint
A position within one document version: block id + display offset (an atom counts 1).
export type SelectionPoint = {
block: BlockId;
offset: number;
};
SelectionProjection
export type SelectionProjection = {
kind: SelectionValue['kind'];
start: SelectionPoint | null;
end: SelectionPoint | null;
isCollapsed: boolean;
isReversed: boolean;
blocks: BlockId[];
segments: SelectionSegment[];
isAtStartOfText: boolean;
isAtEndOfText: boolean;
isAtStartOfBlock: boolean;
isAtEndOfBlock: boolean;
isTextSpanning: boolean;
isBlockSpanning: boolean;
islandRoot: BlockId | null;
voidRoot: BlockId | null;
readonly marks: Marks;
readonly content: string;
};
start: Endpoints in document order;nullwhen no endpoint resolves at this version.blocks: Blocks from the start block to the end block in document order.segments: Content parts from the start point’s text segment to the end point’s.marks: Lazy: the marks at the caret (the character before it; offset 0 → the first run) or at the range’s start edge.content: Lazy: the selected text (text segments only).
SelectionSegment
One content part of a block: a text segment (between atoms, possibly empty) or an inline atom.
export type SelectionSegment = {
block: BlockId;
kind: 'text';
segOrd: number;
start: number;
end: number;
runs: TextRun[];
} | {
block: BlockId;
kind: 'inline';
id: string;
start: number;
end: number;
};
SelectionValue
export type SelectionValue = {
readonly kind: 'none';
} | {
readonly kind: 'text';
readonly anchor: DocAnchor;
readonly focus: DocAnchor;
readonly pending?: Marks;
} | {
readonly kind: 'atom';
readonly blockId: BlockId;
readonly atomId: string;
readonly from: AtomSide;
} | {
readonly kind: 'blocks';
readonly ids: readonly BlockId[];
};
SemanticConflictError
Raised when two declarations of one kind’s structural facts disagree —
a view’s plugin and the document (adoptSemantics), or two configs
mergeSemantics is given: no replica may silently read another
rule than its peers.
export declare class SemanticConflictError extends Error {
readonly subject: string;
readonly existing: unknown;
readonly incoming: unknown;
constructor(subject: string, existing: unknown, incoming: unknown);
}
subject: What conflicted (e.g.
semanticsOf
Kind tables (type → row) as one DocumentSemanticsConfig, deeply
frozen: semanticsOf({ embed: { void: true, rendersContent: false } }).
A row may be a plugin’s kind record (plugin.blocks): only its
structural fields are read (void, island, lines, layout,
table, atomic, rendersContent, defaultChild), and a bare snippet is a
plain kind. To add kinds to a bundled config, use mergeSemantics.
declare const semanticsOf: (...tables: Record<string, KindRecord>[]) => {
roles: Record<string, BlockRole>;
rendersContent: Record<string, boolean>;
defaultChild: Record<string, string>;
};
setBlockColor
Set field of every colourable block of blocks to value (null
removes it: the default), as one command and one undo step; each block
is its own data write (patchData), so an extension vetoing one keeps
that block as it was. Answers whether any block changed.
declare const setBlockColor: (edytor: Edytor, blocks: Iterable<Block>, field: BlockColorField, value: string | null) => boolean;
SlashMenuController
The slash menu: the / trigger (TextTriggerController: the range, the
query, the keys, the pick), whose rows are the editor’s commands, and the
menu a handle’s + opens with a search field of its own (addition).
export declare class SlashMenuController extends TextTriggerController {
addition: BlockAddition | null;
private held;
constructor(edytor: Edytor);
protected popupOf(active: string | undefined): Popup;
keys: Attachment<HTMLElement>;
protected get count(): number;
protected admits(block: Block): boolean;
protected queried(query: string): void;
get commands(): EditorCommand[];
private enabled;
close(): void;
offer(addition: BlockAddition): void;
search(query: string): void;
dismiss(focus?: boolean): void;
runSelected(): Promise<boolean>;
run(command: EditorCommand | undefined): Promise<boolean>;
}
addition: Opened by a handle’s+(BLOCK_ADD_EVENT): no/in the text, the query is the menu’s own field, and a picked row adds the block first.popupOf: A+’s menu names its highlighted row from its own field, and tells the+it opened it.keys: The element that holds a+menu’s keyboard ({@attach controller.keys}: its field, or the menu element itself).count: How many rows the keyboard walks.admits: A/opens the menu in a block a kind can replace (convertible).queried: A query no command matches is prose (a URL, a path), and so is one that opens with whitespace (yes / no) or holds only hyphens (/-): the menu closes.commands: Matching commands, grouped (groups in first-seen order): the menu’s rows and keyboard order.offer: Open for a+: nothing is added until a row is picked.search: The+’s menu typed in its own field (a query that matches nothing shows “No results”).dismiss: Close without picking.
SlashMenuOptions
export type SlashMenuOptions = {
menu?: Snippet<[
SlashMenuController
]>;
};
menu: The menu’s markup and its words; it renders whilecontroller.isOpen(without it, nothing shows).
SplitTail
The tail block’s type/data, decided once by a split (default: copied from the source).
export type SplitTail = {
type: string;
data?: Record<string, unknown>;
};
storableImageSrc
An image source a block may store: safeImageSrc’s, but no
inline image over MAX_INLINE_IMAGE_BYTES. Rendering still takes
any safe source, so a document that holds a larger one shows it.
declare const storableImageSrc: (value: unknown) => string | null;
Suggestion
export declare class Suggestion {
#private;
readonly id: string;
readonly at: ResolvedAt;
readonly label: string | undefined;
constructor(layer: Suggestions, at: ResolvedAt, kind: string, options: SuggestionOptions, status: SuggestionStatus);
get content(): readonly JSONBlock[];
get status(): SuggestionStatus;
get retryable(): boolean;
get live(): boolean;
update: (content: SuggestionContent) => void;
append: (text: string) => void;
done: () => void;
accept: () => OpResult;
discard: () => void;
retry: () => void;
}
at: The position, resolved:replace: 'selection'is the selection’s blocks or text range.content: The proposed blocks (reactive).retryable: AnonRetrywas given: the UI offers “Try again”.live: Listed: not accepted, discarded or dropped.update: Replace the content (call it on each streamed chunk); no-op once not live.append: Append streamed text to the last block’s text (a new paragraph when there is none); away from anend, a blank line starts a new paragraph.done: The stream ended:statusisready.accept: Place the content in the document: one command, one undo step (seeSuggestions).discard: Drop it; writes nothing.retry: Empty it, stream again, and ask the app for another answer (onRetry).
SuggestionAt
Where a suggestion goes. replace: 'selection' resolves at creation (see Suggestion.at).
export type SuggestionAt = {
readonly after: BlockId;
} | {
readonly before: BlockId;
} | {
readonly inside: BlockId;
} | {
readonly end: BlockId;
} | {
readonly replace: readonly BlockId[] | 'selection' | SuggestionRange;
};
SuggestionBarOptions
export type SuggestionBarOptions = {
bar: Snippet<[
SuggestionBarPayload
]>;
};
bar: The bar under each block suggestion and its words; its placement is the plugin’s.
SuggestionBarPayload
What a bar snippet receives: one block suggestion and its actions.
export type SuggestionBarPayload = {
suggestion: Suggestion;
accept: () => void;
discard: () => void;
retry: (() => void) | undefined;
readonly: boolean;
mac: boolean;
};
accept: Place it in the document and give the editor its focus back.retry: Ask for another answer (onRetry);undefinedwithout one.mac: The view’s keys are a Mac’s: Mod is ⌘ (name the Accept key with it).
SuggestionContent
Blocks, or a string: paragraphs split on blank lines (one text run at an end).
export type SuggestionContent = string | JSONBlock[];
SuggestionOptions
export type SuggestionOptions = {
id?: string;
status?: SuggestionStatus;
label?: string;
onRetry?: (suggestion: Suggestion) => void;
};
id: The suggestion’s id (default: minted).status: Defaultreadywith content,streamingwithout.label: A name for the UI (an app’s suggestion bar shows it).onRetry: Offer “Try again”: called byretry()once the content is emptied.
SuggestionRange
A text range, by its anchors (a text selection’s).
export type SuggestionRange = {
readonly anchor: DocAnchor;
readonly focus: DocAnchor;
};
Suggestions
The view’s suggestions (edytor.suggestions).
export declare class Suggestions {
#private;
private edytor;
revision: number;
constructor(edytor: Edytor);
get list(): readonly Suggestion[];
get latest(): Suggestion | undefined;
get: (id: string) => Suggestion | undefined;
add: (at: SuggestionAt, content?: SuggestionContent, options?: SuggestionOptions) => Suggestion;
clear: () => void;
prune: () => void;
at: (id: BlockId) => SuggestionsAt;
accept: (suggestion: Suggestion) => OpResult;
}
revision: Bumped by every change of a suggestion: the surface’s passes re-run.list: Every live suggestion, oldest first (reactive).latest: The newest one: what the keys act on.add: Proposecontentatat.clear: Discard every suggestion.prune: After a commit: a suggestion whose block (or range) died is dropped.at: The suggestions shown at blockid.accept: The one command (acceptSuggestion) that writes a suggestion: refused by a readonly view or a vetoing hook (the suggestion stays); else the content goes in with fresh ids, the suggestion goes, and the caret ends the content.
SuggestionsAt
What a block position shows: before it, after it, as its last children, after its text.
export type SuggestionsAt = {
readonly before: readonly Suggestion[];
readonly after: readonly Suggestion[];
readonly inside: readonly Suggestion[];
readonly end: readonly Suggestion[];
readonly replaced: boolean;
readonly inPlace: boolean;
};
replaced: Areplaceremoves this block.inPlace: It removes only blocks that hold nothing (an empty line): the preview shows in their place.
SuggestionStatus
export type SuggestionStatus = 'streaming' | 'ready';
SyncRefusedError
A terminal refusal: the server closed the socket with a policy code
(isRefusal), so the next dial would be refused the same way (a
stale generation, another user’s replica, a foreign stamp). The provider
stops dialing and the document does not seed.
export declare class SyncRefusedError extends Error {
code: number;
reason: string;
constructor(code: number, reason?: string);
}
code: The close code.reason: The close reason the server gave, e.g.
TableAddPayload
What an add snippet receives: the + under the hovered table (a row at
its end) or beside it (a column at its end). The plugin places it along
the table’s edge, as wide (tall) as the table shows; a press on it keeps
the editor’s selection.
export type TableAddPayload = {
kind: Kind;
add: () => void;
};
add: Add the row or the column.
tableBlock
A table block in the document JSON: rows × columns cells, the table’s
columns and each cell’s column set. It names no block id, so a value
holding it seeds the same table every time.
createDocument({
value: { children: [tableBlock({ cells: [['Name', 'Role'], ['Ada', 'Eng']], headerRow: true })] }
});
declare const tableBlock: (options?: TableBlockOptions) => JSONBlock;
TableBlockOptions
What tableBlock builds.
export type TableBlockOptions = {
rows?: number;
columns?: number;
cells?: readonly (readonly (string | readonly JSONText[])[])[];
headerRow?: boolean;
headerColumn?: boolean;
widths?: readonly (number | undefined)[];
};
rows: Rows (default: the rows ofcells, else 2).columns: Columns (default: the widest row ofcells, else 2).cells: Each cell’s text, by row then column (a string, or rich text).headerRow: The first row is a header row.headerColumn: The first column is a header column.widths: Column widths in pixels, by position (absent: the default width).
TableChrome
The table’s chrome (table.*), in the overlay: while the pointer is over
a table of an editable view, a + under it (a row at its end) and one
beside it (a column at its end), a grip left of the hovered row and one
above the hovered column, each opening its menu (insert, move, delete),
and a resize band on each column’s right edge. Positions are read in the
overlay’s measure, once per frame. A resize drags a view-only preview
width (width), which the table’s grid reads; the release writes the
column’s data.columns[i].width (one command, one undo step), Escape
drops the preview. Every action is a table command (table.ts): refused
when the view is readonly.
What a menu snippet receives: the open menu (menu), its rows
(items, each a MenuItemPayload), the keyboard’s row (index), the
attachments that give it the keys and the ARIA (popup and keys on
the menu’s element, option(index) on each row) and close().
A grip’s own attachment is its payload’s grip.
export declare class TableChrome extends ChromeList {
#private;
menu: TableMenu | null;
constructor(edytor: Edytor, columnWidth: number, minColumnWidth: number);
protected get count(): number;
get readonly(): boolean;
toggle: (kind: Kind) => void;
get isOpen(): boolean;
close: () => void;
get items(): TableMenuItem[];
option(index: number): Attachment<HTMLElement>;
keys: Attachment<HTMLElement>;
gripOf: (kind: Kind) => Attachment<HTMLElement>;
}
menu: What the open grip menu acts on (null: none is open).count: How many rows the keyboard walks.readonly: The view is readonly (reactive): the chrome shows nothing.toggle: Open thekindgrip’s menu on the row or column it stands for, its first row that can run the keyboard’s, or close it when it is open there (a grip’s click).isOpen: Whether a menu is open (reactive).close: Close the menu; the editor takes the keys back.items: The open menu’s rows for yourmenu: each the row shape every menu shares (runruns it and closes the menu,selectmakes it the keyboard’s,optionits attributes), whether it can run now (disabled) and whether it deletes (danger).option: The attachment of the open menu’s row atindex({@attach chrome.option(index)}).keys: The element that holds the menu’s keyboard ({@attach chrome.keys}, the menu’s own): it takes the focus when it mounts and names the keyboard’s row (aria-activedescendant); ArrowDown and ArrowUp walk the rows that can run (around the ends), Home and End reach the first and last, Enter or Space runs the row, Escape and Tab close the menu and give the keys back to the editor.gripOf: Thekindgrip’s attachment ({@attach}it on a grip’s element; agripsnippet’s payload carries it): in an editable view, dragging it moves its row or column within its table (table.drag); a click with no drag opens or closes its menu; ArrowDown opens the menu, Escape closes it and gives the keys back to the editor.
TableGripPayload
What a grip snippet receives: the grip of the hovered row (left of the
table) or column (above it). Put {@attach grip} on its element (a
button): it drags the row or the column, a click opens its menu,
ArrowDown opens it too and Escape closes it, and it carries the ARIA
(aria-haspopup, aria-expanded, aria-controls while its menu is
open; name it). The plugin centers it where the grip stands.
export type TableGripPayload = {
kind: Kind;
id: string;
index: number;
expanded: boolean;
dragging: boolean;
toggle: () => void;
grip: Attachment<HTMLElement>;
chrome: TableChrome;
};
id: The row’s block id, or the column’s id.index: Its position among the table’s rows (columns).expanded: Its menu is open.dragging: It is being dragged.toggle: Open its menu, or close it when it is open (what a click does).grip: The grip’s behavior and ARIA:{@attach grip}.chrome: The table’s chrome (its menu, its actions).
TableLabels
The table block (createTablePlugin).
export type TableLabels = {
table: string;
headerRow: string;
headerColumn: string;
insertRowAbove: string;
insertRowBelow: string;
insertColumnLeft: string;
insertColumnRight: string;
deleteRow: string;
deleteColumn: string;
resize: string;
emptyCell: string;
};
table: The “Table” command, and a table’s accessible name.headerRow: The block menu’s switches.insertRowAbove: The block menu’s rows: at the row and column of the cell the caret was in (else the last ones).resize: A column’s resize band.emptyCell: A padded cell (a row showing none in a column): its first press creates it.
TableMenu
What a grip’s menu acts on: a row (its block id) or a column (its id) of a table.
export type TableMenu = {
kind: Kind;
table: string;
id: string;
};
TableMenuItem
One row of a grip’s menu, for your menu: the row shape every menu
shares (item is the row, run runs it and closes the menu), with
whether it can run now and whether it deletes.
export type TableMenuItem = MenuItemPayload<TableMenuRow> & {
disabled: boolean;
danger: boolean;
};
TableMenuRow
A row of a grip’s menu: insert-above, insert-below, move-up,
move-down, delete-row; insert-left, insert-right, move-left,
move-right, delete-column.
export type TableMenuRow = {
id: string;
};
TablePluginOptions
export type TablePluginOptions = {
rows?: number;
columns?: number;
columnWidth?: number;
minColumnWidth?: number;
grip?: Snippet<[
TableGripPayload
]>;
menu?: Snippet<[
TableChrome
]>;
add?: Snippet<[
TableAddPayload
]>;
};
rows: The rows a new table has (default 3).columns: The columns a new table has (default 3).columnWidth: A column’s width when it sets none, in pixels (default 120, Notion’s).minColumnWidth: The narrowest a column may be resized to, in pixels (default 48).grip: A row’s or a column’s grip: it renders centered where the grip stands (left of the hovered row, above the hovered column; without it, no grip).menu: A grip’s menu: it renders under its grip whilechrome.menuis set (without it, nothing shows).add: The+under the table (a row) and beside it (a column): it renders along that edge (without it, no+).
Text
An id-only, read-only text handle: the ordinal-th text segment of block
blockId — no identity across commits beyond that position. Getters read
the document index; writes are the view’s operations in block offsets
(edytor.insertText(text.parent.id, text.segStart + offset, …)). node
is the element that renders the segment (a Surface fact). A reactive
reader of a getter depends on the block’s cell, so it re-runs when a
commit changes the block’s content.
export declare class Text {
#private;
readonly: boolean;
readonly edytor: Edytor;
readonly blockId: string;
readonly ordinal: number;
readonly id: string;
node: HTMLElement | undefined;
constructor(edytor: Edytor, blockId: string, ordinal: number);
get parent(): Block;
get index(): number;
get value(): JSONText[];
get stringContent(): string;
get isEmpty(): boolean;
get endsWithNewline(): boolean;
get length(): number;
get segStart(): number;
get isInDocument(): boolean;
refreshFromModel: () => void;
getMarksAtRange: (start: number, end: number) => JSONText[];
}
ordinal: The segment’s position among its block’s text segments.index: Position among the block’s content parts.stringContent: The segment’s text.segStart: Display offset of this segment inside its block’s content (atoms count 1).isInDocument: The segment is shown by a live block.refreshFromModel: Re-create this block’s text elements from its cell — the repair of a DOM the browser or a foreign script changed (typing and model commits never remount).getMarksAtRange: The runs of[start, end)(segment offsets), each with a copy of its marks: the MODEL runs (value), never the rendered ones — a kind’stransformTextdecorations (code tokens) are no marks of the text.
TextAnchor
CRDT-stable anchor for a text position — {b} is the home block id of
the BACKING text the bound atom lives in (not necessarily the block that
displays it — merges/splits reroute display while the anchor stays on
the same atoms), a the engine anchor carrying the endpoint affinity in
its a field (a < 0 = left, a >= 0 = right). JSON-serializable;
replaces the v13 RelativePosition. Same wire shape as DocAnchor on
the facade (facade.anchorAt/facade.resolveAnchor).
export type TextAnchor = {
b: string;
a: Anchor;
};
TextRange
A text range an op writes, in display offsets before the write.
export type TextRange = {
block: BlockId;
offset: number;
length: number;
};
TextRuleContext
What an input rule’s replace and a trigger’s onPick receive: where the
matched text is, in block offsets (the offsets edytor.document.facade
takes, an inline atom counting 1).
export type TextRuleContext = {
edytor: Edytor;
block: Block;
from: number;
to: number;
caret: (offset: number) => void;
};
block: The block the text is in.from: Where the matched text starts.to: Where it ends in the document: the caret, for an input rule (the typed text that completed the match is not written yet); the query’s end, for a trigger.caret: Set the command’s result caret atoffsetofblock(block offsets).
textToBlocks
text as blocks (see the module comment).
declare const textToBlocks: (text: string, { markdown, type }?: TextToBlocksOptions) => JSONBlock[];
TextToBlocksOptions
export type TextToBlocksOptions = {
markdown?: boolean;
type?: string;
};
markdown: Read markdown (defaulttrue);falsekeeps every character as text.type: The kind of a paragraph (defaultparagraph).
ThreadComment
One comment of a thread.
export type ThreadComment = {
id: string;
author: string;
body: string;
createdAt: number;
};
author: The verified user who wrote it.body: Plain text (line breaks kept).createdAt: ms since the epoch.
TocEmptyPayload
What a table of contents’ empty snippet receives.
export type TocEmptyPayload = {
label: string;
};
label: The view’s words for it (toc.empty).
TocEntryPayload
What a table of contents’ entry snippet receives: one heading.
export type TocEntryPayload = {
id: string;
level: number;
depth: number;
text: string;
label: string;
go: () => void;
control: Attachment<HTMLElement>;
};
id: The heading’s block id.level: Its level (headingLevel’s answer, 1 the outermost).depth: How many steps in it sits: its level less the shallowest shown.text: Its text (''when it has none).label: What it reads: its text, else the view’stoc.untitled.go: Scroll its heading into view (opening the closed toggles it sits in).control: The entry’s attachment ({@attach entry.control}): its marker (data-edytor-toc-entry), a press that keeps the editor’s selection, and a click that scrolls to it.
TocHeadingLevel
A block’s level in the table of contents (1 the outermost), or null
when it is not a heading: read from its kind and data.
export type TocHeadingLevel = (block: {
type: string;
data: Readonly<Record<string, unknown>>;
}) => number | null;
TocLabels
The table of contents block (createTocPlugin).
export type TocLabels = {
toc: string;
untitled: string;
empty: string;
};
toc: The preset, and the block’s accessible name.untitled: An entry for an empty heading.empty: The hint shown with no heading.
TocPluginOptions
The table of contents plugin’s options.
export type TocPluginOptions = {
headingLevel?: TocHeadingLevel;
entry?: Snippet<[
TocEntryPayload
]>;
empty?: Snippet<[
TocEmptyPayload
]>;
};
headingLevel: Which blocks the table lists, and how deep: by default the rich text headings and toggle headings, by theirdata.level(h11,h22, any other 3, as they are drawn).entry: One entry (default: abuttonwith the heading’s text, one nesting step in per level under the shallowest).empty: What shows while the document has no heading (default: apwith the view’stoc.empty).
TodoCheckboxPayload
What a to-do’s checkbox snippet receives.
export type TodoCheckboxPayload = {
readonly block: Block | undefined;
readonly checked: boolean;
readonly label: string;
toggle: () => void;
readonly control: Attachment<HTMLElement>;
};
block: The to-do;undefinedin a suggestion’s preview (its box writes nothing).checked: It is checked: itsdata.checked(reactive).label: The checkbox’s accessible name (the view’srichText.checkbox).toggle: Check or uncheck it: one data write, refused in a readonly view.control: The control’s attachment ({@attach box.control}), on aninput type="checkbox"(give itchecked={box.checked}) or abutton(it getsrole="checkbox"andaria-checked): its marker (data-edytor-todo-checkbox),contenteditable="false", itsaria-label, a press that keeps the editor’s selection and a click that toggles the to-do.
ToolbarController
export declare class ToolbarController {
#private;
private edytor;
private shown;
linkUrl: string;
panel: "color" | "link" | "turn" | null;
hovered: HTMLAnchorElement | null;
constructor(edytor: Edytor);
get isVisible(): boolean;
get barId(): string;
get linkFieldId(): string;
popup: Attachment<HTMLElement>;
keys: Attachment<HTMLElement>;
linkField: Attachment<HTMLInputElement>;
release(): void;
updateFromSelection(selection?: EdytorSelection): void;
setLinkUrl(value: string): void;
get marks(): {
label: string;
icon: string;
run?: (edytor: Edytor) => void;
mark: string;
}[];
isActive(mark: string): boolean;
get kinds(): KindRow[];
get currentKind(): KindRow | undefined;
togglePanel(panel: 'turn' | 'link' | 'color'): void;
takeFieldFocus(): boolean;
openLinkPanel(): boolean;
closePanel(focus?: boolean): void;
get card(): boolean;
get hoveredHref(): string;
hover(anchor: HTMLAnchorElement | null): void;
openHovered(): void;
editHovered(): void;
removeHovered(): void;
turnInto(kind: KindRow): void;
setColor(mark: 'color' | 'highlight', value: string | null): void;
toggleMark(mark: string): void;
applyLink(): void;
removeLink(): void;
}
panel: The open panel: the kind menu, the link field or the colors.hovered: The link under the pointer, for the link card (link.card).barId: The bar’s element id (page-unique), for the root’saria-controls.linkFieldId: The link panel’s field id (page-unique): alabel’sfor.popup: The bar ({@attach controller.popup}, named by your markup): it takes the bar’s id, itstoolbarrole unless it has its own, the placement’s mark (data-edytor-toolbar-bar, which the toolbar’s shortcut also reads), and is published to the view’s root (edytor.popups, with that shortcut as itsaria-keyshortcuts) while it is in the page.keys: The bar’s keys ({@attach controller.keys}, WAI-ARIA toolbar): one tab stop, the control it last held (tabindex="0", every other-1); the arrows, Home and End walk its controls, Escape gives the focus back to the editor over the held selection (release).linkField: The link panel’s field ({@attach controller.linkField}): its id (linkFieldId), the focus when the panel was just opened (Mod+K, Edit, the Link button), Enter applies the link and Escape closes the panel, both giving the editor its focus and the selection back.release: Give the keyboard back to the editor (Escape in the bar), over the held selection.marks: The mark records declaring a toolbar button, in registration order.isActive: Whethermarkcovers every character of the selection (its button shows pressed).kinds: The kinds the selection’s block may turn into (conversions that keep its content).currentKind: The row naming the selection’s block, for the kind button’s label.takeFieldFocus: Whether the link field mounting now takes the focus (once per opening).openLinkPanel: Mod+K (link.mod-k): the link panel over the selected text, or, at a caret inside a link, over that link, selected first.closePanel: Close the open panel.card: The link card shows: a link is hovered in an editable view.hoveredHref: The hovered link’s URL, as stored (the card shows it).hover: Show the card foranchor, or hide it (null).openHovered: The card’s Open: the hovered link in a new tab.editHovered: The card’s Edit: select the hovered link and open the link panel on it.removeHovered: The card’s Remove: unlink the whole hovered link (one undo step); the selection stays.turnInto: Convert every block the selection touches (Notion), as one undo step.setColor: Set (or clear, withnull) the text color or background of the selection.
ToolbarOptions
export type ToolbarOptions = {
toolbar?: Snippet<[
ToolbarController
]>;
card?: Snippet<[
ToolbarController
]>;
};
toolbar: The toolbar’s markup; it renders whilecontroller.isVisible, placed above the selection (without it, nothing shows).card: The card shown under a hovered link (link.card): it renders whilecontroller.card, just below the hovered link (controller.hoveredHref, andopenHovered,editHovered,removeHovered); without it, no card.
Trigger
A character typed at a text’s start or after whitespace opens a menu at
the caret (@ for people, [[ for pages); the text typed after it is
the query. Picking a row replaces the trigger and its query (onPick).
The menu’s keys are the editor’s while it is open: <kbd>↑</kbd>/<kbd>↓</kbd>
move, <kbd>Enter</kbd> picks, <kbd>Escape</kbd> closes it and keeps the text.
export type Trigger<T = any> = {
char: string;
items: (query: string, ctx: TriggerContext) => readonly T[] | Promise<readonly T[]>;
onPick: (item: T, ctx: TriggerContext) => unknown;
label?: (item: T) => string;
key?: (item: T) => string;
menu?: Snippet<[
TriggerMenuController<T>
]>;
enabled?: (block: Block) => boolean;
};
char: The text that opens the menu (one or two characters:@,[[).items: The rows forquery, at once or as a promise (a search); the newest query wins.onPick: Write the picked row: the trigger and its query (fromtoto) are removed in the same plan as the first operation it runs (one undo step; a refusal keeps them).label: A row’s label: default the item itself when it is a string, else itslabel,titleorname.key: A row’s key, which keeps its markup across queries: default itsid, else its label.menu: The menu’s markup and its words; it renders whilecontroller.isOpen, placed at the caret (without it, nothing shows).enabled: Whether the menu may open inblock(default: a block that is not void and not in a code block).
TriggerContext
What a trigger’s items and onPick receive.
export type TriggerContext = TextRuleContext & {
query: string;
};
TriggerMenuController
A plugin’s trigger (triggers): rows from its items(query), a pick its onPick.
export declare class TriggerMenuController<T = unknown> extends TextTriggerController {
#private;
readonly trigger: Trigger<T>;
items: readonly T[];
loading: boolean;
constructor(edytor: Edytor, trigger: Trigger<T>, owner: string);
protected get count(): number;
protected admits(block: Block): boolean;
labelOf: (item: T) => string;
keyOf: (item: T) => string;
protected queried(query: string): void;
private answered;
close(): void;
pickSelected(): Promise<unknown>;
pick(item: T): Promise<unknown>;
}
items: The rows for the query (the newest answered; a pending search keeps the last).loading: A search for the query is pending.count: How many rows the keyboard walks.admits: Whether a trigger typed inblockopens the menu.queried: The query changed (or was set again): close the menu, or load its rows.pickSelected: Pick the keyboard’s row.pick: Replace the trigger and its query withitem(onPick).
TriggerRange
Where the trigger is now: its text, the trigger’s start and the query’s end in it.
export type TriggerRange = {
text: Text;
triggerStart: number;
queryEnd: number;
};
turnCommands
The commands a Turn into over blocks (as clicked) offers besides the
kinds (EditorCommand.turnsInto), as rows that run them: “3 columns” over
three sibling blocks.
declare const turnCommands: (edytor: Edytor, blocks: Block[]) => KindRow[];
UndecodableUpdateError
Raised when a candidate update cannot even be decoded or integrated — refused before the schema question is ever asked. Distinct from the schema refusals: this payload was never document-shaped.
export declare class UndecodableUpdateError extends Error {
readonly docName: string;
readonly cause: unknown;
constructor(docName: string, cause: unknown);
}
docName: What the update was offered as (e.g.
UnsupportedDocError
Error raised by assertUsableDoc — the doc cannot host a v14 facade:
a foreign engine object (a v13 yjs Doc — a different CRDT implementation
entirely).
export declare class UnsupportedDocError extends Error {
readonly kind: 'foreign';
constructor(kind: 'foreign');
}
Uploader
Upload a file and answer its URL. report.progress (optional to call)
draws the block’s bar; without it the bar is indeterminate.
export type Uploader = (file: File, report: UploadReport) => Promise<string>;
UploadReport
What an upload may report while it runs.
export type UploadReport = {
progress(fraction: number): void;
};
progress: The share sent so far,0to1: the block shows it as a bar.
UploadsOptions
export type UploadsOptions = {
upload: Uploader;
};
upload: Upload a file and answer its URL: an empty image or media block’s Upload, and the pasted and dropped files a kind takes, each placed as a block of that kind at once (one undo step) and filled with the URL this answers (the block shows the progress it reports).
UrlPasteController
Pasting a bare URL on an empty line (Notion): the URL lands as a link at
once (one insertFromPaste step, its own undo step) and a menu under the
line offers to keep it (“Link”, the highlighted row) or to turn the line
into a kind an offer names (Embed, Bookmark), one setBlock command
(its own undo step, which gives the link back). Anything but a pick
closes the menu and keeps the link: Escape, a press outside, an
operation, a caret that leaves the URL’s end, readonly.
One controller per view, shared by the plugins that make offers: the
first one listed carries its hooks (urlPaste).
export declare class UrlPasteController extends ChromeList {
#private;
readonly offers: UrlPasteOffer[];
open: {
block: string;
url: string;
options: UrlPasteOption[];
} | null;
constructor(edytor: Edytor);
protected get count(): number;
get readonly(): boolean;
close: () => void;
pick: (option?: UrlPasteOption | undefined) => void;
}
open: The open menu: the line, the URL, the rows.count: How many rows the keyboard walks.pick: Pick a row: keep the link, or turn the line into the offer’s kind.
UrlPasteOffer
A kind a pasted URL can turn its line into (the embed’s, the bookmark’s).
export type UrlPasteOffer = {
type: string;
icon: string;
data: (url: string) => Record<string, SerializableContent> | null;
created?: (block: Block, url: string) => void;
};
type: The kind the line becomes.data: The block’s data forurl, ornullwhen this kind takes no such URL (no provider plays it).created: After the line became the kind (a bookmark’s unfurl).
UrlPasteOption
One row of the menu: keep the link (link), or an offer (its kind).
export type UrlPasteOption = {
id: string;
icon: string;
offer?: UrlPasteOffer;
};
useEdytor
declare const useEdytor: () => Edytor;
VersionChange
How a block of a version differs from the current document.
export type VersionChange = 'added' | 'removed' | 'changed';
versionDiff
The block-level diff of version against current: which blocks were
added, removed or changed since the version, and the preview showing
them all. Neither input is modified.
declare const versionDiff: (version: JSONDoc, current: JSONDoc) => VersionDiff;
VersionDiff
What versionDiff answers.
export type VersionDiff = {
preview: JSONDoc;
changes: Map<string, VersionChange>;
added: number;
removed: number;
changed: number;
};
preview: The version, with each block added since inserted where the current document has it: after its nearest earlier sibling the preview holds under the same parent, else first under that parent.changes: Each differing block’s change, by block id.added: Blocks added since the version.removed: Blocks removed since the version.changed: Blocks changed since the version.
VersionHistory
A view of a room’s versions, made in a component’s script (its effects
list the versions, and follow the live document’s commits). Read its
state reactively; select, restore, undo and refresh act.
export declare class VersionHistory {
#private;
versions: HistoryVersion[] | null;
selected: string | null;
shown: JSONDoc | null;
reading: boolean;
busy: boolean;
undoable: boolean;
status: VersionHistoryStatus;
failed: boolean;
highlight: boolean;
constructor(options: VersionHistoryOptions);
diff: import("./diff.js").VersionDiff | null;
preview: JSONDoc | null;
previewKey: string | null;
changes: ReadonlyMap<string, VersionChange>;
previewPlugins: Plugin[];
selectedVersion: HistoryVersion | null;
refresh: () => Promise<void>;
select: (key: string | null) => Promise<void>;
restore: () => Promise<void>;
undo: () => Promise<void>;
dateOf: (entry: HistoryVersion) => string;
timeOf: (entry: HistoryVersion) => string;
option: (index: number) => Attachment<HTMLElement>;
keys: Attachment<HTMLElement>;
}
versions: The versions, newest first (nullwhile the first list loads).selected: The previewed version’s key.shown: The previewed version’s JSON, once read (nullwhile it loads or when it is gone).reading: The previewed version is being read.busy: A restore or an undo is in flight.undoable: A restore this view applied, whichundocan take back.status: The last outcome, to announce (aria-live) in your words.failed: The last outcome failed.highlight: Highlight the blocks added, removed and changed since the version.diff: The previewed version against the live document: its counts and the changed blocks.preview: What the preview renders: the version, with the blocks removed since when highlighting.previewKey: A key for the preview: it remounts when what it renders changes (valueis read once).changes: The blocks changed since the version, by id, while highlighting (else none).previewPlugins: The preview’s plugins: the highlights of the changes (read live), then the app’s.selectedVersion: The previewed version, as listed.refresh: List the versions again; keeps the selection when it is still listed, else selects the newest.select: Preview the versionkey(null: none).restore: Restore the previewed version (the room writes it as one change).undo: Take back the restore this view applied.dateOf: A version’s day (Oct 8, 2026), inlocale; its half of the day isentry.slot.timeOf: When a version was saved, inlocale.option: The attachment of the version atindex’s row ({@attach history.option(index)}, on its focusable element):role="option", its id,aria-selected, the list’s one tab stop when it is selected, else the first.keys: The list’s attachment ({@attach history.keys}): the arrows, Home and End move the selection and the focus.
VersionHistoryOptions
export type VersionHistoryOptions = {
client: HistoryClient;
document?: EdytorDocument;
plugins?: Plugin[];
locale?: string | string[];
id?: string;
onRestore?: (result: HistoryRestoreResult) => void;
onUndo?: (result: HistoryUndoResult) => void;
onError?: (error: unknown) => void;
};
client: Where the versions come from:createHistoryClient(…), or any object withlist,read,restoreandundo.document: The live document the preview is compared with.plugins: The preview’s plugins: the ones the live view uses, so every kind renders as it does there.locale: The locale dates and times are written in (default the browser’s).id: The id prefix of the list’s rows (page-unique:$props.id()).onRestore: After a restore the room answered.onUndo: After an undo the room answered.onError: A request failed (statusisdeniedorfailed).
VersionHistoryStatus
The last outcome to announce ('': none): a restore in flight or done
(unchanged: the page already matched), the previewed version gone
(unavailable), an undo done or with nothing to undo, a request refused
(denied: a 401 or 403) or failed. Your panel says it in its words.
export type VersionHistoryStatus = '' | 'restoring' | 'restored' | 'unchanged' | 'unavailable' | 'undone' | 'nothingToUndo' | 'denied' | 'failed';
WebsocketSync
A websocket sync; persistName names its local store (for clearDocument), unset without one.
export type WebsocketSync = EdytorSync & {
persistName?: string;
};
WebsocketSyncOptions
export type WebsocketSyncOptions = WithoutPolyfill<CrdtWebsocketSyncOptions> & {
WebSocketPolyfill?: typeof WebSocket;
};
WebsocketTarget
Where the socket dials: <server>/<room>, the names <Edytor server room> uses.
export type WebsocketTarget = {
server: string;
room: string;
};
server: The sync server’s base URL (wss://…/rooms).room: The room (document) id.
withRulesAndTriggers
plugin with inputRules (text typed at the caret that a rule
replaces: markdown, :smile: → 😄) and triggers (characters that open
a menu of rows at the caret: @ mentions) run through its own hooks.
A plugin declares them by returning this:
(edytor) => withRulesAndTriggers(edytor, { inlineBlocks }, { triggers: [mention] }).
declare const withRulesAndTriggers: (edytor: Edytor, plugin: InitializedPlugin, { inputRules, triggers }: {
inputRules?: InputRule[];
triggers?: Trigger[];
}) => InitializedPlugin;
wrapBlocks
Wrap blocks in a new layout of columns items (default one per block),
at the first one’s place (layout.wrap, Notion’s Turn into N columns): the
blocks one per item, then one empty block per further item; the document’s
wrapInLayout, one undo step. The blocks stay selected. Answers whether
it applied.
declare const wrapBlocks: (edytor: Edytor, blocks: Block[], kind?: string, columns?: number) => boolean;
wrappable
Whether blocks may be wrapped in a new layout of columns items
(layout.wrap: sibling blocks, none a layout, a column or a block holding
one, at a place a layout fits and outside any column; default one item per
block, at least two).
declare const wrappable: (edytor: Edytor, blocks: Block[], kind?: string, columns?: number) => boolean;