@lexical/selection 0.12.1 → 0.12.3
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/LexicalSelection.dev.js +84 -223
- package/LexicalSelection.prod.js +23 -23
- package/package.json +2 -2
package/LexicalSelection.dev.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
9
|
var lexical = require('lexical');
|
|
10
|
+
var utils = require('@lexical/utils');
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
@@ -24,30 +25,24 @@ const CSS_TO_STYLES = new Map();
|
|
|
24
25
|
* LICENSE file in the root directory of this source tree.
|
|
25
26
|
*
|
|
26
27
|
*/
|
|
27
|
-
|
|
28
28
|
function getDOMTextNode(element) {
|
|
29
29
|
let node = element;
|
|
30
|
-
|
|
31
30
|
while (node != null) {
|
|
32
31
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
33
32
|
return node;
|
|
34
33
|
}
|
|
35
|
-
|
|
36
34
|
node = node.firstChild;
|
|
37
35
|
}
|
|
38
|
-
|
|
39
36
|
return null;
|
|
40
37
|
}
|
|
41
|
-
|
|
42
38
|
function getDOMIndexWithinParent(node) {
|
|
43
39
|
const parent = node.parentNode;
|
|
44
|
-
|
|
45
40
|
if (parent == null) {
|
|
46
41
|
throw new Error('Should never happen');
|
|
47
42
|
}
|
|
48
|
-
|
|
49
43
|
return [parent, Array.from(parent.childNodes).indexOf(node)];
|
|
50
44
|
}
|
|
45
|
+
|
|
51
46
|
/**
|
|
52
47
|
* Creates a selection range for the DOM.
|
|
53
48
|
* @param editor - The lexical editor.
|
|
@@ -57,8 +52,6 @@ function getDOMIndexWithinParent(node) {
|
|
|
57
52
|
* @param _focusOffset - The amount of space offset from the focus to the anchor.
|
|
58
53
|
* @returns The range of selection for the DOM that was created.
|
|
59
54
|
*/
|
|
60
|
-
|
|
61
|
-
|
|
62
55
|
function createDOMRange(editor, anchorNode, _anchorOffset, focusNode, _focusOffset) {
|
|
63
56
|
const anchorKey = anchorNode.getKey();
|
|
64
57
|
const focusKey = focusNode.getKey();
|
|
@@ -67,154 +60,129 @@ function createDOMRange(editor, anchorNode, _anchorOffset, focusNode, _focusOffs
|
|
|
67
60
|
let focusDOM = editor.getElementByKey(focusKey);
|
|
68
61
|
let anchorOffset = _anchorOffset;
|
|
69
62
|
let focusOffset = _focusOffset;
|
|
70
|
-
|
|
71
63
|
if (lexical.$isTextNode(anchorNode)) {
|
|
72
64
|
anchorDOM = getDOMTextNode(anchorDOM);
|
|
73
65
|
}
|
|
74
|
-
|
|
75
66
|
if (lexical.$isTextNode(focusNode)) {
|
|
76
67
|
focusDOM = getDOMTextNode(focusDOM);
|
|
77
68
|
}
|
|
78
|
-
|
|
79
69
|
if (anchorNode === undefined || focusNode === undefined || anchorDOM === null || focusDOM === null) {
|
|
80
70
|
return null;
|
|
81
71
|
}
|
|
82
|
-
|
|
83
72
|
if (anchorDOM.nodeName === 'BR') {
|
|
84
73
|
[anchorDOM, anchorOffset] = getDOMIndexWithinParent(anchorDOM);
|
|
85
74
|
}
|
|
86
|
-
|
|
87
75
|
if (focusDOM.nodeName === 'BR') {
|
|
88
76
|
[focusDOM, focusOffset] = getDOMIndexWithinParent(focusDOM);
|
|
89
77
|
}
|
|
90
|
-
|
|
91
78
|
const firstChild = anchorDOM.firstChild;
|
|
92
|
-
|
|
93
79
|
if (anchorDOM === focusDOM && firstChild != null && firstChild.nodeName === 'BR' && anchorOffset === 0 && focusOffset === 0) {
|
|
94
80
|
focusOffset = 1;
|
|
95
81
|
}
|
|
96
|
-
|
|
97
82
|
try {
|
|
98
83
|
range.setStart(anchorDOM, anchorOffset);
|
|
99
84
|
range.setEnd(focusDOM, focusOffset);
|
|
100
85
|
} catch (e) {
|
|
101
86
|
return null;
|
|
102
87
|
}
|
|
103
|
-
|
|
104
88
|
if (range.collapsed && (anchorOffset !== focusOffset || anchorKey !== focusKey)) {
|
|
105
89
|
// Range is backwards, we need to reverse it
|
|
106
90
|
range.setStart(focusDOM, focusOffset);
|
|
107
91
|
range.setEnd(anchorDOM, anchorOffset);
|
|
108
92
|
}
|
|
109
|
-
|
|
110
93
|
return range;
|
|
111
94
|
}
|
|
95
|
+
|
|
112
96
|
/**
|
|
113
97
|
* Creates DOMRects, generally used to help the editor find a specific location on the screen.
|
|
114
98
|
* @param editor - The lexical editor
|
|
115
99
|
* @param range - A fragment of a document that can contain nodes and parts of text nodes.
|
|
116
100
|
* @returns The selectionRects as an array.
|
|
117
101
|
*/
|
|
118
|
-
|
|
119
102
|
function createRectsFromDOMRange(editor, range) {
|
|
120
103
|
const rootElement = editor.getRootElement();
|
|
121
|
-
|
|
122
104
|
if (rootElement === null) {
|
|
123
105
|
return [];
|
|
124
106
|
}
|
|
125
|
-
|
|
126
107
|
const rootRect = rootElement.getBoundingClientRect();
|
|
127
108
|
const computedStyle = getComputedStyle(rootElement);
|
|
128
109
|
const rootPadding = parseFloat(computedStyle.paddingLeft) + parseFloat(computedStyle.paddingRight);
|
|
129
110
|
const selectionRects = Array.from(range.getClientRects());
|
|
130
|
-
let selectionRectsLength = selectionRects.length;
|
|
131
|
-
|
|
111
|
+
let selectionRectsLength = selectionRects.length;
|
|
112
|
+
//sort rects from top left to bottom right.
|
|
132
113
|
selectionRects.sort((a, b) => {
|
|
133
|
-
const top = a.top - b.top;
|
|
114
|
+
const top = a.top - b.top;
|
|
115
|
+
// Some rects match position closely, but not perfectly,
|
|
134
116
|
// so we give a 3px tolerance.
|
|
135
|
-
|
|
136
117
|
if (Math.abs(top) <= 3) {
|
|
137
118
|
return a.left - b.left;
|
|
138
119
|
}
|
|
139
|
-
|
|
140
120
|
return top;
|
|
141
121
|
});
|
|
142
122
|
let prevRect;
|
|
143
|
-
|
|
144
123
|
for (let i = 0; i < selectionRectsLength; i++) {
|
|
145
|
-
const selectionRect = selectionRects[i];
|
|
146
|
-
|
|
147
|
-
const isOverlappingRect = prevRect && prevRect.top <= selectionRect.top && prevRect.top + prevRect.height > selectionRect.top && prevRect.left + prevRect.width > selectionRect.left;
|
|
148
|
-
|
|
124
|
+
const selectionRect = selectionRects[i];
|
|
125
|
+
// Exclude rects that overlap preceding Rects in the sorted list.
|
|
126
|
+
const isOverlappingRect = prevRect && prevRect.top <= selectionRect.top && prevRect.top + prevRect.height > selectionRect.top && prevRect.left + prevRect.width > selectionRect.left;
|
|
127
|
+
// Exclude selections that span the entire element
|
|
149
128
|
const selectionSpansElement = selectionRect.width + rootPadding === rootRect.width;
|
|
150
|
-
|
|
151
129
|
if (isOverlappingRect || selectionSpansElement) {
|
|
152
130
|
selectionRects.splice(i--, 1);
|
|
153
131
|
selectionRectsLength--;
|
|
154
132
|
continue;
|
|
155
133
|
}
|
|
156
|
-
|
|
157
134
|
prevRect = selectionRect;
|
|
158
135
|
}
|
|
159
|
-
|
|
160
136
|
return selectionRects;
|
|
161
137
|
}
|
|
138
|
+
|
|
162
139
|
/**
|
|
163
140
|
* Creates an object containing all the styles and their values provided in the CSS string.
|
|
164
141
|
* @param css - The CSS string of styles and their values.
|
|
165
142
|
* @returns The styleObject containing all the styles and their values.
|
|
166
143
|
*/
|
|
167
|
-
|
|
168
144
|
function getStyleObjectFromRawCSS(css) {
|
|
169
145
|
const styleObject = {};
|
|
170
146
|
const styles = css.split(';');
|
|
171
|
-
|
|
172
147
|
for (const style of styles) {
|
|
173
148
|
if (style !== '') {
|
|
174
149
|
const [key, value] = style.split(/:([^]+)/); // split on first colon
|
|
175
|
-
|
|
176
150
|
styleObject[key.trim()] = value.trim();
|
|
177
151
|
}
|
|
178
152
|
}
|
|
179
|
-
|
|
180
153
|
return styleObject;
|
|
181
154
|
}
|
|
155
|
+
|
|
182
156
|
/**
|
|
183
157
|
* Given a CSS string, returns an object from the style cache.
|
|
184
158
|
* @param css - The CSS property as a string.
|
|
185
159
|
* @returns The value of the given CSS property.
|
|
186
160
|
*/
|
|
187
|
-
|
|
188
161
|
function getStyleObjectFromCSS(css) {
|
|
189
162
|
let value = CSS_TO_STYLES.get(css);
|
|
190
|
-
|
|
191
163
|
if (value === undefined) {
|
|
192
164
|
value = getStyleObjectFromRawCSS(css);
|
|
193
165
|
CSS_TO_STYLES.set(css, value);
|
|
194
166
|
}
|
|
195
|
-
|
|
196
167
|
{
|
|
197
168
|
// Freeze the value in DEV to prevent accidental mutations
|
|
198
169
|
Object.freeze(value);
|
|
199
170
|
}
|
|
200
|
-
|
|
201
171
|
return value;
|
|
202
172
|
}
|
|
173
|
+
|
|
203
174
|
/**
|
|
204
175
|
* Gets the CSS styles from the style object.
|
|
205
176
|
* @param styles - The style object containing the styles to get.
|
|
206
177
|
* @returns A string containing the CSS styles and their values.
|
|
207
178
|
*/
|
|
208
|
-
|
|
209
179
|
function getCSSFromStyleObject(styles) {
|
|
210
180
|
let css = '';
|
|
211
|
-
|
|
212
181
|
for (const style in styles) {
|
|
213
182
|
if (style) {
|
|
214
183
|
css += `${style}: ${styles[style]};`;
|
|
215
184
|
}
|
|
216
185
|
}
|
|
217
|
-
|
|
218
186
|
return css;
|
|
219
187
|
}
|
|
220
188
|
|
|
@@ -225,7 +193,6 @@ function getCSSFromStyleObject(styles) {
|
|
|
225
193
|
* LICENSE file in the root directory of this source tree.
|
|
226
194
|
*
|
|
227
195
|
*/
|
|
228
|
-
|
|
229
196
|
function $updateElementNodeProperties(target, source) {
|
|
230
197
|
target.__first = source.__first;
|
|
231
198
|
target.__last = source.__last;
|
|
@@ -235,7 +202,6 @@ function $updateElementNodeProperties(target, source) {
|
|
|
235
202
|
target.__dir = source.__dir;
|
|
236
203
|
return target;
|
|
237
204
|
}
|
|
238
|
-
|
|
239
205
|
function $updateTextNodeProperties(target, source) {
|
|
240
206
|
target.__format = source.__format;
|
|
241
207
|
target.__style = source.__style;
|
|
@@ -243,31 +209,28 @@ function $updateTextNodeProperties(target, source) {
|
|
|
243
209
|
target.__detail = source.__detail;
|
|
244
210
|
return target;
|
|
245
211
|
}
|
|
212
|
+
|
|
246
213
|
/**
|
|
247
214
|
* Returns a copy of a node, but generates a new key for the copy.
|
|
248
215
|
* @param node - The node to be cloned.
|
|
249
216
|
* @returns The clone of the node.
|
|
250
217
|
*/
|
|
251
|
-
|
|
252
|
-
|
|
253
218
|
function $cloneWithProperties(node) {
|
|
254
|
-
const constructor = node.constructor;
|
|
255
|
-
|
|
219
|
+
const constructor = node.constructor;
|
|
220
|
+
// @ts-expect-error
|
|
256
221
|
const clone = constructor.clone(node);
|
|
257
222
|
clone.__parent = node.__parent;
|
|
258
223
|
clone.__next = node.__next;
|
|
259
224
|
clone.__prev = node.__prev;
|
|
260
|
-
|
|
261
225
|
if (lexical.$isElementNode(node) && lexical.$isElementNode(clone)) {
|
|
262
226
|
return $updateElementNodeProperties(clone, node);
|
|
263
227
|
}
|
|
264
|
-
|
|
265
228
|
if (lexical.$isTextNode(node) && lexical.$isTextNode(clone)) {
|
|
266
229
|
return $updateTextNodeProperties(clone, node);
|
|
267
230
|
}
|
|
268
|
-
|
|
269
231
|
return clone;
|
|
270
232
|
}
|
|
233
|
+
|
|
271
234
|
/**
|
|
272
235
|
* Generally used to append text content to HTML and JSON. Grabs the text content and "slices"
|
|
273
236
|
* it to be generated into the new TextNode.
|
|
@@ -275,14 +238,12 @@ function $cloneWithProperties(node) {
|
|
|
275
238
|
* @param textNode - The TextNode to be edited.
|
|
276
239
|
* @returns The updated TextNode.
|
|
277
240
|
*/
|
|
278
|
-
|
|
279
241
|
function $sliceSelectedTextNodeContent(selection, textNode) {
|
|
280
242
|
if (textNode.isSelected() && !textNode.isSegmented() && !textNode.isToken() && (lexical.$isRangeSelection(selection) || lexical.DEPRECATED_$isGridSelection(selection))) {
|
|
281
243
|
const anchorNode = selection.anchor.getNode();
|
|
282
244
|
const focusNode = selection.focus.getNode();
|
|
283
245
|
const isAnchor = textNode.is(anchorNode);
|
|
284
246
|
const isFocus = textNode.is(focusNode);
|
|
285
|
-
|
|
286
247
|
if (isAnchor || isFocus) {
|
|
287
248
|
const isBackward = selection.isBackward();
|
|
288
249
|
const [anchorOffset, focusOffset] = selection.getCharacterOffsets();
|
|
@@ -291,7 +252,6 @@ function $sliceSelectedTextNodeContent(selection, textNode) {
|
|
|
291
252
|
const isLast = textNode.is(isBackward ? anchorNode : focusNode);
|
|
292
253
|
let startOffset = 0;
|
|
293
254
|
let endOffset = undefined;
|
|
294
|
-
|
|
295
255
|
if (isSame) {
|
|
296
256
|
startOffset = anchorOffset > focusOffset ? focusOffset : anchorOffset;
|
|
297
257
|
endOffset = anchorOffset > focusOffset ? anchorOffset : focusOffset;
|
|
@@ -304,27 +264,25 @@ function $sliceSelectedTextNodeContent(selection, textNode) {
|
|
|
304
264
|
startOffset = 0;
|
|
305
265
|
endOffset = offset;
|
|
306
266
|
}
|
|
307
|
-
|
|
308
267
|
textNode.__text = textNode.__text.slice(startOffset, endOffset);
|
|
309
268
|
return textNode;
|
|
310
269
|
}
|
|
311
270
|
}
|
|
312
|
-
|
|
313
271
|
return textNode;
|
|
314
272
|
}
|
|
273
|
+
|
|
315
274
|
/**
|
|
316
275
|
* Determines if the current selection is at the end of the node.
|
|
317
276
|
* @param point - The point of the selection to test.
|
|
318
277
|
* @returns true if the provided point offset is in the last possible position, false otherwise.
|
|
319
278
|
*/
|
|
320
|
-
|
|
321
279
|
function $isAtNodeEnd(point) {
|
|
322
280
|
if (point.type === 'text') {
|
|
323
281
|
return point.offset === point.getNode().getTextContentSize();
|
|
324
282
|
}
|
|
325
|
-
|
|
326
283
|
return point.offset === point.getNode().getChildrenSize();
|
|
327
284
|
}
|
|
285
|
+
|
|
328
286
|
/**
|
|
329
287
|
* Trims text from a node in order to shorten it, eg. to enforce a text's max length. If it deletes text
|
|
330
288
|
* that is an ancestor of the anchor then it will leave 2 indents, otherwise, if no text content exists, it deletes
|
|
@@ -333,89 +291,72 @@ function $isAtNodeEnd(point) {
|
|
|
333
291
|
* @param anchor - The anchor of the current selection, where the selection should be pointing.
|
|
334
292
|
* @param delCount - The amount of characters to delete. Useful as a dynamic variable eg. textContentSize - maxLength;
|
|
335
293
|
*/
|
|
336
|
-
|
|
337
294
|
function trimTextContentFromAnchor(editor, anchor, delCount) {
|
|
338
295
|
// Work from the current selection anchor point
|
|
339
296
|
let currentNode = anchor.getNode();
|
|
340
297
|
let remaining = delCount;
|
|
341
|
-
|
|
342
298
|
if (lexical.$isElementNode(currentNode)) {
|
|
343
299
|
const descendantNode = currentNode.getDescendantByIndex(anchor.offset);
|
|
344
|
-
|
|
345
300
|
if (descendantNode !== null) {
|
|
346
301
|
currentNode = descendantNode;
|
|
347
302
|
}
|
|
348
303
|
}
|
|
349
|
-
|
|
350
304
|
while (remaining > 0 && currentNode !== null) {
|
|
305
|
+
if (lexical.$isElementNode(currentNode)) {
|
|
306
|
+
const lastDescendant = currentNode.getLastDescendant();
|
|
307
|
+
if (lastDescendant !== null) {
|
|
308
|
+
currentNode = lastDescendant;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
351
311
|
let nextNode = currentNode.getPreviousSibling();
|
|
352
312
|
let additionalElementWhitespace = 0;
|
|
353
|
-
|
|
354
313
|
if (nextNode === null) {
|
|
355
314
|
let parent = currentNode.getParentOrThrow();
|
|
356
315
|
let parentSibling = parent.getPreviousSibling();
|
|
357
|
-
|
|
358
316
|
while (parentSibling === null) {
|
|
359
317
|
parent = parent.getParent();
|
|
360
|
-
|
|
361
318
|
if (parent === null) {
|
|
362
319
|
nextNode = null;
|
|
363
320
|
break;
|
|
364
321
|
}
|
|
365
|
-
|
|
366
322
|
parentSibling = parent.getPreviousSibling();
|
|
367
323
|
}
|
|
368
|
-
|
|
369
324
|
if (parent !== null) {
|
|
370
325
|
additionalElementWhitespace = parent.isInline() ? 0 : 2;
|
|
371
|
-
|
|
372
|
-
if (lexical.$isElementNode(parentSibling)) {
|
|
373
|
-
nextNode = parentSibling.getLastDescendant();
|
|
374
|
-
} else {
|
|
375
|
-
nextNode = parentSibling;
|
|
376
|
-
}
|
|
326
|
+
nextNode = parentSibling;
|
|
377
327
|
}
|
|
378
328
|
}
|
|
379
|
-
|
|
380
|
-
|
|
329
|
+
let text = currentNode.getTextContent();
|
|
330
|
+
// If the text is empty, we need to consider adding in two line breaks to match
|
|
381
331
|
// the content if we were to get it from its parent.
|
|
382
|
-
|
|
383
332
|
if (text === '' && lexical.$isElementNode(currentNode) && !currentNode.isInline()) {
|
|
384
333
|
// TODO: should this be handled in core?
|
|
385
334
|
text = '\n\n';
|
|
386
335
|
}
|
|
387
|
-
|
|
388
336
|
const currentNodeSize = text.length;
|
|
389
|
-
|
|
390
337
|
if (!lexical.$isTextNode(currentNode) || remaining >= currentNodeSize) {
|
|
391
338
|
const parent = currentNode.getParent();
|
|
392
339
|
currentNode.remove();
|
|
393
|
-
|
|
394
340
|
if (parent != null && parent.getChildrenSize() === 0 && !lexical.$isRootNode(parent)) {
|
|
395
341
|
parent.remove();
|
|
396
342
|
}
|
|
397
|
-
|
|
398
343
|
remaining -= currentNodeSize + additionalElementWhitespace;
|
|
399
344
|
currentNode = nextNode;
|
|
400
345
|
} else {
|
|
401
|
-
const key = currentNode.getKey();
|
|
402
|
-
|
|
346
|
+
const key = currentNode.getKey();
|
|
347
|
+
// See if we can just revert it to what was in the last editor state
|
|
403
348
|
const prevTextContent = editor.getEditorState().read(() => {
|
|
404
349
|
const prevNode = lexical.$getNodeByKey(key);
|
|
405
|
-
|
|
406
350
|
if (lexical.$isTextNode(prevNode) && prevNode.isSimpleText()) {
|
|
407
351
|
return prevNode.getTextContent();
|
|
408
352
|
}
|
|
409
|
-
|
|
410
353
|
return null;
|
|
411
354
|
});
|
|
412
355
|
const offset = currentNodeSize - remaining;
|
|
413
356
|
const slicedText = text.slice(0, offset);
|
|
414
|
-
|
|
415
357
|
if (prevTextContent !== null && prevTextContent !== text) {
|
|
416
358
|
const prevSelection = lexical.$getPreviousSelection();
|
|
417
359
|
let target = currentNode;
|
|
418
|
-
|
|
419
360
|
if (!currentNode.isSimpleText()) {
|
|
420
361
|
const textNode = lexical.$createTextNode(prevTextContent);
|
|
421
362
|
currentNode.replace(textNode);
|
|
@@ -423,7 +364,6 @@ function trimTextContentFromAnchor(editor, anchor, delCount) {
|
|
|
423
364
|
} else {
|
|
424
365
|
currentNode.setTextContent(prevTextContent);
|
|
425
366
|
}
|
|
426
|
-
|
|
427
367
|
if (lexical.$isRangeSelection(prevSelection) && prevSelection.isCollapsed()) {
|
|
428
368
|
const prevOffset = prevSelection.anchor.offset;
|
|
429
369
|
target.select(prevOffset, prevOffset);
|
|
@@ -431,16 +371,14 @@ function trimTextContentFromAnchor(editor, anchor, delCount) {
|
|
|
431
371
|
} else if (currentNode.isSimpleText()) {
|
|
432
372
|
// Split text
|
|
433
373
|
const isSelected = anchor.key === key;
|
|
434
|
-
let anchorOffset = anchor.offset;
|
|
374
|
+
let anchorOffset = anchor.offset;
|
|
375
|
+
// Move offset to end if it's less than the remaining number, otherwise
|
|
435
376
|
// we'll have a negative splitStart.
|
|
436
|
-
|
|
437
377
|
if (anchorOffset < remaining) {
|
|
438
378
|
anchorOffset = currentNodeSize;
|
|
439
379
|
}
|
|
440
|
-
|
|
441
380
|
const splitStart = isSelected ? anchorOffset - remaining : 0;
|
|
442
381
|
const splitEnd = isSelected ? anchorOffset : offset;
|
|
443
|
-
|
|
444
382
|
if (isSelected && splitStart === 0) {
|
|
445
383
|
const [excessNode] = currentNode.splitText(splitStart, splitEnd);
|
|
446
384
|
excessNode.remove();
|
|
@@ -452,22 +390,20 @@ function trimTextContentFromAnchor(editor, anchor, delCount) {
|
|
|
452
390
|
const textNode = lexical.$createTextNode(slicedText);
|
|
453
391
|
currentNode.replace(textNode);
|
|
454
392
|
}
|
|
455
|
-
|
|
456
393
|
remaining = 0;
|
|
457
394
|
}
|
|
458
395
|
}
|
|
459
396
|
}
|
|
397
|
+
|
|
460
398
|
/**
|
|
461
399
|
* Gets the TextNode's style object and adds the styles to the CSS.
|
|
462
400
|
* @param node - The TextNode to add styles to.
|
|
463
401
|
*/
|
|
464
|
-
|
|
465
402
|
function $addNodeStyle(node) {
|
|
466
403
|
const CSSText = node.getStyle();
|
|
467
404
|
const styles = getStyleObjectFromRawCSS(CSSText);
|
|
468
405
|
CSS_TO_STYLES.set(CSSText, styles);
|
|
469
406
|
}
|
|
470
|
-
|
|
471
407
|
function $patchStyle(target, patch) {
|
|
472
408
|
const prevStyles = getStyleObjectFromCSS('getStyle' in target ? target.getStyle() : target.style);
|
|
473
409
|
const newStyles = Object.entries(patch).reduce((styles, [key, value]) => {
|
|
@@ -476,14 +412,15 @@ function $patchStyle(target, patch) {
|
|
|
476
412
|
} else {
|
|
477
413
|
styles[key] = value;
|
|
478
414
|
}
|
|
479
|
-
|
|
480
415
|
return styles;
|
|
481
|
-
}, {
|
|
416
|
+
}, {
|
|
417
|
+
...prevStyles
|
|
482
418
|
} || {});
|
|
483
419
|
const newCSSText = getCSSFromStyleObject(newStyles);
|
|
484
420
|
target.setStyle(newCSSText);
|
|
485
421
|
CSS_TO_STYLES.set(newCSSText, newStyles);
|
|
486
422
|
}
|
|
423
|
+
|
|
487
424
|
/**
|
|
488
425
|
* Applies the provided styles to the TextNodes in the provided Selection.
|
|
489
426
|
* Will update partially selected TextNodes by splitting the TextNode and applying
|
|
@@ -491,40 +428,31 @@ function $patchStyle(target, patch) {
|
|
|
491
428
|
* @param selection - The selected node(s) to update.
|
|
492
429
|
* @param patch - The patch to apply, which can include multiple styles. { CSSProperty: value }
|
|
493
430
|
*/
|
|
494
|
-
|
|
495
|
-
|
|
496
431
|
function $patchStyleText(selection, patch) {
|
|
497
432
|
const selectedNodes = selection.getNodes();
|
|
498
433
|
const selectedNodesLength = selectedNodes.length;
|
|
499
|
-
|
|
500
434
|
if (lexical.DEPRECATED_$isGridSelection(selection)) {
|
|
501
435
|
const cellSelection = lexical.$createRangeSelection();
|
|
502
436
|
const cellSelectionAnchor = cellSelection.anchor;
|
|
503
437
|
const cellSelectionFocus = cellSelection.focus;
|
|
504
|
-
|
|
505
438
|
for (let i = 0; i < selectedNodesLength; i++) {
|
|
506
439
|
const node = selectedNodes[i];
|
|
507
|
-
|
|
508
440
|
if (lexical.DEPRECATED_$isGridCellNode(node)) {
|
|
509
441
|
cellSelectionAnchor.set(node.getKey(), 0, 'element');
|
|
510
442
|
cellSelectionFocus.set(node.getKey(), node.getChildrenSize(), 'element');
|
|
511
443
|
$patchStyleText(lexical.$normalizeSelection__EXPERIMENTAL(cellSelection), patch);
|
|
512
444
|
}
|
|
513
445
|
}
|
|
514
|
-
|
|
515
446
|
lexical.$setSelection(selection);
|
|
516
447
|
return;
|
|
517
448
|
}
|
|
518
|
-
|
|
519
449
|
const lastIndex = selectedNodesLength - 1;
|
|
520
450
|
let firstNode = selectedNodes[0];
|
|
521
451
|
let lastNode = selectedNodes[lastIndex];
|
|
522
|
-
|
|
523
452
|
if (selection.isCollapsed()) {
|
|
524
453
|
$patchStyle(selection, patch);
|
|
525
454
|
return;
|
|
526
455
|
}
|
|
527
|
-
|
|
528
456
|
const anchor = selection.anchor;
|
|
529
457
|
const focus = selection.focus;
|
|
530
458
|
const firstNodeText = firstNode.getTextContent();
|
|
@@ -536,31 +464,32 @@ function $patchStyleText(selection, patch) {
|
|
|
536
464
|
let endOffset = isBefore ? focusOffset : anchorOffset;
|
|
537
465
|
const startType = isBefore ? anchor.type : focus.type;
|
|
538
466
|
const endType = isBefore ? focus.type : anchor.type;
|
|
539
|
-
const endKey = isBefore ? focus.key : anchor.key;
|
|
540
|
-
// first node so we don't want to include it in the formatting change.
|
|
467
|
+
const endKey = isBefore ? focus.key : anchor.key;
|
|
541
468
|
|
|
469
|
+
// This is the case where the user only selected the very end of the
|
|
470
|
+
// first node so we don't want to include it in the formatting change.
|
|
542
471
|
if (lexical.$isTextNode(firstNode) && startOffset === firstNodeTextLength) {
|
|
543
472
|
const nextSibling = firstNode.getNextSibling();
|
|
544
|
-
|
|
545
473
|
if (lexical.$isTextNode(nextSibling)) {
|
|
546
474
|
// we basically make the second node the firstNode, changing offsets accordingly
|
|
547
475
|
anchorOffset = 0;
|
|
548
476
|
startOffset = 0;
|
|
549
477
|
firstNode = nextSibling;
|
|
550
478
|
}
|
|
551
|
-
}
|
|
552
|
-
|
|
479
|
+
}
|
|
553
480
|
|
|
481
|
+
// This is the case where we only selected a single node
|
|
554
482
|
if (selectedNodes.length === 1) {
|
|
555
483
|
if (lexical.$isTextNode(firstNode)) {
|
|
556
484
|
startOffset = startType === 'element' ? 0 : anchorOffset > focusOffset ? focusOffset : anchorOffset;
|
|
557
|
-
endOffset = endType === 'element' ? firstNodeTextLength : anchorOffset > focusOffset ? anchorOffset : focusOffset;
|
|
485
|
+
endOffset = endType === 'element' ? firstNodeTextLength : anchorOffset > focusOffset ? anchorOffset : focusOffset;
|
|
558
486
|
|
|
487
|
+
// No actual text is selected, so do nothing.
|
|
559
488
|
if (startOffset === endOffset) {
|
|
560
489
|
return;
|
|
561
|
-
}
|
|
562
|
-
|
|
490
|
+
}
|
|
563
491
|
|
|
492
|
+
// The entire node is selected, so just format it
|
|
564
493
|
if (startOffset === 0 && endOffset === firstNodeTextLength) {
|
|
565
494
|
$patchStyle(firstNode, patch);
|
|
566
495
|
firstNode.select(startOffset, endOffset);
|
|
@@ -573,44 +502,41 @@ function $patchStyleText(selection, patch) {
|
|
|
573
502
|
replacement.select(0, endOffset - startOffset);
|
|
574
503
|
}
|
|
575
504
|
} // multiple nodes selected.
|
|
576
|
-
|
|
577
505
|
} else {
|
|
578
506
|
if (lexical.$isTextNode(firstNode) && startOffset < firstNode.getTextContentSize()) {
|
|
579
507
|
if (startOffset !== 0) {
|
|
580
508
|
// the entire first node isn't selected, so split it
|
|
581
509
|
firstNode = firstNode.splitText(startOffset)[1];
|
|
582
510
|
startOffset = 0;
|
|
511
|
+
anchor.set(firstNode.getKey(), startOffset, 'text');
|
|
583
512
|
}
|
|
584
|
-
|
|
585
513
|
$patchStyle(firstNode, patch);
|
|
586
514
|
}
|
|
587
|
-
|
|
588
515
|
if (lexical.$isTextNode(lastNode)) {
|
|
589
516
|
const lastNodeText = lastNode.getTextContent();
|
|
590
|
-
const lastNodeTextLength = lastNodeText.length;
|
|
517
|
+
const lastNodeTextLength = lastNodeText.length;
|
|
518
|
+
|
|
519
|
+
// The last node might not actually be the end node
|
|
591
520
|
//
|
|
592
521
|
// If not, assume the last node is fully-selected unless the end offset is
|
|
593
522
|
// zero.
|
|
594
|
-
|
|
595
523
|
if (lastNode.__key !== endKey && endOffset !== 0) {
|
|
596
524
|
endOffset = lastNodeTextLength;
|
|
597
|
-
}
|
|
598
|
-
|
|
525
|
+
}
|
|
599
526
|
|
|
527
|
+
// if the entire last node isn't selected, split it
|
|
600
528
|
if (endOffset !== lastNodeTextLength) {
|
|
601
529
|
[lastNode] = lastNode.splitText(endOffset);
|
|
602
530
|
}
|
|
603
|
-
|
|
604
531
|
if (endOffset !== 0) {
|
|
605
532
|
$patchStyle(lastNode, patch);
|
|
606
533
|
}
|
|
607
|
-
}
|
|
608
|
-
|
|
534
|
+
}
|
|
609
535
|
|
|
536
|
+
// style all the text nodes in between
|
|
610
537
|
for (let i = 1; i < lastIndex; i++) {
|
|
611
538
|
const selectedNode = selectedNodes[i];
|
|
612
539
|
const selectedNodeKey = selectedNode.getKey();
|
|
613
|
-
|
|
614
540
|
if (lexical.$isTextNode(selectedNode) && selectedNodeKey !== firstNode.getKey() && selectedNodeKey !== lastNode.getKey() && !selectedNode.isToken()) {
|
|
615
541
|
$patchStyle(selectedNode, patch);
|
|
616
542
|
}
|
|
@@ -625,84 +551,55 @@ function $patchStyleText(selection, patch) {
|
|
|
625
551
|
* LICENSE file in the root directory of this source tree.
|
|
626
552
|
*
|
|
627
553
|
*/
|
|
554
|
+
|
|
628
555
|
/**
|
|
629
556
|
* Converts all nodes in the selection that are of one block type to another.
|
|
630
557
|
* @param selection - The selected blocks to be converted.
|
|
631
558
|
* @param createElement - The function that creates the node. eg. $createParagraphNode.
|
|
632
559
|
*/
|
|
633
|
-
|
|
634
560
|
function $setBlocksType(selection, createElement) {
|
|
635
561
|
if (selection.anchor.key === 'root') {
|
|
636
562
|
const element = createElement();
|
|
637
563
|
const root = lexical.$getRoot();
|
|
638
564
|
const firstChild = root.getFirstChild();
|
|
639
|
-
|
|
640
565
|
if (firstChild) {
|
|
641
566
|
firstChild.replace(element, true);
|
|
642
567
|
} else {
|
|
643
568
|
root.append(element);
|
|
644
569
|
}
|
|
645
|
-
|
|
646
570
|
return;
|
|
647
571
|
}
|
|
648
|
-
|
|
649
572
|
const nodes = selection.getNodes();
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
nodes.push(maybeBlock);
|
|
573
|
+
const firstSelectedBlock = utils.$getAncestor(selection.anchor.getNode(), utils.INTERNAL_$isBlock);
|
|
574
|
+
if (firstSelectedBlock && nodes.indexOf(firstSelectedBlock) === -1) {
|
|
575
|
+
nodes.push(firstSelectedBlock);
|
|
654
576
|
}
|
|
655
|
-
|
|
656
|
-
if (maybeBlock.isInline()) {
|
|
657
|
-
maybeBlock = maybeBlock.getParentOrThrow();
|
|
658
|
-
|
|
659
|
-
if (nodes.indexOf(maybeBlock) === -1) {
|
|
660
|
-
nodes.push(maybeBlock);
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
577
|
for (let i = 0; i < nodes.length; i++) {
|
|
665
578
|
const node = nodes[i];
|
|
666
|
-
|
|
667
|
-
if (!isBlock(node)) {
|
|
579
|
+
if (!utils.INTERNAL_$isBlock(node)) {
|
|
668
580
|
continue;
|
|
669
581
|
}
|
|
670
|
-
|
|
671
582
|
const targetElement = createElement();
|
|
672
583
|
targetElement.setFormat(node.getFormatType());
|
|
673
584
|
targetElement.setIndent(node.getIndent());
|
|
674
585
|
node.replace(targetElement, true);
|
|
675
586
|
}
|
|
676
587
|
}
|
|
677
|
-
|
|
678
|
-
function isBlock(node) {
|
|
679
|
-
if (!lexical.$isElementNode(node) || lexical.$isRootOrShadowRoot(node)) {
|
|
680
|
-
return false;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
const firstChild = node.getFirstChild();
|
|
684
|
-
const isLeafElement = firstChild === null || lexical.$isLineBreakNode(firstChild) || lexical.$isTextNode(firstChild) || firstChild.isInline();
|
|
685
|
-
return !node.isInline() && node.canBeEmpty() !== false && isLeafElement;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
588
|
function isPointAttached(point) {
|
|
689
589
|
return point.getNode().isAttached();
|
|
690
590
|
}
|
|
691
|
-
|
|
692
591
|
function $removeParentEmptyElements(startingNode) {
|
|
693
592
|
let node = startingNode;
|
|
694
|
-
|
|
695
593
|
while (node !== null && !lexical.$isRootOrShadowRoot(node)) {
|
|
696
594
|
const latest = node.getLatest();
|
|
697
595
|
const parentNode = node.getParent();
|
|
698
|
-
|
|
699
596
|
if (latest.getChildrenSize() === 0) {
|
|
700
597
|
node.remove(true);
|
|
701
598
|
}
|
|
702
|
-
|
|
703
599
|
node = parentNode;
|
|
704
600
|
}
|
|
705
601
|
}
|
|
602
|
+
|
|
706
603
|
/**
|
|
707
604
|
* @deprecated
|
|
708
605
|
* Wraps all nodes in the selection into another node of the type returned by createElement.
|
|
@@ -710,13 +607,10 @@ function $removeParentEmptyElements(startingNode) {
|
|
|
710
607
|
* @param createElement - A function that creates the wrapping ElementNode. eg. $createParagraphNode.
|
|
711
608
|
* @param wrappingElement - An element to append the wrapped selection and its children to.
|
|
712
609
|
*/
|
|
713
|
-
|
|
714
|
-
|
|
715
610
|
function $wrapNodes(selection, createElement, wrappingElement = null) {
|
|
716
611
|
const nodes = selection.getNodes();
|
|
717
612
|
const nodesLength = nodes.length;
|
|
718
613
|
const anchor = selection.anchor;
|
|
719
|
-
|
|
720
614
|
if (nodesLength === 0 || nodesLength === 1 && anchor.type === 'element' && anchor.getNode().getChildrenSize() === 0) {
|
|
721
615
|
const target = anchor.type === 'text' ? anchor.getNode().getParentOrThrow() : anchor.getNode();
|
|
722
616
|
const children = target.getChildren();
|
|
@@ -724,24 +618,20 @@ function $wrapNodes(selection, createElement, wrappingElement = null) {
|
|
|
724
618
|
element.setFormat(target.getFormatType());
|
|
725
619
|
element.setIndent(target.getIndent());
|
|
726
620
|
children.forEach(child => element.append(child));
|
|
727
|
-
|
|
728
621
|
if (wrappingElement) {
|
|
729
622
|
element = wrappingElement.append(element);
|
|
730
623
|
}
|
|
731
|
-
|
|
732
624
|
target.replace(element);
|
|
733
625
|
return;
|
|
734
626
|
}
|
|
735
|
-
|
|
736
627
|
let topLevelNode = null;
|
|
737
628
|
let descendants = [];
|
|
738
|
-
|
|
739
629
|
for (let i = 0; i < nodesLength; i++) {
|
|
740
|
-
const node = nodes[i];
|
|
630
|
+
const node = nodes[i];
|
|
631
|
+
// Determine whether wrapping has to be broken down into multiple chunks. This can happen if the
|
|
741
632
|
// user selected multiple Root-like nodes that have to be treated separately as if they are
|
|
742
633
|
// their own branch. I.e. you don't want to wrap a whole table, but rather the contents of each
|
|
743
634
|
// of each of the cell nodes.
|
|
744
|
-
|
|
745
635
|
if (lexical.$isRootOrShadowRoot(node)) {
|
|
746
636
|
$wrapNodesImpl(selection, descendants, descendants.length, createElement, wrappingElement);
|
|
747
637
|
descendants = [];
|
|
@@ -753,9 +643,9 @@ function $wrapNodes(selection, createElement, wrappingElement = null) {
|
|
|
753
643
|
descendants = [node];
|
|
754
644
|
}
|
|
755
645
|
}
|
|
756
|
-
|
|
757
646
|
$wrapNodesImpl(selection, descendants, descendants.length, createElement, wrappingElement);
|
|
758
647
|
}
|
|
648
|
+
|
|
759
649
|
/**
|
|
760
650
|
* Wraps each node into a new ElementNode.
|
|
761
651
|
* @param selection - The selection of nodes to wrap.
|
|
@@ -765,80 +655,67 @@ function $wrapNodes(selection, createElement, wrappingElement = null) {
|
|
|
765
655
|
* @param wrappingElement - An element to wrap all the nodes into.
|
|
766
656
|
* @returns
|
|
767
657
|
*/
|
|
768
|
-
|
|
769
658
|
function $wrapNodesImpl(selection, nodes, nodesLength, createElement, wrappingElement = null) {
|
|
770
659
|
if (nodes.length === 0) {
|
|
771
660
|
return;
|
|
772
661
|
}
|
|
773
|
-
|
|
774
662
|
const firstNode = nodes[0];
|
|
775
663
|
const elementMapping = new Map();
|
|
776
|
-
const elements = [];
|
|
664
|
+
const elements = [];
|
|
665
|
+
// The below logic is to find the right target for us to
|
|
777
666
|
// either insertAfter/insertBefore/append the corresponding
|
|
778
667
|
// elements to. This is made more complicated due to nested
|
|
779
668
|
// structures.
|
|
780
|
-
|
|
781
669
|
let target = lexical.$isElementNode(firstNode) ? firstNode : firstNode.getParentOrThrow();
|
|
782
|
-
|
|
783
670
|
if (target.isInline()) {
|
|
784
671
|
target = target.getParentOrThrow();
|
|
785
672
|
}
|
|
786
|
-
|
|
787
673
|
let targetIsPrevSibling = false;
|
|
788
|
-
|
|
789
674
|
while (target !== null) {
|
|
790
675
|
const prevSibling = target.getPreviousSibling();
|
|
791
|
-
|
|
792
676
|
if (prevSibling !== null) {
|
|
793
677
|
target = prevSibling;
|
|
794
678
|
targetIsPrevSibling = true;
|
|
795
679
|
break;
|
|
796
680
|
}
|
|
797
|
-
|
|
798
681
|
target = target.getParentOrThrow();
|
|
799
|
-
|
|
800
682
|
if (lexical.$isRootOrShadowRoot(target)) {
|
|
801
683
|
break;
|
|
802
684
|
}
|
|
803
685
|
}
|
|
686
|
+
const emptyElements = new Set();
|
|
804
687
|
|
|
805
|
-
|
|
806
|
-
|
|
688
|
+
// Find any top level empty elements
|
|
807
689
|
for (let i = 0; i < nodesLength; i++) {
|
|
808
690
|
const node = nodes[i];
|
|
809
|
-
|
|
810
691
|
if (lexical.$isElementNode(node) && node.getChildrenSize() === 0) {
|
|
811
692
|
emptyElements.add(node.getKey());
|
|
812
693
|
}
|
|
813
694
|
}
|
|
695
|
+
const movedNodes = new Set();
|
|
814
696
|
|
|
815
|
-
|
|
697
|
+
// Move out all leaf nodes into our elements array.
|
|
816
698
|
// If we find a top level empty element, also move make
|
|
817
699
|
// an element for that.
|
|
818
|
-
|
|
819
700
|
for (let i = 0; i < nodesLength; i++) {
|
|
820
701
|
const node = nodes[i];
|
|
821
702
|
let parent = node.getParent();
|
|
822
|
-
|
|
823
703
|
if (parent !== null && parent.isInline()) {
|
|
824
704
|
parent = parent.getParent();
|
|
825
705
|
}
|
|
826
|
-
|
|
827
706
|
if (parent !== null && lexical.$isLeafNode(node) && !movedNodes.has(node.getKey())) {
|
|
828
707
|
const parentKey = parent.getKey();
|
|
829
|
-
|
|
830
708
|
if (elementMapping.get(parentKey) === undefined) {
|
|
831
709
|
const targetElement = createElement();
|
|
832
710
|
targetElement.setFormat(parent.getFormatType());
|
|
833
711
|
targetElement.setIndent(parent.getIndent());
|
|
834
712
|
elements.push(targetElement);
|
|
835
|
-
elementMapping.set(parentKey, targetElement);
|
|
713
|
+
elementMapping.set(parentKey, targetElement);
|
|
714
|
+
// Move node and its siblings to the new
|
|
836
715
|
// element.
|
|
837
|
-
|
|
838
716
|
parent.getChildren().forEach(child => {
|
|
839
717
|
targetElement.append(child);
|
|
840
718
|
movedNodes.add(child.getKey());
|
|
841
|
-
|
|
842
719
|
if (lexical.$isElementNode(child)) {
|
|
843
720
|
// Skip nested leaf nodes if the parent has already been moved
|
|
844
721
|
child.getChildrenKeys().forEach(key => movedNodes.add(key));
|
|
@@ -854,17 +731,16 @@ function $wrapNodesImpl(selection, nodes, nodesLength, createElement, wrappingEl
|
|
|
854
731
|
node.remove(true);
|
|
855
732
|
}
|
|
856
733
|
}
|
|
857
|
-
|
|
858
734
|
if (wrappingElement !== null) {
|
|
859
735
|
for (let i = 0; i < elements.length; i++) {
|
|
860
736
|
const element = elements[i];
|
|
861
737
|
wrappingElement.append(element);
|
|
862
738
|
}
|
|
863
739
|
}
|
|
740
|
+
let lastElement = null;
|
|
864
741
|
|
|
865
|
-
|
|
742
|
+
// If our target is Root-like, let's see if we can re-adjust
|
|
866
743
|
// so that the target is the first child instead.
|
|
867
|
-
|
|
868
744
|
if (lexical.$isRootOrShadowRoot(target)) {
|
|
869
745
|
if (targetIsPrevSibling) {
|
|
870
746
|
if (wrappingElement !== null) {
|
|
@@ -877,11 +753,9 @@ function $wrapNodesImpl(selection, nodes, nodesLength, createElement, wrappingEl
|
|
|
877
753
|
}
|
|
878
754
|
} else {
|
|
879
755
|
const firstChild = target.getFirstChild();
|
|
880
|
-
|
|
881
756
|
if (lexical.$isElementNode(firstChild)) {
|
|
882
757
|
target = firstChild;
|
|
883
758
|
}
|
|
884
|
-
|
|
885
759
|
if (firstChild === null) {
|
|
886
760
|
if (wrappingElement) {
|
|
887
761
|
target.append(wrappingElement);
|
|
@@ -915,9 +789,7 @@ function $wrapNodesImpl(selection, nodes, nodesLength, createElement, wrappingEl
|
|
|
915
789
|
}
|
|
916
790
|
}
|
|
917
791
|
}
|
|
918
|
-
|
|
919
792
|
const prevSelection = lexical.$getPreviousSelection();
|
|
920
|
-
|
|
921
793
|
if (lexical.$isRangeSelection(prevSelection) && isPointAttached(prevSelection.anchor) && isPointAttached(prevSelection.focus)) {
|
|
922
794
|
lexical.$setSelection(prevSelection.clone());
|
|
923
795
|
} else if (lastElement !== null) {
|
|
@@ -926,17 +798,18 @@ function $wrapNodesImpl(selection, nodes, nodesLength, createElement, wrappingEl
|
|
|
926
798
|
selection.dirty = true;
|
|
927
799
|
}
|
|
928
800
|
}
|
|
801
|
+
|
|
929
802
|
/**
|
|
930
803
|
* Determines if the default character selection should be overridden. Used with DecoratorNodes
|
|
931
804
|
* @param selection - The selection whose default character selection may need to be overridden.
|
|
932
805
|
* @param isBackward - Is the selection backwards (the focus comes before the anchor)?
|
|
933
806
|
* @returns true if it should be overridden, false if not.
|
|
934
807
|
*/
|
|
935
|
-
|
|
936
808
|
function $shouldOverrideDefaultCharacterSelection(selection, isBackward) {
|
|
937
809
|
const possibleNode = lexical.$getAdjacentNode(selection.focus, isBackward);
|
|
938
810
|
return lexical.$isDecoratorNode(possibleNode) && !possibleNode.isIsolated() || lexical.$isElementNode(possibleNode) && !possibleNode.isInline() && !possibleNode.canBeEmpty();
|
|
939
811
|
}
|
|
812
|
+
|
|
940
813
|
/**
|
|
941
814
|
* Moves the selection according to the arguments.
|
|
942
815
|
* @param selection - The selected text or nodes.
|
|
@@ -944,37 +817,36 @@ function $shouldOverrideDefaultCharacterSelection(selection, isBackward) {
|
|
|
944
817
|
* @param isBackward - Is the selection selected backwards (the focus comes before the anchor)?
|
|
945
818
|
* @param granularity - The distance to adjust the current selection.
|
|
946
819
|
*/
|
|
947
|
-
|
|
948
820
|
function $moveCaretSelection(selection, isHoldingShift, isBackward, granularity) {
|
|
949
821
|
selection.modify(isHoldingShift ? 'extend' : 'move', isBackward, granularity);
|
|
950
822
|
}
|
|
823
|
+
|
|
951
824
|
/**
|
|
952
825
|
* Tests a parent element for right to left direction.
|
|
953
826
|
* @param selection - The selection whose parent is to be tested.
|
|
954
827
|
* @returns true if the selections' parent element has a direction of 'rtl' (right to left), false otherwise.
|
|
955
828
|
*/
|
|
956
|
-
|
|
957
829
|
function $isParentElementRTL(selection) {
|
|
958
830
|
const anchorNode = selection.anchor.getNode();
|
|
959
831
|
const parent = lexical.$isRootNode(anchorNode) ? anchorNode : anchorNode.getParentOrThrow();
|
|
960
832
|
return parent.getDirection() === 'rtl';
|
|
961
833
|
}
|
|
834
|
+
|
|
962
835
|
/**
|
|
963
836
|
* Moves selection by character according to arguments.
|
|
964
837
|
* @param selection - The selection of the characters to move.
|
|
965
838
|
* @param isHoldingShift - Is the shift key being held down during the operation.
|
|
966
839
|
* @param isBackward - Is the selection backward (the focus comes before the anchor)?
|
|
967
840
|
*/
|
|
968
|
-
|
|
969
841
|
function $moveCharacter(selection, isHoldingShift, isBackward) {
|
|
970
842
|
const isRTL = $isParentElementRTL(selection);
|
|
971
843
|
$moveCaretSelection(selection, isHoldingShift, isBackward ? !isRTL : isRTL, 'character');
|
|
972
844
|
}
|
|
845
|
+
|
|
973
846
|
/**
|
|
974
847
|
* Expands the current Selection to cover all of the content in the editor.
|
|
975
848
|
* @param selection - The current selection.
|
|
976
849
|
*/
|
|
977
|
-
|
|
978
850
|
function $selectAll(selection) {
|
|
979
851
|
const anchor = selection.anchor;
|
|
980
852
|
const focus = selection.focus;
|
|
@@ -986,25 +858,23 @@ function $selectAll(selection) {
|
|
|
986
858
|
let firstType = 'element';
|
|
987
859
|
let lastType = 'element';
|
|
988
860
|
let lastOffset = 0;
|
|
989
|
-
|
|
990
861
|
if (lexical.$isTextNode(firstNode)) {
|
|
991
862
|
firstType = 'text';
|
|
992
863
|
} else if (!lexical.$isElementNode(firstNode) && firstNode !== null) {
|
|
993
864
|
firstNode = firstNode.getParentOrThrow();
|
|
994
865
|
}
|
|
995
|
-
|
|
996
866
|
if (lexical.$isTextNode(lastNode)) {
|
|
997
867
|
lastType = 'text';
|
|
998
868
|
lastOffset = lastNode.getTextContentSize();
|
|
999
869
|
} else if (!lexical.$isElementNode(lastNode) && lastNode !== null) {
|
|
1000
870
|
lastNode = lastNode.getParentOrThrow();
|
|
1001
871
|
}
|
|
1002
|
-
|
|
1003
872
|
if (firstNode && lastNode) {
|
|
1004
873
|
anchor.set(firstNode.getKey(), 0, firstType);
|
|
1005
874
|
focus.set(lastNode.getKey(), lastOffset, lastType);
|
|
1006
875
|
}
|
|
1007
876
|
}
|
|
877
|
+
|
|
1008
878
|
/**
|
|
1009
879
|
* Returns the current value of a CSS property for Nodes, if set. If not set, it returns the defaultValue.
|
|
1010
880
|
* @param node - The node whose style value to get.
|
|
@@ -1012,17 +882,15 @@ function $selectAll(selection) {
|
|
|
1012
882
|
* @param defaultValue - The default value for the property.
|
|
1013
883
|
* @returns The value of the property for node.
|
|
1014
884
|
*/
|
|
1015
|
-
|
|
1016
885
|
function $getNodeStyleValueForProperty(node, styleProperty, defaultValue) {
|
|
1017
886
|
const css = node.getStyle();
|
|
1018
887
|
const styleObject = getStyleObjectFromCSS(css);
|
|
1019
|
-
|
|
1020
888
|
if (styleObject !== null) {
|
|
1021
889
|
return styleObject[styleProperty] || defaultValue;
|
|
1022
890
|
}
|
|
1023
|
-
|
|
1024
891
|
return defaultValue;
|
|
1025
892
|
}
|
|
893
|
+
|
|
1026
894
|
/**
|
|
1027
895
|
* Returns the current value of a CSS property for TextNodes in the Selection, if set. If not set, it returns the defaultValue.
|
|
1028
896
|
* If all TextNodes do not have the same value, it returns an empty string.
|
|
@@ -1031,8 +899,6 @@ function $getNodeStyleValueForProperty(node, styleProperty, defaultValue) {
|
|
|
1031
899
|
* @param defaultValue - The default value for the property, defaults to an empty string.
|
|
1032
900
|
* @returns The value of the property for the selected TextNodes.
|
|
1033
901
|
*/
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
902
|
function $getSelectionStyleValueForProperty(selection, styleProperty, defaultValue = '') {
|
|
1037
903
|
let styleValue = null;
|
|
1038
904
|
const nodes = selection.getNodes();
|
|
@@ -1041,28 +907,24 @@ function $getSelectionStyleValueForProperty(selection, styleProperty, defaultVal
|
|
|
1041
907
|
const isBackward = selection.isBackward();
|
|
1042
908
|
const endOffset = isBackward ? focus.offset : anchor.offset;
|
|
1043
909
|
const endNode = isBackward ? focus.getNode() : anchor.getNode();
|
|
1044
|
-
|
|
1045
|
-
if (selection.style !== '') {
|
|
910
|
+
if (selection.isCollapsed() && selection.style !== '') {
|
|
1046
911
|
const css = selection.style;
|
|
1047
912
|
const styleObject = getStyleObjectFromCSS(css);
|
|
1048
|
-
|
|
1049
913
|
if (styleObject !== null && styleProperty in styleObject) {
|
|
1050
914
|
return styleObject[styleProperty];
|
|
1051
915
|
}
|
|
1052
916
|
}
|
|
1053
|
-
|
|
1054
917
|
for (let i = 0; i < nodes.length; i++) {
|
|
1055
|
-
const node = nodes[i];
|
|
918
|
+
const node = nodes[i];
|
|
919
|
+
|
|
920
|
+
// if no actual characters in the end node are selected, we don't
|
|
1056
921
|
// include it in the selection for purposes of determining style
|
|
1057
922
|
// value
|
|
1058
|
-
|
|
1059
923
|
if (i !== 0 && endOffset === 0 && node.is(endNode)) {
|
|
1060
924
|
continue;
|
|
1061
925
|
}
|
|
1062
|
-
|
|
1063
926
|
if (lexical.$isTextNode(node)) {
|
|
1064
927
|
const nodeStyleValue = $getNodeStyleValueForProperty(node, styleProperty, defaultValue);
|
|
1065
|
-
|
|
1066
928
|
if (styleValue === null) {
|
|
1067
929
|
styleValue = nodeStyleValue;
|
|
1068
930
|
} else if (styleValue !== nodeStyleValue) {
|
|
@@ -1073,7 +935,6 @@ function $getSelectionStyleValueForProperty(selection, styleProperty, defaultVal
|
|
|
1073
935
|
}
|
|
1074
936
|
}
|
|
1075
937
|
}
|
|
1076
|
-
|
|
1077
938
|
return styleValue === null ? defaultValue : styleValue;
|
|
1078
939
|
}
|
|
1079
940
|
|
package/LexicalSelection.prod.js
CHANGED
|
@@ -4,27 +4,27 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
-
'use strict';var k=require("lexical");let
|
|
8
|
-
function
|
|
9
|
-
function
|
|
10
|
-
a=r?
|
|
11
|
-
a!==h&&([
|
|
12
|
-
function
|
|
13
|
-
q&&k.$isLeafNode(n)&&!r.has(n.getKey())){if(n=q.getKey(),void 0===h.get(n)){let t=
|
|
14
|
-
|
|
15
|
-
null!==b?b.selectEnd():a.dirty=!0}}function
|
|
7
|
+
'use strict';var k=require("lexical"),u=require("@lexical/utils");let v=new Map;function w(a){for(;null!=a;){if(a.nodeType===Node.TEXT_NODE)return a;a=a.firstChild}return null}function y(a){let b=a.parentNode;if(null==b)throw Error("Should never happen");return[b,Array.from(b.childNodes).indexOf(a)]}function z(a){let b={};a=a.split(";");for(let d of a)if(""!==d){let [f,c]=d.split(/:([^]+)/);b[f.trim()]=c.trim()}return b}function A(a){let b=v.get(a);void 0===b&&(b=z(a),v.set(a,b));return b}
|
|
8
|
+
function B(a){let b="";for(let d in a)d&&(b+=`${d}: ${a[d]};`);return b}function C(a,b){var d=A("getStyle"in a?a.getStyle():a.style);b=Object.entries(b).reduce((f,[c,g])=>{null===g?delete f[c]:f[c]=g;return f},{...d});d=B(b);a.setStyle(d);v.set(d,b)}
|
|
9
|
+
function D(a,b){var d=a.getNodes(),f=d.length;if(k.DEPRECATED_$isGridSelection(a)){var c=k.$createRangeSelection(),g=c.anchor,h=c.focus;for(var e=0;e<f;e++){var l=d[e];k.DEPRECATED_$isGridCellNode(l)&&(g.set(l.getKey(),0,"element"),h.set(l.getKey(),l.getChildrenSize(),"element"),D(k.$normalizeSelection__EXPERIMENTAL(c),b))}k.$setSelection(a)}else if(--f,c=d[0],g=d[f],a.isCollapsed())C(a,b);else{e=a.anchor;var m=a.focus;l=c.getTextContent().length;var p=m.offset,n=e.offset,r=e.isBefore(m);h=r?n:p;
|
|
10
|
+
a=r?p:n;var q=r?e.type:m.type,t=r?m.type:e.type;m=r?m.key:e.key;k.$isTextNode(c)&&h===l&&(r=c.getNextSibling(),k.$isTextNode(r)&&(h=n=0,c=r));if(1===d.length)k.$isTextNode(c)&&(h="element"===q?0:n>p?p:n,a="element"===t?l:n>p?n:p,h!==a&&(0===h&&a===l?(C(c,b),c.select(h,a)):(d=c.splitText(h,a),d=0===h?d[0]:d[1],C(d,b),d.select(0,a-h))));else for(k.$isTextNode(c)&&h<c.getTextContentSize()&&(0!==h&&(c=c.splitText(h)[1],h=0,e.set(c.getKey(),h,"text")),C(c,b)),k.$isTextNode(g)&&(h=g.getTextContent().length,
|
|
11
|
+
g.__key!==m&&0!==a&&(a=h),a!==h&&([g]=g.splitText(a)),0!==a&&C(g,b)),a=1;a<f;a++)h=d[a],e=h.getKey(),k.$isTextNode(h)&&e!==c.getKey()&&e!==g.getKey()&&!h.isToken()&&C(h,b)}}function E(a){for(;null!==a&&!k.$isRootOrShadowRoot(a);){let b=a.getLatest(),d=a.getParent();0===b.getChildrenSize()&&a.remove(!0);a=d}}
|
|
12
|
+
function F(a,b,d,f,c=null){if(0!==b.length){var g=b[0],h=new Map,e=[];g=k.$isElementNode(g)?g:g.getParentOrThrow();g.isInline()&&(g=g.getParentOrThrow());for(var l=!1;null!==g;){var m=g.getPreviousSibling();if(null!==m){g=m;l=!0;break}g=g.getParentOrThrow();if(k.$isRootOrShadowRoot(g))break}m=new Set;for(var p=0;p<d;p++){var n=b[p];k.$isElementNode(n)&&0===n.getChildrenSize()&&m.add(n.getKey())}var r=new Set;for(p=0;p<d;p++){n=b[p];var q=n.getParent();null!==q&&q.isInline()&&(q=q.getParent());if(null!==
|
|
13
|
+
q&&k.$isLeafNode(n)&&!r.has(n.getKey())){if(n=q.getKey(),void 0===h.get(n)){let t=f();t.setFormat(q.getFormatType());t.setIndent(q.getIndent());e.push(t);h.set(n,t);q.getChildren().forEach(x=>{t.append(x);r.add(x.getKey());k.$isElementNode(x)&&x.getChildrenKeys().forEach(I=>r.add(I))});E(q)}}else m.has(n.getKey())&&(q=f(),q.setFormat(n.getFormatType()),q.setIndent(n.getIndent()),e.push(q),n.remove(!0))}if(null!==c)for(b=0;b<e.length;b++)c.append(e[b]);b=null;if(k.$isRootOrShadowRoot(g))if(l)if(null!==
|
|
14
|
+
c)g.insertAfter(c);else for(c=e.length-1;0<=c;c--)g.insertAfter(e[c]);else if(l=g.getFirstChild(),k.$isElementNode(l)&&(g=l),null===l)if(c)g.append(c);else for(c=0;c<e.length;c++)l=e[c],g.append(l),b=l;else if(null!==c)l.insertBefore(c);else for(g=0;g<e.length;g++)c=e[g],l.insertBefore(c),b=c;else if(c)g.insertAfter(c);else for(c=e.length-1;0<=c;c--)l=e[c],g.insertAfter(l),b=l;e=k.$getPreviousSelection();k.$isRangeSelection(e)&&e.anchor.getNode().isAttached()&&e.focus.getNode().isAttached()?k.$setSelection(e.clone()):
|
|
15
|
+
null!==b?b.selectEnd():a.dirty=!0}}function G(a,b,d,f){a.modify(b?"extend":"move",d,f)}function H(a){a=a.anchor.getNode();return"rtl"===(k.$isRootNode(a)?a:a.getParentOrThrow()).getDirection()}exports.$addNodeStyle=function(a){a=a.getStyle();let b=z(a);v.set(a,b)};
|
|
16
16
|
exports.$cloneWithProperties=function(a){let b=a.constructor.clone(a);b.__parent=a.__parent;b.__next=a.__next;b.__prev=a.__prev;if(k.$isElementNode(a)&&k.$isElementNode(b))return b.__first=a.__first,b.__last=a.__last,b.__size=a.__size,b.__format=a.__format,b.__indent=a.__indent,b.__dir=a.__dir,b;k.$isTextNode(a)&&k.$isTextNode(b)&&(b.__format=a.__format,b.__style=a.__style,b.__mode=a.__mode,b.__detail=a.__detail);return b};
|
|
17
|
-
exports.$getSelectionStyleValueForProperty=function(a,b,
|
|
18
|
-
exports.$isAtNodeEnd=function(a){return"text"===a.type?a.offset===a.getNode().getTextContentSize():a.offset===a.getNode().getChildrenSize()};exports.$isParentElementRTL=
|
|
19
|
-
exports.$selectAll=function(a){let b=a.anchor;a=a.focus;var
|
|
20
|
-
exports.$setBlocksType=function(a,b){if("root"===a.anchor.key){b=b();var
|
|
21
|
-
|
|
22
|
-
exports.$sliceSelectedTextNodeContent=function(a,b){if(b.isSelected()&&!b.isSegmented()&&!b.isToken()&&(k.$isRangeSelection(a)||k.DEPRECATED_$isGridSelection(a))){var
|
|
23
|
-
exports.$wrapNodes=function(a,b,
|
|
24
|
-
|
|
25
|
-
exports.createDOMRange=function(a,b,
|
|
26
|
-
|
|
27
|
-
exports.getStyleObjectFromCSS=
|
|
28
|
-
exports.trimTextContentFromAnchor=function(a,b,
|
|
29
|
-
|
|
30
|
-
c))):
|
|
17
|
+
exports.$getSelectionStyleValueForProperty=function(a,b,d=""){let f=null,c=a.getNodes();var g=a.anchor,h=a.focus,e=a.isBackward();let l=e?h.offset:g.offset;g=e?h.getNode():g.getNode();if(a.isCollapsed()&&""!==a.style&&(a=A(a.style),null!==a&&b in a))return a[b];for(a=0;a<c.length;a++){var m=c[a];if((0===a||0!==l||!m.is(g))&&k.$isTextNode(m))if(h=b,e=d,m=m.getStyle(),m=A(m),h=null!==m?m[h]||e:e,null===f)f=h;else if(f!==h){f="";break}}return null===f?d:f};
|
|
18
|
+
exports.$isAtNodeEnd=function(a){return"text"===a.type?a.offset===a.getNode().getTextContentSize():a.offset===a.getNode().getChildrenSize()};exports.$isParentElementRTL=H;exports.$moveCaretSelection=G;exports.$moveCharacter=function(a,b,d){let f=H(a);G(a,b,d?!f:f,"character")};exports.$patchStyleText=D;
|
|
19
|
+
exports.$selectAll=function(a){let b=a.anchor;a=a.focus;var d=b.getNode().getTopLevelElementOrThrow().getParentOrThrow();let f=d.getFirstDescendant();d=d.getLastDescendant();let c="element",g="element",h=0;k.$isTextNode(f)?c="text":k.$isElementNode(f)||null===f||(f=f.getParentOrThrow());k.$isTextNode(d)?(g="text",h=d.getTextContentSize()):k.$isElementNode(d)||null===d||(d=d.getParentOrThrow());f&&d&&(b.set(f.getKey(),0,c),a.set(d.getKey(),h,g))};
|
|
20
|
+
exports.$setBlocksType=function(a,b){if("root"===a.anchor.key){b=b();var d=k.$getRoot();(a=d.getFirstChild())?a.replace(b,!0):d.append(b)}else for(d=a.getNodes(),(a=u.$getAncestor(a.anchor.getNode(),u.INTERNAL_$isBlock))&&-1===d.indexOf(a)&&d.push(a),a=0;a<d.length;a++){let f=d[a];if(!u.INTERNAL_$isBlock(f))continue;let c=b();c.setFormat(f.getFormatType());c.setIndent(f.getIndent());f.replace(c,!0)}};
|
|
21
|
+
exports.$shouldOverrideDefaultCharacterSelection=function(a,b){a=k.$getAdjacentNode(a.focus,b);return k.$isDecoratorNode(a)&&!a.isIsolated()||k.$isElementNode(a)&&!a.isInline()&&!a.canBeEmpty()};
|
|
22
|
+
exports.$sliceSelectedTextNodeContent=function(a,b){if(b.isSelected()&&!b.isSegmented()&&!b.isToken()&&(k.$isRangeSelection(a)||k.DEPRECATED_$isGridSelection(a))){var d=a.anchor.getNode(),f=a.focus.getNode(),c=b.is(d),g=b.is(f);if(c||g){c=a.isBackward();let [h,e]=a.getCharacterOffsets();a=d.is(f);g=b.is(c?f:d);f=b.is(c?d:f);d=0;let l=void 0;a?(d=h>e?e:h,l=h>e?h:e):g?(d=c?e:h,l=void 0):f&&(c=c?h:e,d=0,l=c);b.__text=b.__text.slice(d,l)}}return b};
|
|
23
|
+
exports.$wrapNodes=function(a,b,d=null){var f=a.getNodes();let c=f.length;var g=a.anchor;if(0===c||1===c&&"element"===g.type&&0===g.getNode().getChildrenSize()){a="text"===g.type?g.getNode().getParentOrThrow():g.getNode();f=a.getChildren();let e=b();e.setFormat(a.getFormatType());e.setIndent(a.getIndent());f.forEach(l=>e.append(l));d&&(e=d.append(e));a.replace(e)}else{g=null;var h=[];for(let e=0;e<c;e++){let l=f[e];k.$isRootOrShadowRoot(l)?(F(a,h,h.length,b,d),h=[],g=l):null===g||null!==g&&k.$hasAncestor(l,
|
|
24
|
+
g)?h.push(l):(F(a,h,h.length,b,d),h=[l])}F(a,h,h.length,b,d)}};
|
|
25
|
+
exports.createDOMRange=function(a,b,d,f,c){let g=b.getKey(),h=f.getKey(),e=document.createRange(),l=a.getElementByKey(g);a=a.getElementByKey(h);k.$isTextNode(b)&&(l=w(l));k.$isTextNode(f)&&(a=w(a));if(void 0===b||void 0===f||null===l||null===a)return null;"BR"===l.nodeName&&([l,d]=y(l));"BR"===a.nodeName&&([a,c]=y(a));b=l.firstChild;l===a&&null!=b&&"BR"===b.nodeName&&0===d&&0===c&&(c=1);try{e.setStart(l,d),e.setEnd(a,c)}catch(m){return null}!e.collapsed||d===c&&g===h||(e.setStart(a,c),e.setEnd(l,
|
|
26
|
+
d));return e};exports.createRectsFromDOMRange=function(a,b){var d=a.getRootElement();if(null===d)return[];a=d.getBoundingClientRect();d=getComputedStyle(d);d=parseFloat(d.paddingLeft)+parseFloat(d.paddingRight);b=Array.from(b.getClientRects());let f=b.length;b.sort((g,h)=>{let e=g.top-h.top;return 3>=Math.abs(e)?g.left-h.left:e});let c;for(let g=0;g<f;g++){let h=b[g],e=h.width+d===a.width;c&&c.top<=h.top&&c.top+c.height>h.top&&c.left+c.width>h.left||e?(b.splice(g--,1),f--):c=h}return b};
|
|
27
|
+
exports.getStyleObjectFromCSS=A;
|
|
28
|
+
exports.trimTextContentFromAnchor=function(a,b,d){let f=b.getNode();if(k.$isElementNode(f)){var c=f.getDescendantByIndex(b.offset);null!==c&&(f=c)}for(;0<d&&null!==f;){k.$isElementNode(f)&&(c=f.getLastDescendant(),null!==c&&(f=c));var g=f.getPreviousSibling(),h=0;if(null===g){c=f.getParentOrThrow();for(var e=c.getPreviousSibling();null===e;){c=c.getParent();if(null===c){g=null;break}e=c.getPreviousSibling()}null!==c&&(h=c.isInline()?0:2,g=e)}e=f.getTextContent();""===e&&k.$isElementNode(f)&&!f.isInline()&&
|
|
29
|
+
(e="\n\n");c=e.length;if(!k.$isTextNode(f)||d>=c)e=f.getParent(),f.remove(),null==e||0!==e.getChildrenSize()||k.$isRootNode(e)||e.remove(),d-=c+h,f=g;else{let l=f.getKey();h=a.getEditorState().read(()=>{const p=k.$getNodeByKey(l);return k.$isTextNode(p)&&p.isSimpleText()?p.getTextContent():null});g=c-d;let m=e.slice(0,g);null!==h&&h!==e?(d=k.$getPreviousSelection(),c=f,f.isSimpleText()?f.setTextContent(h):(c=k.$createTextNode(h),f.replace(c)),k.$isRangeSelection(d)&&d.isCollapsed()&&(d=d.anchor.offset,
|
|
30
|
+
c.select(d,d))):f.isSimpleText()?(h=b.key===l,e=b.offset,e<d&&(e=c),d=h?e-d:0,c=h?e:g,h&&0===d?([d]=f.splitText(d,c),d.remove()):([,d]=f.splitText(d,c),d.remove())):(d=k.$createTextNode(m),f.replace(d));d=0}}}
|
package/package.json
CHANGED