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