lexical 0.7.6 → 0.7.8

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
@@ -6,6 +6,8 @@
6
6
  */
7
7
  'use strict';
8
8
 
9
+ var utils = require('@lexical/utils');
10
+
9
11
  /**
10
12
  * Copyright (c) Meta Platforms, Inc. and affiliates.
11
13
  *
@@ -113,7 +115,8 @@ const IS_UNDERLINE = 1 << 3;
113
115
  const IS_CODE = 1 << 4;
114
116
  const IS_SUBSCRIPT = 1 << 5;
115
117
  const IS_SUPERSCRIPT = 1 << 6;
116
- const IS_ALL_FORMATTING = IS_BOLD | IS_ITALIC | IS_STRIKETHROUGH | IS_UNDERLINE | IS_CODE | IS_SUBSCRIPT | IS_SUPERSCRIPT; // Text node details
118
+ const IS_HIGHLIGHT = 1 << 7;
119
+ const IS_ALL_FORMATTING = IS_BOLD | IS_ITALIC | IS_STRIKETHROUGH | IS_UNDERLINE | IS_CODE | IS_SUBSCRIPT | IS_SUPERSCRIPT | IS_HIGHLIGHT; // Text node details
117
120
 
118
121
  const IS_DIRECTIONLESS = 1;
119
122
  const IS_UNMERGEABLE = 1 << 1; // Element node formatting
@@ -143,6 +146,7 @@ const LTR_REGEX = new RegExp('^[^' + RTL + ']*[' + LTR + ']');
143
146
  const TEXT_TYPE_TO_FORMAT = {
144
147
  bold: IS_BOLD,
145
148
  code: IS_CODE,
149
+ highlight: IS_HIGHLIGHT,
146
150
  italic: IS_ITALIC,
147
151
  strikethrough: IS_STRIKETHROUGH,
148
152
  subscript: IS_SUBSCRIPT,
@@ -892,7 +896,33 @@ function $updateSelectedTextFromDOM(isCompositionEnd, editor, data) {
892
896
  const node = $getNearestNodeFromDOMNode(anchorNode);
893
897
 
894
898
  if (textContent !== null && $isTextNode(node)) {
895
- // Data is intentionally truthy, as we check for boolean, null and empty string.
899
+ if (node.canContainTabs()) {
900
+ const hasTabCharacter = textContent.includes('\t'); // At present, this condition is primarily used for code highlights when
901
+ // grouped together in lines (divs). If a code highlight includes a tab,
902
+ // the newly typed character may be missing from the DOM's textContent.
903
+ // Let's take an example. If a LinedCodeNode looked roughly like this:
904
+ // <code><div><codeHighlight /><codeHighlight /></div></code>,
905
+ // the following could occur when using tabs:
906
+ // a. /tconst --type--> 'd' at offset 1 --get--> /tconst
907
+ // - Missing 'd'
908
+ // b. /tconst --type--> 'd' at offset 3 --get--> /tcondst
909
+ // --type--> 'd' at offset 3 --get--> /tcondst
910
+ // - Missing second 'd'
911
+ // In these cases, we can fix the problem by manually inserting the
912
+ // newly typed character where we know it should have been.
913
+
914
+ if (data && data.length > 0 && hasTabCharacter) {
915
+ const selectionOffset = data.length;
916
+ const insertionOffset = anchorOffset + selectionOffset - 1;
917
+ const beforeInsertion = textContent.slice(0, insertionOffset);
918
+ const afterInsertion = textContent.slice(insertionOffset, textContent.length);
919
+ textContent = `${beforeInsertion}${data}${afterInsertion}`;
920
+ anchorOffset += selectionOffset;
921
+ focusOffset += selectionOffset;
922
+ }
923
+ } // Data is intentionally truthy, as we check for boolean, null and empty string.
924
+
925
+
896
926
  if (textContent === COMPOSITION_SUFFIX && data) {
897
927
  const offset = data.length;
898
928
  textContent = data;
@@ -1873,12 +1903,6 @@ function createNode(key, parentDOM, insertDOM) {
1873
1903
  const endIndex = childrenSize - 1;
1874
1904
  const children = createChildrenArray(node, activeNextNodeMap);
1875
1905
  createChildrenWithDirection(children, endIndex, node, dom);
1876
-
1877
- if ($textContentRequiresDoubleLinebreakAtEnd(node)) {
1878
- subTreeTextContent += DOUBLE_LINE_BREAK; // @ts-expect-error: internal field
1879
-
1880
- dom.__lexicalTextContent = subTreeTextContent;
1881
- }
1882
1906
  }
1883
1907
 
1884
1908
  const format = node.__format;
@@ -1944,18 +1968,22 @@ function createNode(key, parentDOM, insertDOM) {
1944
1968
  function createChildrenWithDirection(children, endIndex, element, dom) {
1945
1969
  const previousSubTreeDirectionedTextContent = subTreeDirectionedTextContent;
1946
1970
  subTreeDirectionedTextContent = '';
1947
- createChildren(children, 0, endIndex, dom, null);
1971
+ createChildren(children, element, 0, endIndex, dom, null);
1948
1972
  reconcileBlockDirection(element, dom);
1949
1973
  subTreeDirectionedTextContent = previousSubTreeDirectionedTextContent;
1950
1974
  }
1951
1975
 
1952
- function createChildren(children, _startIndex, endIndex, dom, insertDOM) {
1976
+ function createChildren(children, element, _startIndex, endIndex, dom, insertDOM) {
1953
1977
  const previousSubTreeTextContent = subTreeTextContent;
1954
1978
  subTreeTextContent = '';
1955
1979
  let startIndex = _startIndex;
1956
1980
 
1957
1981
  for (; startIndex <= endIndex; ++startIndex) {
1958
1982
  createNode(children[startIndex], dom, insertDOM);
1983
+ }
1984
+
1985
+ if ($textContentRequiresDoubleLinebreakAtEnd(element)) {
1986
+ subTreeTextContent += DOUBLE_LINE_BREAK;
1959
1987
  } // @ts-expect-error: internal field
1960
1988
 
1961
1989
 
@@ -2106,7 +2134,7 @@ function reconcileChildren(prevElement, nextElement, dom) {
2106
2134
 
2107
2135
  if (prevChildrenSize === 0) {
2108
2136
  if (nextChildrenSize !== 0) {
2109
- createChildren(nextChildren, 0, nextChildrenSize - 1, dom, null);
2137
+ createChildren(nextChildren, nextElement, 0, nextChildrenSize - 1, dom, null);
2110
2138
  }
2111
2139
  } else if (nextChildrenSize === 0) {
2112
2140
  if (prevChildrenSize !== 0) {
@@ -2121,7 +2149,7 @@ function reconcileChildren(prevElement, nextElement, dom) {
2121
2149
  }
2122
2150
  }
2123
2151
  } else {
2124
- reconcileNodeChildren(prevChildren, nextChildren, prevChildrenSize, nextChildrenSize, dom);
2152
+ reconcileNodeChildren(nextElement, prevChildren, nextChildren, prevChildrenSize, nextChildrenSize, dom);
2125
2153
  }
2126
2154
  }
2127
2155
 
@@ -2287,7 +2315,7 @@ function getNextSibling(element) {
2287
2315
  return nextSibling;
2288
2316
  }
2289
2317
 
2290
- function reconcileNodeChildren(prevChildren, nextChildren, prevChildrenLength, nextChildrenLength, dom) {
2318
+ function reconcileNodeChildren(nextElement, prevChildren, nextChildren, prevChildrenLength, nextChildrenLength, dom) {
2291
2319
  const prevEndIndex = prevChildrenLength - 1;
2292
2320
  const nextEndIndex = nextChildrenLength - 1;
2293
2321
  let prevChildrenSet;
@@ -2353,7 +2381,7 @@ function reconcileNodeChildren(prevChildren, nextChildren, prevChildrenLength, n
2353
2381
  if (appendNewChildren && !removeOldChildren) {
2354
2382
  const previousNode = nextChildren[nextEndIndex + 1];
2355
2383
  const insertDOM = previousNode === undefined ? null : activeEditor$1.getElementByKey(previousNode);
2356
- createChildren(nextChildren, nextIndex, nextEndIndex, dom, insertDOM);
2384
+ createChildren(nextChildren, nextElement, nextIndex, nextEndIndex, dom, insertDOM);
2357
2385
  } else if (removeOldChildren && !appendNewChildren) {
2358
2386
  destroyChildren(prevChildren, prevIndex, prevEndIndex, dom);
2359
2387
  }
@@ -2473,7 +2501,7 @@ function $shouldPreventDefaultAndInsertText(selection, text, timeStamp, isBefore
2473
2501
  // a recent beforeinput event for "textInput". If there has been one in the last
2474
2502
  // 50ms then we proceed as normal. However, if there is not, then this is likely
2475
2503
  // a dangling `input` event caused by execCommand('insertText').
2476
- lastBeforeInputInsertTextTimeStamp < timeStamp + 50) || textLength < 2 || doesContainGrapheme(text)) && anchor.offset !== focus.offset && !anchorNode.isComposing() || // Any non standard text node.
2504
+ lastBeforeInputInsertTextTimeStamp < timeStamp + 50) || anchorNode.isDirty() && textLength < 2 || doesContainGrapheme(text)) && anchor.offset !== focus.offset && !anchorNode.isComposing() || // Any non standard text node.
2477
2505
  $isTokenOrSegmented(anchorNode) || // If the text length is more than a single character and we're either
2478
2506
  // dealing with this in "beforeinput" or where the node has already recently
2479
2507
  // been changed (thus is dirty).
@@ -2914,7 +2942,8 @@ function onInput(event, editor) {
2914
2942
  $setCompositionKey(null);
2915
2943
  }
2916
2944
  } else {
2917
- $updateSelectedTextFromDOM(false, editor); // onInput always fires after onCompositionEnd for FF.
2945
+ const characterData = data !== null ? data : undefined;
2946
+ $updateSelectedTextFromDOM(false, editor, characterData); // onInput always fires after onCompositionEnd for FF.
2918
2947
 
2919
2948
  if (isFirefoxEndingComposition) {
2920
2949
  onCompositionEndImpl(editor, data || undefined);
@@ -3815,7 +3844,13 @@ class RangeSelection {
3815
3844
  }
3816
3845
 
3817
3846
  if ($isElementNode(lastNode)) {
3818
- const lastNodeDescendant = lastNode.getDescendantByIndex(focus.offset);
3847
+ let lastNodeDescendant = lastNode.getDescendantByIndex(focus.offset); // We don't want to over-select, as node selection infers the child before
3848
+ // the last descendant, not including that descendant.
3849
+
3850
+ if (lastNodeDescendant !== null && lastNodeDescendant !== firstNode && lastNode.getChildAtIndex(focus.offset) === lastNodeDescendant) {
3851
+ lastNodeDescendant = lastNodeDescendant.getPreviousSibling();
3852
+ }
3853
+
3819
3854
  lastNode = lastNodeDescendant != null ? lastNodeDescendant : lastNode;
3820
3855
  }
3821
3856
 
@@ -3882,7 +3917,9 @@ class RangeSelection {
3882
3917
 
3883
3918
  if (node === firstNode) {
3884
3919
  if (node === lastNode) {
3885
- text = anchorOffset < focusOffset ? text.slice(anchorOffset, focusOffset) : text.slice(focusOffset, anchorOffset);
3920
+ if (anchor.type !== 'element' || focus.type !== 'element' || focus.offset === anchor.offset) {
3921
+ text = anchorOffset < focusOffset ? text.slice(anchorOffset, focusOffset) : text.slice(focusOffset, anchorOffset);
3922
+ }
3886
3923
  } else {
3887
3924
  text = isBefore ? text.slice(anchorOffset) : text.slice(focusOffset);
3888
3925
  }
@@ -4441,7 +4478,7 @@ class RangeSelection {
4441
4478
  for (let i = 0; i < nodes.length; i++) {
4442
4479
  const node = nodes[i];
4443
4480
 
4444
- if (!$isDecoratorNode(target) && $isElementNode(node) && !node.isInline()) {
4481
+ if (!$isRootOrShadowRoot(target) && !$isDecoratorNode(target) && $isElementNode(node) && !node.isInline()) {
4445
4482
  // -----
4446
4483
  // Heuristics for the replacement or merging of elements
4447
4484
  // -----
@@ -4581,8 +4618,27 @@ class RangeSelection {
4581
4618
  }
4582
4619
  }
4583
4620
  } else if (!$isElementNode(node) || $isElementNode(node) && node.isInline() || $isDecoratorNode(target) && !target.isInline()) {
4584
- lastNode = node;
4585
- target = target.insertAfter(node, false);
4621
+ lastNode = node; // when pasting top level node in the middle of paragraph
4622
+ // we need to split paragraph instead of placing it inline
4623
+
4624
+ if ($isRangeSelection(this) && $isDecoratorNode(node) && ($isElementNode(target) || $isTextNode(target)) && !node.isInline()) {
4625
+ let splitNode;
4626
+ let splitOffset;
4627
+
4628
+ if ($isTextNode(target)) {
4629
+ splitNode = target.getParentOrThrow();
4630
+ const [textNode] = target.splitText(anchorOffset);
4631
+ splitOffset = textNode.getIndexWithinParent() + 1;
4632
+ } else {
4633
+ splitNode = target;
4634
+ splitOffset = anchorOffset;
4635
+ }
4636
+
4637
+ const [, rightTree] = utils.$splitNode(splitNode, splitOffset);
4638
+ target = rightTree.insertBefore(node);
4639
+ } else {
4640
+ target = target.insertAfter(node, false);
4641
+ }
4586
4642
  } else {
4587
4643
  const nextTarget = target.getParentOrThrow(); // if we're inserting an Element after a LineBreak, we want to move the target to the parent
4588
4644
  // and remove the LineBreak so we don't have empty space.
@@ -5096,7 +5152,16 @@ class RangeSelection {
5096
5152
  }
5097
5153
  }
5098
5154
 
5155
+ const wasCollapsed = this.isCollapsed();
5099
5156
  this.removeText();
5157
+
5158
+ if (isBackward && !wasCollapsed && this.isCollapsed() && this.anchor.type === 'element' && this.anchor.offset === 0) {
5159
+ const anchorNode = this.anchor.getNode();
5160
+
5161
+ if (anchorNode.isEmpty() && $isRootNode(anchorNode.getParent())) {
5162
+ anchorNode.collapseAtStart(this);
5163
+ }
5164
+ }
5100
5165
  }
5101
5166
 
5102
5167
  deleteLine(isBackward) {
@@ -6733,14 +6798,14 @@ class LexicalNode {
6733
6798
  return false;
6734
6799
  }
6735
6800
 
6736
- isSelected() {
6737
- const selection = $getSelection();
6801
+ isSelected(selection) {
6802
+ const targetSelection = selection || $getSelection();
6738
6803
 
6739
- if (selection == null) {
6804
+ if (targetSelection == null) {
6740
6805
  return false;
6741
6806
  }
6742
6807
 
6743
- const isSelected = selection.getNodes().some(n => n.__key === this.__key);
6808
+ const isSelected = targetSelection.getNodes().some(n => n.__key === this.__key);
6744
6809
 
6745
6810
  if ($isTextNode(this)) {
6746
6811
  return isSelected;
@@ -6748,7 +6813,7 @@ class LexicalNode {
6748
6813
  // Without this change the image will be selected if the cursor is before or after it.
6749
6814
 
6750
6815
 
6751
- if ($isRangeSelection(selection) && selection.anchor.type === 'element' && selection.focus.type === 'element' && selection.anchor.key === selection.focus.key && selection.anchor.offset === selection.focus.offset) {
6816
+ if ($isRangeSelection(targetSelection) && targetSelection.anchor.type === 'element' && targetSelection.focus.type === 'element' && targetSelection.anchor.key === targetSelection.focus.key && targetSelection.anchor.offset === targetSelection.focus.offset) {
6752
6817
  return false;
6753
6818
  }
6754
6819
 
@@ -7361,6 +7426,14 @@ class LexicalNode {
7361
7426
  return nodeToInsert;
7362
7427
  }
7363
7428
 
7429
+ isParentRequired() {
7430
+ return false;
7431
+ }
7432
+
7433
+ createParentElementNode() {
7434
+ return $createParagraphNode();
7435
+ }
7436
+
7364
7437
  selectPrevious(anchorOffset, focusOffset) {
7365
7438
  errorOnReadOnly();
7366
7439
  const prevSibling = this.getPreviousSibling();
@@ -8369,6 +8442,10 @@ function getElementOuterTag(node, format) {
8369
8442
  return 'code';
8370
8443
  }
8371
8444
 
8445
+ if (format & IS_HIGHLIGHT) {
8446
+ return 'mark';
8447
+ }
8448
+
8372
8449
  if (format & IS_SUBSCRIPT) {
8373
8450
  return 'sub';
8374
8451
  }
@@ -8937,6 +9014,10 @@ class TextNode extends LexicalNode {
8937
9014
  return true;
8938
9015
  }
8939
9016
 
9017
+ canContainTabs() {
9018
+ return false;
9019
+ }
9020
+
8940
9021
  splitText(...splitOffsets) {
8941
9022
  errorOnReadOnly();
8942
9023
  const self = this.getLatest();
@@ -9260,7 +9341,7 @@ class ParagraphNode extends ElementNode {
9260
9341
  return dom;
9261
9342
  }
9262
9343
 
9263
- updateDOM(prevNode, dom) {
9344
+ updateDOM(prevNode, dom, config) {
9264
9345
  return false;
9265
9346
  }
9266
9347
 
@@ -9356,9 +9437,20 @@ class ParagraphNode extends ElementNode {
9356
9437
 
9357
9438
  }
9358
9439
 
9359
- function convertParagraphElement() {
9440
+ function convertParagraphElement(element) {
9441
+ const node = $createParagraphNode();
9442
+
9443
+ if (element.style) {
9444
+ node.setFormat(element.style.textAlign);
9445
+ const indent = parseInt(element.style.textIndent, 10) / 20;
9446
+
9447
+ if (indent > 0) {
9448
+ node.setIndent(indent);
9449
+ }
9450
+ }
9451
+
9360
9452
  return {
9361
- node: $createParagraphNode()
9453
+ node
9362
9454
  };
9363
9455
  }
9364
9456
 
@@ -10051,3 +10143,4 @@ exports.TextNode = TextNode;
10051
10143
  exports.UNDO_COMMAND = UNDO_COMMAND;
10052
10144
  exports.createCommand = createCommand;
10053
10145
  exports.createEditor = createEditor;
10146
+ exports.isSelectionWithinEditor = isSelectionWithinEditor;