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