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