UNPKG

lexical

Version:

Lexical is an extensible text editor framework that provides excellent reliability, accessible and performance.

1,485 lines (1,416 loc) • 76 kB
/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow strict */ /** * LexicalCommands */ export type LexicalCommand<P> = Readonly<{type?: string}>; declare export var SELECTION_CHANGE_COMMAND: LexicalCommand<void>; declare export var CLICK_COMMAND: LexicalCommand<MouseEvent>; declare export var DELETE_CHARACTER_COMMAND: LexicalCommand<boolean>; declare export var INSERT_LINE_BREAK_COMMAND: LexicalCommand<boolean>; declare export var INSERT_PARAGRAPH_COMMAND: LexicalCommand<void>; declare export var CONTROLLED_TEXT_INSERTION_COMMAND: LexicalCommand<string>; declare export var PASTE_COMMAND: LexicalCommand<ClipboardEvent>; declare export var REMOVE_TEXT_COMMAND: LexicalCommand<InputEvent | null>; declare export var DELETE_WORD_COMMAND: LexicalCommand<boolean>; declare export var DELETE_LINE_COMMAND: LexicalCommand<boolean>; declare export var FORMAT_TEXT_COMMAND: LexicalCommand<TextFormatType>; declare export var SET_TEXT_FORMAT_COMMAND: LexicalCommand<Partial<Record<TextFormatType, boolean>>>; declare export var UNDO_COMMAND: LexicalCommand<void>; declare export var REDO_COMMAND: LexicalCommand<void>; declare export var KEY_DOWN_COMMAND: LexicalCommand<KeyboardEvent>; declare export var KEY_ARROW_RIGHT_COMMAND: LexicalCommand<KeyboardEvent>; declare export var KEY_ARROW_LEFT_COMMAND: LexicalCommand<KeyboardEvent>; declare export var KEY_ARROW_UP_COMMAND: LexicalCommand<KeyboardEvent>; declare export var KEY_ARROW_DOWN_COMMAND: LexicalCommand<KeyboardEvent>; declare export var KEY_ENTER_COMMAND: LexicalCommand<KeyboardEvent | null>; declare export var KEY_SPACE_COMMAND: LexicalCommand<KeyboardEvent>; declare export var KEY_BACKSPACE_COMMAND: LexicalCommand<KeyboardEvent>; declare export var KEY_ESCAPE_COMMAND: LexicalCommand<KeyboardEvent>; declare export var KEY_DELETE_COMMAND: LexicalCommand<KeyboardEvent>; declare export var KEY_TAB_COMMAND: LexicalCommand<KeyboardEvent>; declare export var INSERT_TAB_COMMAND: LexicalCommand<void>; declare export var KEY_MODIFIER_COMMAND: LexicalCommand<KeyboardEvent>; declare export var INDENT_CONTENT_COMMAND: LexicalCommand<void>; declare export var OUTDENT_CONTENT_COMMAND: LexicalCommand<void>; declare export var DROP_COMMAND: LexicalCommand<DragEvent>; declare export var FORMAT_ELEMENT_COMMAND: LexicalCommand<ElementFormatType>; declare export var DRAGSTART_COMMAND: LexicalCommand<DragEvent>; declare export var DRAGOVER_COMMAND: LexicalCommand<DragEvent>; declare export var DRAGEND_COMMAND: LexicalCommand<DragEvent>; declare export var COPY_COMMAND: LexicalCommand< ClipboardEvent | KeyboardEvent | null, >; declare export var CUT_COMMAND: LexicalCommand< ClipboardEvent | KeyboardEvent | null, >; declare export var CLEAR_EDITOR_COMMAND: LexicalCommand<void>; declare export var CLEAR_HISTORY_COMMAND: LexicalCommand<void>; declare export var CAN_REDO_COMMAND: LexicalCommand<boolean>; declare export var CAN_UNDO_COMMAND: LexicalCommand<boolean>; declare export var FOCUS_COMMAND: LexicalCommand<FocusEvent>; declare export var BLUR_COMMAND: LexicalCommand<FocusEvent>; declare export var SELECT_ALL_COMMAND: LexicalCommand<KeyboardEvent>; declare export var MOVE_TO_END: LexicalCommand<KeyboardEvent>; declare export var MOVE_TO_START: LexicalCommand<KeyboardEvent>; declare export var SELECTION_INSERT_CLIPBOARD_NODES_COMMAND: LexicalCommand<{ nodes: LexicalNode[]; selection: BaseSelection; }>; declare export function createCommand<T>(type?: string): LexicalCommand<T>; /** * LexicalConstants */ declare export var IS_ALL_FORMATTING: number; declare export var IS_BOLD: number; declare export var IS_CODE: number; declare export var IS_HIGHLIGHT: number; declare export var IS_ITALIC: number; declare export var IS_STRIKETHROUGH: number; declare export var IS_SUBSCRIPT: number; declare export var IS_SUPERSCRIPT: number; declare export var IS_UNDERLINE: number; declare export var IS_UPPERCASE: number; declare export var IS_LOWERCASE: number; declare export var IS_CAPITALIZE: number; declare export var TEXT_TYPE_TO_FORMAT: Record<TextFormatType | string, number>; /** * LexicalEditor */ type IntentionallyMarkedAsDirtyElement = boolean; type MutationListeners = Map<MutationListener, Class<LexicalNode>>; export type NodeMutation = 'created' | 'updated' | 'destroyed'; export type UpdateListenerPayload = { tags: Set<string>, prevEditorState: EditorState, editorState: EditorState, dirtyLeaves: Set<NodeKey>, dirtyElements: Map<NodeKey, IntentionallyMarkedAsDirtyElement>, normalizedNodes: Set<NodeKey>, }; export type UpdateListener = (payload: UpdateListenerPayload) => void; type DecoratorListener = (decorator: { // $FlowFixMe[unclear-type]: defined by user [NodeKey]: any, }) => void; type RootListener = ( rootElement: null | HTMLElement, prevRootElement: null | HTMLElement, ) => void | (() => void); type TextContentListener = (text: string) => void; type ErrorHandler = (error: Error) => void; export type MutationListener = ( nodes: Map<NodeKey, NodeMutation>, { updateTags: Set<string>, dirtyLeaves: Set<string>, prevEditorState: EditorState, }, ) => void; export type MutationListenerOptions = { skipInitialization?: boolean; }; export type EditableListener = (editable: boolean) => void | (() => void); type Listeners = { decorator: Map<DecoratorListener, void | (() => void)>, mutation: MutationListeners, textcontent: Map<TextContentListener, void | (() => void)>, root: Map<RootListener, void | (() => void)>, update: Map<UpdateListener, void | (() => void)>, }; export type CommandListener<P> = (payload: P, editor: LexicalEditor) => boolean; declare class DequeSet<T> { size: number; addBack(v: T): this; addFront(v: T): this; delete(v: T): boolean; toArray(): T[]; toReadonlyArray(): ReadonlyArray<T>; @@iterator(): Iterator<T>; } type Tuple5<T> = Readonly<[T, T, T, T, T]>; // $FlowFixMe[unclear-type] type Commands = Map< // $FlowFixMe[unclear-type] LexicalCommand<any>, // $FlowFixMe[unclear-type] Tuple5<DequeSet<CommandListener<any>>>, >; type RegisteredNodes = Map<string, RegisteredNode>; type RegisteredNode = { klass: Class<LexicalNode>, transforms: Set<Transform<LexicalNode>>, }; export type Transform<T> = (node: T) => void; type DOMConversionCache = Map< string, ((node: Node) => DOMConversion | null)[], >; export type DOMSlotForNode<N extends LexicalNode> = N extends ElementNode ? ElementDOMSlot<HTMLElement> : DOMSlot<HTMLElement>; export type EditorDOMRenderConfig = { /** @internal @experimental */ $createDOM: <T extends LexicalNode>( node: T, editor: LexicalEditor, ) => HTMLElement; /** @internal @experimental */ $getDOMSlot: <N extends LexicalNode>( node: N, dom: HTMLElement, editor: LexicalEditor, ) => DOMSlotForNode<N>; $getSlotTargetElement: <T extends LexicalNode>( node: T, slotName: string, hostDom: HTMLElement, editor: LexicalEditor, ) => HTMLElement | null; /** @internal @experimental */ $exportDOM: <T extends LexicalNode>( node: T, editor: LexicalEditor, ) => DOMExportOutput; /** @internal @experimental */ $extractWithChild: <T extends LexicalNode>( node: T, childNode: LexicalNode, selection: null | BaseSelection, destination: 'clone' | 'html', editor: LexicalEditor, ) => boolean; /** @internal @experimental */ $updateDOM: <T extends LexicalNode>( nextNode: T, prevNode: T, dom: HTMLElement, editor: LexicalEditor, ) => boolean; /** @internal @experimental */ $shouldInclude: <T extends LexicalNode>( node: T, selection: null | BaseSelection, editor: LexicalEditor, ) => boolean; /** @internal @experimental */ $shouldExclude: <T extends LexicalNode>( node: T, selection: null | BaseSelection, editor: LexicalEditor, ) => boolean; } export type CreateEditorArgs = { /** @internal @experimental */ dom?: Partial<EditorDOMRenderConfig>; disableEvents?: boolean; editorState?: EditorState; namespace?: string; nodes?: ReadonlyArray<Class<LexicalNode> | LexicalNodeReplacement>; onError?: ErrorHandler; onWarn?: ErrorHandler; parentEditor?: LexicalEditor; editable?: boolean; theme?: EditorThemeClasses; html?: HTMLConfig; }; declare export class LexicalEditor { _parentEditor: null | LexicalEditor; _rootElement: null | HTMLElement; _editorState: EditorState; _htmlConversions: DOMConversionCache; _pendingEditorState: null | EditorState; _compositionKey: null | NodeKey; _deferred: (() => void)[]; _updates: [() => void, void | EditorUpdateOptions][]; _updating: boolean; _keyToDOMMap: Map<NodeKey, HTMLElement>; _listeners: Listeners; _commands: Commands; _nodes: RegisteredNodes; _onError: ErrorHandler; _onWarn: ErrorHandler; _decorators: { [NodeKey]: unknown, }; _pendingDecorators: null | { [NodeKey]: unknown, }; _createEditorArgs?: CreateEditorArgs; _config: EditorConfig; _dirtyType: 0 | 1 | 2; _cloneNotNeeded: Set<NodeKey>; _dirtyLeaves: Set<NodeKey>; _dirtyElements: Map<NodeKey, IntentionallyMarkedAsDirtyElement>; _normalizedNodes: Set<NodeKey>; _updateTags: Set<UpdateTag>; _observer: null | MutationObserver; _key: string; _editable: boolean; _headless: boolean; isComposing(): boolean; registerUpdateListener(listener: UpdateListener): () => void; registerRootListener(listener: RootListener): () => void; registerDecoratorListener(listener: DecoratorListener): () => void; registerTextContentListener(listener: TextContentListener): () => void; registerCommand<P>( command: LexicalCommand<P>, listener: CommandListener<P>, priority: CommandListenerPriority | CommandListenerPriorityBefore, ): () => void; registerEditableListener(listener: EditableListener): () => void; registerMutationListener( klass: Class<LexicalNode>, listener: MutationListener, options?: MutationListenerOptions, ): () => void; registerNodeTransform<T extends LexicalNode>( klass: Class<T>, listener: Transform<T>, ): () => void; dispatchCommand<P>(command: LexicalCommand<P>, payload: P): boolean; hasNode(node: Class<LexicalNode>): boolean; hasNodes(nodes: Class<LexicalNode>[]): boolean; getKey(): string; getDecorators<X>(): { [NodeKey]: X, }; getRootElement(): null | HTMLElement; setRootElement(rootElement: null | HTMLElement): void; getElementByKey(key: NodeKey): null | HTMLElement; getEditorState(): EditorState; setEditorState(editorState: EditorState, options?: EditorSetOptions): void; parseEditorState( maybeStringifiedEditorState: string | SerializedEditorState, updateFn?: () => void, ): EditorState; read<V>(callbackFn: () => V): V; read<V>(mode: EditorReadMode, callbackFn: () => V): V; update(updateFn: () => void, options?: EditorUpdateOptions): boolean; focus(callbackFn?: () => void, options?: EditorFocusOptions): void; blur(): void; isEditable(): boolean; setEditable(editable: boolean): void; toJSON(): SerializedEditor; } export type EditorReadMode = 'force-commit' | 'pending' | 'latest'; export type EditorUpdateOptions = { onUpdate?: () => void, tag?: string | string[], skipTransforms?: true, discrete?: true, }; type EditorFocusOptions = { defaultSelection?: 'rootStart' | 'rootEnd', }; export type EditorSetOptions = { tag?: string, }; type EditorThemeClassName = string; type TextNodeThemeClasses = { base?: EditorThemeClassName, bold?: EditorThemeClassName, underline?: EditorThemeClassName, strikethrough?: EditorThemeClassName, underlineStrikethrough?: EditorThemeClassName, italic?: EditorThemeClassName, code?: EditorThemeClassName, subscript?: EditorThemeClassName, superscript?: EditorThemeClassName, lowercase?: EditorThemeClassName, uppercase?: EditorThemeClassName, capitalize?: EditorThemeClassName, }; export type EditorThemeClasses = { characterLimit?: EditorThemeClassName, ltr?: EditorThemeClassName, rtl?: EditorThemeClassName, text?: TextNodeThemeClasses, paragraph?: EditorThemeClassName, image?: EditorThemeClassName, list?: { ul?: EditorThemeClassName, ulDepth?: EditorThemeClassName[], ol?: EditorThemeClassName, olDepth?: EditorThemeClassName[], checklist?: EditorThemeClassName, listitem?: EditorThemeClassName, listitemChecked?: EditorThemeClassName, listitemUnchecked?: EditorThemeClassName, nested?: { list?: EditorThemeClassName, listitem?: EditorThemeClassName, }, }, table?: EditorThemeClassName, tableRow?: EditorThemeClassName, tableCell?: EditorThemeClassName, tableCellHeader?: EditorThemeClassName, mark?: EditorThemeClassName, markOverlap?: EditorThemeClassName, link?: EditorThemeClassName, quote?: EditorThemeClassName, code?: EditorThemeClassName, codeHighlight?: {[string]: EditorThemeClassName}, hashtag?: EditorThemeClassName, heading?: { h1?: EditorThemeClassName, h2?: EditorThemeClassName, h3?: EditorThemeClassName, h4?: EditorThemeClassName, h5?: EditorThemeClassName, h6?: EditorThemeClassName, }, embedBlock?: { base?: EditorThemeClassName, focus?: EditorThemeClassName, }, // Handle other generic values [string]: EditorThemeClassName | {[string]: EditorThemeClassName}, }; export type EditorConfig = { dom?: EditorDOMRenderConfig, theme: EditorThemeClasses, namespace: string, disableEvents?: boolean, }; export type CommandListenerPriority = 0 | 1 | 2 | 3 | 4; export type CommandListenerPriorityBefore = -8 | -7 | -6 | -5 | -4; export const COMMAND_PRIORITY_EDITOR = 0; export const COMMAND_PRIORITY_LOW = 1; export const COMMAND_PRIORITY_NORMAL = 2; export const COMMAND_PRIORITY_HIGH = 3; export const COMMAND_PRIORITY_CRITICAL = 4; export const COMMAND_PRIORITY_BEFORE_EDITOR = -8; export const COMMAND_PRIORITY_BEFORE_LOW = -7; export const COMMAND_PRIORITY_BEFORE_NORMAL = -6; export const COMMAND_PRIORITY_BEFORE_HIGH = -5; export const COMMAND_PRIORITY_BEFORE_CRITICAL = -4; export type LexicalNodeReplacement = { replace: Class<LexicalNode>, with: (node: LexicalNode) => LexicalNode, withKlass?: Class<LexicalNode>, }; export type HTMLConfig = { export?: Map< Class<LexicalNode>, (editor: LexicalEditor, target: LexicalNode) => DOMExportOutput, >, import?: DOMConversionMap, }; declare export function createEditor(editorConfig?: { editorState?: EditorState, namespace: string, theme?: EditorThemeClasses, parentEditor?: LexicalEditor, nodes?: ReadonlyArray<Class<LexicalNode> | LexicalNodeReplacement>, onError: (error: Error) => void, disableEvents?: boolean, editable?: boolean, html?: HTMLConfig, }): LexicalEditor; /** * LexicalEditorState */ export interface EditorState { _nodeMap: NodeMap; _selection: null | BaseSelection; _flushSync: boolean; _readOnly: boolean; constructor(nodeMap: NodeMap, selection?: BaseSelection | null): void; isEmpty(): boolean; read<V>(callbackFn: () => V, options?: EditorStateReadOptions): V; toJSON(): SerializedEditorState; clone(selection?: BaseSelection | null): EditorState; } type EditorStateReadOptions = { editor?: LexicalEditor | null; } /** * LexicalNode */ export type DOMConversion = { conversion: DOMConversionFn, priority: 0 | 1 | 2 | 3 | 4, }; export type DOMConversionFn = (element: Node) => DOMConversionOutput | null; export type DOMChildConversion = ( lexicalNode: LexicalNode, parentLexicalNode: ?LexicalNode | null, ) => LexicalNode | null | void; export type DOMConversionMap = { [NodeName]: <T extends HTMLElement>(node: T) => DOMConversion | null, }; type NodeName = string; export type DOMConversionOutput = { after?: (childLexicalNodes: LexicalNode[]) => LexicalNode[], forChild?: DOMChildConversion, node: null | LexicalNode | LexicalNode[], }; export type DOMExportOutput = { after?: (generatedElement: ?HTMLElement) => ?HTMLElement, element?: HTMLElement | null, }; export type NodeKey = string; declare export class LexicalNode { __type: string; __key: NodeKey; __parent: null | NodeKey; __next: null | NodeKey; __prev: null | NodeKey; static getType(): string; static clone(data: $FlowFixMe): LexicalNode; static importDOM(): DOMConversionMap | null; constructor(key?: NodeKey): void; exportDOM(editor: LexicalEditor): DOMExportOutput; exportJSON(): SerializedLexicalNode; updateFromJSON(serializedNode: $FlowFixMe): this; getType(): string; isAttached(): boolean; isSelected(): boolean; getKey(): NodeKey; getIndexWithinParent(): number; getParent(): ElementNode | null; /** * @deprecated The type parameter is an unchecked and unsafe cast and * will be removed in a future release. Call this method without a type * argument and narrow the result with a type guard instead. */ getParent<T extends ElementNode>(): T | null; getParentOrThrow(): ElementNode; /** * @deprecated The type parameter is an unchecked and unsafe cast and * will be removed in a future release. Call this method without a type * argument and narrow the result with a type guard instead. */ getParentOrThrow<T extends ElementNode>(): T; getTopLevelElement(): DecoratorNode<unknown> | ElementNode | null; getTopLevelElementOrThrow(): DecoratorNode<unknown> | ElementNode; getParents(): ElementNode[]; /** * @deprecated The type parameter is an unchecked and unsafe cast and * will be removed in a future release. Call this method without a type * argument and narrow the result with a type guard instead. */ getParents<T extends ElementNode>(): T[]; getParentKeys(): NodeKey[]; getPreviousSibling(): LexicalNode | null; /** * @deprecated The type parameter is an unchecked and unsafe cast and * will be removed in a future release. Call this method without a type * argument and narrow the result with a type guard instead. */ getPreviousSibling<T extends LexicalNode>(): T | null; getPreviousSiblings(): LexicalNode[]; /** * @deprecated The type parameter is an unchecked and unsafe cast and * will be removed in a future release. Call this method without a type * argument and narrow the result with a type guard instead. */ getPreviousSiblings<T extends LexicalNode>(): T[]; getNextSibling(): LexicalNode | null; /** * @deprecated The type parameter is an unchecked and unsafe cast and * will be removed in a future release. Call this method without a type * argument and narrow the result with a type guard instead. */ getNextSibling<T extends LexicalNode>(): T | null; getNextSiblings(): LexicalNode[]; /** * @deprecated The type parameter is an unchecked and unsafe cast and * will be removed in a future release. Call this method without a type * argument and narrow the result with a type guard instead. */ getNextSiblings<T extends LexicalNode>(): T[]; getCommonAncestor<T extends ElementNode>(node: LexicalNode): T | null; is(object: ?LexicalNode): boolean; isBefore(targetNode: LexicalNode): boolean; isParentOf(targetNode: LexicalNode): boolean; getNodesBetween(targetNode: LexicalNode): LexicalNode[]; isDirty(): boolean; // $FlowFixMe[incompatible-type] getLatest<T extends LexicalNode>(this: T): T; // $FlowFixMe[incompatible-type] getWritable<T extends LexicalNode>(this: T): T; getTextContent(includeDirectionless?: boolean): string; getTextContentSize(includeDirectionless?: boolean): number; createDOM(config: EditorConfig, editor: LexicalEditor): HTMLElement; updateDOM( // $FlowFixMe[unclear-type] -- Required to be `this`, but that is not generally sound or allowed in flow's variance model prevNode: any, dom: HTMLElement, config: EditorConfig, ): boolean; getDOMSlot(element: HTMLElement): DOMSlot<HTMLElement>; remove(preserveEmptyParent?: boolean): void; replace<N extends LexicalNode>(replaceWith: N): N; insertAfter( nodeToInsert: LexicalNode, restoreSelection?: boolean, ): LexicalNode; insertBefore( nodeToInsert: LexicalNode, restoreSelection?: boolean, ): LexicalNode; selectPrevious(anchorOffset?: number, focusOffset?: number): RangeSelection; selectNext(anchorOffset?: number, focusOffset?: number): RangeSelection; markDirty(): void; reconcileObservedMutation(dom: HTMLElement, editor: LexicalEditor): void; // $FlowFixMe[unclear-type] -- Required to be `this`, but that is not generally sound or allowed in flow's variance model afterCloneFrom(prevNode: any): void; // $FlowFixMe[unclear-type] -- Required to be `this`, but that is not generally sound or allowed in flow's variance model resetOnCopyNodeFrom(original: any): void; } export type NodeMap = Map<NodeKey, LexicalNode>; /** * LexicalSelection */ declare export function $isBlockElementNode( node: ?LexicalNode, ): node is ElementNode; export interface BaseSelection { dirty: boolean; clone(): BaseSelection; extract(): LexicalNode[]; getNodes(): LexicalNode[]; getStartEndPoints(): null | [PointType, PointType]; getTextContent(): string; insertRawText(text: string): void; is(selection: null | BaseSelection): boolean; isBackward(): boolean; isCollapsed(): boolean; insertText(text: string): void; insertNodes(nodes: LexicalNode[]): void; getCachedNodes(): null | LexicalNode[]; setCachedNodes(nodes: null | LexicalNode[]): void; } declare export class NodeSelection implements BaseSelection { _nodes: Set<NodeKey>; dirty: boolean; constructor(objects: Set<NodeKey>): void; is(selection: null | BaseSelection): boolean; isBackward(): boolean; isCollapsed(): boolean; add(key: NodeKey): void; delete(key: NodeKey): void; clear(): void; has(key: NodeKey): boolean; clone(): NodeSelection; extract(): LexicalNode[]; insertRawText(): void; insertText(): void; getNodes(): LexicalNode[]; getStartEndPoints(): null; getTextContent(): string; insertNodes(nodes: LexicalNode[]): void; getCachedNodes(): null | LexicalNode[]; setCachedNodes(nodes: null | LexicalNode[]): void; } declare export function $isNodeSelection( x: ?unknown, ): x is NodeSelection; declare export class RangeSelection implements BaseSelection { anchor: PointType; focus: PointType; dirty: boolean; format: number; style: string; constructor(anchor: PointType, focus: PointType, format: number): void; is(selection: null | BaseSelection): boolean; isBackward(): boolean; isCollapsed(): boolean; getNodes(): LexicalNode[]; setTextNodeRange( anchorNode: TextNode, anchorOffset: number, focusNode: TextNode, focusOffset: number, ): void; getTextContent(): string; // $FlowFixMe[cannot-resolve-name] DOM API applyDOMRange(range: StaticRange): void; clone(): RangeSelection; toggleFormat(format: TextFormatType): void; setStyle(style: string): void; hasFormat(type: TextFormatType): boolean; insertText(text: string): void; insertRawText(text: string): void; removeText(): void; formatText(formatType: TextFormatType): void; insertNodes(nodes: LexicalNode[]): void; insertParagraph(): void; insertLineBreak(selectStart?: boolean): void; extract(): LexicalNode[]; modify( alter: 'move' | 'extend', isBackward: boolean, granularity: 'character' | 'word' | 'lineboundary', ): void; deleteCharacter(isBackward: boolean): void; deleteLine(isBackward: boolean): void; deleteWord(isBackward: boolean): void; insertNodes(nodes: LexicalNode[]): void; getCachedNodes(): null | LexicalNode[]; setCachedNodes(nodes: null | LexicalNode[]): void; forwardDeletion(anchor: PointType, anchorNode: ElementNode | TextNode, isBackward: boolean): boolean; getStartEndPoints(): [PointType, PointType]; } export type TextPoint = TextPointType; type TextPointType = { key: NodeKey, offset: number, type: 'text', is: (PointType) => boolean, isBefore: (PointType) => boolean, getNode: () => TextNode, set: (key: NodeKey, offset: number, type: 'text' | 'element') => void, getCharacterOffset: () => number, }; export type ElementPoint = ElementPointType; type ElementPointType = { key: NodeKey, offset: number, type: 'element', is: (PointType) => boolean, isBefore: (PointType) => boolean, getNode: () => ElementNode, set: (key: NodeKey, offset: number, type: 'text' | 'element') => void, }; export type Point = PointType; export type PointType = TextPointType | ElementPointType; declare class _Point { key: NodeKey; offset: number; type: 'text' | 'element'; constructor(key: NodeKey, offset: number, type: 'text' | 'element'): void; is(point: PointType): boolean; isBefore(b: PointType): boolean; getNode(): LexicalNode; set(key: NodeKey, offset: number, type: 'text' | 'element', onlyIfChanged?: boolean): void; } declare export function $createPoint( key: NodeKey, offset: number, type: 'text' | 'element', ): PointType; declare export function $createRangeSelection(): RangeSelection; declare export function $createRangeSelectionFromDom( domSelection: Selection | null, editor: LexicalEditor, ): null | RangeSelection; declare export function $createNodeSelection(): NodeSelection; declare export function $isRangeSelection( x: ?unknown, ): x is RangeSelection; declare export function $formatText( selection: RangeSelection | NodeSelection, formatType: TextFormatType, alignWithFormat?: number | null, ): void; declare export function $generateNodesFromRawText( text: string, ): (TextNode | LineBreakNode)[]; declare export function $setTextFormat( selection: RangeSelection | NodeSelection, formats: Partial<Record<TextFormatType, boolean>>, ): void; declare export function $getSelection(): null | BaseSelection; declare export function $getTextContent(): string; declare export function $getPreviousSelection(): null | BaseSelection; declare export function $insertNodes(nodes: LexicalNode[]): void; declare export function $selectAll(selection?: RangeSelection | null): RangeSelection; declare export function $getCharacterOffsets( selection: BaseSelection, ): [number, number]; /** * LexicalTextNode */ export type TextFormatType = | 'bold' | 'underline' | 'strikethrough' | 'italic' | 'highlight' | 'code' | 'subscript' | 'superscript' | 'lowercase' | 'uppercase' | 'capitalize'; type TextModeType = 'normal' | 'token' | 'segmented'; declare export class TextNode extends LexicalNode { __text: string; __format: number; __style: string; __mode: 0 | 1 | 2 | 3; __detail: number; static getType(): string; static clone(node: $FlowFixMe): TextNode; constructor(text: string, key?: NodeKey): void; getTopLevelElement(): ElementNode | null; getTopLevelElementOrThrow(): ElementNode; getFormat(): number; getStyle(): string; isComposing(): boolean; isInline(): true; isToken(): boolean; isSegmented(): boolean; isDirectionless(): boolean; isUnmergeable(): boolean; hasFormat(type: TextFormatType): boolean; isSimpleText(): boolean; getTextContent(): string; getFormatFlags(type: TextFormatType, alignWithFormat: null | number): number; createDOM(config: EditorConfig): HTMLElement; selectionTransform( prevSelection: null | BaseSelection, nextSelection: RangeSelection, ): void; setFormat(format: number): this; setStyle(style: string): this; toggleFormat(type: TextFormatType): TextNode; toggleDirectionless(): this; toggleUnmergeable(): this; setMode(type: TextModeType): this; setDetail(detail: number): this; getDetail(): number; getMode(): TextModeType; setTextContent(text: string): TextNode; select(_anchorOffset?: number, _focusOffset?: number): RangeSelection; spliceText( offset: number, delCount: number, newText: string, moveSelection?: boolean, ): TextNode; canInsertTextBefore(): boolean; canInsertTextAfter(): boolean; splitText(...splitOffsets: number[]): TextNode[]; mergeWithSibling(target: TextNode): TextNode; isTextEntity(): boolean; static importJSON(serializedTextNode: SerializedTextNode): TextNode; exportJSON(): SerializedTextNode; } export interface InlineFormattableNode { readonly __isInlineFormattable: true; getFormat(): number; getFormatFlags(type: TextFormatType, alignWithFormat: null | number): number; hasFormat(type: TextFormatType): boolean; setFormat(format: number): unknown; toggleFormat(type: TextFormatType): unknown; } declare export function $isInlineFormattable( node: ?LexicalNode, ): node is LexicalNode & InlineFormattableNode; declare export function $createTextNode(text?: string): TextNode; declare export function $isTextNode( node: ?LexicalNode, ): node is TextNode; /** * LexicalTabNode */ export type SerializedTabNode = SerializedTextNode; declare export function $createTabNode(): TabNode; declare export function $isTabNode( node: LexicalNode | null | void, ): node is TabNode; declare export class TabNode extends TextNode { static getType(): string; static clone(node: TabNode): TabNode; constructor(key?: NodeKey): void; static importDOM(): DOMConversionMap | null; static importJSON(serializedTabNode: SerializedTabNode): TabNode; exportJSON(): SerializedTabNode; } /** * LexicalLineBreakNode */ declare export class LineBreakNode extends LexicalNode { static getType(): string; static clone(node: LineBreakNode): LineBreakNode; constructor(key?: NodeKey): void; getTextContent(): '\n'; createDOM(): HTMLElement; updateDOM(): false; isInline(): true; static importJSON( serializedLineBreakNode: SerializedLineBreakNode, ): LineBreakNode; exportJSON(): SerializedLexicalNode; } declare export function $createLineBreakNode(): LineBreakNode; declare export function $isLineBreakNode( node: ?LexicalNode, ): node is LineBreakNode; /** * LexicalRootNode */ declare export class RootNode extends ElementNode { __cachedText: null | string; static getType(): string; static clone(): RootNode; constructor(): void; getTextContent(): string; select(_anchorOffset?: number, _focusOffset?: number): RangeSelection; remove(): void; replace<N extends LexicalNode>(node: N): N; insertBefore<T extends LexicalNode>(nodeToInsert: T): T; insertAfter<T extends LexicalNode>(nodeToInsert: T): T; append(...nodesToAppend: LexicalNode[]): this; canBeEmpty(): false; } declare export function $isRootNode( node: ?LexicalNode, ): node is RootNode; /** * LexicalElementNode */ export type ElementFormatType = | 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''; declare export class ElementNode extends LexicalNode { __first: null | NodeKey; __last: null | NodeKey; __size: number; __format: number; __indent: number; __dir: 'ltr' | 'rtl' | null; constructor(key?: NodeKey): void; getTopLevelElement(): ElementNode | null; getTopLevelElementOrThrow(): ElementNode; getFormat(): number; getFormatType(): ElementFormatType; getIndent(): number; getChildren(): LexicalNode[]; /** * @deprecated The type parameter is an unchecked and unsafe cast and * will be removed in a future release. Call this method without a type * argument and narrow the result with a type guard instead. */ getChildren<T extends LexicalNode>(): T[]; /** * @deprecated The type parameter is an unchecked and unsafe cast and * will be removed in a future release. Call this method without a type * argument and narrow the result with a type guard instead. */ getChildren<T extends LexicalNode[]>(): T; getChildrenKeys(): NodeKey[]; getChildrenSize(): number; isEmpty(): boolean; isDirty(): boolean; getAllTextNodes(): TextNode[]; getFirstDescendant(): null | LexicalNode; /** * @deprecated The type parameter is an unchecked and unsafe cast and * will be removed in a future release. Call this method without a type * argument and narrow the result with a type guard instead. */ getFirstDescendant<T extends LexicalNode>(): null | T; getLastDescendant(): null | LexicalNode; /** * @deprecated The type parameter is an unchecked and unsafe cast and * will be removed in a future release. Call this method without a type * argument and narrow the result with a type guard instead. */ getLastDescendant<T extends LexicalNode>(): null | T; getDescendantByIndex(index: number): null | LexicalNode; /** * @deprecated The type parameter is an unchecked and unsafe cast and * will be removed in a future release. Call this method without a type * argument and narrow the result with a type guard instead. */ getDescendantByIndex<T extends LexicalNode>(index: number): null | T; getFirstChild(): null | LexicalNode; /** * @deprecated The type parameter is an unchecked and unsafe cast and * will be removed in a future release. Call this method without a type * argument and narrow the result with a type guard instead. */ getFirstChild<T extends LexicalNode>(): null | T; getFirstChildOrThrow(): LexicalNode; /** * @deprecated The type parameter is an unchecked and unsafe cast and * will be removed in a future release. Call this method without a type * argument and narrow the result with a type guard instead. */ getFirstChildOrThrow<T extends LexicalNode>(): T; getLastChild(): null | LexicalNode; /** * @deprecated The type parameter is an unchecked and unsafe cast and * will be removed in a future release. Call this method without a type * argument and narrow the result with a type guard instead. */ getLastChild<T extends LexicalNode>(): null | T; getLastChildOrThrow(): LexicalNode; /** * @deprecated The type parameter is an unchecked and unsafe cast and * will be removed in a future release. Call this method without a type * argument and narrow the result with a type guard instead. */ getLastChildOrThrow<T extends LexicalNode>(): T; getChildAtIndex(index: number): null | LexicalNode; /** * @deprecated The type parameter is an unchecked and unsafe cast and * will be removed in a future release. Call this method without a type * argument and narrow the result with a type guard instead. */ getChildAtIndex<T extends LexicalNode>(index: number): null | T; getTextContent(): string; getDirection(): 'ltr' | 'rtl' | null; hasFormat(type: ElementFormatType): boolean; select(_anchorOffset?: number, _focusOffset?: number): RangeSelection; selectStart(): RangeSelection; selectEnd(): RangeSelection; clear(): this; append(...nodesToAppend: LexicalNode[]): this; setDirection(direction: 'ltr' | 'rtl' | null): this; setFormat(type: ElementFormatType): this; setIndent(indentLevel: number): this; insertNewAfter( selection: RangeSelection, restoreSelection?: boolean, ): null | LexicalNode; canIndent(): boolean; collapseAtStart(selection: RangeSelection): boolean; excludeFromCopy(destination: 'clone' | 'html'): boolean; canReplaceWith(replacement: LexicalNode): boolean; canInsertAfter(node: LexicalNode): boolean; extractWithChild( child: LexicalNode, selection: BaseSelection, destination: 'clone' | 'html', ): boolean; canBeEmpty(): boolean; canInsertTextBefore(): boolean; canInsertTextAfter(): boolean; isInline(): boolean; isShadowRoot(): boolean; canSelectionRemove(): boolean; splice( start: number, deleteCount: number, nodesToInsert: LexicalNode[], ): this; exportJSON(): SerializedElementNode; getDOMSlot(element: HTMLElement): ElementDOMSlot<HTMLElement>; } declare export function $isElementNode( node: ?LexicalNode, ): node is ElementNode; /** * DOMSlot */ declare export class DOMSlot<out T extends HTMLElement> { readonly element: T; readonly before: Node | null; readonly after: Node | null; constructor(element: T, before?: Node | null | void, after?: Node | null | void): void; withBefore(before: Node | null | void): DOMSlot<T>; withAfter(after: Node | null | void): DOMSlot<T>; withElement<ElementType extends HTMLElement>(element: ElementType): DOMSlot<ElementType>; insertChild(dom: Node): this; removeChild(dom: Node): this; replaceChild(dom: Node, prevDom: Node): this; getFirstChild(): Node | null; resolveLeafPosition(leafDOM: HTMLElement, initialDOM: Node, initialOffset: number): 'before' | 'after'; } /** * ElementDOMSlot */ declare export class ElementDOMSlot<out T extends HTMLElement> extends DOMSlot<T> { withBefore(before: Node | null | void): ElementDOMSlot<T>; withAfter(after: Node | null | void): ElementDOMSlot<T>; withElement<ElementType extends HTMLElement>(element: ElementType): ElementDOMSlot<ElementType>; // getManagedLineBreak(): HTMLElement | null; removeManagedLineBreak(): void; insertManagedLineBreak(webkitHack: boolean): void; getFirstChildOffset(): number; resolveChildIndex(element: ElementNode, elementDOM: HTMLElement, initialDOM: Node, initialOffset: number): [node: ElementNode, idx: number]; } export type SetDOMUnmanagedOptions = {captureSelection?: boolean}; declare export function setDOMUnmanaged( elementDOM: HTMLElement, options?: SetDOMUnmanagedOptions, ): void; declare export function isDOMUnmanaged(elementDOM: HTMLElement): boolean; declare export function $markSlotEditable( element: HTMLElement, editor?: LexicalEditor, ): void; declare export function isDOMCapturingSelection( elementDOM: Node, editor: LexicalEditor, ): boolean; /** * LexicalDecoratorNode */ declare export class DecoratorNode<X> extends LexicalNode { constructor(key?: NodeKey): void; // Not sure how to get flow to agree that the DecoratorNode<unknown> is compatible with this, // so we have a less precise type than in TS // getTopLevelElement(): this | ElementNode | null; // getTopLevelElementOrThrow(): this | ElementNode; decorate(editor: LexicalEditor, config: EditorConfig): X; isIsolated(): boolean; isInline(): boolean; isKeyboardSelectable(): boolean; } declare export function $isDecoratorNode<T = unknown>( node: ?LexicalNode, ): node is DecoratorNode<T>; declare export function $isLexicalNode( node: ?LexicalNode, ): node is LexicalNode; /** * LexicalParagraphNode */ declare export class ParagraphNode extends ElementNode { static getType(): string; static clone(node: ParagraphNode): ParagraphNode; constructor(key?: NodeKey): void; createDOM(config: EditorConfig): HTMLElement; insertNewAfter( selection: RangeSelection, restoreSelection?: boolean, ): ParagraphNode; collapseAtStart(): boolean; static importJSON( serializedParagraphNode: SerializedParagraphNode, ): ParagraphNode; exportJSON(): SerializedElementNode; } declare export function $createParagraphNode(): ParagraphNode; declare export function $isParagraphNode( node: ?LexicalNode, ): node is ParagraphNode; /** * LexicalUtils */ export type EventHandler = (event: Event, editor: LexicalEditor) => void; declare export function stopLexicalPropagation(event: Event): void; declare export function $hasUpdateTag(tag: UpdateTag): boolean; declare export function $addUpdateTag(tag: UpdateTag): void; declare export function $onUpdate(updateFn: () => void): void; declare export function getNearestEditorFromDOMNode( node: Node | null, ): LexicalEditor | null; declare export function $getNearestNodeFromDOMNode( startingDOM: Node, ): LexicalNode | null; declare export function $getNodeByKey( key: NodeKey, _editorState?: EditorState, ): LexicalNode | null; /** * @deprecated The type parameter is an unchecked and unsafe cast and * will be removed in a future release. Call this function without a type * argument and narrow the result with a type guard instead. */ declare export function $getNodeByKey<N extends LexicalNode>(key: NodeKey): N | null; declare export function $getNodeByKeyOrThrow(key: NodeKey): LexicalNode; /** * @deprecated The type parameter is an unchecked and unsafe cast and * will be removed in a future release. Call this function without a type * argument and narrow the result with a type guard instead. */ declare export function $getNodeByKeyOrThrow<N extends LexicalNode>(key: NodeKey): N; declare export function $getRoot(): RootNode; declare export function $isLeafNode<T = unknown>( node: ?LexicalNode, ): node is TextNode | LineBreakNode | DecoratorNode<T>; declare export function $setCompositionKey( compositionKey: null | NodeKey, ): void; declare export function $setSelection(selection: null | BaseSelection): void; declare export function $nodesOfType<T extends LexicalNode>(klass: Class<T>): T[]; declare export function $getAdjacentNode( focus: Point, isBackward: boolean, ): null | LexicalNode; declare export function resetRandomKey(): void; declare export function generateRandomKey(): string; declare export function $isInlineElementOrDecoratorNode<T = unknown>( node: LexicalNode, ): node is ElementNode| DecoratorNode<T>; export interface ShadowRootNode extends ElementNode { isShadowRoot(): true; } declare export function $getNearestRootOrShadowRoot( node: LexicalNode, ): RootNode | ElementNode; declare export function $isShadowRootNode( node: ?LexicalNode, ): node is ShadowRootNode; declare export function $isRootOrShadowRoot( node: ?LexicalNode, ): node is RootNode | ShadowRootNode; declare export function $needsBlockCursorBeside( node: null | LexicalNode, ): boolean; declare export function $hasAncestor( child: LexicalNode, targetNode: LexicalNode, ): boolean; declare export function $createChildrenArray( element: ElementNode, nodeMap: null | NodeMap, ): NodeKey[]; declare export function $findMatchingParent<T extends LexicalNode>( startingNode: LexicalNode, findFn: (node: LexicalNode) => node is T, ): T | null; declare export function $findMatchingParent( startingNode: LexicalNode, findFn: (node: LexicalNode) => boolean, ): LexicalNode | null; declare export function $getNodeFromDOMNode( dom: Node, editorState?: EditorState, ): LexicalNode | null; declare export function $isTokenOrSegmented(node: TextNode): boolean; declare export function $isTokenOrTab(node: TextNode): boolean; declare export function $splitNode( node: ElementNode, offset: number, ): [ElementNode | null, ElementNode]; declare export function $setDirectionFromDOM<T extends ElementNode>( node: T, domNode: HTMLElement, ): T; declare export function $setFormatFromDOM<T extends ElementNode>( node: T, domNode: HTMLElement, ): T; declare export function $cloneWithProperties<T extends LexicalNode>(node: T): T; declare export function $cloneWithPropertiesEphemeral<T extends LexicalNode>(node: T): T; declare export function $copyNode<T extends LexicalNode>(node: T, skipReset?: boolean): T; declare export function $getDocument(): Document; declare export function $getEditor(): LexicalEditor; declare export function $getEditorDOMRenderConfig( editor?: LexicalEditor, ): EditorDOMRenderConfig; declare export function $getDOMSlot<N extends LexicalNode>( node: N, dom: HTMLElement, editor?: LexicalEditor, ): DOMSlotForNode<N>; declare export function mountSlotContainer( editor: LexicalEditor, nodeKey: NodeKey, slotName: string, target: HTMLElement, ): HTMLElement | null; declare export function unmountSlotContainer( editor: LexicalEditor, nodeKey: NodeKey, container: HTMLElement, ): void; export interface SlotHostNode { __slots: null | Map<string, NodeKey>; } export interface SlotChildNode { __slotHost: null | NodeKey; } declare export function $isSlotHost( node: LexicalNode, ): node is LexicalNode & SlotHostNode; declare export function $isSlotChild( node: LexicalNode, ): node is LexicalNode & SlotChildNode; declare export function $getSlotHost( node: LexicalNode, ): ElementNode | DecoratorNode<unknown> | null; declare export function $getSlotFrame( node: LexicalNode, ): LexicalNode | null; declare export function getDeclaredSlots( klass: Class<LexicalNode>, ): ReadonlyArray<string>; declare export function $getSlotNameWithinHost( slotChild: LexicalNode, ): string | null; declare export function $getSlotNames(node: LexicalNode): string[]; declare export function $getSlot( node: LexicalNode, name: string, ): LexicalNode | null; declare export function $setSlot<T extends LexicalNode & SlotHostNode>( host: T, name: string, node: LexicalNode, ): T; declare export function $removeSlot<T extends LexicalNode & SlotHostNode>( host: T, name: string, ): T; declare export function $getDOMTextNode( node: TextNode, dom: HTMLElement, editor?: LexicalEditor, ): Text | null; declare export function $isElementDOMSlot( slot: DOMSlot<HTMLElement>, ): slot is ElementDOMSlot<HTMLElement>; declare export var DEFAULT_EDITOR_DOM_CONFIG: EditorDOMRenderConfig; // Flow's built-in DOM lib doesn't expose StaticRange yet; declare the // minimal shape we need for the shadow-aware selection helpers below. declare type StaticRange = { readonly startContainer: Node, readonly startOffset: number, readonly endContainer: Node, readonly endOffset: number, readonly collapsed: boolean, }; export type DOMSelectionBoundaryPoints = { anchorNode: Node | null, anchorOffset: number, direction?: void | 'forward' | 'backward' | 'none', focusNode: Node | null, focusOffset: number, }; declare export function isDOMShadowRoot(node: unknown): boolean; declare export function findAllLexicalElementsDeep(root: Document | ShadowRoot): Generator<Element, void, void>; declare export function getDOMShadowRoots(node: Node): ShadowRoot[]; declare export function getParentElement(node: Node): HTMLElement | null; declare export function getRootOwnerDocument(rootElement: HTMLElement | null): Document; declare export function getComposedStaticRange( domSelection: Selection, rootElement: HTMLElement | null, ): StaticRange | null; declare export function getDOMSelectionRange( domSelection: Selection, rootElement: HTMLElement | null, ): Range | null; declare export function getDOMSelectionPoints( domSelection: Selection, rootElement: HTMLElement | null, ): DOMSelectionBoundaryPoints; declare export function getDOMSelectionRangeAndPoints( domSelection: Selection, rootElement: HTMLElement | null, ): {points: DOMSelectionBoundaryPoints, range: Range | null}; declare export function getActiveElement(node: Node): Element | null; declare export function getActiveElementDeep( root: Document | ShadowRoot, ): Element | null; declare export function getComposedEventTarget(event: Event): EventTarget | null; /** * LexicalNormalization */ declare export function $normalizeSelection( selection: RangeSelection, ): RangeSelection; declare export function $normalizeSelection__EXPERIMENTAL( selection: RangeSelection, ): RangeSelection; export type RefCountedRegistry<Key, Options = void> = { register: (key: Key, options?: Options) => () => void, dispose: () => void, }; declare export function createRefCountedRegistry<Key, Options = void>( activate: (key: Key, options: Options | void) => () => void, ): RefCountedRegistry<Key, Options>; /** * Serialization/Deserialization * */ type InternalSerializedNode = { children?: InternalSerializedNode[], type: string, version: number, }; declare export function $parseSerializedNode( serializedNode: InternalSerializedNode, ): LexicalNode; declare export function $applyNodeReplacement<N extends LexicalNode>( node: LexicalNode, ): N; export type SerializedLexicalNode = { type: string, version: number, ... }; export type SerializedTextNode = { ...SerializedLexicalNode, detail: number, format: number, mode: TextModeType, style: string, text: string, ... }; export type SerializedElementNode = { ...SerializedLexicalNode, children: SerializedLexicalNode[], direction: 'ltr' | 'rtl' | null, format: ElementFormatType, indent: number, ... }; export type SerializedParagraphNode = { ...SerializedElementNode, ... }; export type SerializedLineBreakNode = { ...SerializedLexicalNode, ... }; export type SerializedDecoratorNode = { ...SerializedLexicalNode, ... }; export type SerializedRootNode = { ...SerializedElementNode, ... }; export type SerializedGridCellNode = { ...SerializedElementNode, colSpan: number, ... }; export interface SerializedEditorState { root: SerializedRootNode; } export type SerializedEditor = { editorState: SerializedEditorState, }; /** * LexicalCaret */ export interface BaseCaret<T extends LexicalNode, D extends CaretDirection, Type> extends Iterable<SiblingCaret<LexicalNode, D>> { readonly origin: T; readonly type: Type; readonly direction: D; getParentAtCaret(): null | ElementNode; getNodeAtCaret(): null | LexicalNode; getAdjacentCaret(): null | SiblingCaret<LexicalNode, D>; getSiblingCaret(): SiblingCaret<T, D>; remove(): BaseCaret<T, D, Type>; // this insert(node: LexicalNode): BaseCaret<T, D, Type>; // this replaceOrInsert(node: LexicalNode, includeChildren?: boolean): BaseCaret<T, D, Type>; // this splice(deleteCount: number, nodes: Iterable<LexicalNode>, nodesDirection?: CaretDirection): BaseCaret<T, D, Type>; // this } export type CaretDirection = 'next' | 'previous'; type FLIP_DIRECTION = {'next' : 'previous', 'previous': 'next'}; export interface CaretRange<D extends CaretDirection = CaretDirection> extends Iterable<NodeCaret<D>> { readonly type: 'node-caret-range'; readonly direction: D; anchor: PointCaret<D>; focus: PointCaret<D>; isCollapsed(): boolean; iterNodeCarets(rootMode?: RootMode): Iterable<NodeCaret<D>>; getTextSlices(): TextPointCaretSliceTuple<D>; } export type CaretType = 'sibling' | 'child'; export interface ChildCaret<T extends ElementNode = ElementNode, D extends CaretDirection = C