lexical 0.3.7 → 0.3.10

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;
@@ -115,7 +113,8 @@ const IS_STRIKETHROUGH = 1 << 2;
115
113
  const IS_UNDERLINE = 1 << 3;
116
114
  const IS_CODE = 1 << 4;
117
115
  const IS_SUBSCRIPT = 1 << 5;
118
- const IS_SUPERSCRIPT = 1 << 6; // Text node details
116
+ const IS_SUPERSCRIPT = 1 << 6;
117
+ const IS_ALL_FORMATTING = IS_BOLD | IS_ITALIC | IS_STRIKETHROUGH | IS_UNDERLINE | IS_CODE | IS_SUBSCRIPT | IS_SUPERSCRIPT; // Text node details
119
118
 
120
119
  const IS_DIRECTIONLESS = 1;
121
120
  const IS_UNMERGEABLE = 1 << 1; // Element node formatting
@@ -125,10 +124,11 @@ const IS_ALIGN_CENTER = 2;
125
124
  const IS_ALIGN_RIGHT = 3;
126
125
  const IS_ALIGN_JUSTIFY = 4; // Reconciliation
127
126
 
128
- const NON_BREAKING_SPACE = '\u00A0'; // For iOS/Safari we use a non breaking space, otherwise the cursor appears
127
+ const NON_BREAKING_SPACE = '\u00A0';
128
+ const ZERO_WIDTH_SPACE = '\u200b'; // For iOS/Safari we use a non breaking space, otherwise the cursor appears
129
129
  // overlapping the composed text.
130
130
 
131
- const COMPOSITION_SUFFIX = IS_SAFARI || IS_IOS ? NON_BREAKING_SPACE : '\u200b';
131
+ const COMPOSITION_SUFFIX = IS_SAFARI || IS_IOS ? NON_BREAKING_SPACE : ZERO_WIDTH_SPACE;
132
132
  const DOUBLE_LINE_BREAK = '\n\n'; // For FF, we need to use a non-breaking space, or it gets composition
133
133
  // in a stuck state.
134
134
 
