]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/lexical/core/LexicalNode.ts
Merge pull request #5917 from BookStackApp/copy_references
[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   declare ['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      * Indicate if this node should be selected directly instead of the default
388      * where the selection would descend to the nearest initial child element.
389      */
390   shouldSelectDirectly(): boolean {
391       return false;
392   }
393
394   /**
395    * Returns this nodes key.
396    */
397   getKey(): NodeKey {
398     // Key is stable between copies
399     return this.__key;
400   }
401
402   /**
403    * Returns the zero-based index of this node within the parent.
404    */
405   getIndexWithinParent(): number {
406     const parent = this.getParent();
407     if (parent === null) {
408       return -1;
409     }
410     let node = parent.getFirstChild();
411     let index = 0;
412     while (node !== null) {
413       if (this.is(node)) {
414         return index;
415       }
416       index++;
417       node = node.getNextSibling();
418     }
419     return -1;
420   }
421
422   /**
423    * Returns the parent of this node, or null if none is found.
424    */
425   getParent<T extends ElementNode>(): T | null {
426     const parent = this.getLatest().__parent;
427     if (parent === null) {
428       return null;
429     }
430     return $getNodeByKey<T>(parent);
431   }
432
433   /**
434    * Returns the parent of this node, or throws if none is found.
435    */
436   getParentOrThrow<T extends ElementNode>(): T {
437     const parent = this.getParent<T>();
438     if (parent === null) {
439       invariant(false, 'Expected node %s to have a parent.', this.__key);
440     }
441     return parent;
442   }
443
444   /**
445    * Returns the highest (in the EditorState tree)
446    * non-root ancestor of this node, or null if none is found. See {@link lexical!$isRootOrShadowRoot}
447    * for more information on which Elements comprise "roots".
448    */
449   getTopLevelElement(): ElementNode | DecoratorNode<unknown> | null {
450     let node: ElementNode | this | null = this;
451     while (node !== null) {
452       const parent: ElementNode | null = node.getParent();
453       if ($isRootOrShadowRoot(parent)) {
454         invariant(
455           $isElementNode(node) || (node === this && $isDecoratorNode(node)),
456           'Children of root nodes must be elements or decorators',
457         );
458         return node;
459       }
460       node = parent;
461     }
462     return null;
463   }
464
465   /**
466    * Returns the highest (in the EditorState tree)
467    * non-root ancestor of this node, or throws if none is found. See {@link lexical!$isRootOrShadowRoot}
468    * for more information on which Elements comprise "roots".
469    */
470   getTopLevelElementOrThrow(): ElementNode | DecoratorNode<unknown> {
471     const parent = this.getTopLevelElement();
472     if (parent === null) {
473       invariant(
474         false,
475         'Expected node %s to have a top parent element.',
476         this.__key,
477       );
478     }
479     return parent;
480   }
481
482   /**
483    * Returns a list of the every ancestor of this node,
484    * all the way up to the RootNode.
485    *
486    */
487   getParents(): Array<ElementNode> {
488     const parents: Array<ElementNode> = [];
489     let node = this.getParent();
490     while (node !== null) {
491       parents.push(node);
492       node = node.getParent();
493     }
494     return parents;
495   }
496
497   /**
498    * Returns a list of the keys of every ancestor of this node,
499    * all the way up to the RootNode.
500    *
501    */
502   getParentKeys(): Array<NodeKey> {
503     const parents = [];
504     let node = this.getParent();
505     while (node !== null) {
506       parents.push(node.__key);
507       node = node.getParent();
508     }
509     return parents;
510   }
511
512   /**
513    * Returns the "previous" siblings - that is, the node that comes
514    * before this one in the same parent.
515    *
516    */
517   getPreviousSibling<T extends LexicalNode>(): T | null {
518     const self = this.getLatest();
519     const prevKey = self.__prev;
520     return prevKey === null ? null : $getNodeByKey<T>(prevKey);
521   }
522
523   /**
524    * Returns the "previous" siblings - that is, the nodes that come between
525    * this one and the first child of it's parent, inclusive.
526    *
527    */
528   getPreviousSiblings<T extends LexicalNode>(): Array<T> {
529     const siblings: Array<T> = [];
530     const parent = this.getParent();
531     if (parent === null) {
532       return siblings;
533     }
534     let node: null | T = parent.getFirstChild();
535     while (node !== null) {
536       if (node.is(this)) {
537         break;
538       }
539       siblings.push(node);
540       node = node.getNextSibling();
541     }
542     return siblings;
543   }
544
545   /**
546    * Returns the "next" siblings - that is, the node that comes
547    * after this one in the same parent
548    *
549    */
550   getNextSibling<T extends LexicalNode>(): T | null {
551     const self = this.getLatest();
552     const nextKey = self.__next;
553     return nextKey === null ? null : $getNodeByKey<T>(nextKey);
554   }
555
556   /**
557    * Returns all "next" siblings - that is, the nodes that come between this
558    * one and the last child of it's parent, inclusive.
559    *
560    */
561   getNextSiblings<T extends LexicalNode>(): Array<T> {
562     const siblings: Array<T> = [];
563     let node: null | T = this.getNextSibling();
564     while (node !== null) {
565       siblings.push(node);
566       node = node.getNextSibling();
567     }
568     return siblings;
569   }
570
571   /**
572    * Returns the closest common ancestor of this node and the provided one or null
573    * if one cannot be found.
574    *
575    * @param node - the other node to find the common ancestor of.
576    */
577   getCommonAncestor<T extends ElementNode = ElementNode>(
578     node: LexicalNode,
579   ): T | null {
580     const a = this.getParents();
581     const b = node.getParents();
582     if ($isElementNode(this)) {
583       a.unshift(this);
584     }
585     if ($isElementNode(node)) {
586       b.unshift(node);
587     }
588     const aLength = a.length;
589     const bLength = b.length;
590     if (aLength === 0 || bLength === 0 || a[aLength - 1] !== b[bLength - 1]) {
591       return null;
592     }
593     const bSet = new Set(b);
594     for (let i = 0; i < aLength; i++) {
595       const ancestor = a[i] as T;
596       if (bSet.has(ancestor)) {
597         return ancestor;
598       }
599     }
600     return null;
601   }
602
603   /**
604    * Returns true if the provided node is the exact same one as this node, from Lexical's perspective.
605    * Always use this instead of referential equality.
606    *
607    * @param object - the node to perform the equality comparison on.
608    */
609   is(object: LexicalNode | null | undefined): boolean {
610     if (object == null) {
611       return false;
612     }
613     return this.__key === object.__key;
614   }
615
616   /**
617    * Returns true if this node logical precedes the target node in the editor state.
618    *
619    * @param targetNode - the node we're testing to see if it's after this one.
620    */
621   isBefore(targetNode: LexicalNode): boolean {
622     if (this === targetNode) {
623       return false;
624     }
625     if (targetNode.isParentOf(this)) {
626       return true;
627     }
628     if (this.isParentOf(targetNode)) {
629       return false;
630     }
631     const commonAncestor = this.getCommonAncestor(targetNode);
632     let indexA = 0;
633     let indexB = 0;
634     let node: this | ElementNode | LexicalNode = this;
635     while (true) {
636       const parent: ElementNode = node.getParentOrThrow();
637       if (parent === commonAncestor) {
638         indexA = node.getIndexWithinParent();
639         break;
640       }
641       node = parent;
642     }
643     node = targetNode;
644     while (true) {
645       const parent: ElementNode = node.getParentOrThrow();
646       if (parent === commonAncestor) {
647         indexB = node.getIndexWithinParent();
648         break;
649       }
650       node = parent;
651     }
652     return indexA < indexB;
653   }
654
655   /**
656    * Returns true if this node is the parent of the target node, false otherwise.
657    *
658    * @param targetNode - the would-be child node.
659    */
660   isParentOf(targetNode: LexicalNode): boolean {
661     const key = this.__key;
662     if (key === targetNode.__key) {
663       return false;
664     }
665     let node: ElementNode | LexicalNode | null = targetNode;
666     while (node !== null) {
667       if (node.__key === key) {
668         return true;
669       }
670       node = node.getParent();
671     }
672     return false;
673   }
674
675   // TO-DO: this function can be simplified a lot
676   /**
677    * Returns a list of nodes that are between this node and
678    * the target node in the EditorState.
679    *
680    * @param targetNode - the node that marks the other end of the range of nodes to be returned.
681    */
682   getNodesBetween(targetNode: LexicalNode): Array<LexicalNode> {
683     const isBefore = this.isBefore(targetNode);
684     const nodes = [];
685     const visited = new Set();
686     let node: LexicalNode | this | null = this;
687     while (true) {
688       if (node === null) {
689         break;
690       }
691       const key = node.__key;
692       if (!visited.has(key)) {
693         visited.add(key);
694         nodes.push(node);
695       }
696       if (node === targetNode) {
697         break;
698       }
699       const child: LexicalNode | null = $isElementNode(node)
700         ? isBefore
701           ? node.getFirstChild()
702           : node.getLastChild()
703         : null;
704       if (child !== null) {
705         node = child;
706         continue;
707       }
708       const nextSibling: LexicalNode | null = isBefore
709         ? node.getNextSibling()
710         : node.getPreviousSibling();
711       if (nextSibling !== null) {
712         node = nextSibling;
713         continue;
714       }
715       const parent: LexicalNode | null = node.getParentOrThrow();
716       if (!visited.has(parent.__key)) {
717         nodes.push(parent);
718       }
719       if (parent === targetNode) {
720         break;
721       }
722       let parentSibling = null;
723       let ancestor: LexicalNode | null = parent;
724       do {
725         if (ancestor === null) {
726           invariant(false, 'getNodesBetween: ancestor is null');
727         }
728         parentSibling = isBefore
729           ? ancestor.getNextSibling()
730           : ancestor.getPreviousSibling();
731         ancestor = ancestor.getParent();
732         if (ancestor !== null) {
733           if (parentSibling === null && !visited.has(ancestor.__key)) {
734             nodes.push(ancestor);
735           }
736         } else {
737           break;
738         }
739       } while (parentSibling === null);
740       node = parentSibling;
741     }
742     if (!isBefore) {
743       nodes.reverse();
744     }
745     return nodes;
746   }
747
748   /**
749    * Returns true if this node has been marked dirty during this update cycle.
750    *
751    */
752   isDirty(): boolean {
753     const editor = getActiveEditor();
754     const dirtyLeaves = editor._dirtyLeaves;
755     return dirtyLeaves !== null && dirtyLeaves.has(this.__key);
756   }
757
758   /**
759    * Returns the latest version of the node from the active EditorState.
760    * This is used to avoid getting values from stale node references.
761    *
762    */
763   getLatest(): this {
764     const latest = $getNodeByKey<this>(this.__key);
765     if (latest === null) {
766       invariant(
767         false,
768         'Lexical node does not exist in active editor state. Avoid using the same node references between nested closures from editorState.read/editor.update.',
769       );
770     }
771     return latest;
772   }
773
774   /**
775    * Returns a mutable version of the node using {@link $cloneWithProperties}
776    * if necessary. Will throw an error if called outside of a Lexical Editor
777    * {@link LexicalEditor.update} callback.
778    *
779    */
780   getWritable(): this {
781     errorOnReadOnly();
782     const editorState = getActiveEditorState();
783     const editor = getActiveEditor();
784     const nodeMap = editorState._nodeMap;
785     const key = this.__key;
786     // Ensure we get the latest node from pending state
787     const latestNode = this.getLatest();
788     const cloneNotNeeded = editor._cloneNotNeeded;
789     const selection = $getSelection();
790     if (selection !== null) {
791       selection.setCachedNodes(null);
792     }
793     if (cloneNotNeeded.has(key)) {
794       // Transforms clear the dirty node set on each iteration to keep track on newly dirty nodes
795       internalMarkNodeAsDirty(latestNode);
796       return latestNode;
797     }
798     const mutableNode = $cloneWithProperties(latestNode);
799     cloneNotNeeded.add(key);
800     internalMarkNodeAsDirty(mutableNode);
801     // Update reference in node map
802     nodeMap.set(key, mutableNode);
803
804     return mutableNode;
805   }
806
807   /**
808    * Returns the text content of the node. Override this for
809    * custom nodes that should have a representation in plain text
810    * format (for copy + paste, for example)
811    *
812    */
813   getTextContent(): string {
814     return '';
815   }
816
817   /**
818    * Returns the length of the string produced by calling getTextContent on this node.
819    *
820    */
821   getTextContentSize(): number {
822     return this.getTextContent().length;
823   }
824
825   // View
826
827   /**
828    * Called during the reconciliation process to determine which nodes
829    * to insert into the DOM for this Lexical Node.
830    *
831    * This method must return exactly one HTMLElement. Nested elements are not supported.
832    *
833    * Do not attempt to update the Lexical EditorState during this phase of the update lifecyle.
834    *
835    * @param _config - allows access to things like the EditorTheme (to apply classes) during reconciliation.
836    * @param _editor - allows access to the editor for context during reconciliation.
837    *
838    * */
839   createDOM(_config: EditorConfig, _editor: LexicalEditor): HTMLElement {
840     invariant(false, 'createDOM: base method not extended');
841   }
842
843   /**
844    * Called when a node changes and should update the DOM
845    * in whatever way is necessary to make it align with any changes that might
846    * have happened during the update.
847    *
848    * Returning "true" here will cause lexical to unmount and recreate the DOM node
849    * (by calling createDOM). You would need to do this if the element tag changes,
850    * for instance.
851    *
852    * */
853   updateDOM(
854     _prevNode: unknown,
855     _dom: HTMLElement,
856     _config: EditorConfig,
857   ): boolean {
858     invariant(false, 'updateDOM: base method not extended');
859   }
860
861   /**
862    * Controls how the this node is serialized to HTML. This is important for
863    * copy and paste between Lexical and non-Lexical editors, or Lexical editors with different namespaces,
864    * in which case the primary transfer format is HTML. It's also important if you're serializing
865    * to HTML for any other reason via {@link @lexical/html!$generateHtmlFromNodes}. You could
866    * also use this method to build your own HTML renderer.
867    *
868    * */
869   exportDOM(editor: LexicalEditor): DOMExportOutput {
870     const element = this.createDOM(editor._config, editor);
871     return {element};
872   }
873
874   /**
875    * Controls how the this node is serialized to JSON. This is important for
876    * copy and paste between Lexical editors sharing the same namespace. It's also important
877    * if you're serializing to JSON for persistent storage somewhere.
878    * See [Serialization & Deserialization](https://lexical.dev/docs/concepts/serialization#lexical---html).
879    *
880    * */
881   exportJSON(): SerializedLexicalNode {
882     invariant(false, 'exportJSON: base method not extended');
883   }
884
885   /**
886    * Controls how the this node is deserialized from JSON. This is usually boilerplate,
887    * but provides an abstraction between the node implementation and serialized interface that can
888    * be important if you ever make breaking changes to a node schema (by adding or removing properties).
889    * See [Serialization & Deserialization](https://lexical.dev/docs/concepts/serialization#lexical---html).
890    *
891    * */
892   static importJSON(_serializedNode: SerializedLexicalNode): LexicalNode {
893     invariant(
894       false,
895       'LexicalNode: Node %s does not implement .importJSON().',
896       this.name,
897     );
898   }
899   /**
900    * @experimental
901    *
902    * Registers the returned function as a transform on the node during
903    * Editor initialization. Most such use cases should be addressed via
904    * the {@link LexicalEditor.registerNodeTransform} API.
905    *
906    * Experimental - use at your own risk.
907    */
908   static transform(): ((node: LexicalNode) => void) | null {
909     return null;
910   }
911
912   // Setters and mutators
913
914   /**
915    * Removes this LexicalNode from the EditorState. If the node isn't re-inserted
916    * somewhere, the Lexical garbage collector will eventually clean it up.
917    *
918    * @param preserveEmptyParent - If falsy, the node's parent will be removed if
919    * it's empty after the removal operation. This is the default behavior, subject to
920    * other node heuristics such as {@link ElementNode#canBeEmpty}
921    * */
922   remove(preserveEmptyParent?: boolean): void {
923     $removeNode(this, true, preserveEmptyParent);
924   }
925
926   /**
927    * Replaces this LexicalNode with the provided node, optionally transferring the children
928    * of the replaced node to the replacing node.
929    *
930    * @param replaceWith - The node to replace this one with.
931    * @param includeChildren - Whether or not to transfer the children of this node to the replacing node.
932    * */
933   replace<N extends LexicalNode>(replaceWith: N, includeChildren?: boolean): N {
934     errorOnReadOnly();
935     let selection = $getSelection();
936     if (selection !== null) {
937       selection = selection.clone();
938     }
939     errorOnInsertTextNodeOnRoot(this, replaceWith);
940     const self = this.getLatest();
941     const toReplaceKey = this.__key;
942     const key = replaceWith.__key;
943     const writableReplaceWith = replaceWith.getWritable();
944     const writableParent = this.getParentOrThrow().getWritable();
945     const size = writableParent.__size;
946     removeFromParent(writableReplaceWith);
947     const prevSibling = self.getPreviousSibling();
948     const nextSibling = self.getNextSibling();
949     const prevKey = self.__prev;
950     const nextKey = self.__next;
951     const parentKey = self.__parent;
952     $removeNode(self, false, true);
953
954     if (prevSibling === null) {
955       writableParent.__first = key;
956     } else {
957       const writablePrevSibling = prevSibling.getWritable();
958       writablePrevSibling.__next = key;
959     }
960     writableReplaceWith.__prev = prevKey;
961     if (nextSibling === null) {
962       writableParent.__last = key;
963     } else {
964       const writableNextSibling = nextSibling.getWritable();
965       writableNextSibling.__prev = key;
966     }
967     writableReplaceWith.__next = nextKey;
968     writableReplaceWith.__parent = parentKey;
969     writableParent.__size = size;
970     if (includeChildren) {
971       invariant(
972         $isElementNode(this) && $isElementNode(writableReplaceWith),
973         'includeChildren should only be true for ElementNodes',
974       );
975       this.getChildren().forEach((child: LexicalNode) => {
976         writableReplaceWith.append(child);
977       });
978     }
979     if ($isRangeSelection(selection)) {
980       $setSelection(selection);
981       const anchor = selection.anchor;
982       const focus = selection.focus;
983       if (anchor.key === toReplaceKey) {
984         $moveSelectionPointToEnd(anchor, writableReplaceWith);
985       }
986       if (focus.key === toReplaceKey) {
987         $moveSelectionPointToEnd(focus, writableReplaceWith);
988       }
989     }
990     if ($getCompositionKey() === toReplaceKey) {
991       $setCompositionKey(key);
992     }
993     return writableReplaceWith;
994   }
995
996   /**
997    * Inserts a node after this LexicalNode (as the next sibling).
998    *
999    * @param nodeToInsert - The node to insert after this one.
1000    * @param restoreSelection - Whether or not to attempt to resolve the
1001    * selection to the appropriate place after the operation is complete.
1002    * */
1003   insertAfter(nodeToInsert: LexicalNode, restoreSelection = true): LexicalNode {
1004     errorOnReadOnly();
1005     errorOnInsertTextNodeOnRoot(this, nodeToInsert);
1006     const writableSelf = this.getWritable();
1007     const writableNodeToInsert = nodeToInsert.getWritable();
1008     const oldParent = writableNodeToInsert.getParent();
1009     const selection = $getSelection();
1010     let elementAnchorSelectionOnNode = false;
1011     let elementFocusSelectionOnNode = false;
1012     if (oldParent !== null) {
1013       // TODO: this is O(n), can we improve?
1014       const oldIndex = nodeToInsert.getIndexWithinParent();
1015       removeFromParent(writableNodeToInsert);
1016       if ($isRangeSelection(selection)) {
1017         const oldParentKey = oldParent.__key;
1018         const anchor = selection.anchor;
1019         const focus = selection.focus;
1020         elementAnchorSelectionOnNode =
1021           anchor.type === 'element' &&
1022           anchor.key === oldParentKey &&
1023           anchor.offset === oldIndex + 1;
1024         elementFocusSelectionOnNode =
1025           focus.type === 'element' &&
1026           focus.key === oldParentKey &&
1027           focus.offset === oldIndex + 1;
1028       }
1029     }
1030     const nextSibling = this.getNextSibling();
1031     const writableParent = this.getParentOrThrow().getWritable();
1032     const insertKey = writableNodeToInsert.__key;
1033     const nextKey = writableSelf.__next;
1034     if (nextSibling === null) {
1035       writableParent.__last = insertKey;
1036     } else {
1037       const writableNextSibling = nextSibling.getWritable();
1038       writableNextSibling.__prev = insertKey;
1039     }
1040     writableParent.__size++;
1041     writableSelf.__next = insertKey;
1042     writableNodeToInsert.__next = nextKey;
1043     writableNodeToInsert.__prev = writableSelf.__key;
1044     writableNodeToInsert.__parent = writableSelf.__parent;
1045     if (restoreSelection && $isRangeSelection(selection)) {
1046       const index = this.getIndexWithinParent();
1047       $updateElementSelectionOnCreateDeleteNode(
1048         selection,
1049         writableParent,
1050         index + 1,
1051       );
1052       const writableParentKey = writableParent.__key;
1053       if (elementAnchorSelectionOnNode) {
1054         selection.anchor.set(writableParentKey, index + 2, 'element');
1055       }
1056       if (elementFocusSelectionOnNode) {
1057         selection.focus.set(writableParentKey, index + 2, 'element');
1058       }
1059     }
1060     return nodeToInsert;
1061   }
1062
1063   /**
1064    * Inserts a node before this LexicalNode (as the previous sibling).
1065    *
1066    * @param nodeToInsert - The node to insert before this one.
1067    * @param restoreSelection - Whether or not to attempt to resolve the
1068    * selection to the appropriate place after the operation is complete.
1069    * */
1070   insertBefore(
1071     nodeToInsert: LexicalNode,
1072     restoreSelection = true,
1073   ): LexicalNode {
1074     errorOnReadOnly();
1075     errorOnInsertTextNodeOnRoot(this, nodeToInsert);
1076     const writableSelf = this.getWritable();
1077     const writableNodeToInsert = nodeToInsert.getWritable();
1078     const insertKey = writableNodeToInsert.__key;
1079     removeFromParent(writableNodeToInsert);
1080     const prevSibling = this.getPreviousSibling();
1081     const writableParent = this.getParentOrThrow().getWritable();
1082     const prevKey = writableSelf.__prev;
1083     // TODO: this is O(n), can we improve?
1084     const index = this.getIndexWithinParent();
1085     if (prevSibling === null) {
1086       writableParent.__first = insertKey;
1087     } else {
1088       const writablePrevSibling = prevSibling.getWritable();
1089       writablePrevSibling.__next = insertKey;
1090     }
1091     writableParent.__size++;
1092     writableSelf.__prev = insertKey;
1093     writableNodeToInsert.__prev = prevKey;
1094     writableNodeToInsert.__next = writableSelf.__key;
1095     writableNodeToInsert.__parent = writableSelf.__parent;
1096     const selection = $getSelection();
1097     if (restoreSelection && $isRangeSelection(selection)) {
1098       const parent = this.getParentOrThrow();
1099       $updateElementSelectionOnCreateDeleteNode(selection, parent, index);
1100     }
1101     return nodeToInsert;
1102   }
1103
1104   /**
1105    * Whether or not this node has a required parent. Used during copy + paste operations
1106    * to normalize nodes that would otherwise be orphaned. For example, ListItemNodes without
1107    * a ListNode parent or TextNodes with a ParagraphNode parent.
1108    *
1109    * */
1110   isParentRequired(): boolean {
1111     return false;
1112   }
1113
1114   /**
1115    * The creation logic for any required parent. Should be implemented if {@link isParentRequired} returns true.
1116    *
1117    * */
1118   createParentElementNode(): ElementNode {
1119     return $createParagraphNode();
1120   }
1121
1122   selectStart(): RangeSelection {
1123     return this.selectPrevious();
1124   }
1125
1126   selectEnd(): RangeSelection {
1127     return this.selectNext(0, 0);
1128   }
1129
1130   /**
1131    * Moves selection to the previous sibling of this node, at the specified offsets.
1132    *
1133    * @param anchorOffset - The anchor offset for selection.
1134    * @param focusOffset -  The focus offset for selection
1135    * */
1136   selectPrevious(anchorOffset?: number, focusOffset?: number): RangeSelection {
1137     errorOnReadOnly();
1138     const prevSibling = this.getPreviousSibling();
1139     const parent = this.getParentOrThrow();
1140     if (prevSibling === null) {
1141       return parent.select(0, 0);
1142     }
1143     if ($isElementNode(prevSibling)) {
1144       return prevSibling.select();
1145     } else if (!$isTextNode(prevSibling)) {
1146       const index = prevSibling.getIndexWithinParent() + 1;
1147       return parent.select(index, index);
1148     }
1149     return prevSibling.select(anchorOffset, focusOffset);
1150   }
1151
1152   /**
1153    * Moves selection to the next sibling of this node, at the specified offsets.
1154    *
1155    * @param anchorOffset - The anchor offset for selection.
1156    * @param focusOffset -  The focus offset for selection
1157    * */
1158   selectNext(anchorOffset?: number, focusOffset?: number): RangeSelection {
1159     errorOnReadOnly();
1160     const nextSibling = this.getNextSibling();
1161     const parent = this.getParentOrThrow();
1162     if (nextSibling === null) {
1163       return parent.select();
1164     }
1165     if ($isElementNode(nextSibling)) {
1166       return nextSibling.select(0, 0);
1167     } else if (!$isTextNode(nextSibling)) {
1168       const index = nextSibling.getIndexWithinParent();
1169       return parent.select(index, index);
1170     }
1171     return nextSibling.select(anchorOffset, focusOffset);
1172   }
1173
1174   /**
1175    * Marks a node dirty, triggering transforms and
1176    * forcing it to be reconciled during the update cycle.
1177    *
1178    * */
1179   markDirty(): void {
1180     this.getWritable();
1181   }
1182
1183   /**
1184    * Insert the DOM of this node into that of the parent.
1185    * Allows this node to implement custom DOM attachment logic.
1186    * Boolean result indicates if the insertion was handled by the function.
1187    * A true return value prevents default insertion logic from taking place.
1188    */
1189   insertDOMIntoParent(nodeDOM: HTMLElement, parentDOM: HTMLElement): boolean {
1190     return false;
1191   }
1192 }
1193
1194 function errorOnTypeKlassMismatch(
1195   type: string,
1196   klass: Klass<LexicalNode>,
1197 ): void {
1198   const registeredNode = getActiveEditor()._nodes.get(type);
1199   // Common error - split in its own invariant
1200   if (registeredNode === undefined) {
1201     invariant(
1202       false,
1203       'Create node: Attempted to create node %s that was not configured to be used on the editor.',
1204       klass.name,
1205     );
1206   }
1207   const editorKlass = registeredNode.klass;
1208   if (editorKlass !== klass) {
1209     invariant(
1210       false,
1211       'Create node: Type %s in node %s does not match registered node %s with the same type',
1212       type,
1213       klass.name,
1214       editorKlass.name,
1215     );
1216   }
1217 }
1218
1219 /**
1220  * Insert a series of nodes after this LexicalNode (as next siblings)
1221  *
1222  * @param firstToInsert - The first node to insert after this one.
1223  * @param lastToInsert - The last node to insert after this one. Must be a
1224  * later sibling of FirstNode. If not provided, it will be its last sibling.
1225  */
1226 export function insertRangeAfter(
1227   node: LexicalNode,
1228   firstToInsert: LexicalNode,
1229   lastToInsert?: LexicalNode,
1230 ) {
1231   const lastToInsert2 =
1232     lastToInsert || firstToInsert.getParentOrThrow().getLastChild()!;
1233   let current = firstToInsert;
1234   const nodesToInsert = [firstToInsert];
1235   while (current !== lastToInsert2) {
1236     if (!current.getNextSibling()) {
1237       invariant(
1238         false,
1239         'insertRangeAfter: lastToInsert must be a later sibling of firstToInsert',
1240       );
1241     }
1242     current = current.getNextSibling()!;
1243     nodesToInsert.push(current);
1244   }
1245
1246   let currentNode: LexicalNode = node;
1247   for (const nodeToInsert of nodesToInsert) {
1248     currentNode = currentNode.insertAfter(nodeToInsert);
1249   }
1250 }