lexical 0.6.4 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Lexical.dev.js CHANGED
@@ -95,8 +95,8 @@ const CAN_USE_BEFORE_INPUT = CAN_USE_DOM && 'InputEvent' in window && !documentM
95
95
  const IS_SAFARI = CAN_USE_DOM && /Version\/[\d.]+.*Safari/.test(navigator.userAgent);
96
96
  const IS_IOS = CAN_USE_DOM && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; // Keep these in case we need to use them in the future.
97
97
  // export const IS_WINDOWS: boolean = CAN_USE_DOM && /Win/.test(navigator.platform);
98
- // export const IS_CHROME: boolean = CAN_USE_DOM && /^(?=.*Chrome).*/i.test(navigator.userAgent);
99
- // export const canUseTextInputEvent: boolean = CAN_USE_DOM && 'TextEvent' in window && !documentMode;
98
+
99
+ const IS_CHROME = CAN_USE_DOM && /^(?=.*Chrome).*/i.test(navigator.userAgent); // export const canUseTextInputEvent: boolean = CAN_USE_DOM && 'TextEvent' in window && !documentMode;
100
100
 
101
101
  /**
102
102
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -133,7 +133,9 @@ const IS_UNMERGEABLE = 1 << 1; // Element node formatting
133
133
  const IS_ALIGN_LEFT = 1;
134
134
  const IS_ALIGN_CENTER = 2;
135
135
  const IS_ALIGN_RIGHT = 3;
136
- const IS_ALIGN_JUSTIFY = 4; // Reconciliation
136
+ const IS_ALIGN_JUSTIFY = 4;
137
+ const IS_ALIGN_START = 5;
138
+ const IS_ALIGN_END = 6; // Reconciliation
137
139
 
138
140
  const NON_BREAKING_SPACE = '\u00A0';
139
141
  const ZERO_WIDTH_SPACE = '\u200b'; // For iOS/Safari we use a non breaking space, otherwise the cursor appears
@@ -165,15 +167,19 @@ const DETAIL_TYPE_TO_DETAIL = {
165
167
  };
166
168
  const ELEMENT_TYPE_TO_FORMAT = {
167
169
  center: IS_ALIGN_CENTER,
170
+ end: IS_ALIGN_END,
168
171
  justify: IS_ALIGN_JUSTIFY,
169
172
  left: IS_ALIGN_LEFT,
170
- right: IS_ALIGN_RIGHT
173
+ right: IS_ALIGN_RIGHT,
174
+ start: IS_ALIGN_START
171
175
  };
172
176
  const ELEMENT_FORMAT_TO_TYPE = {
173
177
  [IS_ALIGN_CENTER]: 'center',
178
+ [IS_ALIGN_END]: 'end',
174
179
  [IS_ALIGN_JUSTIFY]: 'justify',
175
180
  [IS_ALIGN_LEFT]: 'left',
176
- [IS_ALIGN_RIGHT]: 'right'
181
+ [IS_ALIGN_RIGHT]: 'right',
182
+ [IS_ALIGN_START]: 'start'
177
183
  };
178
184
  const TEXT_MODE_TO_TYPE = {
179
185
  normal: IS_NORMAL,
@@ -266,6 +272,7 @@ function $flushMutations$1(editor, mutations, observer) {
266
272
  // actually "on screen".
267
273
 
268
274
  const currentEditorState = editor._editorState;
275
+ const blockCursorElement = editor._blockCursorElement;
269
276
  let shouldRevertSelection = false;
270
277
  let possibleTextForFirefoxPaste = '';
271
278
 
@@ -298,7 +305,7 @@ function $flushMutations$1(editor, mutations, observer) {
298
305
  const node = getNodeFromDOMNode(addedDOM);
299
306
  const parentDOM = addedDOM.parentNode;
300
307
 
301
- if (parentDOM != null && node === null && (addedDOM.nodeName !== 'BR' || !isManagedLineBreak(addedDOM, parentDOM, editor))) {
308
+ if (parentDOM != null && addedDOM !== blockCursorElement && node === null && (addedDOM.nodeName !== 'BR' || !isManagedLineBreak(addedDOM, parentDOM, editor))) {
302
309
  if (IS_FIREFOX) {
303
310
  const possibleText = addedDOM.innerText || addedDOM.nodeValue;
304
311
 
@@ -320,7 +327,7 @@ function $flushMutations$1(editor, mutations, observer) {
320
327
  for (let s = 0; s < removedDOMsLength; s++) {
321
328
  const removedDOM = removedDOMs[s];
322
329
 
323
- if (removedDOM.nodeName === 'BR' && isManagedLineBreak(removedDOM, targetDOM, editor)) {
330
+ if (removedDOM.nodeName === 'BR' && isManagedLineBreak(removedDOM, targetDOM, editor) || blockCursorElement === removedDOM) {
324
331
  targetDOM.appendChild(removedDOM);
325
332
  unremovedBRs++;
326
333
  }
@@ -344,7 +351,7 @@ function $flushMutations$1(editor, mutations, observer) {
344
351
  if (badDOMTargets.size > 0) {
345
352
  for (const [targetDOM, targetNode] of badDOMTargets) {
346
353
  if ($isElementNode(targetNode)) {
347
- const childKeys = targetNode.__children;
354
+ const childKeys = targetNode.getChildrenKeys();
348
355
  let currentDOM = targetDOM.firstChild;
349
356
 
350
357
  for (let s = 0; s < childKeys.length; s++) {
@@ -455,19 +462,18 @@ const scheduleMicroTask = typeof queueMicrotask === 'function' ? queueMicrotask
455
462
  };
456
463
  function $isSelectionCapturedInDecorator(node) {
457
464
  return $isDecoratorNode($getNearestNodeFromDOMNode(node));
458
- } // TODO change to $ function
459
-
465
+ }
460
466
  function isSelectionCapturedInDecoratorInput(anchorDOM) {
461
467
  const activeElement = document.activeElement;
462
468
  const nodeName = activeElement !== null ? activeElement.nodeName : null;
463
- return !$isDecoratorNode($getNearestNodeFromDOMNode(anchorDOM)) || nodeName !== 'INPUT' && nodeName !== 'TEXTAREA';
469
+ return $isDecoratorNode($getNearestNodeFromDOMNode(anchorDOM)) && (nodeName === 'INPUT' || nodeName === 'TEXTAREA');
464
470
  }
465
471
  function isSelectionWithinEditor(editor, anchorDOM, focusDOM) {
466
472
  const rootElement = editor.getRootElement();
467
473
 
468
474
  try {
469
475
  return rootElement !== null && rootElement.contains(anchorDOM) && rootElement.contains(focusDOM) && // Ignore if selection is within nested editor
470
- anchorDOM !== null && isSelectionCapturedInDecoratorInput(anchorDOM) && getNearestEditorFromDOMNode(anchorDOM) === editor;
476
+ anchorDOM !== null && !isSelectionCapturedInDecoratorInput(anchorDOM) && getNearestEditorFromDOMNode(anchorDOM) === editor;
471
477
  } catch (error) {
472
478
  return false;
473
479
  }
@@ -585,22 +591,61 @@ function internalMarkParentElementsAsDirty(parentKey, nodeMap, dirtyElements) {
585
591
  }
586
592
  }
587
593
 
588
- function removeFromParent(writableNode) {
589
- const oldParent = writableNode.getParent();
594
+ function removeFromParent(node) {
595
+ const oldParent = node.getParent();
590
596
 
591
597
  if (oldParent !== null) {
598
+ const writableNode = node.getWritable();
592
599
  const writableParent = oldParent.getWritable();
593
- const children = writableParent.__children;
594
- const index = children.indexOf(writableNode.__key);
600
+ const prevSibling = node.getPreviousSibling();
601
+ const nextSibling = node.getNextSibling(); // TODO: this function duplicates a bunch of operations, can be simplified.
595
602
 
596
- if (index === -1) {
597
- {
598
- throw Error(`Node is not a child of its parent`);
603
+ if (prevSibling === null) {
604
+ if (nextSibling !== null) {
605
+ const writableNextSibling = nextSibling.getWritable();
606
+ writableParent.__first = nextSibling.__key;
607
+ writableNextSibling.__prev = null;
608
+ } else {
609
+ writableParent.__first = null;
610
+ }
611
+ } else {
612
+ const writablePrevSibling = prevSibling.getWritable();
613
+
614
+ if (nextSibling !== null) {
615
+ const writableNextSibling = nextSibling.getWritable();
616
+ writableNextSibling.__prev = writablePrevSibling.__key;
617
+ writablePrevSibling.__next = writableNextSibling.__key;
618
+ } else {
619
+ writablePrevSibling.__next = null;
620
+ }
621
+
622
+ writableNode.__prev = null;
623
+ }
624
+
625
+ if (nextSibling === null) {
626
+ if (prevSibling !== null) {
627
+ const writablePrevSibling = prevSibling.getWritable();
628
+ writableParent.__last = prevSibling.__key;
629
+ writablePrevSibling.__next = null;
630
+ } else {
631
+ writableParent.__last = null;
632
+ }
633
+ } else {
634
+ const writableNextSibling = nextSibling.getWritable();
635
+
636
+ if (prevSibling !== null) {
637
+ const writablePrevSibling = prevSibling.getWritable();
638
+ writablePrevSibling.__next = writableNextSibling.__key;
639
+ writableNextSibling.__prev = writablePrevSibling.__key;
640
+ } else {
641
+ writableNextSibling.__prev = null;
599
642
  }
643
+
644
+ writableNode.__next = null;
600
645
  }
601
646
 
602
- internalMarkSiblingsAsDirty(writableNode);
603
- children.splice(index, 1);
647
+ writableParent.__size--;
648
+ writableNode.__parent = null;
604
649
  }
605
650
  } // Never use this function directly! It will break
606
651
  // the cloning heuristic. Instead use node.getWritable().
@@ -1156,8 +1201,15 @@ function setMutatedNode(mutatedNodes, registeredNodes, mutationListeners, node,
1156
1201
  mutatedNodes.set(klass, mutatedNodesByType);
1157
1202
  }
1158
1203
 
1159
- if (!mutatedNodesByType.has(nodeKey)) {
1160
- mutatedNodesByType.set(nodeKey, mutation);
1204
+ const prevMutation = mutatedNodesByType.get(nodeKey); // If the node has already been "destroyed", yet we are
1205
+ // re-making it, then this means a move likely happened.
1206
+ // We should change the mutation to be that of "updated"
1207
+ // instead.
1208
+
1209
+ const isMove = prevMutation === 'destroyed' && mutation === 'created';
1210
+
1211
+ if (prevMutation === undefined || isMove) {
1212
+ mutatedNodesByType.set(nodeKey, isMove ? 'updated' : mutation);
1161
1213
  }
1162
1214
  }
1163
1215
  function $nodesOfType(klass) {
@@ -1194,7 +1246,7 @@ function resolveElement(element, isBackward, focusOffset) {
1194
1246
  return block.getChildAtIndex(isBackward ? offset - 1 : offset);
1195
1247
  }
1196
1248
 
1197
- function $getDecoratorNode(focus, isBackward) {
1249
+ function $getAdjacentNode(focus, isBackward) {
1198
1250
  const focusOffset = focus.offset;
1199
1251
 
1200
1252
  if (focus.type === 'element') {
@@ -1425,6 +1477,96 @@ function errorOnInsertTextNodeOnRoot(node, insertNode) {
1425
1477
  }
1426
1478
  }
1427
1479
 
1480
+ function createBlockCursorElement(editorConfig) {
1481
+ const theme = editorConfig.theme;
1482
+ const element = document.createElement('div');
1483
+ element.contentEditable = 'false';
1484
+ element.setAttribute('data-lexical-cursor', 'true');
1485
+ let blockCursorTheme = theme.blockCursor;
1486
+
1487
+ if (blockCursorTheme !== undefined) {
1488
+ if (typeof blockCursorTheme === 'string') {
1489
+ const classNamesArr = blockCursorTheme.split(' '); // @ts-expect-error: intentional
1490
+
1491
+ blockCursorTheme = theme.blockCursor = classNamesArr;
1492
+ }
1493
+
1494
+ if (blockCursorTheme !== undefined) {
1495
+ element.classList.add(...blockCursorTheme);
1496
+ }
1497
+ }
1498
+
1499
+ return element;
1500
+ }
1501
+
1502
+ function needsBlockCursor(node) {
1503
+ return ($isDecoratorNode(node) || $isElementNode(node) && !node.canBeEmpty()) && !node.isInline();
1504
+ }
1505
+
1506
+ function removeDOMBlockCursorElement(blockCursorElement, editor, rootElement) {
1507
+ rootElement.style.removeProperty('caret-color');
1508
+ editor._blockCursorElement = null;
1509
+ const parentElement = blockCursorElement.parentElement;
1510
+
1511
+ if (parentElement !== null) {
1512
+ parentElement.removeChild(blockCursorElement);
1513
+ }
1514
+ }
1515
+ function updateDOMBlockCursorElement(editor, rootElement, nextSelection) {
1516
+ let blockCursorElement = editor._blockCursorElement;
1517
+
1518
+ if ($isRangeSelection(nextSelection) && nextSelection.isCollapsed() && nextSelection.anchor.type === 'element' && rootElement.contains(document.activeElement)) {
1519
+ const anchor = nextSelection.anchor;
1520
+ const elementNode = anchor.getNode();
1521
+ const offset = anchor.offset;
1522
+ const elementNodeSize = elementNode.getChildrenSize();
1523
+ let isBlockCursor = false;
1524
+ let insertBeforeElement = null;
1525
+
1526
+ if (offset === elementNodeSize) {
1527
+ const child = elementNode.getChildAtIndex(offset - 1);
1528
+
1529
+ if (needsBlockCursor(child)) {
1530
+ isBlockCursor = true;
1531
+ }
1532
+ } else {
1533
+ const child = elementNode.getChildAtIndex(offset);
1534
+
1535
+ if (needsBlockCursor(child)) {
1536
+ const sibling = child.getPreviousSibling();
1537
+
1538
+ if (sibling === null || needsBlockCursor(sibling)) {
1539
+ isBlockCursor = true;
1540
+ insertBeforeElement = editor.getElementByKey(child.__key);
1541
+ }
1542
+ }
1543
+ }
1544
+
1545
+ if (isBlockCursor) {
1546
+ const elementDOM = editor.getElementByKey(elementNode.__key);
1547
+
1548
+ if (blockCursorElement === null) {
1549
+ editor._blockCursorElement = blockCursorElement = createBlockCursorElement(editor._config);
1550
+ }
1551
+
1552
+ rootElement.style.caretColor = 'transparent';
1553
+
1554
+ if (insertBeforeElement === null) {
1555
+ elementDOM.appendChild(blockCursorElement);
1556
+ } else {
1557
+ elementDOM.insertBefore(blockCursorElement, insertBeforeElement);
1558
+ }
1559
+
1560
+ return;
1561
+ }
1562
+ } // Remove cursor
1563
+
1564
+
1565
+ if (blockCursorElement !== null) {
1566
+ removeDOMBlockCursorElement(blockCursorElement, editor, rootElement);
1567
+ }
1568
+ }
1569
+
1428
1570
  /**
1429
1571
  * Copyright (c) Meta Platforms, Inc. and affiliates.
1430
1572
  *
@@ -1451,12 +1593,10 @@ function $garbageCollectDetachedDecorators(editor, pendingEditorState) {
1451
1593
  }
1452
1594
 
1453
1595
  function $garbageCollectDetachedDeepChildNodes(node, parentKey, prevNodeMap, nodeMap, dirtyNodes) {
1454
- const children = node.__children;
1455
- const childrenLength = children.length;
1596
+ let child = node.getFirstChild();
1456
1597
 
1457
- for (let i = 0; i < childrenLength; i++) {
1458
- const childKey = children[i];
1459
- const child = nodeMap.get(childKey);
1598
+ while (child !== null) {
1599
+ const childKey = child.__key;
1460
1600
 
1461
1601
  if (child !== undefined && child.__parent === parentKey) {
1462
1602
  if ($isElementNode(child)) {
@@ -1471,6 +1611,8 @@ function $garbageCollectDetachedDeepChildNodes(node, parentKey, prevNodeMap, nod
1471
1611
 
1472
1612
  nodeMap.delete(childKey);
1473
1613
  }
1614
+
1615
+ child = child.isAttached() ? child.getNextSibling() : null;
1474
1616
  }
1475
1617
  }
1476
1618
 
@@ -1637,7 +1779,10 @@ function destroyNode(key, parentDOM) {
1637
1779
 
1638
1780
  if (parentDOM !== null) {
1639
1781
  const dom = getPrevElementByKeyOrThrow(key);
1640
- parentDOM.removeChild(dom);
1782
+
1783
+ if (dom.parentNode === parentDOM) {
1784
+ parentDOM.removeChild(dom);
1785
+ }
1641
1786
  } // This logic is really important, otherwise we will leak DOM nodes
1642
1787
  // when their corresponding LexicalNodes are removed from the editor state.
1643
1788
 
@@ -1647,7 +1792,7 @@ function destroyNode(key, parentDOM) {
1647
1792
  }
1648
1793
 
1649
1794
  if ($isElementNode(node)) {
1650
- const children = node.__children;
1795
+ const children = createChildrenArray(node, activePrevNodeMap);
1651
1796
  destroyChildren(children, 0, children.length - 1, null);
1652
1797
  }
1653
1798
 
@@ -1689,6 +1834,10 @@ function setElementFormat(dom, format) {
1689
1834
  setTextAlign(domStyle, 'right');
1690
1835
  } else if (format === IS_ALIGN_JUSTIFY) {
1691
1836
  setTextAlign(domStyle, 'justify');
1837
+ } else if (format === IS_ALIGN_START) {
1838
+ setTextAlign(domStyle, 'start');
1839
+ } else if (format === IS_ALIGN_END) {
1840
+ setTextAlign(domStyle, 'end');
1692
1841
  }
1693
1842
  }
1694
1843
 
@@ -1714,16 +1863,15 @@ function createNode(key, parentDOM, insertDOM) {
1714
1863
 
1715
1864
  if ($isElementNode(node)) {
1716
1865
  const indent = node.__indent;
1866
+ const childrenSize = node.__size;
1717
1867
 
1718
1868
  if (indent !== 0) {
1719
1869
  setElementIndent(dom, indent);
1720
1870
  }
1721
1871
 
1722
- const children = node.__children;
1723
- const childrenLength = children.length;
1724
-
1725
- if (childrenLength !== 0) {
1726
- const endIndex = childrenLength - 1;
1872
+ if (childrenSize !== 0) {
1873
+ const endIndex = childrenSize - 1;
1874
+ const children = createChildrenArray(node, activeNextNodeMap);
1727
1875
  createChildrenWithDirection(children, endIndex, node, dom);
1728
1876
  }
1729
1877
 
@@ -1734,7 +1882,7 @@ function createNode(key, parentDOM, insertDOM) {
1734
1882
  }
1735
1883
 
1736
1884
  if (!node.isInline()) {
1737
- reconcileElementTerminatingLineBreak(null, children, dom);
1885
+ reconcileElementTerminatingLineBreak(null, node, dom);
1738
1886
  }
1739
1887
 
1740
1888
  if ($textContentRequiresDoubleLinebreakAtEnd(node)) {
@@ -1809,16 +1957,15 @@ function createChildren(children, _startIndex, endIndex, dom, insertDOM) {
1809
1957
  subTreeTextContent = previousSubTreeTextContent + subTreeTextContent;
1810
1958
  }
1811
1959
 
1812
- function isLastChildLineBreakOrDecorator(children, nodeMap) {
1813
- const childKey = children[children.length - 1];
1960
+ function isLastChildLineBreakOrDecorator(childKey, nodeMap) {
1814
1961
  const node = nodeMap.get(childKey);
1815
1962
  return $isLineBreakNode(node) || $isDecoratorNode(node) && node.isInline();
1816
1963
  } // If we end an element with a LineBreakNode, then we need to add an additional <br>
1817
1964
 
1818
1965
 
1819
- function reconcileElementTerminatingLineBreak(prevChildren, nextChildren, dom) {
1820
- const prevLineBreak = prevChildren !== null && (prevChildren.length === 0 || isLastChildLineBreakOrDecorator(prevChildren, activePrevNodeMap));
1821
- const nextLineBreak = nextChildren !== null && (nextChildren.length === 0 || isLastChildLineBreakOrDecorator(nextChildren, activeNextNodeMap));
1966
+ function reconcileElementTerminatingLineBreak(prevElement, nextElement, dom) {
1967
+ const prevLineBreak = prevElement !== null && (prevElement.__size === 0 || isLastChildLineBreakOrDecorator(prevElement.__last, activePrevNodeMap));
1968
+ const nextLineBreak = nextElement.__size === 0 || isLastChildLineBreakOrDecorator(nextElement.__last, activeNextNodeMap);
1822
1969
 
1823
1970
  if (prevLineBreak) {
1824
1971
  if (!nextLineBreak) {
@@ -1901,53 +2048,78 @@ function reconcileBlockDirection(element, dom) {
1901
2048
  }
1902
2049
  }
1903
2050
 
1904
- function reconcileChildrenWithDirection(prevChildren, nextChildren, element, dom) {
2051
+ function reconcileChildrenWithDirection(prevElement, nextElement, dom) {
1905
2052
  const previousSubTreeDirectionTextContent = subTreeDirectionedTextContent;
1906
2053
  subTreeDirectionedTextContent = '';
1907
- reconcileChildren(element, prevChildren, nextChildren, dom);
1908
- reconcileBlockDirection(element, dom);
2054
+ reconcileChildren(prevElement, nextElement, dom);
2055
+ reconcileBlockDirection(nextElement, dom);
1909
2056
  subTreeDirectionedTextContent = previousSubTreeDirectionTextContent;
1910
2057
  }
1911
2058
 
1912
- function reconcileChildren(element, prevChildren, nextChildren, dom) {
2059
+ function createChildrenArray(element, nodeMap) {
2060
+ const children = [];
2061
+ let nodeKey = element.__first;
2062
+
2063
+ while (nodeKey !== null) {
2064
+ const node = nodeMap.get(nodeKey);
2065
+
2066
+ if (node === undefined) {
2067
+ {
2068
+ throw Error(`createChildrenArray: node does not exist in nodeMap`);
2069
+ }
2070
+ }
2071
+
2072
+ children.push(nodeKey);
2073
+ nodeKey = node.__next;
2074
+ }
2075
+
2076
+ return children;
2077
+ }
2078
+
2079
+ function reconcileChildren(prevElement, nextElement, dom) {
1913
2080
  const previousSubTreeTextContent = subTreeTextContent;
2081
+ const prevChildrenSize = prevElement.__size;
2082
+ const nextChildrenSize = nextElement.__size;
1914
2083
  subTreeTextContent = '';
1915
- const prevChildrenLength = prevChildren.length;
1916
- const nextChildrenLength = nextChildren.length;
1917
2084
 
1918
- if (prevChildrenLength === 1 && nextChildrenLength === 1) {
1919
- const prevChildKey = prevChildren[0];
1920
- const nextChildKey = nextChildren[0];
2085
+ if (prevChildrenSize === 1 && nextChildrenSize === 1) {
2086
+ const prevFirstChildKey = prevElement.__first;
2087
+ const nextFrstChildKey = nextElement.__first;
1921
2088
 
1922
- if (prevChildKey === nextChildKey) {
1923
- reconcileNode(prevChildKey, dom);
2089
+ if (prevFirstChildKey === nextFrstChildKey) {
2090
+ reconcileNode(prevFirstChildKey, dom);
1924
2091
  } else {
1925
- const lastDOM = getPrevElementByKeyOrThrow(prevChildKey);
1926
- const replacementDOM = createNode(nextChildKey, null, null);
2092
+ const lastDOM = getPrevElementByKeyOrThrow(prevFirstChildKey);
2093
+ const replacementDOM = createNode(nextFrstChildKey, null, null);
1927
2094
  dom.replaceChild(replacementDOM, lastDOM);
1928
- destroyNode(prevChildKey, null);
2095
+ destroyNode(prevFirstChildKey, null);
1929
2096
  }
1930
- } else if (prevChildrenLength === 0) {
1931
- if (nextChildrenLength !== 0) {
1932
- createChildren(nextChildren, 0, nextChildrenLength - 1, dom, null);
1933
- }
1934
- } else if (nextChildrenLength === 0) {
1935
- if (prevChildrenLength !== 0) {
1936
- // @ts-expect-error: internal field
1937
- const lexicalLineBreak = dom.__lexicalLineBreak;
1938
- const canUseFastPath = lexicalLineBreak == null;
1939
- destroyChildren(prevChildren, 0, prevChildrenLength - 1, canUseFastPath ? null : dom);
1940
-
1941
- if (canUseFastPath) {
1942
- // Fast path for removing DOM nodes
1943
- dom.textContent = '';
2097
+ } else {
2098
+ const prevChildren = createChildrenArray(prevElement, activePrevNodeMap);
2099
+ const nextChildren = createChildrenArray(nextElement, activeNextNodeMap);
2100
+
2101
+ if (prevChildrenSize === 0) {
2102
+ if (nextChildrenSize !== 0) {
2103
+ createChildren(nextChildren, 0, nextChildrenSize - 1, dom, null);
2104
+ }
2105
+ } else if (nextChildrenSize === 0) {
2106
+ if (prevChildrenSize !== 0) {
2107
+ // @ts-expect-error: internal field
2108
+ const lexicalLineBreak = dom.__lexicalLineBreak;
2109
+ const canUseFastPath = lexicalLineBreak == null;
2110
+ destroyChildren(prevChildren, 0, prevChildrenSize - 1, canUseFastPath ? null : dom);
2111
+
2112
+ if (canUseFastPath) {
2113
+ // Fast path for removing DOM nodes
2114
+ dom.textContent = '';
2115
+ }
1944
2116
  }
2117
+ } else {
2118
+ reconcileNodeChildren(prevChildren, nextChildren, prevChildrenSize, nextChildrenSize, dom);
1945
2119
  }
1946
- } else {
1947
- reconcileNodeChildren(prevChildren, nextChildren, prevChildrenLength, nextChildrenLength, element, dom);
1948
2120
  }
1949
2121
 
1950
- if ($textContentRequiresDoubleLinebreakAtEnd(element)) {
2122
+ if ($textContentRequiresDoubleLinebreakAtEnd(nextElement)) {
1951
2123
  subTreeTextContent += DOUBLE_LINE_BREAK;
1952
2124
  } // @ts-expect-error: internal field
1953
2125
 
@@ -2036,15 +2208,11 @@ function reconcileNode(key, parentDOM) {
2036
2208
  setElementFormat(dom, nextFormat);
2037
2209
  }
2038
2210
 
2039
- const prevChildren = prevNode.__children;
2040
- const nextChildren = nextNode.__children;
2041
- const childrenAreDifferent = prevChildren !== nextChildren;
2042
-
2043
- if (childrenAreDifferent || isDirty) {
2044
- reconcileChildrenWithDirection(prevChildren, nextChildren, nextNode, dom);
2211
+ if (isDirty) {
2212
+ reconcileChildrenWithDirection(prevNode, nextNode, dom);
2045
2213
 
2046
2214
  if (!$isRootNode(nextNode) && !nextNode.isInline()) {
2047
- reconcileElementTerminatingLineBreak(prevChildren, nextChildren, dom);
2215
+ reconcileElementTerminatingLineBreak(prevNode, nextNode, dom);
2048
2216
  }
2049
2217
  }
2050
2218
 
@@ -2104,10 +2272,16 @@ function getFirstChild(element) {
2104
2272
  }
2105
2273
 
2106
2274
  function getNextSibling(element) {
2107
- return element.nextSibling;
2275
+ let nextSibling = element.nextSibling;
2276
+
2277
+ if (nextSibling !== null && nextSibling === activeEditor$1._blockCursorElement) {
2278
+ nextSibling = nextSibling.nextSibling;
2279
+ }
2280
+
2281
+ return nextSibling;
2108
2282
  }
2109
2283
 
2110
- function reconcileNodeChildren(prevChildren, nextChildren, prevChildrenLength, nextChildrenLength, element, dom) {
2284
+ function reconcileNodeChildren(prevChildren, nextChildren, prevChildrenLength, nextChildrenLength, dom) {
2111
2285
  const prevEndIndex = prevChildrenLength - 1;
2112
2286
  const nextEndIndex = nextChildrenLength - 1;
2113
2287
  let prevChildrenSet;
@@ -3071,7 +3245,9 @@ function addRootElementEvents(rootElement, editor) {
3071
3245
  return dispatchCommand(editor, FOCUS_COMMAND, event);
3072
3246
 
3073
3247
  case 'blur':
3074
- return dispatchCommand(editor, BLUR_COMMAND, event);
3248
+ {
3249
+ return dispatchCommand(editor, BLUR_COMMAND, event);
3250
+ }
3075
3251
 
3076
3252
  case 'drop':
3077
3253
  return dispatchCommand(editor, DROP_COMMAND, event);
@@ -3885,10 +4061,10 @@ class RangeSelection {
3885
4061
  textNode.select();
3886
4062
 
3887
4063
  if (startOffset === 0) {
3888
- firstNode.insertBefore(textNode);
4064
+ firstNode.insertBefore(textNode, false);
3889
4065
  } else {
3890
4066
  const [targetNode] = firstNode.splitText(startOffset);
3891
- targetNode.insertAfter(textNode);
4067
+ targetNode.insertAfter(textNode, false);
3892
4068
  } // When composing, we need to adjust the anchor offset so that
3893
4069
  // we correctly replace that right range.
3894
4070
 
@@ -3939,7 +4115,7 @@ class RangeSelection {
3939
4115
  if ($isTextNode(lastNode) && !lastNode.isToken() && endOffset !== lastNode.getTextContentSize()) {
3940
4116
  if (lastNode.isSegmented()) {
3941
4117
  const textNode = $createTextNode(lastNode.getTextContent());
3942
- lastNode.replace(textNode);
4118
+ lastNode.replace(textNode, false);
3943
4119
  lastNode = textNode;
3944
4120
  }
3945
4121
 
@@ -3982,7 +4158,7 @@ class RangeSelection {
3982
4158
  if (lastNodeChild.isAttached()) {
3983
4159
  if (!selectedNodesSet.has(lastNodeChild) || lastNodeChild.is(lastElementChild)) {
3984
4160
  if (!firstAndLastElementsAreEqual) {
3985
- insertionTarget.insertAfter(lastNodeChild);
4161
+ insertionTarget.insertAfter(lastNodeChild, false);
3986
4162
  }
3987
4163
  } else {
3988
4164
  lastNodeChild.remove();
@@ -4027,7 +4203,7 @@ class RangeSelection {
4027
4203
  } else {
4028
4204
  const textNode = $createTextNode(text);
4029
4205
  textNode.select();
4030
- firstNode.replace(textNode);
4206
+ firstNode.replace(textNode, false);
4031
4207
  } // Remove all selected nodes that haven't already been removed.
4032
4208
 
4033
4209
 
@@ -4364,7 +4540,7 @@ class RangeSelection {
4364
4540
  lastNode = node;
4365
4541
 
4366
4542
  if ($isDecoratorNode(node) && !node.isInline()) {
4367
- target = target.insertAfter(node);
4543
+ target = target.insertAfter(node, false);
4368
4544
  } else if (!$isElementNode(node)) {
4369
4545
  const firstChild = target.getFirstChild();
4370
4546
 
@@ -4391,12 +4567,12 @@ class RangeSelection {
4391
4567
 
4392
4568
  target = node;
4393
4569
  } else {
4394
- target = target.insertAfter(node);
4570
+ target = target.insertAfter(node, false);
4395
4571
  }
4396
4572
  }
4397
4573
  } else if (!$isElementNode(node) || $isElementNode(node) && node.isInline() || $isDecoratorNode(target) && !target.isInline()) {
4398
4574
  lastNode = node;
4399
- target = target.insertAfter(node);
4575
+ target = target.insertAfter(node, false);
4400
4576
  } else {
4401
4577
  const nextTarget = target.getParentOrThrow(); // if we're inserting an Element after a LineBreak, we want to move the target to the parent
4402
4578
  // and remove the LineBreak so we don't have empty space.
@@ -4550,7 +4726,7 @@ class RangeSelection {
4550
4726
  paragraph.select();
4551
4727
 
4552
4728
  if (child !== null) {
4553
- child.insertBefore(paragraph);
4729
+ child.insertBefore(paragraph, false);
4554
4730
  } else {
4555
4731
  currentElement.append(paragraph);
4556
4732
  }
@@ -4565,7 +4741,7 @@ class RangeSelection {
4565
4741
 
4566
4742
  if (anchorOffset === 0 && nodesToMoveLength > 0 && currentElement.isInline()) {
4567
4743
  const parent = currentElement.getParentOrThrow();
4568
- const newElement = parent.insertNewAfter(this);
4744
+ const newElement = parent.insertNewAfter(this, false);
4569
4745
 
4570
4746
  if ($isElementNode(newElement)) {
4571
4747
  const children = parent.getChildren();
@@ -4578,7 +4754,7 @@ class RangeSelection {
4578
4754
  return;
4579
4755
  }
4580
4756
 
4581
- const newElement = currentElement.insertNewAfter(this);
4757
+ const newElement = currentElement.insertNewAfter(this, false);
4582
4758
 
4583
4759
  if (newElement === null) {
4584
4760
  // Handle as a line break insertion
@@ -4710,7 +4886,7 @@ class RangeSelection {
4710
4886
  const anchor = this.anchor;
4711
4887
  const collapse = alter === 'move'; // Handle the selection movement around decorators.
4712
4888
 
4713
- const possibleNode = $getDecoratorNode(focus, isBackward);
4889
+ const possibleNode = $getAdjacentNode(focus, isBackward);
4714
4890
 
4715
4891
  if ($isDecoratorNode(possibleNode) && !possibleNode.isIsolated()) {
4716
4892
  // Make it possible to move selection from range selection to
@@ -4765,6 +4941,16 @@ class RangeSelection {
4765
4941
 
4766
4942
  if (!domSelection) {
4767
4943
  return;
4944
+ }
4945
+
4946
+ const editor = getActiveEditor();
4947
+ const blockCursorElement = editor._blockCursorElement;
4948
+ const rootElement = editor._rootElement; // Remove the block cursor element if it exists. This will ensure selection
4949
+ // works as intended. If we leave it in the DOM all sorts of strange bugs
4950
+ // occur. :/
4951
+
4952
+ if (rootElement !== null && blockCursorElement !== null && $isElementNode(possibleNode) && !possibleNode.isInline() && !possibleNode.canBeEmpty()) {
4953
+ removeDOMBlockCursorElement(blockCursorElement, editor, rootElement);
4768
4954
  } // We use the DOM selection.modify API here to "tell" us what the selection
4769
4955
  // will be. We then use it to update the Lexical selection accordingly. This
4770
4956
  // is much more reliable than waiting for a beforeinput and using the ranges
@@ -4847,7 +5033,7 @@ class RangeSelection {
4847
5033
  } // Handle the deletion around decorators.
4848
5034
 
4849
5035
 
4850
- const possibleNode = $getDecoratorNode(focus, isBackward);
5036
+ const possibleNode = $getAdjacentNode(focus, isBackward);
4851
5037
 
4852
5038
  if ($isDecoratorNode(possibleNode) && !possibleNode.isIsolated()) {
4853
5039
  // Make it possible to move selection from range selection to
@@ -5042,7 +5228,7 @@ function shouldResolveAncestor(resolvedElement, resolvedOffset, lastPoint) {
5042
5228
  return lastPoint === null || parent === null || !parent.canBeEmpty() || parent !== lastPoint.getNode();
5043
5229
  }
5044
5230
 
5045
- function internalResolveSelectionPoint(dom, offset, lastPoint) {
5231
+ function internalResolveSelectionPoint(dom, offset, lastPoint, editor) {
5046
5232
  let resolvedOffset = offset;
5047
5233
  let resolvedNode; // If we have selection on an element, we will
5048
5234
  // need to figure out (using the offset) what text
@@ -5063,7 +5249,16 @@ function internalResolveSelectionPoint(dom, offset, lastPoint) {
5063
5249
  resolvedOffset = childNodesLength - 1;
5064
5250
  }
5065
5251
 
5066
- const childDOM = childNodes[resolvedOffset];
5252
+ let childDOM = childNodes[resolvedOffset];
5253
+ let hasBlockCursor = false;
5254
+
5255
+ if (childDOM === editor._blockCursorElement) {
5256
+ childDOM = childNodes[resolvedOffset + 1];
5257
+ hasBlockCursor = true;
5258
+ } else if (editor._blockCursorElement !== null) {
5259
+ resolvedOffset--;
5260
+ }
5261
+
5067
5262
  resolvedNode = getNodeFromDOM(childDOM);
5068
5263
 
5069
5264
  if ($isTextNode(resolvedNode)) {
@@ -5086,7 +5281,7 @@ function internalResolveSelectionPoint(dom, offset, lastPoint) {
5086
5281
  resolvedOffset = 0;
5087
5282
  } else {
5088
5283
  child = descendant;
5089
- resolvedElement = child.getParentOrThrow();
5284
+ resolvedElement = $isElementNode(child) ? child : child.getParentOrThrow();
5090
5285
  }
5091
5286
  }
5092
5287
 
@@ -5094,7 +5289,7 @@ function internalResolveSelectionPoint(dom, offset, lastPoint) {
5094
5289
  resolvedNode = child;
5095
5290
  resolvedElement = null;
5096
5291
  resolvedOffset = getTextNodeOffset(child, moveSelectionToEnd);
5097
- } else if (child !== resolvedElement && moveSelectionToEnd) {
5292
+ } else if (child !== resolvedElement && moveSelectionToEnd && !hasBlockCursor) {
5098
5293
  resolvedOffset++;
5099
5294
  }
5100
5295
  } else {
@@ -5203,13 +5398,13 @@ function internalResolveSelectionPoints(anchorDOM, anchorOffset, focusDOM, focus
5203
5398
  return null;
5204
5399
  }
5205
5400
 
5206
- const resolvedAnchorPoint = internalResolveSelectionPoint(anchorDOM, anchorOffset, $isRangeSelection(lastSelection) ? lastSelection.anchor : null);
5401
+ const resolvedAnchorPoint = internalResolveSelectionPoint(anchorDOM, anchorOffset, $isRangeSelection(lastSelection) ? lastSelection.anchor : null, editor);
5207
5402
 
5208
5403
  if (resolvedAnchorPoint === null) {
5209
5404
  return null;
5210
5405
  }
5211
5406
 
5212
- const resolvedFocusPoint = internalResolveSelectionPoint(focusDOM, focusOffset, $isRangeSelection(lastSelection) ? lastSelection.focus : null);
5407
+ const resolvedFocusPoint = internalResolveSelectionPoint(focusDOM, focusOffset, $isRangeSelection(lastSelection) ? lastSelection.focus : null, editor);
5213
5408
 
5214
5409
  if (resolvedFocusPoint === null) {
5215
5410
  return null;
@@ -5523,7 +5718,7 @@ function adjustPointOffsetForMergedSibling(point, isBefore, key, target, textLen
5523
5718
  point.offset -= 1;
5524
5719
  }
5525
5720
  }
5526
- function updateDOMSelection(prevSelection, nextSelection, editor, domSelection, tags, rootElement) {
5721
+ function updateDOMSelection(prevSelection, nextSelection, editor, domSelection, tags, rootElement, dirtyLeavesCount) {
5527
5722
  const anchorDOMNode = domSelection.anchorNode;
5528
5723
  const focusDOMNode = domSelection.focusNode;
5529
5724
  const anchorOffset = domSelection.anchorOffset;
@@ -5531,7 +5726,7 @@ function updateDOMSelection(prevSelection, nextSelection, editor, domSelection,
5531
5726
  const activeElement = document.activeElement; // TODO: make this not hard-coded, and add another config option
5532
5727
  // that makes this configurable.
5533
5728
 
5534
- if (tags.has('collaboration') && activeElement !== rootElement) {
5729
+ if (tags.has('collaboration') && activeElement !== rootElement || activeElement !== null && isSelectionCapturedInDecoratorInput(activeElement)) {
5535
5730
  return;
5536
5731
  }
5537
5732
 
@@ -5587,7 +5782,7 @@ function updateDOMSelection(prevSelection, nextSelection, editor, domSelection,
5587
5782
  if (anchorOffset === nextAnchorOffset && focusOffset === nextFocusOffset && anchorDOMNode === nextAnchorNode && focusDOMNode === nextFocusNode && // Badly interpreted range selection when collapsed - #1482
5588
5783
  !(domSelection.type === 'Range' && isCollapsed)) {
5589
5784
  // If the root element does not have focus, ensure it has focus
5590
- if (rootElement !== null && (activeElement === null || !rootElement.contains(activeElement))) {
5785
+ if (activeElement === null || !rootElement.contains(activeElement)) {
5591
5786
  rootElement.focus({
5592
5787
  preventScroll: true
5593
5788
  });
@@ -5596,28 +5791,39 @@ function updateDOMSelection(prevSelection, nextSelection, editor, domSelection,
5596
5791
  if (anchor.type !== 'element') {
5597
5792
  return;
5598
5793
  }
5599
- } // Apply the updated selection to the DOM. Note: this will trigger
5600
- // a "selectionchange" event, although it will be asynchronous.
5601
-
5602
-
5603
- try {
5604
- domSelection.setBaseAndExtent(nextAnchorNode, nextAnchorOffset, nextFocusNode, nextFocusOffset);
5605
-
5606
- if (!tags.has('skip-scroll-into-view') && nextSelection.isCollapsed() && rootElement !== null && rootElement === activeElement) {
5607
- const selectionTarget = nextSelection instanceof RangeSelection && nextSelection.anchor.type === 'element' ? nextAnchorNode.childNodes[nextAnchorOffset] || null : domSelection.rangeCount > 0 ? domSelection.getRangeAt(0) : null;
5794
+ }
5608
5795
 
5609
- if (selectionTarget !== null) {
5610
- // @ts-ignore Text nodes do have getBoundingClientRect
5611
- const selectionRect = selectionTarget.getBoundingClientRect();
5612
- scrollIntoViewIfNeeded(editor, selectionRect, rootElement);
5796
+ if (!tags.has('skip-scroll-into-view')) // Apply the updated selection to the DOM. Note: this will trigger
5797
+ // a "selectionchange" event, although it will be asynchronous.
5798
+ try {
5799
+ // When updating more than 1000 nodes on Chrome, it's actually better to defer
5800
+ // updating the selection till the next frame. This is because Chrome's
5801
+ // Blink engine has hard limit on how many DOM nodes it can redraw in
5802
+ // a single cycle, so keeping it to the next frame improves performance.
5803
+ // The downside is that is makes the computation within Lexical more
5804
+ // complex, as now, we've sync update the DOM, but selection no longer
5805
+ // matches.
5806
+ if (IS_CHROME && dirtyLeavesCount > 1000) {
5807
+ window.requestAnimationFrame(() => domSelection.setBaseAndExtent(nextAnchorNode, nextAnchorOffset, nextFocusNode, nextFocusOffset));
5808
+ } else {
5809
+ domSelection.setBaseAndExtent(nextAnchorNode, nextAnchorOffset, nextFocusNode, nextFocusOffset);
5613
5810
  }
5811
+ } catch (error) {// If we encounter an error, continue. This can sometimes
5812
+ // occur with FF and there's no good reason as to why it
5813
+ // should happen.
5614
5814
  }
5615
5815
 
5616
- markSelectionChangeFromDOMUpdate();
5617
- } catch (error) {// If we encounter an error, continue. This can sometimes
5618
- // occur with FF and there's no good reason as to why it
5619
- // should happen.
5816
+ if (!tags.has('skip-scroll-into-view') && nextSelection.isCollapsed() && rootElement !== null && rootElement === document.activeElement) {
5817
+ const selectionTarget = nextSelection instanceof RangeSelection && nextSelection.anchor.type === 'element' ? nextAnchorNode.childNodes[nextAnchorOffset] || null : domSelection.rangeCount > 0 ? domSelection.getRangeAt(0) : null;
5818
+
5819
+ if (selectionTarget !== null) {
5820
+ // @ts-ignore Text nodes do have getBoundingClientRect
5821
+ const selectionRect = selectionTarget.getBoundingClientRect();
5822
+ scrollIntoViewIfNeeded(editor, selectionRect, rootElement);
5823
+ }
5620
5824
  }
5825
+
5826
+ markSelectionChangeFromDOMUpdate();
5621
5827
  }
5622
5828
  function $insertNodes(nodes, selectStart) {
5623
5829
  let selection = $getSelection();
@@ -5650,6 +5856,11 @@ let activeEditor = null;
5650
5856
  let isReadOnlyMode = false;
5651
5857
  let isAttemptingToRecoverFromReconcilerError = false;
5652
5858
  let infiniteTransformCount = 0;
5859
+ const observerOptions = {
5860
+ characterData: true,
5861
+ childList: true,
5862
+ subtree: true
5863
+ };
5653
5864
  function isCurrentlyReadOnlyMode() {
5654
5865
  return isReadOnlyMode;
5655
5866
  }
@@ -5930,9 +6141,9 @@ function handleDEVOnlyPendingUpdateGuarantees(pendingEditorState) {
5930
6141
  function commitPendingUpdates(editor) {
5931
6142
  const pendingEditorState = editor._pendingEditorState;
5932
6143
  const rootElement = editor._rootElement;
5933
- const headless = editor._headless;
6144
+ const shouldSkipDOM = editor._headless || rootElement === null;
5934
6145
 
5935
- if (rootElement === null && !headless || pendingEditorState === null) {
6146
+ if (pendingEditorState === null) {
5936
6147
  return;
5937
6148
  } // ======
5938
6149
  // Reconciliation has started.
@@ -5952,7 +6163,7 @@ function commitPendingUpdates(editor) {
5952
6163
  editor._pendingEditorState = null;
5953
6164
  editor._editorState = pendingEditorState;
5954
6165
 
5955
- if (!headless && needsUpdate && observer !== null) {
6166
+ if (!shouldSkipDOM && needsUpdate && observer !== null) {
5956
6167
  activeEditor = editor;
5957
6168
  activeEditorState = pendingEditorState;
5958
6169
  isReadOnlyMode = false; // We don't want updates to sync block the reconciliation.
@@ -5986,11 +6197,7 @@ function commitPendingUpdates(editor) {
5986
6197
 
5987
6198
  return;
5988
6199
  } finally {
5989
- observer.observe(rootElement, {
5990
- characterData: true,
5991
- childList: true,
5992
- subtree: true
5993
- });
6200
+ observer.observe(rootElement, observerOptions);
5994
6201
  editor._updating = previouslyUpdating;
5995
6202
  activeEditorState = previousActiveEditorState;
5996
6203
  isReadOnlyMode = previousReadOnlyMode;
@@ -6018,6 +6225,7 @@ function commitPendingUpdates(editor) {
6018
6225
  const normalizedNodes = editor._normalizedNodes;
6019
6226
  const tags = editor._updateTags;
6020
6227
  const deferred = editor._deferred;
6228
+ const dirtyLeavesCount = dirtyLeaves.size;
6021
6229
 
6022
6230
  if (needsUpdate) {
6023
6231
  editor._dirtyType = NO_DIRTY_NODES;
@@ -6034,7 +6242,7 @@ function commitPendingUpdates(editor) {
6034
6242
  // Reconciliation has finished. Now update selection and trigger listeners.
6035
6243
  // ======
6036
6244
 
6037
- const domSelection = headless ? null : getDOMSelection(); // Attempt to update the DOM selection, including focusing of the root element,
6245
+ const domSelection = shouldSkipDOM ? null : getDOMSelection(); // Attempt to update the DOM selection, including focusing of the root element,
6038
6246
  // and scroll into view if needed.
6039
6247
 
6040
6248
  if (editor._editable && // domSelection will be null in headless
@@ -6043,7 +6251,25 @@ function commitPendingUpdates(editor) {
6043
6251
  activeEditorState = pendingEditorState;
6044
6252
 
6045
6253
  try {
6046
- updateDOMSelection(currentSelection, pendingSelection, editor, domSelection, tags, rootElement);
6254
+ if (observer !== null) {
6255
+ observer.disconnect();
6256
+ }
6257
+
6258
+ if (needsUpdate || pendingSelection === null || pendingSelection.dirty) {
6259
+ const blockCursorElement = editor._blockCursorElement;
6260
+
6261
+ if (blockCursorElement !== null) {
6262
+ removeDOMBlockCursorElement(blockCursorElement, editor, rootElement);
6263
+ }
6264
+
6265
+ updateDOMSelection(currentSelection, pendingSelection, editor, domSelection, tags, rootElement, dirtyLeavesCount);
6266
+ }
6267
+
6268
+ updateDOMBlockCursorElement(editor, rootElement, pendingSelection);
6269
+
6270
+ if (observer !== null) {
6271
+ observer.observe(rootElement, observerOptions);
6272
+ }
6047
6273
  } finally {
6048
6274
  activeEditor = previousActiveEditor;
6049
6275
  activeEditorState = previousActiveEditorState;
@@ -6344,7 +6570,10 @@ function beginUpdate(editor, updateFn, options) {
6344
6570
  infiniteTransformCount = 0;
6345
6571
  }
6346
6572
 
6347
- const shouldUpdate = editor._dirtyType !== NO_DIRTY_NODES || editorStateHasDirtySelection(pendingEditorState, editor);
6573
+ const windowObj = editor._window;
6574
+ const windowEvent = windowObj !== null ? window.event : null;
6575
+ const eventType = windowEvent != null ? windowEvent.type : null;
6576
+ const shouldUpdate = editor._dirtyType !== NO_DIRTY_NODES || editorStateHasDirtySelection(pendingEditorState, editor) || editor._blockCursorElement !== null && eventType === 'blur';
6348
6577
 
6349
6578
  if (shouldUpdate) {
6350
6579
  if (pendingEditorState._flushSync) {
@@ -6411,44 +6640,23 @@ function removeNode(nodeToRemove, restoreSelection, preserveEmptyParent) {
6411
6640
  }
6412
6641
  }
6413
6642
 
6414
- const writableParent = parent.getWritable();
6415
- const parentChildren = writableParent.__children;
6416
- const index = parentChildren.indexOf(key);
6417
-
6418
- if (index === -1) {
6419
- {
6420
- throw Error(`Node is not a child of its parent`);
6421
- }
6422
- }
6423
-
6424
- internalMarkSiblingsAsDirty(nodeToRemove);
6425
- parentChildren.splice(index, 1);
6426
- const writableNodeToRemove = nodeToRemove.getWritable();
6427
- writableNodeToRemove.__parent = null;
6428
-
6429
6643
  if ($isRangeSelection(selection) && restoreSelection && !selectionMoved) {
6644
+ // Doing this is O(n) so lets avoid it unless we need to do it
6645
+ const index = nodeToRemove.getIndexWithinParent();
6646
+ removeFromParent(nodeToRemove);
6430
6647
  $updateElementSelectionOnCreateDeleteNode(selection, parent, index, -1);
6648
+ } else {
6649
+ removeFromParent(nodeToRemove);
6431
6650
  }
6432
6651
 
6433
6652
  if (!preserveEmptyParent && !$isRootOrShadowRoot(parent) && !parent.canBeEmpty() && parent.isEmpty()) {
6434
6653
  removeNode(parent, restoreSelection);
6435
6654
  }
6436
6655
 
6437
- if ($isRootNode(parent) && parent.isEmpty()) {
6656
+ if (restoreSelection && $isRootNode(parent) && parent.isEmpty()) {
6438
6657
  parent.selectEnd();
6439
6658
  }
6440
6659
  }
6441
- function $getNodeByKeyOrThrow(key) {
6442
- const node = $getNodeByKey(key);
6443
-
6444
- if (node === null) {
6445
- {
6446
- throw Error(`Expected node with key ${key} to exist but it's not in the nodeMap.`);
6447
- }
6448
- }
6449
-
6450
- return node;
6451
- }
6452
6660
  class LexicalNode {
6453
6661
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
6454
6662
  // Flow doesn't support abstract classes unfortunately, so we can't _force_
@@ -6544,8 +6752,19 @@ class LexicalNode {
6544
6752
  return -1;
6545
6753
  }
6546
6754
 
6547
- const children = parent.__children;
6548
- return children.indexOf(this.__key);
6755
+ let node = parent.getFirstChild();
6756
+ let index = 0;
6757
+
6758
+ while (node !== null) {
6759
+ if (this.is(node)) {
6760
+ return index;
6761
+ }
6762
+
6763
+ index++;
6764
+ node = node.getNextSibling();
6765
+ }
6766
+
6767
+ return -1;
6549
6768
  }
6550
6769
 
6551
6770
  getParent() {
@@ -6623,62 +6842,44 @@ class LexicalNode {
6623
6842
  }
6624
6843
 
6625
6844
  getPreviousSibling() {
6626
- const parent = this.getParent();
6627
-
6628
- if (parent === null) {
6629
- return null;
6630
- }
6631
-
6632
- const children = parent.__children;
6633
- const index = children.indexOf(this.__key);
6634
-
6635
- if (index <= 0) {
6636
- return null;
6637
- }
6638
-
6639
- return $getNodeByKey(children[index - 1]);
6845
+ const self = this.getLatest();
6846
+ const prevKey = self.__prev;
6847
+ return prevKey === null ? null : $getNodeByKey(prevKey);
6640
6848
  }
6641
6849
 
6642
6850
  getPreviousSiblings() {
6643
- const parent = this.getParent();
6851
+ const siblings = [];
6852
+ const parent = this.getParentOrThrow();
6853
+ let node = parent.getFirstChild();
6644
6854
 
6645
- if (parent === null) {
6646
- return [];
6855
+ while (node !== null) {
6856
+ if (node.is(this)) {
6857
+ break;
6858
+ }
6859
+
6860
+ siblings.push(node);
6861
+ node = node.getNextSibling();
6647
6862
  }
6648
6863
 
6649
- const children = parent.__children;
6650
- const index = children.indexOf(this.__key);
6651
- return children.slice(0, index).map(childKey => $getNodeByKeyOrThrow(childKey));
6864
+ return siblings;
6652
6865
  }
6653
6866
 
6654
6867
  getNextSibling() {
6655
- const parent = this.getParent();
6656
-
6657
- if (parent === null) {
6658
- return null;
6659
- }
6660
-
6661
- const children = parent.__children;
6662
- const childrenLength = children.length;
6663
- const index = children.indexOf(this.__key);
6664
-
6665
- if (index >= childrenLength - 1) {
6666
- return null;
6667
- }
6668
-
6669
- return $getNodeByKey(children[index + 1]);
6868
+ const self = this.getLatest();
6869
+ const nextKey = self.__next;
6870
+ return nextKey === null ? null : $getNodeByKey(nextKey);
6670
6871
  }
6671
6872
 
6672
6873
  getNextSiblings() {
6673
- const parent = this.getParent();
6874
+ const siblings = [];
6875
+ let node = this.getNextSibling();
6674
6876
 
6675
- if (parent === null) {
6676
- return [];
6877
+ while (node !== null) {
6878
+ siblings.push(node);
6879
+ node = node.getNextSibling();
6677
6880
  }
6678
6881
 
6679
- const children = parent.__children;
6680
- const index = children.indexOf(this.__key);
6681
- return children.slice(index + 1).map(childKey => $getNodeByKeyOrThrow(childKey));
6882
+ return siblings;
6682
6883
  }
6683
6884
 
6684
6885
  getCommonAncestor(node) {
@@ -6739,7 +6940,7 @@ class LexicalNode {
6739
6940
  const parent = node.getParentOrThrow();
6740
6941
 
6741
6942
  if (parent === commonAncestor) {
6742
- indexA = parent.__children.indexOf(node.__key);
6943
+ indexA = node.getIndexWithinParent();
6743
6944
  break;
6744
6945
  }
6745
6946
 
@@ -6752,7 +6953,7 @@ class LexicalNode {
6752
6953
  const parent = node.getParentOrThrow();
6753
6954
 
6754
6955
  if (parent === commonAncestor) {
6755
- indexB = parent.__children.indexOf(node.__key);
6956
+ indexB = node.getIndexWithinParent();
6756
6957
  break;
6757
6958
  }
6758
6959
 
@@ -6898,9 +7099,13 @@ class LexicalNode {
6898
7099
 
6899
7100
  const mutableNode = constructor.clone(latestNode);
6900
7101
  mutableNode.__parent = parent;
7102
+ mutableNode.__next = latestNode.__next;
7103
+ mutableNode.__prev = latestNode.__prev;
6901
7104
 
6902
7105
  if ($isElementNode(latestNode) && $isElementNode(mutableNode)) {
6903
- mutableNode.__children = Array.from(latestNode.__children);
7106
+ mutableNode.__first = latestNode.__first;
7107
+ mutableNode.__last = latestNode.__last;
7108
+ mutableNode.__size = latestNode.__size;
6904
7109
  mutableNode.__indent = latestNode.__indent;
6905
7110
  mutableNode.__format = latestNode.__format;
6906
7111
  mutableNode.__dir = latestNode.__dir;
@@ -6975,31 +7180,45 @@ class LexicalNode {
6975
7180
  removeNode(this, true, preserveEmptyParent);
6976
7181
  }
6977
7182
 
6978
- replace(replaceWith) {
7183
+ replace(replaceWith, restoreSelection = true) {
6979
7184
  errorOnReadOnly();
6980
7185
  errorOnInsertTextNodeOnRoot(this, replaceWith);
7186
+ const self = this.getLatest();
6981
7187
  const toReplaceKey = this.__key;
7188
+ const key = replaceWith.__key;
6982
7189
  const writableReplaceWith = replaceWith.getWritable();
7190
+ const writableParent = this.getParentOrThrow().getWritable();
7191
+ const size = writableParent.__size;
6983
7192
  removeFromParent(writableReplaceWith);
6984
- const newParent = this.getParentOrThrow();
6985
- const writableParent = newParent.getWritable();
6986
- const children = writableParent.__children;
6987
- const index = children.indexOf(this.__key);
6988
- const newKey = writableReplaceWith.__key;
7193
+ const prevSibling = self.getPreviousSibling();
7194
+ const nextSibling = self.getNextSibling();
7195
+ const prevKey = self.__prev;
7196
+ const nextKey = self.__next;
7197
+ const parentKey = self.__parent;
7198
+ removeNode(self, false);
6989
7199
 
6990
- if (index === -1) {
6991
- {
6992
- throw Error(`Node is not a child of its parent`);
6993
- }
7200
+ if (prevSibling === null) {
7201
+ writableParent.__first = key;
7202
+ } else {
7203
+ const writablePrevSibling = prevSibling.getWritable();
7204
+ writablePrevSibling.__next = key;
6994
7205
  }
6995
7206
 
6996
- children.splice(index, 0, newKey);
6997
- writableReplaceWith.__parent = newParent.__key;
6998
- removeNode(this, false);
6999
- internalMarkSiblingsAsDirty(writableReplaceWith);
7207
+ writableReplaceWith.__prev = prevKey;
7208
+
7209
+ if (nextSibling === null) {
7210
+ writableParent.__last = key;
7211
+ } else {
7212
+ const writableNextSibling = nextSibling.getWritable();
7213
+ writableNextSibling.__prev = key;
7214
+ }
7215
+
7216
+ writableReplaceWith.__next = nextKey;
7217
+ writableReplaceWith.__parent = parentKey;
7218
+ writableParent.__size = size;
7000
7219
  const selection = $getSelection();
7001
7220
 
7002
- if ($isRangeSelection(selection)) {
7221
+ if ($isRangeSelection(selection) && restoreSelection) {
7003
7222
  const anchor = selection.anchor;
7004
7223
  const focus = selection.focus;
7005
7224
 
@@ -7013,24 +7232,25 @@ class LexicalNode {
7013
7232
  }
7014
7233
 
7015
7234
  if ($getCompositionKey() === toReplaceKey) {
7016
- $setCompositionKey(newKey);
7235
+ $setCompositionKey(key);
7017
7236
  }
7018
7237
 
7019
7238
  return writableReplaceWith;
7020
7239
  }
7021
7240
 
7022
- insertAfter(nodeToInsert) {
7241
+ insertAfter(nodeToInsert, restoreSelection = true) {
7023
7242
  errorOnReadOnly();
7024
7243
  errorOnInsertTextNodeOnRoot(this, nodeToInsert);
7025
7244
  const writableSelf = this.getWritable();
7026
7245
  const writableNodeToInsert = nodeToInsert.getWritable();
7027
7246
  const oldParent = writableNodeToInsert.getParent();
7028
7247
  const selection = $getSelection();
7029
- const oldIndex = nodeToInsert.getIndexWithinParent();
7030
7248
  let elementAnchorSelectionOnNode = false;
7031
7249
  let elementFocusSelectionOnNode = false;
7032
7250
 
7033
7251
  if (oldParent !== null) {
7252
+ // TODO: this is O(n), can we improve?
7253
+ const oldIndex = nodeToInsert.getIndexWithinParent();
7034
7254
  removeFromParent(writableNodeToInsert);
7035
7255
 
7036
7256
  if ($isRangeSelection(selection)) {
@@ -7042,22 +7262,26 @@ class LexicalNode {
7042
7262
  }
7043
7263
  }
7044
7264
 
7265
+ const nextSibling = this.getNextSibling();
7045
7266
  const writableParent = this.getParentOrThrow().getWritable();
7046
7267
  const insertKey = writableNodeToInsert.__key;
7047
- writableNodeToInsert.__parent = writableSelf.__parent;
7048
- const children = writableParent.__children;
7049
- const index = children.indexOf(writableSelf.__key);
7268
+ const nextKey = writableSelf.__next;
7050
7269
 
7051
- if (index === -1) {
7052
- {
7053
- throw Error(`Node is not a child of its parent`);
7054
- }
7270
+ if (nextSibling === null) {
7271
+ writableParent.__last = insertKey;
7272
+ } else {
7273
+ const writableNextSibling = nextSibling.getWritable();
7274
+ writableNextSibling.__prev = insertKey;
7055
7275
  }
7056
7276
 
7057
- children.splice(index + 1, 0, insertKey);
7058
- internalMarkSiblingsAsDirty(writableNodeToInsert);
7277
+ writableParent.__size++;
7278
+ writableSelf.__next = insertKey;
7279
+ writableNodeToInsert.__next = nextKey;
7280
+ writableNodeToInsert.__prev = writableSelf.__key;
7281
+ writableNodeToInsert.__parent = writableSelf.__parent;
7059
7282
 
7060
- if ($isRangeSelection(selection)) {
7283
+ if (restoreSelection && $isRangeSelection(selection)) {
7284
+ const index = this.getIndexWithinParent();
7061
7285
  $updateElementSelectionOnCreateDeleteNode(selection, writableParent, index + 1);
7062
7286
  const writableParentKey = writableParent.__key;
7063
7287
 
@@ -7073,30 +7297,36 @@ class LexicalNode {
7073
7297
  return nodeToInsert;
7074
7298
  }
7075
7299
 
7076
- insertBefore(nodeToInsert) {
7300
+ insertBefore(nodeToInsert, restoreSelection = true) {
7077
7301
  errorOnReadOnly();
7078
7302
  errorOnInsertTextNodeOnRoot(this, nodeToInsert);
7079
7303
  const writableSelf = this.getWritable();
7080
7304
  const writableNodeToInsert = nodeToInsert.getWritable();
7305
+ const insertKey = writableNodeToInsert.__key;
7081
7306
  removeFromParent(writableNodeToInsert);
7307
+ const prevSibling = this.getPreviousSibling();
7082
7308
  const writableParent = this.getParentOrThrow().getWritable();
7083
- const insertKey = writableNodeToInsert.__key;
7084
- writableNodeToInsert.__parent = writableSelf.__parent;
7085
- const children = writableParent.__children;
7086
- const index = children.indexOf(writableSelf.__key);
7309
+ const prevKey = writableSelf.__prev; // TODO: this is O(n), can we improve?
7087
7310
 
7088
- if (index === -1) {
7089
- {
7090
- throw Error(`Node is not a child of its parent`);
7091
- }
7311
+ const index = this.getIndexWithinParent();
7312
+
7313
+ if (prevSibling === null) {
7314
+ writableParent.__first = insertKey;
7315
+ } else {
7316
+ const writablePrevSibling = prevSibling.getWritable();
7317
+ writablePrevSibling.__next = insertKey;
7092
7318
  }
7093
7319
 
7094
- children.splice(index, 0, insertKey);
7095
- internalMarkSiblingsAsDirty(writableNodeToInsert);
7320
+ writableParent.__size++;
7321
+ writableSelf.__prev = insertKey;
7322
+ writableNodeToInsert.__prev = prevKey;
7323
+ writableNodeToInsert.__next = writableSelf.__key;
7324
+ writableNodeToInsert.__parent = writableSelf.__parent;
7096
7325
  const selection = $getSelection();
7097
7326
 
7098
- if ($isRangeSelection(selection)) {
7099
- $updateElementSelectionOnCreateDeleteNode(selection, writableParent, index);
7327
+ if (restoreSelection && $isRangeSelection(selection)) {
7328
+ const parent = this.getParentOrThrow();
7329
+ $updateElementSelectionOnCreateDeleteNode(selection, parent, index);
7100
7330
  }
7101
7331
 
7102
7332
  return nodeToInsert;
@@ -7223,13 +7453,9 @@ class ElementNode extends LexicalNode {
7223
7453
 
7224
7454
  /** @internal */
