lexical 0.11.0 → 0.11.2
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 +557 -138
- package/Lexical.js.flow +9 -9
- package/Lexical.prod.js +207 -204
- package/LexicalCommands.d.ts +4 -4
- package/LexicalEditor.d.ts +41 -35
- package/LexicalEvents.d.ts +1 -1
- package/LexicalGC.d.ts +1 -1
- package/LexicalNode.d.ts +10 -10
- package/LexicalReconciler.d.ts +1 -1
- package/LexicalSelection.d.ts +148 -6
- package/LexicalUpdates.d.ts +1 -1
- package/LexicalUtils.d.ts +2 -1
- package/nodes/LexicalElementNode.d.ts +2 -2
- package/nodes/LexicalGridCellNode.d.ts +1 -1
- package/nodes/LexicalLineBreakNode.d.ts +1 -1
- package/nodes/LexicalParagraphNode.d.ts +1 -1
- package/nodes/LexicalRootNode.d.ts +1 -1
- package/nodes/LexicalTabNode.d.ts +1 -1
- package/nodes/LexicalTextNode.d.ts +195 -6
- package/package.json +1 -1
package/Lexical.dev.js
CHANGED
|
@@ -428,6 +428,102 @@ function initMutationObserver(editor) {
|
|
|
428
428
|
});
|
|
429
429
|
}
|
|
430
430
|
|
|
431
|
+
/**
|
|
432
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
433
|
+
*
|
|
434
|
+
* This source code is licensed under the MIT license found in the
|
|
435
|
+
* LICENSE file in the root directory of this source tree.
|
|
436
|
+
*
|
|
437
|
+
*/
|
|
438
|
+
|
|
439
|
+
function $canSimpleTextNodesBeMerged(node1, node2) {
|
|
440
|
+
const node1Mode = node1.__mode;
|
|
441
|
+
const node1Format = node1.__format;
|
|
442
|
+
const node1Style = node1.__style;
|
|
443
|
+
const node2Mode = node2.__mode;
|
|
444
|
+
const node2Format = node2.__format;
|
|
445
|
+
const node2Style = node2.__style;
|
|
446
|
+
return (node1Mode === null || node1Mode === node2Mode) && (node1Format === null || node1Format === node2Format) && (node1Style === null || node1Style === node2Style);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function $mergeTextNodes(node1, node2) {
|
|
450
|
+
const writableNode1 = node1.mergeWithSibling(node2);
|
|
451
|
+
|
|
452
|
+
const normalizedNodes = getActiveEditor()._normalizedNodes;
|
|
453
|
+
|
|
454
|
+
normalizedNodes.add(node1.__key);
|
|
455
|
+
normalizedNodes.add(node2.__key);
|
|
456
|
+
return writableNode1;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function $normalizeTextNode(textNode) {
|
|
460
|
+
let node = textNode;
|
|
461
|
+
|
|
462
|
+
if (node.__text === '' && node.isSimpleText() && !node.isUnmergeable()) {
|
|
463
|
+
node.remove();
|
|
464
|
+
return;
|
|
465
|
+
} // Backward
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
let previousNode;
|
|
469
|
+
|
|
470
|
+
while ((previousNode = node.getPreviousSibling()) !== null && $isTextNode(previousNode) && previousNode.isSimpleText() && !previousNode.isUnmergeable()) {
|
|
471
|
+
if (previousNode.__text === '') {
|
|
472
|
+
previousNode.remove();
|
|
473
|
+
} else if ($canSimpleTextNodesBeMerged(previousNode, node)) {
|
|
474
|
+
node = $mergeTextNodes(previousNode, node);
|
|
475
|
+
break;
|
|
476
|
+
} else {
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
} // Forward
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
let nextNode;
|
|
483
|
+
|
|
484
|
+
while ((nextNode = node.getNextSibling()) !== null && $isTextNode(nextNode) && nextNode.isSimpleText() && !nextNode.isUnmergeable()) {
|
|
485
|
+
if (nextNode.__text === '') {
|
|
486
|
+
nextNode.remove();
|
|
487
|
+
} else if ($canSimpleTextNodesBeMerged(node, nextNode)) {
|
|
488
|
+
node = $mergeTextNodes(node, nextNode);
|
|
489
|
+
break;
|
|
490
|
+
} else {
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function $normalizeSelection(selection) {
|
|
496
|
+
$normalizePoint(selection.anchor);
|
|
497
|
+
$normalizePoint(selection.focus);
|
|
498
|
+
return selection;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function $normalizePoint(point) {
|
|
502
|
+
while (point.type === 'element') {
|
|
503
|
+
const node = point.getNode();
|
|
504
|
+
const offset = point.offset;
|
|
505
|
+
let nextNode;
|
|
506
|
+
let nextOffsetAtEnd;
|
|
507
|
+
|
|
508
|
+
if (offset === node.getChildrenSize()) {
|
|
509
|
+
nextNode = node.getChildAtIndex(offset - 1);
|
|
510
|
+
nextOffsetAtEnd = true;
|
|
511
|
+
} else {
|
|
512
|
+
nextNode = node.getChildAtIndex(offset);
|
|
513
|
+
nextOffsetAtEnd = false;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if ($isTextNode(nextNode)) {
|
|
517
|
+
point.set(nextNode.__key, nextOffsetAtEnd ? nextNode.getTextContentSize() : 0, 'text');
|
|
518
|
+
break;
|
|
519
|
+
} else if (!$isElementNode(nextNode)) {
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
point.set(nextNode.__key, nextOffsetAtEnd ? nextNode.getChildrenSize() : 0, 'element');
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
431
527
|
/**
|
|
432
528
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
433
529
|
*
|
|
@@ -948,13 +1044,13 @@ function $updateTextNodeFromDOMContent(textNode, textContent, anchorOffset, focu
|
|
|
948
1044
|
|
|
949
1045
|
const parent = node.getParent();
|
|
950
1046
|
const prevSelection = $getPreviousSelection();
|
|
1047
|
+
const prevTextContentSize = node.getTextContentSize();
|
|
951
1048
|
const compositionKey = $getCompositionKey();
|
|
952
1049
|
const nodeKey = node.getKey();
|
|
953
1050
|
|
|
954
|
-
if (node.isToken() || compositionKey !== null && nodeKey === compositionKey && !isComposing || // Check if character was added at the start, and we need
|
|
955
|
-
// to clear this input from occurring as that action wasn't
|
|
956
|
-
|
|
957
|
-
parent !== null && $isRangeSelection(prevSelection) && !parent.canInsertTextBefore() && prevSelection.anchor.offset === 0) {
|
|
1051
|
+
if (node.isToken() || compositionKey !== null && nodeKey === compositionKey && !isComposing || // Check if character was added at the start or boundaries when not insertable, and we need
|
|
1052
|
+
// to clear this input from occurring as that action wasn't permitted.
|
|
1053
|
+
$isRangeSelection(prevSelection) && (parent !== null && !parent.canInsertTextBefore() && prevSelection.anchor.offset === 0 || prevSelection.anchor.key === textNode.__key && prevSelection.anchor.offset === 0 && !node.canInsertTextBefore() || prevSelection.focus.key === textNode.__key && prevSelection.focus.offset === prevTextContentSize && !node.canInsertTextAfter())) {
|
|
958
1054
|
node.markDirty();
|
|
959
1055
|
return;
|
|
960
1056
|
}
|
|
@@ -1169,7 +1265,23 @@ function isDelete(keyCode) {
|
|
|
1169
1265
|
function isSelectAll(keyCode, metaKey, ctrlKey) {
|
|
1170
1266
|
return keyCode === 65 && controlOrMeta(metaKey, ctrlKey);
|
|
1171
1267
|
}
|
|
1268
|
+
function $selectAll() {
|
|
1269
|
+
const root = $getRoot();
|
|
1270
|
+
const selection = root.select(0, root.getChildrenSize());
|
|
1271
|
+
$setSelection($normalizeSelection(selection));
|
|
1272
|
+
}
|
|
1172
1273
|
function getCachedClassNameArray(classNamesTheme, classNameThemeType) {
|
|
1274
|
+
if (classNamesTheme.__lexicalClassNameCache === undefined) {
|
|
1275
|
+
classNamesTheme.__lexicalClassNameCache = {};
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
const classNamesCache = classNamesTheme.__lexicalClassNameCache;
|
|
1279
|
+
const cachedClassNames = classNamesCache[classNameThemeType];
|
|
1280
|
+
|
|
1281
|
+
if (cachedClassNames !== undefined) {
|
|
1282
|
+
return cachedClassNames;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1173
1285
|
const classNames = classNamesTheme[classNameThemeType]; // As we're using classList, we need
|
|
1174
1286
|
// to handle className tokens that have spaces.
|
|
1175
1287
|
// The easiest way to do this to convert the
|
|
@@ -1178,7 +1290,7 @@ function getCachedClassNameArray(classNamesTheme, classNameThemeType) {
|
|
|
1178
1290
|
|
|
1179
1291
|
if (typeof classNames === 'string') {
|
|
1180
1292
|
const classNamesArr = classNames.split(' ');
|
|
1181
|
-
|
|
1293
|
+
classNamesCache[classNameThemeType] = classNamesArr;
|
|
1182
1294
|
return classNamesArr;
|
|
1183
1295
|
}
|
|
1184
1296
|
|
|
@@ -1366,13 +1478,7 @@ function $addUpdateTag(tag) {
|
|
|
1366
1478
|
|
|
1367
1479
|
editor._updateTags.add(tag);
|
|
1368
1480
|
}
|
|
1369
|
-
function $maybeMoveChildrenSelectionToParent(parentNode
|
|
1370
|
-
if (offset !== 0) {
|
|
1371
|
-
{
|
|
1372
|
-
throw Error(`TODO`);
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1481
|
+
function $maybeMoveChildrenSelectionToParent(parentNode) {
|
|
1376
1482
|
const selection = $getSelection();
|
|
1377
1483
|
|
|
1378
1484
|
if (!$isRangeSelection(selection) || !$isElementNode(parentNode)) {
|
|
@@ -1741,102 +1847,6 @@ function $garbageCollectDetachedNodes(prevEditorState, editorState, dirtyLeaves,
|
|
|
1741
1847
|
}
|
|
1742
1848
|
}
|
|
1743
1849
|
|
|
1744
|
-
/**
|
|
1745
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
1746
|
-
*
|
|
1747
|
-
* This source code is licensed under the MIT license found in the
|
|
1748
|
-
* LICENSE file in the root directory of this source tree.
|
|
1749
|
-
*
|
|
1750
|
-
*/
|
|
1751
|
-
|
|
1752
|
-
function $canSimpleTextNodesBeMerged(node1, node2) {
|
|
1753
|
-
const node1Mode = node1.__mode;
|
|
1754
|
-
const node1Format = node1.__format;
|
|
1755
|
-
const node1Style = node1.__style;
|
|
1756
|
-
const node2Mode = node2.__mode;
|
|
1757
|
-
const node2Format = node2.__format;
|
|
1758
|
-
const node2Style = node2.__style;
|
|
1759
|
-
return (node1Mode === null || node1Mode === node2Mode) && (node1Format === null || node1Format === node2Format) && (node1Style === null || node1Style === node2Style);
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
function $mergeTextNodes(node1, node2) {
|
|
1763
|
-
const writableNode1 = node1.mergeWithSibling(node2);
|
|
1764
|
-
|
|
1765
|
-
const normalizedNodes = getActiveEditor()._normalizedNodes;
|
|
1766
|
-
|
|
1767
|
-
normalizedNodes.add(node1.__key);
|
|
1768
|
-
normalizedNodes.add(node2.__key);
|
|
1769
|
-
return writableNode1;
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
function $normalizeTextNode(textNode) {
|
|
1773
|
-
let node = textNode;
|
|
1774
|
-
|
|
1775
|
-
if (node.__text === '' && node.isSimpleText() && !node.isUnmergeable()) {
|
|
1776
|
-
node.remove();
|
|
1777
|
-
return;
|
|
1778
|
-
} // Backward
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
let previousNode;
|
|
1782
|
-
|
|
1783
|
-
while ((previousNode = node.getPreviousSibling()) !== null && $isTextNode(previousNode) && previousNode.isSimpleText() && !previousNode.isUnmergeable()) {
|
|
1784
|
-
if (previousNode.__text === '') {
|
|
1785
|
-
previousNode.remove();
|
|
1786
|
-
} else if ($canSimpleTextNodesBeMerged(previousNode, node)) {
|
|
1787
|
-
node = $mergeTextNodes(previousNode, node);
|
|
1788
|
-
break;
|
|
1789
|
-
} else {
|
|
1790
|
-
break;
|
|
1791
|
-
}
|
|
1792
|
-
} // Forward
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
let nextNode;
|
|
1796
|
-
|
|
1797
|
-
while ((nextNode = node.getNextSibling()) !== null && $isTextNode(nextNode) && nextNode.isSimpleText() && !nextNode.isUnmergeable()) {
|
|
1798
|
-
if (nextNode.__text === '') {
|
|
1799
|
-
nextNode.remove();
|
|
1800
|
-
} else if ($canSimpleTextNodesBeMerged(node, nextNode)) {
|
|
1801
|
-
node = $mergeTextNodes(node, nextNode);
|
|
1802
|
-
break;
|
|
1803
|
-
} else {
|
|
1804
|
-
break;
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
|
-
}
|
|
1808
|
-
function $normalizeSelection(selection) {
|
|
1809
|
-
$normalizePoint(selection.anchor);
|
|
1810
|
-
$normalizePoint(selection.focus);
|
|
1811
|
-
return selection;
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
function $normalizePoint(point) {
|
|
1815
|
-
while (point.type === 'element') {
|
|
1816
|
-
const node = point.getNode();
|
|
1817
|
-
const offset = point.offset;
|
|
1818
|
-
let nextNode;
|
|
1819
|
-
let nextOffsetAtEnd;
|
|
1820
|
-
|
|
1821
|
-
if (offset === node.getChildrenSize()) {
|
|
1822
|
-
nextNode = node.getChildAtIndex(offset - 1);
|
|
1823
|
-
nextOffsetAtEnd = true;
|
|
1824
|
-
} else {
|
|
1825
|
-
nextNode = node.getChildAtIndex(offset);
|
|
1826
|
-
nextOffsetAtEnd = false;
|
|
1827
|
-
}
|
|
1828
|
-
|
|
1829
|
-
if ($isTextNode(nextNode)) {
|
|
1830
|
-
point.set(nextNode.__key, nextOffsetAtEnd ? nextNode.getTextContentSize() : 0, 'text');
|
|
1831
|
-
break;
|
|
1832
|
-
} else if (!$isElementNode(nextNode)) {
|
|
1833
|
-
break;
|
|
1834
|
-
}
|
|
1835
|
-
|
|
1836
|
-
point.set(nextNode.__key, nextOffsetAtEnd ? nextNode.getChildrenSize() : 0, 'element');
|
|
1837
|
-
}
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
1850
|
/**
|
|
1841
1851
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
1842
1852
|
*
|
|
@@ -2919,10 +2929,12 @@ function onBeforeInput(event, editor) {
|
|
|
2919
2929
|
case 'insertParagraph':
|
|
2920
2930
|
{
|
|
2921
2931
|
// Used for Android
|
|
2922
|
-
$setCompositionKey(null); //
|
|
2932
|
+
$setCompositionKey(null); // Safari does not provide the type "insertLineBreak".
|
|
2923
2933
|
// So instead, we need to infer it from the keyboard event.
|
|
2934
|
+
// We do not apply this logic to iOS to allow newline auto-capitalization
|
|
2935
|
+
// work without creating linebreaks when pressing Enter
|
|
2924
2936
|
|
|
2925
|
-
if (isInsertLineBreak) {
|
|
2937
|
+
if (isInsertLineBreak && !IS_IOS) {
|
|
2926
2938
|
isInsertLineBreak = false;
|
|
2927
2939
|
dispatchCommand(editor, INSERT_LINE_BREAK_COMMAND, false);
|
|
2928
2940
|
} else {
|
|
@@ -2942,7 +2954,7 @@ function onBeforeInput(event, editor) {
|
|
|
2942
2954
|
case 'deleteByComposition':
|
|
2943
2955
|
{
|
|
2944
2956
|
if ($canRemoveText(anchorNode, focusNode)) {
|
|
2945
|
-
dispatchCommand(editor, REMOVE_TEXT_COMMAND,
|
|
2957
|
+
dispatchCommand(editor, REMOVE_TEXT_COMMAND, event);
|
|
2946
2958
|
}
|
|
2947
2959
|
|
|
2948
2960
|
break;
|
|
@@ -2951,7 +2963,7 @@ function onBeforeInput(event, editor) {
|
|
|
2951
2963
|
case 'deleteByDrag':
|
|
2952
2964
|
case 'deleteByCut':
|
|
2953
2965
|
{
|
|
2954
|
-
dispatchCommand(editor, REMOVE_TEXT_COMMAND,
|
|
2966
|
+
dispatchCommand(editor, REMOVE_TEXT_COMMAND, event);
|
|
2955
2967
|
break;
|
|
2956
2968
|
}
|
|
2957
2969
|
|
|
@@ -3269,10 +3281,15 @@ function onKeyDown(event, editor) {
|
|
|
3269
3281
|
} else if (isSelectAll(keyCode, metaKey, ctrlKey)) {
|
|
3270
3282
|
event.preventDefault();
|
|
3271
3283
|
editor.update(() => {
|
|
3272
|
-
|
|
3273
|
-
root.select(0, root.getChildrenSize());
|
|
3284
|
+
$selectAll();
|
|
3274
3285
|
});
|
|
3275
|
-
}
|
|
3286
|
+
} // FF does it well (no need to override behavior)
|
|
3287
|
+
|
|
3288
|
+
} else if (!IS_FIREFOX && isSelectAll(keyCode, metaKey, ctrlKey)) {
|
|
3289
|
+
event.preventDefault();
|
|
3290
|
+
editor.update(() => {
|
|
3291
|
+
$selectAll();
|
|
3292
|
+
});
|
|
3276
3293
|
}
|
|
3277
3294
|
}
|
|
3278
3295
|
|
|
@@ -3528,6 +3545,8 @@ function removeNode(nodeToRemove, restoreSelection, preserveEmptyParent) {
|
|
|
3528
3545
|
moveSelectionPointToSibling(focus, nodeToRemove, parent, nodeToRemove.getPreviousSibling(), nodeToRemove.getNextSibling());
|
|
3529
3546
|
selectionMoved = true;
|
|
3530
3547
|
}
|
|
3548
|
+
} else if ($isNodeSelection(selection) && restoreSelection && nodeToRemove.isSelected()) {
|
|
3549
|
+
nodeToRemove.selectPrevious();
|
|
3531
3550
|
}
|
|
3532
3551
|
|
|
3533
3552
|
if ($isRangeSelection(selection) && restoreSelection && !selectionMoved) {
|
|
@@ -4854,64 +4873,152 @@ class TextNode extends LexicalNode {
|
|
|
4854
4873
|
this.__mode = 0;
|
|
4855
4874
|
this.__detail = 0;
|
|
4856
4875
|
}
|
|
4876
|
+
/**
|
|
4877
|
+
* Returns a 32-bit integer that represents the TextFormatTypes currently applied to the
|
|
4878
|
+
* TextNode. You probably don't want to use this method directly - consider using TextNode.hasFormat instead.
|
|
4879
|
+
*
|
|
4880
|
+
* @returns a number representing the format of the text node.
|
|
4881
|
+
*/
|
|
4882
|
+
|
|
4857
4883
|
|
|
4858
4884
|
getFormat() {
|
|
4859
4885
|
const self = this.getLatest();
|
|
4860
4886
|
return self.__format;
|
|
4861
4887
|
}
|
|
4888
|
+
/**
|
|
4889
|
+
* Returns a 32-bit integer that represents the TextDetailTypes currently applied to the
|
|
4890
|
+
* TextNode. You probably don't want to use this method directly - consider using TextNode.isDirectionless
|
|
4891
|
+
* or TextNode.isUnmergeable instead.
|
|
4892
|
+
*
|
|
4893
|
+
* @returns a number representing the detail of the text node.
|
|
4894
|
+
*/
|
|
4895
|
+
|
|
4862
4896
|
|
|
4863
4897
|
getDetail() {
|
|
4864
4898
|
const self = this.getLatest();
|
|
4865
4899
|
return self.__detail;
|
|
4866
4900
|
}
|
|
4901
|
+
/**
|
|
4902
|
+
* Returns the mode (TextModeType) of the TextNode, which may be "normal", "token", or "segmented"
|
|
4903
|
+
*
|
|
4904
|
+
* @returns TextModeType.
|
|
4905
|
+
*/
|
|
4906
|
+
|
|
4867
4907
|
|
|
4868
4908
|
getMode() {
|
|
4869
4909
|
const self = this.getLatest();
|
|
4870
4910
|
return TEXT_TYPE_TO_MODE[self.__mode];
|
|
4871
4911
|
}
|
|
4912
|
+
/**
|
|
4913
|
+
* Returns the styles currently applied to the node. This is analogous to CSSText in the DOM.
|
|
4914
|
+
*
|
|
4915
|
+
* @returns CSSText-like string of styles applied to the underlying DOM node.
|
|
4916
|
+
*/
|
|
4917
|
+
|
|
4872
4918
|
|
|
4873
4919
|
getStyle() {
|
|
4874
4920
|
const self = this.getLatest();
|
|
4875
4921
|
return self.__style;
|
|
4876
4922
|
}
|
|
4923
|
+
/**
|
|
4924
|
+
* Returns whether or not the node is in "token" mode. TextNodes in token mode can be navigated through character-by-character
|
|
4925
|
+
* with a RangeSelection, but are deleted as a single entity (not invdividually by character).
|
|
4926
|
+
*
|
|
4927
|
+
* @returns true if the node is in token mode, false otherwise.
|
|
4928
|
+
*/
|
|
4929
|
+
|
|
4877
4930
|
|
|
4878
4931
|
isToken() {
|
|
4879
4932
|
const self = this.getLatest();
|
|
4880
4933
|
return self.__mode === IS_TOKEN;
|
|
4881
4934
|
}
|
|
4935
|
+
/**
|
|
4936
|
+
*
|
|
4937
|
+
* @returns true if Lexical detects that an IME or other 3rd-party script is attempting to
|
|
4938
|
+
* mutate the TextNode, false otherwise.
|
|
4939
|
+
*/
|
|
4940
|
+
|
|
4882
4941
|
|
|
4883
4942
|
isComposing() {
|
|
4884
4943
|
return this.__key === $getCompositionKey();
|
|
4885
4944
|
}
|
|
4945
|
+
/**
|
|
4946
|
+
* Returns whether or not the node is in "segemented" mode. TextNodes in segemented mode can be navigated through character-by-character
|
|
4947
|
+
* with a RangeSelection, but are deleted in space-delimited "segments".
|
|
4948
|
+
*
|
|
4949
|
+
* @returns true if the node is in segmented mode, false otherwise.
|
|
4950
|
+
*/
|
|
4951
|
+
|
|
4886
4952
|
|
|
4887
4953
|
isSegmented() {
|
|
4888
4954
|
const self = this.getLatest();
|
|
4889
4955
|
return self.__mode === IS_SEGMENTED;
|
|
4890
4956
|
}
|
|
4957
|
+
/**
|
|
4958
|
+
* Returns whether or not the node is "directionless". Directionless nodes don't respect changes between RTL and LTR modes.
|
|
4959
|
+
*
|
|
4960
|
+
* @returns true if the node is directionless, false otherwise.
|
|
4961
|
+
*/
|
|
4962
|
+
|
|
4891
4963
|
|
|
4892
4964
|
isDirectionless() {
|
|
4893
4965
|
const self = this.getLatest();
|
|
4894
4966
|
return (self.__detail & IS_DIRECTIONLESS) !== 0;
|
|
4895
4967
|
}
|
|
4968
|
+
/**
|
|
4969
|
+
* Returns whether or not the node is unmergeable. In some scenarios, Lexical tries to merge
|
|
4970
|
+
* adjacent TextNodes into a single TextNode. If a TextNode is unmergeable, this won't happen.
|
|
4971
|
+
*
|
|
4972
|
+
* @returns true if the node is unmergeable, false otherwise.
|
|
4973
|
+
*/
|
|
4974
|
+
|
|
4896
4975
|
|
|
4897
4976
|
isUnmergeable() {
|
|
4898
4977
|
const self = this.getLatest();
|
|
4899
4978
|
return (self.__detail & IS_UNMERGEABLE) !== 0;
|
|
4900
4979
|
}
|
|
4980
|
+
/**
|
|
4981
|
+
* Returns whether or not the node has the provided format applied. Use this with the human-readable TextFormatType
|
|
4982
|
+
* string values to get the format of a TextNode.
|
|
4983
|
+
*
|
|
4984
|
+
* @param type - the TextFormatType to check for.
|
|
4985
|
+
*
|
|
4986
|
+
* @returns true if the node has the provided format, false otherwise.
|
|
4987
|
+
*/
|
|
4988
|
+
|
|
4901
4989
|
|
|
4902
4990
|
hasFormat(type) {
|
|
4903
4991
|
const formatFlag = TEXT_TYPE_TO_FORMAT[type];
|
|
4904
4992
|
return (this.getFormat() & formatFlag) !== 0;
|
|
4905
4993
|
}
|
|
4994
|
+
/**
|
|
4995
|
+
* Returns whether or not the node is simple text. Simple text is defined as a TextNode that has the string type "text"
|
|
4996
|
+
* (i.e., not a subclass) and has no mode applied to it (i.e., not segmented or token).
|
|
4997
|
+
*
|
|
4998
|
+
* @returns true if the node is simple text, false otherwise.
|
|
4999
|
+
*/
|
|
5000
|
+
|
|
4906
5001
|
|
|
4907
5002
|
isSimpleText() {
|
|
4908
5003
|
return this.__type === 'text' && this.__mode === 0;
|
|
4909
5004
|
}
|
|
5005
|
+
/**
|
|
5006
|
+
* Returns the text content of the node as a string.
|
|
5007
|
+
*
|
|
5008
|
+
* @returns a string representing the text content of the node.
|
|
5009
|
+
*/
|
|
5010
|
+
|
|
4910
5011
|
|
|
4911
5012
|
getTextContent() {
|
|
4912
5013
|
const self = this.getLatest();
|
|
4913
5014
|
return self.__text;
|
|
4914
5015
|
}
|
|
5016
|
+
/**
|
|
5017
|
+
* Returns the format flags applied to the node as a 32-bit integer.
|
|
5018
|
+
*
|
|
5019
|
+
* @returns a number representing the TextFormatTypes applied to the node.
|
|
5020
|
+
*/
|
|
5021
|
+
|
|
4915
5022
|
|
|
4916
5023
|
getFormatFlags(type, alignWithFormat) {
|
|
4917
5024
|
const self = this.getLatest();
|
|
@@ -5114,14 +5221,35 @@ class TextNode extends LexicalNode {
|
|
|
5114
5221
|
|
|
5115
5222
|
selectionTransform(prevSelection, nextSelection) {
|
|
5116
5223
|
return;
|
|
5117
|
-
}
|
|
5224
|
+
}
|
|
5225
|
+
/**
|
|
5226
|
+
* Sets the node format to the provided TextFormatType or 32-bit integer. Note that the TextFormatType
|
|
5227
|
+
* version of the argument can only specify one format and doing so will remove all other formats that
|
|
5228
|
+
* may be applied to the node. For toggling behavior, consider using {@link TextNode.toggleFormat}
|
|
5229
|
+
*
|
|
5230
|
+
* @param format - TextFormatType or 32-bit integer representing the node format.
|
|
5231
|
+
*
|
|
5232
|
+
* @returns this TextNode.
|
|
5233
|
+
* // TODO 0.12 This should just be a `string`.
|
|
5234
|
+
*/
|
|
5118
5235
|
|
|
5119
5236
|
|
|
5120
5237
|
setFormat(format) {
|
|
5121
5238
|
const self = this.getWritable();
|
|
5122
5239
|
self.__format = typeof format === 'string' ? TEXT_TYPE_TO_FORMAT[format] : format;
|
|
5123
5240
|
return self;
|
|
5124
|
-
}
|
|
5241
|
+
}
|
|
5242
|
+
/**
|
|
5243
|
+
* Sets the node detail to the provided TextDetailType or 32-bit integer. Note that the TextDetailType
|
|
5244
|
+
* version of the argument can only specify one detail value and doing so will remove all other detail values that
|
|
5245
|
+
* may be applied to the node. For toggling behavior, consider using {@link TextNode.toggleDirectionless}
|
|
5246
|
+
* or {@link TextNode.togglerUnmergeable}
|
|
5247
|
+
*
|
|
5248
|
+
* @param detail - TextDetailType or 32-bit integer representing the node detail.
|
|
5249
|
+
*
|
|
5250
|
+
* @returns this TextNode.
|
|
5251
|
+
* // TODO 0.12 This should just be a `string`.
|
|
5252
|
+
*/
|
|
5125
5253
|
|
|
5126
5254
|
|
|
5127
5255
|
setDetail(detail) {
|
|
@@ -5129,29 +5257,65 @@ class TextNode extends LexicalNode {
|
|
|
5129
5257
|
self.__detail = typeof detail === 'string' ? DETAIL_TYPE_TO_DETAIL[detail] : detail;
|
|
5130
5258
|
return self;
|
|
5131
5259
|
}
|
|
5260
|
+
/**
|
|
5261
|
+
* Sets the node style to the provided CSSText-like string. Set this property as you
|
|
5262
|
+
* would an HTMLElement style attribute to apply inline styles to the underlying DOM Element.
|
|
5263
|
+
*
|
|
5264
|
+
* @param style - CSSText to be applied to the underlying HTMLElement.
|
|
5265
|
+
*
|
|
5266
|
+
* @returns this TextNode.
|
|
5267
|
+
*/
|
|
5268
|
+
|
|
5132
5269
|
|
|
5133
5270
|
setStyle(style) {
|
|
5134
5271
|
const self = this.getWritable();
|
|
5135
5272
|
self.__style = style;
|
|
5136
5273
|
return self;
|
|
5137
5274
|
}
|
|
5275
|
+
/**
|
|
5276
|
+
* Applies the provided format to this TextNode if it's not present. Removes it if it is present.
|
|
5277
|
+
* Prefer using this method to turn specific formats on and off.
|
|
5278
|
+
*
|
|
5279
|
+
* @param type - TextFormatType to toggle.
|
|
5280
|
+
*
|
|
5281
|
+
* @returns this TextNode.
|
|
5282
|
+
*/
|
|
5283
|
+
|
|
5138
5284
|
|
|
5139
5285
|
toggleFormat(type) {
|
|
5140
5286
|
const formatFlag = TEXT_TYPE_TO_FORMAT[type];
|
|
5141
5287
|
return this.setFormat(this.getFormat() ^ formatFlag);
|
|
5142
5288
|
}
|
|
5289
|
+
/**
|
|
5290
|
+
* Toggles the directionless detail value of the node. Prefer using this method over setDetail.
|
|
5291
|
+
*
|
|
5292
|
+
* @returns this TextNode.
|
|
5293
|
+
*/
|
|
5294
|
+
|
|
5143
5295
|
|
|
5144
5296
|
toggleDirectionless() {
|
|
5145
5297
|
const self = this.getWritable();
|
|
5146
5298
|
self.__detail ^= IS_DIRECTIONLESS;
|
|
5147
5299
|
return self;
|
|
5148
5300
|
}
|
|
5301
|
+
/**
|
|
5302
|
+
* Toggles the unmergeable detail value of the node. Prefer using this method over setDetail.
|
|
5303
|
+
*
|
|
5304
|
+
* @returns this TextNode.
|
|
5305
|
+
*/
|
|
5306
|
+
|
|
5149
5307
|
|
|
5150
5308
|
toggleUnmergeable() {
|
|
5151
5309
|
const self = this.getWritable();
|
|
5152
5310
|
self.__detail ^= IS_UNMERGEABLE;
|
|
5153
5311
|
return self;
|
|
5154
5312
|
}
|
|
5313
|
+
/**
|
|
5314
|
+
* Sets the mode of the node.
|
|
5315
|
+
*
|
|
5316
|
+
* @returns this TextNode.
|
|
5317
|
+
*/
|
|
5318
|
+
|
|
5155
5319
|
|
|
5156
5320
|
setMode(type) {
|
|
5157
5321
|
const mode = TEXT_MODE_TO_TYPE[type];
|
|
@@ -5164,6 +5328,14 @@ class TextNode extends LexicalNode {
|
|
|
5164
5328
|
self.__mode = mode;
|
|
5165
5329
|
return self;
|
|
5166
5330
|
}
|
|
5331
|
+
/**
|
|
5332
|
+
* Sets the text content of the node.
|
|
5333
|
+
*
|
|
5334
|
+
* @param text - the string to set as the text value of the node.
|
|
5335
|
+
*
|
|
5336
|
+
* @returns this TextNode.
|
|
5337
|
+
*/
|
|
5338
|
+
|
|
5167
5339
|
|
|
5168
5340
|
setTextContent(text) {
|
|
5169
5341
|
if (this.__text === text) {
|
|
@@ -5174,6 +5346,15 @@ class TextNode extends LexicalNode {
|
|
|
5174
5346
|
self.__text = text;
|
|
5175
5347
|
return self;
|
|
5176
5348
|
}
|
|
5349
|
+
/**
|
|
5350
|
+
* Sets the current Lexical selection to be a RangeSelection with anchor and focus on this TextNode at the provided offsets.
|
|
5351
|
+
*
|
|
5352
|
+
* @param _anchorOffset - the offset at which the Selection anchor will be placed.
|
|
5353
|
+
* @param _focusOffset - the offset at which the Selection focus will be placed.
|
|
5354
|
+
*
|
|
5355
|
+
* @returns the new RangeSelection.
|
|
5356
|
+
*/
|
|
5357
|
+
|
|
5177
5358
|
|
|
5178
5359
|
select(_anchorOffset, _focusOffset) {
|
|
5179
5360
|
errorOnReadOnly();
|
|
@@ -5212,6 +5393,18 @@ class TextNode extends LexicalNode {
|
|
|
5212
5393
|
|
|
5213
5394
|
return selection;
|
|
5214
5395
|
}
|
|
5396
|
+
/**
|
|
5397
|
+
* Inserts the provided text into this TextNode at the provided offset, deleting the number of characters
|
|
5398
|
+
* specified. Can optionally calculate a new selection after the operation is complete.
|
|
5399
|
+
*
|
|
5400
|
+
* @param offset - the offset at which the splice operation should begin.
|
|
5401
|
+
* @param delCount - the number of characters to delete, starting from the offset.
|
|
5402
|
+
* @param newText - the text to insert into the TextNode at the offset.
|
|
5403
|
+
* @param moveSelection - optional, whether or not to move selection to the end of the inserted substring.
|
|
5404
|
+
*
|
|
5405
|
+
* @returns this TextNode.
|
|
5406
|
+
*/
|
|
5407
|
+
|
|
5215
5408
|
|
|
5216
5409
|
spliceText(offset, delCount, newText, moveSelection) {
|
|
5217
5410
|
const writableSelf = this.getWritable();
|
|
@@ -5238,14 +5431,39 @@ class TextNode extends LexicalNode {
|
|
|
5238
5431
|
writableSelf.__text = updatedText;
|
|
5239
5432
|
return writableSelf;
|
|
5240
5433
|
}
|
|
5434
|
+
/**
|
|
5435
|
+
* This method is meant to be overriden by TextNode subclasses to control the behavior of those nodes
|
|
5436
|
+
* when a user event would cause text to be inserted before them in the editor. If true, Lexical will attempt
|
|
5437
|
+
* to insert text into this node. If false, it will insert the text in a new sibling node.
|
|
5438
|
+
*
|
|
5439
|
+
* @returns true if text can be inserted before the node, false otherwise.
|
|
5440
|
+
*/
|
|
5441
|
+
|
|
5241
5442
|
|
|
5242
5443
|
canInsertTextBefore() {
|
|
5243
5444
|
return true;
|
|
5244
5445
|
}
|
|
5446
|
+
/**
|
|
5447
|
+
* This method is meant to be overriden by TextNode subclasses to control the behavior of those nodes
|
|
5448
|
+
* when a user event would cause text to be inserted after them in the editor. If true, Lexical will attempt
|
|
5449
|
+
* to insert text into this node. If false, it will insert the text in a new sibling node.
|
|
5450
|
+
*
|
|
5451
|
+
* @returns true if text can be inserted after the node, false otherwise.
|
|
5452
|
+
*/
|
|
5453
|
+
|
|
5245
5454
|
|
|
5246
5455
|
canInsertTextAfter() {
|
|
5247
5456
|
return true;
|
|
5248
5457
|
}
|
|
5458
|
+
/**
|
|
5459
|
+
* Splits this TextNode at the provided character offsets, forming new TextNodes from the substrings
|
|
5460
|
+
* formed by the split, and inserting those new TextNodes into the editor, replacing the one that was split.
|
|
5461
|
+
*
|
|
5462
|
+
* @param splitOffsets - rest param of the text content character offsets at which this node should be split.
|
|
5463
|
+
*
|
|
5464
|
+
* @returns an Array containing the newly-created TextNodes.
|
|
5465
|
+
*/
|
|
5466
|
+
|
|
5249
5467
|
|
|
5250
5468
|
splitText(...splitOffsets) {
|
|
5251
5469
|
errorOnReadOnly();
|
|
@@ -5359,6 +5577,14 @@ class TextNode extends LexicalNode {
|
|
|
5359
5577
|
|
|
5360
5578
|
return splitNodes;
|
|
5361
5579
|
}
|
|
5580
|
+
/**
|
|
5581
|
+
* Merges the target TextNode into this TextNode, removing the target node.
|
|
5582
|
+
*
|
|
5583
|
+
* @param target - the TextNode to merge into this one.
|
|
5584
|
+
*
|
|
5585
|
+
* @returns this TextNode.
|
|
5586
|
+
*/
|
|
5587
|
+
|
|
5362
5588
|
|
|
5363
5589
|
mergeWithSibling(target) {
|
|
5364
5590
|
const isBefore = target === this.getPreviousSibling();
|
|
@@ -5403,6 +5629,14 @@ class TextNode extends LexicalNode {
|
|
|
5403
5629
|
target.remove();
|
|
5404
5630
|
return writableSelf;
|
|
5405
5631
|
}
|
|
5632
|
+
/**
|
|
5633
|
+
* This method is meant to be overriden by TextNode subclasses to control the behavior of those nodes
|
|
5634
|
+
* when used with the registerLexicalTextEntity function. If you're using registerLexicalTextEntity, the
|
|
5635
|
+
* node class that you create and replace matched text with should return true from this method.
|
|
5636
|
+
*
|
|
5637
|
+
* @returns true if the node is to be treated as a "text entity", false otherwise.
|
|
5638
|
+
*/
|
|
5639
|
+
|
|
5406
5640
|
|
|
5407
5641
|
isTextEntity() {
|
|
5408
5642
|
return false;
|
|
@@ -5895,7 +6129,14 @@ function $transferStartingElementPointToTextPoint(start, end, format, style) {
|
|
|
5895
6129
|
if (placementNode === null) {
|
|
5896
6130
|
element.append(target);
|
|
5897
6131
|
} else {
|
|
5898
|
-
placementNode.insertBefore(target);
|
|
6132
|
+
placementNode.insertBefore(target); // Fix the end point offset if it refers to the same element as start,
|
|
6133
|
+
// as we've now inserted another element before it. Note that we only
|
|
6134
|
+
// do it if selection is not collapsed as otherwise it'll transfer
|
|
6135
|
+
// both focus and anchor to the text node below
|
|
6136
|
+
|
|
6137
|
+
if (end.type === 'element' && end.key === start.key && end.offset !== start.offset) {
|
|
6138
|
+
end.set(end.key, end.offset + 1, 'element');
|
|
6139
|
+
}
|
|
5899
6140
|
} // Transfer the element point to a text point.
|
|
5900
6141
|
|
|
5901
6142
|
|
|
@@ -6287,6 +6528,13 @@ class RangeSelection {
|
|
|
6287
6528
|
anchor._selection = this;
|
|
6288
6529
|
focus._selection = this;
|
|
6289
6530
|
}
|
|
6531
|
+
/**
|
|
6532
|
+
* Used to check if the provided selections is equal to this one by value,
|
|
6533
|
+
* inluding anchor, focus, format, and style properties.
|
|
6534
|
+
* @param selection - the Selection to compare this one to.
|
|
6535
|
+
* @returns true if the Selections are equal, false otherwise.
|
|
6536
|
+
*/
|
|
6537
|
+
|
|
6290
6538
|
|
|
6291
6539
|
is(selection) {
|
|
6292
6540
|
if (!$isRangeSelection(selection)) {
|
|
@@ -6295,14 +6543,34 @@ class RangeSelection {
|
|
|
6295
6543
|
|
|
6296
6544
|
return this.anchor.is(selection.anchor) && this.focus.is(selection.focus) && this.format === selection.format && this.style === selection.style;
|
|
6297
6545
|
}
|
|
6546
|
+
/**
|
|
6547
|
+
* Returns whether the Selection is "backwards", meaning the focus
|
|
6548
|
+
* logically precedes the anchor in the EditorState.
|
|
6549
|
+
* @returns true if the Selection is backwards, false otherwise.
|
|
6550
|
+
*/
|
|
6551
|
+
|
|
6298
6552
|
|
|
6299
6553
|
isBackward() {
|
|
6300
6554
|
return this.focus.isBefore(this.anchor);
|
|
6301
6555
|
}
|
|
6556
|
+
/**
|
|
6557
|
+
* Returns whether the Selection is "collapsed", meaning the anchor and focus are
|
|
6558
|
+
* the same node and have the same offset.
|
|
6559
|
+
*
|
|
6560
|
+
* @returns true if the Selection is collapsed, false otherwise.
|
|
6561
|
+
*/
|
|
6562
|
+
|
|
6302
6563
|
|
|
6303
6564
|
isCollapsed() {
|
|
6304
6565
|
return this.anchor.is(this.focus);
|
|
6305
6566
|
}
|
|
6567
|
+
/**
|
|
6568
|
+
* Gets all the nodes in the Selection. Uses caching to make it generally suitable
|
|
6569
|
+
* for use in hot paths.
|
|
6570
|
+
*
|
|
6571
|
+
* @returns an Array containing all the nodes in the Selection
|
|
6572
|
+
*/
|
|
6573
|
+
|
|
6306
6574
|
|
|
6307
6575
|
getNodes() {
|
|
6308
6576
|
const cachedNodes = this._cachedNodes;
|
|
@@ -6355,6 +6623,15 @@ class RangeSelection {
|
|
|
6355
6623
|
|
|
6356
6624
|
return nodes;
|
|
6357
6625
|
}
|
|
6626
|
+
/**
|
|
6627
|
+
* Sets this Selection to be of type "text" at the provided anchor and focus values.
|
|
6628
|
+
*
|
|
6629
|
+
* @param anchorNode - the anchor node to set on the Selection
|
|
6630
|
+
* @param anchorOffset - the offset to set on the Selection
|
|
6631
|
+
* @param focusNode - the focus node to set on the Selection
|
|
6632
|
+
* @param focusOffset - the focus offset to set on the Selection
|
|
6633
|
+
*/
|
|
6634
|
+
|
|
6358
6635
|
|
|
6359
6636
|
setTextNodeRange(anchorNode, anchorOffset, focusNode, focusOffset) {
|
|
6360
6637
|
$setPointValues(this.anchor, anchorNode.__key, anchorOffset, 'text');
|
|
@@ -6362,6 +6639,12 @@ class RangeSelection {
|
|
|
6362
6639
|
this._cachedNodes = null;
|
|
6363
6640
|
this.dirty = true;
|
|
6364
6641
|
}
|
|
6642
|
+
/**
|
|
6643
|
+
* Gets the (plain) text content of all the nodes in the selection.
|
|
6644
|
+
*
|
|
6645
|
+
* @returns a string representing the text content of all the nodes in the Selection
|
|
6646
|
+
*/
|
|
6647
|
+
|
|
6365
6648
|
|
|
6366
6649
|
getTextContent() {
|
|
6367
6650
|
const nodes = this.getNodes();
|
|
@@ -6419,6 +6702,13 @@ class RangeSelection {
|
|
|
6419
6702
|
|
|
6420
6703
|
return textContent;
|
|
6421
6704
|
}
|
|
6705
|
+
/**
|
|
6706
|
+
* Attempts to map a DOM selection range onto this Lexical Selection,
|
|
6707
|
+
* setting the anchor, focus, and type accordingly
|
|
6708
|
+
*
|
|
6709
|
+
* @param range a DOM Selection range conforming to the StaticRange interface.
|
|
6710
|
+
*/
|
|
6711
|
+
|
|
6422
6712
|
|
|
6423
6713
|
applyDOMRange(range) {
|
|
6424
6714
|
const editor = getActiveEditor();
|
|
@@ -6435,6 +6725,12 @@ class RangeSelection {
|
|
|
6435
6725
|
$setPointValues(this.focus, focusPoint.key, focusPoint.offset, focusPoint.type);
|
|
6436
6726
|
this._cachedNodes = null;
|
|
6437
6727
|
}
|
|
6728
|
+
/**
|
|
6729
|
+
* Creates a new RangeSelection, copying over all the property values from this one.
|
|
6730
|
+
*
|
|
6731
|
+
* @returns a new RangeSelection with the same property values as this one.
|
|
6732
|
+
*/
|
|
6733
|
+
|
|
6438
6734
|
|
|
6439
6735
|
clone() {
|
|
6440
6736
|
const anchor = this.anchor;
|
|
@@ -6442,21 +6738,48 @@ class RangeSelection {
|
|
|
6442
6738
|
const selection = new RangeSelection($createPoint(anchor.key, anchor.offset, anchor.type), $createPoint(focus.key, focus.offset, focus.type), this.format, this.style);
|
|
6443
6739
|
return selection;
|
|
6444
6740
|
}
|
|
6741
|
+
/**
|
|
6742
|
+
* Toggles the provided format on all the TextNodes in the Selection.
|
|
6743
|
+
*
|
|
6744
|
+
* @param format a string TextFormatType to toggle on the TextNodes in the selection
|
|
6745
|
+
*/
|
|
6746
|
+
|
|
6445
6747
|
|
|
6446
6748
|
toggleFormat(format) {
|
|
6447
6749
|
this.format = toggleTextFormatType(this.format, format, null);
|
|
6448
6750
|
this.dirty = true;
|
|
6449
6751
|
}
|
|
6752
|
+
/**
|
|
6753
|
+
* Sets the value of the style property on the Selection
|
|
6754
|
+
*
|
|
6755
|
+
* @param style - the style to set at the value of the style property.
|
|
6756
|
+
*/
|
|
6757
|
+
|
|
6450
6758
|
|
|
6451
6759
|
setStyle(style) {
|
|
6452
6760
|
this.style = style;
|
|
6453
6761
|
this.dirty = true;
|
|
6454
6762
|
}
|
|
6763
|
+
/**
|
|
6764
|
+
* Returns whether the provided TextFormatType is present on the Selection. This will be true if any node in the Selection
|
|
6765
|
+
* has the specified format.
|
|
6766
|
+
*
|
|
6767
|
+
* @param type the TextFormatType to check for.
|
|
6768
|
+
* @returns true if the provided format is currently toggled on on the Selection, false otherwise.
|
|
6769
|
+
*/
|
|
6770
|
+
|
|
6455
6771
|
|
|
6456
6772
|
hasFormat(type) {
|
|
6457
6773
|
const formatFlag = TEXT_TYPE_TO_FORMAT[type];
|
|
6458
6774
|
return (this.format & formatFlag) !== 0;
|
|
6459
6775
|
}
|
|
6776
|
+
/**
|
|
6777
|
+
* Attempts to insert the provided text into the EditorState at the current Selection.
|
|
6778
|
+
* converts tabs, newlines, and carriage returns into LexicalNodes.
|
|
6779
|
+
*
|
|
6780
|
+
* @param text the text to insert into the Selection
|
|
6781
|
+
*/
|
|
6782
|
+
|
|
6460
6783
|
|
|
6461
6784
|
insertRawText(text) {
|
|
6462
6785
|
const parts = text.split(/(\r?\n|\t)/);
|
|
@@ -6477,6 +6800,13 @@ class RangeSelection {
|
|
|
6477
6800
|
|
|
6478
6801
|
this.insertNodes(nodes);
|
|
6479
6802
|
}
|
|
6803
|
+
/**
|
|
6804
|
+
* Attempts to insert the provided text into the EditorState at the current Selection as a new
|
|
6805
|
+
* Lexical TextNode, according to a series of insertion heuristics based on the selection type and position.
|
|
6806
|
+
*
|
|
6807
|
+
* @param text the text to insert into the Selection
|
|
6808
|
+
*/
|
|
6809
|
+
|
|
6480
6810
|
|
|
6481
6811
|
insertText(text) {
|
|
6482
6812
|
const anchor = this.anchor;
|
|
@@ -6752,10 +7082,21 @@ class RangeSelection {
|
|
|
6752
7082
|
}
|
|
6753
7083
|
}
|
|
6754
7084
|
}
|
|
7085
|
+
/**
|
|
7086
|
+
* Removes the text in the Selection, adjusting the EditorState accordingly.
|
|
7087
|
+
*/
|
|
7088
|
+
|
|
6755
7089
|
|
|
6756
7090
|
removeText() {
|
|
6757
7091
|
this.insertText('');
|
|
6758
7092
|
}
|
|
7093
|
+
/**
|
|
7094
|
+
* Applies the provided format to the TextNodes in the Selection, splitting or
|
|
7095
|
+
* merging nodes as necessary.
|
|
7096
|
+
*
|
|
7097
|
+
* @param formatType the format type to apply to the nodes in the Selection.
|
|
7098
|
+
*/
|
|
7099
|
+
|
|
6759
7100
|
|
|
6760
7101
|
formatText(formatType) {
|
|
6761
7102
|
if (this.isCollapsed()) {
|
|
@@ -6876,6 +7217,16 @@ class RangeSelection {
|
|
|
6876
7217
|
|
|
6877
7218
|
this.format = firstNextFormat | lastNextFormat;
|
|
6878
7219
|
}
|
|
7220
|
+
/**
|
|
7221
|
+
* Attempts to "intelligently" insert an arbitrary list of Lexical nodes into the EditorState at the
|
|
7222
|
+
* current Selection according to a set of heuristics that determine how surrounding nodes
|
|
7223
|
+
* should be changed, replaced, or moved to accomodate the incoming ones.
|
|
7224
|
+
*
|
|
7225
|
+
* @param nodes - the nodes to insert
|
|
7226
|
+
* @param selectStart - whether or not to select the start after the insertion.
|
|
7227
|
+
* @returns true if the nodes were inserted successfully, false otherwise.
|
|
7228
|
+
*/
|
|
7229
|
+
|
|
6879
7230
|
|
|
6880
7231
|
insertNodes(nodes, selectStart) {
|
|
6881
7232
|
// If there is a range selected remove the text in it
|
|
@@ -7238,6 +7589,10 @@ class RangeSelection {
|
|
|
7238
7589
|
|
|
7239
7590
|
return true;
|
|
7240
7591
|
}
|
|
7592
|
+
/**
|
|
7593
|
+
* Inserts a new ParagraphNode into the EditorState at the current Selection
|
|
7594
|
+
*/
|
|
7595
|
+
|
|
7241
7596
|
|
|
7242
7597
|
insertParagraph() {
|
|
7243
7598
|
if (!this.isCollapsed()) {
|
|
@@ -7359,6 +7714,13 @@ class RangeSelection {
|
|
|
7359
7714
|
}
|
|
7360
7715
|
}
|
|
7361
7716
|
}
|
|
7717
|
+
/**
|
|
7718
|
+
* Inserts a logical linebreak, which may be a new LineBreakNode or a new ParagraphNode, into the EditorState at the
|
|
7719
|
+
* current Selection.
|
|
7720
|
+
*
|
|
7721
|
+
* @param selectStart whether or not to select the start of the insertion range after the operation completes.
|
|
7722
|
+
*/
|
|
7723
|
+
|
|
7362
7724
|
|
|
7363
7725
|
insertLineBreak(selectStart) {
|
|
7364
7726
|
const lineBreakNode = $createLineBreakNode();
|
|
@@ -7380,10 +7742,24 @@ class RangeSelection {
|
|
|
7380
7742
|
}
|
|
7381
7743
|
}
|
|
7382
7744
|
}
|
|
7745
|
+
/**
|
|
7746
|
+
* Returns the character-based offsets of the Selection, accounting for non-text Points
|
|
7747
|
+
* by using the children size or text content.
|
|
7748
|
+
*
|
|
7749
|
+
* @returns the character offsets for the Selection
|
|
7750
|
+
*/
|
|
7751
|
+
|
|
7383
7752
|
|
|
7384
7753
|
getCharacterOffsets() {
|
|
7385
7754
|
return getCharacterOffsets(this);
|
|
7386
7755
|
}
|
|
7756
|
+
/**
|
|
7757
|
+
* Extracts the nodes in the Selection, splitting nodes where necessary
|
|
7758
|
+
* to get offset-level precision.
|
|
7759
|
+
*
|
|
7760
|
+
* @returns The nodes in the Selection
|
|
7761
|
+
*/
|
|
7762
|
+
|
|
7387
7763
|
|
|
7388
7764
|
extract() {
|
|
7389
7765
|
const selectedNodes = this.getNodes();
|
|
@@ -7437,6 +7813,16 @@ class RangeSelection {
|
|
|
7437
7813
|
|
|
7438
7814
|
return selectedNodes;
|
|
7439
7815
|
}
|
|
7816
|
+
/**
|
|
7817
|
+
* Modifies the Selection according to the parameters and a set of heuristics that account for
|
|
7818
|
+
* various node types. Can be used to safely move or extend selection by one logical "unit" without
|
|
7819
|
+
* dealing explicitly with all the possible node types.
|
|
7820
|
+
*
|
|
7821
|
+
* @param alter the type of modification to perform
|
|
7822
|
+
* @param isBackward whether or not selection is backwards
|
|
7823
|
+
* @param granularity the granularity at which to apply the modification
|
|
7824
|
+
*/
|
|
7825
|
+
|
|
7440
7826
|
|
|
7441
7827
|
modify(alter, isBackward, granularity) {
|
|
7442
7828
|
const focus = this.focus;
|
|
@@ -7573,6 +7959,13 @@ class RangeSelection {
|
|
|
7573
7959
|
}
|
|
7574
7960
|
}
|
|
7575
7961
|
}
|
|
7962
|
+
/**
|
|
7963
|
+
* Performs one logical character deletion operation on the EditorState based on the current Selection.
|
|
7964
|
+
* Handles different node types.
|
|
7965
|
+
*
|
|
7966
|
+
* @param isBackward whether or not the selection is backwards.
|
|
7967
|
+
*/
|
|
7968
|
+
|
|
7576
7969
|
|
|
7577
7970
|
deleteCharacter(isBackward) {
|
|
7578
7971
|
const wasCollapsed = this.isCollapsed();
|
|
@@ -7605,6 +7998,8 @@ class RangeSelection {
|
|
|
7605
7998
|
$setSelection(nodeSelection);
|
|
7606
7999
|
} else {
|
|
7607
8000
|
possibleNode.remove();
|
|
8001
|
+
const editor = getActiveEditor();
|
|
8002
|
+
editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
|
|
7608
8003
|
}
|
|
7609
8004
|
|
|
7610
8005
|
return;
|
|
@@ -7659,6 +8054,13 @@ class RangeSelection {
|
|
|
7659
8054
|
}
|
|
7660
8055
|
}
|
|
7661
8056
|
}
|
|
8057
|
+
/**
|
|
8058
|
+
* Performs one logical line deletion operation on the EditorState based on the current Selection.
|
|
8059
|
+
* Handles different node types.
|
|
8060
|
+
*
|
|
8061
|
+
* @param isBackward whether or not the selection is backwards.
|
|
8062
|
+
*/
|
|
8063
|
+
|
|
7662
8064
|
|
|
7663
8065
|
deleteLine(isBackward) {
|
|
7664
8066
|
if (this.isCollapsed()) {
|
|
@@ -7678,6 +8080,13 @@ class RangeSelection {
|
|
|
7678
8080
|
|
|
7679
8081
|
this.removeText();
|
|
7680
8082
|
}
|
|
8083
|
+
/**
|
|
8084
|
+
* Performs one logical word deletion operation on the EditorState based on the current Selection.
|
|
8085
|
+
* Handles different node types.
|
|
8086
|
+
*
|
|
8087
|
+
* @param isBackward whether or not the selection is backwards.
|
|
8088
|
+
*/
|
|
8089
|
+
|
|
7681
8090
|
|
|
7682
8091
|
deleteWord(isBackward) {
|
|
7683
8092
|
if (this.isCollapsed()) {
|
|
@@ -7726,7 +8135,7 @@ function $swapPoints(selection) {
|
|
|
7726
8135
|
}
|
|
7727
8136
|
|
|
7728
8137
|
function moveNativeSelection(domSelection, alter, direction, granularity) {
|
|
7729
|
-
//
|
|
8138
|
+
// Selection.modify() method applies a change to the current selection or cursor position,
|
|
7730
8139
|
// but is still non-standard in some browsers.
|
|
7731
8140
|
domSelection.modify(alter, direction, granularity);
|
|
7732
8141
|
}
|
|
@@ -8396,7 +8805,7 @@ function updateDOMSelection(prevSelection, nextSelection, editor, domSelection,
|
|
|
8396
8805
|
markSelectionChangeFromDOMUpdate();
|
|
8397
8806
|
}
|
|
8398
8807
|
function $insertNodes(nodes, selectStart) {
|
|
8399
|
-
let selection = $getSelection();
|
|
8808
|
+
let selection = $getSelection() || $getPreviousSelection();
|
|
8400
8809
|
|
|
8401
8810
|
if (selection === null) {
|
|
8402
8811
|
selection = $getRoot().selectEnd();
|
|
@@ -8825,7 +9234,7 @@ function handleDEVOnlyPendingUpdateGuarantees(pendingEditorState) {
|
|
|
8825
9234
|
};
|
|
8826
9235
|
}
|
|
8827
9236
|
|
|
8828
|
-
function commitPendingUpdates(editor) {
|
|
9237
|
+
function commitPendingUpdates(editor, recoveryEditorState) {
|
|
8829
9238
|
const pendingEditorState = editor._pendingEditorState;
|
|
8830
9239
|
const rootElement = editor._rootElement;
|
|
8831
9240
|
const shouldSkipDOM = editor._headless || rootElement === null;
|
|
@@ -8875,7 +9284,7 @@ function commitPendingUpdates(editor) {
|
|
|
8875
9284
|
initMutationObserver(editor);
|
|
8876
9285
|
editor._dirtyType = FULL_RECONCILE;
|
|
8877
9286
|
isAttemptingToRecoverFromReconcilerError = true;
|
|
8878
|
-
commitPendingUpdates(editor);
|
|
9287
|
+
commitPendingUpdates(editor, currentEditorState);
|
|
8879
9288
|
isAttemptingToRecoverFromReconcilerError = false;
|
|
8880
9289
|
} else {
|
|
8881
9290
|
// To avoid a possible situation of infinite loops, lets throw
|
|
@@ -8963,7 +9372,7 @@ function commitPendingUpdates(editor) {
|
|
|
8963
9372
|
}
|
|
8964
9373
|
|
|
8965
9374
|
if (mutatedNodes !== null) {
|
|
8966
|
-
triggerMutationListeners(editor,
|
|
9375
|
+
triggerMutationListeners(editor, mutatedNodes, tags, dirtyLeaves);
|
|
8967
9376
|
}
|
|
8968
9377
|
|
|
8969
9378
|
if (!$isRangeSelection(pendingSelection) && pendingSelection !== null && (currentSelection === null || !currentSelection.is(pendingSelection))) {
|
|
@@ -8980,15 +9389,20 @@ function commitPendingUpdates(editor) {
|
|
|
8980
9389
|
editor._decorators = pendingDecorators;
|
|
8981
9390
|
editor._pendingDecorators = null;
|
|
8982
9391
|
triggerListeners('decorator', editor, true, pendingDecorators);
|
|
8983
|
-
}
|
|
9392
|
+
} // If reconciler fails, we reset whole editor (so current editor state becomes empty)
|
|
9393
|
+
// and attempt to re-render pendingEditorState. If that goes through we trigger
|
|
9394
|
+
// listeners, but instead use recoverEditorState which is current editor state before reset
|
|
9395
|
+
// This specifically important for collab that relies on prevEditorState from update
|
|
9396
|
+
// listener to calculate delta of changed nodes/properties
|
|
9397
|
+
|
|
8984
9398
|
|
|
8985
|
-
triggerTextContentListeners(editor, currentEditorState, pendingEditorState);
|
|
9399
|
+
triggerTextContentListeners(editor, recoveryEditorState || currentEditorState, pendingEditorState);
|
|
8986
9400
|
triggerListeners('update', editor, true, {
|
|
8987
9401
|
dirtyElements,
|
|
8988
9402
|
dirtyLeaves,
|
|
8989
9403
|
editorState: pendingEditorState,
|
|
8990
9404
|
normalizedNodes,
|
|
8991
|
-
prevEditorState: currentEditorState,
|
|
9405
|
+
prevEditorState: recoveryEditorState || currentEditorState,
|
|
8992
9406
|
tags
|
|
8993
9407
|
});
|
|
8994
9408
|
triggerDeferredUpdateCallbacks(editor, deferred);
|
|
@@ -9004,7 +9418,7 @@ function triggerTextContentListeners(editor, currentEditorState, pendingEditorSt
|
|
|
9004
9418
|
}
|
|
9005
9419
|
}
|
|
9006
9420
|
|
|
9007
|
-
function triggerMutationListeners(editor,
|
|
9421
|
+
function triggerMutationListeners(editor, mutatedNodes, updateTags, dirtyLeaves) {
|
|
9008
9422
|
const listeners = Array.from(editor._listeners.mutation);
|
|
9009
9423
|
const listenersLength = listeners.length;
|
|
9010
9424
|
|
|
@@ -10101,7 +10515,7 @@ function exportNodeToJSON(node) {
|
|
|
10101
10515
|
|
|
10102
10516
|
if (serializedNode.type !== nodeClass.getType()) {
|
|
10103
10517
|
{
|
|
10104
|
-
throw Error(`LexicalNode: Node ${nodeClass.name} does not
|
|
10518
|
+
throw Error(`LexicalNode: Node ${nodeClass.name} does not match the serialized type. Check if .exportJSON() is implemented and it is returning the correct type.`);
|
|
10105
10519
|
}
|
|
10106
10520
|
} // @ts-expect-error TODO Replace Class utility type with InstanceType
|
|
10107
10521
|
|
|
@@ -10825,6 +11239,16 @@ class LexicalEditor {
|
|
|
10825
11239
|
registeredNodes.forEach(node => node.transforms.delete(listener));
|
|
10826
11240
|
};
|
|
10827
11241
|
}
|
|
11242
|
+
/**
|
|
11243
|
+
* Used to assert that a certain node is registered, usually by plugins to ensure nodes that they
|
|
11244
|
+
* depend on have been registered.
|
|
11245
|
+
* @returns True if the editor has registered the provided node type, false otherwise.
|
|
11246
|
+
*/
|
|
11247
|
+
|
|
11248
|
+
|
|
11249
|
+
hasNode(node) {
|
|
11250
|
+
return this._nodes.has(node.getType());
|
|
11251
|
+
}
|
|
10828
11252
|
/**
|
|
10829
11253
|
* Used to assert that certain nodes are registered, usually by plugins to ensure nodes that they
|
|
10830
11254
|
* depend on have been registered.
|
|
@@ -10833,16 +11257,7 @@ class LexicalEditor {
|
|
|
10833
11257
|
|
|
10834
11258
|
|
|
10835
11259
|
hasNodes(nodes) {
|
|
10836
|
-
|
|
10837
|
-
const klass = nodes[i];
|
|
10838
|
-
const type = klass.getType();
|
|
10839
|
-
|
|
10840
|
-
if (!this._nodes.has(type)) {
|
|
10841
|
-
return false;
|
|
10842
|
-
}
|
|
10843
|
-
}
|
|
10844
|
-
|
|
10845
|
-
return true;
|
|
11260
|
+
return nodes.every(this.hasNode.bind(this));
|
|
10846
11261
|
}
|
|
10847
11262
|
/**
|
|
10848
11263
|
* Dispatches a command of the specified type with the specified payload.
|
|
@@ -10934,6 +11349,10 @@ class LexicalEditor {
|
|
|
10934
11349
|
nextRootElement.classList.add(...classNames);
|
|
10935
11350
|
}
|
|
10936
11351
|
} else {
|
|
11352
|
+
// If content editable is unmounted we'll reset editor state back to original
|
|
11353
|
+
// (or pending) editor state since there will be no reconciliation
|
|
11354
|
+
this._editorState = pendingEditorState;
|
|
11355
|
+
this._pendingEditorState = null;
|
|
10937
11356
|
this._window = null;
|
|
10938
11357
|
}
|
|
10939
11358
|
|