@lexical/selection 0.4.1 → 0.5.1-next.0

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