7225
7455
 
7226
- /** @internal */
7227
-
7228
7456
  /** @internal */
7229
7457
  constructor(key) {
7230
- super(key); // TODO: remove children and switch to using first/last as part of linked list work
7231
-
7232
- this.__children = [];
7458
+ super(key);
7233
7459
  this.__first = null;
7234
7460
  this.__last = null;
7235
7461
  this.__size = 0;
@@ -7254,28 +7480,32 @@ class ElementNode extends LexicalNode {
7254
7480
  }
7255
7481
 
7256
7482
  getChildren() {
7257
- const self = this.getLatest();
7258
- const children = self.__children;
7259
- const childrenNodes = [];
7483
+ const children = [];
7484
+ let child = this.getFirstChild();
7260
7485
 
7261
- for (let i = 0; i < children.length; i++) {
7262
- const childNode = $getNodeByKey(children[i]);
7263
-
7264
- if (childNode !== null) {
7265
- childrenNodes.push(childNode);
7266
- }
7486
+ while (child !== null) {
7487
+ children.push(child);
7488
+ child = child.getNextSibling();
7267
7489
  }
7268
7490
 
7269
- return childrenNodes;
7491
+ return children;
7270
7492
  }
7271
7493
 
7272
7494
  getChildrenKeys() {
7273
- return this.getLatest().__children;
7495
+ const children = [];
7496
+ let child = this.getFirstChild();
7497
+
7498
+ while (child !== null) {
7499
+ children.push(child.__key);
7500
+ child = child.getNextSibling();
7501
+ }
7502
+
7503
+ return children;
7274
7504
  }
7275
7505
 
7276
7506
  getChildrenSize() {
7277
7507
  const self = this.getLatest();
7278
- return self.__children.length;
7508
+ return self.__size;
7279
7509
  }
7280
7510
 
7281
7511
  isEmpty() {
@@ -7290,24 +7520,25 @@ class ElementNode extends LexicalNode {
7290
7520
 
7291
7521
  isLastChild() {
7292
7522
  const self = this.getLatest();
7293
- const parent = self.getParentOrThrow();
7294
- return parent.getLastChild() === self;
7523
+ const parentLastChild = this.getParentOrThrow().getLastChild();
7524
+ return parentLastChild !== null && parentLastChild.is(self);
7295
7525
  }
7296
7526
 
7297
7527
  getAllTextNodes() {
7298
7528
  const textNodes = [];
7299
- const self = this.getLatest();
7300
- const children = self.__children;
7529
+ let child = this.getFirstChild();
7301
7530
 
7302
- for (let i = 0; i < children.length; i++) {
7303
- const childNode = $getNodeByKey(children[i]);
7531
+ while (child !== null) {
7532
+ if ($isTextNode(child)) {
7533
+ textNodes.push(child);
7534
+ }
7304
7535
 
7305
- if ($isTextNode(childNode)) {
7306
- textNodes.push(childNode);
7307
- } else if ($isElementNode(childNode)) {
7308
- const subChildrenNodes = childNode.getAllTextNodes();
7536
+ if ($isElementNode(child)) {
7537
+ const subChildrenNodes = child.getAllTextNodes();
7309
7538
  textNodes.push(...subChildrenNodes);
7310
7539
  }
7540
+
7541
+ child = child.getNextSibling();
7311
7542
  }
7312
7543
 
7313
7544
  return textNodes;
@@ -7367,14 +7598,8 @@ class ElementNode extends LexicalNode {
7367
7598
 
7368
7599
  getFirstChild() {
7369
7600
  const self = this.getLatest();
7370
- const children = self.__children;
7371
- const childrenLength = children.length;
7372
-
7373
- if (childrenLength === 0) {
7374
- return null;
7375
- }
7376
-
7377
- return $getNodeByKey(children[0]);
7601
+ const firstKey = self.__first;
7602
+ return firstKey === null ? null : $getNodeByKey(firstKey);
7378
7603
  }
7379
7604
 
7380
7605
  getFirstChildOrThrow() {
@@ -7391,14 +7616,8 @@ class ElementNode extends LexicalNode {
7391
7616
 
7392
7617
  getLastChild() {
7393
7618
  const self = this.getLatest();
7394
- const children = self.__children;
7395
- const childrenLength = children.length;
7396
-
7397
- if (childrenLength === 0) {
7398
- return null;
7399
- }
7400
-
7401
- return $getNodeByKey(children[childrenLength - 1]);
7619
+ const lastKey = self.__last;
7620
+ return lastKey === null ? null : $getNodeByKey(lastKey);
7402
7621
  }
7403
7622
 
7404
7623
  getLastChildOrThrow() {
@@ -7414,15 +7633,39 @@ class ElementNode extends LexicalNode {
7414
7633
  }
7415
7634
 
7416
7635
  getChildAtIndex(index) {
7417
- const self = this.getLatest();
7418
- const children = self.__children;
7419
- const key = children[index];
7636
+ const size = this.getChildrenSize();
7637
+ let node;
7638
+ let i;
7639
+
7640
+ if (index < size / 2) {
7641
+ node = this.getFirstChild();
7642
+ i = 0;
7643
+
7644
+ while (node !== null && i <= index) {
7645
+ if (i === index) {
7646
+ return node;
7647
+ }
7648
+
7649
+ node = node.getNextSibling();
7650
+ i++;
7651
+ }
7420
7652
 
7421
- if (key === undefined) {
7422
7653
  return null;
7423
7654
  }
7424
7655
 
7425
- return $getNodeByKey(key);
7656
+ node = this.getLastChild();
7657
+ i = size - 1;
7658
+
7659
+ while (node !== null && i >= index) {
7660
+ if (i === index) {
7661
+ return node;
7662
+ }
7663
+
7664
+ node = node.getPreviousSibling();
7665
+ i--;
7666
+ }
7667
+
7668
+ return null;
7426
7669
  }
7427
7670
 
7428
7671
  getTextContent() {
@@ -7464,6 +7707,22 @@ class ElementNode extends LexicalNode {
7464
7707
  let focusOffset = _focusOffset;
7465
7708
  const childrenCount = this.getChildrenSize();
7466
7709
 
7710
+ if (!this.canBeEmpty()) {
7711
+ if (_anchorOffset === 0 && _focusOffset === 0) {
7712
+ const firstChild = this.getFirstChild();
7713
+
7714
+ if ($isTextNode(firstChild) || $isElementNode(firstChild)) {
7715
+ return firstChild.select(0, 0);
7716
+ }
7717
+ } else if ((_anchorOffset === undefined || _anchorOffset === childrenCount) && (_focusOffset === undefined || _focusOffset === childrenCount)) {
7718
+ const lastChild = this.getLastChild();
7719
+
7720
+ if ($isTextNode(lastChild) || $isElementNode(lastChild)) {
7721
+ return lastChild.select();
7722
+ }
7723
+ }
7724
+ }
7725
+
7467
7726
  if (anchorOffset === undefined) {
7468
7727
  anchorOffset = childrenCount;
7469
7728
  }
@@ -7545,55 +7804,108 @@ class ElementNode extends LexicalNode {
7545
7804
  }
7546
7805
 
7547
7806
  splice(start, deleteCount, nodesToInsert) {
7807
+ const nodesToInsertLength = nodesToInsert.length;
7808
+ const oldSize = this.getChildrenSize();
7548
7809
  const writableSelf = this.getWritable();
7549
7810
  const writableSelfKey = writableSelf.__key;
7550
- const writableSelfChildren = writableSelf.__children;
7551
- const nodesToInsertLength = nodesToInsert.length;
7552
- const nodesToInsertKeys = []; // Remove nodes to insert from their previous parent
7811
+ const nodesToInsertKeys = [];
7812
+ const nodesToRemoveKeys = [];
7813
+ const nodeAfterRange = this.getChildAtIndex(start + deleteCount);
7814
+ let nodeBeforeRange = null;
7815
+ let newSize = oldSize - deleteCount + nodesToInsertLength;
7816
+
7817
+ if (start !== 0) {
7818
+ if (start === oldSize) {
7819
+ nodeBeforeRange = this.getLastChild();
7820
+ } else {
7821
+ const node = this.getChildAtIndex(start);
7822
+
7823
+ if (node !== null) {
7824
+ nodeBeforeRange = node.getPreviousSibling();
7825
+ }
7826
+ }
7827
+ }
7828
+
7829
+ if (deleteCount > 0) {
7830
+ let nodeToDelete = nodeBeforeRange === null ? this.getFirstChild() : nodeBeforeRange.getNextSibling();
7831
+
7832
+ for (let i = 0; i < deleteCount; i++) {
7833
+ if (nodeToDelete === null) {
7834
+ {
7835
+ throw Error(`splice: sibling not found`);
7836
+ }
7837
+ }
7838
+
7839
+ const nextSibling = nodeToDelete.getNextSibling();
7840
+ const nodeKeyToDelete = nodeToDelete.__key;
7841
+ const writableNodeToDelete = nodeToDelete.getWritable();
7842
+ removeFromParent(writableNodeToDelete);
7843
+ nodesToRemoveKeys.push(nodeKeyToDelete);
7844
+ nodeToDelete = nextSibling;
7845
+ }
7846
+ }
7847
+
7848
+ let prevNode = nodeBeforeRange;
7553
7849
 
7554
7850
  for (let i = 0; i < nodesToInsertLength; i++) {
7555
7851
  const nodeToInsert = nodesToInsert[i];
7852
+
7853
+ if (prevNode !== null && nodeToInsert.is(prevNode)) {
7854
+ nodeBeforeRange = prevNode = prevNode.getPreviousSibling();
7855
+ }
7856
+
7556
7857
  const writableNodeToInsert = nodeToInsert.getWritable();
7557
7858
 
7859
+ if (writableNodeToInsert.__parent === writableSelfKey) {
7860
+ newSize--;
7861
+ }
7862
+
7863
+ removeFromParent(writableNodeToInsert);
7864
+ const nodeKeyToInsert = nodeToInsert.__key;
7865
+
7866
+ if (prevNode === null) {
7867
+ writableSelf.__first = nodeKeyToInsert;
7868
+ writableNodeToInsert.__prev = null;
7869
+ } else {
7870
+ const writablePrevNode = prevNode.getWritable();
7871
+ writablePrevNode.__next = nodeKeyToInsert;
7872
+ writableNodeToInsert.__prev = writablePrevNode.__key;
7873
+ }
7874
+
7558
7875
  if (nodeToInsert.__key === writableSelfKey) {
7559
7876
  {
7560
7877
  throw Error(`append: attempting to append self`);
7561
7878
  }
7562
- }
7879
+ } // Set child parent to self
7563
7880
 
7564
- removeFromParent(writableNodeToInsert); // Set child parent to self
7565
7881
 
7566
7882
  writableNodeToInsert.__parent = writableSelfKey;
7567
- const newKey = writableNodeToInsert.__key;
7568
- nodesToInsertKeys.push(newKey);
7569
- } // Mark range edges siblings as dirty
7570
-
7571
-
7572
- const nodeBeforeRange = this.getChildAtIndex(start - 1);
7573
-
7574
- if (nodeBeforeRange) {
7575
- internalMarkNodeAsDirty(nodeBeforeRange);
7883
+ nodesToInsertKeys.push(nodeKeyToInsert);
7884
+ prevNode = nodeToInsert;
7576
7885
  }
7577
7886
 
7578
- const nodeAfterRange = this.getChildAtIndex(start + deleteCount);
7579
-
7580
- if (nodeAfterRange) {
7581
- internalMarkNodeAsDirty(nodeAfterRange);
7582
- } // Remove defined range of children
7583
-
7887
+ if (start + deleteCount === oldSize) {
7888
+ if (prevNode !== null) {
7889
+ const writablePrevNode = prevNode.getWritable();
7890
+ writablePrevNode.__next = null;
7891
+ writableSelf.__last = prevNode.__key;
7892
+ }
7893
+ } else if (nodeAfterRange !== null) {
7894
+ const writableNodeAfterRange = nodeAfterRange.getWritable();
7584
7895
 
7585
- let nodesToRemoveKeys; // Using faster push when only appending nodes
7896
+ if (prevNode !== null) {
7897
+ const writablePrevNode = prevNode.getWritable();
7898
+ writableNodeAfterRange.__prev = prevNode.__key;
7899
+ writablePrevNode.__next = nodeAfterRange.__key;
7900
+ } else {
7901
+ writableNodeAfterRange.__prev = null;
7902
+ }
7903
+ }
7586
7904
 
7587
- if (start === writableSelfChildren.length) {
7588
- writableSelfChildren.push(...nodesToInsertKeys);
7589
- nodesToRemoveKeys = [];
7590
- } else {
7591
- nodesToRemoveKeys = writableSelfChildren.splice(start, deleteCount, ...nodesToInsertKeys);
7592
- } // In case of deletion we need to adjust selection, unlink removed nodes
7905
+ writableSelf.__size = newSize; // In case of deletion we need to adjust selection, unlink removed nodes
7593
7906
  // and clean up node itself if it becomes empty. None of these needed
7594
7907
  // for insertion-only cases
7595
7908
 
7596
-
7597
7909
  if (nodesToRemoveKeys.length) {
7598
7910
  // Adjusting selection, in case node that was anchor/focus will be deleted
7599
7911
  const selection = $getSelection();
@@ -7601,50 +7913,21 @@ class ElementNode extends LexicalNode {
7601
7913
  if ($isRangeSelection(selection)) {
7602
7914
  const nodesToRemoveKeySet = new Set(nodesToRemoveKeys);
7603
7915
  const nodesToInsertKeySet = new Set(nodesToInsertKeys);
7604
-
7605
- const isPointRemoved = point => {
7606
- let node = point.getNode();
7607
-
7608
- while (node) {
7609
- const nodeKey = node.__key;
7610
-
7611
- if (nodesToRemoveKeySet.has(nodeKey) && !nodesToInsertKeySet.has(nodeKey)) {
7612
- return true;
7613
- }
7614
-
7615
- node = node.getParent();
7616
- }
7617
-
7618
- return false;
7619
- };
7620
-
7621
7916
  const {
7622
7917
  anchor,
7623
7918
  focus
7624
7919
  } = selection;
7625
7920
 
7626
- if (isPointRemoved(anchor)) {
7921
+ if (isPointRemoved(anchor, nodesToRemoveKeySet, nodesToInsertKeySet)) {
7627
7922
  moveSelectionPointToSibling(anchor, anchor.getNode(), this, nodeBeforeRange, nodeAfterRange);
7628
7923
  }
7629
7924
 
7630
- if (isPointRemoved(focus)) {
7925
+ if (isPointRemoved(focus, nodesToRemoveKeySet, nodesToInsertKeySet)) {
7631
7926
  moveSelectionPointToSibling(focus, focus.getNode(), this, nodeBeforeRange, nodeAfterRange);
7632
- } // Unlink removed nodes from current parent
7633
-
7634
-
7635
- const nodesToRemoveKeysLength = nodesToRemoveKeys.length;
7636
-
7637
- for (let i = 0; i < nodesToRemoveKeysLength; i++) {
7638
- const nodeToRemove = $getNodeByKey(nodesToRemoveKeys[i]);
7639
-
7640
- if (nodeToRemove != null) {
7641
- const writableNodeToRemove = nodeToRemove.getWritable();
7642
- writableNodeToRemove.__parent = null;
7643
- }
7644
7927
  } // Cleanup if node can't be empty
7645
7928
 
7646
7929
 
7647
- if (writableSelfChildren.length === 0 && !this.canBeEmpty() && !$isRootOrShadowRoot(this)) {
7930
+ if (newSize === 0 && !this.canBeEmpty() && !$isRootOrShadowRoot(this)) {
7648
7931
  this.remove();
7649
7932
  }
7650
7933
  }
@@ -7666,7 +7949,7 @@ class ElementNode extends LexicalNode {
7666
7949
  } // These are intended to be extends for specific element heuristics.
7667
7950
 
7668
7951
 
7669
- insertNewAfter(selection) {
7952
+ insertNewAfter(selection, restoreSelection) {
7670
7953
  return null;
7671
7954
  }
7672
7955
 
@@ -7741,6 +8024,22 @@ function $isElementNode(node) {
7741
8024
  return node instanceof ElementNode;
7742
8025
  }
7743
8026
 
8027
+ function isPointRemoved(point, nodesToRemoveKeySet, nodesToInsertKeySet) {
8028
+ let node = point.getNode();
8029
+
8030
+ while (node) {
8031
+ const nodeKey = node.__key;
8032
+
8033
+ if (nodesToRemoveKeySet.has(nodeKey) && !nodesToInsertKeySet.has(nodeKey)) {
8034
+ return true;
8035
+ }
8036
+
8037
+ node = node.getParent();
8038
+ }
8039
+
8040
+ return false;
8041
+ }
8042
+
7744
8043
  /**
7745
8044
  * Copyright (c) Meta Platforms, Inc. and affiliates.
7746
8045
  *
@@ -8183,6 +8482,13 @@ function createTextInnerDOM(innerDOM, node, innerTag, format, text, config) {
8183
8482
 
8184
8483
 
8185
8484
  class TextNode extends LexicalNode {
8485
+ /** @internal */
8486
+
8487
+ /** @internal */
8488
+
8489
+ /** @internal */
8490
+
8491
+ /** @internal */
8186
8492
  static getType() {
8187
8493
  return 'text';
8188
8494
  }
@@ -8383,6 +8689,14 @@ class TextNode extends LexicalNode {
8383
8689
  conversion: convertTextFormatElement,
8384
8690
  priority: 0
8385
8691
  }),
8692
+ sub: node => ({
8693
+ conversion: convertTextFormatElement,
8694
+ priority: 0
8695
+ }),
8696
+ sup: node => ({
8697
+ conversion: convertTextFormatElement,
8698
+ priority: 0
8699
+ }),
8386
8700
  u: node => ({
8387
8701
  conversion: convertTextFormatElement,
8388
8702
  priority: 0
@@ -8581,7 +8895,6 @@ class TextNode extends LexicalNode {
8581
8895
 
8582
8896
  const firstPart = parts[0];
8583
8897
  const parent = self.getParentOrThrow();
8584
- const parentKey = parent.__key;
8585
8898
  let writableNode;
8586
8899
  const format = self.getFormat();
8587
8900
  const style = self.getStyle();
@@ -8591,7 +8904,6 @@ class TextNode extends LexicalNode {
8591
8904
  if (self.isSegmented()) {
8592
8905
  // Create a new TextNode
8593
8906
  writableNode = $createTextNode(firstPart);
8594
- writableNode.__parent = parentKey;
8595
8907
  writableNode.__format = format;
8596
8908
  writableNode.__style = style;
8597
8909
  writableNode.__detail = detail;
@@ -8640,22 +8952,19 @@ class TextNode extends LexicalNode {
8640
8952
  }
8641
8953
 
8642
8954
  textSize = nextTextSize;
8643
- sibling.__parent = parentKey;
8644
8955
  splitNodes.push(sibling);
8645
8956
  } // Insert the nodes into the parent's children
8646
8957
 
8647
8958
 
8648
8959
  internalMarkSiblingsAsDirty(this);
8649
8960
  const writableParent = parent.getWritable();
8650
- const writableParentChildren = writableParent.__children;
8651
- const insertionIndex = writableParentChildren.indexOf(key);
8652
- const splitNodesKeys = splitNodes.map(splitNode => splitNode.__key);
8961
+ const insertionIndex = this.getIndexWithinParent();
8653
8962
 
8654
8963
  if (hasReplacedSelf) {
8655
- writableParentChildren.splice(insertionIndex, 0, ...splitNodesKeys);
8964
+ writableParent.splice(insertionIndex, 0, splitNodes);
8656
8965
  this.remove();
8657
8966
  } else {
8658
- writableParentChildren.splice(insertionIndex, 1, ...splitNodesKeys);
8967
+ writableParent.splice(insertionIndex, 1, splitNodes);
8659
8968
  }
8660
8969
 
8661
8970
  if ($isRangeSelection(selection)) {
@@ -8804,6 +9113,8 @@ const nodeNameToTextFormat = {
8804
9113
  em: 'italic',
8805
9114
  i: 'italic',
8806
9115
  strong: 'bold',
9116
+ sub: 'subscript',
9117
+ sup: 'superscript',
8807
9118
  u: 'underline'
8808
9119
  };
8809
9120
 
@@ -8927,11 +9238,11 @@ class ParagraphNode extends ElementNode {
8927
9238
  } // Mutation
8928
9239
 
8929
9240
 
8930
- insertNewAfter() {
9241
+ insertNewAfter(_, restoreSelection) {
8931
9242
  const newElement = $createParagraphNode();
8932
9243
  const direction = this.getDirection();
8933
9244
  newElement.setDirection(direction);
8934
- this.insertAfter(newElement);
9245
+ this.insertAfter(newElement, restoreSelection);
8935
9246
  return newElement;
8936
9247
  }
8937
9248
 
@@ -9005,6 +9316,7 @@ function resetEditor(editor, prevRootElement, nextRootElement, pendingEditorStat
9005
9316
  editor._normalizedNodes = new Set();
9006
9317
  editor._updateTags = new Set();
9007
9318
  editor._updates = [];
9319
+ editor._blockCursorElement = null;
9008
9320
  const observer = editor._observer;
9009
9321
 
9010
9322
  if (observer !== null) {
@@ -9191,8 +9503,9 @@ class LexicalEditor {
9191
9503
  // Doing so, causes e2e tests around the lock to fail.
9192
9504
 
9193
9505
  this._editable = true;
9194
- this._headless = false;
9506
+ this._headless = parentEditor !== null && parentEditor._headless;
9195
9507
  this._window = null;
9508
+ this._blockCursorElement = null;
9196
9509
  }
9197
9510
 
9198
9511
  isComposing() {
@@ -9515,7 +9828,7 @@ class LexicalEditor {
9515
9828
  * LICENSE file in the root directory of this source tree.
9516
9829
  *
9517
9830
  */
9518
- const VERSION = '0.6.4';
9831
+ const VERSION = '0.7.0';
9519
9832
 
9520
9833
  /**
9521
9834
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -9576,7 +9889,7 @@ exports.$createNodeSelection = $createNodeSelection;
9576
9889
  exports.$createParagraphNode = $createParagraphNode;
9577
9890
  exports.$createRangeSelection = $createRangeSelection;
9578
9891
  exports.$createTextNode = $createTextNode;
9579
- exports.$getDecoratorNode = $getDecoratorNode;
9892
+ exports.$getAdjacentNode = $getAdjacentNode;
9580
9893
  exports.$getNearestNodeFromDOMNode = $getNearestNodeFromDOMNode;
9581
9894
  exports.$getNearestRootOrShadowRoot = $getNearestRootOrShadowRoot;
9582
9895
  exports.$getNodeByKey = $getNodeByKey;