2 * Copyright (c) Meta Platforms, Inc. and affiliates.
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
9 /* eslint-disable no-constant-condition */
10 import type {EditorConfig, LexicalEditor} from './LexicalEditor';
11 import type {BaseSelection, RangeSelection} from './LexicalSelection';
12 import type {Klass, KlassConstructor} from 'lexical';
14 import invariant from 'lexical/shared/invariant';
29 $moveSelectionPointToEnd,
30 $updateElementSelectionOnCreateDeleteNode,
31 moveSelectionPointToSibling,
32 } from './LexicalSelection';
37 } from './LexicalUpdates';
43 $maybeMoveChildrenSelectionToParent,
47 errorOnInsertTextNodeOnRoot,
48 internalMarkNodeAsDirty,
50 } from './LexicalUtils';
52 export type NodeMap = Map<NodeKey, LexicalNode>;
54 export type SerializedLexicalNode = {
59 export function $removeNode(
60 nodeToRemove: LexicalNode,
61 restoreSelection: boolean,
62 preserveEmptyParent?: boolean,
65 const key = nodeToRemove.__key;
66 const parent = nodeToRemove.getParent();
67 if (parent === null) {
70 const selection = $maybeMoveChildrenSelectionToParent(nodeToRemove);
71 let selectionMoved = false;
72 if ($isRangeSelection(selection) && restoreSelection) {
73 const anchor = selection.anchor;
74 const focus = selection.focus;
75 if (anchor.key === key) {
76 moveSelectionPointToSibling(
80 nodeToRemove.getPreviousSibling(),
81 nodeToRemove.getNextSibling(),
83 selectionMoved = true;
85 if (focus.key === key) {
86 moveSelectionPointToSibling(
90 nodeToRemove.getPreviousSibling(),
91 nodeToRemove.getNextSibling(),
93 selectionMoved = true;
96 $isNodeSelection(selection) &&
98 nodeToRemove.isSelected()
100 nodeToRemove.selectPrevious();
103 if ($isRangeSelection(selection) && restoreSelection && !selectionMoved) {
104 // Doing this is O(n) so lets avoid it unless we need to do it
105 const index = nodeToRemove.getIndexWithinParent();
106 removeFromParent(nodeToRemove);
107 $updateElementSelectionOnCreateDeleteNode(selection, parent, index, -1);
109 removeFromParent(nodeToRemove);
113 !preserveEmptyParent &&
114 !$isRootOrShadowRoot(parent) &&
115 !parent.canBeEmpty() &&
118 $removeNode(parent, restoreSelection);
120 if (restoreSelection && $isRootNode(parent) && parent.isEmpty()) {
125 export type DOMConversion<T extends HTMLElement = HTMLElement> = {
126 conversion: DOMConversionFn<T>;
127 priority?: 0 | 1 | 2 | 3 | 4;
130 export type DOMConversionFn<T extends HTMLElement = HTMLElement> = (
132 ) => DOMConversionOutput | null;
134 export type DOMChildConversion = (
135 lexicalNode: LexicalNode,
136 parentLexicalNode: LexicalNode | null | undefined,
137 ) => LexicalNode | null | undefined;
139 export type DOMConversionMap<T extends HTMLElement = HTMLElement> = Record<
141 (node: T) => DOMConversion<T> | null
143 type NodeName = string;
146 * Output for a DOM conversion.
147 * Node can be set to 'ignore' to ignore the conversion and handling of the DOMNode
148 * including all its children.
150 * You can specify a function to run for each converted child (forChild) or on all
151 * the child nodes after the conversion is complete (after).
152 * The key difference here is that forChild runs for every deeply nested child node
153 * of the current node, whereas after will run only once after the
154 * transformation of the node and all its children is complete.
156 export type DOMConversionOutput = {
157 after?: (childLexicalNodes: Array<LexicalNode>) => Array<LexicalNode>;
158 forChild?: DOMChildConversion;
159 node: null | LexicalNode | Array<LexicalNode> | 'ignore';
162 export type DOMExportOutputMap = Map<
164 (editor: LexicalEditor, target: LexicalNode) => DOMExportOutput
167 export type DOMExportOutput = {
169 generatedElement: HTMLElement | Text | null | undefined,
170 ) => HTMLElement | Text | null | undefined;
171 element: HTMLElement | Text | null;
174 export type NodeKey = string;
176 export class LexicalNode {
177 // Allow us to look up the type including static props
178 declare ['constructor']: KlassConstructor<typeof LexicalNode>;
182 //@ts-ignore We set the key in the constructor.
185 __parent: null | NodeKey;
187 __prev: null | NodeKey;
189 __next: null | NodeKey;
191 // Flow doesn't support abstract classes unfortunately, so we can't _force_
192 // subclasses of Node to implement statics. All subclasses of Node should have
193 // a static getType and clone method though. We define getType and clone here so we can call it
194 // on any Node, and we throw this error by default since the subclass should provide
195 // their own implementation.
197 * Returns the string type of this node. Every node must
198 * implement this and it MUST BE UNIQUE amongst nodes registered
202 static getType(): string {
205 'LexicalNode: Node %s does not implement .getType().',
211 * Clones this node, creating a new node with a different key
212 * and adding it to the EditorState (but not attaching it anywhere!). All nodes must
213 * implement this method.
216 static clone(_data: unknown): LexicalNode {
219 'LexicalNode: Node %s does not implement .clone().',
225 * Perform any state updates on the clone of prevNode that are not already
226 * handled by the constructor call in the static clone method. If you have
227 * state to update in your clone that is not handled directly by the
228 * constructor, it is advisable to override this method but it is required
229 * to include a call to `super.afterCloneFrom(prevNode)` in your
230 * implementation. This is only intended to be called by
231 * {@link $cloneWithProperties} function or via a super call.
235 * class ClassesTextNode extends TextNode {
236 * // Not shown: static getType, static importJSON, exportJSON, createDOM, updateDOM
237 * __classes = new Set<string>();
238 * static clone(node: ClassesTextNode): ClassesTextNode {
239 * // The inherited TextNode constructor is used here, so
240 * // classes is not set by this method.
241 * return new ClassesTextNode(node.__text, node.__key);
243 * afterCloneFrom(node: this): void {
244 * // This calls TextNode.afterCloneFrom and LexicalNode.afterCloneFrom
245 * // for necessary state updates
246 * super.afterCloneFrom(node);
247 * this.__addClasses(node.__classes);
249 * // This method is a private implementation detail, it is not
250 * // suitable for the public API because it does not call getWritable
251 * __addClasses(classNames: Iterable<string>): this {
252 * for (const className of classNames) {
253 * this.__classes.add(className);
257 * addClass(...classNames: string[]): this {
258 * return this.getWritable().__addClasses(classNames);
260 * removeClass(...classNames: string[]): this {
261 * const node = this.getWritable();
262 * for (const className of classNames) {
263 * this.__classes.delete(className);
267 * getClasses(): Set<string> {
268 * return this.getLatest().__classes;
274 afterCloneFrom(prevNode: this) {
275 this.__parent = prevNode.__parent;
276 this.__next = prevNode.__next;
277 this.__prev = prevNode.__prev;
280 // eslint-disable-next-line @typescript-eslint/no-explicit-any
281 static importDOM?: () => DOMConversionMap<any> | null;
283 constructor(key?: NodeKey) {
284 this.__type = this.constructor.getType();
285 this.__parent = null;
288 $setNodeKey(this, key);
291 if (this.__type !== 'root') {
293 errorOnTypeKlassMismatch(this.__type, this.constructor);
297 // Getters and Traversers
300 * Returns the string type of this node.
306 isInline(): boolean {
309 'LexicalNode: Node %s does not implement .isInline().',
310 this.constructor.name,
315 * Returns true if there is a path between this node and the RootNode, false otherwise.
316 * This is a way of determining if the node is "attached" EditorState. Unattached nodes
317 * won't be reconciled and will ultimatelt be cleaned up by the Lexical GC.
319 isAttached(): boolean {
320 let nodeKey: string | null = this.__key;
321 while (nodeKey !== null) {
322 if (nodeKey === 'root') {
326 const node: LexicalNode | null = $getNodeByKey(nodeKey);
331 nodeKey = node.__parent;
337 * Returns true if this node is contained within the provided Selection., false otherwise.
338 * Relies on the algorithms implemented in {@link BaseSelection.getNodes} to determine
341 * @param selection - The selection that we want to determine if the node is in.
343 isSelected(selection?: null | BaseSelection): boolean {
344 const targetSelection = selection || $getSelection();
345 if (targetSelection == null) {
349 const isSelected = targetSelection
351 .some((n) => n.__key === this.__key);
353 if ($isTextNode(this)) {
356 // For inline images inside of element nodes.
357 // Without this change the image will be selected if the cursor is before or after it.
358 const isElementRangeSelection =
359 $isRangeSelection(targetSelection) &&
360 targetSelection.anchor.type === 'element' &&
361 targetSelection.focus.type === 'element';
363 if (isElementRangeSelection) {
364 if (targetSelection.isCollapsed()) {
368 const parentNode = this.getParent();
369 if ($isDecoratorNode(this) && this.isInline() && parentNode) {
370 const firstPoint = targetSelection.isBackward()
371 ? targetSelection.focus
372 : targetSelection.anchor;
373 const firstElement = firstPoint.getNode() as ElementNode;
375 firstPoint.offset === firstElement.getChildrenSize() &&
376 firstElement.is(parentNode) &&
377 firstElement.getLastChildOrThrow().is(this)
387 * Indicate if this node should be selected directly instead of the default
388 * where the selection would descend to the nearest initial child element.
390 shouldSelectDirectly(): boolean {
395 * Returns this nodes key.
398 // Key is stable between copies
403 * Returns the zero-based index of this node within the parent.
405 getIndexWithinParent(): number {
406 const parent = this.getParent();
407 if (parent === null) {
410 let node = parent.getFirstChild();
412 while (node !== null) {
417 node = node.getNextSibling();
423 * Returns the parent of this node, or null if none is found.
425 getParent<T extends ElementNode>(): T | null {
426 const parent = this.getLatest().__parent;
427 if (parent === null) {
430 return $getNodeByKey<T>(parent);
434 * Returns the parent of this node, or throws if none is found.
436 getParentOrThrow<T extends ElementNode>(): T {
437 const parent = this.getParent<T>();
438 if (parent === null) {
439 invariant(false, 'Expected node %s to have a parent.', this.__key);
445 * Returns the highest (in the EditorState tree)
446 * non-root ancestor of this node, or null if none is found. See {@link lexical!$isRootOrShadowRoot}
447 * for more information on which Elements comprise "roots".
449 getTopLevelElement(): ElementNode | DecoratorNode<unknown> | null {
450 let node: ElementNode | this | null = this;
451 while (node !== null) {
452 const parent: ElementNode | null = node.getParent();
453 if ($isRootOrShadowRoot(parent)) {
455 $isElementNode(node) || (node === this && $isDecoratorNode(node)),
456 'Children of root nodes must be elements or decorators',
466 * Returns the highest (in the EditorState tree)
467 * non-root ancestor of this node, or throws if none is found. See {@link lexical!$isRootOrShadowRoot}
468 * for more information on which Elements comprise "roots".
470 getTopLevelElementOrThrow(): ElementNode | DecoratorNode<unknown> {
471 const parent = this.getTopLevelElement();
472 if (parent === null) {
475 'Expected node %s to have a top parent element.',
483 * Returns a list of the every ancestor of this node,
484 * all the way up to the RootNode.
487 getParents(): Array<ElementNode> {
488 const parents: Array<ElementNode> = [];
489 let node = this.getParent();
490 while (node !== null) {
492 node = node.getParent();
498 * Returns a list of the keys of every ancestor of this node,
499 * all the way up to the RootNode.
502 getParentKeys(): Array<NodeKey> {
504 let node = this.getParent();
505 while (node !== null) {
506 parents.push(node.__key);
507 node = node.getParent();
513 * Returns the "previous" siblings - that is, the node that comes
514 * before this one in the same parent.
517 getPreviousSibling<T extends LexicalNode>(): T | null {
518 const self = this.getLatest();
519 const prevKey = self.__prev;
520 return prevKey === null ? null : $getNodeByKey<T>(prevKey);
524 * Returns the "previous" siblings - that is, the nodes that come between
525 * this one and the first child of it's parent, inclusive.
528 getPreviousSiblings<T extends LexicalNode>(): Array<T> {
529 const siblings: Array<T> = [];
530 const parent = this.getParent();
531 if (parent === null) {
534 let node: null | T = parent.getFirstChild();
535 while (node !== null) {
540 node = node.getNextSibling();
546 * Returns the "next" siblings - that is, the node that comes
547 * after this one in the same parent
550 getNextSibling<T extends LexicalNode>(): T | null {
551 const self = this.getLatest();
552 const nextKey = self.__next;
553 return nextKey === null ? null : $getNodeByKey<T>(nextKey);
557 * Returns all "next" siblings - that is, the nodes that come between this
558 * one and the last child of it's parent, inclusive.
561 getNextSiblings<T extends LexicalNode>(): Array<T> {
562 const siblings: Array<T> = [];
563 let node: null | T = this.getNextSibling();
564 while (node !== null) {
566 node = node.getNextSibling();
572 * Returns the closest common ancestor of this node and the provided one or null
573 * if one cannot be found.
575 * @param node - the other node to find the common ancestor of.
577 getCommonAncestor<T extends ElementNode = ElementNode>(
580 const a = this.getParents();
581 const b = node.getParents();
582 if ($isElementNode(this)) {
585 if ($isElementNode(node)) {
588 const aLength = a.length;
589 const bLength = b.length;
590 if (aLength === 0 || bLength === 0 || a[aLength - 1] !== b[bLength - 1]) {
593 const bSet = new Set(b);
594 for (let i = 0; i < aLength; i++) {
595 const ancestor = a[i] as T;
596 if (bSet.has(ancestor)) {
604 * Returns true if the provided node is the exact same one as this node, from Lexical's perspective.
605 * Always use this instead of referential equality.
607 * @param object - the node to perform the equality comparison on.
609 is(object: LexicalNode | null | undefined): boolean {
610 if (object == null) {
613 return this.__key === object.__key;
617 * Returns true if this node logical precedes the target node in the editor state.
619 * @param targetNode - the node we're testing to see if it's after this one.
621 isBefore(targetNode: LexicalNode): boolean {
622 if (this === targetNode) {
625 if (targetNode.isParentOf(this)) {
628 if (this.isParentOf(targetNode)) {
631 const commonAncestor = this.getCommonAncestor(targetNode);
634 let node: this | ElementNode | LexicalNode = this;
636 const parent: ElementNode = node.getParentOrThrow();
637 if (parent === commonAncestor) {
638 indexA = node.getIndexWithinParent();
645 const parent: ElementNode = node.getParentOrThrow();
646 if (parent === commonAncestor) {
647 indexB = node.getIndexWithinParent();
652 return indexA < indexB;
656 * Returns true if this node is the parent of the target node, false otherwise.
658 * @param targetNode - the would-be child node.
660 isParentOf(targetNode: LexicalNode): boolean {
661 const key = this.__key;
662 if (key === targetNode.__key) {
665 let node: ElementNode | LexicalNode | null = targetNode;
666 while (node !== null) {
667 if (node.__key === key) {
670 node = node.getParent();
675 // TO-DO: this function can be simplified a lot
677 * Returns a list of nodes that are between this node and
678 * the target node in the EditorState.
680 * @param targetNode - the node that marks the other end of the range of nodes to be returned.
682 getNodesBetween(targetNode: LexicalNode): Array<LexicalNode> {
683 const isBefore = this.isBefore(targetNode);
685 const visited = new Set();
686 let node: LexicalNode | this | null = this;
691 const key = node.__key;
692 if (!visited.has(key)) {
696 if (node === targetNode) {
699 const child: LexicalNode | null = $isElementNode(node)
701 ? node.getFirstChild()
702 : node.getLastChild()
704 if (child !== null) {
708 const nextSibling: LexicalNode | null = isBefore
709 ? node.getNextSibling()
710 : node.getPreviousSibling();
711 if (nextSibling !== null) {
715 const parent: LexicalNode | null = node.getParentOrThrow();
716 if (!visited.has(parent.__key)) {
719 if (parent === targetNode) {
722 let parentSibling = null;
723 let ancestor: LexicalNode | null = parent;
725 if (ancestor === null) {
726 invariant(false, 'getNodesBetween: ancestor is null');
728 parentSibling = isBefore
729 ? ancestor.getNextSibling()
730 : ancestor.getPreviousSibling();
731 ancestor = ancestor.getParent();
732 if (ancestor !== null) {
733 if (parentSibling === null && !visited.has(ancestor.__key)) {
734 nodes.push(ancestor);
739 } while (parentSibling === null);
740 node = parentSibling;
749 * Returns true if this node has been marked dirty during this update cycle.
753 const editor = getActiveEditor();
754 const dirtyLeaves = editor._dirtyLeaves;
755 return dirtyLeaves !== null && dirtyLeaves.has(this.__key);
759 * Returns the latest version of the node from the active EditorState.
760 * This is used to avoid getting values from stale node references.
764 const latest = $getNodeByKey<this>(this.__key);
765 if (latest === null) {
768 'Lexical node does not exist in active editor state. Avoid using the same node references between nested closures from editorState.read/editor.update.',
775 * Returns a mutable version of the node using {@link $cloneWithProperties}
776 * if necessary. Will throw an error if called outside of a Lexical Editor
777 * {@link LexicalEditor.update} callback.
780 getWritable(): this {
782 const editorState = getActiveEditorState();
783 const editor = getActiveEditor();
784 const nodeMap = editorState._nodeMap;
785 const key = this.__key;
786 // Ensure we get the latest node from pending state
787 const latestNode = this.getLatest();
788 const cloneNotNeeded = editor._cloneNotNeeded;
789 const selection = $getSelection();
790 if (selection !== null) {
791 selection.setCachedNodes(null);
793 if (cloneNotNeeded.has(key)) {
794 // Transforms clear the dirty node set on each iteration to keep track on newly dirty nodes
795 internalMarkNodeAsDirty(latestNode);
798 const mutableNode = $cloneWithProperties(latestNode);
799 cloneNotNeeded.add(key);
800 internalMarkNodeAsDirty(mutableNode);
801 // Update reference in node map
802 nodeMap.set(key, mutableNode);
808 * Returns the text content of the node. Override this for
809 * custom nodes that should have a representation in plain text
810 * format (for copy + paste, for example)
813 getTextContent(): string {
818 * Returns the length of the string produced by calling getTextContent on this node.
821 getTextContentSize(): number {
822 return this.getTextContent().length;
828 * Called during the reconciliation process to determine which nodes
829 * to insert into the DOM for this Lexical Node.
831 * This method must return exactly one HTMLElement. Nested elements are not supported.
833 * Do not attempt to update the Lexical EditorState during this phase of the update lifecyle.
835 * @param _config - allows access to things like the EditorTheme (to apply classes) during reconciliation.
836 * @param _editor - allows access to the editor for context during reconciliation.
839 createDOM(_config: EditorConfig, _editor: LexicalEditor): HTMLElement {
840 invariant(false, 'createDOM: base method not extended');
844 * Called when a node changes and should update the DOM
845 * in whatever way is necessary to make it align with any changes that might
846 * have happened during the update.
848 * Returning "true" here will cause lexical to unmount and recreate the DOM node
849 * (by calling createDOM). You would need to do this if the element tag changes,
856 _config: EditorConfig,
858 invariant(false, 'updateDOM: base method not extended');
862 * Controls how the this node is serialized to HTML. This is important for
863 * copy and paste between Lexical and non-Lexical editors, or Lexical editors with different namespaces,
864 * in which case the primary transfer format is HTML. It's also important if you're serializing
865 * to HTML for any other reason via {@link @lexical/html!$generateHtmlFromNodes}. You could
866 * also use this method to build your own HTML renderer.
869 exportDOM(editor: LexicalEditor): DOMExportOutput {
870 const element = this.createDOM(editor._config, editor);
875 * Controls how the this node is serialized to JSON. This is important for
876 * copy and paste between Lexical editors sharing the same namespace. It's also important
877 * if you're serializing to JSON for persistent storage somewhere.
878 * See [Serialization & Deserialization](https://lexical.dev/docs/concepts/serialization#lexical---html).
881 exportJSON(): SerializedLexicalNode {
882 invariant(false, 'exportJSON: base method not extended');
886 * Controls how the this node is deserialized from JSON. This is usually boilerplate,
887 * but provides an abstraction between the node implementation and serialized interface that can
888 * be important if you ever make breaking changes to a node schema (by adding or removing properties).
889 * See [Serialization & Deserialization](https://lexical.dev/docs/concepts/serialization#lexical---html).
892 static importJSON(_serializedNode: SerializedLexicalNode): LexicalNode {
895 'LexicalNode: Node %s does not implement .importJSON().',
902 * Registers the returned function as a transform on the node during
903 * Editor initialization. Most such use cases should be addressed via
904 * the {@link LexicalEditor.registerNodeTransform} API.
906 * Experimental - use at your own risk.
908 static transform(): ((node: LexicalNode) => void) | null {
912 // Setters and mutators
915 * Removes this LexicalNode from the EditorState. If the node isn't re-inserted
916 * somewhere, the Lexical garbage collector will eventually clean it up.
918 * @param preserveEmptyParent - If falsy, the node's parent will be removed if
919 * it's empty after the removal operation. This is the default behavior, subject to
920 * other node heuristics such as {@link ElementNode#canBeEmpty}
922 remove(preserveEmptyParent?: boolean): void {
923 $removeNode(this, true, preserveEmptyParent);
927 * Replaces this LexicalNode with the provided node, optionally transferring the children
928 * of the replaced node to the replacing node.
930 * @param replaceWith - The node to replace this one with.
931 * @param includeChildren - Whether or not to transfer the children of this node to the replacing node.
933 replace<N extends LexicalNode>(replaceWith: N, includeChildren?: boolean): N {
935 let selection = $getSelection();
936 if (selection !== null) {
937 selection = selection.clone();
939 errorOnInsertTextNodeOnRoot(this, replaceWith);
940 const self = this.getLatest();
941 const toReplaceKey = this.__key;
942 const key = replaceWith.__key;
943 const writableReplaceWith = replaceWith.getWritable();
944 const writableParent = this.getParentOrThrow().getWritable();
945 const size = writableParent.__size;
946 removeFromParent(writableReplaceWith);
947 const prevSibling = self.getPreviousSibling();
948 const nextSibling = self.getNextSibling();
949 const prevKey = self.__prev;
950 const nextKey = self.__next;
951 const parentKey = self.__parent;
952 $removeNode(self, false, true);
954 if (prevSibling === null) {
955 writableParent.__first = key;
957 const writablePrevSibling = prevSibling.getWritable();
958 writablePrevSibling.__next = key;
960 writableReplaceWith.__prev = prevKey;
961 if (nextSibling === null) {
962 writableParent.__last = key;
964 const writableNextSibling = nextSibling.getWritable();
965 writableNextSibling.__prev = key;
967 writableReplaceWith.__next = nextKey;
968 writableReplaceWith.__parent = parentKey;
969 writableParent.__size = size;
970 if (includeChildren) {
972 $isElementNode(this) && $isElementNode(writableReplaceWith),
973 'includeChildren should only be true for ElementNodes',
975 this.getChildren().forEach((child: LexicalNode) => {
976 writableReplaceWith.append(child);
979 if ($isRangeSelection(selection)) {
980 $setSelection(selection);
981 const anchor = selection.anchor;
982 const focus = selection.focus;
983 if (anchor.key === toReplaceKey) {
984 $moveSelectionPointToEnd(anchor, writableReplaceWith);
986 if (focus.key === toReplaceKey) {
987 $moveSelectionPointToEnd(focus, writableReplaceWith);
990 if ($getCompositionKey() === toReplaceKey) {
991 $setCompositionKey(key);
993 return writableReplaceWith;
997 * Inserts a node after this LexicalNode (as the next sibling).
999 * @param nodeToInsert - The node to insert after this one.
1000 * @param restoreSelection - Whether or not to attempt to resolve the
1001 * selection to the appropriate place after the operation is complete.
1003 insertAfter(nodeToInsert: LexicalNode, restoreSelection = true): LexicalNode {
1005 errorOnInsertTextNodeOnRoot(this, nodeToInsert);
1006 const writableSelf = this.getWritable();
1007 const writableNodeToInsert = nodeToInsert.getWritable();
1008 const oldParent = writableNodeToInsert.getParent();
1009 const selection = $getSelection();
1010 let elementAnchorSelectionOnNode = false;
1011 let elementFocusSelectionOnNode = false;
1012 if (oldParent !== null) {
1013 // TODO: this is O(n), can we improve?
1014 const oldIndex = nodeToInsert.getIndexWithinParent();
1015 removeFromParent(writableNodeToInsert);
1016 if ($isRangeSelection(selection)) {
1017 const oldParentKey = oldParent.__key;
1018 const anchor = selection.anchor;
1019 const focus = selection.focus;
1020 elementAnchorSelectionOnNode =
1021 anchor.type === 'element' &&
1022 anchor.key === oldParentKey &&
1023 anchor.offset === oldIndex + 1;
1024 elementFocusSelectionOnNode =
1025 focus.type === 'element' &&
1026 focus.key === oldParentKey &&
1027 focus.offset === oldIndex + 1;
1030 const nextSibling = this.getNextSibling();
1031 const writableParent = this.getParentOrThrow().getWritable();
1032 const insertKey = writableNodeToInsert.__key;
1033 const nextKey = writableSelf.__next;
1034 if (nextSibling === null) {
1035 writableParent.__last = insertKey;
1037 const writableNextSibling = nextSibling.getWritable();
1038 writableNextSibling.__prev = insertKey;
1040 writableParent.__size++;
1041 writableSelf.__next = insertKey;
1042 writableNodeToInsert.__next = nextKey;
1043 writableNodeToInsert.__prev = writableSelf.__key;
1044 writableNodeToInsert.__parent = writableSelf.__parent;
1045 if (restoreSelection && $isRangeSelection(selection)) {
1046 const index = this.getIndexWithinParent();
1047 $updateElementSelectionOnCreateDeleteNode(
1052 const writableParentKey = writableParent.__key;
1053 if (elementAnchorSelectionOnNode) {
1054 selection.anchor.set(writableParentKey, index + 2, 'element');
1056 if (elementFocusSelectionOnNode) {
1057 selection.focus.set(writableParentKey, index + 2, 'element');
1060 return nodeToInsert;
1064 * Inserts a node before this LexicalNode (as the previous sibling).
1066 * @param nodeToInsert - The node to insert before this one.
1067 * @param restoreSelection - Whether or not to attempt to resolve the
1068 * selection to the appropriate place after the operation is complete.
1071 nodeToInsert: LexicalNode,
1072 restoreSelection = true,
1075 errorOnInsertTextNodeOnRoot(this, nodeToInsert);
1076 const writableSelf = this.getWritable();
1077 const writableNodeToInsert = nodeToInsert.getWritable();
1078 const insertKey = writableNodeToInsert.__key;
1079 removeFromParent(writableNodeToInsert);
1080 const prevSibling = this.getPreviousSibling();
1081 const writableParent = this.getParentOrThrow().getWritable();
1082 const prevKey = writableSelf.__prev;
1083 // TODO: this is O(n), can we improve?
1084 const index = this.getIndexWithinParent();
1085 if (prevSibling === null) {
1086 writableParent.__first = insertKey;
1088 const writablePrevSibling = prevSibling.getWritable();
1089 writablePrevSibling.__next = insertKey;
1091 writableParent.__size++;
1092 writableSelf.__prev = insertKey;
1093 writableNodeToInsert.__prev = prevKey;
1094 writableNodeToInsert.__next = writableSelf.__key;
1095 writableNodeToInsert.__parent = writableSelf.__parent;
1096 const selection = $getSelection();
1097 if (restoreSelection && $isRangeSelection(selection)) {
1098 const parent = this.getParentOrThrow();
1099 $updateElementSelectionOnCreateDeleteNode(selection, parent, index);
1101 return nodeToInsert;
1105 * Whether or not this node has a required parent. Used during copy + paste operations
1106 * to normalize nodes that would otherwise be orphaned. For example, ListItemNodes without
1107 * a ListNode parent or TextNodes with a ParagraphNode parent.
1110 isParentRequired(): boolean {
1115 * The creation logic for any required parent. Should be implemented if {@link isParentRequired} returns true.
1118 createParentElementNode(): ElementNode {
1119 return $createParagraphNode();
1122 selectStart(): RangeSelection {
1123 return this.selectPrevious();
1126 selectEnd(): RangeSelection {
1127 return this.selectNext(0, 0);
1131 * Moves selection to the previous sibling of this node, at the specified offsets.
1133 * @param anchorOffset - The anchor offset for selection.
1134 * @param focusOffset - The focus offset for selection
1136 selectPrevious(anchorOffset?: number, focusOffset?: number): RangeSelection {
1138 const prevSibling = this.getPreviousSibling();
1139 const parent = this.getParentOrThrow();
1140 if (prevSibling === null) {
1141 return parent.select(0, 0);
1143 if ($isElementNode(prevSibling)) {
1144 return prevSibling.select();
1145 } else if (!$isTextNode(prevSibling)) {
1146 const index = prevSibling.getIndexWithinParent() + 1;
1147 return parent.select(index, index);
1149 return prevSibling.select(anchorOffset, focusOffset);
1153 * Moves selection to the next sibling of this node, at the specified offsets.
1155 * @param anchorOffset - The anchor offset for selection.
1156 * @param focusOffset - The focus offset for selection
1158 selectNext(anchorOffset?: number, focusOffset?: number): RangeSelection {
1160 const nextSibling = this.getNextSibling();
1161 const parent = this.getParentOrThrow();
1162 if (nextSibling === null) {
1163 return parent.select();
1165 if ($isElementNode(nextSibling)) {
1166 return nextSibling.select(0, 0);
1167 } else if (!$isTextNode(nextSibling)) {
1168 const index = nextSibling.getIndexWithinParent();
1169 return parent.select(index, index);
1171 return nextSibling.select(anchorOffset, focusOffset);
1175 * Marks a node dirty, triggering transforms and
1176 * forcing it to be reconciled during the update cycle.
1184 * Insert the DOM of this node into that of the parent.
1185 * Allows this node to implement custom DOM attachment logic.
1186 * Boolean result indicates if the insertion was handled by the function.
1187 * A true return value prevents default insertion logic from taking place.
1189 insertDOMIntoParent(nodeDOM: HTMLElement, parentDOM: HTMLElement): boolean {
1194 function errorOnTypeKlassMismatch(
1196 klass: Klass<LexicalNode>,
1198 const registeredNode = getActiveEditor()._nodes.get(type);
1199 // Common error - split in its own invariant
1200 if (registeredNode === undefined) {
1203 'Create node: Attempted to create node %s that was not configured to be used on the editor.',
1207 const editorKlass = registeredNode.klass;
1208 if (editorKlass !== klass) {
1211 'Create node: Type %s in node %s does not match registered node %s with the same type',
1220 * Insert a series of nodes after this LexicalNode (as next siblings)
1222 * @param firstToInsert - The first node to insert after this one.
1223 * @param lastToInsert - The last node to insert after this one. Must be a
1224 * later sibling of FirstNode. If not provided, it will be its last sibling.
1226 export function insertRangeAfter(
1228 firstToInsert: LexicalNode,
1229 lastToInsert?: LexicalNode,
1231 const lastToInsert2 =
1232 lastToInsert || firstToInsert.getParentOrThrow().getLastChild()!;
1233 let current = firstToInsert;
1234 const nodesToInsert = [firstToInsert];
1235 while (current !== lastToInsert2) {
1236 if (!current.getNextSibling()) {
1239 'insertRangeAfter: lastToInsert must be a later sibling of firstToInsert',
1242 current = current.getNextSibling()!;
1243 nodesToInsert.push(current);
1246 let currentNode: LexicalNode = node;
1247 for (const nodeToInsert of nodesToInsert) {
1248 currentNode = currentNode.insertAfter(nodeToInsert);