lexical 0.3.8 → 0.3.11

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
@@ -77,14 +77,12 @@ var getDOMSelection = getSelection;
77
77
  *
78
78
  */
79
79
  const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
80
- const documentMode = // @ts-ignore
81
- CAN_USE_DOM && 'documentMode' in document ? document.documentMode : null;
80
+ const documentMode = CAN_USE_DOM && 'documentMode' in document ? document.documentMode : null;
82
81
  const IS_APPLE = CAN_USE_DOM && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
83
82
  const IS_FIREFOX = CAN_USE_DOM && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
84
83
  const CAN_USE_BEFORE_INPUT = CAN_USE_DOM && 'InputEvent' in window && !documentMode ? 'getTargetRanges' in new window.InputEvent('input') : false;
85
84
  const IS_SAFARI = CAN_USE_DOM && /Version\/[\d.]+.*Safari/.test(navigator.userAgent);
86
- const IS_IOS = CAN_USE_DOM && /iPad|iPhone|iPod/.test(navigator.userAgent) && // @ts-ignore
87
- !window.MSStream; // Keep these in case we need to use them in the future.
85
+ 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.
88
86
  // export const IS_WINDOWS: boolean = CAN_USE_DOM && /Win/.test(navigator.platform);
89
87
  // export const IS_CHROME: boolean = CAN_USE_DOM && /^(?=.*Chrome).*/i.test(navigator.userAgent);
90
88
  // export const canUseTextInputEvent: boolean = CAN_USE_DOM && 'TextEvent' in window && !documentMode;