@@ -494,12 +494,16 @@ function $isTokenOrInertOrSegmented(node) {
494
494
  function $isTokenOrInert(node) {
495
495
  return node.isToken() || node.isInert();
496
496
  }
497
+
498
+ function isDOMNodeLexicalTextNode(node) {
499
+ return node.nodeType === DOM_TEXT_TYPE;
500
+ }
501
+
497
502
  function getDOMTextNode(element) {
498
503
  let node = element;
499
504
 
500
505
  while (node != null) {
501
- if (node.nodeType === DOM_TEXT_TYPE) {
502
- // @ts-expect-error: this is a Text
506
+ if (isDOMNodeLexicalTextNode(node)) {
503
507
  return node;
504
508
  }
505
509
 
@@ -810,7 +814,7 @@ function getEditorsToPropagate(editor) {
810
814
  function createUID() {
811
815
  return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);
812
816
  }
813
- function $updateSelectedTextFromDOM(editor, isCompositionEnd, data) {
817
+ function $updateSelectedTextFromDOM(isCompositionEnd, data) {
814
818
  // Update the text content with the latest composition text
815
819
  const domSelection = getDOMSelection();
816
820
 
@@ -881,7 +885,7 @@ function $updateTextNodeFromDOMContent(textNode, textContent, anchorOffset, focu
881
885
  const prevSelection = $getPreviousSelection();
882
886
 
883
887
  if ($isTokenOrInert(node) || $getCompositionKey() !== null && !isComposing || // Check if character was added at the start, and we need
884
- // to clear this input from occuring as that action wasn't
888
+ // to clear this input from occurring as that action wasn't
885
889
  // permitted.
886
890
  parent !== null && $isRangeSelection(prevSelection) && !parent.canInsertTextBefore() && prevSelection.anchor.offset === 0) {
887
891
  node.markDirty();
@@ -1071,22 +1075,22 @@ function isArrowDown(keyCode) {
1071
1075
  return keyCode === 40;
1072
1076
  }
1073
1077
 
1074
- function isMoveBackward(keyCode, ctrlKey, shiftKey, altKey, metaKey) {
1078
+ function isMoveBackward(keyCode, ctrlKey, altKey, metaKey) {
1075
1079
  return isArrowLeft(keyCode) && !ctrlKey && !metaKey && !altKey;
1076
1080
  }
1077
1081
  function isMoveToStart(keyCode, ctrlKey, shiftKey, altKey, metaKey) {
1078
1082
  return isArrowLeft(keyCode) && !altKey && !shiftKey && (ctrlKey || metaKey);
1079
1083
  }
1080
- function isMoveForward(keyCode, ctrlKey, shiftKey, altKey, metaKey) {
1084
+ function isMoveForward(keyCode, ctrlKey, altKey, metaKey) {
1081
1085
  return isArrowRight(keyCode) && !ctrlKey && !metaKey && !altKey;
1082
1086
  }
1083
1087
  function isMoveToEnd(keyCode, ctrlKey, shiftKey, altKey, metaKey) {
1084
1088
  return isArrowRight(keyCode) && !altKey && !shiftKey && (ctrlKey || metaKey);
1085
1089
  }
1086
- function isMoveUp(keyCode, ctrlKey, shiftKey, altKey, metaKey) {
1090
+ function isMoveUp(keyCode, ctrlKey, metaKey) {
1087
1091
  return isArrowUp(keyCode) && !ctrlKey && !metaKey;
1088
1092
  }
1089
- function isMoveDown(keyCode, ctrlKey, shiftKey, altKey, metaKey) {
1093
+ function isMoveDown(keyCode, ctrlKey, metaKey) {
1090
1094
  return isArrowDown(keyCode) && !ctrlKey && !metaKey;
1091
1095
  }
1092
1096
  function isModifier(ctrlKey, shiftKey, altKey, metaKey) {
@@ -1228,7 +1232,7 @@ function getElementByKeyOrThrow(editor, key) {
1228
1232
 
1229
1233
  if (element === undefined) {
1230
1234
  {
1231
- 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}`);
1232
1236
  }
1233
1237
  }
1234
1238
 
@@ -1268,6 +1272,50 @@ function scrollIntoViewIfNeeded(editor, anchor, rootElement, tags) {
1268
1272
  tags.add('scroll-into-view');
1269
1273
  }
1270
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
+ }
1271
1319
 
1272
1320
  /**
1273
1321
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -1633,7 +1681,7 @@ function isLastChildLineBreakOrDecorator(children, nodeMap) {
1633
1681
  const childKey = children[children.length - 1];
1634
1682
  const node = nodeMap.get(childKey);
1635
1683
  return $isLineBreakNode(node) || $isDecoratorNode(node);
1636
- } // 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>
1637
1685
 
1638
1686
 
1639
1687
  function reconcileElementTerminatingLineBreak(prevChildren, nextChildren, dom) {
@@ -2055,7 +2103,7 @@ function getPrevElementByKeyOrThrow(key) {
2055
2103
 
2056
2104
  if (element === undefined) {
2057
2105
  {
2058
- 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}`);
2059
2107
  }
2060
2108
  }
2061
2109
 
@@ -2153,19 +2201,25 @@ function onSelectionChange(domSelection, editor, isActive) {
2153
2201
  }
2154
2202
  }
2155
2203
  } else {
2156
- const focus = selection.focus;
2157
- const focusNode = focus.getNode();
2158
- let combinedFormat = 0;
2204
+ let combinedFormat = IS_ALL_FORMATTING;
2205
+ let hasTextNodes = false;
2206
+ const nodes = selection.getNodes();
2207
+ const nodesLength = nodes.length;
2159
2208
 
2160
- if (anchor.type === 'text') {
2161
- combinedFormat |= anchorNode.getFormat();
2162
- }
2209
+ for (let i = 0; i < nodesLength; i++) {
2210
+ const node = nodes[i];
2211
+
2212
+ if ($isTextNode(node)) {
2213
+ hasTextNodes = true;
2214
+ combinedFormat &= node.getFormat();
2163
2215
 
2164
- if (focus.type === 'text' && !anchorNode.is(focusNode)) {
2165
- combinedFormat |= focusNode.getFormat();
2216
+ if (combinedFormat === 0) {
2217
+ break;
2218
+ }
2219
+ }
2166
2220
  }
2167
2221
 
2168
- selection.format = combinedFormat;
2222
+ selection.format = hasTextNodes ? combinedFormat : 0;
2169
2223
  }
2170
2224
  }
2171
2225
 
@@ -2192,7 +2246,7 @@ function onClick(event, editor) {
2192
2246
  domSelection.removeAllRanges();
2193
2247
  selection.dirty = true;
2194
2248
  }
2195
- } else if (domSelection && $isNodeSelection(selection) && domSelection.isCollapsed) {
2249
+ } else if (domSelection && $isNodeSelection(selection)) {
2196
2250
  const domAnchor = domSelection.anchorNode; // If the user is attempting to click selection back onto text, then
2197
2251
  // we should attempt create a range selection.
2198
2252
  // When we click on an empty paragraph node or the end of a paragraph that ends
@@ -2239,41 +2293,6 @@ function onBeforeInput(event, editor) {
2239
2293
  IS_FIREFOX && isFirefoxClipboardEvents()) {
2240
2294
  return;
2241
2295
  } else if (inputType === 'insertCompositionText') {
2242
- // This logic handles insertion of text between different
2243
- // format text types. We have to detect a change in type
2244
- // during composition and see if the previous text contains
2245
- // part of the composed text to work out the actual text that
2246
- // we need to insert.
2247
- const composedText = event.data; // TODO: evaluate if this is Android only. It doesn't always seem
2248
- // to have any real impact, so could probably be refactored or removed
2249
- // for an alternative approach.
2250
-
2251
- if (composedText) {
2252
- updateEditor(editor, () => {
2253
- const selection = $getSelection();
2254
-
2255
- if ($isRangeSelection(selection)) {
2256
- const anchor = selection.anchor;
2257
- const node = anchor.getNode();
2258
- const prevNode = node.getPreviousSibling();
2259
-
2260
- if (anchor.offset === 0 && $isTextNode(node) && $isTextNode(prevNode) && node.getTextContent() === COMPOSITION_START_CHAR && prevNode.getFormat() !== selection.format) {
2261
- const prevTextContent = prevNode.getTextContent();
2262
-
2263
- if (composedText.indexOf(prevTextContent) === 0) {
2264
- const insertedText = composedText.slice(prevTextContent.length);
2265
- dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, insertedText);
2266
- setTimeout(() => {
2267
- updateEditor(editor, () => {
2268
- node.select();
2269
- });
2270
- }, ANDROID_COMPOSITION_LATENCY);
2271
- }
2272
- }
2273
- }
2274
- });
2275
- }
2276
-
2277
2296
  return;
2278
2297
  }
2279
2298
 
@@ -2333,10 +2352,10 @@ function onBeforeInput(event, editor) {
2333
2352
  const anchorNode = anchor.getNode();
2334
2353
  const focusNode = focus.getNode();
2335
2354
 
2336
- if (inputType === 'insertText') {
2355
+ if (inputType === 'insertText' || inputType === 'insertTranspose') {
2337
2356
  if (data === '\n') {
2338
2357
  event.preventDefault();
2339
- dispatchCommand(editor, INSERT_LINE_BREAK_COMMAND, undefined);
2358
+ dispatchCommand(editor, INSERT_LINE_BREAK_COMMAND, false);
2340
2359
  } else if (data === DOUBLE_LINE_BREAK) {
2341
2360
  event.preventDefault();
2342
2361
  dispatchCommand(editor, INSERT_PARAGRAPH_COMMAND, undefined);
@@ -2379,7 +2398,7 @@ function onBeforeInput(event, editor) {
2379
2398
  {
2380
2399
  // Used for Android
2381
2400
  $setCompositionKey(null);
2382
- dispatchCommand(editor, INSERT_LINE_BREAK_COMMAND, undefined);
2401
+ dispatchCommand(editor, INSERT_LINE_BREAK_COMMAND, false);
2383
2402
  break;
2384
2403
  }
2385
2404
 
@@ -2391,7 +2410,7 @@ function onBeforeInput(event, editor) {
2391
2410
 
2392
2411
  if (isInsertLineBreak) {
2393
2412
  isInsertLineBreak = false;
2394
- dispatchCommand(editor, INSERT_LINE_BREAK_COMMAND, undefined);
2413
+ dispatchCommand(editor, INSERT_LINE_BREAK_COMMAND, false);
2395
2414
  } else {
2396
2415
  dispatchCommand(editor, INSERT_PARAGRAPH_COMMAND, undefined);
2397
2416
  }
@@ -2525,7 +2544,7 @@ function onInput(event, editor) {
2525
2544
  $setCompositionKey(null);
2526
2545
  }
2527
2546
  } else {
2528
- $updateSelectedTextFromDOM(editor, false); // onInput always fires after onCompositionEnd for FF.
2547
+ $updateSelectedTextFromDOM(false); // onInput always fires after onCompositionEnd for FF.
2529
2548
 
2530
2549
  if (isFirefoxEndingComposition) {
2531
2550
  onCompositionEndImpl(editor, data || undefined);
@@ -2597,7 +2616,7 @@ function onCompositionEndImpl(editor, data) {
2597
2616
  }
2598
2617
  }
2599
2618
 
2600
- $updateSelectedTextFromDOM(editor, true, data);
2619
+ $updateSelectedTextFromDOM(true, data);
2601
2620
  }
2602
2621
 
2603
2622
  function onCompositionEnd(event, editor) {
@@ -2631,17 +2650,17 @@ function onKeyDown(event, editor) {
2631
2650
  altKey
2632
2651
  } = event;
2633
2652
 
2634
- if (isMoveForward(keyCode, ctrlKey, shiftKey, altKey, metaKey)) {
2653
+ if (isMoveForward(keyCode, ctrlKey, altKey, metaKey)) {
2635
2654
  dispatchCommand(editor, KEY_ARROW_RIGHT_COMMAND, event);
2636
2655
  } else if (isMoveToEnd(keyCode, ctrlKey, shiftKey, altKey, metaKey)) {
2637
2656
  dispatchCommand(editor, MOVE_TO_END, event);
2638
- } else if (isMoveBackward(keyCode, ctrlKey, shiftKey, altKey, metaKey)) {
2657
+ } else if (isMoveBackward(keyCode, ctrlKey, altKey, metaKey)) {
2639
2658
  dispatchCommand(editor, KEY_ARROW_LEFT_COMMAND, event);
2640
2659
  } else if (isMoveToStart(keyCode, ctrlKey, shiftKey, altKey, metaKey)) {
2641
2660
  dispatchCommand(editor, MOVE_TO_START, event);
2642
- } else if (isMoveUp(keyCode, ctrlKey, shiftKey, altKey, metaKey)) {
2661
+ } else if (isMoveUp(keyCode, ctrlKey, metaKey)) {
2643
2662
  dispatchCommand(editor, KEY_ARROW_UP_COMMAND, event);
2644
- } else if (isMoveDown(keyCode, ctrlKey, shiftKey, altKey, metaKey)) {
2663
+ } else if (isMoveDown(keyCode, ctrlKey, metaKey)) {
2645
2664
  dispatchCommand(editor, KEY_ARROW_DOWN_COMMAND, event);
2646
2665
  } else if (isLineBreak(keyCode, shiftKey)) {
2647
2666
  isInsertLineBreak = true;
@@ -3762,7 +3781,13 @@ class RangeSelection {
3762
3781
  }
3763
3782
 
3764
3783
  formatText(formatType) {
3765
- // 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
+
3766
3791
  const selectedNodes = this.getNodes();
3767
3792
  const selectedTextNodes = [];
3768
3793
 
@@ -3773,12 +3798,8 @@ class RangeSelection {
3773
3798
  }
3774
3799
 
3775
3800
  const selectedTextNodesLength = selectedTextNodes.length;
3776
- let firstIndex = 0;
3777
- const lastIndex = selectedTextNodesLength - 1;
3778
- let firstNode = selectedTextNodes[0];
3779
- let lastNode = selectedTextNodes[lastIndex];
3780
3801
 
3781
- if (this.isCollapsed()) {
3802
+ if (selectedTextNodesLength === 0) {
3782
3803
  this.toggleFormat(formatType); // When changing format, we should stop composition
3783
3804
 
3784
3805
  $setCompositionKey(null);
@@ -3787,98 +3808,96 @@ class RangeSelection {
3787
3808
 
3788
3809
  const anchor = this.anchor;
3789
3810
  const focus = this.focus;
3790
- const anchorOffset = anchor.offset;
3791
- const focusOffset = focus.offset;
3792
- let firstNextFormat = firstNode.getFormatFlags(formatType, null);
3793
- let firstNodeTextLength = firstNode.getTextContent().length;
3794
- const isBefore = anchor.isBefore(focus);
3795
- const endOffset = isBefore ? focusOffset : anchorOffset;
3796
- let startOffset = isBefore ? anchorOffset : focusOffset; // This is the case where the user only selected the very end of the
3797
- // 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
3798
3817
 
3799
- if (startOffset === firstNode.getTextContentSize() && selectedTextNodes.length > 1) {
3800
- const nextNode = selectedTextNodes[1];
3801
- startOffset = 0;
3818
+ if (startPoint.type === 'text' && startOffset === firstNode.getTextContentSize()) {
3802
3819
  firstIndex = 1;
3803
- firstNode = nextNode;
3804
- firstNodeTextLength = nextNode.getTextContentSize();
3805
- firstNextFormat = nextNode.getFormatFlags(formatType, null);
3806
- } // This is the case where we only selected a single node
3820
+ firstNode = selectedTextNodes[1];
3821
+ startOffset = 0;
3822
+ }
3807
3823
 
3824
+ if (firstNode == null) {
3825
+ return;
3826
+ }
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
3808
3832
 
3809
3833
  if (firstNode.is(lastNode)) {
3810
- if ($isTextNode(firstNode)) {
3811
- if (anchor.type === 'element' && focus.type === 'element') {
3812
- firstNode.setFormat(firstNextFormat);
3813
- firstNode.select(startOffset, endOffset);
3814
- this.format = firstNextFormat;
3815
- return;
3816
- } // 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
3817
3838
 
3818
3839
 
3819
- if (startOffset === endOffset) {
3820
- return;
3821
- } // 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
3822
3848
 
3849
+ if (startPoint.type === 'text') {
3850
+ startPoint.set(replacement.__key, 0, 'text');
3851
+ }
3823
3852
 
3824
- if (startOffset === 0 && endOffset === firstNodeTextLength) {
3825
- firstNode.setFormat(firstNextFormat);
3826
- firstNode.select(startOffset, endOffset);
3827
- } else {
3828
- // ndoe is partially selected, so split it into two nodes
3829
- // adnd style the selected one.
3830
- const splitNodes = firstNode.splitText(startOffset, endOffset);
3831
- const replacement = startOffset === 0 ? splitNodes[0] : splitNodes[1];
3832
- replacement.setFormat(firstNextFormat);
3833
- replacement.select(0, endOffset - startOffset);
3853
+ if (endPoint.type === 'text') {
3854
+ endPoint.set(replacement.__key, endOffset - startOffset, 'text');
3834
3855
  }
3856
+ }
3835
3857
 
3836
- this.format = firstNextFormat;
3837
- } // multiple nodes selected.
3858
+ this.format = firstNextFormat;
3859
+ return;
3860
+ } // Multiple nodes selected
3861
+ // The entire first node isn't selected, so split it
3838
3862
 
3839
- } else {
3840
- // Note: startOffset !== firstNodeTextLength should only occur within rare programatic
3841
- // update functions; transforms normalization ensure there's no empty text nodes.
3842
- if ($isTextNode(firstNode) && startOffset !== firstNodeTextLength) {
3843
- if (startOffset !== 0) {
3844
- // the entire first node isn't selected, so split it
3845
- [, firstNode] = firstNode.splitText(startOffset);
3846
- startOffset = 0;
3847
- }
3848
3863
 
3849
- 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);
3850
3876
  }
3851
3877
 
3852
- let lastNextFormat = firstNextFormat;
3878
+ lastNode.setFormat(lastNextFormat);
3879
+ } // Process all text nodes in between
3853
3880
 
3854
- if ($isTextNode(lastNode)) {
3855
- lastNextFormat = lastNode.getFormatFlags(formatType, firstNextFormat);
3856
- const lastNodeText = lastNode.getTextContent();
3857
- const lastNodeTextLength = lastNodeText.length; // if the offset is 0, it means no actual characters are selected,
3858
- // so we skip formatting the last node altogether.
3859
3881
 
3860
- if (endOffset !== 0) {
3861
- // if the entire last node isn't selected, split it
3862
- if (endOffset !== lastNodeTextLength) {
3863
- [lastNode] = lastNode.splitText(endOffset);
3864
- }
3882
+ for (let i = firstIndex + 1; i < lastIndex; i++) {
3883
+ const textNode = selectedTextNodes[i];
3865
3884
 
3866
- lastNode.setFormat(lastNextFormat);
3867
- }
3885
+ if (!textNode.isToken()) {
3886
+ const nextFormat = textNode.getFormatFlags(formatType, lastNextFormat);
3887
+ textNode.setFormat(nextFormat);
3868
3888
  }
3889
+ } // Update selection only if starts/ends on text node
3869
3890
 
3870
- this.format = firstNextFormat | lastNextFormat; // deal with all the nodes in between
3871
3891
 
3872
- for (let i = firstIndex + 1; i < lastIndex; i++) {
3873
- const selectedNode = selectedTextNodes[i];
3874
- const selectedNodeKey = selectedNode.__key;
3892
+ if (startPoint.type === 'text') {
3893
+ startPoint.set(firstNode.__key, startOffset, 'text');
3894
+ }
3875
3895
 
3876
- if ($isTextNode(selectedNode) && selectedNodeKey !== firstNode.__key && selectedNodeKey !== lastNode.__key && !selectedNode.isToken()) {
3877
- const selectedNextFormat = selectedNode.getFormatFlags(formatType, lastNextFormat);
3878
- selectedNode.setFormat(selectedNextFormat);
3879
- }
3880
- }
3896
+ if (endPoint.type === 'text') {
3897
+ endPoint.set(lastNode.__key, endOffset, 'text');
3881
3898
  }
3899
+
3900
+ this.format = firstNextFormat | lastNextFormat;
3882
3901
  }
3883
3902
 
3884
3903
  insertNodes(nodes, selectStart) {
@@ -3949,7 +3968,7 @@ class RangeSelection {
3949
3968
 
3950
3969
  if ($isElementNode(node) && !node.isInline()) {
3951
3970
  // -----
3952
- // Heuristics for the replacment or merging of elements
3971
+ // Heuristics for the replacement or merging of elements
3953
3972
  // -----
3954
3973
  // If we have an incoming element node as the first node, then we'll need
3955
3974
  // see if we can merge any descendant leaf nodes into our existing target.
@@ -4126,7 +4145,7 @@ class RangeSelection {
4126
4145
  const sibling = siblings[i];
4127
4146
  const prevParent = sibling.getParentOrThrow();
4128
4147
 
4129
- if ($isElementNode(target) && !$isBlockElementNode(sibling)) {
4148
+ if ($isElementNode(target) && !$isBlockElementNode(sibling) && !($isDecoratorNode(sibling) && sibling.isTopLevel())) {
4130
4149
  if (originalTarget === target) {
4131
4150
  target.append(sibling);
4132
4151
  } else {
@@ -4335,7 +4354,7 @@ class RangeSelection {
4335
4354
  if (selectedNodesLength === 0) {
4336
4355
  return [];
4337
4356
  } else if (selectedNodesLength === 1) {
4338
- if ($isTextNode(firstNode)) {
4357
+ if ($isTextNode(firstNode) && !this.isCollapsed()) {
4339
4358
  const startOffset = anchorOffset > focusOffset ? focusOffset : anchorOffset;
4340
4359
  const endOffset = anchorOffset > focusOffset ? anchorOffset : focusOffset;
4341
4360
  const splitNodes = firstNode.splitText(startOffset, endOffset);
@@ -4505,7 +4524,18 @@ class RangeSelection {
4505
4524
 
4506
4525
  deleteLine(isBackward) {
4507
4526
  if (this.isCollapsed()) {
4508
- 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
+ }
4509
4539
  }
4510
4540
 
4511
4541
  this.removeText();
@@ -5293,7 +5323,7 @@ function $normalizeAllDirtyTextNodes(editorState, editor) {
5293
5323
  * 2. We transform elements. If element transforms generate additional dirty nodes we repeat step 1.
5294
5324
  * If element transforms only generate additional dirty elements we only repeat step 2.
5295
5325
  *
5296
- * 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
5297
5327
  * editor._subtrees which we reset in every loop.
5298
5328
  */
5299
5329
 
@@ -5498,7 +5528,7 @@ function commitPendingUpdates(editor) {
5498
5528
  if (rootElement === null && !headless || pendingEditorState === null) {
5499
5529
  return;
5500
5530
  } // ======
5501
- // Reconcilation has started.
5531
+ // Reconciliation has started.
5502
5532
  // ======
5503
5533
 
5504
5534
 
@@ -5518,7 +5548,7 @@ function commitPendingUpdates(editor) {
5518
5548
  if (!headless && needsUpdate && observer !== null) {
5519
5549
  activeEditor = editor;
5520
5550
  activeEditorState = pendingEditorState;
5521
- 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.
5522
5552
 
5523
5553
  editor._updating = true;
5524
5554
 
@@ -5593,7 +5623,7 @@ function commitPendingUpdates(editor) {
5593
5623
  }
5594
5624
 
5595
5625
  $garbageCollectDetachedDecorators(editor, pendingEditorState); // ======
5596
- // Reconcilation has finished. Now update selection and trigger listeners.
5626
+ // Reconciliation has finished. Now update selection and trigger listeners.
5597
5627
  // ======
5598
5628
 
5599
5629
  const domSelection = headless ? null : getDOMSelection(); // Attempt to update the DOM selection, including focusing of the root element,
@@ -5613,7 +5643,7 @@ function commitPendingUpdates(editor) {
5613
5643
  }
5614
5644
 
5615
5645
  if (mutatedNodes !== null) {
5616
- triggerMutationListeners(editor, currentEditorState, pendingEditorState, mutatedNodes);
5646
+ triggerMutationListeners(editor, currentEditorState, pendingEditorState, mutatedNodes, tags, dirtyLeaves);
5617
5647
  }
5618
5648
 
5619
5649
  if (pendingDecorators !== null) {
@@ -5644,7 +5674,7 @@ function triggerTextContentListeners(editor, currentEditorState, pendingEditorSt
5644
5674
  }
5645
5675
  }
5646
5676
 
5647
- function triggerMutationListeners(editor, currentEditorState, pendingEditorState, mutatedNodes) {
5677
+ function triggerMutationListeners(editor, currentEditorState, pendingEditorState, mutatedNodes, updateTags, dirtyLeaves) {
5648
5678
  const listeners = Array.from(editor._listeners.mutation);
5649
5679
  const listenersLength = listeners.length;
5650
5680
 
@@ -5653,7 +5683,10 @@ function triggerMutationListeners(editor, currentEditorState, pendingEditorState
5653
5683
  const mutatedNodesByType = mutatedNodes.get(klass);
5654
5684
 
5655
5685
  if (mutatedNodesByType !== undefined) {
5656
- listener(mutatedNodesByType);
5686
+ listener(mutatedNodesByType, {
5687
+ dirtyLeaves,
5688
+ updateTags
5689
+ });
5657
5690
  }
5658
5691
  }
5659
5692
  }
@@ -5937,7 +5970,7 @@ function removeNode(nodeToRemove, restoreSelection, preserveEmptyParent) {
5937
5970
  return;
5938
5971
  }
5939
5972
 
5940
- const selection = $getSelection();
5973
+ const selection = $maybeMoveChildrenSelectionToParent(nodeToRemove);
5941
5974
  let selectionMoved = false;
5942
5975
 
5943
5976
  if ($isRangeSelection(selection) && restoreSelection) {
@@ -6118,7 +6151,7 @@ class LexicalNode {
6118
6151
  while (node !== null) {
6119
6152
  const parent = node.getParent();
6120
6153
 
6121
- if ($isRootNode(parent) && $isElementNode(node)) {
6154
+ if ($isRootNode(parent) && ($isElementNode(node) || $isDecoratorNode(node) && node.isTopLevel())) {
6122
6155
  return node;
6123
6156
  }
6124
6157
 
@@ -7055,7 +7088,7 @@ class ElementNode extends LexicalNode {
7055
7088
 
7056
7089
  if (nodeToInsert.__key === writableSelfKey) {
7057
7090
  {
7058
- throw Error(`append: attemtping to append self`);
7091
+ throw Error(`append: attempting to append self`);
7059
7092
  }
7060
7093
  }
7061
7094
 
@@ -7846,6 +7879,10 @@ class TextNode extends LexicalNode {
7846
7879
  conversion: convertBringAttentionToElement,
7847
7880
  priority: 0
7848
7881
  }),
7882
+ code: node => ({
7883
+ conversion: convertTextFormatElement,
7884
+ priority: 0
7885
+ }),
7849
7886
  em: node => ({
7850
7887
  conversion: convertTextFormatElement,
7851
7888
  priority: 0
@@ -8205,11 +8242,7 @@ function convertSpanElement(domNode) {
8205
8242
 
8206
8243
  const hasUnderlineTextDecoration = span.style.textDecoration === 'underline'; // Google Docs uses span tags + vertical-align to specify subscript and superscript
8207
8244
 
8208
- const verticalAlign = span.style.verticalAlign; // Google Docs uses span tags + color, background-color for coloring
8209
-
8210
- const backgroundColor = span.style.backgroundColor;
8211
- const textColor = span.style.color; //TODO: font-size and coloring of subscript & superscript
8212
-
8245
+ const verticalAlign = span.style.verticalAlign;
8213
8246
  return {
8214
8247
  forChild: lexicalNode => {
8215
8248
  if (!$isTextNode(lexicalNode)) {
@@ -8240,20 +8273,6 @@ function convertSpanElement(domNode) {
8240
8273
  lexicalNode.toggleFormat('superscript');
8241
8274
  }
8242
8275
 
8243
- let cssString = '';
8244
-
8245
- if (textColor && textColor !== 'rgb(0, 0, 0)') {
8246
- cssString += `color: ${textColor};`;
8247
- }
8248
-
8249
- if (backgroundColor && backgroundColor !== 'transparent') {
8250
- cssString += `background-color: ${backgroundColor};`;
8251
- }
8252
-
8253
- if (cssString !== '') {
8254
- lexicalNode.setStyle(cssString);
8255
- }
8256
-
8257
8276
  return lexicalNode;
8258
8277
  },
8259
8278
  node: null
@@ -8297,6 +8316,7 @@ function convertTextDOMNode(domNode) {
8297
8316
  }
8298
8317
 
8299
8318
  const nodeNameToTextFormat = {
8319
+ code: 'code',
8300
8320
  em: 'italic',
8301
8321
  i: 'italic',
8302
8322
  strong: 'bold',
@@ -8641,7 +8661,7 @@ class LexicalEditor {
8641
8661
  this._nodes = nodes; // React node decorators for portals
8642
8662
 
8643
8663
  this._decorators = {};
8644
- this._pendingDecorators = null; // Used to optimize reconcilation
8664
+ this._pendingDecorators = null; // Used to optimize reconciliation
8645
8665
 
8646
8666
  this._dirtyType = NO_DIRTY_NODES;
8647
8667
  this._cloneNotNeeded = new Set();
@@ -8961,7 +8981,7 @@ class LexicalEditor {
8961
8981
  * LICENSE file in the root directory of this source tree.
8962
8982
  *
8963
8983
  */
8964
- const VERSION = '0.3.7';
8984
+ const VERSION = '0.3.10';
8965
8985
 
8966
8986
  /**
8967
8987
  * Copyright (c) Meta Platforms, Inc. and affiliates.