@lexical/selection 0.5.0 → 0.5.1-next.0
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 +567 -502
- package/LexicalSelection.prod.js +24 -24
- package/constants.d.ts +8 -0
- package/grid-selection.d.ts +10 -0
- package/index.d.ts +6 -32
- package/lexical-node.d.ts +19 -0
- package/node-selection.d.ts +10 -0
- package/package.json +2 -2
- package/range-selection.d.ts +28 -0
- package/utils.d.ts +13 -0
package/LexicalSelection.dev.js
CHANGED
|
@@ -8,453 +8,208 @@
|
|
|
8
8
|
|
|
9
9
|
var lexical = require('lexical');
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (lexical.$isElementNode(latest) && lexical.$isElementNode(clone)) {
|
|
21
|
-
clone.__children = Array.from(latest.__children);
|
|
22
|
-
clone.__format = latest.__format;
|
|
23
|
-
clone.__indent = latest.__indent;
|
|
24
|
-
clone.__dir = latest.__dir;
|
|
25
|
-
} else if (lexical.$isTextNode(latest) && lexical.$isTextNode(clone)) {
|
|
26
|
-
clone.__format = latest.__format;
|
|
27
|
-
clone.__style = latest.__style;
|
|
28
|
-
clone.__mode = latest.__mode;
|
|
29
|
-
clone.__detail = latest.__detail;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return clone;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function $getIndexFromPossibleClone(node, parent, nodeMap) {
|
|
36
|
-
const parentClone = nodeMap.get(parent.getKey());
|
|
37
|
-
|
|
38
|
-
if (lexical.$isElementNode(parentClone)) {
|
|
39
|
-
return parentClone.__children.indexOf(node.getKey());
|
|
40
|
-
}
|
|
11
|
+
/**
|
|
12
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
13
|
+
*
|
|
14
|
+
* This source code is licensed under the MIT license found in the
|
|
15
|
+
* LICENSE file in the root directory of this source tree.
|
|
16
|
+
*
|
|
17
|
+
*/
|
|
18
|
+
const CSS_TO_STYLES = new Map();
|
|
41
19
|
|
|
42
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
22
|
+
*
|
|
23
|
+
* This source code is licensed under the MIT license found in the
|
|
24
|
+
* LICENSE file in the root directory of this source tree.
|
|
25
|
+
*
|
|
26
|
+
*/
|
|
27
|
+
function $cloneGridSelectionContent(selection) {
|
|
28
|
+
const nodeMap = selection.getNodes().map(node => {
|
|
29
|
+
const nodeKey = node.getKey();
|
|
30
|
+
const clone = $cloneWithProperties(node);
|
|
31
|
+
return [nodeKey, clone];
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
nodeMap,
|
|
35
|
+
range: [selection.gridKey]
|
|
36
|
+
};
|
|
43
37
|
}
|
|
44
38
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
41
|
+
*
|
|
42
|
+
* This source code is licensed under the MIT license found in the
|
|
43
|
+
* LICENSE file in the root directory of this source tree.
|
|
44
|
+
*
|
|
45
|
+
*/
|
|
46
|
+
function $cloneNodeSelectionContent(selection) {
|
|
47
|
+
{
|
|
48
|
+
throw Error(`TODO`);
|
|
50
49
|
}
|
|
51
|
-
|
|
52
|
-
return parent;
|
|
53
50
|
}
|
|
54
51
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (parent === null) {
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (!lexical.$isElementNode(node) || !node.excludeFromCopy('clone')) {
|
|
67
|
-
const key = node.getKey();
|
|
68
|
-
let clone = nodeMap.get(key);
|
|
69
|
-
const needsClone = clone === undefined;
|
|
70
|
-
|
|
71
|
-
if (needsClone) {
|
|
72
|
-
clone = $cloneWithProperties(node);
|
|
73
|
-
nodeMap.set(key, clone);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (lexical.$isTextNode(clone) && !clone.isSegmented() && !clone.isToken()) {
|
|
77
|
-
clone.__text = clone.__text.slice(isLeftSide ? offset : 0, isLeftSide ? endingOffset : offset);
|
|
78
|
-
} else if (lexical.$isElementNode(clone)) {
|
|
79
|
-
clone.__children = clone.__children.slice(isLeftSide ? offset : 0, isLeftSide ? undefined : (offset || 0) + 1);
|
|
80
|
-
}
|
|
52
|
+
/**
|
|
53
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
54
|
+
*
|
|
55
|
+
* This source code is licensed under the MIT license found in the
|
|
56
|
+
* LICENSE file in the root directory of this source tree.
|
|
57
|
+
*
|
|
58
|
+
*/
|
|
81
59
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// We only want to collect a range of top level nodes.
|
|
85
|
-
// So if the parent is the root, we know this is a top level.
|
|
86
|
-
range.push(key);
|
|
87
|
-
}
|
|
60
|
+
function getDOMTextNode(element) {
|
|
61
|
+
let node = element;
|
|
88
62
|
|
|
89
|
-
|
|
90
|
-
|
|
63
|
+
while (node != null) {
|
|
64
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
65
|
+
return node;
|
|
91
66
|
}
|
|
92
67
|
|
|
93
|
-
|
|
94
|
-
node = parent;
|
|
68
|
+
node = node.firstChild;
|
|
95
69
|
}
|
|
96
|
-
}
|
|
97
70
|
|
|
98
|
-
|
|
99
|
-
{
|
|
100
|
-
throw Error(`getLatest() on clone node`);
|
|
101
|
-
}
|
|
71
|
+
return null;
|
|
102
72
|
}
|
|
103
73
|
|
|
104
|
-
function
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
{
|
|
108
|
-
const nodeMap = clone.nodeMap;
|
|
109
|
-
|
|
110
|
-
for (let i = 0; i < nodeMap.length; i++) {
|
|
111
|
-
const node = nodeMap[i][1];
|
|
112
|
-
|
|
113
|
-
if (node.getLatest === errGetLatestOnClone) {
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
74
|
+
function getDOMIndexWithinParent(node) {
|
|
75
|
+
const parent = node.parentNode;
|
|
116
76
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
configurable: true,
|
|
120
|
-
enumerable: true,
|
|
121
|
-
value: errGetLatestOnClone,
|
|
122
|
-
writable: true
|
|
123
|
-
}
|
|
124
|
-
}));
|
|
125
|
-
}
|
|
77
|
+
if (parent == null) {
|
|
78
|
+
throw new Error('Should never happen');
|
|
126
79
|
}
|
|
127
80
|
|
|
128
|
-
return
|
|
81
|
+
return [parent, Array.from(parent.childNodes).indexOf(node)];
|
|
129
82
|
}
|
|
130
83
|
|
|
131
|
-
function
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
nodeMap: [],
|
|
141
|
-
range: []
|
|
142
|
-
};
|
|
143
|
-
} // Check if we can use the parent of the nodes, if the
|
|
144
|
-
// parent can't be empty, then it's important that we
|
|
145
|
-
// also copy that element node along with its children.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
let nodesLength = nodes.length;
|
|
149
|
-
const firstNode = nodes[0];
|
|
150
|
-
const firstNodeParent = firstNode.getParent();
|
|
151
|
-
|
|
152
|
-
if (firstNodeParent !== null && (!firstNodeParent.canBeEmpty() || lexical.$isRootNode(firstNodeParent))) {
|
|
153
|
-
const parentChildren = firstNodeParent.__children;
|
|
154
|
-
const parentChildrenLength = parentChildren.length;
|
|
155
|
-
|
|
156
|
-
if (parentChildrenLength === nodesLength) {
|
|
157
|
-
let areTheSame = true;
|
|
158
|
-
|
|
159
|
-
for (let i = 0; i < parentChildren.length; i++) {
|
|
160
|
-
if (parentChildren[i] !== nodes[i].__key) {
|
|
161
|
-
areTheSame = false;
|
|
162
|
-
break;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (areTheSame) {
|
|
167
|
-
nodesLength++;
|
|
168
|
-
nodes.push(firstNodeParent);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const lastNode = nodes[nodesLength - 1];
|
|
174
|
-
const isBefore = anchor.isBefore(focus);
|
|
175
|
-
const nodeMap = new Map();
|
|
176
|
-
const range = [];
|
|
177
|
-
const isOnlyText = lexical.$isTextNode(firstNode) && nodesLength === 1; // Do first node to root
|
|
178
|
-
|
|
179
|
-
$copyLeafNodeBranchToRoot(firstNode, isBefore ? anchorOffset : focusOffset, isOnlyText ? isBefore ? focusOffset : anchorOffset : undefined, true, range, nodeMap); // Copy all nodes between
|
|
180
|
-
|
|
181
|
-
for (let i = 0; i < nodesLength; i++) {
|
|
182
|
-
const node = nodes[i];
|
|
183
|
-
const key = node.getKey();
|
|
184
|
-
|
|
185
|
-
if (!nodeMap.has(key) && (!lexical.$isElementNode(node) || !node.excludeFromCopy('clone'))) {
|
|
186
|
-
const clone = $cloneWithProperties(node);
|
|
187
|
-
|
|
188
|
-
if (lexical.$isRootNode(node.getParent())) {
|
|
189
|
-
range.push(node.getKey());
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (key !== 'root') {
|
|
193
|
-
nodeMap.set(key, clone);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
} // Do last node to root
|
|
197
|
-
|
|
84
|
+
function createDOMRange(editor, anchorNode, _anchorOffset, focusNode, _focusOffset) {
|
|
85
|
+
const anchorKey = anchorNode.getKey();
|
|
86
|
+
const focusKey = focusNode.getKey();
|
|
87
|
+
const range = document.createRange();
|
|
88
|
+
let anchorDOM = editor.getElementByKey(anchorKey);
|
|
89
|
+
let focusDOM = editor.getElementByKey(focusKey);
|
|
90
|
+
let anchorOffset = _anchorOffset;
|
|
91
|
+
let focusOffset = _focusOffset;
|
|
198
92
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
nodeMap: Array.from(nodeMap.entries()),
|
|
202
|
-
range
|
|
203
|
-
};
|
|
204
|
-
} else if (lexical.DEPRECATED_$isGridSelection(selection)) {
|
|
205
|
-
const nodeMap = selection.getNodes().map(node => {
|
|
206
|
-
const nodeKey = node.getKey();
|
|
207
|
-
const clone = $cloneWithProperties(node);
|
|
208
|
-
return [nodeKey, clone];
|
|
209
|
-
});
|
|
210
|
-
return {
|
|
211
|
-
nodeMap,
|
|
212
|
-
range: [selection.gridKey]
|
|
213
|
-
};
|
|
93
|
+
if (lexical.$isTextNode(anchorNode)) {
|
|
94
|
+
anchorDOM = getDOMTextNode(anchorDOM);
|
|
214
95
|
}
|
|
215
96
|
|
|
216
|
-
{
|
|
217
|
-
|
|
97
|
+
if (lexical.$isTextNode(focusNode)) {
|
|
98
|
+
focusDOM = getDOMTextNode(focusDOM);
|
|
218
99
|
}
|
|
219
|
-
}
|
|
220
100
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (value === undefined) {
|
|
225
|
-
value = getStyleObjectFromRawCSS(css);
|
|
226
|
-
cssToStyles.set(css, value);
|
|
101
|
+
if (anchorNode === undefined || focusNode === undefined || anchorDOM === null || focusDOM === null) {
|
|
102
|
+
return null;
|
|
227
103
|
}
|
|
228
104
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
function getStyleObjectFromRawCSS(css) {
|
|
233
|
-
const styleObject = {};
|
|
234
|
-
const styles = css.split(';');
|
|
235
|
-
|
|
236
|
-
for (const style of styles) {
|
|
237
|
-
if (style !== '') {
|
|
238
|
-
const [key, value] = style.split(/:([^]+)/); // split on first colon
|
|
239
|
-
|
|
240
|
-
styleObject[key.trim()] = value.trim();
|
|
241
|
-
}
|
|
105
|
+
if (anchorDOM.nodeName === 'BR') {
|
|
106
|
+
[anchorDOM, anchorOffset] = getDOMIndexWithinParent(anchorDOM);
|
|
242
107
|
}
|
|
243
108
|
|
|
244
|
-
|
|
245
|
-
|
|
109
|
+
if (focusDOM.nodeName === 'BR') {
|
|
110
|
+
[focusDOM, focusOffset] = getDOMIndexWithinParent(focusDOM);
|
|
111
|
+
}
|
|
246
112
|
|
|
247
|
-
|
|
248
|
-
let css = '';
|
|
113
|
+
const firstChild = anchorDOM.firstChild;
|
|
249
114
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
css += `${style}: ${styles[style]};`;
|
|
253
|
-
}
|
|
115
|
+
if (anchorDOM === focusDOM && firstChild != null && firstChild.nodeName === 'BR' && anchorOffset === 0 && focusOffset === 0) {
|
|
116
|
+
focusOffset = 1;
|
|
254
117
|
}
|
|
255
118
|
|
|
256
|
-
|
|
257
|
-
|
|
119
|
+
try {
|
|
120
|
+
range.setStart(anchorDOM, anchorOffset);
|
|
121
|
+
range.setEnd(focusDOM, focusOffset);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
258
125
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
126
|
+
if (range.collapsed && (anchorOffset !== focusOffset || anchorKey !== focusKey)) {
|
|
127
|
+
// Range is backwards, we need to reverse it
|
|
128
|
+
range.setStart(focusDOM, focusOffset);
|
|
129
|
+
range.setEnd(anchorDOM, anchorOffset);
|
|
130
|
+
}
|
|
264
131
|
|
|
265
|
-
|
|
266
|
-
const prevStyles = getStyleObjectFromCSS(node.getStyle());
|
|
267
|
-
const newStyles = prevStyles ? { ...prevStyles,
|
|
268
|
-
...patch
|
|
269
|
-
} : patch;
|
|
270
|
-
const newCSSText = getCSSFromStyleObject(newStyles);
|
|
271
|
-
node.setStyle(newCSSText);
|
|
272
|
-
cssToStyles.set(newCSSText, newStyles);
|
|
132
|
+
return range;
|
|
273
133
|
}
|
|
134
|
+
function createRectsFromDOMRange(editor, range) {
|
|
135
|
+
const rootElement = editor.getRootElement();
|
|
274
136
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const selectedNodesLength = selectedNodes.length;
|
|
278
|
-
const lastIndex = selectedNodesLength - 1;
|
|
279
|
-
let firstNode = selectedNodes[0];
|
|
280
|
-
let lastNode = selectedNodes[lastIndex];
|
|
281
|
-
|
|
282
|
-
if (selection.isCollapsed()) {
|
|
283
|
-
return;
|
|
137
|
+
if (rootElement === null) {
|
|
138
|
+
return [];
|
|
284
139
|
}
|
|
285
140
|
|
|
286
|
-
const
|
|
287
|
-
const
|
|
288
|
-
const
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
let
|
|
292
|
-
let startOffset;
|
|
293
|
-
let endOffset;
|
|
294
|
-
const isBefore = anchor.isBefore(focus);
|
|
295
|
-
startOffset = isBefore ? anchorOffset : focusOffset;
|
|
296
|
-
endOffset = isBefore ? focusOffset : anchorOffset; // This is the case where the user only selected the very end of the
|
|
297
|
-
// first node so we don't want to include it in the formatting change.
|
|
298
|
-
|
|
299
|
-
if (startOffset === firstNode.getTextContentSize()) {
|
|
300
|
-
const nextSibling = firstNode.getNextSibling();
|
|
301
|
-
|
|
302
|
-
if (lexical.$isTextNode(nextSibling)) {
|
|
303
|
-
// we basically make the second node the firstNode, changing offsets accordingly
|
|
304
|
-
anchorOffset = 0;
|
|
305
|
-
startOffset = 0;
|
|
306
|
-
firstNode = nextSibling;
|
|
307
|
-
}
|
|
308
|
-
} // This is the case where we only selected a single node
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (firstNode.is(lastNode)) {
|
|
312
|
-
if (lexical.$isTextNode(firstNode)) {
|
|
313
|
-
startOffset = anchorOffset > focusOffset ? focusOffset : anchorOffset;
|
|
314
|
-
endOffset = anchorOffset > focusOffset ? anchorOffset : focusOffset; // No actual text is selected, so do nothing.
|
|
315
|
-
|
|
316
|
-
if (startOffset === endOffset) {
|
|
317
|
-
return;
|
|
318
|
-
} // The entire node is selected, so just format it
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (startOffset === 0 && endOffset === firstNodeTextLength) {
|
|
322
|
-
$patchNodeStyle(firstNode, patch);
|
|
323
|
-
firstNode.select(startOffset, endOffset);
|
|
324
|
-
} else {
|
|
325
|
-
// The node is partially selected, so split it into two nodes
|
|
326
|
-
// and style the selected one.
|
|
327
|
-
const splitNodes = firstNode.splitText(startOffset, endOffset);
|
|
328
|
-
const replacement = startOffset === 0 ? splitNodes[0] : splitNodes[1];
|
|
329
|
-
$patchNodeStyle(replacement, patch);
|
|
330
|
-
replacement.select(0, endOffset - startOffset);
|
|
331
|
-
}
|
|
332
|
-
} // multiple nodes selected.
|
|
333
|
-
|
|
334
|
-
} else {
|
|
335
|
-
if (lexical.$isTextNode(firstNode)) {
|
|
336
|
-
if (startOffset !== 0) {
|
|
337
|
-
// the entire first node isn't selected, so split it
|
|
338
|
-
firstNode = firstNode.splitText(startOffset)[1];
|
|
339
|
-
startOffset = 0;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
$patchNodeStyle(firstNode, patch);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
if (lexical.$isTextNode(lastNode)) {
|
|
346
|
-
const lastNodeText = lastNode.getTextContent();
|
|
347
|
-
const lastNodeTextLength = lastNodeText.length; // if the entire last node isn't selected, split it
|
|
348
|
-
|
|
349
|
-
if (endOffset !== lastNodeTextLength) {
|
|
350
|
-
[lastNode] = lastNode.splitText(endOffset);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (endOffset !== 0) {
|
|
354
|
-
$patchNodeStyle(lastNode, patch);
|
|
355
|
-
}
|
|
356
|
-
} // style all the text nodes in between
|
|
357
|
-
|
|
141
|
+
const rootRect = rootElement.getBoundingClientRect();
|
|
142
|
+
const computedStyle = getComputedStyle(rootElement);
|
|
143
|
+
const rootPadding = parseFloat(computedStyle.paddingLeft) + parseFloat(computedStyle.paddingRight);
|
|
144
|
+
const selectionRects = Array.from(range.getClientRects());
|
|
145
|
+
let selectionRectsLength = selectionRects.length;
|
|
146
|
+
let prevRect;
|
|
358
147
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
148
|
+
for (let i = 0; i < selectionRectsLength; i++) {
|
|
149
|
+
const selectionRect = selectionRects[i]; // Exclude a rect that is the exact same as the last rect. getClientRects() can return
|
|
150
|
+
// the same rect twice for some elements. A more sophisticated thing to do here is to
|
|
151
|
+
// merge all the rects together into a set of rects that don't overlap, so we don't
|
|
152
|
+
// generate backgrounds that are too dark.
|
|
362
153
|
|
|
363
|
-
|
|
364
|
-
$patchNodeStyle(selectedNode, patch);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
function $getSelectionStyleValueForProperty(selection, styleProperty, defaultValue = '') {
|
|
370
|
-
let styleValue = null;
|
|
371
|
-
const nodes = selection.getNodes();
|
|
372
|
-
const anchor = selection.anchor;
|
|
373
|
-
const focus = selection.focus;
|
|
374
|
-
const isBackward = selection.isBackward();
|
|
375
|
-
const endOffset = isBackward ? focus.offset : anchor.offset;
|
|
376
|
-
const endNode = isBackward ? focus.getNode() : anchor.getNode();
|
|
154
|
+
const isDuplicateRect = prevRect && prevRect.top === selectionRect.top && prevRect.left === selectionRect.left && prevRect.width === selectionRect.width && prevRect.height === selectionRect.height; // Exclude selections that span the entire element
|
|
377
155
|
|
|
378
|
-
|
|
379
|
-
const node = nodes[i]; // if no actual characters in the end node are selected, we don't
|
|
380
|
-
// include it in the selection for purposes of determining style
|
|
381
|
-
// value
|
|
156
|
+
const selectionSpansElement = selectionRect.width + rootPadding === rootRect.width;
|
|
382
157
|
|
|
383
|
-
if (
|
|
158
|
+
if (isDuplicateRect || selectionSpansElement) {
|
|
159
|
+
selectionRects.splice(i--, 1);
|
|
160
|
+
selectionRectsLength--;
|
|
384
161
|
continue;
|
|
385
162
|
}
|
|
386
163
|
|
|
387
|
-
|
|
388
|
-
const nodeStyleValue = $getNodeStyleValueForProperty(node, styleProperty, defaultValue);
|
|
389
|
-
|
|
390
|
-
if (styleValue === null) {
|
|
391
|
-
styleValue = nodeStyleValue;
|
|
392
|
-
} else if (styleValue !== nodeStyleValue) {
|
|
393
|
-
// multiple text nodes are in the selection and they don't all
|
|
394
|
-
// have the same font size.
|
|
395
|
-
styleValue = '';
|
|
396
|
-
break;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
return styleValue === null ? defaultValue : styleValue;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
function $getNodeStyleValueForProperty(node, styleProperty, defaultValue) {
|
|
405
|
-
const css = node.getStyle();
|
|
406
|
-
const styleObject = getStyleObjectFromCSS(css);
|
|
407
|
-
|
|
408
|
-
if (styleObject !== null) {
|
|
409
|
-
return styleObject[styleProperty] || defaultValue;
|
|
164
|
+
prevRect = selectionRect;
|
|
410
165
|
}
|
|
411
166
|
|
|
412
|
-
return
|
|
167
|
+
return selectionRects;
|
|
413
168
|
}
|
|
169
|
+
function getStyleObjectFromRawCSS(css) {
|
|
170
|
+
const styleObject = {};
|
|
171
|
+
const styles = css.split(';');
|
|
414
172
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
function $isParentElementRTL(selection) {
|
|
419
|
-
const anchorNode = selection.anchor.getNode();
|
|
420
|
-
const parent = lexical.$isRootNode(anchorNode) ? anchorNode : anchorNode.getParentOrThrow();
|
|
421
|
-
return parent.getDirection() === 'rtl';
|
|
422
|
-
}
|
|
423
|
-
function $moveCharacter(selection, isHoldingShift, isBackward) {
|
|
424
|
-
const isRTL = $isParentElementRTL(selection);
|
|
425
|
-
$moveCaretSelection(selection, isHoldingShift, isBackward ? !isRTL : isRTL, 'character');
|
|
426
|
-
}
|
|
427
|
-
function $selectAll(selection) {
|
|
428
|
-
const anchor = selection.anchor;
|
|
429
|
-
const focus = selection.focus;
|
|
430
|
-
const anchorNode = anchor.getNode();
|
|
431
|
-
const topParent = anchorNode.getTopLevelElementOrThrow();
|
|
432
|
-
const root = topParent.getParentOrThrow();
|
|
433
|
-
let firstNode = root.getFirstDescendant();
|
|
434
|
-
let lastNode = root.getLastDescendant();
|
|
435
|
-
let firstType = 'element';
|
|
436
|
-
let lastType = 'element';
|
|
437
|
-
let lastOffset = 0;
|
|
173
|
+
for (const style of styles) {
|
|
174
|
+
if (style !== '') {
|
|
175
|
+
const [key, value] = style.split(/:([^]+)/); // split on first colon
|
|
438
176
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
} else if (!lexical.$isElementNode(firstNode) && firstNode !== null) {
|
|
442
|
-
firstNode = firstNode.getParentOrThrow();
|
|
177
|
+
styleObject[key.trim()] = value.trim();
|
|
178
|
+
}
|
|
443
179
|
}
|
|
444
180
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
181
|
+
return styleObject;
|
|
182
|
+
}
|
|
183
|
+
function getStyleObjectFromCSS(css) {
|
|
184
|
+
let value = CSS_TO_STYLES.get(css);
|
|
185
|
+
|
|
186
|
+
if (value === undefined) {
|
|
187
|
+
value = getStyleObjectFromRawCSS(css);
|
|
188
|
+
CSS_TO_STYLES.set(css, value);
|
|
450
189
|
}
|
|
451
190
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
191
|
+
return value;
|
|
192
|
+
}
|
|
193
|
+
function getCSSFromStyleObject(styles) {
|
|
194
|
+
let css = '';
|
|
195
|
+
|
|
196
|
+
for (const style in styles) {
|
|
197
|
+
if (style) {
|
|
198
|
+
css += `${style}: ${styles[style]};`;
|
|
199
|
+
}
|
|
455
200
|
}
|
|
201
|
+
|
|
202
|
+
return css;
|
|
456
203
|
}
|
|
457
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
207
|
+
*
|
|
208
|
+
* This source code is licensed under the MIT license found in the
|
|
209
|
+
* LICENSE file in the root directory of this source tree.
|
|
210
|
+
*
|
|
211
|
+
*/
|
|
212
|
+
|
|
458
213
|
function $removeParentEmptyElements(startingNode) {
|
|
459
214
|
let node = startingNode;
|
|
460
215
|
|
|
@@ -469,6 +224,10 @@ function $removeParentEmptyElements(startingNode) {
|
|
|
469
224
|
node = parentNode;
|
|
470
225
|
}
|
|
471
226
|
}
|
|
227
|
+
|
|
228
|
+
function isPointAttached(point) {
|
|
229
|
+
return point.getNode().isAttached();
|
|
230
|
+
}
|
|
472
231
|
/**
|
|
473
232
|
* Attempts to wrap all nodes in the Selection in ElementNodes returned from createElement.
|
|
474
233
|
* If wrappingElement is provided, all of the wrapped leaves are appended to the wrappingElement.
|
|
@@ -674,131 +433,319 @@ function $wrapNodesImpl(selection, nodes, nodesLength, createElement, wrappingEl
|
|
|
674
433
|
selection.dirty = true;
|
|
675
434
|
}
|
|
676
435
|
}
|
|
436
|
+
function $shouldOverrideDefaultCharacterSelection(selection, isBackward) {
|
|
437
|
+
const possibleNode = lexical.$getDecoratorNode(selection.focus, isBackward);
|
|
438
|
+
return lexical.$isDecoratorNode(possibleNode) && !possibleNode.isIsolated();
|
|
439
|
+
}
|
|
440
|
+
function $moveCaretSelection(selection, isHoldingShift, isBackward, granularity) {
|
|
441
|
+
selection.modify(isHoldingShift ? 'extend' : 'move', isBackward, granularity);
|
|
442
|
+
}
|
|
443
|
+
function $isParentElementRTL(selection) {
|
|
444
|
+
const anchorNode = selection.anchor.getNode();
|
|
445
|
+
const parent = lexical.$isRootNode(anchorNode) ? anchorNode : anchorNode.getParentOrThrow();
|
|
446
|
+
return parent.getDirection() === 'rtl';
|
|
447
|
+
}
|
|
448
|
+
function $moveCharacter(selection, isHoldingShift, isBackward) {
|
|
449
|
+
const isRTL = $isParentElementRTL(selection);
|
|
450
|
+
$moveCaretSelection(selection, isHoldingShift, isBackward ? !isRTL : isRTL, 'character');
|
|
451
|
+
}
|
|
452
|
+
function $selectAll(selection) {
|
|
453
|
+
const anchor = selection.anchor;
|
|
454
|
+
const focus = selection.focus;
|
|
455
|
+
const anchorNode = anchor.getNode();
|
|
456
|
+
const topParent = anchorNode.getTopLevelElementOrThrow();
|
|
457
|
+
const root = topParent.getParentOrThrow();
|
|
458
|
+
let firstNode = root.getFirstDescendant();
|
|
459
|
+
let lastNode = root.getLastDescendant();
|
|
460
|
+
let firstType = 'element';
|
|
461
|
+
let lastType = 'element';
|
|
462
|
+
let lastOffset = 0;
|
|
677
463
|
|
|
678
|
-
|
|
679
|
-
|
|
464
|
+
if (lexical.$isTextNode(firstNode)) {
|
|
465
|
+
firstType = 'text';
|
|
466
|
+
} else if (!lexical.$isElementNode(firstNode) && firstNode !== null) {
|
|
467
|
+
firstNode = firstNode.getParentOrThrow();
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (lexical.$isTextNode(lastNode)) {
|
|
471
|
+
lastType = 'text';
|
|
472
|
+
lastOffset = lastNode.getTextContentSize();
|
|
473
|
+
} else if (!lexical.$isElementNode(lastNode) && lastNode !== null) {
|
|
474
|
+
lastNode = lastNode.getParentOrThrow();
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (firstNode && lastNode) {
|
|
478
|
+
anchor.set(firstNode.getKey(), 0, firstType);
|
|
479
|
+
focus.set(lastNode.getKey(), lastOffset, lastType);
|
|
480
|
+
}
|
|
680
481
|
}
|
|
681
482
|
|
|
682
|
-
function $
|
|
683
|
-
|
|
684
|
-
|
|
483
|
+
function $getIndexFromPossibleClone(node, parent, nodeMap) {
|
|
484
|
+
const parentClone = nodeMap.get(parent.getKey());
|
|
485
|
+
|
|
486
|
+
if (lexical.$isElementNode(parentClone)) {
|
|
487
|
+
return parentClone.__children.indexOf(node.getKey());
|
|
685
488
|
}
|
|
686
489
|
|
|
687
|
-
return
|
|
490
|
+
return node.getIndexWithinParent();
|
|
688
491
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
492
|
+
|
|
493
|
+
function $getParentAvoidingExcludedElements(node) {
|
|
494
|
+
let parent = node.getParent();
|
|
495
|
+
|
|
496
|
+
while (parent !== null && parent.excludeFromCopy('clone')) {
|
|
497
|
+
parent = parent.getParent();
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return parent;
|
|
692
501
|
}
|
|
693
502
|
|
|
694
|
-
function
|
|
695
|
-
let node =
|
|
503
|
+
function $copyLeafNodeBranchToRoot(leaf, startingOffset, endingOffset, isLeftSide, range, nodeMap) {
|
|
504
|
+
let node = leaf;
|
|
505
|
+
let offset = startingOffset;
|
|
696
506
|
|
|
697
|
-
while (node
|
|
698
|
-
|
|
699
|
-
|
|
507
|
+
while (node !== null) {
|
|
508
|
+
const parent = $getParentAvoidingExcludedElements(node);
|
|
509
|
+
|
|
510
|
+
if (parent === null) {
|
|
511
|
+
break;
|
|
700
512
|
}
|
|
701
513
|
|
|
702
|
-
node
|
|
703
|
-
|
|
514
|
+
if (!lexical.$isElementNode(node) || !node.excludeFromCopy('clone')) {
|
|
515
|
+
const key = node.getKey();
|
|
516
|
+
let clone = nodeMap.get(key);
|
|
517
|
+
const needsClone = clone === undefined;
|
|
704
518
|
|
|
705
|
-
|
|
519
|
+
if (needsClone) {
|
|
520
|
+
clone = $cloneWithProperties(node);
|
|
521
|
+
nodeMap.set(key, clone);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (lexical.$isTextNode(clone) && !clone.isSegmented() && !clone.isToken()) {
|
|
525
|
+
clone.__text = clone.__text.slice(isLeftSide ? offset : 0, isLeftSide ? endingOffset : offset);
|
|
526
|
+
} else if (lexical.$isElementNode(clone)) {
|
|
527
|
+
clone.__children = clone.__children.slice(isLeftSide ? offset : 0, isLeftSide ? undefined : (offset || 0) + 1);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (lexical.$isRootNode(parent)) {
|
|
531
|
+
if (needsClone) {
|
|
532
|
+
// We only want to collect a range of top level nodes.
|
|
533
|
+
// So if the parent is the root, we know this is a top level.
|
|
534
|
+
range.push(key);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
break;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
offset = $getIndexFromPossibleClone(node, parent, nodeMap);
|
|
542
|
+
node = parent;
|
|
543
|
+
}
|
|
706
544
|
}
|
|
707
545
|
|
|
708
|
-
function
|
|
709
|
-
const
|
|
546
|
+
function $cloneRangeSelectionContent(selection) {
|
|
547
|
+
const anchor = selection.anchor;
|
|
548
|
+
const focus = selection.focus;
|
|
549
|
+
const [anchorOffset, focusOffset] = selection.getCharacterOffsets();
|
|
550
|
+
const nodes = selection.getNodes();
|
|
710
551
|
|
|
711
|
-
if (
|
|
712
|
-
|
|
552
|
+
if (nodes.length === 0) {
|
|
553
|
+
return {
|
|
554
|
+
nodeMap: [],
|
|
555
|
+
range: []
|
|
556
|
+
};
|
|
557
|
+
} // Check if we can use the parent of the nodes, if the
|
|
558
|
+
// parent can't be empty, then it's important that we
|
|
559
|
+
// also copy that element node along with its children.
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
let nodesLength = nodes.length;
|
|
563
|
+
const firstNode = nodes[0];
|
|
564
|
+
const firstNodeParent = firstNode.getParent();
|
|
565
|
+
|
|
566
|
+
if (firstNodeParent !== null && (!firstNodeParent.canBeEmpty() || lexical.$isRootNode(firstNodeParent))) {
|
|
567
|
+
const parentChildren = firstNodeParent.__children;
|
|
568
|
+
const parentChildrenLength = parentChildren.length;
|
|
569
|
+
|
|
570
|
+
if (parentChildrenLength === nodesLength) {
|
|
571
|
+
let areTheSame = true;
|
|
572
|
+
|
|
573
|
+
for (let i = 0; i < parentChildren.length; i++) {
|
|
574
|
+
if (parentChildren[i] !== nodes[i].__key) {
|
|
575
|
+
areTheSame = false;
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (areTheSame) {
|
|
581
|
+
nodesLength++;
|
|
582
|
+
nodes.push(firstNodeParent);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
713
585
|
}
|
|
714
586
|
|
|
715
|
-
|
|
587
|
+
const lastNode = nodes[nodesLength - 1];
|
|
588
|
+
const isBefore = anchor.isBefore(focus);
|
|
589
|
+
const nodeMap = new Map();
|
|
590
|
+
const range = [];
|
|
591
|
+
const isOnlyText = lexical.$isTextNode(firstNode) && nodesLength === 1; // Do first node to root
|
|
592
|
+
|
|
593
|
+
$copyLeafNodeBranchToRoot(firstNode, isBefore ? anchorOffset : focusOffset, isOnlyText ? isBefore ? focusOffset : anchorOffset : undefined, true, range, nodeMap); // Copy all nodes between
|
|
594
|
+
|
|
595
|
+
for (let i = 0; i < nodesLength; i++) {
|
|
596
|
+
const node = nodes[i];
|
|
597
|
+
const key = node.getKey();
|
|
598
|
+
|
|
599
|
+
if (!nodeMap.has(key) && (!lexical.$isElementNode(node) || !node.excludeFromCopy('clone'))) {
|
|
600
|
+
const clone = $cloneWithProperties(node);
|
|
601
|
+
|
|
602
|
+
if (lexical.$isRootNode(node.getParent())) {
|
|
603
|
+
range.push(node.getKey());
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
if (key !== 'root') {
|
|
607
|
+
nodeMap.set(key, clone);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
} // Do last node to root
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
$copyLeafNodeBranchToRoot(lastNode, isOnlyText ? undefined : isBefore ? focusOffset : anchorOffset, undefined, false, range, nodeMap);
|
|
614
|
+
return {
|
|
615
|
+
nodeMap: Array.from(nodeMap.entries()),
|
|
616
|
+
range
|
|
617
|
+
};
|
|
716
618
|
}
|
|
717
619
|
|
|
718
|
-
function
|
|
719
|
-
const
|
|
720
|
-
const
|
|
721
|
-
const range = document.createRange();
|
|
722
|
-
let anchorDOM = editor.getElementByKey(anchorKey);
|
|
723
|
-
let focusDOM = editor.getElementByKey(focusKey);
|
|
724
|
-
let anchorOffset = _anchorOffset;
|
|
725
|
-
let focusOffset = _focusOffset;
|
|
620
|
+
function $getNodeStyleValueForProperty(node, styleProperty, defaultValue) {
|
|
621
|
+
const css = node.getStyle();
|
|
622
|
+
const styleObject = getStyleObjectFromCSS(css);
|
|
726
623
|
|
|
727
|
-
if (
|
|
728
|
-
|
|
624
|
+
if (styleObject !== null) {
|
|
625
|
+
return styleObject[styleProperty] || defaultValue;
|
|
729
626
|
}
|
|
730
627
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
}
|
|
628
|
+
return defaultValue;
|
|
629
|
+
}
|
|
734
630
|
|
|
735
|
-
|
|
736
|
-
|
|
631
|
+
function $getSelectionStyleValueForProperty(selection, styleProperty, defaultValue = '') {
|
|
632
|
+
let styleValue = null;
|
|
633
|
+
const nodes = selection.getNodes();
|
|
634
|
+
const anchor = selection.anchor;
|
|
635
|
+
const focus = selection.focus;
|
|
636
|
+
const isBackward = selection.isBackward();
|
|
637
|
+
const endOffset = isBackward ? focus.offset : anchor.offset;
|
|
638
|
+
const endNode = isBackward ? focus.getNode() : anchor.getNode();
|
|
639
|
+
|
|
640
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
641
|
+
const node = nodes[i]; // if no actual characters in the end node are selected, we don't
|
|
642
|
+
// include it in the selection for purposes of determining style
|
|
643
|
+
// value
|
|
644
|
+
|
|
645
|
+
if (i !== 0 && endOffset === 0 && node.is(endNode)) {
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if (lexical.$isTextNode(node)) {
|
|
650
|
+
const nodeStyleValue = $getNodeStyleValueForProperty(node, styleProperty, defaultValue);
|
|
651
|
+
|
|
652
|
+
if (styleValue === null) {
|
|
653
|
+
styleValue = nodeStyleValue;
|
|
654
|
+
} else if (styleValue !== nodeStyleValue) {
|
|
655
|
+
// multiple text nodes are in the selection and they don't all
|
|
656
|
+
// have the same font size.
|
|
657
|
+
styleValue = '';
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
737
661
|
}
|
|
738
662
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
663
|
+
return styleValue === null ? defaultValue : styleValue;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
668
|
+
*
|
|
669
|
+
* This source code is licensed under the MIT license found in the
|
|
670
|
+
* LICENSE file in the root directory of this source tree.
|
|
671
|
+
*
|
|
672
|
+
*/
|
|
673
|
+
|
|
674
|
+
function $updateElementNodeProperties(target, source) {
|
|
675
|
+
target.__children = Array.from(source.__children);
|
|
676
|
+
target.__format = source.__format;
|
|
677
|
+
target.__indent = source.__indent;
|
|
678
|
+
target.__dir = source.__dir;
|
|
679
|
+
return target;
|
|
680
|
+
}
|
|
742
681
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
682
|
+
function $updateTextNodeProperties(target, source) {
|
|
683
|
+
target.__format = source.__format;
|
|
684
|
+
target.__style = source.__style;
|
|
685
|
+
target.__mode = source.__mode;
|
|
686
|
+
target.__detail = source.__detail;
|
|
687
|
+
return target;
|
|
688
|
+
}
|
|
746
689
|
|
|
747
|
-
|
|
690
|
+
function $cloneWithProperties(node) {
|
|
691
|
+
const latest = node.getLatest();
|
|
692
|
+
const constructor = latest.constructor; // @ts-expect-error
|
|
748
693
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
}
|
|
694
|
+
const clone = constructor.clone(latest);
|
|
695
|
+
clone.__parent = latest.__parent;
|
|
752
696
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
range.setEnd(focusDOM, focusOffset);
|
|
756
|
-
} catch (e) {
|
|
757
|
-
return null;
|
|
697
|
+
if (lexical.$isElementNode(latest) && lexical.$isElementNode(clone)) {
|
|
698
|
+
return $updateElementNodeProperties(clone, latest);
|
|
758
699
|
}
|
|
759
700
|
|
|
760
|
-
if (
|
|
761
|
-
|
|
762
|
-
range.setStart(focusDOM, focusOffset);
|
|
763
|
-
range.setEnd(anchorDOM, anchorOffset);
|
|
701
|
+
if (lexical.$isTextNode(latest) && lexical.$isTextNode(clone)) {
|
|
702
|
+
return $updateTextNodeProperties(clone, latest);
|
|
764
703
|
}
|
|
765
704
|
|
|
766
|
-
return
|
|
705
|
+
return clone;
|
|
767
706
|
}
|
|
768
|
-
function
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
const rootRect = rootElement.getBoundingClientRect();
|
|
776
|
-
const computedStyle = getComputedStyle(rootElement);
|
|
777
|
-
const rootPadding = parseFloat(computedStyle.paddingLeft) + parseFloat(computedStyle.paddingRight);
|
|
778
|
-
const selectionRects = Array.from(range.getClientRects());
|
|
779
|
-
let selectionRectsLength = selectionRects.length;
|
|
780
|
-
let prevRect;
|
|
781
|
-
|
|
782
|
-
for (let i = 0; i < selectionRectsLength; i++) {
|
|
783
|
-
const selectionRect = selectionRects[i]; // Exclude a rect that is the exact same as the last rect. getClientRects() can return
|
|
784
|
-
// the same rect twice for some elements. A more sophisticated thing to do here is to
|
|
785
|
-
// merge all the rects together into a set of rects that don't overlap, so we don't
|
|
786
|
-
// generate backgrounds that are too dark.
|
|
707
|
+
function $sliceSelectedTextNodeContent(selection, textNode) {
|
|
708
|
+
if (textNode.isSelected() && !textNode.isSegmented() && !textNode.isToken() && (lexical.$isRangeSelection(selection) || lexical.DEPRECATED_$isGridSelection(selection))) {
|
|
709
|
+
const anchorNode = selection.anchor.getNode();
|
|
710
|
+
const focusNode = selection.focus.getNode();
|
|
711
|
+
const isAnchor = textNode.is(anchorNode);
|
|
712
|
+
const isFocus = textNode.is(focusNode);
|
|
787
713
|
|
|
788
|
-
|
|
714
|
+
if (isAnchor || isFocus) {
|
|
715
|
+
const isBackward = selection.isBackward();
|
|
716
|
+
const [anchorOffset, focusOffset] = selection.getCharacterOffsets();
|
|
717
|
+
const isSame = anchorNode.is(focusNode);
|
|
718
|
+
const isFirst = textNode.is(isBackward ? focusNode : anchorNode);
|
|
719
|
+
const isLast = textNode.is(isBackward ? anchorNode : focusNode);
|
|
720
|
+
let startOffset = 0;
|
|
721
|
+
let endOffset = undefined;
|
|
789
722
|
|
|
790
|
-
|
|
723
|
+
if (isSame) {
|
|
724
|
+
startOffset = anchorOffset > focusOffset ? focusOffset : anchorOffset;
|
|
725
|
+
endOffset = anchorOffset > focusOffset ? anchorOffset : focusOffset;
|
|
726
|
+
} else if (isFirst) {
|
|
727
|
+
const offset = isBackward ? focusOffset : anchorOffset;
|
|
728
|
+
startOffset = offset;
|
|
729
|
+
endOffset = undefined;
|
|
730
|
+
} else if (isLast) {
|
|
731
|
+
const offset = isBackward ? anchorOffset : focusOffset;
|
|
732
|
+
startOffset = 0;
|
|
733
|
+
endOffset = offset;
|
|
734
|
+
}
|
|
791
735
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
selectionRectsLength--;
|
|
795
|
-
continue;
|
|
736
|
+
textNode.__text = textNode.__text.slice(startOffset, endOffset);
|
|
737
|
+
return textNode;
|
|
796
738
|
}
|
|
739
|
+
}
|
|
797
740
|
|
|
798
|
-
|
|
741
|
+
return textNode;
|
|
742
|
+
}
|
|
743
|
+
function $isAtNodeEnd(point) {
|
|
744
|
+
if (point.type === 'text') {
|
|
745
|
+
return point.offset === point.getNode().getTextContentSize();
|
|
799
746
|
}
|
|
800
747
|
|
|
801
|
-
return
|
|
748
|
+
return point.offset === point.getNode().getChildrenSize();
|
|
802
749
|
}
|
|
803
750
|
function trimTextContentFromAnchor(editor, anchor, delCount) {
|
|
804
751
|
// Work from the current selection anchor point
|
|
@@ -923,41 +870,159 @@ function trimTextContentFromAnchor(editor, anchor, delCount) {
|
|
|
923
870
|
}
|
|
924
871
|
}
|
|
925
872
|
}
|
|
926
|
-
function $sliceSelectedTextNodeContent(selection, textNode) {
|
|
927
|
-
if (textNode.isSelected() && !textNode.isSegmented() && !textNode.isToken() && (lexical.$isRangeSelection(selection) || lexical.DEPRECATED_$isGridSelection(selection))) {
|
|
928
|
-
const anchorNode = selection.anchor.getNode();
|
|
929
|
-
const focusNode = selection.focus.getNode();
|
|
930
|
-
const isAnchor = textNode.is(anchorNode);
|
|
931
|
-
const isFocus = textNode.is(focusNode);
|
|
932
873
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
const isLast = textNode.is(isBackward ? anchorNode : focusNode);
|
|
939
|
-
let startOffset = 0;
|
|
940
|
-
let endOffset = undefined;
|
|
874
|
+
function errGetLatestOnClone() {
|
|
875
|
+
{
|
|
876
|
+
throw Error(`getLatest() on clone node`);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
941
879
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
880
|
+
function $cloneContents(selection) {
|
|
881
|
+
let clone = {
|
|
882
|
+
nodeMap: [],
|
|
883
|
+
range: []
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
if (lexical.$isRangeSelection(selection)) {
|
|
887
|
+
clone = $cloneRangeSelectionContent(selection);
|
|
888
|
+
} else if (lexical.DEPRECATED_$isGridSelection(selection)) {
|
|
889
|
+
clone = $cloneGridSelectionContent(selection);
|
|
890
|
+
} else if (lexical.$isNodeSelection(selection)) {
|
|
891
|
+
clone = $cloneNodeSelectionContent();
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
{
|
|
895
|
+
const nodeMap = clone.nodeMap;
|
|
896
|
+
|
|
897
|
+
for (let i = 0; i < nodeMap.length; i++) {
|
|
898
|
+
const node = nodeMap[i][1];
|
|
899
|
+
|
|
900
|
+
if (node.getLatest === errGetLatestOnClone) {
|
|
901
|
+
continue;
|
|
953
902
|
}
|
|
954
903
|
|
|
955
|
-
|
|
956
|
-
|
|
904
|
+
Object.setPrototypeOf(node, Object.create(Object.getPrototypeOf(node), {
|
|
905
|
+
getLatest: {
|
|
906
|
+
configurable: true,
|
|
907
|
+
enumerable: true,
|
|
908
|
+
value: errGetLatestOnClone,
|
|
909
|
+
writable: true
|
|
910
|
+
}
|
|
911
|
+
}));
|
|
957
912
|
}
|
|
958
913
|
}
|
|
959
914
|
|
|
960
|
-
return
|
|
915
|
+
return clone;
|
|
916
|
+
}
|
|
917
|
+
function $addNodeStyle(node) {
|
|
918
|
+
const CSSText = node.getStyle();
|
|
919
|
+
const styles = getStyleObjectFromRawCSS(CSSText);
|
|
920
|
+
CSS_TO_STYLES.set(CSSText, styles);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
function $patchNodeStyle(node, patch) {
|
|
924
|
+
const prevStyles = getStyleObjectFromCSS(node.getStyle());
|
|
925
|
+
const newStyles = prevStyles ? { ...prevStyles,
|
|
926
|
+
...patch
|
|
927
|
+
} : patch;
|
|
928
|
+
const newCSSText = getCSSFromStyleObject(newStyles);
|
|
929
|
+
node.setStyle(newCSSText);
|
|
930
|
+
CSS_TO_STYLES.set(newCSSText, newStyles);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function $patchStyleText(selection, patch) {
|
|
934
|
+
const selectedNodes = selection.getNodes();
|
|
935
|
+
const selectedNodesLength = selectedNodes.length;
|
|
936
|
+
const lastIndex = selectedNodesLength - 1;
|
|
937
|
+
let firstNode = selectedNodes[0];
|
|
938
|
+
let lastNode = selectedNodes[lastIndex];
|
|
939
|
+
|
|
940
|
+
if (selection.isCollapsed()) {
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
const anchor = selection.anchor;
|
|
945
|
+
const focus = selection.focus;
|
|
946
|
+
const firstNodeText = firstNode.getTextContent();
|
|
947
|
+
const firstNodeTextLength = firstNodeText.length;
|
|
948
|
+
const focusOffset = focus.offset;
|
|
949
|
+
let anchorOffset = anchor.offset;
|
|
950
|
+
let startOffset;
|
|
951
|
+
let endOffset;
|
|
952
|
+
const isBefore = anchor.isBefore(focus);
|
|
953
|
+
startOffset = isBefore ? anchorOffset : focusOffset;
|
|
954
|
+
endOffset = isBefore ? focusOffset : anchorOffset; // This is the case where the user only selected the very end of the
|
|
955
|
+
// first node so we don't want to include it in the formatting change.
|
|
956
|
+
|
|
957
|
+
if (startOffset === firstNode.getTextContentSize()) {
|
|
958
|
+
const nextSibling = firstNode.getNextSibling();
|
|
959
|
+
|
|
960
|
+
if (lexical.$isTextNode(nextSibling)) {
|
|
961
|
+
// we basically make the second node the firstNode, changing offsets accordingly
|
|
962
|
+
anchorOffset = 0;
|
|
963
|
+
startOffset = 0;
|
|
964
|
+
firstNode = nextSibling;
|
|
965
|
+
}
|
|
966
|
+
} // This is the case where we only selected a single node
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
if (firstNode.is(lastNode)) {
|
|
970
|
+
if (lexical.$isTextNode(firstNode)) {
|
|
971
|
+
startOffset = anchorOffset > focusOffset ? focusOffset : anchorOffset;
|
|
972
|
+
endOffset = anchorOffset > focusOffset ? anchorOffset : focusOffset; // No actual text is selected, so do nothing.
|
|
973
|
+
|
|
974
|
+
if (startOffset === endOffset) {
|
|
975
|
+
return;
|
|
976
|
+
} // The entire node is selected, so just format it
|
|
977
|
+
|
|
978
|
+
|
|
979
|
+
if (startOffset === 0 && endOffset === firstNodeTextLength) {
|
|
980
|
+
$patchNodeStyle(firstNode, patch);
|
|
981
|
+
firstNode.select(startOffset, endOffset);
|
|
982
|
+
} else {
|
|
983
|
+
// The node is partially selected, so split it into two nodes
|
|
984
|
+
// and style the selected one.
|
|
985
|
+
const splitNodes = firstNode.splitText(startOffset, endOffset);
|
|
986
|
+
const replacement = startOffset === 0 ? splitNodes[0] : splitNodes[1];
|
|
987
|
+
$patchNodeStyle(replacement, patch);
|
|
988
|
+
replacement.select(0, endOffset - startOffset);
|
|
989
|
+
}
|
|
990
|
+
} // multiple nodes selected.
|
|
991
|
+
|
|
992
|
+
} else {
|
|
993
|
+
if (lexical.$isTextNode(firstNode)) {
|
|
994
|
+
if (startOffset !== 0) {
|
|
995
|
+
// the entire first node isn't selected, so split it
|
|
996
|
+
firstNode = firstNode.splitText(startOffset)[1];
|
|
997
|
+
startOffset = 0;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
$patchNodeStyle(firstNode, patch);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
if (lexical.$isTextNode(lastNode)) {
|
|
1004
|
+
const lastNodeText = lastNode.getTextContent();
|
|
1005
|
+
const lastNodeTextLength = lastNodeText.length; // if the entire last node isn't selected, split it
|
|
1006
|
+
|
|
1007
|
+
if (endOffset !== lastNodeTextLength) {
|
|
1008
|
+
[lastNode] = lastNode.splitText(endOffset);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
if (endOffset !== 0) {
|
|
1012
|
+
$patchNodeStyle(lastNode, patch);
|
|
1013
|
+
}
|
|
1014
|
+
} // style all the text nodes in between
|
|
1015
|
+
|
|
1016
|
+
|
|
1017
|
+
for (let i = 1; i < lastIndex; i++) {
|
|
1018
|
+
const selectedNode = selectedNodes[i];
|
|
1019
|
+
const selectedNodeKey = selectedNode.getKey();
|
|
1020
|
+
|
|
1021
|
+
if (lexical.$isTextNode(selectedNode) && selectedNodeKey !== firstNode.getKey() && selectedNodeKey !== lastNode.getKey() && !selectedNode.isToken()) {
|
|
1022
|
+
$patchNodeStyle(selectedNode, patch);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
961
1026
|
}
|
|
962
1027
|
|
|
963
1028
|
exports.$addNodeStyle = $addNodeStyle;
|