lexical
Version:
Lexical is an extensible text editor framework that provides excellent reliability, accessible and performance.
161 lines (160 loc) • 7.41 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 { LexicalPrivateDOM } from './LexicalNode';
import type { ElementNode } from './nodes/LexicalElementNode';
/**
* Base class for DOM slots — a pointer to the content-bearing element of a
* node's DOM, plus optional `before` / `after` boundaries marking where the
* lexical-managed content sits inside that element.
*
* For ElementNode children management see {@link ElementDOMSlot}. For
* non-Element nodes (TextNode, LineBreakNode, DecoratorNode) the slot still
* supports an internal `before` / `after` so subclasses can prepend or
* append non-lexical siblings around the content node and the reconciler /
* `setTextContent` route the actual content through the slot.
*
* @experimental
*/
export declare class DOMSlot<T extends HTMLElement = HTMLElement> {
/** The content-bearing element of the node's DOM. */
readonly element: T;
/** Upper boundary: the lexical-managed range ends before this node. */
readonly before: Node | null;
/** Lower boundary: the lexical-managed range starts after this node. */
readonly after: Node | null;
constructor(element: T, before?: Node | undefined | null, after?: Node | undefined | null);
/** Return a new slot with `before` updated. */
withBefore(before: Node | undefined | null): DOMSlot<T>;
/** Return a new slot with `after` updated. */
withAfter(after: Node | undefined | null): DOMSlot<T>;
/** Return a new slot with `element` updated. */
withElement<ElementType extends HTMLElement>(element: ElementType): DOMSlot<ElementType>;
/**
* Insert the given node before `this.before` (if defined) or append it to
* `this.element` otherwise. Subclasses may override to respect additional
* boundaries (e.g. `ElementDOMSlot` also keeps the managed line break at
* the end).
*/
insertChild(dom: Node): this;
/**
* Remove the given child from `this.element`. Throws if it was not a child.
*/
removeChild(dom: Node): this;
/**
* Replace `prevDom` with `dom`. Throws if `prevDom` is not a child.
*/
replaceChild(dom: Node, prevDom: Node): this;
/**
* Returns the first managed child (the first node in
* `this.element` that is not a non-lexical prelude / decoration), or
* `null` if there is none. Subclasses may override to also skip
* reconciler-managed scaffolding such as the managed line break.
*/
getFirstChild(): ChildNode | null;
/**
* @internal
*
* The leading-boundary counterpart to {@link getInsertionAnchor}: the node
* the lexical-managed range starts immediately after (its `nextSibling` is
* the first managed child), or `null` when managed children begin at
* `this.element.firstChild`. The base slot uses `this.after`; subclasses
* extend it to skip leading non-lexical scaffolding (e.g. the block cursor).
*/
getFirstChildAnchor(): Node | null;
/**
* Map a DOM selection point landing at or inside `leafDOM` (the node's
* keyed DOM) to whether the caret is positioned BEFORE or AFTER the
* node in document order. The default implementation derives the
* boundary from `this.element`'s index inside `leafDOM`:
*
* - When `this.element === leafDOM` (no wrap exposed an inner content
* element via `withElement`): only a DOM caret directly on
* `leafDOM` at offset 0 counts as "before". Matches the historical
* decorator rule.
* - When `this.element !== leafDOM` (wrap pattern that exposed the
* inner content element via `withElement`, e.g. a `<br>` inside a
* decoration `<span>`): caret positions at or before the content
* element are "before", later positions are "after". Handles
* nested wraps by walking each side up to its top-level child of
* `leafDOM`.
*
* Symmetric with {@link ElementDOMSlot.resolveChildIndex}, which
* performs the analogous mapping for ElementNode children. Together
* they let the slot abstraction own all DOM-offset to lexical-offset
* translation.
*
* @internal
*/
resolveLeafPosition(leafDOM: HTMLElement, initialDOM: Node, initialOffset: number): 'before' | 'after';
/**
* @internal
*
* The node managed children are inserted before, or `null` to append.
* Subclasses widen this to reserve trailing scaffolding (e.g.
* {@link ElementDOMSlot} keeps the managed line break last).
*/
getInsertionAnchor(): Node | null;
}
/**
* A utility class for managing the DOM children of an ElementNode.
*
* Extends {@link DOMSlot} with ElementNode-specific scaffolding — the
* reconciler-managed line break that keeps empty elements selectable, and
* the offset / index resolution helpers needed when mapping DOM selections
* onto lexical positions. The base `before` / `after` boundaries and the
* children mutation helpers (`insertChild`, `removeChild`, …) live on
* {@link DOMSlot}.
*/
export declare class ElementDOMSlot<T extends HTMLElement = HTMLElement> extends DOMSlot<T> {
/** Return a new slot with `before` updated, preserving subclass type. */
withBefore(before: Node | undefined | null): ElementDOMSlot<T>;
/** Return a new slot with `after` updated, preserving subclass type. */
withAfter(after: Node | undefined | null): ElementDOMSlot<T>;
/** Return a new slot with `element` updated, preserving subclass type. */
withElement<ElementType extends HTMLElement>(element: ElementType): ElementDOMSlot<ElementType>;
/**
* @internal
*/
getInsertionAnchor(): Node | null;
/**
* @internal
*
* Extends the leading boundary to skip the editor's transient block cursor
* when it sits at the head of the managed range (a collapsed element
* selection at offset 0), mirroring how {@link getInsertionAnchor} extends
* the trailing boundary past the managed line break. Only ElementNodes host
* a block cursor among their children, so the base slot stays editor-free.
*/
getFirstChildAnchor(): Node | null;
/**
* @internal
*/
getManagedLineBreak(): Exclude<LexicalPrivateDOM['__lexicalLineBreak'], undefined>;
/** @internal */
setManagedLineBreak(lineBreakType: null | 'empty' | 'line-break' | 'decorator'): void;
/** @internal */
removeManagedLineBreak(): void;
/** @internal */
insertManagedLineBreak(webkitHack: boolean): void;
/**
* @internal
*
* The DOM child index at which the first managed child appears — i.e. the
* count of leading non-lexical nodes (the `this.after` region, plus the
* block cursor when it sits at the head). Walks forward from the start,
* stopping at the first managed child, or at the trailing boundary
* (`this.before` / the managed line break via {@link getInsertionAnchor})
* when there are no managed children.
*/
getFirstChildOffset(): number;
/**
* @internal
*/
resolveChildIndex(element: ElementNode, elementDOM: HTMLElement, initialDOM: Node, initialOffset: number): [node: ElementNode, idx: number];
}
export declare function indexPath(root: HTMLElement, child: Node): number[];