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