@lexical/selection 0.1.15
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/LICENSE +21 -0
- package/LexicalSelection.dev.js +579 -0
- package/LexicalSelection.js +9 -0
- package/LexicalSelection.prod.js +21 -0
- package/README.md +3 -0
- package/package.json +26 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Dominic Gannaway
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
var lexical = require('lexical');
|
|
10
|
+
|
|
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
|
+
*/
|
|
19
|
+
const cssToStyles = new Map();
|
|
20
|
+
|
|
21
|
+
function $cloneWithProperties(node) {
|
|
22
|
+
const latest = node.getLatest();
|
|
23
|
+
const constructor = latest.constructor;
|
|
24
|
+
const clone = constructor.clone(latest);
|
|
25
|
+
clone.__parent = latest.__parent;
|
|
26
|
+
|
|
27
|
+
if (lexical.$isElementNode(latest) && lexical.$isElementNode(clone)) {
|
|
28
|
+
clone.__children = Array.from(latest.__children);
|
|
29
|
+
clone.__format = latest.__format;
|
|
30
|
+
clone.__indent = latest.__indent;
|
|
31
|
+
clone.__dir = latest.__dir;
|
|
32
|
+
} else if (lexical.$isTextNode(latest) && lexical.$isTextNode(clone)) {
|
|
33
|
+
clone.__format = latest.__format;
|
|
34
|
+
clone.__style = latest.__style;
|
|
35
|
+
clone.__mode = latest.__mode;
|
|
36
|
+
clone.__detail = latest.__detail;
|
|
37
|
+
} else if (lexical.$isDecoratorNode(latest) && lexical.$isDecoratorNode(clone)) {
|
|
38
|
+
clone.__state = latest.__state;
|
|
39
|
+
} // $FlowFixMe
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
return clone;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function $getIndexFromPossibleClone(node, parent, nodeMap) {
|
|
46
|
+
const parentClone = nodeMap.get(parent.getKey());
|
|
47
|
+
|
|
48
|
+
if (lexical.$isElementNode(parentClone)) {
|
|
49
|
+
return parentClone.__children.indexOf(node.getKey());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return node.getIndexWithinParent();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function $getParentAvoidingExcludedElements(node) {
|
|
56
|
+
let parent = node.getParent();
|
|
57
|
+
|
|
58
|
+
while (parent !== null && parent.excludeFromCopy()) {
|
|
59
|
+
parent = parent.getParent();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return parent;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function $copyLeafNodeBranchToRoot(leaf, startingOffset, isLeftSide, range, nodeMap) {
|
|
66
|
+
let node = leaf;
|
|
67
|
+
let offset = startingOffset;
|
|
68
|
+
|
|
69
|
+
while (node !== null) {
|
|
70
|
+
const parent = $getParentAvoidingExcludedElements(node);
|
|
71
|
+
|
|
72
|
+
if (parent === null) {
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!lexical.$isElementNode(node) || !node.excludeFromCopy()) {
|
|
77
|
+
const key = node.getKey();
|
|
78
|
+
let clone = nodeMap.get(key);
|
|
79
|
+
const needsClone = clone === undefined;
|
|
80
|
+
|
|
81
|
+
if (needsClone) {
|
|
82
|
+
clone = $cloneWithProperties(node);
|
|
83
|
+
nodeMap.set(key, clone);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (lexical.$isTextNode(clone) && !clone.isSegmented() && !clone.isToken()) {
|
|
87
|
+
clone.__text = clone.__text.slice(isLeftSide ? offset : 0, isLeftSide ? undefined : offset);
|
|
88
|
+
} else if (lexical.$isElementNode(clone)) {
|
|
89
|
+
clone.__children = clone.__children.slice(isLeftSide ? offset : 0, isLeftSide ? undefined : offset + 1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (lexical.$isRootNode(parent)) {
|
|
93
|
+
if (needsClone) {
|
|
94
|
+
// We only want to collect a range of top level nodes.
|
|
95
|
+
// So if the parent is the root, we know this is a top level.
|
|
96
|
+
range.push(key);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
offset = $getIndexFromPossibleClone(node, parent, nodeMap);
|
|
104
|
+
node = parent;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function $cloneContents(selection) {
|
|
109
|
+
if (!lexical.$isRangeSelection(selection)) {
|
|
110
|
+
{
|
|
111
|
+
throw Error(`TODO`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const anchor = selection.anchor;
|
|
116
|
+
const focus = selection.focus;
|
|
117
|
+
const anchorOffset = anchor.getCharacterOffset();
|
|
118
|
+
const focusOffset = focus.getCharacterOffset();
|
|
119
|
+
const anchorNode = anchor.getNode();
|
|
120
|
+
const focusNode = focus.getNode();
|
|
121
|
+
const anchorNodeParent = anchorNode.getParentOrThrow(); // Handle a single text node extraction
|
|
122
|
+
|
|
123
|
+
if (anchorNode === focusNode && lexical.$isTextNode(anchorNode) && (anchorNodeParent.canBeEmpty() || anchorNodeParent.getChildrenSize() > 1)) {
|
|
124
|
+
const clonedFirstNode = $cloneWithProperties(anchorNode);
|
|
125
|
+
const isBefore = focusOffset > anchorOffset;
|
|
126
|
+
const startOffset = isBefore ? anchorOffset : focusOffset;
|
|
127
|
+
const endOffset = isBefore ? focusOffset : anchorOffset;
|
|
128
|
+
clonedFirstNode.__text = clonedFirstNode.__text.slice(startOffset, endOffset);
|
|
129
|
+
const key = clonedFirstNode.getKey();
|
|
130
|
+
return {
|
|
131
|
+
nodeMap: [[key, clonedFirstNode]],
|
|
132
|
+
range: [key]
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const nodes = selection.getNodes();
|
|
137
|
+
|
|
138
|
+
if (nodes.length === 0) {
|
|
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 = []; // Do first node to root
|
|
177
|
+
|
|
178
|
+
$copyLeafNodeBranchToRoot(firstNode, isBefore ? anchorOffset : focusOffset, true, range, nodeMap); // Copy all nodes between
|
|
179
|
+
|
|
180
|
+
for (let i = 0; i < nodesLength; i++) {
|
|
181
|
+
const node = nodes[i];
|
|
182
|
+
const key = node.getKey();
|
|
183
|
+
|
|
184
|
+
if (!nodeMap.has(key) && (!lexical.$isElementNode(node) || !node.excludeFromCopy())) {
|
|
185
|
+
const clone = $cloneWithProperties(node);
|
|
186
|
+
|
|
187
|
+
if (lexical.$isRootNode(node.getParent())) {
|
|
188
|
+
range.push(node.getKey());
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
nodeMap.set(key, clone);
|
|
192
|
+
}
|
|
193
|
+
} // Do last node to root
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
$copyLeafNodeBranchToRoot(lastNode, isBefore ? focusOffset : anchorOffset, false, range, nodeMap);
|
|
197
|
+
return {
|
|
198
|
+
nodeMap: Array.from(nodeMap.entries()),
|
|
199
|
+
range
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function getStyleObjectFromCSS(css) {
|
|
203
|
+
return cssToStyles.get(css) || null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function getCSSFromStyleObject(styles) {
|
|
207
|
+
let css = '';
|
|
208
|
+
|
|
209
|
+
for (const style in styles) {
|
|
210
|
+
if (style) {
|
|
211
|
+
css += `${style}: ${styles[style]};`;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return css;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function $patchNodeStyle(node, patch) {
|
|
219
|
+
const prevStyles = getStyleObjectFromCSS(node.getStyle());
|
|
220
|
+
const newStyles = prevStyles ? { ...prevStyles,
|
|
221
|
+
...patch
|
|
222
|
+
} : patch;
|
|
223
|
+
const newCSSText = getCSSFromStyleObject(newStyles);
|
|
224
|
+
node.setStyle(newCSSText);
|
|
225
|
+
cssToStyles.set(newCSSText, newStyles);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function $patchStyleText(selection, patch) {
|
|
229
|
+
const selectedNodes = selection.getNodes();
|
|
230
|
+
const selectedNodesLength = selectedNodes.length;
|
|
231
|
+
const lastIndex = selectedNodesLength - 1;
|
|
232
|
+
let firstNode = selectedNodes[0];
|
|
233
|
+
let lastNode = selectedNodes[lastIndex];
|
|
234
|
+
|
|
235
|
+
if (selection.isCollapsed()) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const anchor = selection.anchor;
|
|
240
|
+
const focus = selection.focus;
|
|
241
|
+
const firstNodeText = firstNode.getTextContent();
|
|
242
|
+
const firstNodeTextLength = firstNodeText.length;
|
|
243
|
+
const focusOffset = focus.offset;
|
|
244
|
+
let anchorOffset = anchor.offset;
|
|
245
|
+
let startOffset;
|
|
246
|
+
let endOffset;
|
|
247
|
+
const isBefore = anchor.isBefore(focus);
|
|
248
|
+
startOffset = isBefore ? anchorOffset : focusOffset;
|
|
249
|
+
endOffset = isBefore ? focusOffset : anchorOffset; // This is the case where the user only selected the very end of the
|
|
250
|
+
// first node so we don't want to include it in the formatting change.
|
|
251
|
+
|
|
252
|
+
if (startOffset === firstNode.getTextContentSize()) {
|
|
253
|
+
const nextSibling = firstNode.getNextSibling();
|
|
254
|
+
|
|
255
|
+
if (lexical.$isTextNode(nextSibling)) {
|
|
256
|
+
// we basically make the second node the firstNode, changing offsets accordingly
|
|
257
|
+
anchorOffset = 0;
|
|
258
|
+
startOffset = 0;
|
|
259
|
+
firstNode = nextSibling;
|
|
260
|
+
}
|
|
261
|
+
} // This is the case where we only selected a single node
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
if (firstNode.is(lastNode)) {
|
|
265
|
+
if (lexical.$isTextNode(firstNode)) {
|
|
266
|
+
startOffset = anchorOffset > focusOffset ? focusOffset : anchorOffset;
|
|
267
|
+
endOffset = anchorOffset > focusOffset ? anchorOffset : focusOffset; // No actual text is selected, so do nothing.
|
|
268
|
+
|
|
269
|
+
if (startOffset === endOffset) {
|
|
270
|
+
return;
|
|
271
|
+
} // The entire node is selected, so just format it
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
if (startOffset === 0 && endOffset === firstNodeTextLength) {
|
|
275
|
+
$patchNodeStyle(firstNode, patch);
|
|
276
|
+
firstNode.select(startOffset, endOffset);
|
|
277
|
+
} else {
|
|
278
|
+
// The node is partially selected, so split it into two nodes
|
|
279
|
+
// and style the selected one.
|
|
280
|
+
const splitNodes = firstNode.splitText(startOffset, endOffset);
|
|
281
|
+
const replacement = startOffset === 0 ? splitNodes[0] : splitNodes[1];
|
|
282
|
+
$patchNodeStyle(replacement, patch);
|
|
283
|
+
replacement.select(0, endOffset - startOffset);
|
|
284
|
+
}
|
|
285
|
+
} // multiple nodes selected.
|
|
286
|
+
|
|
287
|
+
} else {
|
|
288
|
+
if (lexical.$isTextNode(firstNode)) {
|
|
289
|
+
if (startOffset !== 0) {
|
|
290
|
+
// the entire first node isn't selected, so split it
|
|
291
|
+
[, firstNode] = firstNode.splitText(startOffset);
|
|
292
|
+
startOffset = 0;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
$patchNodeStyle(firstNode, patch);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (lexical.$isTextNode(lastNode)) {
|
|
299
|
+
const lastNodeText = lastNode.getTextContent();
|
|
300
|
+
const lastNodeTextLength = lastNodeText.length; // if the entire last node isn't selected, split it
|
|
301
|
+
|
|
302
|
+
if (endOffset !== lastNodeTextLength) {
|
|
303
|
+
[lastNode] = lastNode.splitText(endOffset);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (endOffset !== 0) {
|
|
307
|
+
$patchNodeStyle(lastNode, patch);
|
|
308
|
+
}
|
|
309
|
+
} // style all the text nodes in between
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
for (let i = 1; i < lastIndex; i++) {
|
|
313
|
+
const selectedNode = selectedNodes[i];
|
|
314
|
+
const selectedNodeKey = selectedNode.getKey();
|
|
315
|
+
|
|
316
|
+
if (lexical.$isTextNode(selectedNode) && selectedNodeKey !== firstNode.getKey() && selectedNodeKey !== lastNode.getKey() && !selectedNode.isToken()) {
|
|
317
|
+
$patchNodeStyle(selectedNode, patch);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function $getSelectionStyleValueForProperty(selection, styleProperty, defaultValue = '') {
|
|
323
|
+
let styleValue = null;
|
|
324
|
+
const nodes = selection.getNodes();
|
|
325
|
+
const anchor = selection.anchor;
|
|
326
|
+
const focus = selection.focus;
|
|
327
|
+
const isBackward = selection.isBackward();
|
|
328
|
+
const endOffset = isBackward ? focus.offset : anchor.offset;
|
|
329
|
+
const endNode = isBackward ? focus.getNode() : anchor.getNode();
|
|
330
|
+
|
|
331
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
332
|
+
const node = nodes[i]; // if no actual characters in the end node are selected, we don't
|
|
333
|
+
// include it in the selection for purposes of determining style
|
|
334
|
+
// value
|
|
335
|
+
|
|
336
|
+
if (i !== 0 && endOffset === 0 && node.is(endNode)) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (lexical.$isTextNode(node)) {
|
|
341
|
+
const nodeStyleValue = $getNodeStyleValueForProperty(node, styleProperty, defaultValue);
|
|
342
|
+
|
|
343
|
+
if (styleValue === null) {
|
|
344
|
+
styleValue = nodeStyleValue;
|
|
345
|
+
} else if (styleValue !== nodeStyleValue) {
|
|
346
|
+
// multiple text nodes are in the selection and they don't all
|
|
347
|
+
// have the same font size.
|
|
348
|
+
styleValue = '';
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return styleValue === null ? defaultValue : styleValue;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function $getNodeStyleValueForProperty(node, styleProperty, defaultValue) {
|
|
358
|
+
const css = node.getStyle();
|
|
359
|
+
const styleObject = getStyleObjectFromCSS(css);
|
|
360
|
+
|
|
361
|
+
if (styleObject !== null) {
|
|
362
|
+
return styleObject[styleProperty] || defaultValue;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return defaultValue;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function $moveCaretSelection(selection, isHoldingShift, isBackward, granularity) {
|
|
369
|
+
selection.modify(isHoldingShift ? 'extend' : 'move', isBackward, granularity);
|
|
370
|
+
}
|
|
371
|
+
function $isParentElementRTL(selection) {
|
|
372
|
+
const anchorNode = selection.anchor.getNode();
|
|
373
|
+
const parent = lexical.$isRootNode(anchorNode) ? anchorNode : anchorNode.getParentOrThrow();
|
|
374
|
+
return parent.getDirection() === 'rtl';
|
|
375
|
+
}
|
|
376
|
+
function $moveCharacter(selection, isHoldingShift, isBackward) {
|
|
377
|
+
const isRTL = $isParentElementRTL(selection);
|
|
378
|
+
$moveCaretSelection(selection, isHoldingShift, isBackward ? !isRTL : isRTL, 'character');
|
|
379
|
+
}
|
|
380
|
+
function $selectAll(selection) {
|
|
381
|
+
const anchor = selection.anchor;
|
|
382
|
+
const focus = selection.focus;
|
|
383
|
+
const anchorNode = anchor.getNode();
|
|
384
|
+
const topParent = anchorNode.getTopLevelElementOrThrow();
|
|
385
|
+
const root = topParent.getParentOrThrow();
|
|
386
|
+
let firstNode = root.getFirstDescendant();
|
|
387
|
+
let lastNode = root.getLastDescendant();
|
|
388
|
+
let firstType = 'element';
|
|
389
|
+
let lastType = 'element';
|
|
390
|
+
let lastOffset = 0;
|
|
391
|
+
|
|
392
|
+
if (lexical.$isTextNode(firstNode)) {
|
|
393
|
+
firstType = 'text';
|
|
394
|
+
} else if (!lexical.$isElementNode(firstNode) && firstNode !== null) {
|
|
395
|
+
firstNode = firstNode.getParentOrThrow();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (lexical.$isTextNode(lastNode)) {
|
|
399
|
+
lastType = 'text';
|
|
400
|
+
lastOffset = lastNode.getTextContentSize();
|
|
401
|
+
} else if (!lexical.$isElementNode(lastNode) && lastNode !== null) {
|
|
402
|
+
lastNode = lastNode.getParentOrThrow();
|
|
403
|
+
lastOffset = lastNode.getChildrenSize();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (firstNode && lastNode) {
|
|
407
|
+
anchor.set(firstNode.getKey(), 0, firstType);
|
|
408
|
+
focus.set(lastNode.getKey(), lastOffset, lastType);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function $removeParentEmptyElements(startingNode) {
|
|
413
|
+
let node = startingNode;
|
|
414
|
+
|
|
415
|
+
while (node !== null && !lexical.$isRootNode(node)) {
|
|
416
|
+
const latest = node.getLatest();
|
|
417
|
+
const parentNode = node.getParent();
|
|
418
|
+
|
|
419
|
+
if (latest.__children.length === 0) {
|
|
420
|
+
node.remove();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
node = parentNode;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function $wrapLeafNodesInElements(selection, createElement, wrappingElement) {
|
|
428
|
+
const nodes = selection.getNodes();
|
|
429
|
+
const nodesLength = nodes.length;
|
|
430
|
+
|
|
431
|
+
if (nodesLength === 0) {
|
|
432
|
+
const anchor = selection.anchor;
|
|
433
|
+
const target = anchor.type === 'text' ? anchor.getNode().getParentOrThrow() : anchor.getNode();
|
|
434
|
+
const children = target.getChildren();
|
|
435
|
+
let element = createElement();
|
|
436
|
+
children.forEach(child => element.append(child));
|
|
437
|
+
|
|
438
|
+
if (wrappingElement) {
|
|
439
|
+
element = wrappingElement.append(element);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
target.replace(element);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const firstNode = nodes[0];
|
|
447
|
+
const elementMapping = new Map();
|
|
448
|
+
const elements = []; // The below logic is to find the right target for us to
|
|
449
|
+
// either insertAfter/insertBefore/append the corresponding
|
|
450
|
+
// elements to. This is made more complicated due to nested
|
|
451
|
+
// structures.
|
|
452
|
+
|
|
453
|
+
let target = lexical.$isElementNode(firstNode) ? firstNode : firstNode.getParentOrThrow();
|
|
454
|
+
|
|
455
|
+
while (target !== null) {
|
|
456
|
+
const prevSibling = target.getPreviousSibling();
|
|
457
|
+
|
|
458
|
+
if (prevSibling !== null) {
|
|
459
|
+
target = prevSibling;
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
target = target.getParentOrThrow();
|
|
464
|
+
|
|
465
|
+
if (lexical.$isRootNode(target)) {
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const emptyElements = new Set(); // Find any top level empty elements
|
|
471
|
+
|
|
472
|
+
for (let i = 0; i < nodesLength; i++) {
|
|
473
|
+
const node = nodes[i];
|
|
474
|
+
|
|
475
|
+
if (lexical.$isElementNode(node) && node.getChildrenSize() === 0) {
|
|
476
|
+
emptyElements.add(node.getKey());
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const movedLeafNodes = new Set(); // Move out all leaf nodes into our elements array.
|
|
481
|
+
// If we find a top level empty element, also move make
|
|
482
|
+
// an element for that.
|
|
483
|
+
|
|
484
|
+
for (let i = 0; i < nodesLength; i++) {
|
|
485
|
+
const node = nodes[i];
|
|
486
|
+
const parent = node.getParent();
|
|
487
|
+
|
|
488
|
+
if (parent !== null && lexical.$isLeafNode(node) && !movedLeafNodes.has(node.getKey())) {
|
|
489
|
+
const parentKey = parent.getKey();
|
|
490
|
+
|
|
491
|
+
if (elementMapping.get(parentKey) === undefined) {
|
|
492
|
+
const targetElement = createElement();
|
|
493
|
+
elements.push(targetElement);
|
|
494
|
+
elementMapping.set(parentKey, targetElement); // Move node and its siblings to the new
|
|
495
|
+
// element.
|
|
496
|
+
|
|
497
|
+
parent.getChildren().forEach(child => {
|
|
498
|
+
targetElement.append(child);
|
|
499
|
+
movedLeafNodes.add(child.getKey());
|
|
500
|
+
});
|
|
501
|
+
$removeParentEmptyElements(parent);
|
|
502
|
+
}
|
|
503
|
+
} else if (emptyElements.has(node.getKey())) {
|
|
504
|
+
elements.push(createElement());
|
|
505
|
+
node.remove();
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (wrappingElement) {
|
|
510
|
+
for (let i = 0; i < elements.length; i++) {
|
|
511
|
+
const element = elements[i];
|
|
512
|
+
wrappingElement.append(element);
|
|
513
|
+
}
|
|
514
|
+
} // If our target is the root, let's see if we can re-adjust
|
|
515
|
+
// so that the target is the first child instead.
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
if (lexical.$isRootNode(target)) {
|
|
519
|
+
const firstChild = target.getFirstChild();
|
|
520
|
+
|
|
521
|
+
if (lexical.$isElementNode(firstChild)) {
|
|
522
|
+
target = firstChild;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (firstChild === null) {
|
|
526
|
+
if (wrappingElement) {
|
|
527
|
+
target.append(wrappingElement);
|
|
528
|
+
} else {
|
|
529
|
+
for (let i = 0; i < elements.length; i++) {
|
|
530
|
+
const element = elements[i];
|
|
531
|
+
target.append(element);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
} else {
|
|
535
|
+
if (wrappingElement) {
|
|
536
|
+
firstChild.insertBefore(wrappingElement);
|
|
537
|
+
} else {
|
|
538
|
+
for (let i = 0; i < elements.length; i++) {
|
|
539
|
+
const element = elements[i];
|
|
540
|
+
firstChild.insertBefore(element);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
} else {
|
|
545
|
+
if (wrappingElement) {
|
|
546
|
+
target.insertAfter(wrappingElement);
|
|
547
|
+
} else {
|
|
548
|
+
for (let i = elements.length - 1; i >= 0; i--) {
|
|
549
|
+
const element = elements[i];
|
|
550
|
+
target.insertAfter(element);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
selection.dirty = true;
|
|
556
|
+
}
|
|
557
|
+
function $isAtNodeEnd(point) {
|
|
558
|
+
if (point.type === 'text') {
|
|
559
|
+
return point.offset === point.getNode().getTextContentSize();
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return point.offset === point.getNode().getChildrenSize();
|
|
563
|
+
}
|
|
564
|
+
function $shouldOverrideDefaultCharacterSelection(selection, isBackward) {
|
|
565
|
+
const possibleNode = lexical.$getDecoratorNode(selection.focus, isBackward);
|
|
566
|
+
return lexical.$isDecoratorNode(possibleNode) && !possibleNode.isIsolated();
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
exports.$cloneContents = $cloneContents;
|
|
570
|
+
exports.$getSelectionStyleValueForProperty = $getSelectionStyleValueForProperty;
|
|
571
|
+
exports.$isAtNodeEnd = $isAtNodeEnd;
|
|
572
|
+
exports.$isParentElementRTL = $isParentElementRTL;
|
|
573
|
+
exports.$moveCaretSelection = $moveCaretSelection;
|
|
574
|
+
exports.$moveCharacter = $moveCharacter;
|
|
575
|
+
exports.$patchStyleText = $patchStyleText;
|
|
576
|
+
exports.$selectAll = $selectAll;
|
|
577
|
+
exports.$shouldOverrideDefaultCharacterSelection = $shouldOverrideDefaultCharacterSelection;
|
|
578
|
+
exports.$wrapLeafNodesInElements = $wrapLeafNodesInElements;
|
|
579
|
+
exports.getStyleObjectFromCSS = getStyleObjectFromCSS;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
'use strict'
|
|
8
|
+
const LexicalSelection = process.env.NODE_ENV === 'development' ? require('./LexicalSelection.dev.js') : require('./LexicalSelection.prod.js')
|
|
9
|
+
module.exports = LexicalSelection;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
var k=require("lexical");const r=new Map;function v(a){a=a.getLatest();const c=a.constructor.clone(a);c.__parent=a.__parent;k.$isElementNode(a)&&k.$isElementNode(c)?(c.__children=Array.from(a.__children),c.__format=a.__format,c.__indent=a.__indent,c.__dir=a.__dir):k.$isTextNode(a)&&k.$isTextNode(c)?(c.__format=a.__format,c.__style=a.__style,c.__mode=a.__mode,c.__detail=a.__detail):k.$isDecoratorNode(a)&&k.$isDecoratorNode(c)&&(c.__state=a.__state);return c}
|
|
8
|
+
function w(a,c,b,g,l){for(var e=c;null!==a;){for(c=a.getParent();null!==c&&c.excludeFromCopy();)c=c.getParent();if(null===c)break;if(!k.$isElementNode(a)||!a.excludeFromCopy()){const d=a.getKey();let f=l.get(d);const h=void 0===f;h&&(f=v(a),l.set(d,f));!k.$isTextNode(f)||f.isSegmented()||f.isToken()?k.$isElementNode(f)&&(f.__children=f.__children.slice(b?e:0,b?void 0:e+1)):f.__text=f.__text.slice(b?e:0,b?void 0:e);if(k.$isRootNode(c)){h&&g.push(d);break}}e=l.get(c.getKey());e=k.$isElementNode(e)?
|
|
9
|
+
e.__children.indexOf(a.getKey()):a.getIndexWithinParent();a=c}}function x(a){return r.get(a)||null}function y(a,c){var b=x(a.getStyle());c=b?{...b,...c}:c;b="";for(g in c)g&&(b+=`${g}: ${c[g]};`);var g=b;a.setStyle(g);r.set(g,c)}function z(a,c,b,g){a.modify(c?"extend":"move",b,g)}function A(a){a=a.anchor.getNode();return"rtl"===(k.$isRootNode(a)?a:a.getParentOrThrow()).getDirection()}
|
|
10
|
+
function B(a){for(;null!==a&&!k.$isRootNode(a);){const c=a.getLatest(),b=a.getParent();0===c.__children.length&&a.remove();a=b}}
|
|
11
|
+
exports.$cloneContents=function(a){if(!k.$isRangeSelection(a))throw Error("Minified Lexical error #68; see codes.json for the full message or use the non-minified dev environment for full errors and additional helpful warnings.");var c=a.anchor,b=a.focus,g=c.getCharacterOffset();const l=b.getCharacterOffset();var e=c.getNode(),d=b.getNode(),f=e.getParentOrThrow();if(e===d&&k.$isTextNode(e)&&(f.canBeEmpty()||1<f.getChildrenSize()))return a=v(e),e=l>g,a.__text=a.__text.slice(e?g:l,e?l:g),g=a.getKey(),
|
|
12
|
+
{nodeMap:[[g,a]],range:[g]};a=a.getNodes();if(0===a.length)return{nodeMap:[],range:[]};e=a.length;d=a[0];f=d.getParent();if(null!==f&&(!f.canBeEmpty()||k.$isRootNode(f))){var h=f.__children;if(h.length===e){var m=!0;for(var n=0;n<h.length;n++)if(h[n]!==a[n].__key){m=!1;break}m&&(e++,a.push(f))}}f=a[e-1];c=c.isBefore(b);b=new Map;h=[];w(d,c?g:l,!0,h,b);for(d=0;d<e;d++)if(m=a[d],n=m.getKey(),!(b.has(n)||k.$isElementNode(m)&&m.excludeFromCopy())){const t=v(m);k.$isRootNode(m.getParent())&&h.push(m.getKey());
|
|
13
|
+
b.set(n,t)}w(f,c?l:g,!1,h,b);return{nodeMap:Array.from(b.entries()),range:h}};exports.$getSelectionStyleValueForProperty=function(a,c,b=""){let g=null;const l=a.getNodes();var e=a.anchor,d=a.focus,f=a.isBackward();a=f?d.offset:e.offset;e=f?d.getNode():e.getNode();for(d=0;d<l.length;d++){var h=l[d];if((0===d||0!==a||!h.is(e))&&k.$isTextNode(h)){f=c;var m=b;h=h.getStyle();h=x(h);f=null!==h?h[f]||m:m;if(null===g)g=f;else if(g!==f){g="";break}}}return null===g?b:g};
|
|
14
|
+
exports.$isAtNodeEnd=function(a){return"text"===a.type?a.offset===a.getNode().getTextContentSize():a.offset===a.getNode().getChildrenSize()};exports.$isParentElementRTL=A;exports.$moveCaretSelection=z;exports.$moveCharacter=function(a,c,b){const g=A(a);z(a,c,b?!g:g,"character")};
|
|
15
|
+
exports.$patchStyleText=function(a,c){var b=a.getNodes();const g=b.length-1;let l=b[0],e=b[g];if(!a.isCollapsed()){var d=a.anchor,f=a.focus;a=l.getTextContent().length;var h=f.offset,m=d.offset;d=(f=d.isBefore(f))?m:h;f=f?h:m;if(d===l.getTextContentSize()){const n=l.getNextSibling();k.$isTextNode(n)&&(d=m=0,l=n)}if(l.is(e))k.$isTextNode(l)&&(d=m>h?h:m,f=m>h?m:h,d!==f&&(0===d&&f===a?(y(l,c),l.select(d,f)):(b=l.splitText(d,f),b=0===d?b[0]:b[1],y(b,c),b.select(0,f-d))));else for(k.$isTextNode(l)&&(0!==
|
|
16
|
+
d&&([,l]=l.splitText(d)),y(l,c)),k.$isTextNode(e)&&(a=e.getTextContent().length,f!==a&&([e]=e.splitText(f)),0!==f&&y(e,c)),a=1;a<g;a++)h=b[a],m=h.getKey(),k.$isTextNode(h)&&m!==l.getKey()&&m!==e.getKey()&&!h.isToken()&&y(h,c)}};
|
|
17
|
+
exports.$selectAll=function(a){const c=a.anchor;a=a.focus;var b=c.getNode().getTopLevelElementOrThrow().getParentOrThrow();let g=b.getFirstDescendant();b=b.getLastDescendant();let l="element",e="element",d=0;k.$isTextNode(g)?l="text":k.$isElementNode(g)||null===g||(g=g.getParentOrThrow());k.$isTextNode(b)?(e="text",d=b.getTextContentSize()):k.$isElementNode(b)||null===b||(b=b.getParentOrThrow(),d=b.getChildrenSize());g&&b&&(c.set(g.getKey(),0,l),a.set(b.getKey(),d,e))};
|
|
18
|
+
exports.$shouldOverrideDefaultCharacterSelection=function(a,c){a=k.$getDecoratorNode(a.focus,c);return k.$isDecoratorNode(a)&&!a.isIsolated()};
|
|
19
|
+
exports.$wrapLeafNodesInElements=function(a,c,b){const g=a.getNodes(),l=g.length;if(0===l){a=a.anchor;a="text"===a.type?a.getNode().getParentOrThrow():a.getNode();var e=a.getChildren();let p=c();e.forEach(u=>p.append(u));b&&(p=b.append(p));a.replace(p)}else{var d=g[0],f=new Map;e=[];for(d=k.$isElementNode(d)?d:d.getParentOrThrow();null!==d;){var h=d.getPreviousSibling();if(null!==h){d=h;break}d=d.getParentOrThrow();if(k.$isRootNode(d))break}h=new Set;for(var m=0;m<l;m++){var n=g[m];k.$isElementNode(n)&&
|
|
20
|
+
0===n.getChildrenSize()&&h.add(n.getKey())}var t=new Set;for(m=0;m<l;m++){var q=g[m];n=q.getParent();if(null!==n&&k.$isLeafNode(q)&&!t.has(q.getKey())){if(q=n.getKey(),void 0===f.get(q)){const p=c();e.push(p);f.set(q,p);n.getChildren().forEach(u=>{p.append(u);t.add(u.getKey())});B(n)}}else h.has(q.getKey())&&(e.push(c()),q.remove())}if(b)for(c=0;c<e.length;c++)b.append(e[c]);if(k.$isRootNode(d))if(c=d.getFirstChild(),k.$isElementNode(c)&&(d=c),null===c)if(b)d.append(b);else for(b=0;b<e.length;b++)d.append(e[b]);
|
|
21
|
+
else if(b)c.insertBefore(b);else for(b=0;b<e.length;b++)c.insertBefore(e[b]);else if(b)d.insertAfter(b);else for(b=e.length-1;0<=b;b--)d.insertAfter(e[b]);a.dirty=!0}};exports.getStyleObjectFromCSS=x;
|
package/README.md
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lexical/selection",
|
|
3
|
+
"author": {
|
|
4
|
+
"name": "Dominic Gannaway",
|
|
5
|
+
"email": "dg@domgan.com"
|
|
6
|
+
},
|
|
7
|
+
"description": "This package contains utilities and helpers for handling Lexical selection.",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"lexical",
|
|
10
|
+
"editor",
|
|
11
|
+
"rich-text",
|
|
12
|
+
"list",
|
|
13
|
+
"selection"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"version": "0.1.15",
|
|
17
|
+
"main": "LexicalSelection.js",
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"lexical": "0.1.15"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/facebook/lexical",
|
|
24
|
+
"directory": "packages/lexical-selection"
|
|
25
|
+
}
|
|
26
|
+
}
|