@lexical/utils 0.13.0 → 0.14.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.
@@ -0,0 +1,730 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import { createRectsFromDOMRange, $cloneWithProperties } from '@lexical/selection';
8
+ import { $getSelection, $isRangeSelection, TextNode, $getRoot, $isElementNode, $isTextNode, $setSelection, $getPreviousSelection, $isRootOrShadowRoot, $splitNode, $createParagraphNode } from 'lexical';
9
+ export { $splitNode, isHTMLAnchorElement, isHTMLElement } from 'lexical';
10
+
11
+ /**
12
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
13
+ *
14
+ * This source code is licensed under the MIT license found in the
15
+ * LICENSE file in the root directory of this source tree.
16
+ *
17
+ */
18
+
19
+ function normalizeClassNames(...classNames) {
20
+ const rval = [];
21
+ for (const className of classNames) {
22
+ if (className && typeof className === 'string') {
23
+ for (const [s] of className.matchAll(/\S+/g)) {
24
+ rval.push(s);
25
+ }
26
+ }
27
+ }
28
+ return rval;
29
+ }
30
+
31
+ /**
32
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
33
+ *
34
+ * This source code is licensed under the MIT license found in the
35
+ * LICENSE file in the root directory of this source tree.
36
+ *
37
+ */
38
+
39
+ /**
40
+ * Returns a function that will execute all functions passed when called. It is generally used
41
+ * to register multiple lexical listeners and then tear them down with a single function call, such
42
+ * as React's useEffect hook.
43
+ * @example
44
+ * ```ts
45
+ * useEffect(() => {
46
+ * return mergeRegister(
47
+ * editor.registerCommand(...registerCommand1 logic),
48
+ * editor.registerCommand(...registerCommand2 logic),
49
+ * editor.registerCommand(...registerCommand3 logic)
50
+ * )
51
+ * }, [editor])
52
+ * ```
53
+ * In this case, useEffect is returning the function returned by mergeRegister as a cleanup
54
+ * function to be executed after either the useEffect runs again (due to one of its dependencies
55
+ * updating) or the component it resides in unmounts.
56
+ * Note the functions don't neccesarily need to be in an array as all arguements
57
+ * are considered to be the func argument and spread from there.
58
+ * @param func - An array of functions meant to be executed by the returned function.
59
+ * @returns the function which executes all the passed register command functions.
60
+ */
61
+ function mergeRegister(...func) {
62
+ return () => {
63
+ func.forEach(f => f());
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
69
+ *
70
+ * This source code is licensed under the MIT license found in the
71
+ * LICENSE file in the root directory of this source tree.
72
+ *
73
+ */
74
+
75
+ function px(value) {
76
+ return `${value}px`;
77
+ }
78
+
79
+ /**
80
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
81
+ *
82
+ * This source code is licensed under the MIT license found in the
83
+ * LICENSE file in the root directory of this source tree.
84
+ *
85
+ */
86
+ const mutationObserverConfig = {
87
+ attributes: true,
88
+ characterData: true,
89
+ childList: true,
90
+ subtree: true
91
+ };
92
+ function positionNodeOnRange(editor, range, onReposition) {
93
+ let rootDOMNode = null;
94
+ let parentDOMNode = null;
95
+ let observer = null;
96
+ let lastNodes = [];
97
+ const wrapperNode = document.createElement('div');
98
+ function position() {
99
+ if (!(rootDOMNode !== null)) {
100
+ throw Error(`Unexpected null rootDOMNode`);
101
+ }
102
+ if (!(parentDOMNode !== null)) {
103
+ throw Error(`Unexpected null parentDOMNode`);
104
+ }
105
+ const {
106
+ left: rootLeft,
107
+ top: rootTop
108
+ } = rootDOMNode.getBoundingClientRect();
109
+ const parentDOMNode_ = parentDOMNode;
110
+ const rects = createRectsFromDOMRange(editor, range);
111
+ if (!wrapperNode.isConnected) {
112
+ parentDOMNode_.append(wrapperNode);
113
+ }
114
+ let hasRepositioned = false;
115
+ for (let i = 0; i < rects.length; i++) {
116
+ const rect = rects[i];
117
+ // Try to reuse the previously created Node when possible, no need to
118
+ // remove/create on the most common case reposition case
119
+ const rectNode = lastNodes[i] || document.createElement('div');
120
+ const rectNodeStyle = rectNode.style;
121
+ if (rectNodeStyle.position !== 'absolute') {
122
+ rectNodeStyle.position = 'absolute';
123
+ hasRepositioned = true;
124
+ }
125
+ const left = px(rect.left - rootLeft);
126
+ if (rectNodeStyle.left !== left) {
127
+ rectNodeStyle.left = left;
128
+ hasRepositioned = true;
129
+ }
130
+ const top = px(rect.top - rootTop);
131
+ if (rectNodeStyle.top !== top) {
132
+ rectNode.style.top = top;
133
+ hasRepositioned = true;
134
+ }
135
+ const width = px(rect.width);
136
+ if (rectNodeStyle.width !== width) {
137
+ rectNode.style.width = width;
138
+ hasRepositioned = true;
139
+ }
140
+ const height = px(rect.height);
141
+ if (rectNodeStyle.height !== height) {
142
+ rectNode.style.height = height;
143
+ hasRepositioned = true;
144
+ }
145
+ if (rectNode.parentNode !== wrapperNode) {
146
+ wrapperNode.append(rectNode);
147
+ hasRepositioned = true;
148
+ }
149
+ lastNodes[i] = rectNode;
150
+ }
151
+ while (lastNodes.length > rects.length) {
152
+ lastNodes.pop();
153
+ }
154
+ if (hasRepositioned) {
155
+ onReposition(lastNodes);
156
+ }
157
+ }
158
+ function stop() {
159
+ parentDOMNode = null;
160
+ rootDOMNode = null;
161
+ if (observer !== null) {
162
+ observer.disconnect();
163
+ }
164
+ observer = null;
165
+ wrapperNode.remove();
166
+ for (const node of lastNodes) {
167
+ node.remove();
168
+ }
169
+ lastNodes = [];
170
+ }
171
+ function restart() {
172
+ const currentRootDOMNode = editor.getRootElement();
173
+ if (currentRootDOMNode === null) {
174
+ return stop();
175
+ }
176
+ const currentParentDOMNode = currentRootDOMNode.parentElement;
177
+ if (!(currentParentDOMNode instanceof HTMLElement)) {
178
+ return stop();
179
+ }
180
+ stop();
181
+ rootDOMNode = currentRootDOMNode;
182
+ parentDOMNode = currentParentDOMNode;
183
+ observer = new MutationObserver(mutations => {
184
+ const nextRootDOMNode = editor.getRootElement();
185
+ const nextParentDOMNode = nextRootDOMNode && nextRootDOMNode.parentElement;
186
+ if (nextRootDOMNode !== rootDOMNode || nextParentDOMNode !== parentDOMNode) {
187
+ return restart();
188
+ }
189
+ for (const mutation of mutations) {
190
+ if (!wrapperNode.contains(mutation.target)) {
191
+ // TODO throttle
192
+ return position();
193
+ }
194
+ }
195
+ });
196
+ observer.observe(currentParentDOMNode, mutationObserverConfig);
197
+ position();
198
+ }
199
+ const removeRootListener = editor.registerRootListener(restart);
200
+ return () => {
201
+ removeRootListener();
202
+ stop();
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
208
+ *
209
+ * This source code is licensed under the MIT license found in the
210
+ * LICENSE file in the root directory of this source tree.
211
+ *
212
+ */
213
+ function markSelection(editor, onReposition) {
214
+ let previousAnchorNode = null;
215
+ let previousAnchorOffset = null;
216
+ let previousFocusNode = null;
217
+ let previousFocusOffset = null;
218
+ let removeRangeListener = () => {};
219
+ function compute(editorState) {
220
+ editorState.read(() => {
221
+ const selection = $getSelection();
222
+ if (!$isRangeSelection(selection)) {
223
+ // TODO
224
+ previousAnchorNode = null;
225
+ previousAnchorOffset = null;
226
+ previousFocusNode = null;
227
+ previousFocusOffset = null;
228
+ removeRangeListener();
229
+ removeRangeListener = () => {};
230
+ return;
231
+ }
232
+ const {
233
+ anchor,
234
+ focus
235
+ } = selection;
236
+ const currentAnchorNode = anchor.getNode();
237
+ const currentAnchorNodeKey = currentAnchorNode.getKey();
238
+ const currentAnchorOffset = anchor.offset;
239
+ const currentFocusNode = focus.getNode();
240
+ const currentFocusNodeKey = currentFocusNode.getKey();
241
+ const currentFocusOffset = focus.offset;
242
+ const currentAnchorNodeDOM = editor.getElementByKey(currentAnchorNodeKey);
243
+ const currentFocusNodeDOM = editor.getElementByKey(currentFocusNodeKey);
244
+ const differentAnchorDOM = previousAnchorNode === null || currentAnchorNodeDOM === null || currentAnchorOffset !== previousAnchorOffset || currentAnchorNodeKey !== previousAnchorNode.getKey() || currentAnchorNode !== previousAnchorNode && (!(previousAnchorNode instanceof TextNode) || currentAnchorNode.updateDOM(previousAnchorNode, currentAnchorNodeDOM, editor._config));
245
+ const differentFocusDOM = previousFocusNode === null || currentFocusNodeDOM === null || currentFocusOffset !== previousFocusOffset || currentFocusNodeKey !== previousFocusNode.getKey() || currentFocusNode !== previousFocusNode && (!(previousFocusNode instanceof TextNode) || currentFocusNode.updateDOM(previousFocusNode, currentFocusNodeDOM, editor._config));
246
+ if (differentAnchorDOM || differentFocusDOM) {
247
+ const anchorHTMLElement = editor.getElementByKey(anchor.getNode().getKey());
248
+ const focusHTMLElement = editor.getElementByKey(focus.getNode().getKey());
249
+ // TODO handle selection beyond the common TextNode
250
+ if (anchorHTMLElement !== null && focusHTMLElement !== null && anchorHTMLElement.tagName === 'SPAN' && focusHTMLElement.tagName === 'SPAN') {
251
+ const range = document.createRange();
252
+ let firstHTMLElement;
253
+ let firstOffset;
254
+ let lastHTMLElement;
255
+ let lastOffset;
256
+ if (focus.isBefore(anchor)) {
257
+ firstHTMLElement = focusHTMLElement;
258
+ firstOffset = focus.offset;
259
+ lastHTMLElement = anchorHTMLElement;
260
+ lastOffset = anchor.offset;
261
+ } else {
262
+ firstHTMLElement = anchorHTMLElement;
263
+ firstOffset = anchor.offset;
264
+ lastHTMLElement = focusHTMLElement;
265
+ lastOffset = focus.offset;
266
+ }
267
+ const firstTextNode = firstHTMLElement.firstChild;
268
+ if (!(firstTextNode !== null)) {
269
+ throw Error(`Expected text node to be first child of span`);
270
+ }
271
+ const lastTextNode = lastHTMLElement.firstChild;
272
+ if (!(lastTextNode !== null)) {
273
+ throw Error(`Expected text node to be first child of span`);
274
+ }
275
+ range.setStart(firstTextNode, firstOffset);
276
+ range.setEnd(lastTextNode, lastOffset);
277
+ removeRangeListener();
278
+ removeRangeListener = positionNodeOnRange(editor, range, domNodes => {
279
+ for (const domNode of domNodes) {
280
+ const domNodeStyle = domNode.style;
281
+ if (domNodeStyle.background !== 'Highlight') {
282
+ domNodeStyle.background = 'Highlight';
283
+ }
284
+ if (domNodeStyle.color !== 'HighlightText') {
285
+ domNodeStyle.color = 'HighlightText';
286
+ }
287
+ if (domNodeStyle.zIndex !== '-1') {
288
+ domNodeStyle.zIndex = '-1';
289
+ }
290
+ if (domNodeStyle.pointerEvents !== 'none') {
291
+ domNodeStyle.pointerEvents = 'none';
292
+ }
293
+ if (domNodeStyle.marginTop !== px(-1.5)) {
294
+ domNodeStyle.marginTop = px(-1.5);
295
+ }
296
+ if (domNodeStyle.paddingTop !== px(4)) {
297
+ domNodeStyle.paddingTop = px(4);
298
+ }
299
+ if (domNodeStyle.paddingBottom !== px(0)) {
300
+ domNodeStyle.paddingBottom = px(0);
301
+ }
302
+ }
303
+ if (onReposition !== undefined) {
304
+ onReposition(domNodes);
305
+ }
306
+ });
307
+ }
308
+ }
309
+ previousAnchorNode = currentAnchorNode;
310
+ previousAnchorOffset = currentAnchorOffset;
311
+ previousFocusNode = currentFocusNode;
312
+ previousFocusOffset = currentFocusOffset;
313
+ });
314
+ }
315
+ compute(editor.getEditorState());
316
+ return mergeRegister(editor.registerUpdateListener(({
317
+ editorState
318
+ }) => compute(editorState)), removeRangeListener, () => {
319
+ removeRangeListener();
320
+ });
321
+ }
322
+
323
+ /** @module @lexical/utils */
324
+ /**
325
+ * Takes an HTML element and adds the classNames passed within an array,
326
+ * ignoring any non-string types. A space can be used to add multiple classes
327
+ * eg. addClassNamesToElement(element, ['element-inner active', true, null])
328
+ * will add both 'element-inner' and 'active' as classes to that element.
329
+ * @param element - The element in which the classes are added
330
+ * @param classNames - An array defining the class names to add to the element
331
+ */
332
+ function addClassNamesToElement(element, ...classNames) {
333
+ const classesToAdd = normalizeClassNames(...classNames);
334
+ if (classesToAdd.length > 0) {
335
+ element.classList.add(...classesToAdd);
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Takes an HTML element and removes the classNames passed within an array,
341
+ * ignoring any non-string types. A space can be used to remove multiple classes
342
+ * eg. removeClassNamesFromElement(element, ['active small', true, null])
343
+ * will remove both the 'active' and 'small' classes from that element.
344
+ * @param element - The element in which the classes are removed
345
+ * @param classNames - An array defining the class names to remove from the element
346
+ */
347
+ function removeClassNamesFromElement(element, ...classNames) {
348
+ const classesToRemove = normalizeClassNames(...classNames);
349
+ if (classesToRemove.length > 0) {
350
+ element.classList.remove(...classesToRemove);
351
+ }
352
+ }
353
+
354
+ /**
355
+ * Returns true if the file type matches the types passed within the acceptableMimeTypes array, false otherwise.
356
+ * The types passed must be strings and are CASE-SENSITIVE.
357
+ * eg. if file is of type 'text' and acceptableMimeTypes = ['TEXT', 'IMAGE'] the function will return false.
358
+ * @param file - The file you want to type check.
359
+ * @param acceptableMimeTypes - An array of strings of types which the file is checked against.
360
+ * @returns true if the file is an acceptable mime type, false otherwise.
361
+ */
362
+ function isMimeType(file, acceptableMimeTypes) {
363
+ for (const acceptableType of acceptableMimeTypes) {
364
+ if (file.type.startsWith(acceptableType)) {
365
+ return true;
366
+ }
367
+ }
368
+ return false;
369
+ }
370
+
371
+ /**
372
+ * Lexical File Reader with:
373
+ * 1. MIME type support
374
+ * 2. batched results (HistoryPlugin compatibility)
375
+ * 3. Order aware (respects the order when multiple Files are passed)
376
+ *
377
+ * const filesResult = await mediaFileReader(files, ['image/']);
378
+ * filesResult.forEach(file => editor.dispatchCommand('INSERT_IMAGE', {
379
+ * src: file.result,
380
+ * }));
381
+ */
382
+ function mediaFileReader(files, acceptableMimeTypes) {
383
+ const filesIterator = files[Symbol.iterator]();
384
+ return new Promise((resolve, reject) => {
385
+ const processed = [];
386
+ const handleNextFile = () => {
387
+ const {
388
+ done,
389
+ value: file
390
+ } = filesIterator.next();
391
+ if (done) {
392
+ return resolve(processed);
393
+ }
394
+ const fileReader = new FileReader();
395
+ fileReader.addEventListener('error', reject);
396
+ fileReader.addEventListener('load', () => {
397
+ const result = fileReader.result;
398
+ if (typeof result === 'string') {
399
+ processed.push({
400
+ file,
401
+ result
402
+ });
403
+ }
404
+ handleNextFile();
405
+ });
406
+ if (isMimeType(file, acceptableMimeTypes)) {
407
+ fileReader.readAsDataURL(file);
408
+ } else {
409
+ handleNextFile();
410
+ }
411
+ };
412
+ handleNextFile();
413
+ });
414
+ }
415
+
416
+ /**
417
+ * "Depth-First Search" starts at the root/top node of a tree and goes as far as it can down a branch end
418
+ * before backtracking and finding a new path. Consider solving a maze by hugging either wall, moving down a
419
+ * branch until you hit a dead-end (leaf) and backtracking to find the nearest branching path and repeat.
420
+ * It will then return all the nodes found in the search in an array of objects.
421
+ * @param startingNode - The node to start the search, if ommitted, it will start at the root node.
422
+ * @param endingNode - The node to end the search, if ommitted, it will find all descendants of the startingNode.
423
+ * @returns An array of objects of all the nodes found by the search, including their depth into the tree.
424
+ * {depth: number, node: LexicalNode} It will always return at least 1 node (the ending node) so long as it exists
425
+ */
426
+ function $dfs(startingNode, endingNode) {
427
+ const nodes = [];
428
+ const start = (startingNode || $getRoot()).getLatest();
429
+ const end = endingNode || ($isElementNode(start) ? start.getLastDescendant() : start);
430
+ let node = start;
431
+ let depth = $getDepth(node);
432
+ while (node !== null && !node.is(end)) {
433
+ nodes.push({
434
+ depth,
435
+ node
436
+ });
437
+ if ($isElementNode(node) && node.getChildrenSize() > 0) {
438
+ node = node.getFirstChild();
439
+ depth++;
440
+ } else {
441
+ // Find immediate sibling or nearest parent sibling
442
+ let sibling = null;
443
+ while (sibling === null && node !== null) {
444
+ sibling = node.getNextSibling();
445
+ if (sibling === null) {
446
+ node = node.getParent();
447
+ depth--;
448
+ } else {
449
+ node = sibling;
450
+ }
451
+ }
452
+ }
453
+ }
454
+ if (node !== null && node.is(end)) {
455
+ nodes.push({
456
+ depth,
457
+ node
458
+ });
459
+ }
460
+ return nodes;
461
+ }
462
+ function $getDepth(node) {
463
+ let innerNode = node;
464
+ let depth = 0;
465
+ while ((innerNode = innerNode.getParent()) !== null) {
466
+ depth++;
467
+ }
468
+ return depth;
469
+ }
470
+
471
+ /**
472
+ * Takes a node and traverses up its ancestors (toward the root node)
473
+ * in order to find a specific type of node.
474
+ * @param node - the node to begin searching.
475
+ * @param klass - an instance of the type of node to look for.
476
+ * @returns the node of type klass that was passed, or null if none exist.
477
+ */
478
+ function $getNearestNodeOfType(node, klass) {
479
+ let parent = node;
480
+ while (parent != null) {
481
+ if (parent instanceof klass) {
482
+ return parent;
483
+ }
484
+ parent = parent.getParent();
485
+ }
486
+ return null;
487
+ }
488
+
489
+ /**
490
+ * Returns the element node of the nearest ancestor, otherwise throws an error.
491
+ * @param startNode - The starting node of the search
492
+ * @returns The ancestor node found
493
+ */
494
+ function $getNearestBlockElementAncestorOrThrow(startNode) {
495
+ const blockNode = $findMatchingParent(startNode, node => $isElementNode(node) && !node.isInline());
496
+ if (!$isElementNode(blockNode)) {
497
+ {
498
+ throw Error(`Expected node ${startNode.__key} to have closest block element node.`);
499
+ }
500
+ }
501
+ return blockNode;
502
+ }
503
+ /**
504
+ * Starts with a node and moves up the tree (toward the root node) to find a matching node based on
505
+ * the search parameters of the findFn. (Consider JavaScripts' .find() function where a testing function must be
506
+ * passed as an argument. eg. if( (node) => node.__type === 'div') ) return true; otherwise return false
507
+ * @param startingNode - The node where the search starts.
508
+ * @param findFn - A testing function that returns true if the current node satisfies the testing parameters.
509
+ * @returns A parent node that matches the findFn parameters, or null if one wasn't found.
510
+ */
511
+ const $findMatchingParent = (startingNode, findFn) => {
512
+ let curr = startingNode;
513
+ while (curr !== $getRoot() && curr != null) {
514
+ if (findFn(curr)) {
515
+ return curr;
516
+ }
517
+ curr = curr.getParent();
518
+ }
519
+ return null;
520
+ };
521
+
522
+ /**
523
+ * Attempts to resolve nested element nodes of the same type into a single node of that type.
524
+ * It is generally used for marks/commenting
525
+ * @param editor - The lexical editor
526
+ * @param targetNode - The target for the nested element to be extracted from.
527
+ * @param cloneNode - See {@link $createMarkNode}
528
+ * @param handleOverlap - Handles any overlap between the node to extract and the targetNode
529
+ * @returns The lexical editor
530
+ */
531
+ function registerNestedElementResolver(editor, targetNode, cloneNode, handleOverlap) {
532
+ const $isTargetNode = node => {
533
+ return node instanceof targetNode;
534
+ };
535
+ const $findMatch = node => {
536
+ // First validate we don't have any children that are of the target,
537
+ // as we need to handle them first.
538
+ const children = node.getChildren();
539
+ for (let i = 0; i < children.length; i++) {
540
+ const child = children[i];
541
+ if ($isTargetNode(child)) {
542
+ return null;
543
+ }
544
+ }
545
+ let parentNode = node;
546
+ let childNode = node;
547
+ while (parentNode !== null) {
548
+ childNode = parentNode;
549
+ parentNode = parentNode.getParent();
550
+ if ($isTargetNode(parentNode)) {
551
+ return {
552
+ child: childNode,
553
+ parent: parentNode
554
+ };
555
+ }
556
+ }
557
+ return null;
558
+ };
559
+ const elementNodeTransform = node => {
560
+ const match = $findMatch(node);
561
+ if (match !== null) {
562
+ const {
563
+ child,
564
+ parent
565
+ } = match;
566
+
567
+ // Simple path, we can move child out and siblings into a new parent.
568
+
569
+ if (child.is(node)) {
570
+ handleOverlap(parent, node);
571
+ const nextSiblings = child.getNextSiblings();
572
+ const nextSiblingsLength = nextSiblings.length;
573
+ parent.insertAfter(child);
574
+ if (nextSiblingsLength !== 0) {
575
+ const newParent = cloneNode(parent);
576
+ child.insertAfter(newParent);
577
+ for (let i = 0; i < nextSiblingsLength; i++) {
578
+ newParent.append(nextSiblings[i]);
579
+ }
580
+ }
581
+ if (!parent.canBeEmpty() && parent.getChildrenSize() === 0) {
582
+ parent.remove();
583
+ }
584
+ }
585
+ }
586
+ };
587
+ return editor.registerNodeTransform(targetNode, elementNodeTransform);
588
+ }
589
+
590
+ /**
591
+ * Clones the editor and marks it as dirty to be reconciled. If there was a selection,
592
+ * it would be set back to its previous state, or null otherwise.
593
+ * @param editor - The lexical editor
594
+ * @param editorState - The editor's state
595
+ */
596
+ function $restoreEditorState(editor, editorState) {
597
+ const FULL_RECONCILE = 2;
598
+ const nodeMap = new Map();
599
+ const activeEditorState = editor._pendingEditorState;
600
+ for (const [key, node] of editorState._nodeMap) {
601
+ const clone = $cloneWithProperties(node);
602
+ if ($isTextNode(clone)) {
603
+ if (!$isTextNode(node)) {
604
+ throw Error(`Expected node be a TextNode`);
605
+ }
606
+ clone.__text = node.__text;
607
+ }
608
+ nodeMap.set(key, clone);
609
+ }
610
+ if (activeEditorState) {
611
+ activeEditorState._nodeMap = nodeMap;
612
+ }
613
+ editor._dirtyType = FULL_RECONCILE;
614
+ const selection = editorState._selection;
615
+ $setSelection(selection === null ? null : selection.clone());
616
+ }
617
+
618
+ /**
619
+ * If the selected insertion area is the root/shadow root node (see {@link lexical!$isRootOrShadowRoot}),
620
+ * the node will be appended there, otherwise, it will be inserted before the insertion area.
621
+ * If there is no selection where the node is to be inserted, it will be appended after any current nodes
622
+ * within the tree, as a child of the root node. A paragraph node will then be added after the inserted node and selected.
623
+ * @param node - The node to be inserted
624
+ * @returns The node after its insertion
625
+ */
626
+ function $insertNodeToNearestRoot(node) {
627
+ const selection = $getSelection() || $getPreviousSelection();
628
+ if ($isRangeSelection(selection)) {
629
+ const {
630
+ focus
631
+ } = selection;
632
+ const focusNode = focus.getNode();
633
+ const focusOffset = focus.offset;
634
+ if ($isRootOrShadowRoot(focusNode)) {
635
+ const focusChild = focusNode.getChildAtIndex(focusOffset);
636
+ if (focusChild == null) {
637
+ focusNode.append(node);
638
+ } else {
639
+ focusChild.insertBefore(node);
640
+ }
641
+ node.selectNext();
642
+ } else {
643
+ let splitNode;
644
+ let splitOffset;
645
+ if ($isTextNode(focusNode)) {
646
+ splitNode = focusNode.getParentOrThrow();
647
+ splitOffset = focusNode.getIndexWithinParent();
648
+ if (focusOffset > 0) {
649
+ splitOffset += 1;
650
+ focusNode.splitText(focusOffset);
651
+ }
652
+ } else {
653
+ splitNode = focusNode;
654
+ splitOffset = focusOffset;
655
+ }
656
+ const [, rightTree] = $splitNode(splitNode, splitOffset);
657
+ rightTree.insertBefore(node);
658
+ rightTree.selectStart();
659
+ }
660
+ } else {
661
+ if (selection != null) {
662
+ const nodes = selection.getNodes();
663
+ nodes[nodes.length - 1].getTopLevelElementOrThrow().insertAfter(node);
664
+ } else {
665
+ const root = $getRoot();
666
+ root.append(node);
667
+ }
668
+ const paragraphNode = $createParagraphNode();
669
+ node.insertAfter(paragraphNode);
670
+ paragraphNode.select();
671
+ }
672
+ return node.getLatest();
673
+ }
674
+
675
+ /**
676
+ * Wraps the node into another node created from a createElementNode function, eg. $createParagraphNode
677
+ * @param node - Node to be wrapped.
678
+ * @param createElementNode - Creates a new lexical element to wrap the to-be-wrapped node and returns it.
679
+ * @returns A new lexical element with the previous node appended within (as a child, including its children).
680
+ */
681
+ function $wrapNodeInElement(node, createElementNode) {
682
+ const elementNode = createElementNode();
683
+ node.replace(elementNode);
684
+ elementNode.append(node);
685
+ return elementNode;
686
+ }
687
+
688
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
689
+
690
+ /**
691
+ * @param object = The instance of the type
692
+ * @param objectClass = The class of the type
693
+ * @returns Whether the object is has the same Klass of the objectClass, ignoring the difference across window (e.g. different iframs)
694
+ */
695
+ function objectKlassEquals(object, objectClass) {
696
+ return object !== null ? Object.getPrototypeOf(object).constructor.name === objectClass.name : false;
697
+ }
698
+
699
+ /**
700
+ * Filter the nodes
701
+ * @param nodes Array of nodes that needs to be filtered
702
+ * @param filterFn A filter function that returns node if the current node satisfies the condition otherwise null
703
+ * @returns Array of filtered nodes
704
+ */
705
+
706
+ function $filter(nodes, filterFn) {
707
+ const result = [];
708
+ for (let i = 0; i < nodes.length; i++) {
709
+ const node = filterFn(nodes[i]);
710
+ if (node !== null) {
711
+ result.push(node);
712
+ }
713
+ }
714
+ return result;
715
+ }
716
+ /**
717
+ * Appends the node before the first child of the parent node
718
+ * @param parent A parent node
719
+ * @param node Node that needs to be appended
720
+ */
721
+ function $insertFirst(parent, node) {
722
+ const firstChild = parent.getFirstChild();
723
+ if (firstChild !== null) {
724
+ firstChild.insertBefore(node);
725
+ } else {
726
+ parent.append(node);
727
+ }
728
+ }
729
+
730
+ export { $dfs, $filter, $findMatchingParent, $getNearestBlockElementAncestorOrThrow, $getNearestNodeOfType, $insertFirst, $insertNodeToNearestRoot, $restoreEditorState, $wrapNodeInElement, addClassNamesToElement, isMimeType, markSelection, mediaFileReader, mergeRegister, objectKlassEquals, positionNodeOnRange, registerNestedElementResolver, removeClassNamesFromElement };
@@ -9,6 +9,26 @@
9
9
  var selection = require('@lexical/selection');
10
10
  var lexical = require('lexical');
11
11
 
12
+ /**
13
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
14
+ *
15
+ * This source code is licensed under the MIT license found in the
16
+ * LICENSE file in the root directory of this source tree.
17
+ *
18
+ */
19
+
20
+ function normalizeClassNames(...classNames) {
21
+ const rval = [];
22
+ for (const className of classNames) {
23
+ if (className && typeof className === 'string') {
24
+ for (const [s] of className.matchAll(/\S+/g)) {
25
+ rval.push(s);
26
+ }
27
+ }
28
+ }
29
+ return rval;
30
+ }
31
+
12
32
  /**
13
33
  * Copyright (c) Meta Platforms, Inc. and affiliates.
14
34
  *
@@ -311,12 +331,10 @@ function markSelection(editor, onReposition) {
311
331
  * @param classNames - An array defining the class names to add to the element
312
332
  */
313
333
  function addClassNamesToElement(element, ...classNames) {
314
- classNames.forEach(className => {
315
- if (typeof className === 'string') {
316
- const classesToAdd = className.split(' ').filter(n => n !== '');
317
- element.classList.add(...classesToAdd);
318
- }
319
- });
334
+ const classesToAdd = normalizeClassNames(...classNames);
335
+ if (classesToAdd.length > 0) {
336
+ element.classList.add(...classesToAdd);
337
+ }
320
338
  }
321
339
 
322
340
  /**
@@ -328,11 +346,10 @@ function addClassNamesToElement(element, ...classNames) {
328
346
  * @param classNames - An array defining the class names to remove from the element
329
347
  */
330
348
  function removeClassNamesFromElement(element, ...classNames) {
331
- classNames.forEach(className => {
332
- if (typeof className === 'string') {
333
- element.classList.remove(...className.split(' '));
334
- }
335
- });
349
+ const classesToRemove = normalizeClassNames(...classNames);
350
+ if (classesToRemove.length > 0) {
351
+ element.classList.remove(...classesToRemove);
352
+ }
336
353
  }
337
354
 
338
355
  /**
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import * as modDev from './LexicalUtils.dev.esm.js';
8
+ import * as modProd from './LexicalUtils.prod.esm.js';
9
+ const mod = process.env.NODE_ENV === 'development' ? modDev : modProd;
10
+ export const $dfs = mod.$dfs;
11
+ export const $filter = mod.$filter;
12
+ export const $findMatchingParent = mod.$findMatchingParent;
13
+ export const $getNearestBlockElementAncestorOrThrow = mod.$getNearestBlockElementAncestorOrThrow;
14
+ export const $getNearestNodeOfType = mod.$getNearestNodeOfType;
15
+ export const $insertFirst = mod.$insertFirst;
16
+ export const $insertNodeToNearestRoot = mod.$insertNodeToNearestRoot;
17
+ export const $restoreEditorState = mod.$restoreEditorState;
18
+ export const $splitNode = mod.$splitNode;
19
+ export const $wrapNodeInElement = mod.$wrapNodeInElement;
20
+ export const addClassNamesToElement = mod.addClassNamesToElement;
21
+ export const isHTMLAnchorElement = mod.isHTMLAnchorElement;
22
+ export const isHTMLElement = mod.isHTMLElement;
23
+ export const isMimeType = mod.isMimeType;
24
+ export const markSelection = mod.markSelection;
25
+ export const mediaFileReader = mod.mediaFileReader;
26
+ export const mergeRegister = mod.mergeRegister;
27
+ export const objectKlassEquals = mod.objectKlassEquals;
28
+ export const positionNodeOnRange = mod.positionNodeOnRange;
29
+ export const registerNestedElementResolver = mod.registerNestedElementResolver;
30
+ export const removeClassNamesFromElement = mod.removeClassNamesFromElement;
package/LexicalUtils.js CHANGED
@@ -5,5 +5,5 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
  'use strict'
8
- const LexicalUtils = process.env.NODE_ENV === 'development' ? require('./LexicalUtils.dev.js') : require('./LexicalUtils.prod.js')
8
+ const LexicalUtils = process.env.NODE_ENV === 'development' ? require('./LexicalUtils.dev.js') : require('./LexicalUtils.prod.js');
9
9
  module.exports = LexicalUtils;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import{createRectsFromDOMRange as t,$cloneWithProperties as e}from"@lexical/selection";import{$getSelection as n,$isRangeSelection as o,TextNode as r,$getRoot as l,$isElementNode as i,$isTextNode as s,$setSelection as c,$getPreviousSelection as f,$isRootOrShadowRoot as u,$splitNode as a,$createParagraphNode as d}from"lexical";export{$splitNode,isHTMLAnchorElement,isHTMLElement}from"lexical";var g=function(t){const e=new URLSearchParams;e.append("code",t);for(let t=1;t<arguments.length;t++)e.append("v",arguments[t]);throw Error(`Minified Lexical error #${t}; visit https://lexical.dev/docs/error?${e} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)};function p(...t){const e=[];for(const n of t)if(n&&"string"==typeof n)for(const[t]of n.matchAll(/\S+/g))e.push(t);return e}function h(...t){return()=>{t.forEach((t=>t()))}}function m(t){return`${t}px`}const E={attributes:!0,characterData:!0,childList:!0,subtree:!0};function x(e,n,o){let r=null,l=null,i=null,s=[];const c=document.createElement("div");function f(){if(null===r)throw Error("Unexpected null rootDOMNode");if(null===l)throw Error("Unexpected null parentDOMNode");const{left:i,top:f}=r.getBoundingClientRect(),u=l,a=t(e,n);c.isConnected||u.append(c);let d=!1;for(let t=0;t<a.length;t++){const e=a[t],n=s[t]||document.createElement("div"),o=n.style;"absolute"!==o.position&&(o.position="absolute",d=!0);const r=m(e.left-i);o.left!==r&&(o.left=r,d=!0);const l=m(e.top-f);o.top!==l&&(n.style.top=l,d=!0);const u=m(e.width);o.width!==u&&(n.style.width=u,d=!0);const g=m(e.height);o.height!==g&&(n.style.height=g,d=!0),n.parentNode!==c&&(c.append(n),d=!0),s[t]=n}for(;s.length>a.length;)s.pop();d&&o(s)}function u(){l=null,r=null,null!==i&&i.disconnect(),i=null,c.remove();for(const t of s)t.remove();s=[]}const a=e.registerRootListener((function t(){const n=e.getRootElement();if(null===n)return u();const o=n.parentElement;if(!(o instanceof HTMLElement))return u();u(),r=n,l=o,i=new MutationObserver((n=>{const o=e.getRootElement(),i=o&&o.parentElement;if(o!==r||i!==l)return t();for(const t of n)if(!c.contains(t.target))return f()})),i.observe(o,E),f()}));return()=>{a(),u()}}function y(t,e){let l=null,i=null,s=null,c=null,f=()=>{};function u(u){u.read((()=>{const u=n();if(!o(u))return l=null,i=null,s=null,c=null,f(),void(f=()=>{});const{anchor:a,focus:d}=u,g=a.getNode(),p=g.getKey(),h=a.offset,E=d.getNode(),y=E.getKey(),v=d.offset,N=t.getElementByKey(p),w=t.getElementByKey(y),L=null===l||null===N||h!==i||p!==l.getKey()||g!==l&&(!(l instanceof r)||g.updateDOM(l,N,t._config)),T=null===s||null===w||v!==c||y!==s.getKey()||E!==s&&(!(s instanceof r)||E.updateDOM(s,w,t._config));if(L||T){const n=t.getElementByKey(a.getNode().getKey()),o=t.getElementByKey(d.getNode().getKey());if(null!==n&&null!==o&&"SPAN"===n.tagName&&"SPAN"===o.tagName){const r=document.createRange();let l,i,s,c;d.isBefore(a)?(l=o,i=d.offset,s=n,c=a.offset):(l=n,i=a.offset,s=o,c=d.offset);const u=l.firstChild;if(null===u)throw Error("Expected text node to be first child of span");const g=s.firstChild;if(null===g)throw Error("Expected text node to be first child of span");r.setStart(u,i),r.setEnd(g,c),f(),f=x(t,r,(t=>{for(const e of t){const t=e.style;"Highlight"!==t.background&&(t.background="Highlight"),"HighlightText"!==t.color&&(t.color="HighlightText"),"-1"!==t.zIndex&&(t.zIndex="-1"),"none"!==t.pointerEvents&&(t.pointerEvents="none"),t.marginTop!==m(-1.5)&&(t.marginTop=m(-1.5)),t.paddingTop!==m(4)&&(t.paddingTop=m(4)),t.paddingBottom!==m(0)&&(t.paddingBottom=m(0))}void 0!==e&&e(t)}))}}l=g,i=h,s=E,c=v}))}return u(t.getEditorState()),h(t.registerUpdateListener((({editorState:t})=>u(t))),f,(()=>{f()}))}function v(t,...e){const n=p(...e);n.length>0&&t.classList.add(...n)}function N(t,...e){const n=p(...e);n.length>0&&t.classList.remove(...n)}function w(t,e){for(const n of e)if(t.type.startsWith(n))return!0;return!1}function L(t,e){const n=t[Symbol.iterator]();return new Promise(((t,o)=>{const r=[],l=()=>{const{done:i,value:s}=n.next();if(i)return t(r);const c=new FileReader;c.addEventListener("error",o),c.addEventListener("load",(()=>{const t=c.result;"string"==typeof t&&r.push({file:s,result:t}),l()})),w(s,e)?c.readAsDataURL(s):l()};l()}))}function T(t,e){const n=[],o=(t||l()).getLatest(),r=e||(i(o)?o.getLastDescendant():o);let s=o,c=function(t){let e=t,n=0;for(;null!==(e=e.getParent());)n++;return n}(s);for(;null!==s&&!s.is(r);)if(n.push({depth:c,node:s}),i(s)&&s.getChildrenSize()>0)s=s.getFirstChild(),c++;else{let t=null;for(;null===t&&null!==s;)t=s.getNextSibling(),null===t?(s=s.getParent(),c--):s=t}return null!==s&&s.is(r)&&n.push({depth:c,node:s}),n}function b(t,e){let n=t;for(;null!=n;){if(n instanceof e)return n;n=n.getParent()}return null}function S(t){const e=_(t,(t=>i(t)&&!t.isInline()));return i(e)||g(4,t.__key),e}const _=(t,e)=>{let n=t;for(;n!==l()&&null!=n;){if(e(n))return n;n=n.getParent()}return null};function B(t,e,n,o){const r=t=>t instanceof e;return t.registerNodeTransform(e,(t=>{const e=(t=>{const e=t.getChildren();for(let t=0;t<e.length;t++){const n=e[t];if(r(n))return null}let n=t,o=t;for(;null!==n;)if(o=n,n=n.getParent(),r(n))return{child:o,parent:n};return null})(t);if(null!==e){const{child:r,parent:l}=e;if(r.is(t)){o(l,t);const e=r.getNextSiblings(),i=e.length;if(l.insertAfter(r),0!==i){const t=n(l);r.insertAfter(t);for(let n=0;n<i;n++)t.append(e[n])}l.canBeEmpty()||0!==l.getChildrenSize()||l.remove()}}}))}function M(t,n){const o=new Map,r=t._pendingEditorState;for(const[t,r]of n._nodeMap){const n=e(r);if(s(n)){if(!s(r))throw Error("Expected node be a TextNode");n.__text=r.__text}o.set(t,n)}r&&(r._nodeMap=o),t._dirtyType=2;const l=n._selection;c(null===l?null:l.clone())}function P(t){const e=n()||f();if(o(e)){const{focus:n}=e,o=n.getNode(),r=n.offset;if(u(o)){const e=o.getChildAtIndex(r);null==e?o.append(t):e.insertBefore(t),t.selectNext()}else{let e,n;s(o)?(e=o.getParentOrThrow(),n=o.getIndexWithinParent(),r>0&&(n+=1,o.splitText(r))):(e=o,n=r);const[,l]=a(e,n);l.insertBefore(t),l.selectStart()}}else{if(null!=e){const n=e.getNodes();n[n.length-1].getTopLevelElementOrThrow().insertAfter(t)}else{l().append(t)}const n=d();t.insertAfter(n),n.select()}return t.getLatest()}function A(t,e){const n=e();return t.replace(n),n.append(t),n}function C(t,e){return null!==t&&Object.getPrototypeOf(t).constructor.name===e.name}function K(t,e){const n=[];for(let o=0;o<t.length;o++){const r=e(t[o]);null!==r&&n.push(r)}return n}function O(t,e){const n=t.getFirstChild();null!==n?n.insertBefore(e):t.append(e)}export{T as $dfs,K as $filter,_ as $findMatchingParent,S as $getNearestBlockElementAncestorOrThrow,b as $getNearestNodeOfType,O as $insertFirst,P as $insertNodeToNearestRoot,M as $restoreEditorState,A as $wrapNodeInElement,v as addClassNamesToElement,w as isMimeType,y as markSelection,L as mediaFileReader,h as mergeRegister,C as objectKlassEquals,x as positionNodeOnRange,B as registerNestedElementResolver,N as removeClassNamesFromElement};
@@ -4,19 +4,20 @@
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 h=require("@lexical/selection"),B=require("lexical");function C(a){let b=new URLSearchParams;b.append("code",a);for(let c=1;c<arguments.length;c++)b.append("v",arguments[c]);throw Error(`Minified Lexical error #${a}; visit https://lexical.dev/docs/error?${b} for the full message or `+"use the non-minified dev environment for full errors and additional helpful warnings.");}function D(...a){return()=>{a.forEach(b=>b())}}let E={attributes:!0,characterData:!0,childList:!0,subtree:!0};
8
- function F(a,b,c){function e(){if(null===g)throw Error("Unexpected null rootDOMNode");if(null===n)throw Error("Unexpected null parentDOMNode");let {left:p,top:z}=g.getBoundingClientRect();var q=n;let r=h.createRectsFromDOMRange(a,b);t.isConnected||q.append(t);q=!1;for(let x=0;x<r.length;x++){var w=r[x];let u=k[x]||document.createElement("div"),y=u.style;"absolute"!==y.position&&(y.position="absolute",q=!0);var l=`${w.left-p}px`;y.left!==l&&(y.left=l,q=!0);l=`${w.top-z}px`;y.top!==l&&(u.style.top=
7
+ 'use strict';var h=require("@lexical/selection"),B=require("lexical");function C(a){let b=new URLSearchParams;b.append("code",a);for(let c=1;c<arguments.length;c++)b.append("v",arguments[c]);throw Error(`Minified Lexical error #${a}; visit https://lexical.dev/docs/error?${b} for the full message or `+"use the non-minified dev environment for full errors and additional helpful warnings.");}
8
+ function D(...a){let b=[];for(let c of a)if(c&&"string"===typeof c)for(let [e]of c.matchAll(/\S+/g))b.push(e);return b}function E(...a){return()=>{a.forEach(b=>b())}}let F={attributes:!0,characterData:!0,childList:!0,subtree:!0};
9
+ function G(a,b,c){function e(){if(null===g)throw Error("Unexpected null rootDOMNode");if(null===n)throw Error("Unexpected null parentDOMNode");let {left:p,top:z}=g.getBoundingClientRect();var q=n;let r=h.createRectsFromDOMRange(a,b);t.isConnected||q.append(t);q=!1;for(let x=0;x<r.length;x++){var w=r[x];let u=k[x]||document.createElement("div"),y=u.style;"absolute"!==y.position&&(y.position="absolute",q=!0);var l=`${w.left-p}px`;y.left!==l&&(y.left=l,q=!0);l=`${w.top-z}px`;y.top!==l&&(u.style.top=
9
10
  l,q=!0);l=`${w.width}px`;y.width!==l&&(u.style.width=l,q=!0);w=`${w.height}px`;y.height!==w&&(u.style.height=w,q=!0);u.parentNode!==t&&(t.append(u),q=!0);k[x]=u}for(;k.length>r.length;)k.pop();q&&c(k)}function d(){g=n=null;null!==m&&m.disconnect();m=null;t.remove();for(let p of k)p.remove();k=[]}function f(){let p=a.getRootElement();if(null===p)return d();let z=p.parentElement;if(!(z instanceof HTMLElement))return d();d();g=p;n=z;m=new MutationObserver(q=>{let r=a.getRootElement(),w=r&&r.parentElement;
10
- if(r!==g||w!==n)return f();for(let l of q)if(!t.contains(l.target))return e()});m.observe(z,E);e()}let g=null,n=null,m=null,k=[],t=document.createElement("div"),A=a.registerRootListener(f);return()=>{A();d()}}function G(a,b){for(let c of b)if(a.type.startsWith(c))return!0;return!1}let H=(a,b)=>{for(;a!==B.$getRoot()&&null!=a;){if(b(a))return a;a=a.getParent()}return null};exports.$splitNode=B.$splitNode;exports.isHTMLAnchorElement=B.isHTMLAnchorElement;exports.isHTMLElement=B.isHTMLElement;
11
+ if(r!==g||w!==n)return f();for(let l of q)if(!t.contains(l.target))return e()});m.observe(z,F);e()}let g=null,n=null,m=null,k=[],t=document.createElement("div"),A=a.registerRootListener(f);return()=>{A();d()}}function H(a,b){for(let c of b)if(a.type.startsWith(c))return!0;return!1}let I=(a,b)=>{for(;a!==B.$getRoot()&&null!=a;){if(b(a))return a;a=a.getParent()}return null};exports.$splitNode=B.$splitNode;exports.isHTMLAnchorElement=B.isHTMLAnchorElement;exports.isHTMLElement=B.isHTMLElement;
11
12
  exports.$dfs=function(a,b){let c=[];a=(a||B.$getRoot()).getLatest();b=b||(B.$isElementNode(a)?a.getLastDescendant():a);for(var e=a,d=0;null!==(e=e.getParent());)d++;for(e=d;null!==a&&!a.is(b);)if(c.push({depth:e,node:a}),B.$isElementNode(a)&&0<a.getChildrenSize())a=a.getFirstChild(),e++;else for(d=null;null===d&&null!==a;)d=a.getNextSibling(),null===d?(a=a.getParent(),e--):a=d;null!==a&&a.is(b)&&c.push({depth:e,node:a});return c};
12
- exports.$filter=function(a,b){let c=[];for(let e=0;e<a.length;e++){let d=b(a[e]);null!==d&&c.push(d)}return c};exports.$findMatchingParent=H;exports.$getNearestBlockElementAncestorOrThrow=function(a){let b=H(a,c=>B.$isElementNode(c)&&!c.isInline());B.$isElementNode(b)||C(4,a.__key);return b};exports.$getNearestNodeOfType=function(a,b){for(;null!=a;){if(a instanceof b)return a;a=a.getParent()}return null};exports.$insertFirst=function(a,b){let c=a.getFirstChild();null!==c?c.insertBefore(b):a.append(b)};
13
+ exports.$filter=function(a,b){let c=[];for(let e=0;e<a.length;e++){let d=b(a[e]);null!==d&&c.push(d)}return c};exports.$findMatchingParent=I;exports.$getNearestBlockElementAncestorOrThrow=function(a){let b=I(a,c=>B.$isElementNode(c)&&!c.isInline());B.$isElementNode(b)||C(4,a.__key);return b};exports.$getNearestNodeOfType=function(a,b){for(;null!=a;){if(a instanceof b)return a;a=a.getParent()}return null};exports.$insertFirst=function(a,b){let c=a.getFirstChild();null!==c?c.insertBefore(b):a.append(b)};
13
14
  exports.$insertNodeToNearestRoot=function(a){var b=B.$getSelection()||B.$getPreviousSelection();if(B.$isRangeSelection(b)){var {focus:c}=b;b=c.getNode();c=c.offset;if(B.$isRootOrShadowRoot(b))c=b.getChildAtIndex(c),null==c?b.append(a):c.insertBefore(a),a.selectNext();else{let e,d;B.$isTextNode(b)?(e=b.getParentOrThrow(),d=b.getIndexWithinParent(),0<c&&(d+=1,b.splitText(c))):(e=b,d=c);[,b]=B.$splitNode(e,d);b.insertBefore(a);b.selectStart()}}else null!=b?(b=b.getNodes(),b[b.length-1].getTopLevelElementOrThrow().insertAfter(a)):
14
15
  B.$getRoot().append(a),b=B.$createParagraphNode(),a.insertAfter(b),b.select();return a.getLatest()};exports.$restoreEditorState=function(a,b){let c=new Map,e=a._pendingEditorState;for(let [d,f]of b._nodeMap){let g=h.$cloneWithProperties(f);if(B.$isTextNode(g)){if(!B.$isTextNode(f))throw Error("Expected node be a TextNode");g.__text=f.__text}c.set(d,g)}e&&(e._nodeMap=c);a._dirtyType=2;a=b._selection;B.$setSelection(null===a?null:a.clone())};
15
- exports.$wrapNodeInElement=function(a,b){b=b();a.replace(b);b.append(a);return b};exports.addClassNamesToElement=function(a,...b){b.forEach(c=>{"string"===typeof c&&(c=c.split(" ").filter(e=>""!==e),a.classList.add(...c))})};exports.isMimeType=G;
16
+ exports.$wrapNodeInElement=function(a,b){b=b();a.replace(b);b.append(a);return b};exports.addClassNamesToElement=function(a,...b){b=D(...b);0<b.length&&a.classList.add(...b)};exports.isMimeType=H;
16
17
  exports.markSelection=function(a,b){function c(m){m.read(()=>{var k=B.$getSelection();if(B.$isRangeSelection(k)){var {anchor:t,focus:A}=k;k=t.getNode();var p=k.getKey(),z=t.offset,q=A.getNode(),r=q.getKey(),w=A.offset,l=a.getElementByKey(p),x=a.getElementByKey(r);p=null===e||null===l||z!==d||p!==e.getKey()||k!==e&&(!(e instanceof B.TextNode)||k.updateDOM(e,l,a._config));r=null===f||null===x||w!==g||r!==f.getKey()||q!==f&&(!(f instanceof B.TextNode)||q.updateDOM(f,x,a._config));if(p||r){l=a.getElementByKey(t.getNode().getKey());
17
- var u=a.getElementByKey(A.getNode().getKey());if(null!==l&&null!==u&&"SPAN"===l.tagName&&"SPAN"===u.tagName){r=document.createRange();A.isBefore(t)?(p=u,x=A.offset,u=l,l=t.offset):(p=l,x=t.offset,l=A.offset);p=p.firstChild;if(null===p)throw Error("Expected text node to be first child of span");u=u.firstChild;if(null===u)throw Error("Expected text node to be first child of span");r.setStart(p,x);r.setEnd(u,l);n();n=F(a,r,y=>{for(let I of y){let v=I.style;"Highlight"!==v.background&&(v.background="Highlight");
18
- "HighlightText"!==v.color&&(v.color="HighlightText");"-1"!==v.zIndex&&(v.zIndex="-1");"none"!==v.pointerEvents&&(v.pointerEvents="none");"-1.5px"!==v.marginTop&&(v.marginTop="-1.5px");"4px"!==v.paddingTop&&(v.paddingTop="4px");"0px"!==v.paddingBottom&&(v.paddingBottom="0px")}void 0!==b&&b(y)})}}e=k;d=z;f=q;g=w}else g=f=d=e=null,n(),n=()=>{}})}let e=null,d=null,f=null,g=null,n=()=>{};c(a.getEditorState());return D(a.registerUpdateListener(({editorState:m})=>c(m)),n,()=>{n()})};
19
- exports.mediaFileReader=function(a,b){let c=a[Symbol.iterator]();return new Promise((e,d)=>{let f=[],g=()=>{const {done:n,value:m}=c.next();if(n)return e(f);const k=new FileReader;k.addEventListener("error",d);k.addEventListener("load",()=>{const t=k.result;"string"===typeof t&&f.push({file:m,result:t});g()});G(m,b)?k.readAsDataURL(m):g()};g()})};exports.mergeRegister=D;exports.objectKlassEquals=function(a,b){return null!==a?Object.getPrototypeOf(a).constructor.name===b.name:!1};
20
- exports.positionNodeOnRange=F;
18
+ var u=a.getElementByKey(A.getNode().getKey());if(null!==l&&null!==u&&"SPAN"===l.tagName&&"SPAN"===u.tagName){r=document.createRange();A.isBefore(t)?(p=u,x=A.offset,u=l,l=t.offset):(p=l,x=t.offset,l=A.offset);p=p.firstChild;if(null===p)throw Error("Expected text node to be first child of span");u=u.firstChild;if(null===u)throw Error("Expected text node to be first child of span");r.setStart(p,x);r.setEnd(u,l);n();n=G(a,r,y=>{for(let J of y){let v=J.style;"Highlight"!==v.background&&(v.background="Highlight");
19
+ "HighlightText"!==v.color&&(v.color="HighlightText");"-1"!==v.zIndex&&(v.zIndex="-1");"none"!==v.pointerEvents&&(v.pointerEvents="none");"-1.5px"!==v.marginTop&&(v.marginTop="-1.5px");"4px"!==v.paddingTop&&(v.paddingTop="4px");"0px"!==v.paddingBottom&&(v.paddingBottom="0px")}void 0!==b&&b(y)})}}e=k;d=z;f=q;g=w}else g=f=d=e=null,n(),n=()=>{}})}let e=null,d=null,f=null,g=null,n=()=>{};c(a.getEditorState());return E(a.registerUpdateListener(({editorState:m})=>c(m)),n,()=>{n()})};
20
+ exports.mediaFileReader=function(a,b){let c=a[Symbol.iterator]();return new Promise((e,d)=>{let f=[],g=()=>{const {done:n,value:m}=c.next();if(n)return e(f);const k=new FileReader;k.addEventListener("error",d);k.addEventListener("load",()=>{const t=k.result;"string"===typeof t&&f.push({file:m,result:t});g()});H(m,b)?k.readAsDataURL(m):g()};g()})};exports.mergeRegister=E;exports.objectKlassEquals=function(a,b){return null!==a?Object.getPrototypeOf(a).constructor.name===b.name:!1};
21
+ exports.positionNodeOnRange=G;
21
22
  exports.registerNestedElementResolver=function(a,b,c,e){return a.registerNodeTransform(b,d=>{a:{var f=d.getChildren();for(var g=0;g<f.length;g++)if(f[g]instanceof b){f=null;break a}for(f=d;null!==f;)if(g=f,f=f.getParent(),f instanceof b){f={child:g,parent:f};break a}f=null}if(null!==f){const {child:n,parent:m}=f;if(n.is(d)){e(m,d);d=n.getNextSiblings();f=d.length;m.insertAfter(n);if(0!==f){g=c(m);n.insertAfter(g);for(let k=0;k<f;k++)g.append(d[k])}m.canBeEmpty()||0!==m.getChildrenSize()||m.remove()}}})};
22
- exports.removeClassNamesFromElement=function(a,...b){b.forEach(c=>{"string"===typeof c&&a.classList.remove(...c.split(" "))})}
23
+ exports.removeClassNamesFromElement=function(a,...b){b=D(...b);0<b.length&&a.classList.remove(...b)}
package/README.md CHANGED
@@ -1,3 +1,5 @@
1
1
  # `@lexical/utils`
2
2
 
3
+ [![See API Documentation](https://lexical.dev/img/see-api-documentation.svg)](https://lexical.dev/docs/api/modules/lexical_utils)
4
+
3
5
  This package contains misc utilities for Lexical.
package/package.json CHANGED
@@ -8,19 +8,21 @@
8
8
  "utils"
9
9
  ],
10
10
  "license": "MIT",
11
- "version": "0.13.0",
11
+ "version": "0.14.1",
12
12
  "main": "LexicalUtils.js",
13
13
  "peerDependencies": {
14
- "lexical": "0.13.0"
14
+ "lexical": "0.14.1"
15
15
  },
16
16
  "dependencies": {
17
- "@lexical/list": "0.13.0",
18
- "@lexical/table": "0.13.0",
19
- "@lexical/selection": "0.13.0"
17
+ "@lexical/list": "0.14.1",
18
+ "@lexical/table": "0.14.1",
19
+ "@lexical/selection": "0.14.1"
20
20
  },
21
21
  "repository": {
22
22
  "type": "git",
23
23
  "url": "https://github.com/facebook/lexical",
24
24
  "directory": "packages/lexical-utils"
25
- }
25
+ },
26
+ "module": "LexicalUtils.esm.js",
27
+ "sideEffects": false
26
28
  }