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 export type DOMConversionOutput = {
151 after?: (childLexicalNodes: Array<LexicalNode>) => Array<LexicalNode>;
152 forChild?: DOMChildConversion;
153 node: null | LexicalNode | Array<LexicalNode> | 'ignore';
156 export type DOMExportOutputMap = Map<
158 (editor: LexicalEditor, target: LexicalNode) => DOMExportOutput
161 export type DOMExportOutput = {
163 generatedElement: HTMLElement | Text | null | undefined,
164 ) => HTMLElement | Text | null | undefined;
165 element: HTMLElement | Text | null;
168 export type NodeKey = string;
170 export class LexicalNode {
171 // Allow us to look up the type including static props
172 ['constructor']!: KlassConstructor<typeof LexicalNode>;
176 //@ts-ignore We set the key in the constructor.
179 __parent: null | NodeKey;
181 __prev: null | NodeKey;
183 __next: null | NodeKey;
185 // Flow doesn't support abstract classes unfortunately, so we can't _force_
186 // subclasses of Node to implement statics. All subclasses of Node should have
187 // a static getType and clone method though. We define getType and clone here so we can call it
188 // on any Node, and we throw this error by default since the subclass should provide
189 // their own implementation.
191 * Returns the string type of this node. Every node must
192 * implement this and it MUST BE UNIQUE amongst nodes registered
196 static getType(): string {
199 'LexicalNode: Node %s does not implement .getType().',
205 * Clones this node, creating a new node with a different key
206 * and adding it to the EditorState (but not attaching it anywhere!). All nodes must
207 * implement this method.
210 static clone(_data: unknown): LexicalNode {
213 'LexicalNode: Node %s does not implement .clone().',
219 * Perform any state updates on the clone of prevNode that are not already
220 * handled by the constructor call in the static clone method. If you have
221 * state to update in your clone that is not handled directly by the
222 * constructor, it is advisable to override this method but it is required
223 * to include a call to `super.afterCloneFrom(prevNode)` in your
224 * implementation. This is only intended to be called by
225 * {@link $cloneWithProperties} function or via a super call.
229 * class ClassesTextNode extends TextNode {
230 * // Not shown: static getType, static importJSON, exportJSON, createDOM, updateDOM
231 * __classes = new Set<string>();
232 * static clone(node: ClassesTextNode): ClassesTextNode {
233 * // The inherited TextNode constructor is used here, so
234 * // classes is not set by this method.
235 * return new ClassesTextNode(node.__text, node.__key);
237 * afterCloneFrom(node: this): void {
238 * // This calls TextNode.afterCloneFrom and LexicalNode.afterCloneFrom
239 * // for necessary state updates
240 * super.afterCloneFrom(node);
241 * this.__addClasses(node.__classes);
243 * // This method is a private implementation detail, it is not
244 * // suitable for the public API because it does not call getWritable
245 * __addClasses(classNames: Iterable<string>): this {
246 * for (const className of classNames) {
247 * this.__classes.add(className);
251 * addClass(...classNames: string[]): this {
252 * return this.getWritable().__addClasses(classNames);
254 * removeClass(...classNames: string[]): this {
255 * const node = this.getWritable();
256 * for (const className of classNames) {
257 * this.__classes.delete(className);
261 * getClasses(): Set<string> {
262 * return this.getLatest().__classes;
268 afterCloneFrom(prevNode: this) {
269 this.__parent = prevNode.__parent;
270 this.__next = prevNode.__next;
271 this.__prev = prevNode.__prev;
274 // eslint-disable-next-line @typescript-eslint/no-explicit-any
275 static importDOM?: () => DOMConversionMap<any> | null;
277 constructor(key?: NodeKey) {
278 this.__type = this.constructor.getType();
279 this.__parent = null;
282 $setNodeKey(this, key);
285 if (this.__type !== 'root') {
287 errorOnTypeKlassMismatch(this.__type, this.constructor);
291 // Getters and Traversers
294 * Returns the string type of this node.
300 isInline(): boolean {
303 'LexicalNode: Node %s does not implement .isInline().',
304 this.constructor.name,
309 * Returns true if there is a path between this node and the RootNode, false otherwise.
310 * This is a way of determining if the node is "attached" EditorState. Unattached nodes
311 * won't be reconciled and will ultimatelt be cleaned up by the Lexical GC.
313 isAttached(): boolean {
314 let nodeKey: string | null = this.__key;
315 while (nodeKey !== null) {
316 if (nodeKey === 'root') {
320 const node: LexicalNode | null = $getNodeByKey(nodeKey);
325 nodeKey = node.__parent;
331 * Returns true if this node is contained within the provided Selection., false otherwise.
332 * Relies on the algorithms implemented in {@link BaseSelection.getNodes} to determine
335 * @param selection - The selection that we want to determine if the node is in.
337 isSelected(selection?: null | BaseSelection): boolean {
338 const targetSelection = selection || $getSelection();
339 if (targetSelection == null) {
343 const isSelected = targetSelection
345 .some((n) => n.__key === this.__key);
347 if ($isTextNode(this)) {
350 // For inline images inside of element nodes.
351 // Without this change the image will be selected if the cursor is before or after it.
352 const isElementRangeSelection =
353 $isRangeSelection(targetSelection) &&
354 targetSelection.anchor.type === 'element' &&
355 targetSelection.focus.type === 'element';
357 if (isElementRangeSelection) {
358 if (targetSelection.isCollapsed()) {
362 const parentNode = this.getParent();
363 if ($isDecoratorNode(this) && this.isInline() && parentNode) {
364 const firstPoint = targetSelection.isBackward()
365 ? targetSelection.focus
366 : targetSelection.anchor;
367 const firstElement = firstPoint.getNode() as ElementNode;
369 firstPoint.offset === firstElement.getChildrenSize() &&
370 firstElement.is(parentNode) &&
371 firstElement.getLastChildOrThrow().is(this)
381 * Returns this nodes key.
384 // Key is stable between copies
389 * Returns the zero-based index of this node within the parent.
391 getIndexWithinParent(): number {
392 const parent = this.getParent();
393 if (parent === null) {
396 let node = parent.getFirstChild();
398 while (node !== null) {
403 node = node.getNextSibling();
409 * Returns the parent of this node, or null if none is found.
411 getParent<T extends ElementNode>(): T | null {
412 const parent = this.getLatest().__parent;
413 if (parent === null) {
416 return $getNodeByKey<T>(parent);
420 * Returns the parent of this node, or throws if none is found.
422 getParentOrThrow<T extends ElementNode>(): T {
423 const parent = this.getParent<T>();
424 if (parent === null) {
425 invariant(false, 'Expected node %s to have a parent.', this.__key);
431 * Returns the highest (in the EditorState tree)
432 * non-root ancestor of this node, or null if none is found. See {@link lexical!$isRootOrShadowRoot}
433 * for more information on which Elements comprise "roots".
435 getTopLevelElement(): ElementNode | DecoratorNode<unknown> | null {
436 let node: ElementNode | this | null = this;
437 while (node !== null) {
438 const parent: ElementNode | null = node.getParent();
439 if ($isRootOrShadowRoot(parent)) {
441 $isElementNode(node) || (node === this && $isDecoratorNode(node)),
442 'Children of root nodes must be elements or decorators',
452 * Returns the highest (in the EditorState tree)
453 * non-root ancestor of this node, or throws if none is found. See {@link lexical!$isRootOrShadowRoot}
454 * for more information on which Elements comprise "roots".
456 getTopLevelElementOrThrow(): ElementNode | DecoratorNode<unknown> {
457 const parent = this.getTopLevelElement();
458 if (parent === null) {
461 'Expected node %s to have a top parent element.',
469 * Returns a list of the every ancestor of this node,
470 * all the way up to the RootNode.
473 getParents(): Array<ElementNode> {
474 const parents: Array<ElementNode> = [];
475 let node = this.getParent();
476 while (node !== null) {
478 node = node.getParent();
484 * Returns a list of the keys of every ancestor of this node,
485 * all the way up to the RootNode.
488 getParentKeys(): Array<NodeKey> {
490 let node = this.getParent();
491 while (node !== null) {
492 parents.push(node.__key);
493 node = node.getParent();
499 * Returns the "previous" siblings - that is, the node that comes
500 * before this one in the same parent.
503 getPreviousSibling<T extends LexicalNode>(): T | null {
504 const self = this.getLatest();
505 const prevKey = self.__prev;
506 return prevKey === null ? null : $getNodeByKey<T>(prevKey);
510 * Returns the "previous" siblings - that is, the nodes that come between
511 * this one and the first child of it's parent, inclusive.
514 getPreviousSiblings<T extends LexicalNode>(): Array<T> {
515 const siblings: Array<T> = [];
516 const parent = this.getParent();
517 if (parent === null) {
520 let node: null | T = parent.getFirstChild();
521 while (node !== null) {
526 node = node.getNextSibling();
532 * Returns the "next" siblings - that is, the node that comes
533 * after this one in the same parent
536 getNextSibling<T extends LexicalNode>(): T | null {
537 const self = this.getLatest();
538 const nextKey = self.__next;
539 return nextKey === null ? null : $getNodeByKey<T>(nextKey);
543 * Returns all "next" siblings - that is, the nodes that come between this
544 * one and the last child of it's parent, inclusive.
547 getNextSiblings<T extends LexicalNode>(): Array<T> {
548 const siblings: Array<T> = [];
549 let node: null | T = this.getNextSibling();
550 while (node !== null) {
552 node = node.getNextSibling();
558 * Returns the closest common ancestor of this node and the provided one or null
559 * if one cannot be found.
561 * @param node - the other node to find the common ancestor of.
563 getCommonAncestor<T extends ElementNode = ElementNode>(
566 const a = this.getParents();
567 const b = node.getParents();
568 if ($isElementNode(this)) {
571 if ($isElementNode(node)) {
574 const aLength = a.length;
575 const bLength = b.length;
576 if (aLength === 0 || bLength === 0 || a[aLength - 1] !== b[bLength - 1]) {
579 const bSet = new Set(b);
580 for (let i = 0; i < aLength; i++) {
581 const ancestor = a[i] as T;
582 if (bSet.has(ancestor)) {
590 * Returns true if the provided node is the exact same one as this node, from Lexical's perspective.
591 * Always use this instead of referential equality.
593 * @param object - the node to perform the equality comparison on.
595 is(object: LexicalNode | null | undefined): boolean {
596 if (object == null) {
599 return this.__key === object.__key;
603 * Returns true if this node logical precedes the target node in the editor state.
605 * @param targetNode - the node we're testing to see if it's after this one.
607 isBefore(targetNode: LexicalNode): boolean {
608 if (this === targetNode) {
611 if (targetNode.isParentOf(this)) {
614 if (this.isParentOf(targetNode)) {
617 const commonAncestor = this.getCommonAncestor(targetNode);
620 let node: this | ElementNode | LexicalNode = this;
622 const parent: ElementNode = node.getParentOrThrow();
623 if (parent === commonAncestor) {
624 indexA = node.getIndexWithinParent();
631 const parent: ElementNode = node.getParentOrThrow();
632 if (parent === commonAncestor) {
633 indexB = node.getIndexWithinParent();
638 return indexA < indexB;
642 * Returns true if this node is the parent of the target node, false otherwise.
644 * @param targetNode - the would-be child node.
646 isParentOf(targetNode: LexicalNode): boolean {
647 const key = this.__key;
648 if (key === targetNode.__key) {
651 let node: ElementNode | LexicalNode | null = targetNode;
652 while (node !== null) {
653 if (node.__key === key) {
656 node = node.getParent();
661 // TO-DO: this function can be simplified a lot
663 * Returns a list of nodes that are between this node and
664 * the target node in the EditorState.
666 * @param targetNode - the node that marks the other end of the range of nodes to be returned.
668 getNodesBetween(targetNode: LexicalNode): Array<LexicalNode> {
669 const isBefore = this.isBefore(targetNode);
671 const visited = new Set();
672 let node: LexicalNode | this | null = this;
677 const key = node.__key;
678 if (!visited.has(key)) {
682 if (node === targetNode) {
685 const child: LexicalNode | null = $isElementNode(node)
687 ? node.getFirstChild()
688 : node.getLastChild()
690 if (child !== null) {
694 const nextSibling: LexicalNode | null = isBefore
695 ? node.getNextSibling()
696 : node.getPreviousSibling();
697 if (nextSibling !== null) {
701 const parent: LexicalNode | null = node.getParentOrThrow();
702 if (!visited.has(parent.__key)) {
705 if (parent === targetNode) {
708 let parentSibling = null;
709 let ancestor: LexicalNode | null = parent;
711 if (ancestor === null) {
712 invariant(false, 'getNodesBetween: ancestor is null');
714 parentSibling = isBefore
715 ? ancestor.getNextSibling()
716 : ancestor.getPreviousSibling();
717 ancestor = ancestor.getParent();
718 if (ancestor !== null) {
719 if (parentSibling === null && !visited.has(ancestor.__key)) {
720 nodes.push(ancestor);
725 } while (parentSibling === null);
726 node = parentSibling;
735 * Returns true if this node has been marked dirty during this update cycle.
739 const editor = getActiveEditor();
740 const dirtyLeaves = editor._dirtyLeaves;
741 return dirtyLeaves !== null && dirtyLeaves.has(this.__key);
745 * Returns the latest version of the node from the active EditorState.
746 * This is used to avoid getting values from stale node references.
750 const latest = $getNodeByKey<this>(this.__key);
751 if (latest === null) {
754 'Lexical node does not exist in active editor state. Avoid using the same node references between nested closures from editorState.read/editor.update.',
761 * Returns a mutable version of the node using {@link $cloneWithProperties}
762 * if necessary. Will throw an error if called outside of a Lexical Editor
763 * {@link LexicalEditor.update} callback.
766 getWritable(): this {
768 const editorState = getActiveEditorState();
769 const editor = getActiveEditor();
770 const nodeMap = editorState._nodeMap;
771 const key = this.__key;
772 // Ensure we get the latest node from pending state
773 const latestNode = this.getLatest();
774 const cloneNotNeeded = editor._cloneNotNeeded;
775 const selection = $getSelection();
776 if (selection !== null) {
777 selection.setCachedNodes(null);
779 if (cloneNotNeeded.has(key)) {
780 // Transforms clear the dirty node set on each iteration to keep track on newly dirty nodes
781 internalMarkNodeAsDirty(latestNode);
784 const mutableNode = $cloneWithProperties(latestNode);
785 cloneNotNeeded.add(key);
786 internalMarkNodeAsDirty(mutableNode);
787 // Update reference in node map
788 nodeMap.set(key, mutableNode);
794 * Returns the text content of the node. Override this for
795 * custom nodes that should have a representation in plain text
796 * format (for copy + paste, for example)
799 getTextContent(): string {
804 * Returns the length of the string produced by calling getTextContent on this node.
807 getTextContentSize(): number {
808 return this.getTextContent().length;
814 * Called during the reconciliation process to determine which nodes
815 * to insert into the DOM for this Lexical Node.
817 * This method must return exactly one HTMLElement. Nested elements are not supported.
819 * Do not attempt to update the Lexical EditorState during this phase of the update lifecyle.
821 * @param _config - allows access to things like the EditorTheme (to apply classes) during reconciliation.
822 * @param _editor - allows access to the editor for context during reconciliation.
825 createDOM(_config: EditorConfig, _editor: LexicalEditor): HTMLElement {
826 invariant(false, 'createDOM: base method not extended');
830 * Called when a node changes and should update the DOM
831 * in whatever way is necessary to make it align with any changes that might
832 * have happened during the update.
834 * Returning "true" here will cause lexical to unmount and recreate the DOM node
835 * (by calling createDOM). You would need to do this if the element tag changes,
842 _config: EditorConfig,
844 invariant(false, 'updateDOM: base method not extended');
848 * Controls how the this node is serialized to HTML. This is important for
849 * copy and paste between Lexical and non-Lexical editors, or Lexical editors with different namespaces,
850 * in which case the primary transfer format is HTML. It's also important if you're serializing
851 * to HTML for any other reason via {@link @lexical/html!$generateHtmlFromNodes}. You could
852 * also use this method to build your own HTML renderer.
855 exportDOM(editor: LexicalEditor): DOMExportOutput {
856 const element = this.createDOM(editor._config, editor);
861 * Controls how the this node is serialized to JSON. This is important for
862 * copy and paste between Lexical editors sharing the same namespace. It's also important
863 * if you're serializing to JSON for persistent storage somewhere.
864 * See [Serialization & Deserialization](https://lexical.dev/docs/concepts/serialization#lexical---html).
867 exportJSON(): SerializedLexicalNode {
868 invariant(false, 'exportJSON: base method not extended');
872 * Controls how the this node is deserialized from JSON. This is usually boilerplate,
873 * but provides an abstraction between the node implementation and serialized interface that can
874 * be important if you ever make breaking changes to a node schema (by adding or removing properties).
875 * See [Serialization & Deserialization](https://lexical.dev/docs/concepts/serialization#lexical---html).
878 static importJSON(_serializedNode: SerializedLexicalNode): LexicalNode {
881 'LexicalNode: Node %s does not implement .importJSON().',
888 * Registers the returned function as a transform on the node during
889 * Editor initialization. Most such use cases should be addressed via
890 * the {@link LexicalEditor.registerNodeTransform} API.
892 * Experimental - use at your own risk.
894 static transform(): ((node: LexicalNode) => void) | null {
898 // Setters and mutators
901 * Removes this LexicalNode from the EditorState. If the node isn't re-inserted
902 * somewhere, the Lexical garbage collector will eventually clean it up.
904 * @param preserveEmptyParent - If falsy, the node's parent will be removed if
905 * it's empty after the removal operation. This is the default behavior, subject to
906 * other node heuristics such as {@link ElementNode#canBeEmpty}
908 remove(preserveEmptyParent?: boolean): void {
909 $removeNode(this, true, preserveEmptyParent);
913 * Replaces this LexicalNode with the provided node, optionally transferring the children
914 * of the replaced node to the replacing node.
916 * @param replaceWith - The node to replace this one with.
917 * @param includeChildren - Whether or not to transfer the children of this node to the replacing node.
919 replace<N extends LexicalNode>(replaceWith: N, includeChildren?: boolean): N {
921 let selection = $getSelection();
922 if (selection !== null) {
923 selection = selection.clone();
925 errorOnInsertTextNodeOnRoot(this, replaceWith);
926 const self = this.getLatest();
927 const toReplaceKey = this.__key;
928 const key = replaceWith.__key;
929 const writableReplaceWith = replaceWith.getWritable();
930 const writableParent = this.getParentOrThrow().getWritable();
931 const size = writableParent.__size;
932 removeFromParent(writableReplaceWith);
933 const prevSibling = self.getPreviousSibling();
934 const nextSibling = self.getNextSibling();
935 const prevKey = self.__prev;
936 const nextKey = self.__next;
937 const parentKey = self.__parent;
938 $removeNode(self, false, true);
940 if (prevSibling === null) {
941 writableParent.__first = key;
943 const writablePrevSibling = prevSibling.getWritable();
944 writablePrevSibling.__next = key;
946 writableReplaceWith.__prev = prevKey;
947 if (nextSibling === null) {
948 writableParent.__last = key;
950 const writableNextSibling = nextSibling.getWritable();
951 writableNextSibling.__prev = key;
953 writableReplaceWith.__next = nextKey;
954 writableReplaceWith.__parent = parentKey;
955 writableParent.__size = size;
956 if (includeChildren) {
958 $isElementNode(this) && $isElementNode(writableReplaceWith),
959 'includeChildren should only be true for ElementNodes',
961 this.getChildren().forEach((child: LexicalNode) => {
962 writableReplaceWith.append(child);
965 if ($isRangeSelection(selection)) {
966 $setSelection(selection);
967 const anchor = selection.anchor;
968 const focus = selection.focus;
969 if (anchor.key === toReplaceKey) {
970 $moveSelectionPointToEnd(anchor, writableReplaceWith);
972 if (focus.key === toReplaceKey) {
973 $moveSelectionPointToEnd(focus, writableReplaceWith);
976 if ($getCompositionKey() === toReplaceKey) {
977 $setCompositionKey(key);
979 return writableReplaceWith;
983 * Inserts a node after this LexicalNode (as the next sibling).
985 * @param nodeToInsert - The node to insert after this one.
986 * @param restoreSelection - Whether or not to attempt to resolve the
987 * selection to the appropriate place after the operation is complete.
989 insertAfter(nodeToInsert: LexicalNode, restoreSelection = true): LexicalNode {
991 errorOnInsertTextNodeOnRoot(this, nodeToInsert);
992 const writableSelf = this.getWritable();
993 const writableNodeToInsert = nodeToInsert.getWritable();
994 const oldParent = writableNodeToInsert.getParent();
995 const selection = $getSelection();
996 let elementAnchorSelectionOnNode = false;
997 let elementFocusSelectionOnNode = false;
998 if (oldParent !== null) {
999 // TODO: this is O(n), can we improve?
1000 const oldIndex = nodeToInsert.getIndexWithinParent();
1001 removeFromParent(writableNodeToInsert);
1002 if ($isRangeSelection(selection)) {
1003 const oldParentKey = oldParent.__key;
1004 const anchor = selection.anchor;
1005 const focus = selection.focus;
1006 elementAnchorSelectionOnNode =
1007 anchor.type === 'element' &&
1008 anchor.key === oldParentKey &&
1009 anchor.offset === oldIndex + 1;
1010 elementFocusSelectionOnNode =
1011 focus.type === 'element' &&
1012 focus.key === oldParentKey &&
1013 focus.offset === oldIndex + 1;
1016 const nextSibling = this.getNextSibling();
1017 const writableParent = this.getParentOrThrow().getWritable();
1018 const insertKey = writableNodeToInsert.__key;
1019 const nextKey = writableSelf.__next;
1020 if (nextSibling === null) {
1021 writableParent.__last = insertKey;
1023 const writableNextSibling = nextSibling.getWritable();
1024 writableNextSibling.__prev = insertKey;
1026 writableParent.__size++;
1027 writableSelf.__next = insertKey;
1028 writableNodeToInsert.__next = nextKey;
1029 writableNodeToInsert.__prev = writableSelf.__key;
1030 writableNodeToInsert.__parent = writableSelf.__parent;
1031 if (restoreSelection && $isRangeSelection(selection)) {
1032 const index = this.getIndexWithinParent();
1033 $updateElementSelectionOnCreateDeleteNode(
1038 const writableParentKey = writableParent.__key;
1039 if (elementAnchorSelectionOnNode) {
1040 selection.anchor.set(writableParentKey, index + 2, 'element');
1042 if (elementFocusSelectionOnNode) {
1043 selection.focus.set(writableParentKey, index + 2, 'element');
1046 return nodeToInsert;
1050 * Inserts a node before this LexicalNode (as the previous sibling).
1052 * @param nodeToInsert - The node to insert before this one.
1053 * @param restoreSelection - Whether or not to attempt to resolve the
1054 * selection to the appropriate place after the operation is complete.
1057 nodeToInsert: LexicalNode,
1058 restoreSelection = true,
1061 errorOnInsertTextNodeOnRoot(this, nodeToInsert);
1062 const writableSelf = this.getWritable();
1063 const writableNodeToInsert = nodeToInsert.getWritable();
1064 const insertKey = writableNodeToInsert.__key;
1065 removeFromParent(writableNodeToInsert);
1066 const prevSibling = this.getPreviousSibling();
1067 const writableParent = this.getParentOrThrow().getWritable();
1068 const prevKey = writableSelf.__prev;
1069 // TODO: this is O(n), can we improve?
1070 const index = this.getIndexWithinParent();
1071 if (prevSibling === null) {
1072 writableParent.__first = insertKey;
1074 const writablePrevSibling = prevSibling.getWritable();
1075 writablePrevSibling.__next = insertKey;
1077 writableParent.__size++;
1078 writableSelf.__prev = insertKey;
1079 writableNodeToInsert.__prev = prevKey;
1080 writableNodeToInsert.__next = writableSelf.__key;
1081 writableNodeToInsert.__parent = writableSelf.__parent;
1082 const selection = $getSelection();
1083 if (restoreSelection && $isRangeSelection(selection)) {
1084 const parent = this.getParentOrThrow();
1085 $updateElementSelectionOnCreateDeleteNode(selection, parent, index);
1087 return nodeToInsert;
1091 * Whether or not this node has a required parent. Used during copy + paste operations
1092 * to normalize nodes that would otherwise be orphaned. For example, ListItemNodes without
1093 * a ListNode parent or TextNodes with a ParagraphNode parent.
1096 isParentRequired(): boolean {
1101 * The creation logic for any required parent. Should be implemented if {@link isParentRequired} returns true.
1104 createParentElementNode(): ElementNode {
1105 return $createParagraphNode();
1108 selectStart(): RangeSelection {
1109 return this.selectPrevious();
1112 selectEnd(): RangeSelection {
1113 return this.selectNext(0, 0);
1117 * Moves selection to the previous sibling of this node, at the specified offsets.
1119 * @param anchorOffset - The anchor offset for selection.
1120 * @param focusOffset - The focus offset for selection
1122 selectPrevious(anchorOffset?: number, focusOffset?: number): RangeSelection {
1124 const prevSibling = this.getPreviousSibling();
1125 const parent = this.getParentOrThrow();
1126 if (prevSibling === null) {
1127 return parent.select(0, 0);
1129 if ($isElementNode(prevSibling)) {
1130 return prevSibling.select();
1131 } else if (!$isTextNode(prevSibling)) {
1132 const index = prevSibling.getIndexWithinParent() + 1;
1133 return parent.select(index, index);
1135 return prevSibling.select(anchorOffset, focusOffset);
1139 * Moves selection to the next sibling of this node, at the specified offsets.
1141 * @param anchorOffset - The anchor offset for selection.
1142 * @param focusOffset - The focus offset for selection
1144 selectNext(anchorOffset?: number, focusOffset?: number): RangeSelection {
1146 const nextSibling = this.getNextSibling();
1147 const parent = this.getParentOrThrow();
1148 if (nextSibling === null) {
1149 return parent.select();
1151 if ($isElementNode(nextSibling)) {
1152 return nextSibling.select(0, 0);
1153 } else if (!$isTextNode(nextSibling)) {
1154 const index = nextSibling.getIndexWithinParent();
1155 return parent.select(index, index);
1157 return nextSibling.select(anchorOffset, focusOffset);
1161 * Marks a node dirty, triggering transforms and
1162 * forcing it to be reconciled during the update cycle.
1170 function errorOnTypeKlassMismatch(
1172 klass: Klass<LexicalNode>,
1174 const registeredNode = getActiveEditor()._nodes.get(type);
1175 // Common error - split in its own invariant
1176 if (registeredNode === undefined) {
1179 'Create node: Attempted to create node %s that was not configured to be used on the editor.',
1183 const editorKlass = registeredNode.klass;
1184 if (editorKlass !== klass) {
1187 'Create node: Type %s in node %s does not match registered node %s with the same type',
1196 * Insert a series of nodes after this LexicalNode (as next siblings)
1198 * @param firstToInsert - The first node to insert after this one.
1199 * @param lastToInsert - The last node to insert after this one. Must be a
1200 * later sibling of FirstNode. If not provided, it will be its last sibling.
1202 export function insertRangeAfter(
1204 firstToInsert: LexicalNode,
1205 lastToInsert?: LexicalNode,
1207 const lastToInsert2 =
1208 lastToInsert || firstToInsert.getParentOrThrow().getLastChild()!;
1209 let current = firstToInsert;
1210 const nodesToInsert = [firstToInsert];
1211 while (current !== lastToInsert2) {
1212 if (!current.getNextSibling()) {
1215 'insertRangeAfter: lastToInsert must be a later sibling of firstToInsert',
1218 current = current.getNextSibling()!;
1219 nodesToInsert.push(current);
1222 let currentNode: LexicalNode = node;
1223 for (const nodeToInsert of nodesToInsert) {
1224 currentNode = currentNode.insertAfter(nodeToInsert);