/** * 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 { PROTOTYPE_CONFIG_METHOD } from './LexicalConstants.js'; import type { EditorConfig, Klass, KlassConstructor, LexicalEditor } from './LexicalEditor.js'; import type { GeneratedJSONFactory } from './LexicalGeneratedJSON.js'; import { type DecoratorNode, type ElementNode, type NODE_STATE_KEY } from './index.js'; import { DOMSlot } from './LexicalDOMSlot.js'; import { type NodeState, type NodeStateJSON, type Prettify, type RequiredNodeStateConfig } from './LexicalNodeState.js'; import { CACHED_TEXT_SIZE_KEY } from './LexicalReconciler.js'; import { type NodeSerializationSchema } from './LexicalSchema.js'; import { type BaseSelection, type RangeSelection } from './LexicalSelection.js'; export type NodeMap = Map; /** * The base type for all serialized nodes */ export type SerializedLexicalNode = { /** The type string used by the Node class */ type: string; /** * @deprecated A numeric schema version. Nothing reads it — parsing ignores * it entirely — and nothing should. * * `exportJSON()` still writes it as `1` so the output stays readable by * older versions, which is the only reason it remains, and it stays required * here so that the legacy form promises what it actually writes. The two * places it is genuinely absent relax it themselves: a compact export omits * it along with everything else parsing restores on its own (see * {@link SerializedPartial}), and the parse shapes drop it outright (see * {@link LexicalUpdateJSON}). */ version: number; /** * Any state persisted with the NodeState API that is not * configured for flat storage */ [NODE_STATE_KEY]?: Record; /** * Named slot subtrees keyed by slot name. Present on host nodes (an * ElementNode or DecoratorNode that registered slots via `$setSlot`). * The `$` prefix keeps the framework-owned key out of the namespace a * third-party subclass may already use for its own serialized `slots` * property (mirroring the reserved NodeState `'$'` key). * @experimental named-slots */ $slots?: Record; }; /** * EXPERIMENTAL * The configuration of a node returned by LexicalNode.$config() * * @example * ```ts * class CustomText extends TextNode { * $config() { * return this.config('custom-text', {extends: TextNode}}; * } * } * ``` */ export interface StaticNodeConfigValue { /** * The exact type of T.getType(), e.g. 'text' - the method itself must * have a more generic 'string' type to be compatible wtih subclassing. * * For a concrete node this is its string `type`. An abstract base class is * keyed in {@link BaseStaticNodeConfig} by a symbol (it has no concrete node * `type`), so `Type` is widened to `string | symbol`; the `type` field is * never populated for a symbol-keyed config. */ readonly type?: Type; /** * An alternative to the internal static transform() method * that provides better type inference. If implemented this * transform will be registered for this class and any subclass. */ readonly $transform?: (node: T) => void; /** * An alternative to the static importJSON() method * that provides better type inference. * * A method signature rather than a function-typed property, so that the * parameter is checked bivariantly: the JSON handed in may be the compact * form, so a callback may take `SerializedPartial`, * while one written before that form existed takes `SerializedLexicalNode` * (`version` required) and has to stay assignable. A property's parameter * is compared contravariantly and would refuse it. */ $importJSON?(serializedNode: SerializedPartial): T; /** * An alternative to the static importDOM() method */ readonly importDOM?: DOMConversionMap; /** * EXPERIMENTAL * * An array of RequiredNodeStateConfig to initialize your node with * its state requirements. This may be used to configure serialization of * that state. * * This function will be called (at most) once per editor initialization, * directly on your node's prototype. It must not depend on any state * initialized in the constructor. * * @example * ```ts * const flatState = createState("flat", {parse: parseNumber}); * const nestedState = createState("nested", {parse: parseNumber}); * class MyNode extends TextNode { * $config() { * return this.config( * 'my-node', * { * extends: TextNode, * stateConfigs: [ * { stateConfig: flatState, flat: true}, * nestedState, * ] * }, * ); * } * } * ``` */ readonly stateConfigs?: readonly RequiredNodeStateConfig[]; /** * @experimental named-slots * * Canonical order for this host's named slots. Declared names render, * fold, serialize, and traverse in this order; occupied names that are * not declared follow in code-unit order. Order is derived from this * declaration at every {@link $setSlot} (never stored), so documents * re-canonicalize on load and concurrent collaborative slot additions * converge to the same order on every client. The declaration is not a * schema: undeclared names are still accepted and retained, so adding, * reordering, or dropping entries over time is non-destructive. * * Declaring slots also opts the host into eager slots-map creation in * \@lexical/yjs, which makes each name's first set merge per-entry under * concurrency instead of racing on attribute creation. */ readonly slots?: readonly string[]; /** * The exact superclass of the node. Always name it. * * The runtime fills it in from the prototype chain when it is left out, but * the type system cannot: `extends` is what the composed serialization types * follow from one config to the next. A node that omits it still contributes * its own declarations — `LexicalSchemaInput` reads the config in hand — but * the walk stops there, so every property the node inherits is missing from * the type while the runtime keeps applying it. Where the superclass itself * declares a `$config()` — which `TextNode`, `ElementNode` and * `LineBreakNode` all do — omitting it is a compile error on the override * rather than a silent loss. * * It must be the *exact* superclass. Nothing checks that: naming a class * further up the chain silently skips everything in between, which drops * those classes' schema fields, `$transform`, `slots` and `stateConfigs` * from every walk. */ readonly extends?: Klass; /** * EXPERIMENTAL * * A {@link SerializationSchema} describing this node's serialized JSON (the * node-specific properties it adds over its parent's, not including * `type`/`version`/`children` or node state). When provided it is the single * source of truth for parsing those properties — a node's `updateFromJSON` * can apply it — and, because the schema is introspectable, tooling such as * `@lexical/fast-check` can use it to generate example serializations. * * It is named `json` rather than `schema` to avoid ambiguity with other kinds * of node schema (e.g. a schema of allowed children). */ readonly json?: NodeSerializationSchema; /** * @internal * * Specialized `exportJSON`/`updateFromJSON` implementations compiled from * this class's serialization schema by `scripts/generate-node-json.mjs`, * which the schema-driven walks use in place of walking. Set by the core * node classes * that are code-generated; there is no reason to write it by hand. * * Carried here rather than looked up by node type, because a type does not * identify a class: a subclass that declares no `$config` of its own inherits * its ancestor's, type included, and may still override an accessor the * generated code compiled away. Passing the code through the config makes the * association the same one the schema itself has — the class whose `$config` * named it — so the two cannot come apart. * * A factory rather than the functions themselves: registration calls it * with the class's composed schema, and the generated code reads the lookup * tables it needs off that schema, so a generated module carries no copy of * a table and a subclass that inherits the code runs it over its own. */ readonly generated?: GeneratedJSONFactory; } /** * This is the type of LexicalNode.$config() that can be * overridden by subclasses. * * Concrete nodes are keyed by their string `type`. An abstract base class * (such as ElementNode or DecoratorNode) has no concrete node `type`, so when * it needs to declare configuration that is shared with its concrete * subclasses (for example required {@link RequiredNodeStateConfig} state or a * `$transform`) it is keyed instead by a well-known symbol, by convention * `Symbol.for()` (e.g. `Symbol.for('ElementNode')`). The * descriptive, globally-registered symbol keeps the config easy to find in a * debugger and can never collide with a real node `type`. */ export type BaseStaticNodeConfig = { readonly [K in string | symbol]?: undefined | StaticNodeConfigValue; }; /** * Used to extract the node and type from a StaticNodeConfigRecord */ export type StaticNodeConfig = BaseStaticNodeConfig & { readonly [K in Type]?: StaticNodeConfigValue; }; /** * Any StaticNodeConfigValue (for generics and collections) */ export type AnyStaticNodeConfigValue = StaticNodeConfigValue; /** * @internal * * Type-only key under which a {@link StaticNodeConfigRecord} carries the node's * own (most-derived) `type` as a nullary accessor `() => Type`. * * TypeScript's type system is structural, so a node subclass that adds no * type-distinguishable members over its base (e.g. {@link TabNode} over * {@link TextNode}, which only overrides methods with identical signatures) is * structurally identical to that base despite being a distinct class — the * negative branch of an `instanceof`-style guard like `$isTabNode()` would * otherwise collapse to `never` because TypeScript concludes the base is also * assignable to the subclass. * * Encoding the type as a *function* (rather than a bare `Type`) lets the * accessor accumulate down the `extends` chain by intersection: a subclass' * record is `ParentRecord & {[STATIC_NODE_TYPE]: () => OwnType}`, so the key * holds `(() => ParentType) & (() => OwnType)`. TypeScript resolves an * intersection of call signatures as an overload set, which has two effects: * * - {@link GetStaticNodeType} reads `ReturnType<...>`, which selects the *last* * overload — the most-derived own `type` — rather than a union of the chain. * - A node stays assignable to each of its ancestors (the intersection * satisfies every `() => AncestorType`) while an ancestor is not assignable * to it (it lacks the `() => OwnType` signature), so guards narrow correctly * at every level of the hierarchy — not just one. * * This keeps the node classes themselves nominally distinguishable along their * real hierarchy; it is not a cross-cutting trait marker. The accessor is never * read at runtime — `config()` does not set this key — so it is `declare`-only * and carries no runtime cost. It is `@internal` (surfaced via the * {@link StaticNodeTypeAccessor} interface so that inferred `$config()` return * types remain nameable in generated declaration files) and a node never * references it. Distinction is automatic for any node that declares its * `extends`; a subclass adds nothing by hand. */ export declare const STATIC_NODE_TYPE: unique symbol; /** * The brand every {@link LexicalNode} carries in its type and nothing else * does, for a check that has to say "a node" without relating a class to * `LexicalNode` member by member; see `SetterReturn` in LexicalSchema.ts. * Declared only — `instanceof LexicalNode` is what a runtime check uses. * @internal */ export declare const LEXICAL_NODE_BRAND: unique symbol; /** * @internal * * Carries a node's own `type` under the {@link STATIC_NODE_TYPE} accessor. This * is an `interface` (rather than an inline object type) on purpose: it is never * inlined in generated declaration files, so a subclass' `$config()` return type * references it by name (`StaticNodeTypeAccessor<'tab'>`) and the symbol key * stays encapsulated here rather than leaking — unnamed — into every node's * `.d.ts`. */ export interface StaticNodeTypeAccessor { readonly [STATIC_NODE_TYPE]: () => Type; } /** * @internal * * Type-only key under which a {@link StaticNodeConfigRecord} carries the node's * own configuration (the value passed to {@link LexicalNode.config}) as a * nullary accessor `() => Config`. * * This mirrors {@link STATIC_NODE_TYPE}: encoding the config as a *function* * lets it accumulate down the `extends` chain by call-signature intersection so * that `ReturnType<...>` resolves the most-derived own config. It exists so that * an abstract base class keyed by a symbol — which has no string `type` to index * the record by — can still expose its own config to the state-config * collectors, and so that such a base's symbol-keyed `$config()` return remains * a valid override of an accessor-bearing superclass `$config()` (it inherits * the superclass record, hence that record's {@link STATIC_NODE_TYPE} accessor). * Never read at runtime — `config()` does not set this key — so it is * `declare`-only and carries no runtime cost. */ export declare const STATIC_NODE_CONFIG: unique symbol; /** * @internal * * Carries a node's own config under the {@link STATIC_NODE_CONFIG} accessor. * Like {@link StaticNodeTypeAccessor} this is a named `interface` so the symbol * key stays encapsulated and inferred `$config()` return types remain nameable * in generated declaration files. */ export interface StaticNodeConfigAccessor { readonly [STATIC_NODE_CONFIG]: () => Config; } /** * @internal * * This is the more specific type than BaseStaticNodeConfig that a subclass * should return from $config(). * * A node that declares its `extends` accumulates the configuration of its * superclass and records its own `type` under {@link STATIC_NODE_TYPE} and its * own config under {@link STATIC_NODE_CONFIG}, so that it is nominally distinct * from — yet still assignable to — that superclass, and so the state-config * collectors can read its own config without indexing by `type`. */ export type StaticNodeConfigRecord = Config extends { extends: abstract new (...args: never) => infer Inst extends LexicalNode; } ? ReturnType & { readonly [K in Type]?: Config; } & StaticNodeTypeAccessor & StaticNodeConfigAccessor : BaseStaticNodeConfig & { readonly [K in Type]?: Config; }; /** * @internal * * The record returned by {@link LexicalNode.config} for an abstract base class * keyed by a symbol. Unlike {@link StaticNodeConfigRecord} it records no string * `type` and adds no {@link STATIC_NODE_TYPE} accessor (an abstract base has no * concrete node type), but it does inherit its superclass record and expose its * own config under {@link STATIC_NODE_CONFIG}. Inheriting the superclass record * is what keeps the override valid when the superclass is a concrete node whose * `$config()` return already carries a {@link STATIC_NODE_TYPE} accessor. */ export type AbstractStaticNodeConfigRecord = Config extends { extends: abstract new (...args: never) => infer Inst extends LexicalNode; } ? ReturnType & StaticNodeConfigAccessor : BaseStaticNodeConfig & StaticNodeConfigAccessor; /** * Extract the type from a node based on its $config * * @example * ```ts * type TextNodeType = GetStaticNodeType; * // ? 'text' * ``` */ export type GetStaticNodeType = ReturnType extends { readonly [STATIC_NODE_TYPE]: infer Accessor extends () => string; } ? ReturnType : ReturnType extends StaticNodeConfig ? Type : string; /** * @internal * * A node's own config (the value it passed to {@link LexicalNode.config}), read * from the {@link STATIC_NODE_CONFIG} accessor, or `never` for a node whose * `$config()` does not set that accessor (a legacy node, or one whose record * uses the {@link BaseStaticNodeConfig} fallback). Unlike indexing the record by * a resolved string `type`, this also resolves the own config of an abstract * base class keyed by a symbol. */ export type GetStaticNodeOwnConfig = ReturnType extends { readonly [STATIC_NODE_CONFIG]: infer Accessor extends () => AnyStaticNodeConfigValue; } ? ReturnType : never; /** * What `T.exportJSON(compact)` returns for the *legacy* form, which is the one * that writes every property. The compact form omits properties and so returns * the {@link SerializedPartial} of this. * * Matched against the whole overload set rather than read with `ReturnType`, * which resolves an overloaded type to its *last* signature — the compact one, * where nothing is promised. Both signatures have to appear in the pattern: * matching only the first infers `never`, because an overloaded source is * assignable to a single-signature target through its last overload. * * A node that declares one `exportJSON(compact?: boolean)` signature — a * narrowing of both overloads at once, which therefore cannot distinguish * them — satisfies both and matches too. */ type LexicalFullExportJSON = T['exportJSON'] extends { (compact?: false): infer R; (compact: boolean): unknown; } ? R : ReturnType; export type LexicalExportJSON = Prettify, 'type' | 'version'> & { type: GetStaticNodeType; /** * Written by `exportJSON()` so the output remains readable by older * versions. Required, like {@link SerializedLexicalNode.version}: this is * the legacy form, which writes it unconditionally. `exportJSON(true)` * returns the {@link SerializedPartial} of this, where it is optional * along with everything else parsing restores on its own. */ version: number; } & NodeStateJSON>; /** * Omit the children, type, and version properties from the given SerializedLexicalNode definition. * * This is the shape a hand-written `updateFromJSON` override reads: each * property keeps its declared type. The parser behind a serialization schema * faces wider input than that — see {@link LexicalParseJSON}. */ export type LexicalUpdateJSON = Omit; /** * The serialized form of a node as accepted by the parsing methods * ({@link LexicalNode.importJSON} and {@link LexicalNode.updateFromJSON}). * * Only `type` identifies the node here: every node-specific property is made * optional via `Partial`. Parsing is generally untrusted and must tolerate * missing or out-of-domain values, so implementations are expected to * substitute sensible defaults — see the {@link Parse} helpers such as * {@link stringValue}, {@link numberValue}, and {@link enumValue}. This also * enables a "compact" serialization variant in which any property left at its * default is omitted. * * The deprecated `version` is relaxed here rather than on * {@link SerializedLexicalNode}: a compact export omits it, but the legacy * form always writes it, and making it optional at the base would take that * promise away from the full output type as well. */ export type SerializedPartial = Omit, '$slots' | 'children' | 'version'> & { /** * Slot values are parsed by the same rules, so they relax the same way — * and, like the parse entry point, name the declared form too: naming only * the indexed `SerializedPartialNode` meant a slot could not hold a value * whose type is a declared interface, since TypeScript gives an interface no * implicit index signature. */ $slots?: Record; /** Omitted by a compact export, like every other restorable property. */ version?: number; } & (T extends { children: readonly SerializedLexicalNode[]; } ? { /** * An element's children are nodes of the same document, written in the * same form, so they relax too: `Partial` alone would make the array * optional while still promising that everything in it is fully * serialized, which is untrue of every compact element but the leaves. */ children?: SerializedPartialNode[]; } : unknown); /** * A node of a compact document read without knowing its type, which is every * child: a node cannot declare what kind of children it accepts, so any node * may appear under any element and there is no type to name their properties * from. The outer node of a {@link SerializedPartial} is refinable — you know * what you asked for — and its children never are. * * So the framework properties are named and a node's own arrive as `unknown`, * which a reader narrows by `type` as it would any untrusted JSON. `children` * and `$slots` recurse, because a compact export applies to a subtree exactly * as it does to its root: naming `SerializedPartial` for * them instead would leave a nested element unable to carry the children it has. * * The index signature is what lets a document be *written*. Without it every * node-specific property on a child is an excess-property error, so * `editor.parseEditorState({root: {children: [{children: [{text: 'hi', type: * 'text'}], …}], …}})` — a hand-authored initial state, the most ordinary * literal a caller writes — does not compile, and neither does a fixture, a * migration script, or `$parseSerializedNode` on a literal. Closing the type * was tried for the misspelling it would catch; excess-property checking fires * only on fresh literals, and everything arriving at load comes from * `JSON.parse`, so it caught no misspelling that mattered and cost every * correct property. Flow's counterpart is inexact for the same reason. */ export type SerializedPartialNode = { /** The one property every node carries and a reader narrows by. */ type: string; /** Omitted by a compact export, like every other restorable property. */ version?: number; /** Node state, parsed by the same rules whatever the node turns out to be. */ [NODE_STATE_KEY]?: Record; /** A slot holds a node subtree, so it relaxes exactly as `children` do. */ $slots?: Record; /** Present when the node is an element; the same form all the way down. */ children?: SerializedPartialNode[]; /** A node's own properties: there is no type here to name them from. */ [key: string]: unknown; }; /** * The least a value has to be for {@link $parseSerializedNode} to read it: a * `type` to look the class up by, and subtrees of the same shape. * * A type alias rather than an `interface`, which is what lets it stand in for * the internal shape the parse walks: TypeScript gives an alias an implicit * index signature and an interface none, and the walk's own parameter carries * one. An interface is still assignable *to* it, which is the direction that * matters for a caller like `@lexical/clipboard`'s `BaseSerializedNode`. * * `version` is optional because the parser drops it — it is deprecated and * nothing reads it — so requiring it described the caller rather than the * parameter. That mattered because {@link SerializedPartialNode} carries an * index signature, which an `interface` never satisfies, and * {@link SerializedLexicalNode} requires `version`: a caller holding an * interface with an optional `version`, such as `@lexical/clipboard`'s * `BaseSerializedNode`, matched neither, and the mismatch repeated at every * level because `children` and `$slots` recurse. */ export type ParsableSerializedNode = { /** A slot holds a node subtree, so it relaxes exactly as `children` do. */ $slots?: Record; /** Present when the node is an element; the same form all the way down. */ children?: ParsableSerializedNode[]; /** The one property every node carries and a reader narrows by. */ type: string; /** * @deprecated Dropped when parsing; see {@link SerializedLexicalNode.version}. */ version?: number; }; /** * The shape {@link LexicalNode.updateFromJSON} accepts for a node whose * serialized type is `S`, as a schema-driven parser faces it: every * node-specific property optional (a compact export omits a default-valued * one, and an older document predates a newer one) and `unknown`, with * `type`, `version` and `children` dropped. * * `unknown`, because this is the untrusted-JSON boundary and a parser here is * *total*: it validates every property against the schema's domain and * substitutes a default for anything outside it. Typing a property as what it * parses *to* would claim the caller has already done that validation, which * is both untrue and narrower than what is accepted — a schema reads more than * it writes wherever it has an alias table or reads a number spelled as a * string, so `format: 'bold'` and `width: '640'` are valid input that the * narrower type rejected. The property *names* stay, so a misspelled one is * still an excess-property error. NodeState and slots keep their declared * shapes: neither is a schema-declared property, and each is read structurally * by the code that applies it rather than validated against a domain. * * A node that declares a serialization schema narrows both JSON methods to its * own serialized type by declaration merging, which is the one thing a schema * cannot do for it: * * ```ts * export interface MarkNode { * exportJSON(compact?: false): SerializedMarkNode; * exportJSON(compact: boolean): SerializedPartial; * updateFromJSON(serializedNode: LexicalParseJSON): this; * } * ``` * * A hand-written override that reads its properties typed uses * {@link LexicalUpdateJSON} instead, as it always has. */ export type LexicalParseJSON = Pick, Extract, typeof NODE_STATE_KEY | '$slots'>> & { [K in keyof Omit, '$slots' | 'children' | 'type' | typeof NODE_STATE_KEY | 'version'>]?: unknown; }; /** @internal */ export interface LexicalPrivateDOM { __lexicalTextContent?: string | undefined | null; /** * @experimental named-slots. Byte length of the slot text folded slots-first * into the front of `__lexicalTextContent` for a host element. The suffix * fast path splices the linked-list child suffix, which lives after this * prefix, so it strips this many leading chars to recover the child-only * text before splicing and lets the slot fold re-prepend the slot text. * Absent / `0` for non-host elements, so non-slot trees splice unchanged. */ __lexicalSlotTextLength?: number | undefined; /** * NodeKey of the deep first text descendant (DFS order) of this * element, or `null` if the subtree carries no text descendants. * Maintained alongside `__lexicalTextContent` and used by the * suffix-incremental fast path in `$reconcileChildren` to decide in * O(1) (via the cycle's dirty-children set) whether the prefix still * carries the canonical first text descendant. */ __lexicalFirstTextKey?: NodeKey | null | undefined; __lexicalLineBreak?: HTMLBRElement | HTMLImageElement | undefined | null; /** * Kind of last child recorded for this element during the previous * reconcile, used by `$reconcileElementTerminatingLineBreak` to decide * whether the trailing-`
` shape needs to change without calling * `isInline()` on the prev-state node reference (which would resolve * to a detached node once the last child has been removed in this * commit). Set alongside `setManagedLineBreak` / cleared alongside * `removeManagedLineBreak`. */ __lexicalLastChildKind?: 'line-break' | 'decorator' | 'empty' | null | undefined; __lexicalDir?: 'ltr' | 'rtl' | null | undefined; __lexicalUnmanaged?: boolean | undefined; /** * When true, the DOM subtree owns its own window selection — analogous to * a DecoratorNode subtree. Resolution logic that would otherwise force the * Lexical selection back onto a managed position treats the caret as * intentional and leaves it alone. Set via the `captureSelection` option * on {@link setDOMUnmanaged}. */ __lexicalCapturedSelection?: boolean | undefined; } export declare function $removeNode(nodeToRemove: LexicalNode, restoreSelection: boolean, preserveEmptyParent?: boolean): void; export type DOMConversionProp = (node: T) => DOMConversion | null; export type DOMConversionPropByTagName = DOMConversionProp; export type DOMConversionTagNameMap = { [NodeName in K]?: DOMConversionPropByTagName; }; /** * An identity function that will infer the type of DOM nodes * based on tag names to make it easier to construct a * DOMConversionMap. */ export declare function buildImportMap(importMap: { [NodeName in K]: DOMConversionPropByTagName; }): DOMConversionMap; export type DOMConversion = { conversion: DOMConversionFn; priority?: 0 | 1 | 2 | 3 | 4; }; export type DOMConversionFn = (element: T) => DOMConversionOutput | null; export type DOMChildConversion = (lexicalNode: LexicalNode, parentLexicalNode: LexicalNode | null | undefined) => LexicalNode | null | undefined; export type DOMConversionMap = Record>; type NodeName = string; export type DOMConversionOutput = { after?: (childLexicalNodes: LexicalNode[]) => LexicalNode[]; forChild?: DOMChildConversion; node: null | LexicalNode | LexicalNode[]; }; export type DOMExportOutputMap = Map, (editor: LexicalEditor, target: LexicalNode) => DOMExportOutput>; export interface DOMExportOutput { /** * Called after the node and all of its children are constructed, can be used * to perform any in-place updates to the node or return something else * entirely. * * @param generatedElement `element` after children are appended * @returns The final representation of this node in the exported DOM */ after?: (generatedElement: HTMLElement | DocumentFragment | Text | null | undefined) => HTMLElement | DocumentFragment | Text | null | undefined; /** * A DOM node for this lexical node, or null to skip it */ element: HTMLElement | DocumentFragment | Text | null; /** * An optional override to change how and where DOM nodes for this * ElementNode's children are appended, particularly useful if * this node's children are not direct ancestors. * * @param element The DOM of a child node to append */ append?: (element: HTMLElement | DocumentFragment | Text) => void; /** * If defined, will be used instead of `node.getChildren()` to determine * which children to render for this LexicalNode. * * @returns The children to export */ $getChildNodes?: () => Iterable; } export type NodeKey = string; declare const EPHEMERAL: unique symbol; /** * @internal * @param node any LexicalNode * @returns true if the node was created with {@link $cloneWithPropertiesEphemeral} */ export declare function $isEphemeral(node: LexicalNode & { readonly [EPHEMERAL]?: boolean; }): boolean; /** * @internal * Mark this node as ephemeral, its instance always returns this * for getLatest and getWritable. It must not be added to an EditorState. */ export declare function $markEphemeral(node: T & { [EPHEMERAL]?: boolean; }): T; /** * A node that can host named slots, implemented by {@link ElementNode} and * {@link DecoratorNode}. The map is allocated lazily (null until the first * {@link $setSlot}) since most nodes have none. Declaring this off the base * {@link LexicalNode} is what lets {@link $setSlot} / {@link $removeSlot} * reject a non-host at compile time. * * @experimental */ export interface SlotHostNode { /** @internal */ __slots: null | Map; } /** * A node that can occupy a named slot, implemented by {@link ElementNode} and * {@link DecoratorNode}. Its up-pointer is `__slotHost` rather than `__parent` * (the two are mutually exclusive), so the slot boundary behaves like a shadow * root. * * @experimental */ export interface SlotChildNode { /** @internal */ __slotHost: null | NodeKey; } export declare class LexicalNode { /** @internal Allow us to look up the type including static props */ ['constructor']: KlassConstructor; /** @internal See {@link LEXICAL_NODE_BRAND}. */ readonly [LEXICAL_NODE_BRAND]: true; /** @internal */ readonly __type: string; /** @internal */ __key: string; /** @internal */ __parent: null | NodeKey; /** @internal */ __prev: null | NodeKey; /** @internal */ __next: null | NodeKey; /** @internal */ __state?: NodeState; /** @internal */ [CACHED_TEXT_SIZE_KEY]?: number; /** * Returns the string type of this node. Every node must * implement this and it MUST BE UNIQUE amongst nodes registered * on the editor. * */ static getType(): string; /** * Clones this node, creating a new node with a different key * and adding it to the EditorState (but not attaching it anywhere!). All nodes must * implement this method. * */ static clone(_data: unknown): LexicalNode; /** * Override this to implement the new static node configuration protocol, * this method is called directly on the prototype and must not depend * on anything initialized in the constructor. Generally it should be * a trivial implementation. * * @example * ```ts * class MyNode extends TextNode { * $config() { * return this.config('my-node', {extends: TextNode}); * } * } * ``` */ $config(): BaseStaticNodeConfig; /** * This is a convenience method for $config that * aids in type inference. See {@link LexicalNode.$config} * for example usage. * * An abstract base class that has no concrete node `type` may pass a * well-known symbol (by convention `Symbol.for()`) instead of * a string `type` to declare configuration shared with its subclasses. */ config>(type: symbol, config: Config): AbstractStaticNodeConfigRecord; config>(type: Type, config: Config): StaticNodeConfigRecord; /** * Perform any state updates on the clone of prevNode that are not already * handled by the constructor call in the static clone method. If you have * state to update in your clone that is not handled directly by the * constructor, it is advisable to override this method but it is required * to include a call to `super.afterCloneFrom(prevNode)` in your * implementation. This is only intended to be called by * {@link $cloneWithProperties} function or via a super call. * * @example * ```ts * class ClassesTextNode extends TextNode { * // Not shown: static getType, static importJSON, exportJSON, createDOM, updateDOM * __classes = new Set(); * static clone(node: ClassesTextNode): ClassesTextNode { * // The inherited TextNode constructor is used here, so * // classes is not set by this method. * return new ClassesTextNode(node.__text, node.__key); * } * afterCloneFrom(node: this): void { * // This calls TextNode.afterCloneFrom and LexicalNode.afterCloneFrom * // for necessary state updates * super.afterCloneFrom(node); * this.__addClasses(node.__classes); * } * // This method is a private implementation detail, it is not * // suitable for the public API because it does not call getWritable * __addClasses(classNames: Iterable): this { * for (const className of classNames) { * this.__classes.add(className); * } * return this; * } * addClass(...classNames: string[]): this { * return this.getWritable().__addClasses(classNames); * } * removeClass(...classNames: string[]): this { * const node = this.getWritable(); * for (const className of classNames) { * this.__classes.delete(className); * } * return this; * } * getClasses(): Set { * return this.getLatest().__classes; * } * } * ``` * */ afterCloneFrom(prevNode: this): void; /** * Reset state in this copy of originalNode, if necessary * * @param originalNode */ resetOnCopyNodeFrom(originalNode: this): void; static importDOM?: () => DOMConversionMap | null; constructor(key?: NodeKey); /** * Returns the string type of this node. */ getType(): string; isInline(): boolean; /** * Returns true if there is a path between this node and the RootNode, false otherwise. * This is a way of determining if the node is "attached" EditorState. Unattached nodes * won't be reconciled and will ultimately be cleaned up by the Lexical GC. */ isAttached(): boolean; /** * Returns true if this node is contained within the provided Selection., false otherwise. * Relies on the algorithms implemented in {@link BaseSelection.getNodes} to determine * what's included. * * @param selection - The selection that we want to determine if the node is in. */ isSelected(selection?: null | BaseSelection): boolean; /** * Returns this nodes key. */ getKey(): NodeKey; /** * Returns the zero-based index of this node within the parent. */ getIndexWithinParent(): number; /** * Returns the parent of this node, or null if none is found. */ getParent(): ElementNode | null; /** * @deprecated The type parameter is an unchecked and unsafe cast, * equivalent to `node.getParent() as T | null`, 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 | null; /** * Returns the parent of this node, or throws if none is found. */ getParentOrThrow(): ElementNode; /** * @deprecated The type parameter is an unchecked and unsafe cast, * equivalent to `node.getParentOrThrow() as T`, 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; /** * Returns the highest (in the EditorState tree) * non-root ancestor of this node, or null if none is found. See {@link lexical!$isRootOrShadowRoot} * for more information on which Elements comprise "roots". */ getTopLevelElement(): ElementNode | DecoratorNode | null; /** * Returns the highest (in the EditorState tree) * non-root ancestor of this node, or throws if none is found. See {@link lexical!$isRootOrShadowRoot} * for more information on which Elements comprise "roots". */ getTopLevelElementOrThrow(): ElementNode | DecoratorNode; /** * Returns a list of the every ancestor of this node, * all the way up to the RootNode. * */ getParents(): ElementNode[]; /** * Returns a list of the keys of every ancestor of this node, * all the way up to the RootNode. * */ getParentKeys(): NodeKey[]; /** * Returns the node before this one in the same parent, or null * if there is no such node. */ getPreviousSibling(): LexicalNode | null; /** * @deprecated The type parameter is an unchecked and unsafe cast, * equivalent to `node.getPreviousSibling() as T | null`, 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 | null; /** * Returns all nodes before this one in the same parent, * in document order. */ getPreviousSiblings(): LexicalNode[]; /** * @deprecated The type parameter is an unchecked and unsafe cast, * equivalent to `node.getPreviousSiblings() as T[]`, and will be * removed in a future release. Call this method without a type argument * and narrow the results with a type guard instead. */ getPreviousSiblings(): T[]; /** * Returns the node after this one in the same parent, or null * if there is no such node. */ getNextSibling(): LexicalNode | null; /** * @deprecated The type parameter is an unchecked and unsafe cast, * equivalent to `node.getNextSibling() as T | null`, 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 | null; /** * Returns all nodes after this one in the same parent, * in document order. */ getNextSiblings(): LexicalNode[]; /** * @deprecated The type parameter is an unchecked and unsafe cast, * equivalent to `node.getNextSiblings() as T[]`, and will be * removed in a future release. Call this method without a type argument * and narrow the results with a type guard instead. */ getNextSiblings(): T[]; /** * @deprecated use {@link $getCommonAncestor} * * Returns the closest common ancestor of this node and the provided one or null * if one cannot be found. * * @param node - the other node to find the common ancestor of. */ getCommonAncestor(node: LexicalNode): T | null; /** * Returns true if the provided node is the exact same one as this node, from Lexical's perspective. * Always use this instead of referential equality. * * @param object - the node to perform the equality comparison on. */ is(object: LexicalNode | null | undefined): boolean; /** * Returns true if this node logically precedes the target node in the * editor state, false otherwise (including if there is no common ancestor). * * Note that this notion of isBefore is based on post-order; a descendant * node is always before its ancestors. See also * {@link $getCommonAncestor} and {@link $comparePointCaretNext} for * more flexible ways to determine the relative positions of nodes. * * @param targetNode - the node we're testing to see if it's after this one. */ isBefore(targetNode: LexicalNode): boolean; /** * Returns true if this node is an ancestor of and distinct from the target node, false otherwise. * * @param targetNode - the would-be child node. */ isParentOf(targetNode: LexicalNode): boolean; /** * Returns a list of nodes that are between this node and * the target node in the EditorState. * * @param targetNode - the node that marks the other end of the range of nodes to be returned. */ getNodesBetween(targetNode: LexicalNode): LexicalNode[]; /** * Returns true if this node has been marked dirty during this update cycle. * */ isDirty(): boolean; /** * Returns the latest version of the node from the active EditorState. * This is used to avoid getting values from stale node references. * */ getLatest(): this; /** * Returns a mutable version of the node using {@link $cloneWithProperties} * if necessary. Will throw an error if called outside of a Lexical Editor * {@link LexicalEditor.update} callback. * */ getWritable(): this; /** * Returns the text content of the node. Override this for * custom nodes that should have a representation in plain text * format (for copy + paste, for example) * */ getTextContent(): string; /** * Returns the length of the string produced by calling getTextContent on this node. * */ getTextContentSize(): number; /** * Called during the reconciliation process to determine which nodes * to insert into the DOM for this Lexical Node. * * This method must return exactly one HTMLElement. Nested elements are not supported. * * Do not attempt to update the Lexical EditorState during this phase of the update lifecycle. * * @param _config - allows access to things like the EditorTheme (to apply classes) during reconciliation. * @param _editor - allows access to the editor for context during reconciliation. * * */ createDOM(_config: EditorConfig, _editor: LexicalEditor): HTMLElement; /** * Called when a node changes and should update the DOM * in whatever way is necessary to make it align with any changes that might * have happened during the update. * * Returning "true" here will cause lexical to unmount and recreate the DOM node * (by calling createDOM). You would need to do this if the element tag changes, * for instance. * * */ updateDOM(_prevNode: unknown, _dom: HTMLElement, _config: EditorConfig): boolean; /** * Returns a {@link DOMSlot} pointing at the content-bearing element of this * node's DOM. The default returns a slot wrapping the keyed DOM as-is. * * Override this when {@link createDOM} returns a wrapper around the * content-bearing element (e.g. `
` for a styled line * break), so selection / reconciliation logic can target the inner element. * * {@link ElementNode} overrides this to return an {@link ElementDOMSlot} * with children-management semantics (used by the reconciler to place * managed children). * * @experimental */ getDOMSlot(element: HTMLElement): DOMSlot; /** * Controls how the this node is serialized to HTML. This is important for * copy and paste between Lexical and non-Lexical editors, or Lexical editors with different namespaces, * in which case the primary transfer format is HTML. It's also important if you're serializing * to HTML for any other reason via {@link @lexical/html!$generateHtmlFromNodes}. You could * also use this method to build your own HTML renderer. * * */ exportDOM(editor: LexicalEditor): DOMExportOutput; /** * Controls how the this node is serialized to JSON. This is important for * copy and paste between Lexical editors sharing the same namespace. It's also important * if you're serializing to JSON for persistent storage somewhere. * See [Serialization & Deserialization](https://lexical.dev/docs/concepts/serialization#lexical---html). * * The base implementation writes every property the node's schema declares * (its own and those it inherits), reading each through its getter — * `get` by default, or the name recorded with `withAccessors`. A getter * that returns `undefined` omits its property. Override this only for output * a schema can not describe, and call `super.exportJSON(compact)` when you do. * * **This may serialize the instance as-is, without resolving the latest * version.** A property declared with {@link withField} is read straight off * the node, which is the optimization the serialization walk is built on — * every node the walk reaches comes from the EditorState's node map and is * already current, so it resolves nothing per node. * * So on a reference that a `getWritable()` (any `set`) has since * superseded, this writes pre-mutation values. Which properties do is not * something to rely on: a property whose accessor a subclass overrode still * goes through that accessor and resolves the latest, so one node can write * a current `text` beside a stale `style`. Call * `node.getLatest().exportJSON()` whenever you hold such a reference rather * than reasoning about which properties resolve. * * This is a breaking change. Every property previously went through an * accessor, and every accessor resolves `getLatest()`, so a stale reference * exported current values. * * @param compact Write the compact form: omit a property the parser derives * rather than reads, one whose value is the schema default parsing would * restore, and the deprecated `version`. The two forms describe the same * document. A node that overrides this and ignores the flag simply keeps * writing the full form, which still parses. * */ exportJSON(compact?: false): SerializedLexicalNode; /** * The compact form omits properties, so what it returns is the *partial* * serialized type — every node-specific property optional — rather than the * full one. Passing a `boolean` whose value is not statically known selects * this overload too, which is right: neither form can be promised then. * * @see {@link SerializedPartial} */ exportJSON(compact: boolean): SerializedPartial; /** * Controls how the this node is deserialized from JSON. This is usually boilerplate, * but provides an abstraction between the node implementation and serialized interface that can * be important if you ever make breaking changes to a node schema (by adding or removing properties). * See [Serialization & Deserialization](https://lexical.dev/docs/concepts/serialization#lexical---html). * * */ static importJSON(_serializedNode: SerializedPartial & Record): LexicalNode; /** * Update this LexicalNode instance from serialized JSON. It's recommended * to implement as much logic as possible in this method instead of the * static importJSON method, so that the functionality can be inherited in subclasses. * * The LexicalUpdateJSON utility type should be used to ignore any type, version, * or children properties in the JSON so that the extended JSON from subclasses * are acceptable parameters for the super call. * * If overridden, this method must call super. * * @example * ```ts * class MyTextNode extends TextNode { * // ... * static importJSON(serializedNode: SerializedMyTextNode): MyTextNode { * return $createMyTextNode() * .updateFromJSON(serializedNode); * } * updateFromJSON( * serializedNode: LexicalUpdateJSON, * ): this { * return super.updateFromJSON(serializedNode) * .setMyProperty(serializedNode.myProperty); * } * } * ``` * * The whole schema is applied, so a property the JSON omits is set to * its schema default rather than left as it is — that is what lets the * compact form omit a default-valued property and have parsing restore it. * (A flat NodeState is the exception: it is applied only when present.) Pass * the node's complete serialized form unless you mean to reset what you * leave out. */ updateFromJSON(serializedNode: LexicalParseJSON): this; /** * @experimental * * Registers the returned function as a transform on the node during * Editor initialization. Most such use cases should be addressed via * the {@link LexicalEditor.registerNodeTransform} API. * * Experimental - use at your own risk. */ static transform(): ((node: LexicalNode) => void) | null; /** * Removes this LexicalNode from the EditorState. If the node isn't re-inserted * somewhere, the Lexical garbage collector will eventually clean it up. * * @param preserveEmptyParent - If falsy, the node's parent will be removed if * it's empty after the removal operation. This is the default behavior, subject to * other node heuristics such as {@link ElementNode#canBeEmpty} * */ remove(preserveEmptyParent?: boolean): void; /** * Replaces this LexicalNode with the provided node, optionally transferring the children * of the replaced node to the replacing node. * * Named slots are bound to their host node and are never transferred: this * node keeps its slot map, so if it is reattached elsewhere (as * `$wrapNodeInElement` does) its slots come with it, and if it stays * detached the slot subtrees are garbage-collected along with it. To move a * slot value onto another host, use `$setSlot` explicitly. * * @param replaceWith - The node to replace this one with. * @param includeChildren - Whether or not to transfer the children of this node to the replacing node. * */ replace(replaceWith: N, includeChildren?: boolean): N; /** * Inserts a node after this LexicalNode (as the next sibling). * * @param nodeToInsert - The node to insert after this one. * @param restoreSelection - Whether or not to attempt to resolve the * selection to the appropriate place after the operation is complete. * */ insertAfter(nodeToInsert: LexicalNode, restoreSelection?: boolean): LexicalNode; /** * Inserts a node before this LexicalNode (as the previous sibling). * * @param nodeToInsert - The node to insert before this one. * @param restoreSelection - Whether or not to attempt to resolve the * selection to the appropriate place after the operation is complete. * */ insertBefore(nodeToInsert: LexicalNode, restoreSelection?: boolean): LexicalNode; /** * Whether or not this node has a required parent. Used during copy + paste operations * to normalize nodes that would otherwise be orphaned. For example, ListItemNodes without * a ListNode parent or TextNodes with a ParagraphNode parent. * * */ isParentRequired(): boolean; /** * The creation logic for any required parent. Should be implemented if {@link isParentRequired} returns true. * * */ createParentElementNode(): ElementNode; selectStart(): RangeSelection; selectEnd(): RangeSelection; /** * Moves selection to the previous sibling of this node, at the specified offsets. * * @param anchorOffset - The anchor offset for selection. * @param focusOffset - The focus offset for selection * */ selectPrevious(anchorOffset?: number, focusOffset?: number): RangeSelection; /** * Moves selection to the next sibling of this node, at the specified offsets. * * @param anchorOffset - The anchor offset for selection. * @param focusOffset - The focus offset for selection * */ selectNext(anchorOffset?: number, focusOffset?: number): RangeSelection; /** * Marks a node dirty, triggering transforms and * forcing it to be reconciled during the update cycle. * * */ markDirty(): void; /** * @internal * * When the reconciler detects that a node was mutated, this method * may be called to restore the node to a known good state. */ reconcileObservedMutation(dom: HTMLElement, editor: LexicalEditor): void; } /** * Insert a series of nodes after this LexicalNode (as next siblings) * * @param firstToInsert - The first node to insert after this one. * @param lastToInsert - The last node to insert after this one. Must be a * later sibling of FirstNode. If not provided, it will be its last sibling. */ export declare function insertRangeAfter(node: LexicalNode, firstToInsert: LexicalNode, lastToInsert?: LexicalNode): void; /** * Returns true if the given value is a {@link LexicalNode} instance. */ export declare function $isLexicalNode(node: null | undefined | LexicalNode): node is LexicalNode; export {};