lexical
Version:
Lexical is an extensible text editor framework that provides excellent reliability, accessible and performance.
843 lines (842 loc) • 43.5 kB
TypeScript
/**
* 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.
*
*/
import type { EditorState } from './LexicalEditorState';
import type { RootNode } from './nodes/LexicalRootNode';
import { DecoratorNode, ElementNode, type LineBreakNode, type UpdateTag } from '.';
import { type DOMSlot, ElementDOMSlot } from './LexicalDOMSlot';
import { type CommandPayloadType, type DOMSlotForNode, type EditorDOMRenderConfig, type EditorThemeClasses, type Klass, type LexicalCommand, LexicalEditor, type MutatedNodes, type MutationListeners, type NodeMutation, type RegisteredNode, type RegisteredNodes } from './LexicalEditor';
import { LexicalNode, type LexicalPrivateDOM, type NodeKey, type NodeMap, type StaticNodeConfigValue } from './LexicalNode';
import { type BaseSelection, type PointType, type RangeSelection } from './LexicalSelection';
import { type TextFormatType, TextNode } from './nodes/LexicalTextNode';
export declare const emptyFunction: () => void;
export declare function setPendingNodeToClone(pendingNode: null | LexicalNode): void;
export declare function getPendingNodeToClone(): null | LexicalNode;
export declare function resetRandomKey(): void;
export declare function generateRandomKey(): string;
/**
* @internal
*/
export declare function getRegisteredNodeOrThrow(editor: LexicalEditor, nodeType: string): RegisteredNode;
/**
* @internal
*/
export declare function getRegisteredNode(editor: LexicalEditor, nodeType: string): undefined | RegisteredNode;
export declare const isArray: (arg: any) => arg is any[];
/** @internal */
export declare const scheduleMicroTask: (fn: () => void) => void;
export declare function $isSelectionCapturedInDecoratorInput(anchorDOM: Node, preResolvedActiveElement?: Element | null): boolean;
/** @deprecated renamed to {@link $isSelectionCapturedInDecoratorInput} by @lexical/eslint-plugin rules-of-lexical */
export declare const isSelectionCapturedInDecoratorInput: typeof $isSelectionCapturedInDecoratorInput;
export declare function isSelectionWithinEditor(editor: LexicalEditor, anchorDOM: null | Node, focusDOM: null | Node): boolean;
/**
* @returns true if the given argument is a LexicalEditor instance from this build of Lexical
*/
export declare function isLexicalEditor(editor: unknown): editor is LexicalEditor;
export declare function getNearestEditorFromDOMNode(node: Node | null): LexicalEditor | null;
/** @internal */
export declare function getEditorPropertyFromDOMNode(node: Node | null): unknown;
export declare function getTextDirection(text: string): 'ltr' | 'rtl' | null;
/**
* Return true if the TextNode is a TabNode or is in token mode.
*/
export declare function $isTokenOrTab(node: TextNode): boolean;
/**
* Return true if the TextNode is a TabNode, or is in token or segmented mode.
*/
export declare function $isTokenOrSegmented(node: TextNode): boolean;
/**
* @param node - The element being tested
* @returns Returns true if node is an DOM Text node, false otherwise.
*/
export declare function isDOMTextNode(node: unknown): node is Text;
/**
* @param node - The element being tested
* @returns Returns true if node is an DOM Document node, false otherwise.
*/
export declare function isDOMDocumentNode(node: unknown): node is Document;
export declare function getDOMTextNode(element: Node | null): Text | null;
export declare function toggleTextFormatType(format: number, type: TextFormatType, alignWithFormat: null | number): number;
export declare function $isLeafNode(node: LexicalNode | null | undefined): node is TextNode | LineBreakNode | DecoratorNode<unknown>;
export declare function $setNodeKey(node: LexicalNode, existingKey: NodeKey | null | undefined): void;
/**
* Removes a node from its parent, updating all necessary pointers and links.
* @internal
*
* This function does not adjust the editor's current selection. Callers
* that need element-anchored offsets in the old parent to track the child
* count change must call `$updateElementSelectionOnCreateDeleteNode` (with
* `times = -1`) after invoking this — see `$removeNode`, `replace`,
* `insertBefore`, and `insertAfter` for the pattern.
*
* This function is for internal use of the library.
* Please do not use it as it may change in the future.
*/
export declare function $removeFromParent(node: LexicalNode): void;
/** @deprecated renamed to {@link $removeFromParent} by @lexical/eslint-plugin rules-of-lexical */
export declare const removeFromParent: typeof $removeFromParent;
export declare function internalMarkNodeAsDirty(node: LexicalNode): void;
export declare function internalMarkSiblingsAsDirty(node: LexicalNode): void;
export declare function $setCompositionKey(compositionKey: null | NodeKey): void;
export declare function $getCompositionKey(): null | NodeKey;
/**
* Returns the node with the given key from the active EditorState
* (or the given EditorState), or null if it does not exist.
*/
export declare function $getNodeByKey(key: NodeKey, _editorState?: EditorState): LexicalNode | null;
/**
* @deprecated The type parameter is an unchecked and unsafe cast,
* equivalent to `$getNodeByKey(key) as T | null`, and will be removed
* in a future release. Call this function without a type argument and
* narrow the result with a type guard instead.
*/
export declare function $getNodeByKey<T extends LexicalNode>(key: NodeKey, _editorState?: EditorState): T | null;
export declare function $getNodeFromDOMNode(dom: Node, editorState?: EditorState): LexicalNode | null;
export declare function setNodeKeyOnDOMNode(dom: Node, editor: LexicalEditor, key: NodeKey): void;
export declare function clearNodeKeyOnDOMNode(dom: Node, editor: LexicalEditor): void;
export declare function getNodeKeyFromDOMNode(dom: Node, editor: LexicalEditor): NodeKey | undefined;
export declare function $getNearestNodeFromDOMNode(startingDOM: Node, editorState?: EditorState): LexicalNode | null;
export declare function cloneDecorators(editor: LexicalEditor): Record<NodeKey, unknown>;
export declare function getEditorStateTextContent(editorState: EditorState): string;
export declare function markNodesWithTypesAsDirty(editor: LexicalEditor, types: string[]): void;
export declare function $getRoot(): RootNode;
export declare function internalGetRoot(editorState: EditorState): RootNode;
export declare function $setSelection(selection: null | BaseSelection): void;
export declare function $flushMutations(): void;
export declare function $getNodeFromDOM(dom: Node): null | LexicalNode;
/**
* Return true if `str` contains any valid surrogate pair.
*
* See also $updateCaretSelectionForUnicodeCharacter for
* a discussion on when and why this is useful.
*/
export declare function doesContainSurrogatePair(str: string): boolean;
export declare function getEditorsToPropagate(editor: LexicalEditor): LexicalEditor[];
export declare function createUID(): string;
export declare function getAnchorTextFromDOM(anchorNode: Node): null | string;
export declare function $updateSelectedTextFromDOM(isCompositionEnd: boolean, editor: LexicalEditor, data?: string): void;
export declare function $updateTextNodeFromDOMContent(textNode: TextNode, textContent: string, anchorOffset: null | number, focusOffset: null | number, compositionEnd: boolean): void;
export declare function $shouldInsertTextAfterOrBeforeTextNode(selection: RangeSelection, node: TextNode): boolean;
/**
* A KeyboardEvent or structurally similar object with a string `key` as well
* as `altKey`, `ctrlKey`, `metaKey`, and `shiftKey` boolean properties.
*/
export type KeyboardEventModifiers = Pick<KeyboardEvent, 'key' | 'code' | 'metaKey' | 'ctrlKey' | 'shiftKey' | 'altKey'>;
/**
* A record of keyboard modifiers that must be enabled.
* If the value is `'any'` then the modifier key's state is ignored.
* If the value is `true` then the modifier key must be pressed.
* If the value is `false` or the property is omitted then the modifier key must
* not be pressed.
*/
export type KeyboardEventModifierMask = {
[K in Exclude<keyof KeyboardEventModifiers, 'key'>]?: boolean | undefined | 'any';
};
/**
* Match a KeyboardEvent with its expected modifier state
*
* @param event A KeyboardEvent, or structurally similar object
* @param mask An object specifying the expected state of the modifiers
* @returns true if the event matches
*/
export declare function isModifierMatch(event: KeyboardEventModifiers, mask: KeyboardEventModifierMask): boolean;
/**
* Match a KeyboardEvent with its expected state
*
* @param event A KeyboardEvent, or structurally similar object
* @param expectedKey The string to compare with event.key (case insensitive)
* @param mask An object specifying the expected state of the modifiers
* @returns true if the event matches
*/
export declare function isExactShortcutMatch(event: KeyboardEventModifiers, expectedKey: string, mask: KeyboardEventModifierMask): boolean;
export declare function isTab(event: KeyboardEventModifiers): boolean;
export declare function isBold(event: KeyboardEventModifiers): boolean;
export declare function isItalic(event: KeyboardEventModifiers): boolean;
export declare function isUnderline(event: KeyboardEventModifiers): boolean;
export declare function isParagraph(event: KeyboardEventModifiers): boolean;
export declare function isLineBreak(event: KeyboardEventModifiers): boolean;
export declare function isOpenLineBreak(event: KeyboardEventModifiers): boolean;
export declare function isDeleteWordBackward(event: KeyboardEventModifiers): boolean;
export declare function isDeleteWordForward(event: KeyboardEventModifiers): boolean;
export declare function isDeleteLineBackward(event: KeyboardEventModifiers): boolean;
export declare function isDeleteLineForward(event: KeyboardEventModifiers): boolean;
export declare function isDeleteBackward(event: KeyboardEventModifiers): boolean;
export declare function isDeleteForward(event: KeyboardEventModifiers): boolean;
export declare function isUndo(event: KeyboardEventModifiers): boolean;
export declare function isRedo(event: KeyboardEventModifiers): boolean;
export declare function isCopy(event: KeyboardEventModifiers): boolean;
export declare function isCut(event: KeyboardEventModifiers): boolean;
export declare function isMoveBackward(event: KeyboardEventModifiers): boolean;
export declare function isMoveToStart(event: KeyboardEventModifiers): boolean;
export declare function isMoveForward(event: KeyboardEventModifiers): boolean;
export declare function isMoveToEnd(event: KeyboardEventModifiers): boolean;
export declare function isMoveUp(event: KeyboardEventModifiers): boolean;
export declare function isMoveDown(event: KeyboardEventModifiers): boolean;
export declare function isModifier(event: KeyboardEventModifiers): boolean;
export declare function isSpace(event: KeyboardEventModifiers): boolean;
export declare function controlOrMeta(metaKey: boolean, ctrlKey: boolean): boolean;
export declare function isBackspace(event: KeyboardEventModifiers): boolean;
export declare function isEscape(event: KeyboardEventModifiers): boolean;
export declare function isDelete(event: KeyboardEventModifiers): boolean;
export declare function isSelectAll(event: KeyboardEventModifiers): boolean;
export declare function $selectAll(selection?: RangeSelection | null): RangeSelection;
export declare function getCachedClassNameArray(classNamesTheme: EditorThemeClasses, classNameThemeType: string): string[];
export declare function setMutatedNode(mutatedNodes: MutatedNodes, registeredNodes: RegisteredNodes, mutationListeners: MutationListeners, node: LexicalNode, mutation: NodeMutation): void;
/**
* Returns all nodes of the given type in the active editor state.
*
* Consider {@link LexicalEditor.registerMutationListener} with
* `skipInitialization: false` instead if you need to track these nodes over
* time rather than read them once.
*/
export declare function $nodesOfType<T extends LexicalNode>(klass: Klass<T>): T[];
export declare function $getAdjacentNode(focus: PointType, isBackward: boolean): null | LexicalNode;
export declare function isFirefoxClipboardEvents(editor: LexicalEditor): boolean;
export declare function dispatchCommand<TCommand extends LexicalCommand<unknown>>(editor: LexicalEditor, command: TCommand, payload: CommandPayloadType<TCommand>): boolean;
export declare function getElementByKeyOrThrow(editor: LexicalEditor, key: NodeKey): HTMLElement;
export declare function getParentElement(node: Node): HTMLElement | null;
export declare function getDOMOwnerDocument(target: EventTarget | null): Document | null;
export declare function scrollIntoViewIfNeeded(editor: LexicalEditor, selectionRect: DOMRect, rootElement: HTMLElement): void;
export declare function $hasUpdateTag(tag: UpdateTag): boolean;
export declare function $addUpdateTag(tag: UpdateTag): void;
/**
* Add a function to run after the current update. This will run after any
* `onUpdate` function already supplied to `editor.update()`, as well as any
* functions added with previous calls to `$onUpdate`.
*
* @param updateFn The function to run after the current update.
*/
export declare function $onUpdate(updateFn: () => void): void;
export declare function $maybeMoveChildrenSelectionToParent(parentNode: LexicalNode): BaseSelection | null;
export declare function $hasAncestor(child: LexicalNode, targetNode: LexicalNode): boolean;
export declare function getDefaultView(domElem: EventTarget | null): Window | null;
export declare function getWindow(editor: LexicalEditor): Window;
declare const InlineNodeBrand: unique symbol;
export declare function $isInlineElementOrDecoratorNode<T>(node: LexicalNode): node is (ElementNode | DecoratorNode<T>) & {
isInline(): true;
[InlineNodeBrand]: never;
};
export declare function $getNearestRootOrShadowRoot(node: LexicalNode): RootNode | ElementNode;
declare const ShadowRootNodeBrand: unique symbol;
export interface ShadowRootNode extends ElementNode {
[ShadowRootNodeBrand]: never;
isShadowRoot(): true;
}
export declare function $isShadowRootNode(node: null | LexicalNode): node is ShadowRootNode;
export declare function $isRootOrShadowRoot(node: null | LexicalNode): node is RootNode | ShadowRootNode;
/**
* Returns a shallow clone of node with a new key. All properties of the node
* will be copied to the new node (by `clone` and then `afterCloneFrom`),
* except those related to parent/sibling/child
* relationships in the `EditorState`. This means that the copy must be
* separately added to the document, and it will not have any children.
*
* @param node - The node to be copied.
* @param skipReset - If true (default false) skip the call to resetOnCopyNodeFrom
* @returns The copy of the node.
*/
export declare function $copyNode<T extends LexicalNode>(node: T, skipReset?: boolean): T;
export declare function $applyNodeReplacement<N extends LexicalNode>(node: N): N;
export declare function errorOnInsertTextNodeOnRoot(node: LexicalNode, insertNode: LexicalNode): void;
/**
* Returns the node with the given key from the active EditorState,
* or throws if it does not exist.
*/
export declare function $getNodeByKeyOrThrow(key: NodeKey): LexicalNode;
/**
* @deprecated The type parameter is an unchecked and unsafe cast,
* equivalent to `$getNodeByKeyOrThrow(key) as N`, and will be removed
* in a future release. Call this function without a type argument and
* narrow the result with a type guard instead.
*/
export declare function $getNodeByKeyOrThrow<N extends LexicalNode>(key: NodeKey): N;
/**
* Returns true if the given node needs a block cursor given an adjacent selection,
* the node must be non-inline and one of:
* - DecoratorNode
* - ShadowRootNode with a parent that is not also a ShadowRootNode
* - An ElementNode that can't be empty
*/
export declare function $needsBlockCursorBeside(node: null | LexicalNode): boolean;
export declare function removeDOMBlockCursorElement(blockCursorElement: HTMLElement, editor: LexicalEditor, rootElement: HTMLElement): void;
export declare function $updateDOMBlockCursorElement(editor: LexicalEditor, rootElement: HTMLElement, nextSelection: null | BaseSelection): void;
/**
* Returns the selection for the given window, or the global window if null.
* Will return null if {@link CAN_USE_DOM} is false.
*
* @param targetWindow The window to get the selection from
* @returns a Selection or null
*/
export declare function getDOMSelection(targetWindow: null | Window): null | Selection;
/**
* Returns the selection for the defaultView of the ownerDocument of given EventTarget.
*
* @param eventTarget The node to get the selection from
* @returns a Selection or null
*/
export declare function getDOMSelectionFromTarget(eventTarget: null | EventTarget): null | Selection;
/**
* @param node A value that may be a DOM ShadowRoot.
* @returns True if node is a DOM ShadowRoot (an open or closed shadow tree
* root), false otherwise. A ShadowRoot is a DocumentFragment with a host.
*
* @experimental Shape may change as shadow DOM support stabilizes.
*/
export declare function isDOMShadowRoot(node: unknown): node is ShadowRoot;
export declare function getDOMShadowRoots(node: Node): ShadowRoot[];
/**
* Walks `root` and every open shadow root nested inside it, yielding each
* element that matches `selector`. `querySelectorAll` does not pierce
* shadow boundaries on its own; this descent does.
*
* @internal
*/
export declare function findAllLexicalElementsDeep(initialRoot: Document | ShadowRoot): Generator<Element>;
/**
* Resolves the document that hosts an editor's root element, falling
* back to the global `document` when the editor isn't mounted. Use this
* over `editor.getRootElement()?.ownerDocument ?? document` so iframe /
* shadow-mounted editors land in the right realm.
*
* @internal
*/
export declare function getRootOwnerDocument(rootElement: HTMLElement | null): Document;
/**
* Returns the {@link Document} that owns the active editor's root element.
* Falls back to `globalThis.document` when there is no active editor (e.g.
* a node method such as `createDOM` / `exportDOM` is invoked headlessly,
* outside of `editor.update()` / `editor.read()`), or when the active
* editor has no root element (e.g. headless mode with
* {@link @lexical/headless!withDOM | withDOM}).
*
* Use this inside `createDOM`, `updateDOM`, and `exportDOM` instead of the
* bare `document` global so the node works correctly when the editor lives
* inside a Shadow DOM or a cross-origin `<iframe>`.
*
* Unlike most `$`-prefixed helpers, this does NOT require an ambient active
* editor: it must remain callable from `createDOM` / `exportDOM`, which are
* public methods that consumers may legitimately call while serializing
* nodes headlessly. Throwing here would silently break every node whose DOM
* methods were migrated off the bare `document` global.
*/
export declare function $getDocument(): Document;
/**
* A subset of `Selection` covering the four boundary-point fields Lexical
* reads plus `direction`. Designed so a `Selection` instance can be returned
* where a `DOMSelectionBoundaryPoints` is expected (see {@link getDOMSelectionPoints}).
*
* `direction` is the standard
* {@link https://developer.mozilla.org/docs/Web/API/Selection/direction | Selection.direction}
* pass-through: `'forward'` / `'backward'` / `'none'` when the engine
* implements it, or `undefined` when a future engine ships
* `getComposedRanges` without `direction` (no current shipping
* configuration matches — every engine that ships the former also ships
* the latter). In the undefined case anchor/focus default to the composed
* StaticRange's tree order; callers needing strict backward fidelity
* inside a shadow root should check `direction !== undefined`.
*
* @experimental Shape may change as shadow DOM support stabilizes.
*/
export interface DOMSelectionBoundaryPoints {
anchorNode: Node | null;
anchorOffset: number;
direction?: undefined | 'forward' | 'backward' | 'none';
focusNode: Node | null;
focusOffset: number;
}
/**
* Resolves a DOM Selection's range through any DOM ShadowRoots enclosing
* `rootElement`, using the standard
* {@link https://developer.mozilla.org/docs/Web/API/Selection/getComposedRanges | Selection.getComposedRanges}
* platform API.
*
* When a selection is inside a shadow tree the browser retargets
* `Selection.getRangeAt`/`anchorNode`/`focusNode` to the shadow host, which
* hides the real nodes Lexical needs to resolve. Passing the enclosing shadow
* roots to `getComposedRanges` returns the un-retargeted boundary points as a
* {@link https://developer.mozilla.org/docs/Web/API/StaticRange | StaticRange}
* (in tree order, i.e. start before end).
*
* @returns The composed StaticRange, or `null` when `rootElement` is in the
* light DOM, the platform does not implement `getComposedRanges`, or there
* is no selection.
*
* @experimental Shape may change as shadow DOM support stabilizes.
*/
export declare function getComposedStaticRange(domSelection: Selection, rootElement: HTMLElement | null): StaticRange | null;
/**
* Returns a live DOM Range for the Selection, resolved through any DOM
* ShadowRoots enclosing `rootElement`. Inside a shadow tree
* `Selection.getRangeAt(0)` is retargeted to the shadow host, so this builds a
* Range from the composed boundary points instead (see
* {@link getComposedStaticRange}); in the light DOM it returns
* `getRangeAt(0)` unchanged. Use this instead of `getRangeAt(0)` when the
* Range is needed for layout (e.g. `getBoundingClientRect`), which a
* StaticRange cannot provide.
*
* @returns A live Range, or null when the selection has no ranges.
*
* @experimental Shape may change as shadow DOM support stabilizes.
*/
export declare function getDOMSelectionRange(domSelection: Selection, rootElement: HTMLElement | null): Range | null;
/**
* Resolves a DOM Selection's anchor/focus boundary points through any DOM
* ShadowRoots enclosing `rootElement`. Inside a shadow tree the boundary
* points come from {@link getComposedStaticRange} mapped back onto
* anchor/focus with the standard
* {@link https://developer.mozilla.org/docs/Web/API/Selection/direction | Selection.direction};
* in the light DOM (or when `getComposedRanges` is unavailable) the Selection's
* own anchorNode/focusNode are already correct, so the Selection is returned
* as-is (it satisfies {@link DOMSelectionBoundaryPoints}).
*
* Use this instead of reading `Selection.anchorNode`/`focusNode` directly,
* which are retargeted to the shadow host inside a shadow tree.
*
* @remarks
* The two return paths have different read semantics:
* - light DOM: the return aliases `domSelection`, so subsequent reads
* reflect any post-call selection changes. The aliasing is intentional;
* each `Selection` property read forces a synchronous style/layout
* recalculation, so `$updateDOMSelection` defers these reads until they
* are actually needed.
* - shadow DOM: the return is a snapshot taken at call time, including
* `direction`. If a future engine ships `getComposedRanges` without
* `Selection.direction` (no current shipping configuration matches),
* the snapshot's `direction` is `undefined` and anchor/focus default
* to the StaticRange's tree order — a backward selection will appear
* forward.
*
* Read the four points immediately after the call, or compare identity
* via `points === domSelection` to detect when the return aliases
* `domSelection`, rather than caching the returned reference across
* selection mutations.
*
* @experimental Shape may change as shadow DOM support stabilizes.
*/
export declare function getDOMSelectionPoints(domSelection: Selection, rootElement: HTMLElement | null): DOMSelectionBoundaryPoints;
/**
* Resolves the live DOM Range (for layout reads like `getBoundingClientRect`)
* and the anchor/focus boundary points in one pass, sharing a single
* {@link getComposedStaticRange} read rather than computing it twice as a
* call to {@link getDOMSelectionRange} followed by {@link getDOMSelectionPoints}
* would. Use this at sites that need both shapes from the same selection.
*
* @returns The composed Range plus the boundary points; the Range is null
* when the selection has no ranges.
*
* @experimental Shape may change as shadow DOM support stabilizes.
*/
export declare function getDOMSelectionRangeAndPoints(domSelection: Selection, rootElement: HTMLElement | null): {
points: DOMSelectionBoundaryPoints;
range: Range | null;
};
/**
* Returns the focused element within the same Document or ShadowRoot as
* `node`, using the standard `DocumentOrShadowRoot.activeElement`.
*
* Unlike `document.activeElement` — which is retargeted to the outermost
* shadow host when focus is inside a shadow tree — this returns the focused
* element within `node`'s own tree (e.g. the editor's contentEditable when it
* lives inside a shadow root).
*
* @param node A node whose tree's active element is wanted.
* @returns The active element, or null.
*
* @experimental Shape may change as shadow DOM support stabilizes.
*/
export declare function getActiveElement(node: Node): Element | null;
/**
* Descends from `root.activeElement` through nested open ShadowRoots to the
* deepest focused element. `document.activeElement` only reports the outermost
* shadow host; this walks into the shadow trees via `ShadowRoot.activeElement`
* to find the element that actually has focus.
*
* @param root The Document or ShadowRoot to start from.
* @returns The deepest active element, or null.
*
* @experimental Shape may change as shadow DOM support stabilizes.
*/
export declare function getActiveElementDeep(root: Document | ShadowRoot): Element | null;
/**
* Returns the un-retargeted event target — the real element the user
* interacted with — for events observed by a listener above an enclosing
* DOM shadow root. `Event.target` is retargeted to the outermost shadow
* host in that case, hiding the actual element; `composedPath()[0]`
* returns the original target for `composed: true` events (most
* user-agent UI events: click, mousedown, pointerdown, focusin, etc.).
* Falls back to `event.target` when `composedPath` is unavailable or
* returns an empty array (e.g. the event has already finished
* dispatching).
*
* Pairs with the shadow-aware helpers above
* ({@link getDOMSelectionPoints}, {@link getActiveElement}) for the
* event side of the shadow boundary — useful when an
* `Element.contains(target)` check needs to test against an editor root
* inside a shadow tree.
*
* @param event The dispatched event.
* @returns The un-retargeted target, or null when the event has none.
*
* @experimental Shape may change as shadow DOM support stabilizes.
*/
export declare function getComposedEventTarget(event: Event): EventTarget | null;
export declare function $splitNode(node: ElementNode, offset: number): [ElementNode | null, ElementNode];
/**
* @param x - The element being tested
* @returns Returns true if x is an HTML anchor tag, false otherwise
*/
export declare function isHTMLAnchorElement(x: unknown): x is HTMLAnchorElement;
/**
* @param x - The element being tested
* @returns Returns true if x is an HTML `<tr>` element, false otherwise
*/
export declare function isHTMLTableRowElement(x: unknown): x is HTMLTableRowElement;
/**
* @param x - The element being tested
* @returns Returns true if x is an HTML `<td>` or `<th>` element, false
* otherwise
*/
export declare function isHTMLTableCellElement(x: unknown): x is HTMLTableCellElement;
/**
* @param x - The element being tested
* @returns Returns true if x is an HTML element, false otherwise.
*/
export declare function isHTMLElement(x: unknown): x is HTMLElement;
/**
* @param x - The element being tested
* @returns Returns true if x is a DOM Node, false otherwise.
*/
export declare function isDOMNode(x: unknown): x is Node;
/**
* @param x - The element being testing
* @returns Returns true if x is a document fragment, false otherwise.
*/
export declare function isDocumentFragment(x: unknown): x is DocumentFragment;
/**
*
* @param node - the Dom Node to check
* @returns if the Dom Node is an inline node
*/
export declare function isInlineDomNode(node: Node): node is (HTMLElement | Text) & {
[InlineDOMBrand]: never;
};
declare const BlockDOMBrand: unique symbol;
declare const InlineDOMBrand: unique symbol;
/**
*
* @param node - the Dom Node to check
* @returns if the Dom Node is a block node
*/
export declare function isBlockDomNode(node: Node): node is HTMLElement & {
[BlockDOMBrand]: never;
};
declare const BlockNodeBrand: unique symbol;
/**
* @internal
*
* This function is for internal use of the library.
* Please do not use it as it may change in the future.
*
* This function returns true for a DecoratorNode that is not inline OR
* an ElementNode that is:
* - not a root or shadow root
* - not inline
* - can't be empty
* - has no children or an inline first child
*/
export declare function INTERNAL_$isBlock(node: LexicalNode): node is (ElementNode | DecoratorNode<unknown>) & {
[BlockNodeBrand]: never;
};
/**
* Utility function for accessing current active editor instance.
* @returns Current active editor
*/
export declare function $getEditor(): LexicalEditor;
/**
* @experimental
*
* Read the editor's `$getDOMSlot` configuration (defaulting to the base
* implementation when no override is registered via {@link DOMRenderExtension}).
* Cross-package consumers (`@lexical/utils`, `@lexical/react`) use this to
* route selection / DOM lookups through extension-configured slots.
*/
export declare function $getEditorDOMRenderConfig(editor?: LexicalEditor): EditorDOMRenderConfig;
/**
* @experimental
*
* Resolve the DOM slot for a node through the configured `$getDOMSlot` hook,
* narrowing the return type via {@link DOMSlotForNode}: for an `ElementNode`
* the result is an {@link ElementDOMSlot} (with children-management methods),
* for non-Element nodes the base {@link DOMSlot} pointing at the keyed DOM.
*
* Invariants if an extension override returns a slot that doesn't match the
* expected narrow type for the node (extension contract violation).
*/
export declare function $getDOMSlot<N extends LexicalNode>(node: N, dom: HTMLElement, editor?: LexicalEditor): DOMSlotForNode<N>;
/**
* @internal
*
* Returns the scaffolding container element that `host`'s named slot renders
* into, or null if the slot is empty or not yet rendered. The container is the
* parent of the slotted node's DOM, resolved by key so it is found wherever it
* sits — the reconciler parks it as a hidden placeholder in the host DOM, and
* an explicit mount ({@link mountSlotContainer}) may relocate it; this lookup
* still resolves it after that relocation. Editor-time analog of the
* reconciler's internal `$slotContainerForKey`, which resolves the same
* container from the reconcile-time DOM map instead of
* `editor.getElementByKey`.
*/
export declare function $getSlotContainer(host: LexicalNode, name: string, editor?: LexicalEditor): HTMLElement | null;
/**
* @experimental
*
* Attach a host's named-slot container to `target` and make it visible.
* The reconciler renders every slot subtree synchronously into a hidden
* (`display: 'none'`) placeholder container parked slots-first in the host
* DOM; nothing is visible until the host explicitly attaches the container
* somewhere — mirroring how `getDOMSlot` gives an element control over where
* its linked-list children render. This helper moves the container into
* `target` (a no-op when it is already there, so mounting in place just
* reveals it) and clears the inline `display` so the container renders as a
* normal block that stylesheets may restyle. It deliberately does NOT use
* `display: 'contents'`: Chromium cannot reliably edit inside a boxless
* contenteditable subtree (caret hit-testing resolves clicks to a
* neighboring box and native text insertion is dropped).
*
* Idempotent and framework-independent: lexical-react's `useLexicalSlotRef`
* wraps it, and a node class or extension can call it directly (e.g. from a
* mutation listener) to control slot placement without React.
*
* @returns the container, or null when the slot (or its DOM) does not exist
* yet — e.g. before the host's first reconciliation.
*/
export declare function mountSlotContainer(editor: LexicalEditor, nodeKey: NodeKey, slotName: string, target: HTMLElement): HTMLElement | null;
/**
* @experimental
*
* Reverse of {@link mountSlotContainer}: hide `container` again and park it
* back in the host's DOM as the leading hidden placeholder, where the
* reconciler manages it. Call when the mount target goes away while the host
* remains (e.g. chrome unmount) so the slot subtree stays in the document
* instead of leaving with the detached target.
*/
export declare function unmountSlotContainer(editor: LexicalEditor, nodeKey: NodeKey, container: HTMLElement): void;
/**
* @experimental
*
* Type guard narrowing a {@link DOMSlot} to an {@link ElementDOMSlot}, which
* exposes children-management methods like `insertChild` and the managed
* line-break helpers.
*/
export declare function $isElementDOMSlot(slot: DOMSlot<HTMLElement>): slot is ElementDOMSlot<HTMLElement>;
/**
* @experimental
*
* Resolve the actual text DOM (`Text`) for a `TextNode` through the
* configured `$getDOMSlot` hook. Unlike the plain {@link getDOMTextNode}
* which descends the first child chain from a raw element, this routes
* through the slot so an extension wrapping the text node's keyed DOM
* (e.g. one that injects a `contentEditable=false` sibling before the
* text) still points at the correct content element.
*/
export declare function $getDOMTextNode(node: TextNode, dom: HTMLElement, editor?: LexicalEditor): Text | null;
/** @internal */
export type TypeToNodeMap = Map<string, NodeMap>;
export declare function getCachedTypeToNodeMap(editorState: EditorState): TypeToNodeMap;
/**
* Returns a clone of a node using `node.constructor.clone()` followed by
* `clone.afterCloneFrom(node)`. The resulting clone must have the same key,
* parent/next/prev pointers, and other properties that are not set by
* `node.constructor.clone` (format, style, etc.). This is primarily used by
* {@link LexicalNode.getWritable} to create a writable version of an
* existing node. The clone is the same logical node as the original node,
* do not try and use this function to duplicate or copy an existing node.
*
* Does not mutate the EditorState.
* @param latestNode - The node to be cloned.
* @returns The clone of the node.
*/
export declare function $cloneWithProperties<T extends LexicalNode>(latestNode: T): T;
/**
* Returns a clone with {@link $cloneWithProperties} and then "detaches"
* it from the state by overriding its getLatest and getWritable to always
* return this. This node can not be added to an EditorState or become the
* parent, child, or sibling of another node. It is primarily only useful
* for making in-place temporary modifications to a TextNode when
* serializing a partial slice.
*
* Does not mutate the EditorState.
* @param latestNode - The node to be cloned.
* @returns The clone of the node.
*/
export declare function $cloneWithPropertiesEphemeral<T extends LexicalNode>(latestNode: T): T;
export declare function setNodeIndentFromDOM(elementDom: HTMLElement, elementNode: ElementNode): void;
/**
* Reads the `dir` attribute from a DOM element and applies it to the given
* ElementNode via {@link ElementNode.setDirection} when it is a valid direction
* value (`'ltr'` or `'rtl'`). Other values, including missing or empty `dir`,
* leave the node unchanged. Useful inside `importDOM` converters to preserve
* explicit text direction from imported HTML.
*
* @param node - The ElementNode to update.
* @param domNode - The source HTMLElement whose `dir` attribute is read.
* @returns The node, with its direction set when the source `dir` was valid.
*/
export declare function $setDirectionFromDOM<T extends ElementNode>(node: T, domNode: HTMLElement): T;
/**
* Reads the `style` and CSS `textAlign` property from a DOM element
* and set format to the given ElementNode via {@link ElementNode.setFormat}
* when it is a valid alignment value {@link ElementFormatType}
* Other values, including missing or empty, leave the node unchanged.
* Useful inside `importDOM` converters to preserve explicit alignment from imported HTML.
*
* @param node - The ElementNode to update.
* @param domNode - The source HTMLElement whose `style` property is read.
* @returns The node, with its align format set when the source `style.textAlign` was valid.
*/
export declare function $setFormatFromDOM<T extends ElementNode>(node: T, domNode: HTMLElement): T;
/**
* Options accepted by {@link setDOMUnmanaged}.
*
* @experimental
*/
export interface SetDOMUnmanagedOptions {
/**
* When true, the marked subtree owns its own window selection — analogous
* to a DecoratorNode subtree. Selection resolution that would otherwise
* mark the selection dirty for a caret position inside unmanaged DOM
* leaves it alone, so the embedded interaction (custom input, focusable
* widget, etc.) can keep its native caret.
*
* Pass `false` to clear a previously-set marker; omit the field to leave
* `__lexicalCapturedSelection` untouched.
*/
captureSelection?: boolean;
}
/**
* Mark this DOM element as unmanaged by lexical's mutation observer (like
* decorator nodes are). Extensions that inject non-lexical decoration
* elements into a node's DOM should mark them so the mutation observer
* doesn't evict them as "unknown DOM children" during cleanup.
*
* Pass `{captureSelection: true}` to additionally treat the subtree's
* window selection as decorator-like, so resolution does not force-sync
* the caret out of unmanaged DOM (see {@link isDOMCapturingSelection}).
*
* @experimental
*/
export declare function setDOMUnmanaged(elementDom: HTMLElement & LexicalPrivateDOM, options?: SetDOMUnmanagedOptions): void;
/**
* True if this DOM node was marked with {@link setDOMUnmanaged}.
*
* @experimental
*/
export declare function isDOMUnmanaged(elementDom: Node & LexicalPrivateDOM): boolean;
/**
* Mark a DOM element as a named-slot editable island: set its `contentEditable`
* to follow the editor's editable state. A slot rendered inside a non-editable
* host (a decorator, or a `contentEditable=false` element shell) does not track
* the editor on its own, so its container carries an explicit `contentEditable`;
* {@link $fullReconcile} re-applies this when {@link LexicalEditor.setEditable}
* toggles. Call it for any other editable island an app attaches itself (e.g. a
* `getDOMSlot` children element rendered inside a `contentEditable=false` shell).
*
* @experimental
*/
export declare function $markSlotEditable(element: HTMLElement & {
__lexicalEditor?: undefined | LexicalEditor;
}, editor?: LexicalEditor): void;
/**
* True if the DOM node sits inside a subtree marked with
* `{captureSelection: true}` via {@link setDOMUnmanaged}. Walks ancestors
* so any descendant of a marked subtree (e.g. an `<input>` inside a marked
* `<div>`) reports as captured too.
*
* The walk aborts at the first DOM node that corresponds to a Lexical
* node in `editor` — that boundary is the implicit owner of the subtree's
* selection, so a captureSelection marker above it (in non-Lexical
* scaffolding around the editor) does not leak in.
*
* DecoratorNode DOM is marked with `setDOMUnmanaged({captureSelection:
* true})` by the reconciler, so decorator subtrees also report as
* captured here.
*
* @experimental
*/
export declare function isDOMCapturingSelection(elementDom: Node & LexicalPrivateDOM, editor: LexicalEditor): boolean;
/**
* @internal
*/
export declare function hasOwnStaticMethod(klass: Klass<LexicalNode>, k: keyof Klass<LexicalNode>): boolean;
export interface OwnStaticNodeConfig {
klass: Klass<LexicalNode>;
ownNodeType: undefined | string;
ownNodeConfig: undefined | StaticNodeConfigValue<LexicalNode, string | symbol>;
}
/** @internal */
export declare function getStaticNodeConfig(klass: Klass<LexicalNode>): OwnStaticNodeConfig;
/**
* Collect all configuration for this class and its superclasses
*
* @internal
*/
export declare function iterStaticNodeConfigChain(klass: Klass<LexicalNode>): Iterable<OwnStaticNodeConfig>;
/**
* Build a map from each registered node type to the set of registered node
* types that are it or extend it (including the type itself). For every node
* class in `nodes`, its prototype chain is walked and the class's own type is
* added to the bucket of each registered ancestor type it inherits from.
*
* The result lets callers expand a base node type to all of its registered
* subclass types up front, so a subclass instance can be matched by type
* without a runtime `instanceof`.
*
* @experimental
*/
export declare function getRegisteredSubtypeMap(nodes: Iterable<Klass<LexicalNode>>): Map<string, Set<string>>;
/**
* Create an node from its class.
*
* Note that this will directly construct the final `withKlass` node type,
* and will ignore the deprecated `with` functions. This allows `$create` to
* skip any intermediate steps where the replaced node would be created and
* then immediately discarded (once per configured replacement of that node).
*
* This does not support any arguments to the constructor.
* Setters can be used to initialize your node, and they can
* be chained. You can of course write your own mutliple-argument functions
* to wrap that.
*
* @example
* ```ts
* function $createTokenText(text: string): TextNode {
* return $create(TextNode).setTextContent(text).setMode('token');
* }
* ```
*/
export declare function $create<T extends LexicalNode>(klass: Klass<T>): T;
/**
* Starts with a node and moves up the tree (toward the root node) to find a matching node based on
* the search parameters of the findFn. (Consider JavaScripts' .find() function where a testing function must be
* passed as an argument. eg. if( (node) => node.__type === 'div') ) return true; otherwise return false
* @param startingNode - The node where the search starts.
* @param findFn - A testing function that returns true if the current node satisfies the testing parameters.
* @returns `startingNode` or one of its ancestors that matches the `findFn` predicate and is not the `RootNode`, or `null` if no match was found.
*/
export declare const $findMatchingParent: {
<T extends LexicalNode>(startingNode: LexicalNode, findFn: (node: LexicalNode) => node is T): T | null;
(startingNode: LexicalNode, findFn: (node: LexicalNode) => boolean): LexicalNode | null;
};
export declare function $createChildrenArray(element: ElementNode, nodeMap: null | NodeMap): NodeKey[];
/**
* Look up the superclass of this class, prefer
* {@link iterStaticNodeConfigChain} when implementing loops.
*
* @internal
*/
export declare function getSuperclassOf(klass: Klass<LexicalNode>): null | Klass<LexicalNode>;
export {};