lexical 0.1.8 → 0.1.9

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
@@ -14,7 +14,7 @@
14
14
  *
15
15
  *
16
16
  */
17
- const VERSION = '0.1.8'; // DOM
17
+ const VERSION = '0.1.9'; // DOM
18
18
 
19
19
  const DOM_ELEMENT_TYPE = 1;
20
20
  const DOM_TEXT_TYPE = 3; // Reconciling
@@ -159,7 +159,6 @@ function $flushMutations$1(editor, mutations, observer) {
159
159
 
160
160
  try {
161
161
  updateEditor(editor, () => {
162
- $pushLogEntry('onMutation');
163
162
  const badDOMTargets = new Map();
164
163
  const rootElement = editor.getRootElement(); // We use the current edtior state, as that reflects what is
165
164
  // actually "on screen".
@@ -218,7 +217,7 @@ function $flushMutations$1(editor, mutations, observer) {
218
217
 
219
218
  if (removedDOMsLength !== unremovedBRs) {
220
219
  if (targetDOM === rootElement) {
221
- targetNode = $getRoot(currentEditorState);
220
+ targetNode = internalGetRoot(currentEditorState);
222
221
  }
223
222
 
224
223
  badDOMTargets.set(targetDOM, targetNode);
@@ -299,7 +298,7 @@ function $flushMutations$1(editor, mutations, observer) {
299
298
  $setSelection(selection);
300
299
  }
301
300
  }
302
- }, true);
301
+ });
303
302
  } finally {
304
303
  isProcessingMutations = false;
305
304
  }
@@ -400,9 +399,14 @@ function toggleTextFormatType(format, type, alignWithFormat) {
400
399
  return format;
401
400
  }
402
401
  function $isLeafNode(node) {
403
- return $isTextNode(node) || $isLineBreakNode(node) || $isDecoratorNode(node) || $isHorizontalRuleNode(node);
402
+ return $isTextNode(node) || $isLineBreakNode(node) || $isDecoratorNode(node);
404
403
  }
