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';
51 import {$insertAndSelectNewEmptyAdjacentNode} from "../../utils/nodes";
53 export type NodeMap = Map<NodeKey, LexicalNode>;
55 export type SerializedLexicalNode = {
60 export function $removeNode(
61 nodeToRemove: LexicalNode,
62 restoreSelection: boolean,
63 preserveEmptyParent?: boolean,
66 const key = nodeToRemove.__key;
67 const parent = nodeToRemove.getParent();
68 if (parent === null) {
71 const selection = $maybeMoveChildrenSelectionToParent(nodeToRemove);
72 let selectionMoved = false;
73 if ($isRangeSelection(selection) && restoreSelection) {
74 const anchor = selection.anchor;
75 const focus = selection.focus;
76 if (anchor.key === key) {
77 moveSelectionPointToSibling(
81 nodeToRemove.getPreviousSibling(),
82 nodeToRemove.getNextSibling(),
84 selectionMoved = true;
86 if (focus.key === key) {
87 moveSelectionPointToSibling(
91 nodeToRemove.getPreviousSibling(),
92 nodeToRemove.getNextSibling(),
94 selectionMoved = true;
97 $isNodeSelection(selection) &&
99 nodeToRemove.isSelected()
101 nodeToRemove.selectPrevious();
104 if ($isRangeSelection(selection) && restoreSelection && !selectionMoved) {
105 // Doing this is O(n) so lets avoid it unless we need to do it
106 const index = nodeToRemove.getIndexWithinParent();
107 removeFromParent(nodeToRemove);
108 $updateElementSelectionOnCreateDeleteNode(selection, parent, index, -1);
110 removeFromParent(nodeToRemove);
114 !preserveEmptyParent &&
115 !$isRootOrShadowRoot(parent) &&
116 !parent.canBeEmpty() &&
119 $removeNode(parent, restoreSelection);
121 if (restoreSelection && $isRootNode(parent) && parent.isEmpty()) {
126 export type DOMConversion<T extends HTMLElement = HTMLElement> = {
127 conversion: DOMConversionFn<T>;
128 priority?: 0 | 1 | 2 | 3 | 4;
131 export type DOMConversionFn<T extends HTMLElement = HTMLElement> = (
133 ) => DOMConversionOutput | null;
135 export type DOMChildConversion = (
136 lexicalNode: LexicalNode,
137 parentLexicalNode: LexicalNode | null | undefined,
138 ) => LexicalNode | null | undefined;
140 export type DOMConversionMap<T extends HTMLElement = HTMLElement> = Record<
142 (node: T) => DOMConversion<T> | null
144 type NodeName = string;
147 * Output for a DOM conversion.
148 * Node can be set to 'ignore' to ignore the conversion and handling of the DOMNode
149 * including all its children.
151 * You can specify a function to run for each converted child (forChild) or on all
152 * the child nodes after the conversion is complete (after).
153 * The key difference here is that forChild runs for every deeply nested child node
154 * of the current node, whereas after will run only once after the
155 * transformation of the node and all its children is complete.
157 export type DOMConversionOutput = {
158 after?: (childLexicalNodes: Array<LexicalNode>) => Array<LexicalNode>;
159 forChild?: DOMChildConversion;
160 node: null | LexicalNode | Array<LexicalNode> | 'ignore';
163 export type DOMExportOutputMap = Map<
165 (editor: LexicalEditor, target: LexicalNode) => DOMExportOutput
168 export type DOMExportOutput = {
170 generatedElement: HTMLElement | Text | null | undefined,
171 ) => HTMLElement | Text | null | undefined;
172 element: HTMLElement | Text | null;
175 export type NodeKey = string;
177 export class LexicalNode {
178 // Allow us to look up the type including static props
179 ['constructor']!: KlassConstructor<typeof LexicalNode>;
183 //@ts-ignore We set the key in the constructor.
186 __parent: null | NodeKey;
188 __prev: null | NodeKey;
190 __next: null | NodeKey;
192 // Flow doesn't support abstract classes unfortunately, so we can't _force_
193 // subclasses of Node to implement statics. All subclasses of Node should have
194 // a static getType and clone method though. We define getType and clone here so we can call it
195 // on any Node, and we throw this error by default since the subclass should provide
196 // their own implementation.
198 * Returns the string type of this node. Every node must
199 * implement this and it MUST BE UNIQUE amongst nodes registered
203 static getType(): string {
206 'LexicalNode: Node %s does not implement .getType().',
212 * Clones this node, creating a new node with a different key
213 * and adding it to the EditorState (but not attaching it anywhere!). All nodes must
214 * implement this method.
217 static clone(_data: unknown): LexicalNode {
220 'LexicalNode: Node %s does not implement .clone().',
226 * Perform any state updates on the clone of prevNode that are not already
227 * handled by the constructor call in the static clone method. If you have
228 * state to update in your clone that is not handled directly by the
229 * constructor, it is advisable to override this method but it is required
230 * to include a call to `super.afterCloneFrom(prevNode)` in your
231 * implementation. This is only intended to be called by
232 * {@link $cloneWithProperties} function or via a super call.
236 * class ClassesTextNode extends TextNode {
237 * // Not shown: static getType, static importJSON, exportJSON, createDOM, updateDOM
238 * __classes = new Set<string>();
239 * static clone(node: ClassesTextNode): ClassesTextNode {
240 * // The inherited TextNode constructor is used here, so
241 * // classes is not set by this method.
242 * return new ClassesTextNode(node.__text, node.__key);
244 * afterCloneFrom(node: this): void {
245 * // This calls TextNode.afterCloneFrom and LexicalNode.afterCloneFrom
246 * // for necessary state updates
247 * super.afterCloneFrom(node);
248 * this.__addClasses(node.__classes);
250 * // This method is a private implementation detail, it is not
251 * // suitable for the public API because it does not call getWritable
252 * __addClasses(classNames: Iterable<string>): this {
253 * for (const className of classNames) {
254 * this.__classes.add(className);
258 * addClass(...classNames: string[]): this {
259 * return this.getWritable().__addClasses(classNames);
261 * removeClass(...classNames: string[]): this {
262 * const node = this.getWritable();
263 * for (const className of classNames) {
264 * this.__classes.delete(className);
268 * getClasses(): Set<string> {
269 * return this.getLatest().__classes;
275 afterCloneFrom(prevNode: this) {
276 this.__parent = prevNode.__parent;
277 this.__next = prevNode.__next;
278 this.__prev = prevNode.__prev;
281 // eslint-disable-next-line @typescript-eslint/no-explicit-any
282 static importDOM?: () => DOMConversionMap<any> | null;
284 constructor(key?: NodeKey) {
285 this.__type = this.constructor.getType();
286 this.__parent = null;
289 $setNodeKey(this, key);
292 if (this.__type !== 'root') {
294 errorOnTypeKlassMismatch(this.__type, this.constructor);
298 // Getters and Traversers
301 * Returns the string type of this node.
307 isInline(): boolean {
310 'LexicalNode: Node %s does not implement .isInline().',
311 this.constructor.name,
316 * Returns true if there is a path between this node and the RootNode, false otherwise.
317 * This is a way of determining if the node is "attached" EditorState. Unattached nodes
318 * won't be reconciled and will ultimatelt be cleaned up by the Lexical GC.
320 isAttached(): boolean {
321 let nodeKey: string | null = this.__key;
322 while (nodeKey !== null) {
323 if (nodeKey === 'root') {
327 const node: LexicalNode | null = $getNodeByKey(nodeKey);
332 nodeKey = node.__parent;
338 * Returns true if this node is contained within the provided Selection., false otherwise.
339 * Relies on the algorithms implemented in {@link BaseSelection.getNodes} to determine
342 * @param selection - The selection that we want to determine if the node is in.
344 isSelected(selection?: null | BaseSelection): boolean {
345 const targetSelection = selection || $getSelection();
346 if (targetSelection == null) {
350 const isSelected = targetSelection
352 .some((n) => n.__key === this.__key);
354 if ($isTextNode(this)) {
357 // For inline images inside of element nodes.
358 // Without this change the image will be selected if the cursor is before or after it.
359 const isElementRangeSelection =
360 $isRangeSelection(targetSelection) &&
361 targetSelection.anchor.type === 'element' &&
362 targetSelection.focus.type === 'element';
364 if (isElementRangeSelection) {
365 if (targetSelection.isCollapsed()) {
369 const parentNode = this.getParent();
370 if ($isDecoratorNode(this) && this.isInline() && parentNode) {
371 const firstPoint = targetSelection.isBackward()
372 ? targetSelection.focus
373 : targetSelection.anchor;
374 const firstElement = firstPoint.getNode() as ElementNode;
376 firstPoint.offset === firstElement.getChildrenSize() &&
377 firstElement.is(parentNode) &&
378 firstElement.getLastChildOrThrow().is(this)
388 * Returns this nodes key.
391 // Key is stable between copies
396 * Returns the zero-based index of this node within the parent.
398 getIndexWithinParent(): number {
399 const parent = this.getParent();
400 if (parent === null) {
403 let node = parent.getFirstChild();
405 while (node !== null) {
410 node = node.getNextSibling();
416 * Returns the parent of this node, or null if none is found.
418 getParent<T extends ElementNode>(): T | null {
419 const parent = this.getLatest().__parent;
420 if (parent === null) {
423 return $getNodeByKey<T>(parent);
427 * Returns the parent of this node, or throws if none is found.
429 getParentOrThrow<T extends ElementNode>(): T {
430 const parent = this.getParent<T>();
431 if (parent === null) {
432 invariant(false, 'Expected node %s to have a parent.', this.__key);
438 * Returns the highest (in the EditorState tree)
439 * non-root ancestor of this node, or null if none is found. See {@link lexical!$isRootOrShadowRoot}
440 * for more information on which Elements comprise "roots".
442 getTopLevelElement(): ElementNode | DecoratorNode<unknown> | null {
443 let node: ElementNode | this | null = this;
444 while (node !== null) {
445 const parent: ElementNode | null = node.getParent();
446 if ($isRootOrShadowRoot(parent)) {
448 $isElementNode(node) || (node === this && $isDecoratorNode(node)),
449 'Children of root nodes must be elements or decorators',
459 * Returns the highest (in the EditorState tree)
460 * non-root ancestor of this node, or throws if none is found. See {@link lexical!$isRootOrShadowRoot}
461 * for more information on which Elements comprise "roots".
463 getTopLevelElementOrThrow(): ElementNode | DecoratorNode<unknown> {
464 const parent = this.getTopLevelElement();
465 if (parent === null) {
468 'Expected node %s to have a top parent element.',
476 * Returns a list of the every ancestor of this node,
477 * all the way up to the RootNode.
480 getParents(): Array<ElementNode> {
481 const parents: Array<ElementNode> = [];
482 let node = this.getParent();
483 while (node !== null) {
485 node = node.getParent();
491 * Returns a list of the keys of every ancestor of this node,
492 * all the way up to the RootNode.
495 getParentKeys(): Array<NodeKey> {
497 let node = this.getParent();
498 while (node !== null) {
499 parents.push(node.__key);
500 node = node.getParent();
506 * Returns the "previous" siblings - that is, the node that comes
507 * before this one in the same parent.
510 getPreviousSibling<T extends LexicalNode>(): T | null {
511 const self = this.getLatest();
512 const prevKey = self.__prev;
513 return prevKey === null ? null : $getNodeByKey<T>(prevKey);
517 * Returns the "previous" siblings - that is, the nodes that come between
518 * this one and the first child of it's parent, inclusive.
521 getPreviousSiblings<T extends LexicalNode>(): Array<T> {
522 const siblings: Array<T> = [];
523 const parent = this.getParent();
524 if (parent === null) {
527 let node: null | T = parent.getFirstChild();
528 while (node !== null) {
533 node = node.getNextSibling();
539 * Returns the "next" siblings - that is, the node that comes
540 * after this one in the same parent
543 getNextSibling<T extends LexicalNode>(): T | null {
544 const self = this.getLatest();
545 const nextKey = self.__next;
546 return nextKey === null ? null : $getNodeByKey<T>(nextKey);
550 * Returns all "next" siblings - that is, the nodes that come between this
551 * one and the last child of it's parent, inclusive.
554 getNextSiblings<T extends LexicalNode>(): Array<T> {
555 const siblings: Array<T> = [];
556 let node: null | T = this.getNextSibling();
557 while (node !== null) {
559 node = node.getNextSibling();
565 * Returns the closest common ancestor of this node and the provided one or null
566 * if one cannot be found.
568 * @param node - the other node to find the common ancestor of.
570 getCommonAncestor<T extends ElementNode = ElementNode>(
573 const a = this.getParents();
574 const b = node.getParents();
575 if ($isElementNode(this)) {
578 if ($isElementNode(node)) {
581 const aLength = a.length;
582 const bLength = b.length;
583 if (aLength === 0 || bLength === 0 || a[aLength - 1] !== b[bLength - 1]) {
586 const bSet = new Set(b);
587 for (let i = 0; i < aLength; i++) {
588 const ancestor = a[i] as T;
589 if (bSet.has(ancestor)) {
597 * Returns true if the provided node is the exact same one as this node, from Lexical's perspective.
598 * Always use this instead of referential equality.
600 * @param object - the node to perform the equality comparison on.
602 is(object: LexicalNode | null | undefined): boolean {
603 if (object == null) {
606 return this.__key === object.__key;
610 * Returns true if this node logical precedes the target node in the editor state.
612 * @param targetNode - the node we're testing to see if it's after this one.
614 isBefore(targetNode: LexicalNode): boolean {
615 if (this === targetNode) {
618 if (targetNode.isParentOf(this)) {
621 if (this.isParentOf(targetNode)) {
624 const commonAncestor = this.getCommonAncestor(targetNode);
627 let node: this | ElementNode | LexicalNode = this;
629 const parent: ElementNode = node.getParentOrThrow();
630 if (parent === commonAncestor) {
631 indexA = node.getIndexWithinParent();
638 const parent: ElementNode = node.getParentOrThrow();
639 if (parent === commonAncestor) {
640 indexB = node.getIndexWithinParent();
645 return indexA < indexB;
649 * Returns true if this node is the parent of the target node, false otherwise.
651 * @param targetNode - the would-be child node.
653 isParentOf(targetNode: LexicalNode): boolean {
654 const key = this.__key;
655 if (key === targetNode.__key) {
658 let node: ElementNode | LexicalNode | null = targetNode;
659 while (node !== null) {
660 if (node.__key === key) {
663 node = node.getParent();
668 // TO-DO: this function can be simplified a lot
670 * Returns a list of nodes that are between this node and
671 * the target node in the EditorState.
673 * @param targetNode - the node that marks the other end of the range of nodes to be returned.
675 getNodesBetween(targetNode: LexicalNode): Array<LexicalNode> {
676 const isBefore = this.isBefore(targetNode);
678 const visited = new Set();
679 let node: LexicalNode | this | null = this;
684 const key = node.__key;
685 if (!visited.has(key)) {
689 if (node === targetNode) {
692 const child: LexicalNode | null = $isElementNode(node)
694 ? node.getFirstChild()
695 : node.getLastChild()
697 if (child !== null) {
701 const nextSibling: LexicalNode | null = isBefore
702 ? node.getNextSibling()
703 : node.getPreviousSibling();
704 if (nextSibling !== null) {
708 const parent: LexicalNode | null = node.getParentOrThrow();
709 if (!visited.has(parent.__key)) {
712 if (parent === targetNode) {
715 let parentSibling = null;
716 let ancestor: LexicalNode | null = parent;
718 if (ancestor === null) {
719 invariant(false, 'getNodesBetween: ancestor is null');
721 parentSibling = isBefore
722 ? ancestor.getNextSibling()
723 : ancestor.getPreviousSibling();
724 ancestor = ancestor.getParent();
725 if (ancestor !== null) {
726 if (parentSibling === null && !visited.has(ancestor.__key)) {
727 nodes.push(ancestor);
732 } while (parentSibling === null);
733 node = parentSibling;
742 * Returns true if this node has been marked dirty during this update cycle.
746 const editor = getActiveEditor();
747 const dirtyLeaves = editor._dirtyLeaves;
748 return dirtyLeaves !== null && dirtyLeaves.has(this.__key);
752 * Returns the latest version of the node from the active EditorState.
753 * This is used to avoid getting values from stale node references.
757 const latest = $getNodeByKey<this>(this.__key);
758 if (latest === null) {
761 'Lexical node does not exist in active editor state. Avoid using the same node references between nested closures from editorState.read/editor.update.',
768 * Returns a mutable version of the node using {@link $cloneWithProperties}
769 * if necessary. Will throw an error if called outside of a Lexical Editor
770 * {@link LexicalEditor.update} callback.
773 getWritable(): this {
775 const editorState = getActiveEditorState();
776 const editor = getActiveEditor();
777 const nodeMap = editorState._nodeMap;
778 const key = this.__key;
779 // Ensure we get the latest node from pending state
780 const latestNode = this.getLatest();
781 const cloneNotNeeded = editor._cloneNotNeeded;
782 const selection = $getSelection();
783 if (selection !== null) {
784 selection.setCachedNodes(null);
786 if (cloneNotNeeded.has(key)) {
787 // Transforms clear the dirty node set on each iteration to keep track on newly dirty nodes
788 internalMarkNodeAsDirty(latestNode);
791 const mutableNode = $cloneWithProperties(latestNode);
792 cloneNotNeeded.add(key);
793 internalMarkNodeAsDirty(mutableNode);
794 // Update reference in node map
795 nodeMap.set(key, mutableNode);
801 * Returns the text content of the node. Override this for
802 * custom nodes that should have a representation in plain text
803 * format (for copy + paste, for example)
806 getTextContent(): string {
811 * Returns the length of the string produced by calling getTextContent on this node.
814 getTextContentSize(): number {
815 return this.getTextContent().length;
821 * Called during the reconciliation process to determine which nodes
822 * to insert into the DOM for this Lexical Node.
824 * This method must return exactly one HTMLElement. Nested elements are not supported.
826 * Do not attempt to update the Lexical EditorState during this phase of the update lifecyle.
828 * @param _config - allows access to things like the EditorTheme (to apply classes) during reconciliation.
829 * @param _editor - allows access to the editor for context during reconciliation.
832 createDOM(_config: EditorConfig, _editor: LexicalEditor): HTMLElement {
833 invariant(false, 'createDOM: base method not extended');
837 * Called when a node changes and should update the DOM
838 * in whatever way is necessary to make it align with any changes that might
839 * have happened during the update.
841 * Returning "true" here will cause lexical to unmount and recreate the DOM node
842 * (by calling createDOM). You would need to do this if the element tag changes,
849 _config: EditorConfig,
851 invariant(false, 'updateDOM: base method not extended');
855 * Controls how the this node is serialized to HTML. This is important for
856 * copy and paste between Lexical and non-Lexical editors, or Lexical editors with different namespaces,
857 * in which case the primary transfer format is HTML. It's also important if you're serializing
858 * to HTML for any other reason via {@link @lexical/html!$generateHtmlFromNodes}. You could
859 * also use this method to build your own HTML renderer.
862 exportDOM(editor: LexicalEditor): DOMExportOutput {
863 const element = this.createDOM(editor._config, editor);
868 * Controls how the this node is serialized to JSON. This is important for
869 * copy and paste between Lexical editors sharing the same namespace. It's also important
870 * if you're serializing to JSON for persistent storage somewhere.
871 * See [Serialization & Deserialization](https://lexical.dev/docs/concepts/serialization#lexical---html).
874 exportJSON(): SerializedLexicalNode {
875 invariant(false, 'exportJSON: base method not extended');
879 * Controls how the this node is deserialized from JSON. This is usually boilerplate,
880 * but provides an abstraction between the node implementation and serialized interface that can
881 * be important if you ever make breaking changes to a node schema (by adding or removing properties).
882 * See [Serialization & Deserialization](https://lexical.dev/docs/concepts/serialization#lexical---html).
885 static importJSON(_serializedNode: SerializedLexicalNode): LexicalNode {
888 'LexicalNode: Node %s does not implement .importJSON().',
895 * Registers the returned function as a transform on the node during
896 * Editor initialization. Most such use cases should be addressed via
897 * the {@link LexicalEditor.registerNodeTransform} API.
899 * Experimental - use at your own risk.
901 static transform(): ((node: LexicalNode) => void) | null {
905 // Setters and mutators
908 * Removes this LexicalNode from the EditorState. If the node isn't re-inserted
909 * somewhere, the Lexical garbage collector will eventually clean it up.
911 * @param preserveEmptyParent - If falsy, the node's parent will be removed if
912 * it's empty after the removal operation. This is the default behavior, subject to
913 * other node heuristics such as {@link ElementNode#canBeEmpty}
915 remove(preserveEmptyParent?: boolean): void {
916 $removeNode(this, true, preserveEmptyParent);
920 * Replaces this LexicalNode with the provided node, optionally transferring the children
921 * of the replaced node to the replacing node.
923 * @param replaceWith - The node to replace this one with.
924 * @param includeChildren - Whether or not to transfer the children of this node to the replacing node.
926 replace<N extends LexicalNode>(replaceWith: N, includeChildren?: boolean): N {
928 let selection = $getSelection();
929 if (selection !== null) {
930 selection = selection.clone();
932 errorOnInsertTextNodeOnRoot(this, replaceWith);
933 const self = this.getLatest();
934 const toReplaceKey = this.__key;
935 const key = replaceWith.__key;
936 const writableReplaceWith = replaceWith.getWritable();
937 const writableParent = this.getParentOrThrow().getWritable();
938 const size = writableParent.__size;
939 removeFromParent(writableReplaceWith);
940 const prevSibling = self.getPreviousSibling();
941 const nextSibling = self.getNextSibling();
942 const prevKey = self.__prev;
943 const nextKey = self.__next;
944 const parentKey = self.__parent;
945 $removeNode(self, false, true);
947 if (prevSibling === null) {
948 writableParent.__first = key;
950 const writablePrevSibling = prevSibling.getWritable();
951 writablePrevSibling.__next = key;
953 writableReplaceWith.__prev = prevKey;
954 if (nextSibling === null) {
955 writableParent.__last = key;
957 const writableNextSibling = nextSibling.getWritable();
958 writableNextSibling.__prev = key;
960 writableReplaceWith.__next = nextKey;
961 writableReplaceWith.__parent = parentKey;
962 writableParent.__size = size;
963 if (includeChildren) {
965 $isElementNode(this) && $isElementNode(writableReplaceWith),
966 'includeChildren should only be true for ElementNodes',
968 this.getChildren().forEach((child: LexicalNode) => {
969 writableReplaceWith.append(child);
972 if ($isRangeSelection(selection)) {
973 $setSelection(selection);
974 const anchor = selection.anchor;
975 const focus = selection.focus;
976 if (anchor.key === toReplaceKey) {
977 $moveSelectionPointToEnd(anchor, writableReplaceWith);
979 if (focus.key === toReplaceKey) {
980 $moveSelectionPointToEnd(focus, writableReplaceWith);
983 if ($getCompositionKey() === toReplaceKey) {
984 $setCompositionKey(key);
986 return writableReplaceWith;
990 * Inserts a node after this LexicalNode (as the next sibling).
992 * @param nodeToInsert - The node to insert after this one.
993 * @param restoreSelection - Whether or not to attempt to resolve the
994 * selection to the appropriate place after the operation is complete.
996 insertAfter(nodeToInsert: LexicalNode, restoreSelection = true): LexicalNode {
998 errorOnInsertTextNodeOnRoot(this, nodeToInsert);
999 const writableSelf = this.getWritable();
1000 const writableNodeToInsert = nodeToInsert.getWritable();
1001 const oldParent = writableNodeToInsert.getParent();
1002 const selection = $getSelection();
1003 let elementAnchorSelectionOnNode = false;
1004 let elementFocusSelectionOnNode = false;
1005 if (oldParent !== null) {
1006 // TODO: this is O(n), can we improve?
1007 const oldIndex = nodeToInsert.getIndexWithinParent();
1008 removeFromParent(writableNodeToInsert);
1009 if ($isRangeSelection(selection)) {
1010 const oldParentKey = oldParent.__key;
1011 const anchor = selection.anchor;
1012 const focus = selection.focus;
1013 elementAnchorSelectionOnNode =
1014 anchor.type === 'element' &&
1015 anchor.key === oldParentKey &&
1016 anchor.offset === oldIndex + 1;
1017 elementFocusSelectionOnNode =
1018 focus.type === 'element' &&
1019 focus.key === oldParentKey &&
1020 focus.offset === oldIndex + 1;
1023 const nextSibling = this.getNextSibling();
1024 const writableParent = this.getParentOrThrow().getWritable();
1025 const insertKey = writableNodeToInsert.__key;
1026 const nextKey = writableSelf.__next;
1027 if (nextSibling === null) {
1028 writableParent.__last = insertKey;
1030 const writableNextSibling = nextSibling.getWritable();
1031 writableNextSibling.__prev = insertKey;
1033 writableParent.__size++;
1034 writableSelf.__next = insertKey;
1035 writableNodeToInsert.__next = nextKey;
1036 writableNodeToInsert.__prev = writableSelf.__key;
1037 writableNodeToInsert.__parent = writableSelf.__parent;
1038 if (restoreSelection && $isRangeSelection(selection)) {
1039 const index = this.getIndexWithinParent();
1040 $updateElementSelectionOnCreateDeleteNode(
1045 const writableParentKey = writableParent.__key;
1046 if (elementAnchorSelectionOnNode) {
1047 selection.anchor.set(writableParentKey, index + 2, 'element');
1049 if (elementFocusSelectionOnNode) {
1050 selection.focus.set(writableParentKey, index + 2, 'element');
1053 return nodeToInsert;
1057 * Inserts a node before this LexicalNode (as the previous sibling).
1059 * @param nodeToInsert - The node to insert before this one.
1060 * @param restoreSelection - Whether or not to attempt to resolve the
1061 * selection to the appropriate place after the operation is complete.
1064 nodeToInsert: LexicalNode,
1065 restoreSelection = true,
1068 errorOnInsertTextNodeOnRoot(this, nodeToInsert);
1069 const writableSelf = this.getWritable();
1070 const writableNodeToInsert = nodeToInsert.getWritable();
1071 const insertKey = writableNodeToInsert.__key;
1072 removeFromParent(writableNodeToInsert);
1073 const prevSibling = this.getPreviousSibling();
1074 const writableParent = this.getParentOrThrow().getWritable();
1075 const prevKey = writableSelf.__prev;
1076 // TODO: this is O(n), can we improve?
1077 const index = this.getIndexWithinParent();
1078 if (prevSibling === null) {
1079 writableParent.__first = insertKey;
1081 const writablePrevSibling = prevSibling.getWritable();
1082 writablePrevSibling.__next = insertKey;
1084 writableParent.__size++;
1085 writableSelf.__prev = insertKey;
1086 writableNodeToInsert.__prev = prevKey;
1087 writableNodeToInsert.__next = writableSelf.__key;
1088 writableNodeToInsert.__parent = writableSelf.__parent;
1089 const selection = $getSelection();
1090 if (restoreSelection && $isRangeSelection(selection)) {
1091 const parent = this.getParentOrThrow();
1092 $updateElementSelectionOnCreateDeleteNode(selection, parent, index);
1094 return nodeToInsert;
1098 * Whether or not this node has a required parent. Used during copy + paste operations
1099 * to normalize nodes that would otherwise be orphaned. For example, ListItemNodes without
1100 * a ListNode parent or TextNodes with a ParagraphNode parent.
1103 isParentRequired(): boolean {
1108 * The creation logic for any required parent. Should be implemented if {@link isParentRequired} returns true.
1111 createParentElementNode(): ElementNode {
1112 return $createParagraphNode();
1115 selectStart(): RangeSelection {
1116 return this.selectPrevious();
1119 selectEnd(): RangeSelection {
1120 return this.selectNext(0, 0);
1124 * Moves selection to the previous sibling of this node, at the specified offsets.
1126 * @param anchorOffset - The anchor offset for selection.
1127 * @param focusOffset - The focus offset for selection
1129 selectPrevious(anchorOffset?: number, focusOffset?: number): RangeSelection {
1131 const prevSibling = this.getPreviousSibling();
1132 const parent = this.getParentOrThrow();
1133 if (prevSibling === null) {
1134 return $insertAndSelectNewEmptyAdjacentNode(this, false);
1136 if ($isElementNode(prevSibling)) {
1137 return prevSibling.select();
1138 } else if (!$isTextNode(prevSibling)) {
1139 const index = prevSibling.getIndexWithinParent() + 1;
1140 return parent.select(index, index);
1142 return prevSibling.select(anchorOffset, focusOffset);
1146 * Moves selection to the next sibling of this node, at the specified offsets.
1148 * @param anchorOffset - The anchor offset for selection.
1149 * @param focusOffset - The focus offset for selection
1151 selectNext(anchorOffset?: number, focusOffset?: number): RangeSelection {
1153 const nextSibling = this.getNextSibling();
1154 const parent = this.getParentOrThrow();
1155 if (nextSibling === null) {
1156 return $insertAndSelectNewEmptyAdjacentNode(this, true);
1158 if ($isElementNode(nextSibling)) {
1159 return nextSibling.select(0, 0);
1160 } else if (!$isTextNode(nextSibling)) {
1161 const index = nextSibling.getIndexWithinParent();
1162 return parent.select(index, index);
1164 return nextSibling.select(anchorOffset, focusOffset);
1168 * Marks a node dirty, triggering transforms and
1169 * forcing it to be reconciled during the update cycle.
1177 * Insert the DOM of this node into that of the parent.
1178 * Allows this node to implement custom DOM attachment logic.
1179 * Boolean result indicates if the insertion was handled by the function.
1180 * A true return value prevents default insertion logic from taking place.
1182 insertDOMIntoParent(nodeDOM: HTMLElement, parentDOM: HTMLElement): boolean {
1187 function errorOnTypeKlassMismatch(
1189 klass: Klass<LexicalNode>,
1191 const registeredNode = getActiveEditor()._nodes.get(type);
1192 // Common error - split in its own invariant
1193 if (registeredNode === undefined) {
1196 'Create node: Attempted to create node %s that was not configured to be used on the editor.',
1200 const editorKlass = registeredNode.klass;
1201 if (editorKlass !== klass) {
1204 'Create node: Type %s in node %s does not match registered node %s with the same type',
1213 * Insert a series of nodes after this LexicalNode (as next siblings)
1215 * @param firstToInsert - The first node to insert after this one.
1216 * @param lastToInsert - The last node to insert after this one. Must be a
1217 * later sibling of FirstNode. If not provided, it will be its last sibling.
1219 export function insertRangeAfter(
1221 firstToInsert: LexicalNode,
1222 lastToInsert?: LexicalNode,
1224 const lastToInsert2 =
1225 lastToInsert || firstToInsert.getParentOrThrow().getLastChild()!;
1226 let current = firstToInsert;
1227 const nodesToInsert = [firstToInsert];
1228 while (current !== lastToInsert2) {
1229 if (!current.getNextSibling()) {
1232 'insertRangeAfter: lastToInsert must be a later sibling of firstToInsert',
1235 current = current.getNextSibling()!;
1236 nodesToInsert.push(current);
1239 let currentNode: LexicalNode = node;
1240 for (const nodeToInsert of nodesToInsert) {
1241 currentNode = currentNode.insertAfter(nodeToInsert);