lexical 0.2.4 → 0.2.5

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
@@ -56,6 +56,7 @@ const IS_ALIGN_RIGHT = 3;
56
56
  const IS_ALIGN_JUSTIFY = 4; // Reconciliation
57
57
 
58
58
  const ZERO_WIDTH_CHAR = '\u200b';
59
+ const DOUBLE_LINE_BREAK = '\n\n';
59
60
  const RTL = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC';
60
61
  const LTR = 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6' + '\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF\u200E\u2C00-\uFB1C' + '\uFE00-\uFE6F\uFEFD-\uFFFF';
61
62
  const RTL_REGEX = new RegExp('^[^' + LTR + ']*[' + RTL + ']');
@@ -181,6 +182,9 @@ function getTextDirection(text) {
181
182
 
182
183
  return null;
183
184
  }
185
+ function $isTokenOrInertOrSegmented(node) {
186
+ return $isTokenOrInert(node) || node.isSegmented();
187
+ }
184
188
  function $isTokenOrInert(node) {
185
189
  return node.isToken() || node.isInert();
186
190
  }
@@ -322,21 +326,24 @@ function $setCompositionKey(compositionKey) {
322
326
  errorOnReadOnly();
323
327
  const editor = getActiveEditor();
324
328
  const previousCompositionKey = editor._compositionKey;
325
- editor._compositionKey = compositionKey;
326
329
 
327
- if (previousCompositionKey !== null) {
328
- const node = $getNodeByKey(previousCompositionKey);
330
+ if (compositionKey !== previousCompositionKey) {
331
+ editor._compositionKey = compositionKey;
329
332
 
330
- if (node !== null) {
331
- node.getWritable();
333
+ if (previousCompositionKey !== null) {
334
+ const node = $getNodeByKey(previousCompositionKey);
335
+
336
+ if (node !== null) {
337
+ node.getWritable();
338
+ }
332
339
  }
333
- }
334
340
 
335
- if (compositionKey !== null) {
336
- const node = $getNodeByKey(compositionKey);
341
+ if (compositionKey !== null) {
342
+ const node = $getNodeByKey(compositionKey);
337
343
 
338
- if (node !== null) {
339
- node.getWritable();
344
+ if (node !== null) {
345
+ node.getWritable();
346
+ }
340
347
  }
341
348
  }
342
349
  }
@@ -543,7 +550,9 @@ function $updateTextNodeFromDOMContent(textNode, textContent, anchorOffset, focu
543
550
  const editor = getActiveEditor();
544
551
  setTimeout(() => {
545
552
  editor.update(() => {
546
- node.remove();
553
+ if (node.isAttached()) {
554
+ node.remove();
555
+ }
547
556
  });
548
557
  }, 20);
549
558
  } else {
@@ -852,6 +861,9 @@ function isFirefoxClipboardEvents() {
852
861
  function dispatchCommand(editor, type, payload) {
853
862
  return triggerCommandListeners(editor, type, payload);
854
863
  }
864
+ function $textContentRequiresDoubleLinebreakAtEnd(node) {
865
+ return !$isRootNode(node) && !node.isLastChild() && !node.isInline();
866
+ }
855
867
 
856
868
  /**
857
869
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -1091,6 +1103,7 @@ function internalCreateNodeFromParse(parsedNode, parsedNodeMap, editor, parentKe
1091
1103
  node.__style = parsedNode.__style;
1092
1104
  node.__mode = parsedNode.__mode;
1093
1105
  node.__detail = parsedNode.__detail;
1106
+ node.__marks = parsedNode.__marks;
1094
1107
  } // The selection might refer to an old node whose key has changed. Produce a
1095
1108
  // new selection record with the old keys mapped to the new ones.
1096
1109
 
@@ -1141,8 +1154,8 @@ function internalCreateNodeFromParse(parsedNode, parsedNodeMap, editor, parentKe
1141
1154
  }
1142
1155
  } else if (originalSelection.type === 'grid') {
1143
1156
  const gridKey = originalSelection.gridKey;
1144
- const anchorCellKey = originalSelection.anchorCellKey;
1145
- const focusCellKey = originalSelection.focusCellKey;
1157
+ const anchorCellKey = originalSelection.anchor.key;
1158
+ const focusCellKey = originalSelection.focus.key;
1146
1159
 
1147
1160
  if (remappedSelection == null && (gridKey === parsedKey || gridKey === anchorCellKey || gridKey === focusCellKey)) {
1148
1161
  state.remappedSelection = remappedSelection = { ...originalSelection,
@@ -1156,11 +1169,11 @@ function internalCreateNodeFromParse(parsedNode, parsedNodeMap, editor, parentKe
1156
1169
  }
1157
1170
 
1158
1171
  if (anchorCellKey === parsedKey) {
1159
- remappedSelection.anchorCellKey = key;
1172
+ remappedSelection.anchor.key = key;
1160
1173
  }
1161
1174
 
1162
1175
  if (focusCellKey === parsedKey) {
1163
- remappedSelection.focusCellKey = key;
1176
+ remappedSelection.focus.key = key;
1164
1177
  }
1165
1178
  }
1166
1179
  }
@@ -1208,6 +1221,7 @@ const OUTDENT_CONTENT_COMMAND = createCommand();
1208
1221
  const DROP_COMMAND = createCommand();
1209
1222
  const FORMAT_ELEMENT_COMMAND = createCommand();
1210
1223
  const DRAGSTART_COMMAND = createCommand();
1224
+ const DRAGEND_COMMAND = createCommand();
1211
1225
  const COPY_COMMAND = createCommand();
1212
1226
  const CUT_COMMAND = createCommand();
1213
1227
  const CLEAR_EDITOR_COMMAND = createCommand();
@@ -1245,20 +1259,31 @@ if (CAN_USE_BEFORE_INPUT) {
1245
1259
  let lastKeyDownTimeStamp = 0;
1246
1260
  let rootElementsRegistered = 0;
1247
1261
  let isSelectionChangeFromReconcile = false;
1262
+ let isInsertLineBreak = false;
1263
+ let collapsedSelectionFormat = [0, 0, 'root', 0];
1264
+
1265
+ function shouldSkipSelectionChange(domNode, offset) {
1266
+ return domNode !== null && domNode.nodeType === DOM_TEXT_TYPE && offset !== 0 && offset !== domNode.nodeValue.length;
1267
+ }
1248
1268
 
1249
1269
  function onSelectionChange(domSelection, editor, isActive) {
1250
1270
  if (isSelectionChangeFromReconcile) {
1251
1271
  isSelectionChangeFromReconcile = false;
1252
1272
  const {
1253
1273
  anchorNode,
1254
- focusNode
1274
+ anchorOffset,
1275
+ focusNode,
1276
+ focusOffset
1255
1277
  } = domSelection; // If native DOM selection is on a DOM element, then
1256
1278
  // we should continue as usual, as Lexical's selection
1257
1279
  // may have normalized to a better child. If the DOM
1258
1280
  // element is a text node, we can safely apply this
1259
1281
  // optimization and skip the selection change entirely.
1282
+ // We also need to check if the offset is at the boundary,
1283
+ // because in this case, we might need to normalize to a
1284
+ // sibling instead.
1260
1285
 
1261
- if (anchorNode !== null && focusNode !== null && anchorNode.nodeType === DOM_TEXT_TYPE && focusNode.nodeType === DOM_TEXT_TYPE) {
1286
+ if (shouldSkipSelectionChange(anchorNode, anchorOffset) && shouldSkipSelectionChange(focusNode, focusOffset)) {
1262
1287
  return;
1263
1288
  }
1264
1289
  }
@@ -1273,19 +1298,45 @@ function onSelectionChange(domSelection, editor, isActive) {
1273
1298
 
1274
1299
  const selection = $getSelection(); // Update the selection format
1275
1300
 
1276
- if ($isRangeSelection(selection) && selection.isCollapsed()) {
1277
- // Badly interpreted range selection when collapsed - #1482
1278
- if (domSelection.type === 'Range') {
1279
- selection.dirty = true;
1280
- }
1281
-
1301
+ if ($isRangeSelection(selection)) {
1282
1302
  const anchor = selection.anchor;
1303
+ const anchorNode = anchor.getNode();
1304
+
1305
+ if (selection.isCollapsed()) {
1306
+ // Badly interpreted range selection when collapsed - #1482
1307
+ if (domSelection.type === 'Range') {
1308
+ selection.dirty = true;
1309
+ } // If we have marked a collapsed selection format, and we're
1310
+ // within the given time range – then attempt to use that format
1311
+ // instead of getting the format from the anchor node.
1312
+
1313
+
1314
+ const currentTimeStamp = window.event.timeStamp;
1315
+ const [lastFormat, lastOffset, lastKey, timeStamp] = collapsedSelectionFormat;
1316
+
1317
+ if (currentTimeStamp < timeStamp + 200 && anchor.offset === lastOffset && anchor.key === lastKey) {
1318
+ selection.format = lastFormat;
1319
+ } else {
1320
+ if (anchor.type === 'text') {
1321
+ selection.format = anchorNode.getFormat();
1322
+ } else if (anchor.type === 'element') {
1323
+ selection.format = 0;
1324
+ }
1325
+ }
1326
+ } else {
1327
+ const focus = selection.focus;
1328
+ const focusNode = focus.getNode();
1329
+ let combinedFormat = 0;
1283
1330
 
1284
- if (anchor.type === 'text') {
1285
- const anchorNode = anchor.getNode();
1286
- selection.format = anchorNode.getFormat();
1287
- } else if (anchor.type === 'element') {
1288
- selection.format = 0;
1331
+ if (anchor.type === 'text') {
1332
+ combinedFormat |= anchorNode.getFormat();
1333
+ }
1334
+
1335
+ if (focus.type === 'text' && !anchorNode.is(focusNode)) {
1336
+ combinedFormat |= focusNode.getFormat();
1337
+ }
1338
+
1339
+ selection.format = combinedFormat;
1289
1340
  }
1290
1341
  }
1291
1342
 
@@ -1306,7 +1357,7 @@ function onClick(event, editor) {
1306
1357
  const anchor = selection.anchor;
1307
1358
 
1308
1359
  if (anchor.type === 'element' && anchor.offset === 0 && selection.isCollapsed() && $getRoot().getChildrenSize() === 1 && anchor.getNode().getTopLevelElementOrThrow().isEmpty()) {
1309
- const lastSelection = editor.getEditorState()._selection;
1360
+ const lastSelection = $getPreviousSelection();
1310
1361
 
1311
1362
  if (lastSelection !== null && selection.is(lastSelection)) {
1312
1363
  getDOMSelection().removeAllRanges();
@@ -1383,12 +1434,19 @@ function onBeforeInput(event, editor) {
1383
1434
  updateEditor(editor, () => {
1384
1435
  const selection = $getSelection();
1385
1436
 
1386
- if (!$isRangeSelection(selection)) {
1387
- return;
1388
- }
1389
-
1390
1437
  if (inputType === 'deleteContentBackward') {
1391
- // Used for Android
1438
+ if (selection === null) {
1439
+ // Use previous selection
1440
+ const prevSelection = $getPreviousSelection();
1441
+
1442
+ if (!$isRangeSelection(prevSelection)) {
1443
+ return;
1444
+ }
1445
+
1446
+ $setSelection(prevSelection.clone());
1447
+ } // Used for Android
1448
+
1449
+
1392
1450
  $setCompositionKey(null);
1393
1451
  event.preventDefault();
1394
1452
  lastKeyDownTimeStamp = 0;
@@ -1402,6 +1460,10 @@ function onBeforeInput(event, editor) {
1402
1460
  return;
1403
1461
  }
1404
1462
 
1463
+ if (!$isRangeSelection(selection)) {
1464
+ return;
1465
+ }
1466
+
1405
1467
  const data = event.data;
1406
1468
 
1407
1469
  if (!selection.dirty && selection.isCollapsed() && !$isRootNode(selection.anchor.getNode())) {
@@ -1417,7 +1479,7 @@ function onBeforeInput(event, editor) {
1417
1479
  if (data === '\n') {
1418
1480
  event.preventDefault();
1419
1481
  dispatchCommand(editor, INSERT_LINE_BREAK_COMMAND);
1420
- } else if (data === '\n\n') {
1482
+ } else if (data === DOUBLE_LINE_BREAK) {
1421
1483
  event.preventDefault();
1422
1484
  dispatchCommand(editor, INSERT_PARAGRAPH_COMMAND);
1423
1485
  } else if (data == null && event.dataTransfer) {
@@ -1466,8 +1528,16 @@ function onBeforeInput(event, editor) {
1466
1528
  case 'insertParagraph':
1467
1529
  {
1468
1530
  // Used for Android
1469
- $setCompositionKey(null);
1470
- dispatchCommand(editor, INSERT_PARAGRAPH_COMMAND);
1531
+ $setCompositionKey(null); // Some browsers do not provide the type "insertLineBreak".
1532
+ // So instead, we need to infer it from the keyboard event.
1533
+
1534
+ if (isInsertLineBreak) {
1535
+ isInsertLineBreak = false;
1536
+ dispatchCommand(editor, INSERT_LINE_BREAK_COMMAND);
1537
+ } else {
1538
+ dispatchCommand(editor, INSERT_PARAGRAPH_COMMAND);
1539
+ }
1540
+
1471
1541
  break;
1472
1542
  }
1473
1543
 
@@ -1601,7 +1671,9 @@ function onCompositionStart(event, editor) {
1601
1671
 
1602
1672
  if ( // If it has been 30ms since the last keydown, then we should
1603
1673
  // apply the empty space heuristic.
1604
- event.timeStamp < lastKeyDownTimeStamp + ANDROID_COMPOSITION_LATENCY || anchor.type === 'element' || !selection.isCollapsed() || selection.anchor.getNode().getFormat() !== selection.format) {
1674
+ event.timeStamp < lastKeyDownTimeStamp + ANDROID_COMPOSITION_LATENCY || // FF has issues around composing multibyte characters, so we also
1675
+ // need to invoke the empty space heuristic below.
1676
+ IS_FIREFOX && anchor.type === 'element' || !selection.isCollapsed() || selection.anchor.getNode().getFormat() !== selection.format) {
1605
1677
  // We insert an empty space, ready for the composition
1606
1678
  // to get inserted into the new node we create. If
1607
1679
  // we don't do this, Safari will fail on us because
@@ -1672,11 +1744,14 @@ function onKeyDown(event, editor) {
1672
1744
  } else if (isMoveDown(keyCode, ctrlKey, shiftKey, altKey, metaKey)) {
1673
1745
  dispatchCommand(editor, KEY_ARROW_DOWN_COMMAND, event);
1674
1746
  } else if (isLineBreak(keyCode, shiftKey)) {
1747
+ isInsertLineBreak = true;
1675
1748
  dispatchCommand(editor, KEY_ENTER_COMMAND, event);
1676
1749
  } else if (isOpenLineBreak(keyCode, ctrlKey)) {
1677
1750
  event.preventDefault();
1751
+ isInsertLineBreak = true;
1678
1752
  dispatchCommand(editor, INSERT_LINE_BREAK_COMMAND, true);
1679
1753
  } else if (isParagraph(keyCode, shiftKey)) {
1754
+ isInsertLineBreak = false;
1680
1755
  dispatchCommand(editor, KEY_ENTER_COMMAND, event);
1681
1756
  } else if (isDeleteBackward(keyCode, altKey, metaKey, ctrlKey)) {
1682
1757
  if (isBackspace(keyCode)) {
@@ -1811,6 +1886,9 @@ function addRootElementEvents(rootElement, editor) {
1811
1886
  case 'dragstart':
1812
1887
  return dispatchCommand(editor, DRAGSTART_COMMAND, event);
1813
1888
 
1889
+ case 'dragend':
1890
+ return dispatchCommand(editor, DRAGEND_COMMAND, event);
1891
+
1814
1892
  case 'focus':
1815
1893
  return dispatchCommand(editor, FOCUS_COMMAND, event);
1816
1894
 
@@ -1877,6 +1955,9 @@ function cleanActiveNestedEditorsMap(editor) {
1877
1955
  function markSelectionChangeFromReconcile() {
1878
1956
  isSelectionChangeFromReconcile = true;
1879
1957
  }
1958
+ function markCollapsedSelectionFormat(format, offset, key, timeStamp) {
1959
+ collapsedSelectionFormat = [format, offset, key, timeStamp];
1960
+ }
1880
1961
 
1881
1962
  /**
1882
1963
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -1944,7 +2025,7 @@ function setTextAlign(domStyle, value) {
1944
2025
  }
1945
2026
 
1946
2027
  function setElementIndent(dom, indent) {
1947
- dom.style.setProperty('padding-inline-start', indent === 0 ? '' : indent * 40 + 'px');
2028
+ dom.style.setProperty('padding-inline-start', indent === 0 ? '' : indent * 20 + 'px');
1948
2029
  }
1949
2030
 
1950
2031
  function setElementFormat(dom, format) {
@@ -2005,6 +2086,11 @@ function createNode(key, parentDOM, insertDOM) {
2005
2086
  }
2006
2087
 
2007
2088
  reconcileElementTerminatingLineBreak(null, children, dom);
2089
+
2090
+ if ($textContentRequiresDoubleLinebreakAtEnd(node)) {
2091
+ subTreeTextContent += DOUBLE_LINE_BREAK;
2092
+ editorTextContent += DOUBLE_LINE_BREAK;
2093
+ }
2008
2094
  } else {
2009
2095
  const text = node.getTextContent();
2010
2096
 
@@ -2176,12 +2262,12 @@ function reconcileBlockDirection(element, dom) {
2176
2262
  function reconcileChildrenWithDirection(prevChildren, nextChildren, element, dom) {
2177
2263
  const previousSubTreeDirectionTextContent = subTreeDirectionedTextContent;
2178
2264
  subTreeDirectionedTextContent = '';
2179
- reconcileChildren(prevChildren, nextChildren, dom);
2265
+ reconcileChildren(element, prevChildren, nextChildren, dom);
2180
2266
  reconcileBlockDirection(element, dom);
2181
2267
  subTreeDirectionedTextContent = previousSubTreeDirectionTextContent;
2182
2268
  }
2183
2269
 
2184
- function reconcileChildren(prevChildren, nextChildren, dom) {
2270
+ function reconcileChildren(element, prevChildren, nextChildren, dom) {
2185
2271
  const previousSubTreeTextContent = subTreeTextContent;
2186
2272
  subTreeTextContent = '';
2187
2273
  const prevChildrenLength = prevChildren.length;
@@ -2216,7 +2302,11 @@ function reconcileChildren(prevChildren, nextChildren, dom) {
2216
2302
  }
2217
2303
  }
2218
2304
  } else {
2219
- reconcileNodeChildren(prevChildren, nextChildren, prevChildrenLength, nextChildrenLength, dom);
2305
+ reconcileNodeChildren(prevChildren, nextChildren, prevChildrenLength, nextChildrenLength, element, dom);
2306
+ }
2307
+
2308
+ if ($textContentRequiresDoubleLinebreakAtEnd(element)) {
2309
+ subTreeTextContent += DOUBLE_LINE_BREAK;
2220
2310
  } // $FlowFixMe: internal field
2221
2311
 
2222
2312
 
@@ -2311,6 +2401,11 @@ function reconcileNode(key, parentDOM) {
2311
2401
  reconcileElementTerminatingLineBreak(prevChildren, nextChildren, dom);
2312
2402
  }
2313
2403
  }
2404
+
2405
+ if ($textContentRequiresDoubleLinebreakAtEnd(nextNode)) {
2406
+ subTreeTextContent += DOUBLE_LINE_BREAK;
2407
+ editorTextContent += DOUBLE_LINE_BREAK;
2408
+ }
2314
2409
  } else {
2315
2410
  const text = nextNode.getTextContent();
2316
2411
 
@@ -2320,6 +2415,9 @@ function reconcileNode(key, parentDOM) {
2320
2415
  if (decorator !== null) {
2321
2416
  reconcileDecorator(key, decorator);
2322
2417
  }
2418
+
2419
+ subTreeTextContent += text;
2420
+ editorTextContent += text;
2323
2421
  } else if ($isTextNode(nextNode) && !nextNode.isDirectionless()) {
2324
2422
  // Handle text content, for LTR, LTR cases.
2325
2423
  subTreeDirectionedTextContent += text;
@@ -2368,7 +2466,7 @@ function getNextSibling(element) {
2368
2466
  return element.nextSibling;
2369
2467
  }
2370
2468
 
2371
- function reconcileNodeChildren(prevChildren, nextChildren, prevChildrenLength, nextChildrenLength, dom) {
2469
+ function reconcileNodeChildren(prevChildren, nextChildren, prevChildrenLength, nextChildrenLength, element, dom) {
2372
2470
  const prevEndIndex = prevChildrenLength - 1;
2373
2471
  const nextEndIndex = nextChildrenLength - 1;
2374
2472
  let prevChildrenSet;
@@ -2582,11 +2680,15 @@ function reconcileSelection(prevSelection, nextSelection, editor, domSelection)
2582
2680
  const focusDOM = getElementByKeyOrThrow(editor, focusKey);
2583
2681
  const nextAnchorOffset = anchor.offset;
2584
2682
  const nextFocusOffset = focus.offset;
2683
+ const nextFormat = nextSelection.format;
2684
+ const isCollapsed = nextSelection.isCollapsed();
2585
2685
  let nextAnchorNode = anchorDOM;
2586
2686
  let nextFocusNode = focusDOM;
2687
+ let anchorFormatChanged = false;
2587
2688
 
2588
2689
  if (anchor.type === 'text') {
2589
2690
  nextAnchorNode = getDOMTextNode(anchorDOM);
2691
+ anchorFormatChanged = anchor.getNode().getFormat() !== nextFormat;
2590
2692
  }
2591
2693
 
2592
2694
  if (focus.type === 'text') {
@@ -2597,20 +2699,31 @@ function reconcileSelection(prevSelection, nextSelection, editor, domSelection)
2597
2699
 
2598
2700
  if (nextAnchorNode === null || nextFocusNode === null) {
2599
2701
  return;
2702
+ }
2703
+
2704
+ if (isCollapsed && (prevSelection === null || anchorFormatChanged || prevSelection.format !== nextFormat)) {
2705
+ markCollapsedSelectionFormat(nextFormat, nextAnchorOffset, anchorKey, performance.now());
2600
2706
  } // Diff against the native DOM selection to ensure we don't do
2601
- // an unnecessary selection update.
2707
+ // an unnecessary selection update. We also skip this check if
2708
+ // we're moving selection to within an element, as this can
2709
+ // sometimes be problematic around scrolling.
2602
2710
 
2603
2711
 
2604
2712
  if (anchorOffset === nextAnchorOffset && focusOffset === nextFocusOffset && anchorDOMNode === nextAnchorNode && focusDOMNode === nextFocusNode && // Badly interpreted range selection when collapsed - #1482
2605
- !(domSelection.type === 'Range' && nextSelection.isCollapsed())) {
2713
+ !(domSelection.type === 'Range' && isCollapsed)) {
2606
2714
  // If the root element does not have focus, ensure it has focus
2607
2715
  if (rootElement !== null && (activeElement === null || !rootElement.contains(activeElement))) {
2608
2716
  rootElement.focus({
2609
2717
  preventScroll: true
2610
2718
  });
2611
- }
2719
+ } // In Safari/iOS if we have selection on an element, then we also
2720
+ // need to additionally set the DOM selection, otherwise a selectionchange
2721
+ // event will not fire.
2612
2722
 
2613
- return;
2723
+
2724
+ if (!(IS_IOS || IS_SAFARI) || anchor.type !== 'element') {
2725
+ return;
2726
+ }
2614
2727
  } // Apply the updated selection to the DOM. Note: this will trigger
2615
2728
  // a "selectionchange" event, although it will be asynchronous.
2616
2729
 
@@ -2741,7 +2854,7 @@ function $normalizeAllDirtyTextNodes(editorState, editor) {
2741
2854
  for (const nodeKey of dirtyLeaves) {
2742
2855
  const node = nodeMap.get(nodeKey);
2743
2856
 
2744
- if ($isTextNode(node) && node.isSimpleText() && !node.isUnmergeable()) {
2857
+ if ($isTextNode(node) && node.isAttached() && node.isSimpleText() && !node.isUnmergeable()) {
2745
2858
  $normalizeTextNode(node);
2746
2859
  }
2747
2860
  }
@@ -2777,7 +2890,7 @@ function $applyAllTransforms(editorState, editor) {
2777
2890
  for (const nodeKey of untransformedDirtyLeaves) {
2778
2891
  const node = nodeMap.get(nodeKey);
2779
2892
 
2780
- if ($isTextNode(node) && node.isSimpleText() && !node.isUnmergeable()) {
2893
+ if ($isTextNode(node) && node.isAttached() && node.isSimpleText() && !node.isUnmergeable()) {
2781
2894
  $normalizeTextNode(node);
2782
2895
  }
2783
2896
 
@@ -3095,9 +3208,9 @@ function triggerDeferredUpdateCallbacks(editor) {
3095
3208
  }
3096
3209
  }
3097
3210
 
3098
- function processNestedUpdates(editor) {
3211
+ function processNestedUpdates(editor, initialSkipTransforms) {
3099
3212
  const queuedUpdates = editor._updates;
3100
- let skipTransforms = false; // Updates might grow as we process them, we so we'll need
3213
+ let skipTransforms = initialSkipTransforms || false; // Updates might grow as we process them, we so we'll need
3101
3214
  // to handle each update as we go until the updates array is
3102
3215
  // empty.
3103
3216
 
@@ -3175,7 +3288,7 @@ function beginUpdate(editor, updateFn, options) {
3175
3288
 
3176
3289
  const startingCompositionKey = editor._compositionKey;
3177
3290
  updateFn();
3178
- skipTransforms = processNestedUpdates(editor);
3291
+ skipTransforms = processNestedUpdates(editor, skipTransforms);
3179
3292
  applySelectionTransforms(pendingEditorState, editor);
3180
3293
 
3181
3294
  if (editor._dirtyType !== NO_DIRTY_NODES) {
@@ -3536,10 +3649,6 @@ class Point {
3536
3649
  return aNode.isBefore(bNode);
3537
3650
  }
3538
3651
 
3539
- getCharacterOffset() {
3540
- return this.type === 'text' ? this.offset : 0;
3541
- }
3542
-
3543
3652
  getNode() {
3544
3653
  const key = this.key;
3545
3654
  const node = $getNodeByKey(key);
@@ -3721,12 +3830,10 @@ function $isRangeSelection(x) {
3721
3830
  return x instanceof RangeSelection;
3722
3831
  }
3723
3832
  class GridSelection {
3724
- constructor(gridKey, anchorCellKey, focusCellKey) {
3833
+ constructor(gridKey, anchor, focus) {
3725
3834
  this.gridKey = gridKey;
3726
- this.anchorCellKey = anchorCellKey;
3727
- this.anchor = $createPoint(anchorCellKey, 0, 'element');
3728
- this.focusCellKey = focusCellKey;
3729
- this.focus = $createPoint(focusCellKey, 0, 'element');
3835
+ this.anchor = anchor;
3836
+ this.focus = focus;
3730
3837
  this.dirty = false;
3731
3838
  }
3732
3839
 
@@ -3735,18 +3842,18 @@ class GridSelection {
3735
3842
  return false;
3736
3843
  }
3737
3844
 
3738
- return this.gridKey === selection.gridKey && this.anchorCellKey === selection.anchorCellKey && this.focusCellKey === selection.focusCellKey;
3845
+ return this.gridKey === selection.gridKey && this.anchor.is(this.focus);
3739
3846
  }
3740
3847
 
3741
3848
  set(gridKey, anchorCellKey, focusCellKey) {
3742
3849
  this.dirty = true;
3743
3850
  this.gridKey = gridKey;
3744
- this.anchorCellKey = anchorCellKey;
3745
- this.focusCellKey = focusCellKey;
3851
+ this.anchor.key = anchorCellKey;
3852
+ this.focus.key = focusCellKey;
3746
3853
  }
3747
3854
 
3748
3855
  clone() {
3749
- return new GridSelection(this.gridKey, this.anchorCellKey, this.focusCellKey);
3856
+ return new GridSelection(this.gridKey, this.anchor, this.focus);
3750
3857
  }
3751
3858
 
3752
3859
  isCollapsed() {
@@ -3757,6 +3864,10 @@ class GridSelection {
3757
3864
  return this.focus.isBefore(this.anchor);
3758
3865
  }
3759
3866
 
3867
+ getCharacterOffsets() {
3868
+ return getCharacterOffsets(this);
3869
+ }
3870
+
3760
3871
  extract() {
3761
3872
  return this.getNodes();
3762
3873
  }
@@ -3768,7 +3879,7 @@ class GridSelection {
3768
3879
  }
3769
3880
 
3770
3881
  getShape() {
3771
- const anchorCellNode = $getNodeByKey(this.anchorCellKey);
3882
+ const anchorCellNode = $getNodeByKey(this.anchor.key);
3772
3883
 
3773
3884
  if (!anchorCellNode) {
3774
3885
  throw Error(`getNodes: expected to find AnchorNode`);
@@ -3776,7 +3887,7 @@ class GridSelection {
3776
3887
 
3777
3888
  const anchorCellNodeIndex = anchorCellNode.getIndexWithinParent();
3778
3889
  const anchorCelRoweIndex = anchorCellNode.getParentOrThrow().getIndexWithinParent();
3779
- const focusCellNode = $getNodeByKey(this.focusCellKey);
3890
+ const focusCellNode = $getNodeByKey(this.focus.key);
3780
3891
 
3781
3892
  if (!focusCellNode) {
3782
3893
  throw Error(`getNodes: expected to find FocusNode`);
@@ -3907,7 +4018,7 @@ class RangeSelection {
3907
4018
  }
3908
4019
 
3909
4020
  if (firstNode.is(lastNode)) {
3910
- if ($isElementNode(firstNode)) {
4021
+ if ($isElementNode(firstNode) && (firstNode.getChildrenSize() > 0 || firstNode.excludeFromCopy())) {
3911
4022
  return [];
3912
4023
  }
3913
4024
 
@@ -3935,8 +4046,7 @@ class RangeSelection {
3935
4046
  const anchor = this.anchor;
3936
4047
  const focus = this.focus;
3937
4048
  const isBefore = anchor.isBefore(focus);
3938
- const anchorOffset = anchor.getCharacterOffset();
3939
- const focusOffset = focus.getCharacterOffset();
4049
+ const [anchorOffset, focusOffset] = getCharacterOffsets(this);
3940
4050
  let textContent = '';
3941
4051
  let prevWasElement = true;
3942
4052
 
@@ -4064,11 +4174,13 @@ class RangeSelection {
4064
4174
  const firstNodeText = firstNode.getTextContent();
4065
4175
  const firstNodeTextLength = firstNodeText.length;
4066
4176
  const firstNodeParent = firstNode.getParentOrThrow();
4177
+ const lastIndex = selectedNodesLength - 1;
4178
+ let lastNode = selectedNodes[lastIndex];
4067
4179
 
4068
4180
  if (this.isCollapsed() && startOffset === firstNodeTextLength && (firstNode.isSegmented() || firstNode.isToken() || !firstNode.canInsertTextAfter() || !firstNodeParent.canInsertTextAfter())) {
4069
4181
  let nextSibling = firstNode.getNextSibling();
4070
4182
 
4071
- if (!$isTextNode(nextSibling) || $isTokenOrInert(nextSibling) || nextSibling.isSegmented()) {
4183
+ if (!$isTextNode(nextSibling) || $isTokenOrInertOrSegmented(nextSibling)) {
4072
4184
  nextSibling = $createTextNode();
4073
4185
 
4074
4186
  if (!firstNodeParent.canInsertTextAfter()) {
@@ -4088,7 +4200,7 @@ class RangeSelection {
4088
4200
  } else if (this.isCollapsed() && startOffset === 0 && (firstNode.isSegmented() || firstNode.isToken() || !firstNode.canInsertTextBefore() || !firstNodeParent.canInsertTextBefore())) {
4089
4201
  let prevSibling = firstNode.getPreviousSibling();
4090
4202
 
4091
- if (!$isTextNode(prevSibling) || $isTokenOrInert(prevSibling) || prevSibling.isSegmented()) {
4203
+ if (!$isTextNode(prevSibling) || $isTokenOrInertOrSegmented(prevSibling)) {
4092
4204
  prevSibling = $createTextNode();
4093
4205
 
4094
4206
  if (!firstNodeParent.canInsertTextBefore()) {
@@ -4109,6 +4221,19 @@ class RangeSelection {
4109
4221
  const textNode = $createTextNode(firstNode.getTextContent());
4110
4222
  firstNode.replace(textNode);
4111
4223
  firstNode = textNode;
4224
+ } else if (!this.isCollapsed() && text !== '') {
4225
+ // When the firstNode or lastNode parents are elements that
4226
+ // do not allow text to be inserted before or after, we first
4227
+ // clear the content. Then we normalize selection, then insert
4228
+ // the new content.
4229
+ const lastNodeParent = lastNode.getParent();
4230
+
4231
+ if (!firstNodeParent.canInsertTextBefore() || !firstNodeParent.canInsertTextAfter() || $isElementNode(lastNodeParent) && (!lastNodeParent.canInsertTextBefore() || !lastNodeParent.canInsertTextAfter())) {
4232
+ this.insertText('');
4233
+ normalizeSelectionPointsForBoundaries(this.anchor, this.focus, null);
4234
+ this.insertText(text);
4235
+ return;
4236
+ }
4112
4237
  }
4113
4238
 
4114
4239
  if (selectedNodesLength === 1) {
@@ -4157,8 +4282,6 @@ class RangeSelection {
4157
4282
  this.anchor.offset -= text.length;
4158
4283
  }
4159
4284
  } else {
4160
- const lastIndex = selectedNodesLength - 1;
4161
- let lastNode = selectedNodes[lastIndex];
4162
4285
  const markedNodeKeysForKeep = new Set([...firstNode.getParentKeys(), ...lastNode.getParentKeys()]);
4163
4286
  const firstElement = $isElementNode(firstNode) ? firstNode : firstNode.getParentOrThrow();
4164
4287
  const lastElement = $isElementNode(lastNode) ? lastNode : lastNode.getParentOrThrow(); // Handle mutations to the last node.
@@ -4174,7 +4297,13 @@ class RangeSelection {
4174
4297
  lastNode = lastNode.spliceText(0, endOffset, '');
4175
4298
  markedNodeKeysForKeep.add(lastNode.__key);
4176
4299
  } else {
4177
- lastNode.remove();
4300
+ const lastNodeParent = lastNode.getParentOrThrow();
4301
+
4302
+ if (!lastNodeParent.canBeEmpty() && lastNodeParent.getChildrenSize() === 1) {
4303
+ lastNodeParent.remove();
4304
+ } else {
4305
+ lastNode.remove();
4306
+ }
4178
4307
  }
4179
4308
  } else {
4180
4309
  markedNodeKeysForKeep.add(lastNode.__key);
@@ -4283,10 +4412,9 @@ class RangeSelection {
4283
4412
 
4284
4413
  const anchor = this.anchor;
4285
4414
  const focus = this.focus;
4286
- const firstNodeText = firstNode.getTextContent();
4287
- const firstNodeTextLength = firstNodeText.length;
4288
4415
  const focusOffset = focus.offset;
4289
4416
  let firstNextFormat = 0;
4417
+ let firstNodeTextLength = firstNode.getTextContent().length;
4290
4418
 
4291
4419
  for (let i = 0; i < selectedNodes.length; i++) {
4292
4420
  const selectedNode = selectedNodes[i];
@@ -4313,6 +4441,7 @@ class RangeSelection {
4313
4441
  anchorOffset = 0;
4314
4442
  startOffset = 0;
4315
4443
  firstNode = nextSibling;
4444
+ firstNodeTextLength = nextSibling.getTextContent().length;
4316
4445
  firstNextFormat = firstNode.getFormatFlags(formatType, null);
4317
4446
  }
4318
4447
  } // This is the case where we only selected a single node
@@ -4812,6 +4941,10 @@ class RangeSelection {
4812
4941
  }
4813
4942
  }
4814
4943
 
4944
+ getCharacterOffsets() {
4945
+ return getCharacterOffsets(this);
4946
+ }
4947
+
4815
4948
  extract() {
4816
4949
  const selectedNodes = this.getNodes();
4817
4950
  const selectedNodesLength = selectedNodes.length;
@@ -4820,8 +4953,7 @@ class RangeSelection {
4820
4953
  const focus = this.focus;
4821
4954
  let firstNode = selectedNodes[0];
4822
4955
  let lastNode = selectedNodes[lastIndex];
4823
- const anchorOffset = anchor.getCharacterOffset();
4824
- const focusOffset = focus.getCharacterOffset();
4956
+ const [anchorOffset, focusOffset] = getCharacterOffsets(this);
4825
4957
 
4826
4958
  if (selectedNodesLength === 0) {
4827
4959
  return [];
@@ -4899,6 +5031,16 @@ class RangeSelection {
4899
5031
  anchor.set(elementKey, offset, 'element');
4900
5032
  }
4901
5033
 
5034
+ return;
5035
+ } else {
5036
+ const siblingKey = sibling.__key;
5037
+ const offset = isBackward ? sibling.getTextContent().length : 0;
5038
+ focus.set(siblingKey, offset, 'text');
5039
+
5040
+ if (collapse) {
5041
+ anchor.set(siblingKey, offset, 'text');
5042
+ }
5043
+
4902
5044
  return;
4903
5045
  }
4904
5046
  }
@@ -5001,6 +5143,29 @@ function $isNodeSelection(x) {
5001
5143
  return x instanceof NodeSelection;
5002
5144
  }
5003
5145
 
5146
+ function getCharacterOffset(point) {
5147
+ const offset = point.offset;
5148
+
5149
+ if (point.type === 'text') {
5150
+ return offset;
5151
+ } // $FlowFixMe: cast
5152
+
5153
+
5154
+ const parent = point.getNode();
5155
+ return offset === parent.getChildrenSize() ? parent.getTextContent().length : 0;
5156
+ }
5157
+
5158
+ function getCharacterOffsets(selection) {
5159
+ const anchor = selection.anchor;
5160
+ const focus = selection.focus;
5161
+
5162
+ if (anchor.type === 'element' && focus.type === 'element' && anchor.key === focus.key && anchor.offset === focus.offset) {
5163
+ return [0, 0];
5164
+ }
5165
+
5166
+ return [getCharacterOffset(anchor), getCharacterOffset(focus)];
5167
+ }
5168
+
5004
5169
  function $swapPoints(selection) {
5005
5170
  const focus = selection.focus;
5006
5171
  const anchor = selection.anchor;
@@ -5169,6 +5334,78 @@ function internalResolveSelectionPoint(dom, offset, lastPoint) {
5169
5334
  return $createPoint(resolvedNode.__key, resolvedOffset, 'text');
5170
5335
  }
5171
5336
 
5337
+ function resolveSelectionPointOnBoundary(point, isBackward, isCollapsed) {
5338
+ const offset = point.offset;
5339
+ const node = point.getNode();
5340
+
5341
+ if (offset === 0) {
5342
+ const prevSibling = node.getPreviousSibling();
5343
+ const parent = node.getParent();
5344
+
5345
+ if (!isBackward) {
5346
+ if ($isElementNode(prevSibling) && !isCollapsed && prevSibling.isInline()) {
5347
+ point.key = prevSibling.__key;
5348
+ point.offset = prevSibling.getChildrenSize(); // $FlowFixMe: intentional
5349
+
5350
+ point.type = 'element';
5351
+ } else if ($isTextNode(prevSibling) && !prevSibling.isInert()) {
5352
+ point.key = prevSibling.__key;
5353
+ point.offset = prevSibling.getTextContent().length;
5354
+ }
5355
+ } else if ((isCollapsed || !isBackward) && prevSibling === null && $isElementNode(parent) && parent.isInline()) {
5356
+ const parentSibling = parent.getPreviousSibling();
5357
+
5358
+ if ($isTextNode(parentSibling)) {
5359
+ point.key = parentSibling.__key;
5360
+ point.offset = parentSibling.getTextContent().length;
5361
+ }
5362
+ }
5363
+ } else if (offset === node.getTextContent().length) {
5364
+ const nextSibling = node.getNextSibling();
5365
+ const parent = node.getParent();
5366
+
5367
+ if (isBackward && $isElementNode(nextSibling) && nextSibling.isInline()) {
5368
+ point.key = nextSibling.__key;
5369
+ point.offset = 0; // $FlowFixMe: intentional
5370
+
5371
+ point.type = 'element';
5372
+ } else if ((isCollapsed || isBackward) && nextSibling === null && $isElementNode(parent) && parent.isInline()) {
5373
+ const parentSibling = parent.getNextSibling();
5374
+
5375
+ if ($isTextNode(parentSibling)) {
5376
+ point.key = parentSibling.__key;
5377
+ point.offset = 0;
5378
+ }
5379
+ }
5380
+ }
5381
+ }
5382
+
5383
+ function normalizeSelectionPointsForBoundaries(anchor, focus, lastSelection) {
5384
+ if (anchor.type === 'text' && focus.type === 'text') {
5385
+ const isBackward = anchor.isBefore(focus);
5386
+ const isCollapsed = anchor.is(focus); // Attempt to normalize the offset to the previous sibling if we're at the
5387
+ // start of a text node and the sibling is a text node or inline element.
5388
+
5389
+ resolveSelectionPointOnBoundary(anchor, isBackward, isCollapsed);
5390
+ resolveSelectionPointOnBoundary(focus, !isBackward, isCollapsed);
5391
+
5392
+ if (isCollapsed) {
5393
+ focus.key = anchor.key;
5394
+ focus.offset = anchor.offset;
5395
+ focus.type = anchor.type;
5396
+ }
5397
+
5398
+ const editor = getActiveEditor();
5399
+
5400
+ if (editor.isComposing() && editor._compositionKey !== anchor.key && $isRangeSelection(lastSelection)) {
5401
+ const lastAnchor = lastSelection.anchor;
5402
+ const lastFocus = lastSelection.focus;
5403
+ $setPointValues(anchor, lastAnchor.key, lastAnchor.offset, lastAnchor.type);
5404
+ $setPointValues(focus, lastFocus.key, lastFocus.offset, lastFocus.type);
5405
+ }
5406
+ }
5407
+ }
5408
+
5172
5409
  function internalResolveSelectionPoints(anchorDOM, anchorOffset, focusDOM, focusOffset, editor, lastSelection) {
5173
5410
  if (anchorDOM === null || focusDOM === null || !isSelectionWithinEditor(editor, anchorDOM, focusDOM)) {
5174
5411
  return null;
@@ -5184,48 +5421,10 @@ function internalResolveSelectionPoints(anchorDOM, anchorOffset, focusDOM, focus
5184
5421
 
5185
5422
  if (resolvedFocusPoint === null) {
5186
5423
  return null;
5187
- }
5188
-
5189
- if (resolvedAnchorPoint.type === 'text' && resolvedFocusPoint.type === 'text') {
5190
- const resolvedAnchorNode = resolvedAnchorPoint.getNode();
5191
- const resolvedFocusNode = resolvedFocusPoint.getNode(); // Handle normalization of selection when it is at the boundaries.
5192
-
5193
- const textContentSize = resolvedAnchorNode.getTextContentSize();
5194
- const resolvedAnchorOffset = resolvedAnchorPoint.offset;
5195
- const resolvedFocusOffset = resolvedFocusPoint.offset;
5196
-
5197
- if (resolvedAnchorNode === resolvedFocusNode && resolvedAnchorOffset === resolvedFocusOffset) {
5198
- if (anchorOffset === 0) {
5199
- const prevSibling = resolvedAnchorNode.getPreviousSibling();
5200
-
5201
- if ($isTextNode(prevSibling) && !prevSibling.isInert()) {
5202
- const offset = prevSibling.getTextContentSize();
5203
- const key = prevSibling.__key;
5204
- resolvedAnchorPoint.key = key;
5205
- resolvedFocusPoint.key = key;
5206
- resolvedAnchorPoint.offset = offset;
5207
- resolvedFocusPoint.offset = offset;
5208
- }
5209
- }
5210
- } else {
5211
- if (resolvedAnchorOffset === textContentSize) {
5212
- const nextSibling = resolvedAnchorNode.getNextSibling();
5424
+ } // Handle normalization of selection when it is at the boundaries.
5213
5425
 
5214
- if ($isTextNode(nextSibling) && !nextSibling.isInert()) {
5215
- resolvedAnchorPoint.key = nextSibling.__key;
5216
- resolvedAnchorPoint.offset = 0;
5217
- }
5218
- }
5219
- }
5220
-
5221
- if (editor.isComposing() && editor._compositionKey !== resolvedAnchorPoint.key && $isRangeSelection(lastSelection)) {
5222
- const lastAnchor = lastSelection.anchor;
5223
- const lastFocus = lastSelection.focus;
5224
- $setPointValues(resolvedAnchorPoint, lastAnchor.key, lastAnchor.offset, lastAnchor.type);
5225
- $setPointValues(resolvedFocusPoint, lastFocus.key, lastFocus.offset, lastFocus.type);
5226
- }
5227
- }
5228
5426
 
5427
+ normalizeSelectionPointsForBoundaries(resolvedAnchorPoint, resolvedFocusPoint, lastSelection);
5229
5428
  return [resolvedAnchorPoint, resolvedFocusPoint];
5230
5429
  } // This is used to make a selection when the existing
5231
5430
  // selection is null, i.e. forcing selection on the editor
@@ -5248,7 +5447,9 @@ function $createEmptyObjectSelection() {
5248
5447
  return new NodeSelection(new Set());
5249
5448
  }
5250
5449
  function $createEmptyGridSelection() {
5251
- return new GridSelection('root', 'root', 'root');
5450
+ const anchor = $createPoint('root', 0, 'element');
5451
+ const focus = $createPoint('root', 0, 'element');
5452
+ return new GridSelection('root', anchor, focus);
5252
5453
  }
5253
5454
 
5254
5455
  function getActiveEventType() {
@@ -5327,7 +5528,7 @@ function internalCreateSelectionFromParse(parsedSelection) {
5327
5528
  } else if (parsedSelection.type === 'node') {
5328
5529
  return new NodeSelection(new Set(parsedSelection.nodes));
5329
5530
  } else if (parsedSelection.type === 'grid') {
5330
- return new GridSelection(parsedSelection.gridKey, parsedSelection.anchorCellKey, parsedSelection.focusCellKey);
5531
+ return new GridSelection(parsedSelection.gridKey, $createPoint(parsedSelection.anchor.key, parsedSelection.anchor.offset, parsedSelection.anchor.type), $createPoint(parsedSelection.focus.key, parsedSelection.focus.offset, parsedSelection.focus.type));
5331
5532
  }
5332
5533
  }
5333
5534
 
@@ -6071,10 +6272,12 @@ class LexicalNode {
6071
6272
  mutableNode.__format = latestNode.__format;
6072
6273
  mutableNode.__dir = latestNode.__dir;
6073
6274
  } else if ($isTextNode(latestNode) && $isTextNode(mutableNode)) {
6275
+ const marks = latestNode.__marks;
6074
6276
  mutableNode.__format = latestNode.__format;
6075
6277
  mutableNode.__style = latestNode.__style;
6076
6278
  mutableNode.__mode = latestNode.__mode;
6077
6279
  mutableNode.__detail = latestNode.__detail;
6280
+ mutableNode.__marks = marks === null ? marks : Array.from(marks);
6078
6281
  }
6079
6282
 
6080
6283
  cloneNotNeeded.add(key);
@@ -6095,23 +6298,20 @@ class LexicalNode {
6095
6298
  getTextContentSize(includeInert, includeDirectionless) {
6096
6299
  return this.getTextContent(includeInert, includeDirectionless).length;
6097
6300
  } // View
6098
- // $FlowFixMe: Revise typings for EditorContext
6099
6301
 
6100
6302
 
6101
6303
  createDOM(config, editor) {
6102
6304
  {
6103
6305
  throw Error(`createDOM: base method not extended`);
6104
6306
  }
6105
- } // $FlowFixMe: Revise typings for EditorContext
6106
-
6307
+ }
6107
6308
 
6108
6309
  updateDOM( // $FlowFixMe: TODO
6109
6310
  prevNode, dom, config) {
6110
6311
  {
6111
6312
  throw Error(`updateDOM: base method not extended`);
6112
6313
  }
6113
- } // $FlowFixMe: Revise typings for EditorContext
6114
-
6314
+ }
6115
6315
 
6116
6316
  exportDOM(editor) {
6117
6317
  if ($isDecoratorNode(this)) {
@@ -6428,6 +6628,12 @@ class ElementNode extends LexicalNode {
6428
6628
  return dirtyElements !== null && dirtyElements.has(this.__key);
6429
6629
  }
6430
6630
 
6631
+ isLastChild() {
6632
+ const self = this.getLatest();
6633
+ const parent = self.getParentOrThrow();
6634
+ return parent.getLastChild() === self;
6635
+ }
6636
+
6431
6637
  getAllTextNodes(includeInert) {
6432
6638
  const textNodes = [];
6433
6639
  const self = this.getLatest();
@@ -6562,7 +6768,7 @@ class ElementNode extends LexicalNode {
6562
6768
  textContent += child.getTextContent(includeInert, includeDirectionless);
6563
6769
 
6564
6770
  if ($isElementNode(child) && i !== childrenLength - 1 && !child.isInline()) {
6565
- textContent += '\n\n';
6771
+ textContent += DOUBLE_LINE_BREAK;
6566
6772
  }
6567
6773
  }
6568
6774
 
@@ -6791,6 +6997,10 @@ class ElementNode extends LexicalNode {
6791
6997
  return false;
6792
6998
  }
6793
6999
 
7000
+ canIndent() {
7001
+ return true;
7002
+ }
7003
+
6794
7004
  collapseAtStart(selection) {
6795
7005
  return false;
6796
7006
  }
@@ -7011,8 +7221,16 @@ class EditorState {
7011
7221
  nodes: Array.from(selection._nodes),
7012
7222
  type: 'node'
7013
7223
  } : $isGridSelection(selection) ? {
7014
- anchorCellKey: selection.anchorCellKey,
7015
- focusCellKey: selection.focusCellKey,
7224
+ anchor: {
7225
+ key: selection.anchor.key,
7226
+ offset: selection.anchor.offset,
7227
+ type: selection.anchor.type
7228
+ },
7229
+ focus: {
7230
+ key: selection.focus.key,
7231
+ offset: selection.focus.offset,
7232
+ type: selection.focus.type
7233
+ },
7016
7234
  gridKey: selection.gridKey,
7017
7235
  type: 'grid'
7018
7236
  } : null
@@ -7078,6 +7296,44 @@ function $isLineBreakNode(node) {
7078
7296
  return node instanceof LineBreakNode;
7079
7297
  }
7080
7298
 
7299
+ /**
7300
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
7301
+ *
7302
+ * This source code is licensed under the MIT license found in the
7303
+ * LICENSE file in the root directory of this source tree.
7304
+ *
7305
+ *
7306
+ */
7307
+ function simpleDiffWithCursor(a, b, cursor) {
7308
+ const aLength = a.length;
7309
+ const bLength = b.length;
7310
+ let left = 0; // number of same characters counting from left
7311
+
7312
+ let right = 0; // number of same characters counting from right
7313
+ // Iterate left to the right until we find a changed character
7314
+ // First iteration considers the current cursor position
7315
+
7316
+ while (left < aLength && left < bLength && a[left] === b[left] && left < cursor) {
7317
+ left++;
7318
+ } // Iterate right to the left until we find a changed character
7319
+
7320
+
7321
+ while (right + left < aLength && right + left < bLength && a[aLength - right - 1] === b[bLength - right - 1]) {
7322
+ right++;
7323
+ } // Try to iterate left further to the right without caring about the current cursor position
7324
+
7325
+
7326
+ while (right + left < aLength && right + left < bLength && a[left] === b[left]) {
7327
+ left++;
7328
+ }
7329
+
7330
+ return {
7331
+ index: left,
7332
+ insert: b.slice(left, bLength - right),
7333
+ remove: aLength - left - right
7334
+ };
7335
+ }
7336
+
7081
7337
  /**
7082
7338
  * Copyright (c) Meta Platforms, Inc. and affiliates.
7083
7339
  *
@@ -7226,6 +7482,42 @@ function createTextInnerDOM(innerDOM, node, innerTag, format, text, config) {
7226
7482
  }
7227
7483
  }
7228
7484
 
7485
+ function updateTextMarks(textNode, marks, offset, delCount, size) {
7486
+ for (let i = 0; i < marks.length; i++) {
7487
+ const {
7488
+ id,
7489
+ start,
7490
+ end
7491
+ } = marks[i];
7492
+ let newStart = start;
7493
+ let newEnd = end;
7494
+
7495
+ if (newStart !== null && newStart >= offset) {
7496
+ if (offset + delCount >= newStart) {
7497
+ newStart = offset + delCount;
7498
+ }
7499
+
7500
+ newStart += size - delCount;
7501
+ }
7502
+
7503
+ if (newEnd !== null && newEnd >= offset) {
7504
+ if (offset + delCount >= newEnd) {
7505
+ newEnd = offset;
7506
+ }
7507
+
7508
+ newEnd += size - delCount;
7509
+ }
7510
+
7511
+ if (newStart !== start || newEnd !== end) {
7512
+ if (newStart === null && newEnd === null || newStart !== null && newEnd !== null && newStart >= newEnd) {
7513
+ textNode.deleteMark(id);
7514
+ } else {
7515
+ textNode.setMark(id, newStart, newEnd);
7516
+ }
7517
+ }
7518
+ }
7519
+ }
7520
+
7229
7521
  class TextNode extends LexicalNode {
7230
7522
  static getType() {
7231
7523
  return 'text';
@@ -7242,6 +7534,7 @@ class TextNode extends LexicalNode {
7242
7534
  this.__style = '';
7243
7535
  this.__mode = 0;
7244
7536
  this.__detail = 0;
7537
+ this.__marks = null;
7245
7538
  }
7246
7539
 
7247
7540
  getFormat() {
@@ -7249,6 +7542,23 @@ class TextNode extends LexicalNode {
7249
7542
  return self.__format;
7250
7543
  }
7251
7544
 
7545
+ getMark(id) {
7546
+ const self = this.getLatest();
7547
+ const marks = self.__marks;
7548
+
7549
+ if (marks !== null) {
7550
+ for (let i = 0; i < marks.length; i++) {
7551
+ const mark = marks[i];
7552
+
7553
+ if (mark.id === id) {
7554
+ return mark;
7555
+ }
7556
+ }
7557
+ }
7558
+
7559
+ return null;
7560
+ }
7561
+
7252
7562
  getStyle() {
7253
7563
  const self = this.getLatest();
7254
7564
  return self.__style;
@@ -7302,7 +7612,6 @@ class TextNode extends LexicalNode {
7302
7612
  const format = self.__format;
7303
7613
  return toggleTextFormatType(format, type, alignWithFormat);
7304
7614
  } // View
7305
- // $FlowFixMe: Revise typings for EditorContext
7306
7615
 
7307
7616
 
7308
7617
  createDOM(config) {
@@ -7327,8 +7636,7 @@ class TextNode extends LexicalNode {
7327
7636
  }
7328
7637
 
7329
7638
  return dom;
7330
- } // $FlowFixMe: Revise typings for EditorContext
7331
-
7639
+ }
7332
7640
 
7333
7641
  updateDOM(prevNode, dom, config) {
7334
7642
  const nextText = this.__text;
@@ -7464,6 +7772,64 @@ class TextNode extends LexicalNode {
7464
7772
  return self;
7465
7773
  }
7466
7774
 
7775
+ setMark(id, start, end) {
7776
+ errorOnReadOnly();
7777
+ const self = this.getWritable();
7778
+ let marks = self.__marks;
7779
+ let found = false;
7780
+
7781
+ if (marks === null) {
7782
+ self.__marks = marks = [];
7783
+ }
7784
+
7785
+ const nextMark = {
7786
+ end,
7787
+ id,
7788
+ start
7789
+ };
7790
+
7791
+ {
7792
+ Object.freeze(nextMark);
7793
+ }
7794
+
7795
+ for (let i = 0; i < marks.length; i++) {
7796
+ const prevMark = marks[i];
7797
+
7798
+ if (prevMark.id === id) {
7799
+ found = true;
7800
+ marks.splice(i, 1, nextMark);
7801
+ break;
7802
+ }
7803
+ }
7804
+
7805
+ if (!found) {
7806
+ marks.push(nextMark);
7807
+ }
7808
+ }
7809
+
7810
+ deleteMark(id) {
7811
+ errorOnReadOnly();
7812
+ const self = this.getWritable();
7813
+ const marks = self.__marks;
7814
+
7815
+ if (marks === null) {
7816
+ return;
7817
+ }
7818
+
7819
+ for (let i = 0; i < marks.length; i++) {
7820
+ const prevMark = marks[i];
7821
+
7822
+ if (prevMark.id === id) {
7823
+ marks.splice(i, 1);
7824
+ break;
7825
+ }
7826
+ }
7827
+
7828
+ if (marks.length === 0) {
7829
+ self.__marks = null;
7830
+ }
7831
+ }
7832
+
7467
7833
  setMode(type) {
7468
7834
  errorOnReadOnly();
7469
7835
  const mode = TEXT_MODE_TO_TYPE[type];
@@ -7475,7 +7841,26 @@ class TextNode extends LexicalNode {
7475
7841
  setTextContent(text) {
7476
7842
  errorOnReadOnly();
7477
7843
  const writableSelf = this.getWritable();
7478
- writableSelf.__text = text;
7844
+ const marks = writableSelf.__marks;
7845
+
7846
+ if (marks !== null) {
7847
+ const selection = $getSelection();
7848
+ let cursorOffset = text.length;
7849
+
7850
+ if ($isRangeSelection(selection) && selection.isCollapsed()) {
7851
+ const anchor = selection.anchor;
7852
+
7853
+ if (anchor.key === this.__key) {
7854
+ cursorOffset = anchor.offset;
7855
+ }
7856
+ }
7857
+
7858
+ const diff = simpleDiffWithCursor(writableSelf.__text, text, cursorOffset);
7859
+ this.spliceText(diff.index, diff.remove, diff.insert);
7860
+ } else {
7861
+ writableSelf.__text = text;
7862
+ }
7863
+
7479
7864
  return writableSelf;
7480
7865
  }
7481
7866
 
@@ -7540,7 +7925,14 @@ class TextNode extends LexicalNode {
7540
7925
  }
7541
7926
 
7542
7927
  const updatedText = text.slice(0, index) + newText + text.slice(index + delCount);
7543
- return writableSelf.setTextContent(updatedText);
7928
+ const marks = writableSelf.__marks;
7929
+
7930
+ if (marks !== null) {
7931
+ updateTextMarks(writableSelf, marks, offset, delCount, handledTextLength);
7932
+ }
7933
+
7934
+ writableSelf.__text = updatedText;
7935
+ return writableSelf;
7544
7936
  }
7545
7937
 
7546
7938
  canInsertTextBefore() {
@@ -7590,6 +7982,7 @@ class TextNode extends LexicalNode {
7590
7982
  const format = self.getFormat();
7591
7983
  const style = self.getStyle();
7592
7984
  const detail = self.__detail;
7985
+ const marks = self.__marks;
7593
7986
  let hasReplacedSelf = false;
7594
7987
 
7595
7988
  if (self.isSegmented()) {
@@ -7599,6 +7992,7 @@ class TextNode extends LexicalNode {
7599
7992
  writableNode.__format = format;
7600
7993
  writableNode.__style = style;
7601
7994
  writableNode.__detail = detail;
7995
+ writableNode.__marks = marks;
7602
7996
  hasReplacedSelf = true;
7603
7997
  } else {
7604
7998
  // For the first part, update the existing node
@@ -7646,6 +8040,46 @@ class TextNode extends LexicalNode {
7646
8040
  textSize = nextTextSize;
7647
8041
  sibling.__parent = parentKey;
7648
8042
  splitNodes.push(sibling);
8043
+ }
8044
+
8045
+ if (marks !== null) {
8046
+ for (let i = 0; i < marks.length; i++) {
8047
+ const {
8048
+ id,
8049
+ start,
8050
+ end
8051
+ } = marks[i];
8052
+ let foundStart = false;
8053
+ let foundEnd = false;
8054
+ let partSize = 0;
8055
+
8056
+ for (let s = 0; s < partsLength; s++) {
8057
+ const textNode = splitNodes[s];
8058
+ const nextPartSize = partSize + parts[s].length;
8059
+ const nextStart = !foundStart && start !== null && nextPartSize > start - (start === 0 ? 1 : 0) ? start - partSize : null;
8060
+ const nextEnd = !foundEnd && end !== null && nextPartSize >= end ? end - partSize : null;
8061
+
8062
+ if (nextStart !== null || nextEnd !== null) {
8063
+ if (nextStart !== null) {
8064
+ foundStart = true;
8065
+ }
8066
+
8067
+ if (nextEnd !== null) {
8068
+ foundEnd = true;
8069
+ }
8070
+
8071
+ textNode.setMark(id, nextStart, nextEnd);
8072
+
8073
+ if (foundStart && foundEnd) {
8074
+ break;
8075
+ }
8076
+ } else {
8077
+ textNode.deleteMark(id);
8078
+ }
8079
+
8080
+ partSize = nextPartSize;
8081
+ }
8082
+ }
7649
8083
  } // Insert the nodes into the parent's children
7650
8084
 
7651
8085
 
@@ -7705,10 +8139,20 @@ class TextNode extends LexicalNode {
7705
8139
  }
7706
8140
  }
7707
8141
 
7708
- const newText = isBefore ? target.__text + text : text + target.__text;
8142
+ const targetText = target.__text;
8143
+ const targetTextLength = targetText.length;
8144
+ const newText = isBefore ? targetText + text : text + targetText;
7709
8145
  this.setTextContent(newText);
8146
+ const writableSelf = this.getWritable();
8147
+
8148
+ const marks = target.getLatest().__marks;
8149
+
8150
+ if (marks !== null) {
8151
+ updateTextMarks(writableSelf, marks, isBefore ? targetTextLength : 0, 0, textLength);
8152
+ }
8153
+
7710
8154
  target.remove();
7711
- return this.getLatest();
8155
+ return writableSelf;
7712
8156
  }
7713
8157
 
7714
8158
  isTextEntity() {
@@ -7982,7 +8426,6 @@ function createEditor(editorConfig) {
7982
8426
  const config = editorConfig || {};
7983
8427
  const namespace = config.namespace || createUID();
7984
8428
  const theme = config.theme || {};
7985
- const context = config.context || {};
7986
8429
  const parentEditor = config.parentEditor || null;
7987
8430
  const disableEvents = config.disableEvents || false;
7988
8431
  const editorState = createEmptyEditorState();
@@ -8004,8 +8447,6 @@ function createEditor(editorConfig) {
8004
8447
 
8005
8448
 
8006
8449
  const editor = new LexicalEditor(editorState, parentEditor, registeredNodes, {
8007
- // $FlowFixMe: we use our internal type to simpify the generics
8008
- context,
8009
8450
  disableEvents,
8010
8451
  namespace,
8011
8452
  theme
@@ -8364,7 +8805,7 @@ class LexicalEditor {
8364
8805
  *
8365
8806
  *
8366
8807
  */
8367
- const VERSION = '0.2.4';
8808
+ const VERSION = '0.2.5';
8368
8809
 
8369
8810
  /**
8370
8811
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -8455,6 +8896,7 @@ exports.CUT_COMMAND = CUT_COMMAND;
8455
8896
  exports.DELETE_CHARACTER_COMMAND = DELETE_CHARACTER_COMMAND;
8456
8897
  exports.DELETE_LINE_COMMAND = DELETE_LINE_COMMAND;
8457
8898
  exports.DELETE_WORD_COMMAND = DELETE_WORD_COMMAND;
8899
+ exports.DRAGEND_COMMAND = DRAGEND_COMMAND;
8458
8900
  exports.DRAGSTART_COMMAND = DRAGSTART_COMMAND;
8459
8901
  exports.DROP_COMMAND = DROP_COMMAND;
8460
8902
  exports.DecoratorNode = DecoratorNode;