405
- function $generateKey(node) {
404
+ function $setNodeKey(node, existingKey) {
405
+ if (existingKey != null) {
406
+ node.__key = existingKey;
407
+ return;
408
+ }
409
+
406
410
  errorOnReadOnly();
407
411
  errorOnInfiniteTransforms();
408
412
  const editor = getActiveEditor();
@@ -421,10 +425,10 @@ function $generateKey(node) {
421
425
  editor._cloneNotNeeded.add(key);
422
426
 
423
427
  editor._dirtyType = HAS_DIRTY_NODES;
424
- return key;
428
+ node.__key = key;
425
429
  }
426
430
 
427
- function $internallyMarkParentElementsAsDirty(parentKey, nodeMap, dirtyElements) {
431
+ function internalMarkParentElementsAsDirty(parentKey, nodeMap, dirtyElements) {
428
432
  let nextParentKey = parentKey;
429
433
 
430
434
  while (nextParentKey !== null) {
@@ -445,7 +449,7 @@ function $internallyMarkParentElementsAsDirty(parentKey, nodeMap, dirtyElements)
445
449
  // the cloning heuristic. Instead use node.getWritable().
446
450
 
447
451
 
448
- function $internallyMarkNodeAsDirty(node) {
452
+ function internalMarkNodeAsDirty(node) {
449
453
  errorOnInfiniteTransforms();
450
454
  const latest = node.getLatest();
451
455
  const parent = latest.__parent;
@@ -455,7 +459,7 @@ function $internallyMarkNodeAsDirty(node) {
455
459
  const dirtyElements = editor._dirtyElements;
456
460
 
457
461
  if (parent !== null) {
458
- $internallyMarkParentElementsAsDirty(parent, nodeMap, dirtyElements);
462
+ internalMarkParentElementsAsDirty(parent, nodeMap, dirtyElements);
459
463
  }
460
464
 
461
465
  const key = latest.__key;
@@ -468,16 +472,16 @@ function $internallyMarkNodeAsDirty(node) {
468
472
  editor._dirtyLeaves.add(key);
469
473
  }
470
474
  }
471
- function $internallyMarkSiblingsAsDirty(node) {
475
+ function internalMarkSiblingsAsDirty(node) {
472
476
  const previousNode = node.getPreviousSibling();
473
477
  const nextNode = node.getNextSibling();
474
478
 
475
479
  if (previousNode !== null) {
476
- $internallyMarkNodeAsDirty(previousNode);
480
+ internalMarkNodeAsDirty(previousNode);
477
481
  }
478
482
 
479
483
  if (nextNode !== null) {
480
- $internallyMarkNodeAsDirty(nextNode);
484
+ internalMarkNodeAsDirty(nextNode);
481
485
  }
482
486
  }
483
487
  function $setCompositionKey(compositionKey) {
@@ -549,11 +553,6 @@ function cloneDecorators(editor) {
549
553
  editor._pendingDecorators = pendingDecorators;
550
554
  return pendingDecorators;
551
555
  }
552
- function $pushLogEntry(entry) {
553
- const editor = getActiveEditor();
554
-
555
- editor._log.push(entry);
556
- }
557
556
  function getEditorStateTextContent(editorState) {
558
557
  return editorState.read(view => $getRoot().getTextContent());
559
558
  }
@@ -579,12 +578,15 @@ function markAllNodesAsDirty(editor, type) {
579
578
  const node = nodeMapEntries[i][1];
580
579
  node.markDirty();
581
580
  }
582
- }, true, editor._pendingEditorState === null ? {
583
- tag: 'without-history'
581
+ }, editor._pendingEditorState === null ? {
582
+ tag: 'history-merge'
584
583
  } : undefined);
585
584
  }
586
- function $getRoot(editorState) {
587
- return (editorState || getActiveEditorState())._nodeMap.get('root' // $FlowFixMe: root is always in our Map
585
+ function $getRoot() {
586
+ return internalGetRoot(getActiveEditorState());
587
+ }
588
+ function internalGetRoot(editorState) {
589
+ return editorState._nodeMap.get('root' // $FlowFixMe: root is always in our Map
588
590
  );
589
591
  }
590
592
  function $setSelection(selection) {
@@ -896,6 +898,25 @@ function getCachedClassNameArray(classNamesTheme, classNameThemeType) {
896
898
 
897
899
  return classNames;
898
900
  }
901
+ function setMutatedNode(mutatedNodes, registeredNodes, node, mutation) {
902
+ const registeredNode = registeredNodes.get(node.__type);
903
+
904
+ if (registeredNode === undefined) {
905
+ {
906
+ throw Error(`Type ${node.__type} not in registeredNodes`);
907
+ }
908
+ }
909
+
910
+ const klass = registeredNode.klass;
911
+ let mutatedNodesByType = mutatedNodes.get(klass);
912
+
913
+ if (mutatedNodesByType === undefined) {
914
+ mutatedNodesByType = new Map();
915
+ mutatedNodes.set(klass, mutatedNodesByType);
916
+ }
917
+
918
+ mutatedNodesByType.set(node.__key, mutation);
919
+ }
899
920
 
900
921
  /**
901
922
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -1065,17 +1086,41 @@ function $normalizeTextNode(textNode) {
1065
1086
  *
1066
1087
  *
1067
1088
  */
1089
+ function resolveElement(element, isBackward, focusOffset) {
1090
+ const parent = element.getParent();
1091
+ let offset = focusOffset;
1092
+ let block = element;
1093
+
1094
+ if (parent !== null) {
1095
+ if (isBackward && focusOffset === 0) {
1096
+ offset = block.getIndexWithinParent();
1097
+ block = parent;
1098
+ } else if (!isBackward && focusOffset === block.getChildrenSize()) {
1099
+ offset = block.getIndexWithinParent() + 1;
1100
+ block = parent;
1101
+ }
1102
+ }
1103
+
1104
+ return block.getChildAtIndex(isBackward ? offset - 1 : offset);
1105
+ }
1106
+
1068
1107
  function getPossibleDecoratorNode(focus, isBackward) {
1069
1108
  const focusOffset = focus.offset;
1070
1109
 
1071
1110
  if (focus.type === 'element') {
1072
1111
  const block = focus.getNode();
1073
- return block.getChildAtIndex(isBackward ? focusOffset - 1 : focusOffset);
1112
+ return resolveElement(block, isBackward, focusOffset);
1074
1113
  } else {
1075
1114
  const focusNode = focus.getNode();
1076
1115
 
1077
1116
  if (isBackward && focusOffset === 0 || !isBackward && focusOffset === focusNode.getTextContentSize()) {
1078
- return isBackward ? focusNode.getPreviousSibling() : focusNode.getNextSibling();
1117
+ const possibleNode = isBackward ? focusNode.getPreviousSibling() : focusNode.getNextSibling();
1118
+
1119
+ if (possibleNode === null) {
1120
+ return resolveElement(focusNode.getParentOrThrow(), isBackward, focusNode.getIndexWithinParent() + (isBackward ? 0 : 1));
1121
+ }
1122
+
1123
+ return possibleNode;
1079
1124
  }
1080
1125
  }
1081
1126
 
@@ -1200,15 +1245,20 @@ function $transferStartingElementPointToTextPoint(start, end, format) {
1200
1245
  const element = start.getNode();
1201
1246
  const placementNode = element.getChildAtIndex(start.offset);
1202
1247
  const textNode = $createTextNode();
1248
+ const target = $isRootNode(element) ? $createParagraphNode().append(textNode) : textNode;
1203
1249
  textNode.setFormat(format);
1204
1250
 
1205
1251
  if (placementNode === null) {
1206
- element.append(textNode);
1252
+ element.append(target);
1207
1253
  } else {
1208
- placementNode.insertBefore(textNode);
1254
+ placementNode.insertBefore(target);
1209
1255
  } // Transfer the element point to a text point.
1210
1256
 
1211
1257
 
1258
+ if (start.is(end)) {
1259
+ end.set(textNode.getKey(), 0, 'text');
1260
+ }
1261
+
1212
1262
  start.set(textNode.getKey(), 0, 'text');
1213
1263
  }
1214
1264
 
@@ -1322,7 +1372,7 @@ class RangeSelection {
1322
1372
  }
1323
1373
 
1324
1374
  textContent += text;
1325
- } else if (($isDecoratorNode(node) || $isLineBreakNode(node) || $isHorizontalRuleNode(node)) && (node !== lastNode || !this.isCollapsed())) {
1375
+ } else if (($isDecoratorNode(node) || $isLineBreakNode(node)) && (node !== lastNode || !this.isCollapsed())) {
1326
1376
  textContent += node.getTextContent();
1327
1377
  }
1328
1378
  }
@@ -1333,7 +1383,9 @@ class RangeSelection {
1333
1383
 
1334
1384
  applyDOMRange(range) {
1335
1385
  const editor = getActiveEditor();
1336
- const resolvedSelectionPoints = internalResolveSelectionPoints(range.startContainer, range.startOffset, range.endContainer, range.endOffset, editor);
1386
+ const currentEditorState = editor.getEditorState();
1387
+ const lastSelection = currentEditorState._selection;
1388
+ const resolvedSelectionPoints = internalResolveSelectionPoints(range.startContainer, range.startOffset, range.endContainer, range.endOffset, editor, lastSelection);
1337
1389
 
1338
1390
  if (resolvedSelectionPoints === null) {
1339
1391
  return;
@@ -1591,7 +1643,7 @@ class RangeSelection {
1591
1643
  const selectedNode = selectedNodes[i];
1592
1644
  const key = selectedNode.getKey();
1593
1645
 
1594
- if (!markedNodeKeysForKeep.has(key) && (!$isElementNode(selectedNode) || selectedNode.canSelectionRemove())) {
1646
+ if (!markedNodeKeysForKeep.has(key)) {
1595
1647
  selectedNode.remove();
1596
1648
  }
1597
1649
  }
@@ -1752,7 +1804,7 @@ class RangeSelection {
1752
1804
  // append them after the last node we're inserting.
1753
1805
 
1754
1806
  const nextSiblings = anchorNode.getNextSiblings();
1755
- const topLevelElement = anchorNode.getTopLevelElementOrThrow();
1807
+ const topLevelElement = $isRootNode(anchorNode) ? null : anchorNode.getTopLevelElementOrThrow();
1756
1808
 
1757
1809
  if ($isTextNode(anchorNode)) {
1758
1810
  const textContent = anchorNode.getTextContent();
@@ -1869,9 +1921,15 @@ class RangeSelection {
1869
1921
  }
1870
1922
 
1871
1923
  if ($isTextNode(target)) {
1924
+ if (topLevelElement === null) {
1925
+ {
1926
+ throw Error(`insertNode: topLevelElement is root node`);
1927
+ }
1928
+ }
1929
+
1872
1930
  target = topLevelElement;
1873
1931
  }
1874
- } else if (didReplaceOrMerge && $isRootNode(target.getParent())) {
1932
+ } else if (didReplaceOrMerge && !$isDecoratorNode(node) && $isRootNode(target.getParent())) {
1875
1933
  {
1876
1934
  throw Error(`insertNodes: cannot insert a non-element into a root node`);
1877
1935
  }
@@ -1880,7 +1938,9 @@ class RangeSelection {
1880
1938
  didReplaceOrMerge = false;
1881
1939
 
1882
1940
  if ($isElementNode(target)) {
1883
- if (!$isElementNode(node)) {
1941
+ if ($isDecoratorNode(node) && node.isTopLevel()) {
1942
+ target = target.insertAfter(node);
1943
+ } else if (!$isElementNode(node)) {
1884
1944
  const firstChild = target.getFirstChild();
1885
1945
 
1886
1946
  if (firstChild !== null) {
@@ -1895,9 +1955,21 @@ class RangeSelection {
1895
1955
  continue;
1896
1956
  }
1897
1957
 
1898
- target = target.insertAfter(node);
1958
+ if ($isRootNode(target)) {
1959
+ const placementNode = target.getChildAtIndex(anchorOffset);
1960
+
1961
+ if (placementNode !== null) {
1962
+ placementNode.insertBefore(node);
1963
+ } else {
1964
+ target.append(node);
1965
+ }
1966
+
1967
+ target = node;
1968
+ } else {
1969
+ target = target.insertAfter(node);
1970
+ }
1899
1971
  }
1900
- } else if (!$isElementNode(node)) {
1972
+ } else if (!$isElementNode(node) || $isDecoratorNode(target) && target.isTopLevel()) {
1901
1973
  target = target.insertAfter(node);
1902
1974
  } else {
1903
1975
  target = node.getParentOrThrow(); // Re-try again with the target being the parent
@@ -2008,6 +2080,21 @@ class RangeSelection {
2008
2080
  }
2009
2081
  } else {
2010
2082
  currentElement = anchor.getNode();
2083
+
2084
+ if ($isRootNode(currentElement)) {
2085
+ const paragraph = $createParagraphNode();
2086
+ const child = currentElement.getChildAtIndex(anchorOffset);
2087
+ paragraph.select();
2088
+
2089
+ if (child !== null) {
2090
+ child.insertBefore(paragraph);
2091
+ } else {
2092
+ currentElement.append(paragraph);
2093
+ }
2094
+
2095
+ return;
2096
+ }
2097
+
2011
2098
  nodesToMove = currentElement.getChildren().slice(anchorOffset).reverse();
2012
2099
  }
2013
2100
 
@@ -2045,6 +2132,15 @@ class RangeSelection {
2045
2132
 
2046
2133
  insertLineBreak(selectStart) {
2047
2134
  const lineBreakNode = $createLineBreakNode();
2135
+ const anchor = this.anchor;
2136
+
2137
+ if (anchor.type === 'element') {
2138
+ const element = anchor.getNode();
2139
+
2140
+ if ($isRootNode(element)) {
2141
+ this.insertParagraph();
2142
+ }
2143
+ }
2048
2144
 
2049
2145
  if (selectStart) {
2050
2146
  this.insertNodes([lineBreakNode], true);
@@ -2114,17 +2210,26 @@ class RangeSelection {
2114
2210
  const anchor = this.anchor;
2115
2211
  const collapse = alter === 'move'; // Handle the selection movement around decorators.
2116
2212
 
2117
- const possibleDecoratorNode = getPossibleDecoratorNode(focus, isBackward);
2213
+ const possibleNode = getPossibleDecoratorNode(focus, isBackward);
2118
2214
 
2119
- if ($isDecoratorNode(possibleDecoratorNode)) {
2120
- const sibling = isBackward ? possibleDecoratorNode.getPreviousSibling() : possibleDecoratorNode.getNextSibling();
2215
+ if ($isDecoratorNode(possibleNode) && !possibleNode.isIsolated()) {
2216
+ const sibling = isBackward ? possibleNode.getPreviousSibling() : possibleNode.getNextSibling();
2121
2217
 
2122
2218
  if (!$isTextNode(sibling)) {
2123
- const elementKey = possibleDecoratorNode.getParentOrThrow().getKey();
2124
- let offset = possibleDecoratorNode.getIndexWithinParent();
2219
+ const parent = possibleNode.getParentOrThrow();
2220
+ let offset;
2221
+ let elementKey;
2222
+
2223
+ if ($isElementNode(sibling)) {
2224
+ elementKey = sibling.getKey();
2225
+ offset = isBackward ? sibling.getChildrenSize() : 0;
2226
+ } else {
2227
+ offset = possibleNode.getIndexWithinParent();
2228
+ elementKey = parent.getKey();
2125
2229
 
2126
- if (!isBackward) {
2127
- offset++;
2230
+ if (!isBackward) {
2231
+ offset++;
2232
+ }
2128
2233
  }
2129
2234
 
2130
2235
  focus.set(elementKey, offset, 'element');
@@ -2165,9 +2270,7 @@ class RangeSelection {
2165
2270
  const focus = this.focus;
2166
2271
  let anchorNode = anchor.getNode();
2167
2272
 
2168
- if ($isElementNode(anchorNode) && !anchorNode.canSelectionRemove()) {
2169
- return;
2170
- } else if (!isBackward && ( // Delete forward handle case
2273
+ if (!isBackward && ( // Delete forward handle case
2171
2274
  anchor.type === 'element' && // $FlowFixMe: always an element node
2172
2275
  anchor.offset === anchorNode.getChildrenSize() || anchor.type === 'text' && anchor.offset === anchorNode.getTextContentSize())) {
2173
2276
  const nextSibling = anchorNode.getNextSibling() || anchorNode.getParentOrThrow().getNextSibling();
@@ -2334,7 +2437,12 @@ function $removeSegment(node, isBackward) {
2334
2437
  }
2335
2438
  }
2336
2439
 
2337
- function internalResolveSelectionPoint(dom, offset) {
2440
+ function shouldResolveAncestor(resolvedElement, resolvedOffset, lastPoint) {
2441
+ const parent = resolvedElement.getParent();
2442
+ return lastPoint === null || parent === null || !parent.canBeEmpty() || parent !== lastPoint.getNode();
2443
+ }
2444
+
2445
+ function internalResolveSelectionPoint(dom, offset, lastPoint) {
2338
2446
  let resolvedOffset = offset;
2339
2447
  let resolvedNode; // If we have selection on an element, we will
2340
2448
  // need to figure out (using the offset) what text
@@ -2360,9 +2468,6 @@ function internalResolveSelectionPoint(dom, offset) {
2360
2468
 
2361
2469
  if ($isTextNode(resolvedNode)) {
2362
2470
  resolvedOffset = getTextNodeOffset(resolvedNode, moveSelectionToEnd);
2363
- } else if ($isHorizontalRuleNode(resolvedNode)) {
2364
- resolvedOffset = 0;
2365
- return $createPoint(resolvedNode.__key, resolvedOffset, 'element');
2366
2471
  } else {
2367
2472
  let resolvedElement = getNodeFromDOM(dom); // Ensure resolvedElement is actually a element.
2368
2473
 
@@ -2373,7 +2478,7 @@ function internalResolveSelectionPoint(dom, offset) {
2373
2478
  if ($isElementNode(resolvedElement)) {
2374
2479
  let child = resolvedElement.getChildAtIndex(resolvedOffset);
2375
2480
 
2376
- if ($isElementNode(child)) {
2481
+ if ($isElementNode(child) && shouldResolveAncestor(child, resolvedOffset, lastPoint)) {
2377
2482
  const descendant = moveSelectionToEnd ? child.getLastDescendant() : child.getFirstDescendant();
2378
2483
 
2379
2484
  if (descendant === null) {
@@ -2395,11 +2500,6 @@ function internalResolveSelectionPoint(dom, offset) {
2395
2500
  } else {
2396
2501
  resolvedOffset = resolvedElement.getIndexWithinParent() + 1;
2397
2502
  resolvedElement = resolvedElement.getParentOrThrow();
2398
- } // You can't select root nodes
2399
-
2400
-
2401
- if ($isRootNode(resolvedElement)) {
2402
- return null;
2403
2503
  }
2404
2504
 
2405
2505
  if ($isElementNode(resolvedElement)) {
@@ -2418,18 +2518,18 @@ function internalResolveSelectionPoint(dom, offset) {
2418
2518
  return $createPoint(resolvedNode.__key, resolvedOffset, 'text');
2419
2519
  }
2420
2520
 
2421
- function internalResolveSelectionPoints(anchorDOM, anchorOffset, focusDOM, focusOffset, editor) {
2521
+ function internalResolveSelectionPoints(anchorDOM, anchorOffset, focusDOM, focusOffset, editor, lastSelection) {
2422
2522
  if (anchorDOM === null || focusDOM === null || !isSelectionWithinEditor(editor, anchorDOM, focusDOM)) {
2423
2523
  return null;
2424
2524
  }
2425
2525
 
2426
- const resolvedAnchorPoint = internalResolveSelectionPoint(anchorDOM, anchorOffset);
2526
+ const resolvedAnchorPoint = internalResolveSelectionPoint(anchorDOM, anchorOffset, lastSelection !== null ? lastSelection.anchor : null);
2427
2527
 
2428
2528
  if (resolvedAnchorPoint === null) {
2429
2529
  return null;
2430
2530
  }
2431
2531
 
2432
- const resolvedFocusPoint = internalResolveSelectionPoint(focusDOM, focusOffset);
2532
+ const resolvedFocusPoint = internalResolveSelectionPoint(focusDOM, focusOffset, lastSelection !== null ? lastSelection.focus : null);
2433
2533
 
2434
2534
  if (resolvedFocusPoint === null) {
2435
2535
  return null;
@@ -2467,9 +2567,6 @@ function internalResolveSelectionPoints(anchorDOM, anchorOffset, focusDOM, focus
2467
2567
  }
2468
2568
  }
2469
2569
 
2470
- const currentEditorState = editor.getEditorState();
2471
- const lastSelection = currentEditorState._selection;
2472
-
2473
2570
  if (editor.isComposing() && editor._compositionKey !== resolvedAnchorPoint.key && lastSelection !== null) {
2474
2571
  const lastAnchor = lastSelection.anchor;
2475
2572
  const lastFocus = lastSelection.focus;
@@ -2542,7 +2639,7 @@ function internalCreateRangeSelection(editor) {
2542
2639
  // native selection.
2543
2640
 
2544
2641
 
2545
- const resolvedSelectionPoints = internalResolveSelectionPoints(anchorDOM, anchorOffset, focusDOM, focusOffset, editor);
2642
+ const resolvedSelectionPoints = internalResolveSelectionPoints(anchorDOM, anchorOffset, focusDOM, focusOffset, editor, lastSelection);
2546
2643
 
2547
2644
  if (resolvedSelectionPoints === null) {
2548
2645
  return null;
@@ -2797,7 +2894,7 @@ function removeNode(nodeToRemove, restoreSelection) {
2797
2894
  }
2798
2895
  }
2799
2896
 
2800
- $internallyMarkSiblingsAsDirty(nodeToRemove);
2897
+ internalMarkSiblingsAsDirty(nodeToRemove);
2801
2898
  parentChildren.splice(index, 1);
2802
2899
  const writableNodeToRemove = nodeToRemove.getWritable();
2803
2900
  writableNodeToRemove.__parent = null;
@@ -2830,8 +2927,8 @@ class LexicalNode {
2830
2927
 
2831
2928
  constructor(key) {
2832
2929
  this.__type = this.constructor.getType();
2833
- this.__key = key || $generateKey(this);
2834
- this.__parent = null; // Ensure custom nodes implement required methods.
2930
+ this.__parent = null;
2931
+ $setNodeKey(this, key); // Ensure custom nodes implement required methods.
2835
2932
 
2836
2933
  {
2837
2934
  const proto = Object.getPrototypeOf(this);
@@ -3269,7 +3366,7 @@ class LexicalNode {
3269
3366
 
3270
3367
  if (cloneNotNeeded.has(key)) {
3271
3368
  // Transforms clear the dirty node set on each iteration to keep track on newly dirty nodes
3272
- $internallyMarkNodeAsDirty(latestNode);
3369
+ internalMarkNodeAsDirty(latestNode);
3273
3370
  return latestNode;
3274
3371
  }
3275
3372
 
@@ -3293,7 +3390,7 @@ class LexicalNode {
3293
3390
 
3294
3391
  cloneNotNeeded.add(key);
3295
3392
  mutableNode.__key = key;
3296
- $internallyMarkNodeAsDirty(mutableNode); // Update reference in node map
3393
+ internalMarkNodeAsDirty(mutableNode); // Update reference in node map
3297
3394
 
3298
3395
  nodeMap.set(key, mutableNode); // $FlowFixMe this is LexicalNode
3299
3396
 
@@ -3349,7 +3446,7 @@ class LexicalNode {
3349
3446
  }
3350
3447
  }
3351
3448
 
3352
- $internallyMarkSiblingsAsDirty(writableReplaceWith);
3449
+ internalMarkSiblingsAsDirty(writableReplaceWith);
3353
3450
  children.splice(index, 1);
3354
3451
  }
3355
3452
 
@@ -3368,7 +3465,7 @@ class LexicalNode {
3368
3465
  children.splice(index, 0, newKey);
3369
3466
  writableReplaceWith.__parent = newParent.__key;
3370
3467
  removeNode(this, false);
3371
- $internallyMarkSiblingsAsDirty(writableReplaceWith);
3468
+ internalMarkSiblingsAsDirty(writableReplaceWith);
3372
3469
  const selection = $getSelection();
3373
3470
 
3374
3471
  if (selection !== null) {
@@ -3411,7 +3508,7 @@ class LexicalNode {
3411
3508
  }
3412
3509
  }
3413
3510
 
3414
- $internallyMarkSiblingsAsDirty(writableNodeToInsert);
3511
+ internalMarkSiblingsAsDirty(writableNodeToInsert);
3415
3512
 
3416
3513
  if (selection !== null) {
3417
3514
  const oldParentKey = oldParent.getKey();
@@ -3438,7 +3535,7 @@ class LexicalNode {
3438
3535
  }
3439
3536
 
3440
3537
  children.splice(index + 1, 0, insertKey);
3441
- $internallyMarkSiblingsAsDirty(writableNodeToInsert);
3538
+ internalMarkSiblingsAsDirty(writableNodeToInsert);
3442
3539
 
3443
3540
  if (selection !== null) {
3444
3541
  $updateElementSelectionOnCreateDeleteNode(selection, writableParent, index + 1);
@@ -3473,7 +3570,7 @@ class LexicalNode {
3473
3570
  }
3474
3571
  }
3475
3572
 
3476
- $internallyMarkSiblingsAsDirty(writableNodeToInsert);
3573
+ internalMarkSiblingsAsDirty(writableNodeToInsert);
3477
3574
  children.splice(index, 1);
3478
3575
  }
3479
3576
 
@@ -3490,7 +3587,7 @@ class LexicalNode {
3490
3587
  }
3491
3588
 
3492
3589
  children.splice(index, 0, insertKey);
3493
- $internallyMarkSiblingsAsDirty(writableNodeToInsert);
3590
+ internalMarkSiblingsAsDirty(writableNodeToInsert);
3494
3591
  const selection = $getSelection();
3495
3592
 
3496
3593
  if (selection !== null) {
@@ -3605,7 +3702,7 @@ class DecoratorEditor {
3605
3702
 
3606
3703
  if (editorState !== null) {
3607
3704
  editor.setEditorState(editorState, {
3608
- tag: 'without-history'
3705
+ tag: 'history-merge'
3609
3706
  });
3610
3707
  }
3611
3708
  }
@@ -3792,6 +3889,14 @@ class DecoratorNode extends LexicalNode {
3792
3889
  }
3793
3890
  }
3794
3891
 
3892
+ isIsolated() {
3893
+ return false;
3894
+ }
3895
+
3896
+ isTopLevel() {
3897
+ return false;
3898
+ }
3899
+
3795
3900
  }
3796
3901
  function $isDecoratorNode(node) {
3797
3902
  return node instanceof DecoratorNode;
@@ -3960,6 +4065,7 @@ let subTreeDirectionedTextContent = '';
3960
4065
  let editorTextContent = '';
3961
4066
  let activeEditorConfig;
3962
4067
  let activeEditor$1;
4068
+ let activeEditorNodes;
3963
4069
  let treatAllNodesAsDirty = false;
3964
4070
  let activeEditorStateReadOnly = false;
3965
4071
  let activeTextDirection = null;
@@ -3968,6 +4074,7 @@ let activeDirtyLeaves;
3968
4074
  let activePrevNodeMap;
3969
4075
  let activeNextNodeMap;
3970
4076
  let activePrevKeyToDOMMap;
4077
+ let mutatedNodes;
3971
4078
 
3972
4079
  function destroyNode(key, parentDOM) {
3973
4080
  const node = activePrevNodeMap.get(key);
@@ -3987,6 +4094,10 @@ function destroyNode(key, parentDOM) {
3987
4094
  const children = node.__children;
3988
4095
  destroyChildren(children, 0, children.length - 1, null);
3989
4096
  }
4097
+
4098
+ if (node !== undefined) {
4099
+ setMutatedNode(mutatedNodes, activeEditorNodes, node, 'destroyed');
4100
+ }
3990
4101
  }
3991
4102
 
3992
4103
  function destroyChildren(children, _startIndex, endIndex, dom) {
@@ -4123,6 +4234,7 @@ function createNode(key, parentDOM, insertDOM) {
4123
4234
  Object.freeze(node);
4124
4235
  }
4125
4236
 
4237
+ setMutatedNode(mutatedNodes, activeEditorNodes, node, 'created');
4126
4238
  return dom;
4127
4239
  }
4128
4240
 
@@ -4512,12 +4624,15 @@ function reconcileRoot(prevEditorState, nextEditorState, editor, selection, dirt
4512
4624
  activeTextDirection = null;
4513
4625
  activeEditor$1 = editor;
4514
4626
  activeEditorConfig = editor._config;
4627
+ activeEditorNodes = editor._nodes;
4515
4628
  activeDirtyElements = dirtyElements;
4516
4629
  activeDirtyLeaves = dirtyLeaves;
4517
4630
  activePrevNodeMap = prevEditorState._nodeMap;
4518
4631
  activeNextNodeMap = nextEditorState._nodeMap;
4519
4632
  activeEditorStateReadOnly = nextEditorState._readOnly;
4520
4633
  activePrevKeyToDOMMap = new Map(editor._keyToDOMMap);
4634
+ const currentMutatedNodes = new Map();
4635
+ mutatedNodes = currentMutatedNodes;
4521
4636
  reconcileNode('root', null); // We don't want a bunch of void checks throughout the scope
4522
4637
  // so instead we make it seem that these values are always set.
4523
4638
  // We also want to make sure we clear them down, otherwise we
@@ -4526,6 +4641,8 @@ function reconcileRoot(prevEditorState, nextEditorState, editor, selection, dirt
4526
4641
 
4527
4642
  activeEditor$1 = undefined; // $FlowFixMe
4528
4643
 
4644
+ activeEditorNodes = undefined; // $FlowFixMe
4645
+
4529
4646
  activeDirtyElements = undefined; // $FlowFixMe
4530
4647
 
4531
4648
  activeDirtyLeaves = undefined; // $FlowFixMe
@@ -4536,11 +4653,15 @@ function reconcileRoot(prevEditorState, nextEditorState, editor, selection, dirt
4536
4653
 
4537
4654
  activeEditorConfig = undefined; // $FlowFixMe
4538
4655
 
4539
- activePrevKeyToDOMMap = undefined;
4656
+ activePrevKeyToDOMMap = undefined; // $FlowFixMe
4657
+
4658
+ mutatedNodes = undefined;
4659
+ return currentMutatedNodes;
4540
4660
  }
4541
4661
 
4542
4662
  function updateEditorState(rootElement, currentEditorState, pendingEditorState, currentSelection, pendingSelection, needsUpdate, editor) {
4543
4663
  const observer = editor._observer;
4664
+ let reconcileMutatedNodes = null;
4544
4665
 
4545
4666
  if (needsUpdate && observer !== null) {
4546
4667
  const dirtyType = editor._dirtyType;
@@ -4549,7 +4670,7 @@ function updateEditorState(rootElement, currentEditorState, pendingEditorState,
4549
4670
  observer.disconnect();
4550
4671
 
4551
4672
  try {
4552
- reconcileRoot(currentEditorState, pendingEditorState, editor, pendingSelection, dirtyType, dirtyElements, dirtyLeaves);
4673
+ reconcileMutatedNodes = reconcileRoot(currentEditorState, pendingEditorState, editor, pendingSelection, dirtyType, dirtyElements, dirtyLeaves);
4553
4674
  } finally {
4554
4675
  observer.observe(rootElement, {
4555
4676
  characterData: true,
@@ -4564,6 +4685,8 @@ function updateEditorState(rootElement, currentEditorState, pendingEditorState,
4564
4685
  if (domSelection !== null && (needsUpdate || pendingSelection === null || pendingSelection.dirty)) {
4565
4686
  reconcileSelection(currentSelection, pendingSelection, editor, domSelection);
4566
4687
  }
4688
+
4689
+ return reconcileMutatedNodes;
4567
4690
  }
4568
4691
 
4569
4692
  function scrollIntoViewIfNeeded(node, rootElement) {
@@ -4979,19 +5102,20 @@ function commitPendingUpdates(editor) {
4979
5102
  editor._updating = true;
4980
5103
 
4981
5104
  try {
4982
- updateEditorState(rootElement, currentEditorState, pendingEditorState, currentSelection, pendingSelection, needsUpdate, editor);
5105
+ const mutatedNodes = updateEditorState(rootElement, currentEditorState, pendingEditorState, currentSelection, pendingSelection, needsUpdate, editor);
5106
+
5107
+ if (mutatedNodes !== null) {
5108
+ triggerMutationListeners(editor, currentEditorState, pendingEditorState, mutatedNodes);
5109
+ }
4983
5110
  } catch (error) {
4984
5111
  // Report errors
4985
- triggerListeners('error', editor, false, error, error._log); // Reset editor and restore incoming editor state to the DOM
5112
+ triggerListeners('error', editor, false, error); // Reset editor and restore incoming editor state to the DOM
4986
5113
 
4987
5114
  if (!isAttemptingToRecoverFromReconcilerError) {
4988
5115
  resetEditor(editor, null, rootElement, pendingEditorState);
4989
5116
  initMutationObserver(editor);
4990
5117
  editor._dirtyType = FULL_RECONCILE;
4991
5118
  isAttemptingToRecoverFromReconcilerError = true;
4992
-
4993
- editor._log.push('ReconcileRecover');
4994
-
4995
5119
  commitPendingUpdates(editor);
4996
5120
  isAttemptingToRecoverFromReconcilerError = false;
4997
5121
  }
@@ -5014,8 +5138,6 @@ function commitPendingUpdates(editor) {
5014
5138
  const dirtyElements = editor._dirtyElements;
5015
5139
  const normalizedNodes = editor._normalizedNodes;
5016
5140
  const tags = editor._updateTags;
5017
- const log = editor._log;
5018
- editor._log = [];
5019
5141
 
5020
5142
  if (needsUpdate) {
5021
5143
  editor._dirtyType = NO_DIRTY_NODES;
@@ -5042,7 +5164,6 @@ function commitPendingUpdates(editor) {
5042
5164
  dirtyElements,
5043
5165
  dirtyLeaves,
5044
5166
  editorState: pendingEditorState,
5045
- log,
5046
5167
  normalizedNodes,
5047
5168
  prevEditorState: currentEditorState,
5048
5169
  tags
@@ -5060,6 +5181,19 @@ function triggerTextContentListeners(editor, currentEditorState, pendingEditorSt
5060
5181
  }
5061
5182
  }
5062
5183
 
5184
+ function triggerMutationListeners(editor, currentEditorState, pendingEditorState, mutatedNodes) {
5185
+ const listeners = editor._listeners.mutation;
5186
+ listeners.forEach((klass, listener) => {
5187
+ const mutatedNodesByType = mutatedNodes.get(klass);
5188
+
5189
+ if (mutatedNodesByType === undefined) {
5190
+ return;
5191
+ }
5192
+
5193
+ listener(mutatedNodesByType);
5194
+ });
5195
+ }
5196
+
5063
5197
  function triggerListeners(type, editor, isCurrentlyEnqueuingUpdates, // $FlowFixMe: needs refining
5064
5198
  ...payload) {
5065
5199
  const previouslyUpdating = editor._updating;
@@ -5108,7 +5242,7 @@ function triggerEnqueuedUpdates(editor) {
5108
5242
 
5109
5243
  if (queuedUpdates.length !== 0) {
5110
5244
  const [updateFn, options] = queuedUpdates.shift();
5111
- beginUpdate(editor, updateFn, false, options);
5245
+ beginUpdate(editor, updateFn, options);
5112
5246
  }
5113
5247
  }
5114
5248
 
@@ -5164,7 +5298,7 @@ function processNestedUpdates(editor) {
5164
5298
  return skipTransforms;
5165
5299
  }
5166
5300
 
5167
- function beginUpdate(editor, updateFn, skipEmptyCheck, options) {
5301
+ function beginUpdate(editor, updateFn, options) {
5168
5302
  const updateTags = editor._updateTags;
5169
5303
  let onUpdate;
5170
5304
  let tag;
@@ -5214,12 +5348,6 @@ function beginUpdate(editor, updateFn, skipEmptyCheck, options) {
5214
5348
  applySelectionTransforms(pendingEditorState, editor);
5215
5349
 
5216
5350
  if (editor._dirtyType !== NO_DIRTY_NODES) {
5217
- if (!skipEmptyCheck && pendingEditorState.isEmpty()) {
5218
- {
5219
- throw Error(`updateEditor: the pending editor state is empty. Ensure the root not never becomes empty from an update.`);
5220
- }
5221
- }
5222
-
5223
5351
  if (skipTransforms) {
5224
5352
  $normalizeAllDirtyTextNodes(pendingEditorState, editor);
5225
5353
  } else {
@@ -5251,7 +5379,7 @@ function beginUpdate(editor, updateFn, skipEmptyCheck, options) {
5251
5379
  }
5252
5380
  } catch (error) {
5253
5381
  // Report errors
5254
- triggerListeners('error', editor, false, error, editor._log); // Restore existing editor state to the DOM
5382
+ triggerListeners('error', editor, false, error); // Restore existing editor state to the DOM
5255
5383
 
5256
5384
  editor._pendingEditorState = currentEditorState;
5257
5385
  editor._dirtyType = FULL_RECONCILE;
@@ -5262,8 +5390,6 @@ function beginUpdate(editor, updateFn, skipEmptyCheck, options) {
5262
5390
 
5263
5391
  editor._dirtyElements.clear();
5264
5392
 
5265
- editor._log.push('UpdateRecover');
5266
-
5267
5393
  commitPendingUpdates(editor);
5268
5394
  return;
5269
5395
  } finally {
@@ -5296,11 +5422,11 @@ function beginUpdate(editor, updateFn, skipEmptyCheck, options) {
5296
5422
  }
5297
5423
  }
5298
5424
 
5299
- function updateEditor(editor, updateFn, skipEmptyCheck, options) {
5425
+ function updateEditor(editor, updateFn, options) {
5300
5426
  if (editor._updating) {
5301
5427
  editor._updates.push([updateFn, options]);
5302
5428
  } else {
5303
- beginUpdate(editor, updateFn, skipEmptyCheck, options);
5429
+ beginUpdate(editor, updateFn, options);
5304
5430
  }
5305
5431
  }
5306
5432
 
@@ -5592,7 +5718,7 @@ class ElementNode extends LexicalNode {
5592
5718
  const lastChild = this.getLastChild();
5593
5719
 
5594
5720
  if (lastChild !== null) {
5595
- $internallyMarkNodeAsDirty(lastChild);
5721
+ internalMarkNodeAsDirty(lastChild);
5596
5722
  }
5597
5723
 
5598
5724
  for (let i = 0; i < nodesToAppendLength; i++) {
@@ -5683,7 +5809,7 @@ class ElementNode extends LexicalNode {
5683
5809
  }
5684
5810
  }
5685
5811
 
5686
- $internallyMarkSiblingsAsDirty(nodeToInsert);
5812
+ internalMarkSiblingsAsDirty(nodeToInsert);
5687
5813
  children.splice(index, 1);
5688
5814
  } // Set child parent to self
5689
5815
 
@@ -5697,13 +5823,13 @@ class ElementNode extends LexicalNode {
5697
5823
  const nodeBeforeRange = this.getChildAtIndex(start - 1);
5698
5824
 
5699
5825
  if (nodeBeforeRange) {
5700
- $internallyMarkNodeAsDirty(nodeBeforeRange);
5826
+ internalMarkNodeAsDirty(nodeBeforeRange);
5701
5827
  }
5702
5828
 
5703
5829
  const nodeAfterRange = this.getChildAtIndex(start + deleteCount);
5704
5830
 
5705
5831
  if (nodeAfterRange) {
5706
- $internallyMarkNodeAsDirty(nodeAfterRange);
5832
+ internalMarkNodeAsDirty(nodeAfterRange);
5707
5833
  } // Remove defined range of children
5708
5834
 
5709
5835
 
@@ -5815,10 +5941,6 @@ class ElementNode extends LexicalNode {
5815
5941
  return false;
5816
5942
  }
5817
5943
 
5818
- canSelectionRemove() {
5819
- return true;
5820
- }
5821
-
5822
5944
  canMergeWith(node) {
5823
5945
  return false;
5824
5946
  }
@@ -5850,6 +5972,12 @@ class RootNode extends ElementNode {
5850
5972
  this.__cachedText = null;
5851
5973
  }
5852
5974
 
5975
+ getTopLevelElementOrThrow() {
5976
+ {
5977
+ throw Error(`getTopLevelElementOrThrow: root nodes are not top level elements`);
5978
+ }
5979
+ }
5980
+
5853
5981
  getTextContent(includeInert, includeDirectionless) {
5854
5982
  const cachedText = this.__cachedText;
5855
5983
 
@@ -5862,22 +5990,13 @@ class RootNode extends ElementNode {
5862
5990
  return super.getTextContent(includeInert, includeDirectionless);
5863
5991
  }
5864
5992
 
5865
- select() {
5866
- // You can't select root nodes.
5867
- {
5868
- throw Error(`select: cannot be called on root nodes`);
5869
- }
5870
- }
5871
-
5872
5993
  remove() {
5873
- // You can't select root nodes.
5874
5994
  {
5875
5995
  throw Error(`remove: cannot be called on root nodes`);
5876
5996
  }
5877
5997
  }
5878
5998
 
5879
5999
  replace(node) {
5880
- // You can't select root nodes.
5881
6000
  {
5882
6001
  throw Error(`replace: cannot be called on root nodes`);
5883
6002
  }
@@ -5915,10 +6034,6 @@ class RootNode extends ElementNode {
5915
6034
  return super.append(...nodesToAppend);
5916
6035
  }
5917
6036
 
5918
- canBeEmpty() {
5919
- return false;
5920
- }
5921
-
5922
6037
  }
5923
6038
  function $createRootNode() {
5924
6039
  return new RootNode();
@@ -6023,27 +6138,14 @@ if (CAN_USE_BEFORE_INPUT) {
6023
6138
  }
6024
6139
 
6025
6140
  let lastKeyWasMaybeAndroidSoftKey = false;
6141
+ let rootElementsRegistered = 0;
6026
6142
 
6027
- function onSelectionChange(editor) {
6028
- const domSelection = window.getSelection();
6029
- const parentEditors = getEditorsToPropagate(editor);
6030
- const topLevelEditor = parentEditors[parentEditors.length - 1];
6031
- const topLevelEditorElement = topLevelEditor.getRootElement(); // This is a hot-path, so let's avoid doing an update when
6032
- // the anchorNode is not actually inside the editor (or its parent).
6033
-
6034
- if (topLevelEditorElement && !topLevelEditorElement.contains(domSelection.anchorNode)) {
6035
- return;
6036
- } // This update functions as a way of reconciling a bad selection
6037
- // to a good selection.
6038
-
6039
-
6143
+ function onSelectionChange(editor, isActive) {
6040
6144
  editor.update(() => {
6041
- $pushLogEntry('onSelectionChange'); // Non-active editor don't need any extra logic for selection, it only needs update
6145
+ // Non-active editor don't need any extra logic for selection, it only needs update
6042
6146
  // to reconcile selection (set it to null) to ensure that only one editor has non-null selection.
6043
-
6044
- const isActiveEditor = isSelectionWithinEditor(editor, domSelection.anchorNode, domSelection.focusNode);
6045
-
6046
- if (!isActiveEditor) {
6147
+ if (!isActive) {
6148
+ $setSelection(null);
6047
6149
  return;
6048
6150
  }
6049
6151
 
@@ -6071,7 +6173,6 @@ function onSelectionChange(editor) {
6071
6173
 
6072
6174
  function onClick(event, editor) {
6073
6175
  editor.update(() => {
6074
- $pushLogEntry('onClick');
6075
6176
  const selection = $getSelection();
6076
6177
 
6077
6178
  if (selection === null) {
@@ -6113,7 +6214,6 @@ function onBeforeInput(event, editor) {
6113
6214
  }
6114
6215
 
6115
6216
  editor.update(() => {
6116
- $pushLogEntry('onBeforeInputForRichText');
6117
6217
  const selection = $getSelection();
6118
6218
 
6119
6219
  if (selection === null) {
@@ -6130,7 +6230,7 @@ function onBeforeInput(event, editor) {
6130
6230
 
6131
6231
  const data = event.data;
6132
6232
 
6133
- if (!selection.dirty && selection.isCollapsed()) {
6233
+ if (!selection.dirty && selection.isCollapsed() && !$isRootNode(selection.anchor.getNode())) {
6134
6234
  $applyTargetRange(selection, event);
6135
6235
  }
6136
6236
 
@@ -6297,7 +6397,6 @@ function onInput(event, editor) {
6297
6397
  // We don't want the onInput to bubble, in the case of nested editors.
6298
6398
  event.stopPropagation();
6299
6399
  editor.update(() => {
6300
- $pushLogEntry('onInput');
6301
6400
  const selection = $getSelection();
6302
6401
  const data = event.data;
6303
6402
 
@@ -6315,7 +6414,6 @@ function onInput(event, editor) {
6315
6414
 
6316
6415
  function onCompositionStart(event, editor) {
6317
6416
  editor.update(() => {
6318
- $pushLogEntry('onCompositionStart');
6319
6417
  const selection = $getSelection();
6320
6418
 
6321
6419
  if (selection !== null && !editor.isComposing()) {
@@ -6335,7 +6433,6 @@ function onCompositionStart(event, editor) {
6335
6433
 
6336
6434
  function onCompositionEndInternal(event, editor) {
6337
6435
  editor.update(() => {
6338
- $pushLogEntry('onCompositionEnd');
6339
6436
  $setCompositionKey(null);
6340
6437
  $updateSelectedTextFromDOM(editor, true);
6341
6438
  });
@@ -6443,12 +6540,12 @@ function isRootEditable(editor) {
6443
6540
 
6444
6541
  function getRootElementRemoveHandles(rootElement) {
6445
6542
  // $FlowFixMe: internal field
6446
- let eventHandles = rootElement._lexicalEventHandles;
6543
+ let eventHandles = rootElement.__lexicalEventHandles;
6447
6544
 
6448
6545
  if (eventHandles === undefined) {
6449
6546
  eventHandles = []; // $FlowFixMe: internal field
6450
6547
 
6451
- rootElement._lexicalEventHandles = eventHandles;
6548
+ rootElement.__lexicalEventHandles = eventHandles;
6452
6549
  }
6453
6550
 
6454
6551
  return eventHandles;
@@ -6457,7 +6554,11 @@ function getRootElementRemoveHandles(rootElement) {
6457
6554
  function clearRootElementRemoveHandles(rootElement) {
6458
6555
  // $FlowFixMe: internal field
6459
6556
  rootElement._lexicalEventHandles = [];
6460
- }
6557
+ } // Mapping root editors to their active nested editors, contains nested editors
6558
+ // mapping only, so if root editor is selected map will have no reference to free up memory
6559
+
6560
+
6561
+ const activeNestedEditorsMap = new Map();
6461
6562
 
6462
6563
  function onDocumentSelectionChange(event) {
6463
6564
  const sel = window.getSelection();
@@ -6465,10 +6566,31 @@ function onDocumentSelectionChange(event) {
6465
6566
 
6466
6567
  while (node != null) {
6467
6568
  if (node.contentEditable === 'true') {
6468
- const possibleLexicalEditor = node.__lexicalEditor;
6569
+ const nextActiveEditor = node.__lexicalEditor;
6570
+
6571
+ if (nextActiveEditor !== undefined) {
6572
+ // When editor receives selection change event, we're checking if
6573
+ // it has any sibling editors (within same parent editor) that were active
6574
+ // before, and trigger selection change on it to nullify selection.
6575
+ const editors = getEditorsToPropagate(nextActiveEditor);
6576
+ const rootEditor = editors[editors.length - 1];
6577
+ const rootEditorKey = rootEditor._key;
6578
+ const activeNestedEditor = activeNestedEditorsMap.get(rootEditorKey);
6579
+ const prevActiveEditor = activeNestedEditor || rootEditor;
6580
+
6581
+ if (prevActiveEditor !== nextActiveEditor) {
6582
+ onSelectionChange(prevActiveEditor, false);
6583
+ }
6584
+
6585
+ onSelectionChange(nextActiveEditor, true); // If newly selected editor is nested, then add it to the map, clean map otherwise
6469
6586
 
6470
- if (possibleLexicalEditor !== undefined) {
6471
- onSelectionChange(possibleLexicalEditor);
6587
+ if (nextActiveEditor !== rootEditor) {
6588
+ activeNestedEditorsMap.set(rootEditorKey, nextActiveEditor);
6589
+ } else if (activeNestedEditor) {
6590
+ activeNestedEditorsMap.delete(rootEditorKey);
6591
+ }
6592
+
6593
+ return;
6472
6594
  }
6473
6595
  }
6474
6596
 
@@ -6476,18 +6598,17 @@ function onDocumentSelectionChange(event) {
6476
6598
  }
6477
6599
  }
6478
6600
 
6479
- function addDocumentSelectionChangeEvent(rootElement, editor) {
6480
- const doc = rootElement.ownerDocument; // $FlowFixMe: internal field
6481
-
6482
- rootElement.__lexicalEditor = editor; // $FlowFixMe: internal field
6483
-
6484
- if (doc._lexicalEvent === undefined) {
6485
- // $FlowFixMe: internal field
6486
- doc._lexicalEvent = true;
6601
+ function addRootElementEvents(rootElement, editor) {
6602
+ // We only want to have a single global selectionchange event handler, shared
6603
+ // between all editor instances.
6604
+ if (rootElementsRegistered === 0) {
6605
+ const doc = rootElement.ownerDocument;
6487
6606
  doc.addEventListener('selectionchange', onDocumentSelectionChange);
6488
6607
  }
6489
- }
6490
- function addRootElementEvents(rootElement, editor) {
6608
+
6609
+ rootElementsRegistered++; // $FlowFixMe: internal field
6610
+
6611
+ rootElement.__lexicalEditor = editor;
6491
6612
  const removeHandles = getRootElementRemoveHandles(rootElement);
6492
6613
 
6493
6614
  for (let i = 0; i < rootElementEvents.length; i++) {
@@ -6508,7 +6629,17 @@ function addRootElementEvents(rootElement, editor) {
6508
6629
  }
6509
6630
  }
6510
6631
  function removeRootElementEvents(rootElement) {
6511
- // $FlowFixMe: internal field
6632
+ rootElementsRegistered--; // We only want to have a single global selectionchange event handler, shared
6633
+ // between all editor instances.
6634
+
6635
+ if (rootElementsRegistered === 0) {
6636
+ const doc = rootElement.ownerDocument;
6637
+ doc.removeEventListener('selectionchange', onDocumentSelectionChange);
6638
+ } // $FlowFixMe: internal field
6639
+
6640
+
6641
+ cleanActiveNestedEditorsMap(rootElement.__lexicalEditor); // $FlowFixMe: internal field
6642
+
6512
6643
  rootElement.__lexicalEditor = null;
6513
6644
  const removeHandles = getRootElementRemoveHandles(rootElement);
6514
6645
 
@@ -6519,43 +6650,21 @@ function removeRootElementEvents(rootElement) {
6519
6650
  clearRootElementRemoveHandles(rootElement);
6520
6651
  }
6521
6652
 
6522
- /**
6523
- * Copyright (c) Meta Platforms, Inc. and affiliates.
6524
- *
6525
- * This source code is licensed under the MIT license found in the
6526
- * LICENSE file in the root directory of this source tree.
6527
- *
6528
- *
6529
- */
6530
- class HorizontalRuleNode extends LexicalNode {
6531
- static getType() {
6532
- return 'horizontal-rule';
6533
- }
6534
-
6535
- static clone(node) {
6536
- return new HorizontalRuleNode(node.__key);
6537
- }
6538
-
6539
- constructor(key) {
6540
- super(key);
6541
- }
6542
-
6543
- createDOM() {
6544
- const element = document.createElement('hr');
6545
- element.setAttribute('contenteditable', 'false');
6546
- return element;
6547
- }
6653
+ function cleanActiveNestedEditorsMap(editor) {
6654
+ // $FlowFixMe: internal field
6655
+ if (editor._parentEditor) {
6656
+ // For nested editor cleanup map if this editor was marked as active
6657
+ const editors = getEditorsToPropagate(editor);
6658
+ const rootEditor = editors[editors.length - 1];
6659
+ const rootEditorKey = rootEditor._key;
6548
6660
 
6549
- updateDOM() {
6550
- return false;
6661
+ if (activeNestedEditorsMap.get(rootEditorKey) === editor) {
6662
+ activeNestedEditorsMap.delete(rootEditorKey);
6663
+ }
6664
+ } else {
6665
+ // For top-level editors cleanup map
6666
+ activeNestedEditorsMap.delete(editor._key);
6551
6667
  }
6552
-
6553
- }
6554
- function $createHorizontalRuleNode() {
6555
- return new HorizontalRuleNode();
6556
- }
6557
- function $isHorizontalRuleNode(node) {
6558
- return node instanceof HorizontalRuleNode;
6559
6668
  }
6560
6669
 
6561
6670
  /**
@@ -6685,7 +6794,25 @@ function setTextThemeClassNames(tag, prevFormat, nextFormat, dom, textClassNames
6685
6794
  }
6686
6795
  }
6687
6796
 
6797
+ function diffComposedText(a, b) {
6798
+ const aLength = a.length;
6799
+ const bLength = b.length;
6800
+ let left = 0;
6801
+ let right = 0;
6802
+
6803
+ while (left < aLength && left < bLength && a[left] === b[left]) {
6804
+ left++;
6805
+ }
6806
+
6807
+ while (right + left < aLength && right + left < bLength && a[aLength - right - 1] === b[bLength - right - 1]) {
6808
+ right++;
6809
+ }
6810
+
6811
+ return [left, aLength - left - right, b.slice(left, bLength - right)];
6812
+ }
6813
+
6688
6814
  function setTextContent(nextText, dom, node) {
6815
+ // $FlowFixMe: first node is always text
6689
6816
  const firstChild = dom.firstChild;
6690
6817
  const isComposing = node.isComposing(); // Always add a suffix if we're composing a node
6691
6818
 
@@ -6694,8 +6821,19 @@ function setTextContent(nextText, dom, node) {
6694
6821
 
6695
6822
  if (firstChild == null) {
6696
6823
  dom.textContent = text;
6697
- } else if (firstChild.nodeValue !== text) {
6698
- firstChild.nodeValue = text;
6824
+ } else {
6825
+ const nodeValue = firstChild.nodeValue;
6826
+ if (nodeValue !== text) if (isComposing) {
6827
+ const [index, remove, insert] = diffComposedText(nodeValue, text);
6828
+
6829
+ if (remove !== 0) {
6830
+ firstChild.deleteData(index, remove);
6831
+ }
6832
+
6833
+ firstChild.insertData(index, insert);
6834
+ } else {
6835
+ firstChild.nodeValue = text;
6836
+ }
6699
6837
  }
6700
6838
  }
6701
6839
 
@@ -7097,7 +7235,7 @@ class TextNode extends LexicalNode {
7097
7235
  } // Insert the nodes into the parent's children
7098
7236
 
7099
7237
 
7100
- $internallyMarkSiblingsAsDirty(this);
7238
+ internalMarkSiblingsAsDirty(this);
7101
7239
  const writableParent = parent.getWritable();
7102
7240
  const writableParentChildren = writableParent.__children;
7103
7241
  const insertionIndex = writableParentChildren.indexOf(key);
@@ -7215,21 +7353,25 @@ class ParagraphNode extends ElementNode {
7215
7353
  }
7216
7354
 
7217
7355
  collapseAtStart() {
7218
- const children = this.getChildren();
7219
- const sibling = this.getNextSibling(); // If we have an empty (trimmed) first paragraph and try and remove it,
7356
+ const children = this.getChildren(); // If we have an empty (trimmed) first paragraph and try and remove it,
7220
7357
  // delete the paragraph as long as we have another sibling to go to
7221
7358
 
7222
- if ($isElementNode(sibling) && this.getIndexWithinParent() === 0 && (children.length === 0 || $isTextNode(children[0]) && children[0].getTextContent().trim() === '')) {
7223
- const firstChild = sibling.getFirstChild();
7359
+ if (children.length === 0 || $isTextNode(children[0]) && children[0].getTextContent().trim() === '') {
7360
+ const nextSibling = this.getNextSibling();
7224
7361
 
7225
- if ($isTextNode(firstChild)) {
7226
- firstChild.select(0, 0);
7227
- } else {
7228
- sibling.select(0, 0);
7362
+ if (nextSibling !== null) {
7363
+ this.selectNext();
7364
+ this.remove();
7365
+ return true;
7229
7366
  }
7230
7367
 
7231
- this.remove();
7232
- return true;
7368
+ const prevSibling = this.getPreviousSibling();
7369
+
7370
+ if (prevSibling !== null) {
7371
+ this.selectPrevious();
7372
+ this.remove();
7373
+ return true;
7374
+ }
7233
7375
  }
7234
7376
 
7235
7377
  return false;
@@ -7267,7 +7409,6 @@ function resetEditor(editor, prevRootElement, nextRootElement, pendingEditorStat
7267
7409
 
7268
7410
  editor._normalizedNodes = new Set();
7269
7411
  editor._updateTags = new Set();
7270
- editor._log = [];
7271
7412
  editor._updates = [];
7272
7413
  const observer = editor._observer;
7273
7414
 
@@ -7296,7 +7437,7 @@ function createEditor(editorConfig) {
7296
7437
  const disableEvents = config.disableEvents || false;
7297
7438
  const editorState = createEmptyEditorState();
7298
7439
  const initialEditorState = config.editorState;
7299
- const nodes = [RootNode, TextNode, HorizontalRuleNode, LineBreakNode, ParagraphNode, ...(config.nodes || [])];
7440
+ const nodes = [RootNode, TextNode, LineBreakNode, ParagraphNode, ...(config.nodes || [])];
7300
7441
  const registeredNodes = new Map();
7301
7442
 
7302
7443
  for (let i = 0; i < nodes.length; i++) {
@@ -7353,9 +7494,9 @@ class BaseLexicalEditor {
7353
7494
  command: [new Set(), new Set(), new Set(), new Set(), new Set()],
7354
7495
  decorator: new Set(),
7355
7496
  error: new Set(),
7497
+ mutation: new Map(),
7356
7498
  root: new Set(),
7357
7499
  textcontent: new Set(),
7358
- textmutation: new Set(),
7359
7500
  update: new Set()
7360
7501
  }; // Editor configuration for theme/context.
7361
7502
 
@@ -7373,9 +7514,7 @@ class BaseLexicalEditor {
7373
7514
  this._normalizedNodes = new Set();
7374
7515
  this._updateTags = new Set(); // Handling of DOM mutations
7375
7516
 
7376
- this._observer = null; // Logging for updates
7377
-
7378
- this._log = []; // Used for identifying owning editors
7517
+ this._observer = null; // Used for identifying owning editors
7379
7518
 
7380
7519
  this._key = generateRandomKey();
7381
7520
  }
@@ -7384,27 +7523,50 @@ class BaseLexicalEditor {
7384
7523
  return this._compositionKey != null;
7385
7524
  }
7386
7525
 
7387
- addListener(type, listener, priority) {
7526
+ addListener(type, arg1, arg2) {
7388
7527
  const listenerSetOrMap = this._listeners[type];
7389
7528
 
7390
7529
  if (type === 'command') {
7530
+ // $FlowFixMe: TODO refine
7531
+ const listener = arg1; // $FlowFixMe: TODO refine
7532
+
7533
+ const priority = arg2;
7534
+
7391
7535
  if (priority === undefined) {
7392
7536
  {
7393
7537
  throw Error(`Listener for type "command" requires a "priority".`);
7394
7538
  }
7395
- } // $FlowFixMe: unsure how to csae this
7539
+ } // $FlowFixMe: unsure how to cast this
7396
7540
 
7397
7541
 
7398
7542
  const commands = listenerSetOrMap;
7399
- const commandSet = commands[priority]; // $FlowFixMe: cast
7400
-
7543
+ const commandSet = commands[priority];
7401
7544
  commandSet.add(listener);
7402
7545
  return () => {
7403
- // $FlowFixMe: cast
7404
7546
  commandSet.delete(listener);
7405
7547
  };
7548
+ } else if (type === 'mutation') {
7549
+ // $FlowFixMe: refine
7550
+ const klass = arg1; // $FlowFixMe: refine
7551
+
7552
+ const mutationListener = arg2;
7553
+
7554
+ const registeredNode = this._nodes.get(klass.getType());
7555
+
7556
+ if (registeredNode === undefined) {
7557
+ {
7558
+ throw Error(`Node ${klass.name} has not been registered. Ensure node has been passed to createEditor.`);
7559
+ }
7560
+ }
7561
+
7562
+ const mutations = this._listeners.mutation;
7563
+ mutations.set(mutationListener, klass);
7564
+ return () => {
7565
+ mutations.delete(mutationListener);
7566
+ };
7406
7567
  } else {
7407
- // $FlowFixMe: TODO refine this from the above types
7568
+ const listener = arg1; // $FlowFixMe: TODO refine this from the above types
7569
+
7408
7570
  listenerSetOrMap.add(listener);
7409
7571
  const isRootType = type === 'root';
7410
7572
 
@@ -7497,12 +7659,11 @@ class BaseLexicalEditor {
7497
7659
  this._dirtyType = FULL_RECONCILE;
7498
7660
  initMutationObserver(getSelf(this));
7499
7661
 
7500
- this._updateTags.add('without-history');
7662
+ this._updateTags.add('history-merge');
7501
7663
 
7502
7664
  commitPendingUpdates(getSelf(this)); // TODO: remove this flag once we no longer use UEv2 internally
7503
7665
 
7504
7666
  if (!this._config.disableEvents) {
7505
- addDocumentSelectionChangeEvent(nextRootElement, getSelf(this));
7506
7667
  addRootElementEvents(nextRootElement, getSelf(this));
7507
7668
  }
7508
7669
  }
@@ -7557,7 +7718,7 @@ class BaseLexicalEditor {
7557
7718
  }
7558
7719
 
7559
7720
  update(updateFn, options) {
7560
- updateEditor(getSelf(this), updateFn, false, options);
7721
+ updateEditor(getSelf(this), updateFn, options);
7561
7722
  }
7562
7723
 
7563
7724
  focus(callbackFn) {
@@ -7576,7 +7737,7 @@ class BaseLexicalEditor {
7576
7737
  } else if (root.getChildrenSize() !== 0) {
7577
7738
  root.selectEnd();
7578
7739
  }
7579
- }, true, {
7740
+ }, {
7580
7741
  onUpdate: () => {
7581
7742
  rootElement.removeAttribute('autocapitalize');
7582
7743
 
@@ -7600,13 +7761,11 @@ class BaseLexicalEditor {
7600
7761
  // For some reason, we can't do this via an interface without
7601
7762
  // Flow messing up the types. It's hacky, but it improves DX.
7602
7763
 
7603
- exports.$createHorizontalRuleNode = $createHorizontalRuleNode;
7604
7764
  exports.$createLineBreakNode = $createLineBreakNode;
7605
7765
  exports.$createNodeFromParse = $createNodeFromParse;
7606
7766
  exports.$createParagraphNode = $createParagraphNode;
7607
7767
  exports.$createRangeSelection = $createEmptyRangeSelection;
7608
7768
  exports.$createTextNode = $createTextNode;
7609
- exports.$getCompositionKey = $getCompositionKey;
7610
7769
  exports.$getNearestNodeFromDOMNode = $getNearestNodeFromDOMNode;
7611
7770
  exports.$getNodeByKey = $getNodeByKey;
7612
7771
  exports.$getPreviousSelection = $getPreviousSelection;
@@ -7614,14 +7773,12 @@ exports.$getRoot = $getRoot;
7614
7773
  exports.$getSelection = $getSelection;
7615
7774
  exports.$isDecoratorNode = $isDecoratorNode;
7616
7775
  exports.$isElementNode = $isElementNode;
7617
- exports.$isHorizontalRuleNode = $isHorizontalRuleNode;
7618
7776
  exports.$isLeafNode = $isLeafNode;
7619
7777
  exports.$isLineBreakNode = $isLineBreakNode;
7620
7778
  exports.$isParagraphNode = $isParagraphNode;
7621
7779
  exports.$isRangeSelection = $isRangeSelection;
7622
7780
  exports.$isRootNode = $isRootNode;
7623
7781
  exports.$isTextNode = $isTextNode;
7624
- exports.$log = $pushLogEntry;
7625
7782
  exports.$setCompositionKey = $setCompositionKey;
7626
7783
  exports.$setSelection = $setSelection;
7627
7784
  exports.DecoratorNode = DecoratorNode;