@@ -496,12 +494,16 @@ function $isTokenOrInertOrSegmented(node) {
496
494
  function $isTokenOrInert(node) {
497
495
  return node.isToken() || node.isInert();
498
496
  }
497
+
498
+ function isDOMNodeLexicalTextNode(node) {
499
+ return node.nodeType === DOM_TEXT_TYPE;
500
+ }
501
+
499
502
  function getDOMTextNode(element) {
500
503
  let node = element;
501
504
 
502
505
  while (node != null) {
503
- if (node.nodeType === DOM_TEXT_TYPE) {
504
- // @ts-expect-error: this is a Text
506
+ if (isDOMNodeLexicalTextNode(node)) {
505
507
  return node;
506
508
  }
507
509
 
@@ -812,7 +814,7 @@ function getEditorsToPropagate(editor) {
812
814
  function createUID() {
813
815
  return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);
814
816
  }
815
- function $updateSelectedTextFromDOM(editor, isCompositionEnd, data) {
817
+ function $updateSelectedTextFromDOM(isCompositionEnd, data) {
816
818
  // Update the text content with the latest composition text
817
819
  const domSelection = getDOMSelection();
818
820
 
@@ -883,7 +885,7 @@ function $updateTextNodeFromDOMContent(textNode, textContent, anchorOffset, focu
883
885
  const prevSelection = $getPreviousSelection();
884
886
 
885
887
  if ($isTokenOrInert(node) || $getCompositionKey() !== null && !isComposing || // Check if character was added at the start, and we need
886
- // to clear this input from occuring as that action wasn't
888
+ // to clear this input from occurring as that action wasn't
887
889
  // permitted.
888
890
  parent !== null && $isRangeSelection(prevSelection) && !parent.canInsertTextBefore() && prevSelection.anchor.offset === 0) {
889
891
  node.markDirty();
@@ -1073,22 +1075,22 @@ function isArrowDown(keyCode) {
1073
1075
  return keyCode === 40;
1074
1076
  }
1075
1077
 
1076
- function isMoveBackward(keyCode, ctrlKey, shiftKey, altKey, metaKey) {
1078
+ function isMoveBackward(keyCode, ctrlKey, altKey, metaKey) {
1077
1079
  return isArrowLeft(keyCode) && !ctrlKey && !metaKey && !altKey;
1078
1080
  }
1079
1081
  function isMoveToStart(keyCode, ctrlKey, shiftKey, altKey, metaKey) {
1080
1082
  return isArrowLeft(keyCode) && !altKey && !shiftKey && (ctrlKey || metaKey);
1081
1083
  }
1082
- function isMoveForward(keyCode, ctrlKey, shiftKey, altKey, metaKey) {
1084
+ function isMoveForward(keyCode, ctrlKey, altKey, metaKey) {
1083
1085
  return isArrowRight(keyCode) && !ctrlKey && !metaKey && !altKey;
1084
1086
  }
1085
1087
  function isMoveToEnd(keyCode, ctrlKey, shiftKey, altKey, metaKey) {
1086
1088
  return isArrowRight(keyCode) && !altKey && !shiftKey && (ctrlKey || metaKey);
1087
1089
  }
1088
- function isMoveUp(keyCode, ctrlKey, shiftKey, altKey, metaKey) {
1090
+ function isMoveUp(keyCode, ctrlKey, metaKey) {
1089
1091
  return isArrowUp(keyCode) && !ctrlKey && !metaKey;
1090
1092
  }
1091
- function isMoveDown(keyCode, ctrlKey, shiftKey, altKey, metaKey) {
1093
+ function isMoveDown(keyCode, ctrlKey, metaKey) {
1092
1094
  return isArrowDown(keyCode) && !ctrlKey && !metaKey;
1093
1095
  }
1094
1096
  function isModifier(ctrlKey, shiftKey, altKey, metaKey) {
@@ -1230,7 +1232,7 @@ function getElementByKeyOrThrow(editor, key) {
1230
1232
 
1231
1233
  if (element === undefined) {
1232
1234
  {
1233
- throw Error(`Reconciliation: could not find DOM element for node key "${key}"`);
1235
+ throw Error(`Reconciliation: could not find DOM element for node key ${key}`);
1234
1236
  }
1235
1237
  }
1236
1238
 
@@ -1270,6 +1272,50 @@ function scrollIntoViewIfNeeded(editor, anchor, rootElement, tags) {
1270
1272
  tags.add('scroll-into-view');
1271
1273
  }
1272
1274
  }
1275
+ function $maybeMoveChildrenSelectionToParent(parentNode, offset = 0) {
1276
+ if (offset !== 0) {
1277
+ {
1278
+ throw Error(`TODO`);
1279
+ }
1280
+ }
1281
+
1282
+ const selection = $getSelection();
1283
+
1284
+ if (!$isRangeSelection(selection) || !$isElementNode(parentNode)) {
1285
+ return selection;
1286
+ }
1287
+
1288
+ const {
1289
+ anchor,
1290
+ focus
1291
+ } = selection;
1292
+ const anchorNode = anchor.getNode();
1293
+ const focusNode = focus.getNode();
1294
+
1295
+ if ($hasAncestor(anchorNode, parentNode)) {
1296
+ anchor.set(parentNode.__key, 0, 'element');
1297
+ }
1298
+
1299
+ if ($hasAncestor(focusNode, parentNode)) {
1300
+ focus.set(parentNode.__key, 0, 'element');
1301
+ }
1302
+
1303
+ return selection;
1304
+ }
1305
+
1306
+ function $hasAncestor(child, targetNode) {
1307
+ let parent = child.getParent();
1308
+
1309
+ while (parent !== null) {
1310
+ if (parent.is(targetNode)) {
1311
+ return true;
1312
+ }
1313
+
1314
+ parent = parent.getParent();
1315
+ }
1316
+
1317
+ return false;
1318
+ }
1273
1319
 
1274
1320
  /**
1275
1321
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -1635,7 +1681,7 @@ function isLastChildLineBreakOrDecorator(children, nodeMap) {
1635
1681
  const childKey = children[children.length - 1];
1636
1682
  const node = nodeMap.get(childKey);
1637
1683
  return $isLineBreakNode(node) || $isDecoratorNode(node);
1638
- } // If we end an element with a LinkBreakNode, then we need to add an additonal <br>
1684
+ } // If we end an element with a LinkBreakNode, then we need to add an additional <br>
1639
1685
 
1640
1686
 
1641
1687
  function reconcileElementTerminatingLineBreak(prevChildren, nextChildren, dom) {
@@ -2057,7 +2103,7 @@ function getPrevElementByKeyOrThrow(key) {
2057
2103
 
2058
2104
  if (element === undefined) {
2059
2105
  {
2060
- throw Error(`Reconciliation: could not find DOM element for node key "${key}"`);
2106
+ throw Error(`Reconciliation: could not find DOM element for node key ${key}`);
2061
2107
  }
2062
2108
  }
2063
2109
 
@@ -2156,6 +2202,7 @@ function onSelectionChange(domSelection, editor, isActive) {
2156
2202
  }
2157
2203
  } else {
2158
2204
  let combinedFormat = IS_ALL_FORMATTING;
2205
+ let hasTextNodes = false;
2159
2206
  const nodes = selection.getNodes();
2160
2207
  const nodesLength = nodes.length;
2161
2208
 
@@ -2163,6 +2210,7 @@ function onSelectionChange(domSelection, editor, isActive) {
2163
2210
  const node = nodes[i];
2164
2211
 
2165
2212
  if ($isTextNode(node)) {
2213
+ hasTextNodes = true;
2166
2214
  combinedFormat &= node.getFormat();
2167
2215
 
2168
2216
  if (combinedFormat === 0) {
@@ -2171,7 +2219,7 @@ function onSelectionChange(domSelection, editor, isActive) {
2171
2219
  }
2172
2220
  }
2173
2221
 
2174
- selection.format = combinedFormat;
2222
+ selection.format = hasTextNodes ? combinedFormat : 0;
2175
2223
  }
2176
2224
  }
2177
2225
 
@@ -2198,7 +2246,7 @@ function onClick(event, editor) {
2198
2246
  domSelection.removeAllRanges();
2199
2247
  selection.dirty = true;
2200
2248
  }
2201
- } else if (domSelection && $isNodeSelection(selection) && domSelection.isCollapsed) {
2249
+ } else if (domSelection && $isNodeSelection(selection)) {
2202
2250
  const domAnchor = domSelection.anchorNode; // If the user is attempting to click selection back onto text, then
2203
2251
  // we should attempt create a range selection.
2204
2252
  // When we click on an empty paragraph node or the end of a paragraph that ends
@@ -2304,7 +2352,7 @@ function onBeforeInput(event, editor) {
2304
2352
  const anchorNode = anchor.getNode();
2305
2353
  const focusNode = focus.getNode();
2306
2354
 
2307
- if (inputType === 'insertText') {
2355
+ if (inputType === 'insertText' || inputType === 'insertTranspose') {
2308
2356
  if (data === '\n') {
2309
2357
  event.preventDefault();
2310
2358
  dispatchCommand(editor, INSERT_LINE_BREAK_COMMAND, false);
@@ -2496,7 +2544,7 @@ function onInput(event, editor) {
2496
2544
  $setCompositionKey(null);
2497
2545
  }
2498
2546
  } else {
2499
- $updateSelectedTextFromDOM(editor, false); // onInput always fires after onCompositionEnd for FF.
2547
+ $updateSelectedTextFromDOM(false); // onInput always fires after onCompositionEnd for FF.
2500
2548
 
2501
2549
  if (isFirefoxEndingComposition) {
2502
2550
  onCompositionEndImpl(editor, data || undefined);
@@ -2568,7 +2616,7 @@ function onCompositionEndImpl(editor, data) {
2568
2616
  }
2569
2617
  }
2570
2618
 
2571
- $updateSelectedTextFromDOM(editor, true, data);
2619
+ $updateSelectedTextFromDOM(true, data);
2572
2620
  }
2573
2621
 
2574
2622
  function onCompositionEnd(event, editor) {
@@ -2602,17 +2650,17 @@ function onKeyDown(event, editor) {
2602
2650
  altKey
2603
2651
  } = event;
2604
2652
 
2605
- if (isMoveForward(keyCode, ctrlKey, shiftKey, altKey, metaKey)) {
2653
+ if (isMoveForward(keyCode, ctrlKey, altKey, metaKey)) {
2606
2654
  dispatchCommand(editor, KEY_ARROW_RIGHT_COMMAND, event);
2607
2655
  } else if (isMoveToEnd(keyCode, ctrlKey, shiftKey, altKey, metaKey)) {
2608
2656
  dispatchCommand(editor, MOVE_TO_END, event);
2609
- } else if (isMoveBackward(keyCode, ctrlKey, shiftKey, altKey, metaKey)) {
2657
+ } else if (isMoveBackward(keyCode, ctrlKey, altKey, metaKey)) {
2610
2658
  dispatchCommand(editor, KEY_ARROW_LEFT_COMMAND, event);
2611
2659
  } else if (isMoveToStart(keyCode, ctrlKey, shiftKey, altKey, metaKey)) {
2612
2660
  dispatchCommand(editor, MOVE_TO_START, event);
2613
- } else if (isMoveUp(keyCode, ctrlKey, shiftKey, altKey, metaKey)) {
2661
+ } else if (isMoveUp(keyCode, ctrlKey, metaKey)) {
2614
2662
  dispatchCommand(editor, KEY_ARROW_UP_COMMAND, event);
2615
- } else if (isMoveDown(keyCode, ctrlKey, shiftKey, altKey, metaKey)) {
2663
+ } else if (isMoveDown(keyCode, ctrlKey, metaKey)) {
2616
2664
  dispatchCommand(editor, KEY_ARROW_DOWN_COMMAND, event);
2617
2665
  } else if (isLineBreak(keyCode, shiftKey)) {
2618
2666
  isInsertLineBreak = true;
@@ -3733,7 +3781,13 @@ class RangeSelection {
3733
3781
  }
3734
3782
 
3735
3783
  formatText(formatType) {
3736
- // TODO I wonder if this methods use selection.extract() instead?
3784
+ if (this.isCollapsed()) {
3785
+ this.toggleFormat(formatType); // When changing format, we should stop composition
3786
+
3787
+ $setCompositionKey(null);
3788
+ return;
3789
+ }
3790
+
3737
3791
  const selectedNodes = this.getNodes();
3738
3792
  const selectedTextNodes = [];
3739
3793
 
@@ -3744,12 +3798,8 @@ class RangeSelection {
3744
3798
  }
3745
3799
 
3746
3800
  const selectedTextNodesLength = selectedTextNodes.length;
3747
- let firstIndex = 0;
3748
- const lastIndex = selectedTextNodesLength - 1;
3749
- let firstNode = selectedTextNodes[0];
3750
- let lastNode = selectedTextNodes[lastIndex];
3751
3801
 
3752
- if (this.isCollapsed()) {
3802
+ if (selectedTextNodesLength === 0) {
3753
3803
  this.toggleFormat(formatType); // When changing format, we should stop composition
3754
3804
 
3755
3805
  $setCompositionKey(null);
@@ -3758,98 +3808,96 @@ class RangeSelection {
3758
3808
 
3759
3809
  const anchor = this.anchor;
3760
3810
  const focus = this.focus;
3761
- const anchorOffset = anchor.offset;
3762
- const focusOffset = focus.offset;
3763
- let firstNextFormat = firstNode.getFormatFlags(formatType, null);
3764
- let firstNodeTextLength = firstNode.getTextContent().length;
3765
- const isBefore = anchor.isBefore(focus);
3766
- const endOffset = isBefore ? focusOffset : anchorOffset;
3767
- let startOffset = isBefore ? anchorOffset : focusOffset; // This is the case where the user only selected the very end of the
3768
- // first node so we don't want to include it in the formatting change.
3811
+ const isBackward = this.isBackward();
3812
+ const startPoint = isBackward ? focus : anchor;
3813
+ const endPoint = isBackward ? anchor : focus;
3814
+ let firstIndex = 0;
3815
+ let firstNode = selectedTextNodes[0];
3816
+ let startOffset = startPoint.type === 'element' ? 0 : startPoint.offset; // In case selection started at the end of text node use next text node
3769
3817
 
3770
- if (startOffset === firstNode.getTextContentSize() && selectedTextNodes.length > 1) {
3771
- const nextNode = selectedTextNodes[1];
3772
- startOffset = 0;
3818
+ if (startPoint.type === 'text' && startOffset === firstNode.getTextContentSize()) {
3773
3819
  firstIndex = 1;
3774
- firstNode = nextNode;
3775
- firstNodeTextLength = nextNode.getTextContentSize();
3776
- firstNextFormat = nextNode.getFormatFlags(formatType, null);
3777
- } // This is the case where we only selected a single node
3820
+ firstNode = selectedTextNodes[1];
3821
+ startOffset = 0;
3822
+ }
3823
+
3824
+ if (firstNode == null) {
3825
+ return;
3826
+ }
3778
3827
 
3828
+ const firstNextFormat = firstNode.getFormatFlags(formatType, null);
3829
+ const lastIndex = selectedTextNodesLength - 1;
3830
+ let lastNode = selectedTextNodes[lastIndex];
3831
+ const endOffset = endPoint.type === 'text' ? endPoint.offset : lastNode.getTextContentSize(); // Single node selected
3779
3832
 
3780
3833
  if (firstNode.is(lastNode)) {
3781
- if ($isTextNode(firstNode)) {
3782
- if (anchor.type === 'element' && focus.type === 'element') {
3783
- firstNode.setFormat(firstNextFormat);
3784
- firstNode.select(startOffset, endOffset);
3785
- this.format = firstNextFormat;
3786
- return;
3787
- } // No actual text is selected, so do nothing.
3834
+ // No actual text is selected, so do nothing.
3835
+ if (startOffset === endOffset) {
3836
+ return;
3837
+ } // The entire node is selected, so just format it
3788
3838
 
3789
3839
 
3790
- if (startOffset === endOffset) {
3791
- return;
3792
- } // The entire node is selected, so just format it
3840
+ if (startOffset === 0 && endOffset === firstNode.getTextContentSize()) {
3841
+ firstNode.setFormat(firstNextFormat);
3842
+ } else {
3843
+ // Node is partially selected, so split it into two nodes
3844
+ // add style the selected one.
3845
+ const splitNodes = firstNode.splitText(startOffset, endOffset);
3846
+ const replacement = startOffset === 0 ? splitNodes[0] : splitNodes[1];
3847
+ replacement.setFormat(firstNextFormat); // Update selection only if starts/ends on text node
3793
3848
 
3849
+ if (startPoint.type === 'text') {
3850
+ startPoint.set(replacement.__key, 0, 'text');
3851
+ }
3794
3852
 
3795
- if (startOffset === 0 && endOffset === firstNodeTextLength) {
3796
- firstNode.setFormat(firstNextFormat);
3797
- firstNode.select(startOffset, endOffset);
3798
- } else {
3799
- // ndoe is partially selected, so split it into two nodes
3800
- // adnd style the selected one.
3801
- const splitNodes = firstNode.splitText(startOffset, endOffset);
3802
- const replacement = startOffset === 0 ? splitNodes[0] : splitNodes[1];
3803
- replacement.setFormat(firstNextFormat);
3804
- replacement.select(0, endOffset - startOffset);
3853
+ if (endPoint.type === 'text') {
3854
+ endPoint.set(replacement.__key, endOffset - startOffset, 'text');
3805
3855
  }
3856
+ }
3806
3857
 
3807
- this.format = firstNextFormat;
3808
- } // multiple nodes selected.
3858
+ this.format = firstNextFormat;
3859
+ return;
3860
+ } // Multiple nodes selected
3861
+ // The entire first node isn't selected, so split it
3809
3862
 
3810
- } else {
3811
- // Note: startOffset !== firstNodeTextLength should only occur within rare programatic
3812
- // update functions; transforms normalization ensure there's no empty text nodes.
3813
- if ($isTextNode(firstNode) && startOffset !== firstNodeTextLength) {
3814
- if (startOffset !== 0) {
3815
- // the entire first node isn't selected, so split it
3816
- [, firstNode] = firstNode.splitText(startOffset);
3817
- startOffset = 0;
3818
- }
3819
3863
 
3820
- firstNode.setFormat(firstNextFormat);
3864
+ if (startOffset !== 0) {
3865
+ [, firstNode] = firstNode.splitText(startOffset);
3866
+ startOffset = 0;
3867
+ }
3868
+
3869
+ firstNode.setFormat(firstNextFormat);
3870
+ const lastNextFormat = lastNode.getFormatFlags(formatType, firstNextFormat); // If the offset is 0, it means no actual characters are selected,
3871
+ // so we skip formatting the last node altogether.
3872
+
3873
+ if (endOffset > 0) {
3874
+ if (endOffset !== lastNode.getTextContentSize()) {
3875
+ [lastNode] = lastNode.splitText(endOffset);
3821
3876
  }
3822
3877
 
3823
- let lastNextFormat = firstNextFormat;
3878
+ lastNode.setFormat(lastNextFormat);
3879
+ } // Process all text nodes in between
3824
3880
 
3825
- if ($isTextNode(lastNode)) {
3826
- lastNextFormat = lastNode.getFormatFlags(formatType, firstNextFormat);
3827
- const lastNodeText = lastNode.getTextContent();
3828
- const lastNodeTextLength = lastNodeText.length; // if the offset is 0, it means no actual characters are selected,
3829
- // so we skip formatting the last node altogether.
3830
3881
 
3831
- if (endOffset !== 0) {
3832
- // if the entire last node isn't selected, split it
3833
- if (endOffset !== lastNodeTextLength) {
3834
- [lastNode] = lastNode.splitText(endOffset);
3835
- }
3882
+ for (let i = firstIndex + 1; i < lastIndex; i++) {
3883
+ const textNode = selectedTextNodes[i];
3836
3884
 
3837
- lastNode.setFormat(lastNextFormat);
3838
- }
3885
+ if (!textNode.isToken()) {
3886
+ const nextFormat = textNode.getFormatFlags(formatType, lastNextFormat);
3887
+ textNode.setFormat(nextFormat);
3839
3888
  }
3889
+ } // Update selection only if starts/ends on text node
3840
3890
 
3841
- this.format = firstNextFormat | lastNextFormat; // deal with all the nodes in between
3842
3891
 
3843
- for (let i = firstIndex + 1; i < lastIndex; i++) {
3844
- const selectedNode = selectedTextNodes[i];
3845
- const selectedNodeKey = selectedNode.__key;
3892
+ if (startPoint.type === 'text') {
3893
+ startPoint.set(firstNode.__key, startOffset, 'text');
3894
+ }
3846
3895
 
3847
- if ($isTextNode(selectedNode) && selectedNodeKey !== firstNode.__key && selectedNodeKey !== lastNode.__key && !selectedNode.isToken()) {
3848
- const selectedNextFormat = selectedNode.getFormatFlags(formatType, lastNextFormat);
3849
- selectedNode.setFormat(selectedNextFormat);
3850
- }
3851
- }
3896
+ if (endPoint.type === 'text') {
3897
+ endPoint.set(lastNode.__key, endOffset, 'text');
3852
3898
  }
3899
+
3900
+ this.format = firstNextFormat | lastNextFormat;
3853
3901
  }
3854
3902
 
3855
3903
  insertNodes(nodes, selectStart) {
@@ -3920,7 +3968,7 @@ class RangeSelection {
3920
3968
 
3921
3969
  if ($isElementNode(node) && !node.isInline()) {
3922
3970
  // -----
3923
- // Heuristics for the replacment or merging of elements
3971
+ // Heuristics for the replacement or merging of elements
3924
3972
  // -----
3925
3973
  // If we have an incoming element node as the first node, then we'll need
3926
3974
  // see if we can merge any descendant leaf nodes into our existing target.
@@ -4097,7 +4145,7 @@ class RangeSelection {
4097
4145
  const sibling = siblings[i];
4098
4146
  const prevParent = sibling.getParentOrThrow();
4099
4147
 
4100
- if ($isElementNode(target) && !$isBlockElementNode(sibling)) {
4148
+ if ($isElementNode(target) && !$isBlockElementNode(sibling) && !($isDecoratorNode(sibling) && sibling.isTopLevel())) {
4101
4149
  if (originalTarget === target) {
4102
4150
  target.append(sibling);
4103
4151
  } else {
@@ -4476,7 +4524,18 @@ class RangeSelection {
4476
4524
 
4477
4525
  deleteLine(isBackward) {
4478
4526
  if (this.isCollapsed()) {
4479
- this.modify('extend', isBackward, 'lineboundary');
4527
+ if (this.anchor.type === 'text') {
4528
+ this.modify('extend', isBackward, 'lineboundary');
4529
+ } // If selection is extended to cover text edge then extend it one character more
4530
+ // to delete its parent element. Otherwise text content will be deleted but empty
4531
+ // parent node will remain
4532
+
4533
+
4534
+ const endPoint = isBackward ? this.focus : this.anchor;
4535
+
4536
+ if (endPoint.offset === 0) {
4537
+ this.modify('extend', isBackward, 'character');
4538
+ }
4480
4539
  }
4481
4540
 
4482
4541
  this.removeText();
@@ -5264,7 +5323,7 @@ function $normalizeAllDirtyTextNodes(editorState, editor) {
5264
5323
  * 2. We transform elements. If element transforms generate additional dirty nodes we repeat step 1.
5265
5324
  * If element transforms only generate additional dirty elements we only repeat step 2.
5266
5325
  *
5267
- * Note that to keep track of newly dirty nodes and subtress we leverage the editor._dirtyNodes and
5326
+ * Note that to keep track of newly dirty nodes and subtrees we leverage the editor._dirtyNodes and
5268
5327
  * editor._subtrees which we reset in every loop.
5269
5328
  */
5270
5329
 
@@ -5469,7 +5528,7 @@ function commitPendingUpdates(editor) {
5469
5528
  if (rootElement === null && !headless || pendingEditorState === null) {
5470
5529
  return;
5471
5530
  } // ======
5472
- // Reconcilation has started.
5531
+ // Reconciliation has started.
5473
5532
  // ======
5474
5533
 
5475
5534
 
@@ -5489,7 +5548,7 @@ function commitPendingUpdates(editor) {
5489
5548
  if (!headless && needsUpdate && observer !== null) {
5490
5549
  activeEditor = editor;
5491
5550
  activeEditorState = pendingEditorState;
5492
- isReadOnlyMode = false; // We don't want updates to sync block the reconcilation.
5551
+ isReadOnlyMode = false; // We don't want updates to sync block the reconciliation.
5493
5552
 
5494
5553
  editor._updating = true;
5495
5554
 
@@ -5564,7 +5623,7 @@ function commitPendingUpdates(editor) {
5564
5623
  }
5565
5624
 
5566
5625
  $garbageCollectDetachedDecorators(editor, pendingEditorState); // ======
5567
- // Reconcilation has finished. Now update selection and trigger listeners.
5626
+ // Reconciliation has finished. Now update selection and trigger listeners.
5568
5627
  // ======
5569
5628
 
5570
5629
  const domSelection = headless ? null : getDOMSelection(); // Attempt to update the DOM selection, including focusing of the root element,
@@ -5584,7 +5643,7 @@ function commitPendingUpdates(editor) {
5584
5643
  }
5585
5644
 
5586
5645
  if (mutatedNodes !== null) {
5587
- triggerMutationListeners(editor, currentEditorState, pendingEditorState, mutatedNodes);
5646
+ triggerMutationListeners(editor, currentEditorState, pendingEditorState, mutatedNodes, tags, dirtyLeaves);
5588
5647
  }
5589
5648
 
5590
5649
  if (pendingDecorators !== null) {
@@ -5615,7 +5674,7 @@ function triggerTextContentListeners(editor, currentEditorState, pendingEditorSt
5615
5674
  }
5616
5675
  }
5617
5676
 
5618
- function triggerMutationListeners(editor, currentEditorState, pendingEditorState, mutatedNodes) {
5677
+ function triggerMutationListeners(editor, currentEditorState, pendingEditorState, mutatedNodes, updateTags, dirtyLeaves) {
5619
5678
  const listeners = Array.from(editor._listeners.mutation);
5620
5679
  const listenersLength = listeners.length;
5621
5680
 
@@ -5624,7 +5683,10 @@ function triggerMutationListeners(editor, currentEditorState, pendingEditorState
5624
5683
  const mutatedNodesByType = mutatedNodes.get(klass);
5625
5684
 
5626
5685
  if (mutatedNodesByType !== undefined) {
5627
- listener(mutatedNodesByType);
5686
+ listener(mutatedNodesByType, {
5687
+ dirtyLeaves,
5688
+ updateTags
5689
+ });
5628
5690
  }
5629
5691
  }
5630
5692
  }
@@ -5908,7 +5970,7 @@ function removeNode(nodeToRemove, restoreSelection, preserveEmptyParent) {
5908
5970
  return;
5909
5971
  }
5910
5972
 
5911
- const selection = $getSelection();
5973
+ const selection = $maybeMoveChildrenSelectionToParent(nodeToRemove);
5912
5974
  let selectionMoved = false;
5913
5975
 
5914
5976
  if ($isRangeSelection(selection) && restoreSelection) {
@@ -6089,7 +6151,7 @@ class LexicalNode {
6089
6151
  while (node !== null) {
6090
6152
  const parent = node.getParent();
6091
6153
 
6092
- if ($isRootNode(parent) && $isElementNode(node)) {
6154
+ if ($isRootNode(parent) && ($isElementNode(node) || $isDecoratorNode(node) && node.isTopLevel())) {
6093
6155
  return node;
6094
6156
  }
6095
6157
 
@@ -7026,7 +7088,7 @@ class ElementNode extends LexicalNode {
7026
7088
 
7027
7089
  if (nodeToInsert.__key === writableSelfKey) {
7028
7090
  {
7029
- throw Error(`append: attemtping to append self`);
7091
+ throw Error(`append: attempting to append self`);
7030
7092
  }
7031
7093
  }
7032
7094
 
@@ -8599,7 +8661,7 @@ class LexicalEditor {
8599
8661
  this._nodes = nodes; // React node decorators for portals
8600
8662
 
8601
8663
  this._decorators = {};
8602
- this._pendingDecorators = null; // Used to optimize reconcilation
8664
+ this._pendingDecorators = null; // Used to optimize reconciliation
8603
8665
 
8604
8666
  this._dirtyType = NO_DIRTY_NODES;
8605
8667
  this._cloneNotNeeded = new Set();
@@ -8919,7 +8981,7 @@ class LexicalEditor {
8919
8981
  * LICENSE file in the root directory of this source tree.
8920
8982
  *
8921
8983
  */
8922
- const VERSION = '0.3.8';
8984
+ const VERSION = '0.3.11';
8923
8985
 
8924
8986
  /**
8925
8987
  * Copyright (c) Meta Platforms, Inc. and affiliates.
package/Lexical.js.flow CHANGED
@@ -78,7 +78,10 @@ type RootListener = (
78
78
  ) => void;
79
79
  type TextContentListener = (text: string) => void;
80
80
  type ErrorHandler = (error: Error) => void;
81
- type MutationListener = (nodes: Map<NodeKey, NodeMutation>) => void;
81
+ export type MutationListener = (
82
+ nodes: Map<NodeKey, NodeMutation>,
83
+ {updateTags: Set<string>, dirtyLeaves: Set<string>},
84
+ ) => void;
82
85
  export type ReadOnlyListener = (readOnly: boolean) => void;
83
86
  type Listeners = {
84
87
  decorator: Set<DecoratorListener>,