@lexical/utils 0.13.1 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LexicalUtils.dev.esm.js +730 -0
- package/LexicalUtils.dev.js +28 -11
- package/LexicalUtils.esm.js +30 -0
- package/LexicalUtils.js +1 -1
- package/LexicalUtils.prod.esm.js +7 -0
- package/LexicalUtils.prod.js +11 -10
- package/README.md +2 -0
- package/package.json +8 -6
|
@@ -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 };
|
package/LexicalUtils.dev.js
CHANGED
|
@@ -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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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};
|
package/LexicalUtils.prod.js
CHANGED
|
@@ -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.");}
|
|
8
|
-
function
|
|
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,
|
|
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=
|
|
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
|
|
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=
|
|
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
|
|
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()});
|
|
20
|
-
exports.positionNodeOnRange=
|
|
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.
|
|
23
|
+
exports.removeClassNamesFromElement=function(a,...b){b=D(...b);0<b.length&&a.classList.remove(...b)}
|
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -8,19 +8,21 @@
|
|
|
8
8
|
"utils"
|
|
9
9
|
],
|
|
10
10
|
"license": "MIT",
|
|
11
|
-
"version": "0.
|
|
11
|
+
"version": "0.14.2",
|
|
12
12
|
"main": "LexicalUtils.js",
|
|
13
13
|
"peerDependencies": {
|
|
14
|
-
"lexical": "0.
|
|
14
|
+
"lexical": "0.14.2"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@lexical/list": "0.
|
|
18
|
-
"@lexical/table": "0.
|
|
19
|
-
"@lexical/selection": "0.
|
|
17
|
+
"@lexical/list": "0.14.2",
|
|
18
|
+
"@lexical/table": "0.14.2",
|
|
19
|
+
"@lexical/selection": "0.14.2"
|
|
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
|
}
|