]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/lexical/core/LexicalNode.ts
a6c9b6023e534eab3f343be79124b92492031c9a
[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 export type DOMConversionOutput = {
151   after?: (childLexicalNodes: Array<LexicalNode>) => Array<LexicalNode>;
152   forChild?: DOMChildConversion;
153   node: null | LexicalNode | Array<LexicalNode> | 'ignore';
154 };
155
156 export type DOMExportOutputMap = Map<
157   Klass<LexicalNode>,
158   (editor: LexicalEditor, target: LexicalNode) => DOMExportOutput
159 >;
160
161 export type DOMExportOutput = {
162   after?: (
163     generatedElement: HTMLElement | Text | null | undefined,
164   ) => HTMLElement | Text | null | undefined;
165   element: HTMLElement | Text | null;
166 };
167
168 export type NodeKey = string;
169
170 export class LexicalNode {
171   // Allow us to look up the type including static props
172   ['constructor']!: KlassConstructor<typeof LexicalNode>;
173   /** @internal */
174   __type: string;
175   /** @internal */
176   //@ts-ignore We set the key in the constructor.
177   __key: string;
178   /** @internal */
179   __parent: null | NodeKey;
180   /** @internal */
181   __prev: null | NodeKey;
182   /** @internal */
183   __next: null | NodeKey;
184
185   // Flow doesn't support abstract classes unfortunately, so we can't _force_
186   // subclasses of Node to implement statics. All subclasses of Node should have
187   // a static getType and clone method though. We define getType and clone here so we can call it
188   // on any  Node, and we throw this error by default since the subclass should provide
189   // their own implementation.
190   /**
191    * Returns the string type of this node. Every node must
192    * implement this and it MUST BE UNIQUE amongst nodes registered
193    * on the editor.
194    *
195    */
196   static getType(): string {
197     invariant(
198       false,
199       'LexicalNode: Node %s does not implement .getType().',
200       this.name,
201     );
202   }
203
204   /**
205    * Clones this node, creating a new node with a different key
206    * and adding it to the EditorState (but not attaching it anywhere!). All nodes must
207    * implement this method.
208    *
209    */
210   static clone(_data: unknown): LexicalNode {
211     invariant(
212       false,
213       'LexicalNode: Node %s does not implement .clone().',
214       this.name,
215     );
216   }
217
218   /**
219    * Perform any state updates on the clone of prevNode that are not already
220    * handled by the constructor call in the static clone method. If you have
221    * state to update in your clone that is not handled directly by the
222    * constructor, it is advisable to override this method but it is required
223    * to include a call to `super.afterCloneFrom(prevNode)` in your
224    * implementation. This is only intended to be called by
225    * {@link $cloneWithProperties} function or via a super call.
226    *
227    * @example
228    * ```ts
229    * class ClassesTextNode extends TextNode {
230    *   // Not shown: static getType, static importJSON, exportJSON, createDOM, updateDOM
231    *   __classes = new Set<string>();
232    *   static clone(node: ClassesTextNode): ClassesTextNode {
233    *     // The inherited TextNode constructor is used here, so
234    *     // classes is not set by this method.
235    *     return new ClassesTextNode(node.__text, node.__key);
236    *   }
237    *   afterCloneFrom(node: this): void {
238    *     // This calls TextNode.afterCloneFrom and LexicalNode.afterCloneFrom
239    *     // for necessary state updates
240    *     super.afterCloneFrom(node);
241    *     this.__addClasses(node.__classes);
242    *   }
243    *   // This method is a private implementation detail, it is not
244    *   // suitable for the public API because it does not call getWritable
245    *   __addClasses(classNames: Iterable<string>): this {
246    *     for (const className of classNames) {
247    *       this.__classes.add(className);
248    *     }
249    *     return this;
250    *   }
251    *   addClass(...classNames: string[]): this {
252    *     return this.getWritable().__addClasses(classNames);
253    *   }
254    *   removeClass(...classNames: string[]): this {
255    *     const node = this.getWritable();
256    *     for (const className of classNames) {
257    *       this.__classes.delete(className);
258    *     }
259    *     return this;
260    *   }
261    *   getClasses(): Set<string> {
262    *     return this.getLatest().__classes;
263    *   }
264    * }
265    * ```
266    *
267    */
268   afterCloneFrom(prevNode: this) {
269     this.__parent = prevNode.__parent;
270     this.__next = prevNode.__next;
271     this.__prev = prevNode.__prev;
272   }
273
274   // eslint-disable-next-line @typescript-eslint/no-explicit-any
275   static importDOM?: () => DOMConversionMap<any> | null;
276
277   constructor(key?: NodeKey) {
278     this.__type = this.constructor.getType();
279     this.__parent = null;
280     this.__prev = null;
281     this.__next = null;
282     $setNodeKey(this, key);
283
284     if (__DEV__) {
285       if (this.__type !== 'root') {
286         errorOnReadOnly();
287         errorOnTypeKlassMismatch(this.__type, this.constructor);
288       }
289     }
290   }
291   // Getters and Traversers
292
293   /**
294    * Returns the string type of this node.
295    */
296   getType(): string {
297     return this.__type;
298   }
299
300   isInline(): boolean {
301     invariant(
302       false,
303       'LexicalNode: Node %s does not implement .isInline().',
304       this.constructor.name,
305     );
306   }
307
308   /**
309    * Returns true if there is a path between this node and the RootNode, false otherwise.
310    * This is a way of determining if the node is "attached" EditorState. Unattached nodes
311    * won't be reconciled and will ultimatelt be cleaned up by the Lexical GC.
312    */
313   isAttached(): boolean {
314     let nodeKey: string | null = this.__key;
315     while (nodeKey !== null) {
316       if (nodeKey === 'root') {
317         return true;
318       }
319
320       const node: LexicalNode | null = $getNodeByKey(nodeKey);
321
322       if (node === null) {
323         break;
324       }
325       nodeKey = node.__parent;
326     }
327     return false;
328   }
329
330   /**
331    * Returns true if this node is contained within the provided Selection., false otherwise.
332    * Relies on the algorithms implemented in {@link BaseSelection.getNodes} to determine
333    * what's included.
334    *
335    * @param selection - The selection that we want to determine if the node is in.
336    */
337   isSelected(selection?: null | BaseSelection): boolean {
338     const targetSelection = selection || $getSelection();
339     if (targetSelection == null) {
340       return false;
341     }
342
343     const isSelected = targetSelection
344       .getNodes()
345       .some((n) => n.__key === this.__key);
346
347     if ($isTextNode(this)) {
348       return isSelected;
349     }
350     // For inline images inside of element nodes.
351     // Without this change the image will be selected if the cursor is before or after it.
352     const isElementRangeSelection =
353       $isRangeSelection(targetSelection) &&
354       targetSelection.anchor.type === 'element' &&
355       targetSelection.focus.type === 'element';
356
357     if (isElementRangeSelection) {
358       if (targetSelection.isCollapsed()) {
359         return false;
360       }
361
362       const parentNode = this.getParent();
363       if ($isDecoratorNode(this) && this.isInline() && parentNode) {
364         const firstPoint = targetSelection.isBackward()
365           ? targetSelection.focus
366           : targetSelection.anchor;
367         const firstElement = firstPoint.getNode() as ElementNode;
368         if (
369           firstPoint.offset === firstElement.getChildrenSize() &&
370           firstElement.is(parentNode) &&
371           firstElement.getLastChildOrThrow().is(this)
372         ) {
373           return false;
374         }
375       }
376     }
377     return isSelected;
378   }
379
380   /**
381    * Returns this nodes key.
382    */
383   getKey(): NodeKey {
384     // Key is stable between copies
385     return this.__key;
386   }
387
388   /**
389    * Returns the zero-based index of this node within the parent.
390    */
391   getIndexWithinParent(): number {
392     const parent = this.getParent();
393     if (parent === null) {
394       return -1;
395     }
396     let node = parent.getFirstChild();
397     let index = 0;
398     while (node !== null) {
399       if (this.is(node)) {
400         return index;
401       }
402       index++;
403       node = node.getNextSibling();
404     }
405     return -1;
406   }
407
408   /**
409    * Returns the parent of this node, or null if none is found.
410    */
411   getParent<T extends ElementNode>(): T | null {
412     const parent = this.getLatest().__parent;
413     if (parent === null) {
414       return null;
415     }
416     return $getNodeByKey<T>(parent);
417   }
418
419   /**
420    * Returns the parent of this node, or throws if none is found.
421    */
422   getParentOrThrow<T extends ElementNode>(): T {
423     const parent = this.getParent<T>();
424     if (parent === null) {
425       invariant(false, 'Expected node %s to have a parent.', this.__key);
426     }
427     return parent;
428   }
429
430   /**
431    * Returns the highest (in the EditorState tree)
432    * non-root ancestor of this node, or null if none is found. See {@link lexical!$isRootOrShadowRoot}
433    * for more information on which Elements comprise "roots".
434    */
435   getTopLevelElement(): ElementNode | DecoratorNode<unknown> | null {
436     let node: ElementNode | this | null = this;
437     while (node !== null) {
438       const parent: ElementNode | null = node.getParent();
439       if ($isRootOrShadowRoot(parent)) {
440         invariant(
441           $isElementNode(node) || (node === this && $isDecoratorNode(node)),
442           'Children of root nodes must be elements or decorators',
443         );
444         return node;
445       }
446       node = parent;
447     }
448     return null;
449   }
450
451   /**
452    * Returns the highest (in the EditorState tree)
453    * non-root ancestor of this node, or throws if none is found. See {@link lexical!$isRootOrShadowRoot}
454    * for more information on which Elements comprise "roots".
455    */
456   getTopLevelElementOrThrow(): ElementNode | DecoratorNode<unknown> {
457     const parent = this.getTopLevelElement();
458     if (parent === null) {
459       invariant(
460         false,
461         'Expected node %s to have a top parent element.',
462         this.__key,
463       );
464     }
465     return parent;
466   }
467
468   /**
469    * Returns a list of the every ancestor of this node,
470    * all the way up to the RootNode.
471    *
472    */
473   getParents(): Array<ElementNode> {
474     const parents: Array<ElementNode> = [];
475     let node = this.getParent();
476     while (node !== null) {
477       parents.push(node);
478       node = node.getParent();
479     }
480     return parents;
481   }
482
483   /**
484    * Returns a list of the keys of every ancestor of this node,
485    * all the way up to the RootNode.
486    *
487    */
488   getParentKeys(): Array<NodeKey> {
489     const parents = [];
490     let node = this.getParent();
491     while (node !== null) {
492       parents.push(node.__key);
493       node = node.getParent();
494     }
495     return parents;
496   }
497
498   /**
499    * Returns the "previous" siblings - that is, the node that comes
500    * before this one in the same parent.
501    *
502    */
503   getPreviousSibling<T extends LexicalNode>(): T | null {
504     const self = this.getLatest();
505     const prevKey = self.__prev;
506     return prevKey === null ? null : $getNodeByKey<T>(prevKey);
507   }
508
509   /**
510    * Returns the "previous" siblings - that is, the nodes that come between
511    * this one and the first child of it's parent, inclusive.
512    *
513    */
514   getPreviousSiblings<T extends LexicalNode>(): Array<T> {
515     const siblings: Array<T> = [];
516     const parent = this.getParent();
517     if (parent === null) {
518       return siblings;
519     }
520     let node: null | T = parent.getFirstChild();
521     while (node !== null) {
522       if (node.is(this)) {
523         break;
524       }
525       siblings.push(node);
526       node = node.getNextSibling();
527     }
528     return siblings;
529   }
530
531   /**
532    * Returns the "next" siblings - that is, the node that comes
533    * after this one in the same parent
534    *
535    */
536   getNextSibling<T extends LexicalNode>(): T | null {
537     const self = this.getLatest();
538     const nextKey = self.__next;
539     return nextKey === null ? null : $getNodeByKey<T>(nextKey);
540   }
541
542   /**
543    * Returns all "next" siblings - that is, the nodes that come between this
544    * one and the last child of it's parent, inclusive.
545    *
546    */
547   getNextSiblings<T extends LexicalNode>(): Array<T> {
548     const siblings: Array<T> = [];
549     let node: null | T = this.getNextSibling();
550     while (node !== null) {
551       siblings.push(node);
552       node = node.getNextSibling();
553     }
554     return siblings;
555   }
556
557   /**
558    * Returns the closest common ancestor of this node and the provided one or null
559    * if one cannot be found.
560    *
561    * @param node - the other node to find the common ancestor of.
562    */
563   getCommonAncestor<T extends ElementNode = ElementNode>(
564     node: LexicalNode,
565   ): T | null {
566     const a = this.getParents();
567     const b = node.getParents();
568     if ($isElementNode(this)) {
569       a.unshift(this);
570     }
571     if ($isElementNode(node)) {
572       b.unshift(node);
573     }
574     const aLength = a.length;
575     const bLength = b.length;
576     if (aLength === 0 || bLength === 0 || a[aLength - 1] !== b[bLength - 1]) {
577       return null;
578     }
579     const bSet = new Set(b);
580     for (let i = 0; i < aLength; i++) {
581       const ancestor = a[i] as T;
582       if (bSet.has(ancestor)) {
583         return ancestor;
584       }
585     }
586     return null;
587   }
588
589   /**
590    * Returns true if the provided node is the exact same one as this node, from Lexical's perspective.
591    * Always use this instead of referential equality.
592    *
593    * @param object - the node to perform the equality comparison on.
594    */
595   is(object: LexicalNode | null | undefined): boolean {
596     if (object == null) {
597       return false;
598     }
599     return this.__key === object.__key;
600   }
601
602   /**
603    * Returns true if this node logical precedes the target node in the editor state.
604    *
605    * @param targetNode - the node we're testing to see if it's after this one.
606    */
607   isBefore(targetNode: LexicalNode): boolean {
608     if (this === targetNode) {
609       return false;
610     }
611     if (targetNode.isParentOf(this)) {
612       return true;
613     }
614     if (this.isParentOf(targetNode)) {
615       return false;
616     }
617     const commonAncestor = this.getCommonAncestor(targetNode);
618     let indexA = 0;
619     let indexB = 0;
620     let node: this | ElementNode | LexicalNode = this;
621     while (true) {
622       const parent: ElementNode = node.getParentOrThrow();
623       if (parent === commonAncestor) {
624         indexA = node.getIndexWithinParent();
625         break;
626       }
627       node = parent;
628     }
629     node = targetNode;
630     while (true) {
631       const parent: ElementNode = node.getParentOrThrow();
632       if (parent === commonAncestor) {
633         indexB = node.getIndexWithinParent();
634         break;
635       }
636       node = parent;
637     }
638     return indexA < indexB;
639   }
640
641   /**
642    * Returns true if this node is the parent of the target node, false otherwise.
643    *
644    * @param targetNode - the would-be child node.
645    */
646   isParentOf(targetNode: LexicalNode): boolean {
647     const key = this.__key;
648     if (key === targetNode.__key) {
649       return false;
650     }
651     let node: ElementNode | LexicalNode | null = targetNode;
652     while (node !== null) {
653       if (node.__key === key) {
654         return true;
655       }
656       node = node.getParent();
657     }
658     return false;
659   }
660
661   // TO-DO: this function can be simplified a lot
662   /**
663    * Returns a list of nodes that are between this node and
664    * the target node in the EditorState.
665    *
666    * @param targetNode - the node that marks the other end of the range of nodes to be returned.
667    */
668   getNodesBetween(targetNode: LexicalNode): Array<LexicalNode> {
669     const isBefore = this.isBefore(targetNode);
670     const nodes = [];
671     const visited = new Set();
672     let node: LexicalNode | this | null = this;
673     while (true) {
674       if (node === null) {
675         break;
676       }
677       const key = node.__key;
678       if (!visited.has(key)) {
679         visited.add(key);
680         nodes.push(node);
681       }
682       if (node === targetNode) {
683         break;
684       }
685       const child: LexicalNode | null = $isElementNode(node)
686         ? isBefore
687           ? node.getFirstChild()
688           : node.getLastChild()
689         : null;
690       if (child !== null) {
691         node = child;
692         continue;
693       }
694       const nextSibling: LexicalNode | null = isBefore
695         ? node.getNextSibling()
696         : node.getPreviousSibling();
697       if (nextSibling !== null) {
698         node = nextSibling;
699         continue;
700       }
701       const parent: LexicalNode | null = node.getParentOrThrow();
702       if (!visited.has(parent.__key)) {
703         nodes.push(parent);
704       }
705       if (parent === targetNode) {
706         break;
707       }
708       let parentSibling = null;
709       let ancestor: LexicalNode | null = parent;
710       do {
711         if (ancestor === null) {
712           invariant(false, 'getNodesBetween: ancestor is null');
713         }
714         parentSibling = isBefore
715           ? ancestor.getNextSibling()
716           : ancestor.getPreviousSibling();
717         ancestor = ancestor.getParent();
718         if (ancestor !== null) {
719           if (parentSibling === null && !visited.has(ancestor.__key)) {
720             nodes.push(ancestor);
721           }
722         } else {
723           break;
724         }
725       } while (parentSibling === null);
726       node = parentSibling;
727     }
728     if (!isBefore) {
729       nodes.reverse();
730     }
731     return nodes;
732   }
733
734   /**
735    * Returns true if this node has been marked dirty during this update cycle.
736    *
737    */
738   isDirty(): boolean {
739     const editor = getActiveEditor();
740     const dirtyLeaves = editor._dirtyLeaves;
741     return dirtyLeaves !== null && dirtyLeaves.has(this.__key);
742   }
743
744   /**
745    * Returns the latest version of the node from the active EditorState.
746    * This is used to avoid getting values from stale node references.
747    *
748    */
749   getLatest(): this {
750     const latest = $getNodeByKey<this>(this.__key);
751     if (latest === null) {
752       invariant(
753         false,
754         'Lexical node does not exist in active editor state. Avoid using the same node references between nested closures from editorState.read/editor.update.',
755       );
756     }
757     return latest;
758   }
759
760   /**
761    * Returns a mutable version of the node using {@link $cloneWithProperties}
762    * if necessary. Will throw an error if called outside of a Lexical Editor
763    * {@link LexicalEditor.update} callback.
764    *
765    */
766   getWritable(): this {
767     errorOnReadOnly();
768     const editorState = getActiveEditorState();
769     const editor = getActiveEditor();
770     const nodeMap = editorState._nodeMap;
771     const key = this.__key;
772     // Ensure we get the latest node from pending state
773     const latestNode = this.getLatest();
774     const cloneNotNeeded = editor._cloneNotNeeded;
775     const selection = $getSelection();
776     if (selection !== null) {
777       selection.setCachedNodes(null);
778     }
779     if (cloneNotNeeded.has(key)) {
780       // Transforms clear the dirty node set on each iteration to keep track on newly dirty nodes
781       internalMarkNodeAsDirty(latestNode);
782       return latestNode;
783     }
784     const mutableNode = $cloneWithProperties(latestNode);
785     cloneNotNeeded.add(key);
786     internalMarkNodeAsDirty(mutableNode);
787     // Update reference in node map
788     nodeMap.set(key, mutableNode);
789
790     return mutableNode;
791   }
792
793   /**
794    * Returns the text content of the node. Override this for
795    * custom nodes that should have a representation in plain text
796    * format (for copy + paste, for example)
797    *
798    */
799   getTextContent(): string {
800     return '';
801   }
802
803   /**
804    * Returns the length of the string produced by calling getTextContent on this node.
805    *
806    */
807   getTextContentSize(): number {
808     return this.getTextContent().length;
809   }
810
811   // View
812
813   /**
814    * Called during the reconciliation process to determine which nodes
815    * to insert into the DOM for this Lexical Node.
816    *
817    * This method must return exactly one HTMLElement. Nested elements are not supported.
818    *
819    * Do not attempt to update the Lexical EditorState during this phase of the update lifecyle.
820    *
821    * @param _config - allows access to things like the EditorTheme (to apply classes) during reconciliation.
822    * @param _editor - allows access to the editor for context during reconciliation.
823    *
824    * */
825   createDOM(_config: EditorConfig, _editor: LexicalEditor): HTMLElement {
826     invariant(false, 'createDOM: base method not extended');
827   }
828
829   /**
830    * Called when a node changes and should update the DOM
831    * in whatever way is necessary to make it align with any changes that might
832    * have happened during the update.
833    *
834    * Returning "true" here will cause lexical to unmount and recreate the DOM node
835    * (by calling createDOM). You would need to do this if the element tag changes,
836    * for instance.
837    *
838    * */
839   updateDOM(
840     _prevNode: unknown,
841     _dom: HTMLElement,
842     _config: EditorConfig,
843   ): boolean {
844     invariant(false, 'updateDOM: base method not extended');
845   }
846
847   /**
848    * Controls how the this node is serialized to HTML. This is important for
849    * copy and paste between Lexical and non-Lexical editors, or Lexical editors with different namespaces,
850    * in which case the primary transfer format is HTML. It's also important if you're serializing
851    * to HTML for any other reason via {@link @lexical/html!$generateHtmlFromNodes}. You could
852    * also use this method to build your own HTML renderer.
853    *
854    * */
855   exportDOM(editor: LexicalEditor): DOMExportOutput {
856     const element = this.createDOM(editor._config, editor);
857     return {element};
858   }
859
860   /**
861    * Controls how the this node is serialized to JSON. This is important for
862    * copy and paste between Lexical editors sharing the same namespace. It's also important
863    * if you're serializing to JSON for persistent storage somewhere.
864    * See [Serialization & Deserialization](https://lexical.dev/docs/concepts/serialization#lexical---html).
865    *
866    * */
867   exportJSON(): SerializedLexicalNode {
868     invariant(false, 'exportJSON: base method not extended');
869   }
870
871   /**
872    * Controls how the this node is deserialized from JSON. This is usually boilerplate,
873    * but provides an abstraction between the node implementation and serialized interface that can
874    * be important if you ever make breaking changes to a node schema (by adding or removing properties).
875    * See [Serialization & Deserialization](https://lexical.dev/docs/concepts/serialization#lexical---html).
876    *
877    * */
878   static importJSON(_serializedNode: SerializedLexicalNode): LexicalNode {
879     invariant(
880       false,
881       'LexicalNode: Node %s does not implement .importJSON().',
882       this.name,
883     );
884   }
885   /**
886    * @experimental
887    *
888    * Registers the returned function as a transform on the node during
889    * Editor initialization. Most such use cases should be addressed via
890    * the {@link LexicalEditor.registerNodeTransform} API.
891    *
892    * Experimental - use at your own risk.
893    */
894   static transform(): ((node: LexicalNode) => void) | null {
895     return null;
896   }
897
898   // Setters and mutators
899
900   /**
901    * Removes this LexicalNode from the EditorState. If the node isn't re-inserted
902    * somewhere, the Lexical garbage collector will eventually clean it up.
903    *
904    * @param preserveEmptyParent - If falsy, the node's parent will be removed if
905    * it's empty after the removal operation. This is the default behavior, subject to
906    * other node heuristics such as {@link ElementNode#canBeEmpty}
907    * */
908   remove(preserveEmptyParent?: boolean): void {
909     $removeNode(this, true, preserveEmptyParent);
910   }
911
912   /**
913    * Replaces this LexicalNode with the provided node, optionally transferring the children
914    * of the replaced node to the replacing node.
915    *
916    * @param replaceWith - The node to replace this one with.
917    * @param includeChildren - Whether or not to transfer the children of this node to the replacing node.
918    * */
919   replace<N extends LexicalNode>(replaceWith: N, includeChildren?: boolean): N {
920     errorOnReadOnly();
921     let selection = $getSelection();
922     if (selection !== null) {
923       selection = selection.clone();
924     }
925     errorOnInsertTextNodeOnRoot(this, replaceWith);
926     const self = this.getLatest();
927     const toReplaceKey = this.__key;
928     const key = replaceWith.__key;
929     const writableReplaceWith = replaceWith.getWritable();
930     const writableParent = this.getParentOrThrow().getWritable();
931     const size = writableParent.__size;
932     removeFromParent(writableReplaceWith);
933     const prevSibling = self.getPreviousSibling();
934     const nextSibling = self.getNextSibling();
935     const prevKey = self.__prev;
936     const nextKey = self.__next;
937     const parentKey = self.__parent;
938     $removeNode(self, false, true);
939
940     if (prevSibling === null) {
941       writableParent.__first = key;
942     } else {
943       const writablePrevSibling = prevSibling.getWritable();
944       writablePrevSibling.__next = key;
945     }
946     writableReplaceWith.__prev = prevKey;
947     if (nextSibling === null) {
948       writableParent.__last = key;
949     } else {
950       const writableNextSibling = nextSibling.getWritable();
951       writableNextSibling.__prev = key;
952     }
953     writableReplaceWith.__next = nextKey;
954     writableReplaceWith.__parent = parentKey;
955     writableParent.__size = size;
956     if (includeChildren) {
957       invariant(
958         $isElementNode(this) && $isElementNode(writableReplaceWith),
959         'includeChildren should only be true for ElementNodes',
960       );
961       this.getChildren().forEach((child: LexicalNode) => {
962         writableReplaceWith.append(child);
963       });
964     }
965     if ($isRangeSelection(selection)) {
966       $setSelection(selection);
967       const anchor = selection.anchor;
968       const focus = selection.focus;
969       if (anchor.key === toReplaceKey) {
970         $moveSelectionPointToEnd(anchor, writableReplaceWith);
971       }
972       if (focus.key === toReplaceKey) {
973         $moveSelectionPointToEnd(focus, writableReplaceWith);
974       }
975     }
976     if ($getCompositionKey() === toReplaceKey) {
977       $setCompositionKey(key);
978     }
979     return writableReplaceWith;
980   }
981
982   /**
983    * Inserts a node after this LexicalNode (as the next sibling).
984    *
985    * @param nodeToInsert - The node to insert after this one.
986    * @param restoreSelection - Whether or not to attempt to resolve the
987    * selection to the appropriate place after the operation is complete.
988    * */
989   insertAfter(nodeToInsert: LexicalNode, restoreSelection = true): LexicalNode {
990     errorOnReadOnly();
991     errorOnInsertTextNodeOnRoot(this, nodeToInsert);
992     const writableSelf = this.getWritable();
993     const writableNodeToInsert = nodeToInsert.getWritable();
994     const oldParent = writableNodeToInsert.getParent();
995     const selection = $getSelection();
996     let elementAnchorSelectionOnNode = false;
997     let elementFocusSelectionOnNode = false;
998     if (oldParent !== null) {
999       // TODO: this is O(n), can we improve?
1000       const oldIndex = nodeToInsert.getIndexWithinParent();
1001       removeFromParent(writableNodeToInsert);
1002       if ($isRangeSelection(selection)) {
1003         const oldParentKey = oldParent.__key;
1004         const anchor = selection.anchor;
1005         const focus = selection.focus;
1006         elementAnchorSelectionOnNode =
1007           anchor.type === 'element' &&
1008           anchor.key === oldParentKey &&
1009           anchor.offset === oldIndex + 1;
1010         elementFocusSelectionOnNode =
1011           focus.type === 'element' &&
1012           focus.key === oldParentKey &&
1013           focus.offset === oldIndex + 1;
1014       }
1015     }
1016     const nextSibling = this.getNextSibling();
1017     const writableParent = this.getParentOrThrow().getWritable();
1018     const insertKey = writableNodeToInsert.__key;
1019     const nextKey = writableSelf.__next;
1020     if (nextSibling === null) {
1021       writableParent.__last = insertKey;
1022     } else {
1023       const writableNextSibling = nextSibling.getWritable();
1024       writableNextSibling.__prev = insertKey;
1025     }
1026     writableParent.__size++;
1027     writableSelf.__next = insertKey;
1028     writableNodeToInsert.__next = nextKey;
1029     writableNodeToInsert.__prev = writableSelf.__key;
1030     writableNodeToInsert.__parent = writableSelf.__parent;
1031     if (restoreSelection && $isRangeSelection(selection)) {
1032       const index = this.getIndexWithinParent();
1033       $updateElementSelectionOnCreateDeleteNode(
1034         selection,
1035         writableParent,
1036         index + 1,
1037       );
1038       const writableParentKey = writableParent.__key;
1039       if (elementAnchorSelectionOnNode) {
1040         selection.anchor.set(writableParentKey, index + 2, 'element');
1041       }
1042       if (elementFocusSelectionOnNode) {
1043         selection.focus.set(writableParentKey, index + 2, 'element');
1044       }
1045     }
1046     return nodeToInsert;
1047   }
1048
1049   /**
1050    * Inserts a node before this LexicalNode (as the previous sibling).
1051    *
1052    * @param nodeToInsert - The node to insert before this one.
1053    * @param restoreSelection - Whether or not to attempt to resolve the
1054    * selection to the appropriate place after the operation is complete.
1055    * */
1056   insertBefore(
1057     nodeToInsert: LexicalNode,
1058     restoreSelection = true,
1059   ): LexicalNode {
1060     errorOnReadOnly();
1061     errorOnInsertTextNodeOnRoot(this, nodeToInsert);
1062     const writableSelf = this.getWritable();
1063     const writableNodeToInsert = nodeToInsert.getWritable();
1064     const insertKey = writableNodeToInsert.__key;
1065     removeFromParent(writableNodeToInsert);
1066     const prevSibling = this.getPreviousSibling();
1067     const writableParent = this.getParentOrThrow().getWritable();
1068     const prevKey = writableSelf.__prev;
1069     // TODO: this is O(n), can we improve?
1070     const index = this.getIndexWithinParent();
1071     if (prevSibling === null) {
1072       writableParent.__first = insertKey;
1073     } else {
1074       const writablePrevSibling = prevSibling.getWritable();
1075       writablePrevSibling.__next = insertKey;
1076     }
1077     writableParent.__size++;
1078     writableSelf.__prev = insertKey;
1079     writableNodeToInsert.__prev = prevKey;
1080     writableNodeToInsert.__next = writableSelf.__key;
1081     writableNodeToInsert.__parent = writableSelf.__parent;
1082     const selection = $getSelection();
1083     if (restoreSelection && $isRangeSelection(selection)) {
1084       const parent = this.getParentOrThrow();
1085       $updateElementSelectionOnCreateDeleteNode(selection, parent, index);
1086     }
1087     return nodeToInsert;
1088   }
1089
1090   /**
1091    * Whether or not this node has a required parent. Used during copy + paste operations
1092    * to normalize nodes that would otherwise be orphaned. For example, ListItemNodes without
1093    * a ListNode parent or TextNodes with a ParagraphNode parent.
1094    *
1095    * */
1096   isParentRequired(): boolean {
1097     return false;
1098   }
1099
1100   /**
1101    * The creation logic for any required parent. Should be implemented if {@link isParentRequired} returns true.
1102    *
1103    * */
1104   createParentElementNode(): ElementNode {
1105     return $createParagraphNode();
1106   }
1107
1108   selectStart(): RangeSelection {
1109     return this.selectPrevious();
1110   }
1111
1112   selectEnd(): RangeSelection {
1113     return this.selectNext(0, 0);
1114   }
1115
1116   /**
1117    * Moves selection to the previous sibling of this node, at the specified offsets.
1118    *
1119    * @param anchorOffset - The anchor offset for selection.
1120    * @param focusOffset -  The focus offset for selection
1121    * */
1122   selectPrevious(anchorOffset?: number, focusOffset?: number): RangeSelection {
1123     errorOnReadOnly();
1124     const prevSibling = this.getPreviousSibling();
1125     const parent = this.getParentOrThrow();
1126     if (prevSibling === null) {
1127       return parent.select(0, 0);
1128     }
1129     if ($isElementNode(prevSibling)) {
1130       return prevSibling.select();
1131     } else if (!$isTextNode(prevSibling)) {
1132       const index = prevSibling.getIndexWithinParent() + 1;
1133       return parent.select(index, index);
1134     }
1135     return prevSibling.select(anchorOffset, focusOffset);
1136   }
1137
1138   /**
1139    * Moves selection to the next sibling of this node, at the specified offsets.
1140    *
1141    * @param anchorOffset - The anchor offset for selection.
1142    * @param focusOffset -  The focus offset for selection
1143    * */
1144   selectNext(anchorOffset?: number, focusOffset?: number): RangeSelection {
1145     errorOnReadOnly();
1146     const nextSibling = this.getNextSibling();
1147     const parent = this.getParentOrThrow();
1148     if (nextSibling === null) {
1149       return parent.select();
1150     }
1151     if ($isElementNode(nextSibling)) {
1152       return nextSibling.select(0, 0);
1153     } else if (!$isTextNode(nextSibling)) {
1154       const index = nextSibling.getIndexWithinParent();
1155       return parent.select(index, index);
1156     }
1157     return nextSibling.select(anchorOffset, focusOffset);
1158   }
1159
1160   /**
1161    * Marks a node dirty, triggering transforms and
1162    * forcing it to be reconciled during the update cycle.
1163    *
1164    * */
1165   markDirty(): void {
1166     this.getWritable();
1167   }
1168 }
1169
1170 function errorOnTypeKlassMismatch(
1171   type: string,
1172   klass: Klass<LexicalNode>,
1173 ): void {
1174   const registeredNode = getActiveEditor()._nodes.get(type);
1175   // Common error - split in its own invariant
1176   if (registeredNode === undefined) {
1177     invariant(
1178       false,
1179       'Create node: Attempted to create node %s that was not configured to be used on the editor.',
1180       klass.name,
1181     );
1182   }
1183   const editorKlass = registeredNode.klass;
1184   if (editorKlass !== klass) {
1185     invariant(
1186       false,
1187       'Create node: Type %s in node %s does not match registered node %s with the same type',
1188       type,
1189       klass.name,
1190       editorKlass.name,
1191     );
1192   }
1193 }
1194
1195 /**
1196  * Insert a series of nodes after this LexicalNode (as next siblings)
1197  *
1198  * @param firstToInsert - The first node to insert after this one.
1199  * @param lastToInsert - The last node to insert after this one. Must be a
1200  * later sibling of FirstNode. If not provided, it will be its last sibling.
1201  */
1202 export function insertRangeAfter(
1203   node: LexicalNode,
1204   firstToInsert: LexicalNode,
1205   lastToInsert?: LexicalNode,
1206 ) {
1207   const lastToInsert2 =
1208     lastToInsert || firstToInsert.getParentOrThrow().getLastChild()!;
1209   let current = firstToInsert;
1210   const nodesToInsert = [firstToInsert];
1211   while (current !== lastToInsert2) {
1212     if (!current.getNextSibling()) {
1213       invariant(
1214         false,
1215         'insertRangeAfter: lastToInsert must be a later sibling of firstToInsert',
1216       );
1217     }
1218     current = current.getNextSibling()!;
1219     nodesToInsert.push(current);
1220   }
1221
1222   let currentNode: LexicalNode = node;
1223   for (const nodeToInsert of nodesToInsert) {
1224     currentNode = currentNode.insertAfter(nodeToInsert);
1225   }
1226 }