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