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 ['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 * Returns this nodes key.
390 // Key is stable between copies
395 * Returns the zero-based index of this node within the parent.
397 getIndexWithinParent(): number {
398 const parent = this.getParent();
399 if (parent === null) {
402 let node = parent.getFirstChild();
404 while (node !== null) {
409 node = node.getNextSibling();
415 * Returns the parent of this node, or null if none is found.
417 getParent<T extends ElementNode>(): T | null {
418 const parent = this.getLatest().__parent;
419 if (parent === null) {
422 return $getNodeByKey<T>(parent);
426 * Returns the parent of this node, or throws if none is found.
428 getParentOrThrow<T extends ElementNode>(): T {
429 const parent = this.getParent<T>();
430 if (parent === null) {
431 invariant(false, 'Expected node %s to have a parent.', this.__key);
437 * Returns the highest (in the EditorState tree)
438 * non-root ancestor of this node, or null if none is found. See {@link lexical!$isRootOrShadowRoot}
439 * for more information on which Elements comprise "roots".
441 getTopLevelElement(): ElementNode | DecoratorNode<unknown> | null {
442 let node: ElementNode | this | null = this;
443 while (node !== null) {
444 const parent: ElementNode | null = node.getParent();
445 if ($isRootOrShadowRoot(parent)) {
447 $isElementNode(node) || (node === this && $isDecoratorNode(node)),
448 'Children of root nodes must be elements or decorators',
458 * Returns the highest (in the EditorState tree)
459 * non-root ancestor of this node, or throws if none is found. See {@link lexical!$isRootOrShadowRoot}
460 * for more information on which Elements comprise "roots".
462 getTopLevelElementOrThrow(): ElementNode | DecoratorNode<unknown> {
463 const parent = this.getTopLevelElement();
464 if (parent === null) {
467 'Expected node %s to have a top parent element.',
475 * Returns a list of the every ancestor of this node,
476 * all the way up to the RootNode.
479 getParents(): Array<ElementNode> {
480 const parents: Array<ElementNode> = [];
481 let node = this.getParent();
482 while (node !== null) {
484 node = node.getParent();
490 * Returns a list of the keys of every ancestor of this node,
491 * all the way up to the RootNode.
494 getParentKeys(): Array<NodeKey> {
496 let node = this.getParent();
497 while (node !== null) {
498 parents.push(node.__key);
499 node = node.getParent();
505 * Returns the "previous" siblings - that is, the node that comes
506 * before this one in the same parent.
509 getPreviousSibling<T extends LexicalNode>(): T | null {
510 const self = this.getLatest();
511 const prevKey = self.__prev;
512 return prevKey === null ? null : $getNodeByKey<T>(prevKey);
516 * Returns the "previous" siblings - that is, the nodes that come between
517 * this one and the first child of it's parent, inclusive.
520 getPreviousSiblings<T extends LexicalNode>(): Array<T> {
521 const siblings: Array<T> = [];
522 const parent = this.getParent();
523 if (parent === null) {
526 let node: null | T = parent.getFirstChild();
527 while (node !== null) {
532 node = node.getNextSibling();
538 * Returns the "next" siblings - that is, the node that comes
539 * after this one in the same parent
542 getNextSibling<T extends LexicalNode>(): T | null {
543 const self = this.getLatest();
544 const nextKey = self.__next;
545 return nextKey === null ? null : $getNodeByKey<T>(nextKey);
549 * Returns all "next" siblings - that is, the nodes that come between this
550 * one and the last child of it's parent, inclusive.
553 getNextSiblings<T extends LexicalNode>(): Array<T> {
554 const siblings: Array<T> = [];
555 let node: null | T = this.getNextSibling();
556 while (node !== null) {
558 node = node.getNextSibling();
564 * Returns the closest common ancestor of this node and the provided one or null
565 * if one cannot be found.
567 * @param node - the other node to find the common ancestor of.
569 getCommonAncestor<T extends ElementNode = ElementNode>(
572 const a = this.getParents();
573 const b = node.getParents();
574 if ($isElementNode(this)) {
577 if ($isElementNode(node)) {
580 const aLength = a.length;
581 const bLength = b.length;
582 if (aLength === 0 || bLength === 0 || a[aLength - 1] !== b[bLength - 1]) {
585 const bSet = new Set(b);
586 for (let i = 0; i < aLength; i++) {
587 const ancestor = a[i] as T;
588 if (bSet.has(ancestor)) {
596 * Returns true if the provided node is the exact same one as this node, from Lexical's perspective.
597 * Always use this instead of referential equality.
599 * @param object - the node to perform the equality comparison on.
601 is(object: LexicalNode | null | undefined): boolean {
602 if (object == null) {
605 return this.__key === object.__key;
609 * Returns true if this node logical precedes the target node in the editor state.
611 * @param targetNode - the node we're testing to see if it's after this one.
613 isBefore(targetNode: LexicalNode): boolean {
614 if (this === targetNode) {
617 if (targetNode.isParentOf(this)) {
620 if (this.isParentOf(targetNode)) {
623 const commonAncestor = this.getCommonAncestor(targetNode);
626 let node: this | ElementNode | LexicalNode = this;
628 const parent: ElementNode = node.getParentOrThrow();
629 if (parent === commonAncestor) {
630 indexA = node.getIndexWithinParent();
637 const parent: ElementNode = node.getParentOrThrow();
638 if (parent === commonAncestor) {
639 indexB = node.getIndexWithinParent();
644 return indexA < indexB;
648 * Returns true if this node is the parent of the target node, false otherwise.
650 * @param targetNode - the would-be child node.
652 isParentOf(targetNode: LexicalNode): boolean {
653 const key = this.__key;
654 if (key === targetNode.__key) {
657 let node: ElementNode | LexicalNode | null = targetNode;
658 while (node !== null) {
659 if (node.__key === key) {
662 node = node.getParent();
667 // TO-DO: this function can be simplified a lot
669 * Returns a list of nodes that are between this node and
670 * the target node in the EditorState.
672 * @param targetNode - the node that marks the other end of the range of nodes to be returned.
674 getNodesBetween(targetNode: LexicalNode): Array<LexicalNode> {
675 const isBefore = this.isBefore(targetNode);
677 const visited = new Set();
678 let node: LexicalNode | this | null = this;
683 const key = node.__key;
684 if (!visited.has(key)) {
688 if (node === targetNode) {
691 const child: LexicalNode | null = $isElementNode(node)
693 ? node.getFirstChild()
694 : node.getLastChild()
696 if (child !== null) {
700 const nextSibling: LexicalNode | null = isBefore
701 ? node.getNextSibling()
702 : node.getPreviousSibling();
703 if (nextSibling !== null) {
707 const parent: LexicalNode | null = node.getParentOrThrow();
708 if (!visited.has(parent.__key)) {
711 if (parent === targetNode) {
714 let parentSibling = null;
715 let ancestor: LexicalNode | null = parent;
717 if (ancestor === null) {
718 invariant(false, 'getNodesBetween: ancestor is null');
720 parentSibling = isBefore
721 ? ancestor.getNextSibling()
722 : ancestor.getPreviousSibling();
723 ancestor = ancestor.getParent();
724 if (ancestor !== null) {
725 if (parentSibling === null && !visited.has(ancestor.__key)) {
726 nodes.push(ancestor);
731 } while (parentSibling === null);
732 node = parentSibling;
741 * Returns true if this node has been marked dirty during this update cycle.
745 const editor = getActiveEditor();
746 const dirtyLeaves = editor._dirtyLeaves;
747 return dirtyLeaves !== null && dirtyLeaves.has(this.__key);
751 * Returns the latest version of the node from the active EditorState.
752 * This is used to avoid getting values from stale node references.
756 const latest = $getNodeByKey<this>(this.__key);
757 if (latest === null) {
760 'Lexical node does not exist in active editor state. Avoid using the same node references between nested closures from editorState.read/editor.update.',
767 * Returns a mutable version of the node using {@link $cloneWithProperties}
768 * if necessary. Will throw an error if called outside of a Lexical Editor
769 * {@link LexicalEditor.update} callback.
772 getWritable(): this {
774 const editorState = getActiveEditorState();
775 const editor = getActiveEditor();
776 const nodeMap = editorState._nodeMap;
777 const key = this.__key;
778 // Ensure we get the latest node from pending state
779 const latestNode = this.getLatest();
780 const cloneNotNeeded = editor._cloneNotNeeded;
781 const selection = $getSelection();
782 if (selection !== null) {
783 selection.setCachedNodes(null);
785 if (cloneNotNeeded.has(key)) {
786 // Transforms clear the dirty node set on each iteration to keep track on newly dirty nodes
787 internalMarkNodeAsDirty(latestNode);
790 const mutableNode = $cloneWithProperties(latestNode);
791 cloneNotNeeded.add(key);
792 internalMarkNodeAsDirty(mutableNode);
793 // Update reference in node map
794 nodeMap.set(key, mutableNode);
800 * Returns the text content of the node. Override this for
801 * custom nodes that should have a representation in plain text
802 * format (for copy + paste, for example)
805 getTextContent(): string {
810 * Returns the length of the string produced by calling getTextContent on this node.
813 getTextContentSize(): number {
814 return this.getTextContent().length;
820 * Called during the reconciliation process to determine which nodes
821 * to insert into the DOM for this Lexical Node.
823 * This method must return exactly one HTMLElement. Nested elements are not supported.
825 * Do not attempt to update the Lexical EditorState during this phase of the update lifecyle.
827 * @param _config - allows access to things like the EditorTheme (to apply classes) during reconciliation.
828 * @param _editor - allows access to the editor for context during reconciliation.
831 createDOM(_config: EditorConfig, _editor: LexicalEditor): HTMLElement {
832 invariant(false, 'createDOM: base method not extended');
836 * Called when a node changes and should update the DOM
837 * in whatever way is necessary to make it align with any changes that might
838 * have happened during the update.
840 * Returning "true" here will cause lexical to unmount and recreate the DOM node
841 * (by calling createDOM). You would need to do this if the element tag changes,
848 _config: EditorConfig,
850 invariant(false, 'updateDOM: base method not extended');
854 * Controls how the this node is serialized to HTML. This is important for
855 * copy and paste between Lexical and non-Lexical editors, or Lexical editors with different namespaces,
856 * in which case the primary transfer format is HTML. It's also important if you're serializing
857 * to HTML for any other reason via {@link @lexical/html!$generateHtmlFromNodes}. You could
858 * also use this method to build your own HTML renderer.
861 exportDOM(editor: LexicalEditor): DOMExportOutput {
862 const element = this.createDOM(editor._config, editor);
867 * Controls how the this node is serialized to JSON. This is important for
868 * copy and paste between Lexical editors sharing the same namespace. It's also important
869 * if you're serializing to JSON for persistent storage somewhere.
870 * See [Serialization & Deserialization](https://lexical.dev/docs/concepts/serialization#lexical---html).
873 exportJSON(): SerializedLexicalNode {
874 invariant(false, 'exportJSON: base method not extended');
878 * Controls how the this node is deserialized from JSON. This is usually boilerplate,
879 * but provides an abstraction between the node implementation and serialized interface that can
880 * be important if you ever make breaking changes to a node schema (by adding or removing properties).
881 * See [Serialization & Deserialization](https://lexical.dev/docs/concepts/serialization#lexical---html).
884 static importJSON(_serializedNode: SerializedLexicalNode): LexicalNode {
887 'LexicalNode: Node %s does not implement .importJSON().',
894 * Registers the returned function as a transform on the node during
895 * Editor initialization. Most such use cases should be addressed via
896 * the {@link LexicalEditor.registerNodeTransform} API.
898 * Experimental - use at your own risk.
900 static transform(): ((node: LexicalNode) => void) | null {
904 // Setters and mutators
907 * Removes this LexicalNode from the EditorState. If the node isn't re-inserted
908 * somewhere, the Lexical garbage collector will eventually clean it up.
910 * @param preserveEmptyParent - If falsy, the node's parent will be removed if
911 * it's empty after the removal operation. This is the default behavior, subject to
912 * other node heuristics such as {@link ElementNode#canBeEmpty}
914 remove(preserveEmptyParent?: boolean): void {
915 $removeNode(this, true, preserveEmptyParent);
919 * Replaces this LexicalNode with the provided node, optionally transferring the children
920 * of the replaced node to the replacing node.
922 * @param replaceWith - The node to replace this one with.
923 * @param includeChildren - Whether or not to transfer the children of this node to the replacing node.
925 replace<N extends LexicalNode>(replaceWith: N, includeChildren?: boolean): N {
927 let selection = $getSelection();
928 if (selection !== null) {
929 selection = selection.clone();
931 errorOnInsertTextNodeOnRoot(this, replaceWith);
932 const self = this.getLatest();
933 const toReplaceKey = this.__key;
934 const key = replaceWith.__key;
935 const writableReplaceWith = replaceWith.getWritable();
936 const writableParent = this.getParentOrThrow().getWritable();
937 const size = writableParent.__size;
938 removeFromParent(writableReplaceWith);
939 const prevSibling = self.getPreviousSibling();
940 const nextSibling = self.getNextSibling();
941 const prevKey = self.__prev;
942 const nextKey = self.__next;
943 const parentKey = self.__parent;
944 $removeNode(self, false, true);
946 if (prevSibling === null) {
947 writableParent.__first = key;
949 const writablePrevSibling = prevSibling.getWritable();
950 writablePrevSibling.__next = key;
952 writableReplaceWith.__prev = prevKey;
953 if (nextSibling === null) {
954 writableParent.__last = key;
956 const writableNextSibling = nextSibling.getWritable();
957 writableNextSibling.__prev = key;
959 writableReplaceWith.__next = nextKey;
960 writableReplaceWith.__parent = parentKey;
961 writableParent.__size = size;
962 if (includeChildren) {
964 $isElementNode(this) && $isElementNode(writableReplaceWith),
965 'includeChildren should only be true for ElementNodes',
967 this.getChildren().forEach((child: LexicalNode) => {
968 writableReplaceWith.append(child);
971 if ($isRangeSelection(selection)) {
972 $setSelection(selection);
973 const anchor = selection.anchor;
974 const focus = selection.focus;
975 if (anchor.key === toReplaceKey) {
976 $moveSelectionPointToEnd(anchor, writableReplaceWith);
978 if (focus.key === toReplaceKey) {
979 $moveSelectionPointToEnd(focus, writableReplaceWith);
982 if ($getCompositionKey() === toReplaceKey) {
983 $setCompositionKey(key);
985 return writableReplaceWith;
989 * Inserts a node after this LexicalNode (as the next sibling).
991 * @param nodeToInsert - The node to insert after this one.
992 * @param restoreSelection - Whether or not to attempt to resolve the
993 * selection to the appropriate place after the operation is complete.
995 insertAfter(nodeToInsert: LexicalNode, restoreSelection = true): LexicalNode {
997 errorOnInsertTextNodeOnRoot(this, nodeToInsert);
998 const writableSelf = this.getWritable();
999 const writableNodeToInsert = nodeToInsert.getWritable();
1000 const oldParent = writableNodeToInsert.getParent();
1001 const selection = $getSelection();
1002 let elementAnchorSelectionOnNode = false;
1003 let elementFocusSelectionOnNode = false;
1004 if (oldParent !== null) {
1005 // TODO: this is O(n), can we improve?
1006 const oldIndex = nodeToInsert.getIndexWithinParent();
1007 removeFromParent(writableNodeToInsert);
1008 if ($isRangeSelection(selection)) {
1009 const oldParentKey = oldParent.__key;
1010 const anchor = selection.anchor;
1011 const focus = selection.focus;
1012 elementAnchorSelectionOnNode =
1013 anchor.type === 'element' &&
1014 anchor.key === oldParentKey &&
1015 anchor.offset === oldIndex + 1;
1016 elementFocusSelectionOnNode =
1017 focus.type === 'element' &&
1018 focus.key === oldParentKey &&
1019 focus.offset === oldIndex + 1;
1022 const nextSibling = this.getNextSibling();
1023 const writableParent = this.getParentOrThrow().getWritable();
1024 const insertKey = writableNodeToInsert.__key;
1025 const nextKey = writableSelf.__next;
1026 if (nextSibling === null) {
1027 writableParent.__last = insertKey;
1029 const writableNextSibling = nextSibling.getWritable();
1030 writableNextSibling.__prev = insertKey;
1032 writableParent.__size++;
1033 writableSelf.__next = insertKey;
1034 writableNodeToInsert.__next = nextKey;
1035 writableNodeToInsert.__prev = writableSelf.__key;
1036 writableNodeToInsert.__parent = writableSelf.__parent;
1037 if (restoreSelection && $isRangeSelection(selection)) {
1038 const index = this.getIndexWithinParent();
1039 $updateElementSelectionOnCreateDeleteNode(
1044 const writableParentKey = writableParent.__key;
1045 if (elementAnchorSelectionOnNode) {
1046 selection.anchor.set(writableParentKey, index + 2, 'element');
1048 if (elementFocusSelectionOnNode) {
1049 selection.focus.set(writableParentKey, index + 2, 'element');
1052 return nodeToInsert;
1056 * Inserts a node before this LexicalNode (as the previous sibling).
1058 * @param nodeToInsert - The node to insert before this one.
1059 * @param restoreSelection - Whether or not to attempt to resolve the
1060 * selection to the appropriate place after the operation is complete.
1063 nodeToInsert: LexicalNode,
1064 restoreSelection = true,
1067 errorOnInsertTextNodeOnRoot(this, nodeToInsert);
1068 const writableSelf = this.getWritable();
1069 const writableNodeToInsert = nodeToInsert.getWritable();
1070 const insertKey = writableNodeToInsert.__key;
1071 removeFromParent(writableNodeToInsert);
1072 const prevSibling = this.getPreviousSibling();
1073 const writableParent = this.getParentOrThrow().getWritable();
1074 const prevKey = writableSelf.__prev;
1075 // TODO: this is O(n), can we improve?
1076 const index = this.getIndexWithinParent();
1077 if (prevSibling === null) {
1078 writableParent.__first = insertKey;
1080 const writablePrevSibling = prevSibling.getWritable();
1081 writablePrevSibling.__next = insertKey;
1083 writableParent.__size++;
1084 writableSelf.__prev = insertKey;
1085 writableNodeToInsert.__prev = prevKey;
1086 writableNodeToInsert.__next = writableSelf.__key;
1087 writableNodeToInsert.__parent = writableSelf.__parent;
1088 const selection = $getSelection();
1089 if (restoreSelection && $isRangeSelection(selection)) {
1090 const parent = this.getParentOrThrow();
1091 $updateElementSelectionOnCreateDeleteNode(selection, parent, index);
1093 return nodeToInsert;
1097 * Whether or not this node has a required parent. Used during copy + paste operations
1098 * to normalize nodes that would otherwise be orphaned. For example, ListItemNodes without
1099 * a ListNode parent or TextNodes with a ParagraphNode parent.
1102 isParentRequired(): boolean {
1107 * The creation logic for any required parent. Should be implemented if {@link isParentRequired} returns true.
1110 createParentElementNode(): ElementNode {
1111 return $createParagraphNode();
1114 selectStart(): RangeSelection {
1115 return this.selectPrevious();
1118 selectEnd(): RangeSelection {
1119 return this.selectNext(0, 0);
1123 * Moves selection to the previous sibling of this node, at the specified offsets.
1125 * @param anchorOffset - The anchor offset for selection.
1126 * @param focusOffset - The focus offset for selection
1128 selectPrevious(anchorOffset?: number, focusOffset?: number): RangeSelection {
1130 const prevSibling = this.getPreviousSibling();
1131 const parent = this.getParentOrThrow();
1132 if (prevSibling === null) {
1133 return parent.select(0, 0);
1135 if ($isElementNode(prevSibling)) {
1136 return prevSibling.select();
1137 } else if (!$isTextNode(prevSibling)) {
1138 const index = prevSibling.getIndexWithinParent() + 1;
1139 return parent.select(index, index);
1141 return prevSibling.select(anchorOffset, focusOffset);
1145 * Moves selection to the next sibling of this node, at the specified offsets.
1147 * @param anchorOffset - The anchor offset for selection.
1148 * @param focusOffset - The focus offset for selection
1150 selectNext(anchorOffset?: number, focusOffset?: number): RangeSelection {
1152 const nextSibling = this.getNextSibling();
1153 const parent = this.getParentOrThrow();
1154 if (nextSibling === null) {
1155 return parent.select();
1157 if ($isElementNode(nextSibling)) {
1158 return nextSibling.select(0, 0);
1159 } else if (!$isTextNode(nextSibling)) {
1160 const index = nextSibling.getIndexWithinParent();
1161 return parent.select(index, index);
1163 return nextSibling.select(anchorOffset, focusOffset);
1167 * Marks a node dirty, triggering transforms and
1168 * forcing it to be reconciled during the update cycle.
1176 * Insert the DOM of this node into that of the parent.
1177 * Allows this node to implement custom DOM attachment logic.
1178 * Boolean result indicates if the insertion was handled by the function.
1179 * A true return value prevents default insertion logic from taking place.
1181 insertDOMIntoParent(nodeDOM: HTMLElement, parentDOM: HTMLElement): boolean {
1186 function errorOnTypeKlassMismatch(
1188 klass: Klass<LexicalNode>,
1190 const registeredNode = getActiveEditor()._nodes.get(type);
1191 // Common error - split in its own invariant
1192 if (registeredNode === undefined) {
1195 'Create node: Attempted to create node %s that was not configured to be used on the editor.',
1199 const editorKlass = registeredNode.klass;
1200 if (editorKlass !== klass) {
1203 'Create node: Type %s in node %s does not match registered node %s with the same type',
1212 * Insert a series of nodes after this LexicalNode (as next siblings)
1214 * @param firstToInsert - The first node to insert after this one.
1215 * @param lastToInsert - The last node to insert after this one. Must be a
1216 * later sibling of FirstNode. If not provided, it will be its last sibling.
1218 export function insertRangeAfter(
1220 firstToInsert: LexicalNode,
1221 lastToInsert?: LexicalNode,
1223 const lastToInsert2 =
1224 lastToInsert || firstToInsert.getParentOrThrow().getLastChild()!;
1225 let current = firstToInsert;
1226 const nodesToInsert = [firstToInsert];
1227 while (current !== lastToInsert2) {
1228 if (!current.getNextSibling()) {
1231 'insertRangeAfter: lastToInsert must be a later sibling of firstToInsert',
1234 current = current.getNextSibling()!;
1235 nodesToInsert.push(current);
1238 let currentNode: LexicalNode = node;
1239 for (const nodeToInsert of nodesToInsert) {
1240 currentNode = currentNode.insertAfter(nodeToInsert);