]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/lexical/core/LexicalNode.ts
Lexical: Fixed code in lists, removed extra old alignment code
[bookstack] / resources / js / wysiwyg / lexical / core / LexicalNode.ts
1 /**
2  * Copyright (c) Meta Platforms, Inc. and affiliates.
3  *
4  * This source code is licensed under the MIT license found in the
5  * LICENSE file in the root directory of this source tree.
6  *
7  */
8
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';
13
14 import invariant from 'lexical/shared/invariant';
15
16 import {
17   $createParagraphNode,
18   $isDecoratorNode,
19   $isElementNode,
20   $isRootNode,
21   $isTextNode,
22   type DecoratorNode,
23   ElementNode,
24 } from '.';
25 import {
26   $getSelection,
27   $isNodeSelection,
28   $isRangeSelection,
29   $moveSelectionPointToEnd,
30   $updateElementSelectionOnCreateDeleteNode,
31   moveSelectionPointToSibling,
32 } from './LexicalSelection';
33 import {
34   errorOnReadOnly,
35   getActiveEditor,
36   getActiveEditorState,
37 } from './LexicalUpdates';
38 import {
39   $cloneWithProperties,
40   $getCompositionKey,
41   $getNodeByKey,
42   $isRootOrShadowRoot,
43   $maybeMoveChildrenSelectionToParent,
44   $setCompositionKey,
45   $setNodeKey,
46   $setSelection,
47   errorOnInsertTextNodeOnRoot,
48   internalMarkNodeAsDirty,
49   removeFromParent,
50 } from './LexicalUtils';
51
52 export type NodeMap = Map<NodeKey, LexicalNode>;
53
54 export type SerializedLexicalNode = {
55   type: string;
56   version: number;
57 };
58
59 export function $removeNode(
60   nodeToRemove: LexicalNode,
61   restoreSelection: boolean,
62   preserveEmptyParent?: boolean,
63 ): void {
64   errorOnReadOnly();
65   const key = nodeToRemove.__key;
66   const parent = nodeToRemove.getParent();
67   if (parent === null) {
68     return;
69   }
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(
77         anchor,
78         nodeToRemove,
79         parent,
80         nodeToRemove.getPreviousSibling(),
81         nodeToRemove.getNextSibling(),
82       );
83       selectionMoved = true;
84     }
85     if (focus.key === key) {
86       moveSelectionPointToSibling(
87         focus,
88         nodeToRemove,
89         parent,
90         nodeToRemove.getPreviousSibling(),
91         nodeToRemove.getNextSibling(),
92       );
93       selectionMoved = true;
94     }
95   } else if (
96     $isNodeSelection(selection) &&
97     restoreSelection &&
98     nodeToRemove.isSelected()
99   ) {
100     nodeToRemove.selectPrevious();
101   }
102
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);
108   } else {
109     removeFromParent(nodeToRemove);
110   }
111
112   if (
113     !preserveEmptyParent &&
114     !$isRootOrShadowRoot(parent) &&
115     !parent.canBeEmpty() &&
116     parent.isEmpty()
117   ) {
118     $removeNode(parent, restoreSelection);
119   }
120   if (restoreSelection && $isRootNode(parent) && parent.isEmpty()) {
121     parent.selectEnd();
122   }
123 }
124
125 export type DOMConversion<T extends HTMLElement = HTMLElement> = {
126   conversion: DOMConversionFn<T>;
127   priority?: 0 | 1 | 2 | 3 | 4;
128 };
129
130 export type DOMConversionFn<T extends HTMLElement = HTMLElement> = (
131   element: T,
132 ) => DOMConversionOutput | null;
133
134 export type DOMChildConversion = (
135   lexicalNode: LexicalNode,
136   parentLexicalNode: LexicalNode | null | undefined,
137 ) => LexicalNode | null | undefined;
138
139 export type DOMConversionMap<T extends HTMLElement = HTMLElement> = Record<
140   NodeName,
141   (node: T) => DOMConversion<T> | null
142 >;
143 type NodeName = string;
144
145 /**
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.
149  *
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.
155  */
156 export type DOMConversionOutput = {
157   after?: (childLexicalNodes: Array<LexicalNode>) => Array<LexicalNode>;
158   forChild?: DOMChildConversion;
159   node: null | LexicalNode | Array<LexicalNode> | 'ignore';
160 };
161
162 export type DOMExportOutputMap = Map<
163   Klass<LexicalNode>,
164   (editor: LexicalEditor, target: LexicalNode) => DOMExportOutput
165 >;
166
167 export type DOMExportOutput = {
168   after?: (
169     generatedElement: HTMLElement | Text | null | undefined,
170   ) => HTMLElement | Text | null | undefined;
171   element: HTMLElement | Text | null;
172 };
173
174 export type NodeKey = string;
175
176 export class LexicalNode {
177   // Allow us to look up the type including static props
178   ['constructor']!: KlassConstructor<typeof LexicalNode>;
179   /** @internal */
180   __type: string;
181   /** @internal */
182   //@ts-ignore We set the key in the constructor.
183   __key: string;
184   /** @internal */
185   __parent: null | NodeKey;
186   /** @internal */
187   __prev: null | NodeKey;
188   /** @internal */
189   __next: null | NodeKey;
190
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.
196   /**
197    * Returns the string type of this node. Every node must
198    * implement this and it MUST BE UNIQUE amongst nodes registered
199    * on the editor.
200    *
201    */
202   static getType(): string {
203     invariant(
204       false,
205       'LexicalNode: Node %s does not implement .getType().',
206       this.name,
207     );
208   }
209
210   /**
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.
214    *
215    */
216   static clone(_data: unknown): LexicalNode {
217     invariant(
218       false,
219       'LexicalNode: Node %s does not implement .clone().',
220       this.name,
221     );
222   }
223
224   /**
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.
232    *
233    * @example
234    * ```ts
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);
242    *   }
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);
248    *   }
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);
254    *     }
255    *     return this;
256    *   }
257    *   addClass(...classNames: string[]): this {
258    *     return this.getWritable().__addClasses(classNames);
259    *   }
260    *   removeClass(...classNames: string[]): this {
261    *     const node = this.getWritable();
262    *     for (const className of classNames) {
263    *       this.__classes.delete(className);
264    *     }
265    *     return this;
266    *   }
267    *   getClasses(): Set<string> {
268    *     return this.getLatest().__classes;
269    *   }
270    * }
271    * ```
272    *
273    */
274   afterCloneFrom(prevNode: this) {
275     this.__parent = prevNode.__parent;
276     this.__next = prevNode.__next;
277     this.__prev = prevNode.__prev;
278   }
279
280   // eslint-disable-next-line @typescript-eslint/no-explicit-any
281   static importDOM?: () => DOMConversionMap<any> | null;
282
283   constructor(key?: NodeKey) {
284     this.__type = this.constructor.getType();
285     this.__parent = null;
286     this.__prev = null;
287     this.__next = null;
288     $setNodeKey(this, key);
289
290     if (__DEV__) {
291       if (this.__type !== 'root') {
292         errorOnReadOnly();
293         errorOnTypeKlassMismatch(this.__type, this.constructor);
294       }
295     }
296   }
297   // Getters and Traversers
298
299   /**
300    * Returns the string type of this node.
301    */
302   getType(): string {
303     return this.__type;
304   }
305
306   isInline(): boolean {
307     invariant(
308       false,
309       'LexicalNode: Node %s does not implement .isInline().',
310       this.constructor.name,
311     );
312   }
313
314   /**
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.
318    */
319   isAttached(): boolean {
320     let nodeKey: string | null = this.__key;
321     while (nodeKey !== null) {
322       if (nodeKey === 'root') {
323         return true;
324       }
325
326       const node: LexicalNode | null = $getNodeByKey(nodeKey);
327
328       if (node === null) {
329         break;
330       }
331       nodeKey = node.__parent;
332     }
333     return false;
334   }
335
336   /**
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
339    * what's included.
340    *
341    * @param selection - The selection that we want to determine if the node is in.
342    */
343   isSelected(selection?: null | BaseSelection): boolean {
344     const targetSelection = selection || $getSelection();
345     if (targetSelection == null) {
346       return false;
347     }
348
349     const isSelected = targetSelection
350       .getNodes()
351       .some((n) => n.__key === this.__key);
352
353     if ($isTextNode(this)) {
354       return isSelected;
355     }
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';
362
363     if (isElementRangeSelection) {
364       if (targetSelection.isCollapsed()) {
365         return false;
366       }
367
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;
374         if (
375           firstPoint.offset === firstElement.getChildrenSize() &&
376           firstElement.is(parentNode) &&
377           firstElement.getLastChildOrThrow().is(this)
378         ) {
379           return false;
380         }
381       }
382     }
383     return isSelected;
384   }
385
386   /**
387    * Returns this nodes key.
388    */
389   getKey(): NodeKey {
390     // Key is stable between copies
391     return this.__key;
392   }
393
394   /**
395    * Returns the zero-based index of this node within the parent.
396    */
397   getIndexWithinParent(): number {
398     const parent = this.getParent();
399     if (parent === null) {
400       return -1;
401     }
402     let node = parent.getFirstChild();
403     let index = 0;
404     while (node !== null) {
405       if (this.is(node)) {
406         return index;
407       }
408       index++;
409       node = node.getNextSibling();
410     }
411     return -1;
412   }
413
414   /**
415    * Returns the parent of this node, or null if none is found.
416    */
417   getParent<T extends ElementNode>(): T | null {
418     const parent = this.getLatest().__parent;
419     if (parent === null) {
420       return null;
421     }
422     return $getNodeByKey<T>(parent);
423   }
424
425   /**
426    * Returns the parent of this node, or throws if none is found.
427    */
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);
432     }
433     return parent;
434   }
435
436   /**
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".
440    */
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)) {
446         invariant(
447           $isElementNode(node) || (node === this && $isDecoratorNode(node)),
448           'Children of root nodes must be elements or decorators',
449         );
450         return node;
451       }
452       node = parent;
453     }
454     return null;
455   }
456
457   /**
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".
461    */
462   getTopLevelElementOrThrow(): ElementNode | DecoratorNode<unknown> {
463     const parent = this.getTopLevelElement();
464     if (parent === null) {
465       invariant(
466         false,
467         'Expected node %s to have a top parent element.',
468         this.__key,
469       );
470     }
471     return parent;
472   }
473
474   /**
475    * Returns a list of the every ancestor of this node,
476    * all the way up to the RootNode.
477    *
478    */
479   getParents(): Array<ElementNode> {
480     const parents: Array<ElementNode> = [];
481     let node = this.getParent();
482     while (node !== null) {
483       parents.push(node);
484       node = node.getParent();
485     }
486     return parents;
487   }
488
489   /**
490    * Returns a list of the keys of every ancestor of this node,
491    * all the way up to the RootNode.
492    *
493    */
494   getParentKeys(): Array<NodeKey> {
495     const parents = [];
496     let node = this.getParent();
497     while (node !== null) {
498       parents.push(node.__key);
499       node = node.getParent();
500     }
501     return parents;
502   }
503
504   /**
505    * Returns the "previous" siblings - that is, the node that comes
506    * before this one in the same parent.
507    *
508    */
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);
513   }
514
515   /**
516    * Returns the "previous" siblings - that is, the nodes that come between
517    * this one and the first child of it's parent, inclusive.
518    *
519    */
520   getPreviousSiblings<T extends LexicalNode>(): Array<T> {
521     const siblings: Array<T> = [];
522     const parent = this.getParent();
523     if (parent === null) {
524       return siblings;
525     }
526     let node: null | T = parent.getFirstChild();
527     while (node !== null) {
528       if (node.is(this)) {
529         break;
530       }
531       siblings.push(node);
532       node = node.getNextSibling();
533     }
534     return siblings;
535   }
536
537   /**
538    * Returns the "next" siblings - that is, the node that comes
539    * after this one in the same parent
540    *
541    */
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);
546   }
547
548   /**
549    * Returns all "next" siblings - that is, the nodes that come between this
550    * one and the last child of it's parent, inclusive.
551    *
552    */
553   getNextSiblings<T extends LexicalNode>(): Array<T> {
554     const siblings: Array<T> = [];
555     let node: null | T = this.getNextSibling();
556     while (node !== null) {
557       siblings.push(node);
558       node = node.getNextSibling();
559     }
560     return siblings;
561   }
562
563   /**
564    * Returns the closest common ancestor of this node and the provided one or null
565    * if one cannot be found.
566    *
567    * @param node - the other node to find the common ancestor of.
568    */
569   getCommonAncestor<T extends ElementNode = ElementNode>(
570     node: LexicalNode,
571   ): T | null {
572     const a = this.getParents();
573     const b = node.getParents();
574     if ($isElementNode(this)) {
575       a.unshift(this);
576     }
577     if ($isElementNode(node)) {
578       b.unshift(node);
579     }
580     const aLength = a.length;
581     const bLength = b.length;
582     if (aLength === 0 || bLength === 0 || a[aLength - 1] !== b[bLength - 1]) {
583       return null;
584     }
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)) {
589         return ancestor;
590       }
591     }
592     return null;
593   }
594
595   /**
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.
598    *
599    * @param object - the node to perform the equality comparison on.
600    */
601   is(object: LexicalNode | null | undefined): boolean {
602     if (object == null) {
603       return false;
604     }
605     return this.__key === object.__key;
606   }
607
608   /**
609    * Returns true if this node logical precedes the target node in the editor state.
610    *
611    * @param targetNode - the node we're testing to see if it's after this one.
612    */
613   isBefore(targetNode: LexicalNode): boolean {
614     if (this === targetNode) {
615       return false;
616     }
617     if (targetNode.isParentOf(this)) {
618       return true;
619     }
620     if (this.isParentOf(targetNode)) {
621       return false;
622     }
623     const commonAncestor = this.getCommonAncestor(targetNode);
624     let indexA = 0;
625     let indexB = 0;
626     let node: this | ElementNode | LexicalNode = this;
627     while (true) {
628       const parent: ElementNode = node.getParentOrThrow();
629       if (parent === commonAncestor) {
630         indexA = node.getIndexWithinParent();
631         break;
632       }
633       node = parent;
634     }
635     node = targetNode;
636     while (true) {
637       const parent: ElementNode = node.getParentOrThrow();
638       if (parent === commonAncestor) {
639         indexB = node.getIndexWithinParent();
640         break;
641       }
642       node = parent;
643     }
644     return indexA < indexB;
645   }
646
647   /**
648    * Returns true if this node is the parent of the target node, false otherwise.
649    *
650    * @param targetNode - the would-be child node.
651    */
652   isParentOf(targetNode: LexicalNode): boolean {
653     const key = this.__key;
654     if (key === targetNode.__key) {
655       return false;
656     }
657     let node: ElementNode | LexicalNode | null = targetNode;
658     while (node !== null) {
659       if (node.__key === key) {
660         return true;
661       }
662       node = node.getParent();
663     }
664     return false;
665   }
666
667   // TO-DO: this function can be simplified a lot
668   /**
669    * Returns a list of nodes that are between this node and
670    * the target node in the EditorState.
671    *
672    * @param targetNode - the node that marks the other end of the range of nodes to be returned.
673    */
674   getNodesBetween(targetNode: LexicalNode): Array<LexicalNode> {
675     const isBefore = this.isBefore(targetNode);
676     const nodes = [];
677     const visited = new Set();
678     let node: LexicalNode | this | null = this;
679     while (true) {
680       if (node === null) {
681         break;
682       }
683       const key = node.__key;
684       if (!visited.has(key)) {
685         visited.add(key);
686         nodes.push(node);
687       }
688       if (node === targetNode) {
689         break;
690       }
691       const child: LexicalNode | null = $isElementNode(node)
692         ? isBefore
693           ? node.getFirstChild()
694           : node.getLastChild()
695         : null;
696       if (child !== null) {
697         node = child;
698         continue;
699       }
700       const nextSibling: LexicalNode | null = isBefore
701         ? node.getNextSibling()
702         : node.getPreviousSibling();
703       if (nextSibling !== null) {
704         node = nextSibling;
705         continue;
706       }
707       const parent: LexicalNode | null = node.getParentOrThrow();
708       if (!visited.has(parent.__key)) {
709         nodes.push(parent);
710       }
711       if (parent === targetNode) {
712         break;
713       }
714       let parentSibling = null;
715       let ancestor: LexicalNode | null = parent;
716       do {
717         if (ancestor === null) {
718           invariant(false, 'getNodesBetween: ancestor is null');
719         }
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);
727           }
728         } else {
729           break;
730         }
731       } while (parentSibling === null);
732       node = parentSibling;
733     }
734     if (!isBefore) {
735       nodes.reverse();
736     }
737     return nodes;
738   }
739
740   /**
741    * Returns true if this node has been marked dirty during this update cycle.
742    *
743    */
744   isDirty(): boolean {
745     const editor = getActiveEditor();
746     const dirtyLeaves = editor._dirtyLeaves;
747     return dirtyLeaves !== null && dirtyLeaves.has(this.__key);
748   }
749
750   /**
751    * Returns the latest version of the node from the active EditorState.
752    * This is used to avoid getting values from stale node references.
753    *
754    */
755   getLatest(): this {
756     const latest = $getNodeByKey<this>(this.__key);
757     if (latest === null) {
758       invariant(
759         false,
760         'Lexical node does not exist in active editor state. Avoid using the same node references between nested closures from editorState.read/editor.update.',
761       );
762     }
763     return latest;
764   }
765
766   /**
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.
770    *
771    */
772   getWritable(): this {
773     errorOnReadOnly();
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);
784     }
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);
788       return latestNode;
789     }
790     const mutableNode = $cloneWithProperties(latestNode);
791     cloneNotNeeded.add(key);
792     internalMarkNodeAsDirty(mutableNode);
793     // Update reference in node map
794     nodeMap.set(key, mutableNode);
795
796     return mutableNode;
797   }
798
799   /**
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)
803    *
804    */
805   getTextContent(): string {
806     return '';
807   }
808
809   /**
810    * Returns the length of the string produced by calling getTextContent on this node.
811    *
812    */
813   getTextContentSize(): number {
814     return this.getTextContent().length;
815   }
816
817   // View
818
819   /**
820    * Called during the reconciliation process to determine which nodes
821    * to insert into the DOM for this Lexical Node.
822    *
823    * This method must return exactly one HTMLElement. Nested elements are not supported.
824    *
825    * Do not attempt to update the Lexical EditorState during this phase of the update lifecyle.
826    *
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.
829    *
830    * */
831   createDOM(_config: EditorConfig, _editor: LexicalEditor): HTMLElement {
832     invariant(false, 'createDOM: base method not extended');
833   }
834
835   /**
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.
839    *
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,
842    * for instance.
843    *
844    * */
845   updateDOM(
846     _prevNode: unknown,
847     _dom: HTMLElement,
848     _config: EditorConfig,
849   ): boolean {
850     invariant(false, 'updateDOM: base method not extended');
851   }
852
853   /**
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.
859    *
860    * */
861   exportDOM(editor: LexicalEditor): DOMExportOutput {
862     const element = this.createDOM(editor._config, editor);
863     return {element};
864   }
865
866   /**
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).
871    *
872    * */
873   exportJSON(): SerializedLexicalNode {
874     invariant(false, 'exportJSON: base method not extended');
875   }
876
877   /**
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).
882    *
883    * */
884   static importJSON(_serializedNode: SerializedLexicalNode): LexicalNode {
885     invariant(
886       false,
887       'LexicalNode: Node %s does not implement .importJSON().',
888       this.name,
889     );
890   }
891   /**
892    * @experimental
893    *
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.
897    *
898    * Experimental - use at your own risk.
899    */
900   static transform(): ((node: LexicalNode) => void) | null {
901     return null;
902   }
903
904   // Setters and mutators
905
906   /**
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.
909    *
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}
913    * */
914   remove(preserveEmptyParent?: boolean): void {
915     $removeNode(this, true, preserveEmptyParent);
916   }
917
918   /**
919    * Replaces this LexicalNode with the provided node, optionally transferring the children
920    * of the replaced node to the replacing node.
921    *
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.
924    * */
925   replace<N extends LexicalNode>(replaceWith: N, includeChildren?: boolean): N {
926     errorOnReadOnly();
927     let selection = $getSelection();
928     if (selection !== null) {
929       selection = selection.clone();
930     }
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);
945
946     if (prevSibling === null) {
947       writableParent.__first = key;
948     } else {
949       const writablePrevSibling = prevSibling.getWritable();
950       writablePrevSibling.__next = key;
951     }
952     writableReplaceWith.__prev = prevKey;
953     if (nextSibling === null) {
954       writableParent.__last = key;
955     } else {
956       const writableNextSibling = nextSibling.getWritable();
957       writableNextSibling.__prev = key;
958     }
959     writableReplaceWith.__next = nextKey;
960     writableReplaceWith.__parent = parentKey;
961     writableParent.__size = size;
962     if (includeChildren) {
963       invariant(
964         $isElementNode(this) && $isElementNode(writableReplaceWith),
965         'includeChildren should only be true for ElementNodes',
966       );
967       this.getChildren().forEach((child: LexicalNode) => {
968         writableReplaceWith.append(child);
969       });
970     }
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);
977       }
978       if (focus.key === toReplaceKey) {
979         $moveSelectionPointToEnd(focus, writableReplaceWith);
980       }
981     }
982     if ($getCompositionKey() === toReplaceKey) {
983       $setCompositionKey(key);
984     }
985     return writableReplaceWith;
986   }
987
988   /**
989    * Inserts a node after this LexicalNode (as the next sibling).
990    *
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.
994    * */
995   insertAfter(nodeToInsert: LexicalNode, restoreSelection = true): LexicalNode {
996     errorOnReadOnly();
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;
1020       }
1021     }
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;
1028     } else {
1029       const writableNextSibling = nextSibling.getWritable();
1030       writableNextSibling.__prev = insertKey;
1031     }
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(
1040         selection,
1041         writableParent,
1042         index + 1,
1043       );
1044       const writableParentKey = writableParent.__key;
1045       if (elementAnchorSelectionOnNode) {
1046         selection.anchor.set(writableParentKey, index + 2, 'element');
1047       }
1048       if (elementFocusSelectionOnNode) {
1049         selection.focus.set(writableParentKey, index + 2, 'element');
1050       }
1051     }
1052     return nodeToInsert;
1053   }
1054
1055   /**
1056    * Inserts a node before this LexicalNode (as the previous sibling).
1057    *
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.
1061    * */
1062   insertBefore(
1063     nodeToInsert: LexicalNode,
1064     restoreSelection = true,
1065   ): LexicalNode {
1066     errorOnReadOnly();
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;
1079     } else {
1080       const writablePrevSibling = prevSibling.getWritable();
1081       writablePrevSibling.__next = insertKey;
1082     }
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);
1092     }
1093     return nodeToInsert;
1094   }
1095
1096   /**
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.
1100    *
1101    * */
1102   isParentRequired(): boolean {
1103     return false;
1104   }
1105
1106   /**
1107    * The creation logic for any required parent. Should be implemented if {@link isParentRequired} returns true.
1108    *
1109    * */
1110   createParentElementNode(): ElementNode {
1111     return $createParagraphNode();
1112   }
1113
1114   selectStart(): RangeSelection {
1115     return this.selectPrevious();
1116   }
1117
1118   selectEnd(): RangeSelection {
1119     return this.selectNext(0, 0);
1120   }
1121
1122   /**
1123    * Moves selection to the previous sibling of this node, at the specified offsets.
1124    *
1125    * @param anchorOffset - The anchor offset for selection.
1126    * @param focusOffset -  The focus offset for selection
1127    * */
1128   selectPrevious(anchorOffset?: number, focusOffset?: number): RangeSelection {
1129     errorOnReadOnly();
1130     const prevSibling = this.getPreviousSibling();
1131     const parent = this.getParentOrThrow();
1132     if (prevSibling === null) {
1133       return parent.select(0, 0);
1134     }
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);
1140     }
1141     return prevSibling.select(anchorOffset, focusOffset);
1142   }
1143
1144   /**
1145    * Moves selection to the next sibling of this node, at the specified offsets.
1146    *
1147    * @param anchorOffset - The anchor offset for selection.
1148    * @param focusOffset -  The focus offset for selection
1149    * */
1150   selectNext(anchorOffset?: number, focusOffset?: number): RangeSelection {
1151     errorOnReadOnly();
1152     const nextSibling = this.getNextSibling();
1153     const parent = this.getParentOrThrow();
1154     if (nextSibling === null) {
1155       return parent.select();
1156     }
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);
1162     }
1163     return nextSibling.select(anchorOffset, focusOffset);
1164   }
1165
1166   /**
1167    * Marks a node dirty, triggering transforms and
1168    * forcing it to be reconciled during the update cycle.
1169    *
1170    * */
1171   markDirty(): void {
1172     this.getWritable();
1173   }
1174
1175   /**
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.
1180    */
1181   insertDOMIntoParent(nodeDOM: HTMLElement, parentDOM: HTMLElement): boolean {
1182     return false;
1183   }
1184 }
1185
1186 function errorOnTypeKlassMismatch(
1187   type: string,
1188   klass: Klass<LexicalNode>,
1189 ): void {
1190   const registeredNode = getActiveEditor()._nodes.get(type);
1191   // Common error - split in its own invariant
1192   if (registeredNode === undefined) {
1193     invariant(
1194       false,
1195       'Create node: Attempted to create node %s that was not configured to be used on the editor.',
1196       klass.name,
1197     );
1198   }
1199   const editorKlass = registeredNode.klass;
1200   if (editorKlass !== klass) {
1201     invariant(
1202       false,
1203       'Create node: Type %s in node %s does not match registered node %s with the same type',
1204       type,
1205       klass.name,
1206       editorKlass.name,
1207     );
1208   }
1209 }
1210
1211 /**
1212  * Insert a series of nodes after this LexicalNode (as next siblings)
1213  *
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.
1217  */
1218 export function insertRangeAfter(
1219   node: LexicalNode,
1220   firstToInsert: LexicalNode,
1221   lastToInsert?: LexicalNode,
1222 ) {
1223   const lastToInsert2 =
1224     lastToInsert || firstToInsert.getParentOrThrow().getLastChild()!;
1225   let current = firstToInsert;
1226   const nodesToInsert = [firstToInsert];
1227   while (current !== lastToInsert2) {
1228     if (!current.getNextSibling()) {
1229       invariant(
1230         false,
1231         'insertRangeAfter: lastToInsert must be a later sibling of firstToInsert',
1232       );
1233     }
1234     current = current.getNextSibling()!;
1235     nodesToInsert.push(current);
1236   }
1237
1238   let currentNode: LexicalNode = node;
1239   for (const nodeToInsert of nodesToInsert) {
1240     currentNode = currentNode.insertAfter(nodeToInsert);
1241   }
1242 }