@lexical/react 0.10.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LexicalAutoEmbedPlugin.d.ts +2 -2
- package/LexicalAutoEmbedPlugin.dev.js +4 -4
- package/LexicalAutoEmbedPlugin.js.flow +2 -2
- package/LexicalAutoEmbedPlugin.prod.js +2 -2
- package/LexicalCharacterLimitPlugin.dev.js +4 -3
- package/LexicalCharacterLimitPlugin.prod.js +6 -6
- package/LexicalClickableLinkPlugin.dev.js +12 -9
- package/LexicalClickableLinkPlugin.prod.js +3 -4
- package/LexicalCollaborationPlugin.d.ts +2 -1
- package/LexicalCollaborationPlugin.dev.js +11 -10
- package/LexicalCollaborationPlugin.js.flow +13 -3
- package/LexicalCollaborationPlugin.prod.js +9 -9
- package/LexicalContentEditable.d.ts +1 -1
- package/LexicalContentEditable.dev.js +21 -10
- package/LexicalContentEditable.prod.js +3 -3
- package/LexicalContextMenuPlugin.d.ts +29 -0
- package/LexicalContextMenuPlugin.dev.js +508 -0
- package/LexicalContextMenuPlugin.js +9 -0
- package/LexicalContextMenuPlugin.prod.js +21 -0
- package/LexicalDecoratorBlockNode.d.ts +1 -0
- package/LexicalDecoratorBlockNode.dev.js +4 -0
- package/LexicalDecoratorBlockNode.prod.js +1 -1
- package/LexicalNodeEventPlugin.d.ts +1 -1
- package/LexicalNodeEventPlugin.dev.js +26 -33
- package/LexicalNodeEventPlugin.prod.js +2 -2
- package/LexicalNodeMenuPlugin.d.ts +22 -0
- package/LexicalNodeMenuPlugin.dev.js +521 -0
- package/LexicalNodeMenuPlugin.js +9 -0
- package/LexicalNodeMenuPlugin.js.flow +64 -0
- package/LexicalNodeMenuPlugin.prod.js +21 -0
- package/LexicalPlainTextPlugin.dev.js +1 -1
- package/LexicalPlainTextPlugin.prod.js +4 -4
- package/LexicalRichTextPlugin.dev.js +1 -1
- package/LexicalRichTextPlugin.prod.js +4 -4
- package/LexicalTabIndentationPlugin.d.ts +2 -0
- package/LexicalTabIndentationPlugin.dev.js +69 -10
- package/LexicalTabIndentationPlugin.js.flow +6 -0
- package/LexicalTabIndentationPlugin.prod.js +3 -1
- package/LexicalTypeaheadMenuPlugin.d.ts +10 -40
- package/LexicalTypeaheadMenuPlugin.dev.js +237 -210
- package/LexicalTypeaheadMenuPlugin.js.flow +7 -26
- package/LexicalTypeaheadMenuPlugin.prod.js +20 -20
- package/package.json +19 -19
- package/shared/LexicalMenu.d.ts +49 -0
- package/shared/useYjsCollaboration.d.ts +2 -2
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
9
|
var LexicalComposerContext = require('@lexical/react/LexicalComposerContext');
|
|
10
|
-
var utils = require('@lexical/utils');
|
|
11
10
|
var lexical = require('lexical');
|
|
12
11
|
var React = require('react');
|
|
12
|
+
var utils = require('@lexical/utils');
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
@@ -37,8 +37,7 @@ var useLayoutEffect = useLayoutEffectImpl;
|
|
|
37
37
|
* LICENSE file in the root directory of this source tree.
|
|
38
38
|
*
|
|
39
39
|
*/
|
|
40
|
-
|
|
41
|
-
class TypeaheadOption {
|
|
40
|
+
class MenuOption {
|
|
42
41
|
constructor(key) {
|
|
43
42
|
this.key = key;
|
|
44
43
|
this.ref = {
|
|
@@ -76,62 +75,6 @@ const scrollIntoViewIfNeeded = target => {
|
|
|
76
75
|
block: 'nearest'
|
|
77
76
|
});
|
|
78
77
|
};
|
|
79
|
-
|
|
80
|
-
function getTextUpToAnchor(selection) {
|
|
81
|
-
const anchor = selection.anchor;
|
|
82
|
-
|
|
83
|
-
if (anchor.type !== 'text') {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const anchorNode = anchor.getNode();
|
|
88
|
-
|
|
89
|
-
if (!anchorNode.isSimpleText()) {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const anchorOffset = anchor.offset;
|
|
94
|
-
return anchorNode.getTextContent().slice(0, anchorOffset);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function tryToPositionRange(leadOffset, range) {
|
|
98
|
-
const domSelection = window.getSelection();
|
|
99
|
-
|
|
100
|
-
if (domSelection === null || !domSelection.isCollapsed) {
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const anchorNode = domSelection.anchorNode;
|
|
105
|
-
const startOffset = leadOffset;
|
|
106
|
-
const endOffset = domSelection.anchorOffset;
|
|
107
|
-
|
|
108
|
-
if (anchorNode == null || endOffset == null) {
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
range.setStart(anchorNode, startOffset);
|
|
114
|
-
range.setEnd(anchorNode, endOffset);
|
|
115
|
-
} catch (error) {
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return true;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function getQueryTextForSearch(editor) {
|
|
123
|
-
let text = null;
|
|
124
|
-
editor.getEditorState().read(() => {
|
|
125
|
-
const selection = lexical.$getSelection();
|
|
126
|
-
|
|
127
|
-
if (!lexical.$isRangeSelection(selection)) {
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
text = getTextUpToAnchor(selection);
|
|
132
|
-
});
|
|
133
|
-
return text;
|
|
134
|
-
}
|
|
135
78
|
/**
|
|
136
79
|
* Walk backwards along user input and forward through entity title to try
|
|
137
80
|
* and replace more of the user's text with entity.
|
|
@@ -155,7 +98,7 @@ function getFullMatchOffset(documentText, entryText, offset) {
|
|
|
155
98
|
*/
|
|
156
99
|
|
|
157
100
|
|
|
158
|
-
function splitNodeContainingQuery(
|
|
101
|
+
function $splitNodeContainingQuery(match) {
|
|
159
102
|
const selection = lexical.$getSelection();
|
|
160
103
|
|
|
161
104
|
if (!lexical.$isRangeSelection(selection) || !selection.isCollapsed()) {
|
|
@@ -193,37 +136,10 @@ function splitNodeContainingQuery(editor, match) {
|
|
|
193
136
|
}
|
|
194
137
|
|
|
195
138
|
return newNode;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function isSelectionOnEntityBoundary(editor, offset) {
|
|
199
|
-
if (offset !== 0) {
|
|
200
|
-
return false;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return editor.getEditorState().read(() => {
|
|
204
|
-
const selection = lexical.$getSelection();
|
|
205
|
-
|
|
206
|
-
if (lexical.$isRangeSelection(selection)) {
|
|
207
|
-
const anchor = selection.anchor;
|
|
208
|
-
const anchorNode = anchor.getNode();
|
|
209
|
-
const prevSibling = anchorNode.getPreviousSibling();
|
|
210
|
-
return lexical.$isTextNode(prevSibling) && prevSibling.isTextEntity();
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return false;
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function startTransition(callback) {
|
|
218
|
-
if (React.startTransition) {
|
|
219
|
-
React.startTransition(callback);
|
|
220
|
-
} else {
|
|
221
|
-
callback();
|
|
222
|
-
}
|
|
223
139
|
} // Got from https://stackoverflow.com/a/42543908/2013580
|
|
224
140
|
|
|
225
141
|
|
|
226
|
-
function getScrollParent(element, includeHidden) {
|
|
142
|
+
function getScrollParent$1(element, includeHidden) {
|
|
227
143
|
let style = getComputedStyle(element);
|
|
228
144
|
const excludeStaticParent = style.position === 'absolute';
|
|
229
145
|
const overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/;
|
|
@@ -247,21 +163,21 @@ function getScrollParent(element, includeHidden) {
|
|
|
247
163
|
return document.body;
|
|
248
164
|
}
|
|
249
165
|
|
|
250
|
-
function isTriggerVisibleInNearestScrollContainer(targetElement, containerElement) {
|
|
166
|
+
function isTriggerVisibleInNearestScrollContainer$1(targetElement, containerElement) {
|
|
251
167
|
const tRect = targetElement.getBoundingClientRect();
|
|
252
168
|
const cRect = containerElement.getBoundingClientRect();
|
|
253
169
|
return tRect.top > cRect.top && tRect.top < cRect.bottom;
|
|
254
170
|
} // Reposition the menu on scroll, window resize, and element resize.
|
|
255
171
|
|
|
256
172
|
|
|
257
|
-
function useDynamicPositioning(resolution, targetElement, onReposition, onVisibilityChange) {
|
|
173
|
+
function useDynamicPositioning$1(resolution, targetElement, onReposition, onVisibilityChange) {
|
|
258
174
|
const [editor] = LexicalComposerContext.useLexicalComposerContext();
|
|
259
175
|
React.useEffect(() => {
|
|
260
176
|
if (targetElement != null && resolution != null) {
|
|
261
177
|
const rootElement = editor.getRootElement();
|
|
262
|
-
const rootScrollParent = rootElement != null ? getScrollParent(rootElement, false) : document.body;
|
|
178
|
+
const rootScrollParent = rootElement != null ? getScrollParent$1(rootElement, false) : document.body;
|
|
263
179
|
let ticking = false;
|
|
264
|
-
let previousIsInView = isTriggerVisibleInNearestScrollContainer(targetElement, rootScrollParent);
|
|
180
|
+
let previousIsInView = isTriggerVisibleInNearestScrollContainer$1(targetElement, rootScrollParent);
|
|
265
181
|
|
|
266
182
|
const handleScroll = function () {
|
|
267
183
|
if (!ticking) {
|
|
@@ -272,7 +188,7 @@ function useDynamicPositioning(resolution, targetElement, onReposition, onVisibi
|
|
|
272
188
|
ticking = true;
|
|
273
189
|
}
|
|
274
190
|
|
|
275
|
-
const isInView = isTriggerVisibleInNearestScrollContainer(targetElement, rootScrollParent);
|
|
191
|
+
const isInView = isTriggerVisibleInNearestScrollContainer$1(targetElement, rootScrollParent);
|
|
276
192
|
|
|
277
193
|
if (isInView !== previousIsInView) {
|
|
278
194
|
previousIsInView = isInView;
|
|
@@ -298,27 +214,28 @@ function useDynamicPositioning(resolution, targetElement, onReposition, onVisibi
|
|
|
298
214
|
}
|
|
299
215
|
}, [targetElement, editor, onVisibilityChange, onReposition, resolution]);
|
|
300
216
|
}
|
|
301
|
-
const SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND = lexical.createCommand('SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND');
|
|
302
|
-
|
|
303
|
-
function LexicalPopoverMenu({
|
|
217
|
+
const SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND$1 = lexical.createCommand('SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND');
|
|
218
|
+
function LexicalMenu({
|
|
304
219
|
close,
|
|
305
220
|
editor,
|
|
306
221
|
anchorElementRef,
|
|
307
222
|
resolution,
|
|
308
223
|
options,
|
|
309
224
|
menuRenderFn,
|
|
310
|
-
onSelectOption
|
|
225
|
+
onSelectOption,
|
|
226
|
+
shouldSplitNodeWithQuery = false
|
|
311
227
|
}) {
|
|
312
228
|
const [selectedIndex, setHighlightedIndex] = React.useState(null);
|
|
229
|
+
const matchingString = resolution.match && resolution.match.matchingString;
|
|
313
230
|
React.useEffect(() => {
|
|
314
231
|
setHighlightedIndex(0);
|
|
315
|
-
}, [
|
|
232
|
+
}, [matchingString]);
|
|
316
233
|
const selectOptionAndCleanUp = React.useCallback(selectedEntry => {
|
|
317
234
|
editor.update(() => {
|
|
318
|
-
const textNodeContainingQuery = splitNodeContainingQuery(
|
|
319
|
-
onSelectOption(selectedEntry, textNodeContainingQuery, close, resolution.match.matchingString);
|
|
235
|
+
const textNodeContainingQuery = resolution.match != null && shouldSplitNodeWithQuery ? $splitNodeContainingQuery(resolution.match) : null;
|
|
236
|
+
onSelectOption(selectedEntry, textNodeContainingQuery, close, resolution.match ? resolution.match.matchingString : '');
|
|
320
237
|
});
|
|
321
|
-
}, [
|
|
238
|
+
}, [editor, shouldSplitNodeWithQuery, resolution.match, onSelectOption, close]);
|
|
322
239
|
const updateSelectedIndex = React.useCallback(index => {
|
|
323
240
|
const rootElem = editor.getRootElement();
|
|
324
241
|
|
|
@@ -344,7 +261,7 @@ function LexicalPopoverMenu({
|
|
|
344
261
|
}
|
|
345
262
|
}, [options, selectedIndex, updateSelectedIndex]);
|
|
346
263
|
React.useEffect(() => {
|
|
347
|
-
return utils.mergeRegister(editor.registerCommand(SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND, ({
|
|
264
|
+
return utils.mergeRegister(editor.registerCommand(SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND$1, ({
|
|
348
265
|
option
|
|
349
266
|
}) => {
|
|
350
267
|
if (option.ref && option.ref.current != null) {
|
|
@@ -365,7 +282,7 @@ function LexicalPopoverMenu({
|
|
|
365
282
|
const option = options[newSelectedIndex];
|
|
366
283
|
|
|
367
284
|
if (option.ref != null && option.ref.current) {
|
|
368
|
-
editor.dispatchCommand(SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND, {
|
|
285
|
+
editor.dispatchCommand(SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND$1, {
|
|
369
286
|
index: newSelectedIndex,
|
|
370
287
|
option
|
|
371
288
|
});
|
|
@@ -430,41 +347,15 @@ function LexicalPopoverMenu({
|
|
|
430
347
|
selectedIndex,
|
|
431
348
|
setHighlightedIndex
|
|
432
349
|
}), [selectOptionAndCleanUp, selectedIndex, options]);
|
|
433
|
-
return menuRenderFn(anchorElementRef, listItemProps, resolution.match.matchingString);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function useBasicTypeaheadTriggerMatch(trigger, {
|
|
437
|
-
minLength = 1,
|
|
438
|
-
maxLength = 75
|
|
439
|
-
}) {
|
|
440
|
-
return React.useCallback(text => {
|
|
441
|
-
const validChars = '[^' + trigger + PUNCTUATION + '\\s]';
|
|
442
|
-
const TypeaheadTriggerRegex = new RegExp('(^|\\s|\\()(' + '[' + trigger + ']' + '((?:' + validChars + '){0,' + maxLength + '})' + ')$');
|
|
443
|
-
const match = TypeaheadTriggerRegex.exec(text);
|
|
444
|
-
|
|
445
|
-
if (match !== null) {
|
|
446
|
-
const maybeLeadingWhitespace = match[1];
|
|
447
|
-
const matchingString = match[3];
|
|
448
|
-
|
|
449
|
-
if (matchingString.length >= minLength) {
|
|
450
|
-
return {
|
|
451
|
-
leadOffset: match.index + maybeLeadingWhitespace.length,
|
|
452
|
-
matchingString,
|
|
453
|
-
replaceableString: match[2]
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
return null;
|
|
459
|
-
}, [maxLength, minLength, trigger]);
|
|
350
|
+
return menuRenderFn(anchorElementRef, listItemProps, resolution.match ? resolution.match.matchingString : '');
|
|
460
351
|
}
|
|
461
|
-
|
|
462
352
|
function useMenuAnchorRef(resolution, setResolution, className) {
|
|
463
353
|
const [editor] = LexicalComposerContext.useLexicalComposerContext();
|
|
464
354
|
const anchorElementRef = React.useRef(document.createElement('div'));
|
|
465
355
|
const positionMenu = React.useCallback(() => {
|
|
466
356
|
const rootElement = editor.getRootElement();
|
|
467
357
|
const containerDiv = anchorElementRef.current;
|
|
358
|
+
const menuEle = containerDiv.firstChild;
|
|
468
359
|
|
|
469
360
|
if (rootElement !== null && resolution !== null) {
|
|
470
361
|
const {
|
|
@@ -478,6 +369,23 @@ function useMenuAnchorRef(resolution, setResolution, className) {
|
|
|
478
369
|
containerDiv.style.height = `${height}px`;
|
|
479
370
|
containerDiv.style.width = `${width}px`;
|
|
480
371
|
|
|
372
|
+
if (menuEle !== null) {
|
|
373
|
+
const menuRect = menuEle.getBoundingClientRect();
|
|
374
|
+
const menuHeight = menuRect.height;
|
|
375
|
+
const menuWidth = menuRect.width;
|
|
376
|
+
const rootElementRect = rootElement.getBoundingClientRect();
|
|
377
|
+
|
|
378
|
+
if (left + menuWidth > rootElementRect.right) {
|
|
379
|
+
containerDiv.style.left = `${left - menuWidth + window.pageXOffset}px`;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const margin = 10;
|
|
383
|
+
|
|
384
|
+
if ((top + menuHeight > window.innerHeight || top + menuHeight > rootElementRect.bottom) && top - rootElementRect.top > menuHeight) {
|
|
385
|
+
containerDiv.style.top = `${top - menuHeight + window.pageYOffset - (height + margin)}px`;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
481
389
|
if (!containerDiv.isConnected) {
|
|
482
390
|
if (className != null) {
|
|
483
391
|
containerDiv.className = className;
|
|
@@ -520,10 +428,204 @@ function useMenuAnchorRef(resolution, setResolution, className) {
|
|
|
520
428
|
}
|
|
521
429
|
}
|
|
522
430
|
}, [resolution, setResolution]);
|
|
523
|
-
useDynamicPositioning(resolution, anchorElementRef.current, positionMenu, onVisibilityChange);
|
|
431
|
+
useDynamicPositioning$1(resolution, anchorElementRef.current, positionMenu, onVisibilityChange);
|
|
524
432
|
return anchorElementRef;
|
|
525
433
|
}
|
|
526
434
|
|
|
435
|
+
/**
|
|
436
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
437
|
+
*
|
|
438
|
+
* This source code is licensed under the MIT license found in the
|
|
439
|
+
* LICENSE file in the root directory of this source tree.
|
|
440
|
+
*
|
|
441
|
+
*/
|
|
442
|
+
const PUNCTUATION = '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;';
|
|
443
|
+
|
|
444
|
+
function getTextUpToAnchor(selection) {
|
|
445
|
+
const anchor = selection.anchor;
|
|
446
|
+
|
|
447
|
+
if (anchor.type !== 'text') {
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const anchorNode = anchor.getNode();
|
|
452
|
+
|
|
453
|
+
if (!anchorNode.isSimpleText()) {
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const anchorOffset = anchor.offset;
|
|
458
|
+
return anchorNode.getTextContent().slice(0, anchorOffset);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function tryToPositionRange(leadOffset, range) {
|
|
462
|
+
const domSelection = window.getSelection();
|
|
463
|
+
|
|
464
|
+
if (domSelection === null || !domSelection.isCollapsed) {
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const anchorNode = domSelection.anchorNode;
|
|
469
|
+
const startOffset = leadOffset;
|
|
470
|
+
const endOffset = domSelection.anchorOffset;
|
|
471
|
+
|
|
472
|
+
if (anchorNode == null || endOffset == null) {
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
try {
|
|
477
|
+
range.setStart(anchorNode, startOffset);
|
|
478
|
+
range.setEnd(anchorNode, endOffset);
|
|
479
|
+
} catch (error) {
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function getQueryTextForSearch(editor) {
|
|
487
|
+
let text = null;
|
|
488
|
+
editor.getEditorState().read(() => {
|
|
489
|
+
const selection = lexical.$getSelection();
|
|
490
|
+
|
|
491
|
+
if (!lexical.$isRangeSelection(selection)) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
text = getTextUpToAnchor(selection);
|
|
496
|
+
});
|
|
497
|
+
return text;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function isSelectionOnEntityBoundary(editor, offset) {
|
|
501
|
+
if (offset !== 0) {
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return editor.getEditorState().read(() => {
|
|
506
|
+
const selection = lexical.$getSelection();
|
|
507
|
+
|
|
508
|
+
if (lexical.$isRangeSelection(selection)) {
|
|
509
|
+
const anchor = selection.anchor;
|
|
510
|
+
const anchorNode = anchor.getNode();
|
|
511
|
+
const prevSibling = anchorNode.getPreviousSibling();
|
|
512
|
+
return lexical.$isTextNode(prevSibling) && prevSibling.isTextEntity();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return false;
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function startTransition(callback) {
|
|
520
|
+
if (React.startTransition) {
|
|
521
|
+
React.startTransition(callback);
|
|
522
|
+
} else {
|
|
523
|
+
callback();
|
|
524
|
+
}
|
|
525
|
+
} // Got from https://stackoverflow.com/a/42543908/2013580
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
function getScrollParent(element, includeHidden) {
|
|
529
|
+
let style = getComputedStyle(element);
|
|
530
|
+
const excludeStaticParent = style.position === 'absolute';
|
|
531
|
+
const overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/;
|
|
532
|
+
|
|
533
|
+
if (style.position === 'fixed') {
|
|
534
|
+
return document.body;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
for (let parent = element; parent = parent.parentElement;) {
|
|
538
|
+
style = getComputedStyle(parent);
|
|
539
|
+
|
|
540
|
+
if (excludeStaticParent && style.position === 'static') {
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX)) {
|
|
545
|
+
return parent;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return document.body;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function isTriggerVisibleInNearestScrollContainer(targetElement, containerElement) {
|
|
553
|
+
const tRect = targetElement.getBoundingClientRect();
|
|
554
|
+
const cRect = containerElement.getBoundingClientRect();
|
|
555
|
+
return tRect.top > cRect.top && tRect.top < cRect.bottom;
|
|
556
|
+
} // Reposition the menu on scroll, window resize, and element resize.
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
function useDynamicPositioning(resolution, targetElement, onReposition, onVisibilityChange) {
|
|
560
|
+
const [editor] = LexicalComposerContext.useLexicalComposerContext();
|
|
561
|
+
React.useEffect(() => {
|
|
562
|
+
if (targetElement != null && resolution != null) {
|
|
563
|
+
const rootElement = editor.getRootElement();
|
|
564
|
+
const rootScrollParent = rootElement != null ? getScrollParent(rootElement, false) : document.body;
|
|
565
|
+
let ticking = false;
|
|
566
|
+
let previousIsInView = isTriggerVisibleInNearestScrollContainer(targetElement, rootScrollParent);
|
|
567
|
+
|
|
568
|
+
const handleScroll = function () {
|
|
569
|
+
if (!ticking) {
|
|
570
|
+
window.requestAnimationFrame(function () {
|
|
571
|
+
onReposition();
|
|
572
|
+
ticking = false;
|
|
573
|
+
});
|
|
574
|
+
ticking = true;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const isInView = isTriggerVisibleInNearestScrollContainer(targetElement, rootScrollParent);
|
|
578
|
+
|
|
579
|
+
if (isInView !== previousIsInView) {
|
|
580
|
+
previousIsInView = isInView;
|
|
581
|
+
|
|
582
|
+
if (onVisibilityChange != null) {
|
|
583
|
+
onVisibilityChange(isInView);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
const resizeObserver = new ResizeObserver(onReposition);
|
|
589
|
+
window.addEventListener('resize', onReposition);
|
|
590
|
+
document.addEventListener('scroll', handleScroll, {
|
|
591
|
+
capture: true,
|
|
592
|
+
passive: true
|
|
593
|
+
});
|
|
594
|
+
resizeObserver.observe(targetElement);
|
|
595
|
+
return () => {
|
|
596
|
+
resizeObserver.unobserve(targetElement);
|
|
597
|
+
window.removeEventListener('resize', onReposition);
|
|
598
|
+
document.removeEventListener('scroll', handleScroll);
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
}, [targetElement, editor, onVisibilityChange, onReposition, resolution]);
|
|
602
|
+
}
|
|
603
|
+
const SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND = lexical.createCommand('SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND');
|
|
604
|
+
function useBasicTypeaheadTriggerMatch(trigger, {
|
|
605
|
+
minLength = 1,
|
|
606
|
+
maxLength = 75
|
|
607
|
+
}) {
|
|
608
|
+
return React.useCallback(text => {
|
|
609
|
+
const validChars = '[^' + trigger + PUNCTUATION + '\\s]';
|
|
610
|
+
const TypeaheadTriggerRegex = new RegExp('(^|\\s|\\()(' + '[' + trigger + ']' + '((?:' + validChars + '){0,' + maxLength + '})' + ')$');
|
|
611
|
+
const match = TypeaheadTriggerRegex.exec(text);
|
|
612
|
+
|
|
613
|
+
if (match !== null) {
|
|
614
|
+
const maybeLeadingWhitespace = match[1];
|
|
615
|
+
const matchingString = match[3];
|
|
616
|
+
|
|
617
|
+
if (matchingString.length >= minLength) {
|
|
618
|
+
return {
|
|
619
|
+
leadOffset: match.index + maybeLeadingWhitespace.length,
|
|
620
|
+
matchingString,
|
|
621
|
+
replaceableString: match[2]
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return null;
|
|
627
|
+
}, [maxLength, minLength, trigger]);
|
|
628
|
+
}
|
|
527
629
|
function LexicalTypeaheadMenuPlugin({
|
|
528
630
|
options,
|
|
529
631
|
onQueryChange,
|
|
@@ -587,97 +689,22 @@ function LexicalTypeaheadMenuPlugin({
|
|
|
587
689
|
removeUpdateListener();
|
|
588
690
|
};
|
|
589
691
|
}, [editor, triggerFn, onQueryChange, resolution, closeTypeahead, openTypeahead]);
|
|
590
|
-
return resolution === null || editor === null ? null : /*#__PURE__*/React.createElement(
|
|
692
|
+
return resolution === null || editor === null ? null : /*#__PURE__*/React.createElement(LexicalMenu, {
|
|
591
693
|
close: closeTypeahead,
|
|
592
694
|
resolution: resolution,
|
|
593
695
|
editor: editor,
|
|
594
696
|
anchorElementRef: anchorElementRef,
|
|
595
697
|
options: options,
|
|
596
698
|
menuRenderFn: menuRenderFn,
|
|
597
|
-
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
function LexicalNodeMenuPlugin({
|
|
601
|
-
options,
|
|
602
|
-
nodeKey,
|
|
603
|
-
onClose,
|
|
604
|
-
onOpen,
|
|
605
|
-
onSelectOption,
|
|
606
|
-
menuRenderFn,
|
|
607
|
-
anchorClassName
|
|
608
|
-
}) {
|
|
609
|
-
const [editor] = LexicalComposerContext.useLexicalComposerContext();
|
|
610
|
-
const [resolution, setResolution] = React.useState(null);
|
|
611
|
-
const anchorElementRef = useMenuAnchorRef(resolution, setResolution, anchorClassName);
|
|
612
|
-
const closeNodeMenu = React.useCallback(() => {
|
|
613
|
-
setResolution(null);
|
|
614
|
-
|
|
615
|
-
if (onClose != null && resolution !== null) {
|
|
616
|
-
onClose();
|
|
617
|
-
}
|
|
618
|
-
}, [onClose, resolution]);
|
|
619
|
-
const openNodeMenu = React.useCallback(res => {
|
|
620
|
-
setResolution(res);
|
|
621
|
-
|
|
622
|
-
if (onOpen != null && resolution === null) {
|
|
623
|
-
onOpen(res);
|
|
624
|
-
}
|
|
625
|
-
}, [onOpen, resolution]);
|
|
626
|
-
const positionOrCloseMenu = React.useCallback(() => {
|
|
627
|
-
if (nodeKey) {
|
|
628
|
-
editor.update(() => {
|
|
629
|
-
const node = lexical.$getNodeByKey(nodeKey);
|
|
630
|
-
const domElement = editor.getElementByKey(nodeKey);
|
|
631
|
-
|
|
632
|
-
if (node != null && domElement != null) {
|
|
633
|
-
const text = node.getTextContent();
|
|
634
|
-
|
|
635
|
-
if (resolution == null || resolution.match.matchingString !== text) {
|
|
636
|
-
startTransition(() => openNodeMenu({
|
|
637
|
-
getRect: () => domElement.getBoundingClientRect(),
|
|
638
|
-
match: {
|
|
639
|
-
leadOffset: text.length,
|
|
640
|
-
matchingString: text,
|
|
641
|
-
replaceableString: text
|
|
642
|
-
}
|
|
643
|
-
}));
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
});
|
|
647
|
-
} else if (nodeKey == null && resolution != null) {
|
|
648
|
-
closeNodeMenu();
|
|
649
|
-
}
|
|
650
|
-
}, [closeNodeMenu, editor, nodeKey, openNodeMenu, resolution]);
|
|
651
|
-
React.useEffect(() => {
|
|
652
|
-
positionOrCloseMenu();
|
|
653
|
-
}, [positionOrCloseMenu, nodeKey]);
|
|
654
|
-
React.useEffect(() => {
|
|
655
|
-
if (nodeKey != null) {
|
|
656
|
-
return editor.registerUpdateListener(({
|
|
657
|
-
dirtyElements
|
|
658
|
-
}) => {
|
|
659
|
-
if (dirtyElements.get(nodeKey)) {
|
|
660
|
-
positionOrCloseMenu();
|
|
661
|
-
}
|
|
662
|
-
});
|
|
663
|
-
}
|
|
664
|
-
}, [editor, positionOrCloseMenu, nodeKey]);
|
|
665
|
-
return resolution === null || editor === null ? null : /*#__PURE__*/React.createElement(LexicalPopoverMenu, {
|
|
666
|
-
close: closeNodeMenu,
|
|
667
|
-
resolution: resolution,
|
|
668
|
-
editor: editor,
|
|
669
|
-
anchorElementRef: anchorElementRef,
|
|
670
|
-
options: options,
|
|
671
|
-
menuRenderFn: menuRenderFn,
|
|
699
|
+
shouldSplitNodeWithQuery: true,
|
|
672
700
|
onSelectOption: onSelectOption
|
|
673
701
|
});
|
|
674
702
|
}
|
|
675
703
|
|
|
676
|
-
exports.LexicalNodeMenuPlugin = LexicalNodeMenuPlugin;
|
|
677
704
|
exports.LexicalTypeaheadMenuPlugin = LexicalTypeaheadMenuPlugin;
|
|
705
|
+
exports.MenuOption = MenuOption;
|
|
678
706
|
exports.PUNCTUATION = PUNCTUATION;
|
|
679
707
|
exports.SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND = SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND;
|
|
680
|
-
exports.TypeaheadOption = TypeaheadOption;
|
|
681
708
|
exports.getScrollParent = getScrollParent;
|
|
682
709
|
exports.useBasicTypeaheadTriggerMatch = useBasicTypeaheadTriggerMatch;
|
|
683
710
|
exports.useDynamicPositioning = useDynamicPositioning;
|
|
@@ -10,21 +10,21 @@
|
|
|
10
10
|
import type {LexicalCommand, LexicalEditor, NodeKey, TextNode} from 'lexical';
|
|
11
11
|
import * as React from 'react';
|
|
12
12
|
|
|
13
|
-
export type
|
|
13
|
+
export type MenuTextMatch = {
|
|
14
14
|
leadOffset: number,
|
|
15
15
|
matchingString: string,
|
|
16
16
|
replaceableString: string,
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
export type
|
|
20
|
-
match
|
|
19
|
+
export type MenuResolution = {
|
|
20
|
+
match?: MenuTextMatch,
|
|
21
21
|
getRect: () => ClientRect,
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
export const PUNCTUATION: string =
|
|
25
25
|
'\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;';
|
|
26
26
|
|
|
27
|
-
declare export class
|
|
27
|
+
declare export class MenuOption {
|
|
28
28
|
key: string;
|
|
29
29
|
ref: {current: HTMLElement | null};
|
|
30
30
|
constructor(key: string): void;
|
|
@@ -33,7 +33,7 @@ declare export class TypeaheadOption {
|
|
|
33
33
|
|
|
34
34
|
declare export var SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND: LexicalCommand<{
|
|
35
35
|
index: number,
|
|
36
|
-
option:
|
|
36
|
+
option: MenuOption,
|
|
37
37
|
}>;
|
|
38
38
|
|
|
39
39
|
export type MenuRenderFn<TOption> = (
|
|
@@ -63,7 +63,7 @@ export type TypeaheadMenuPluginProps<TOption> = {
|
|
|
63
63
|
options: Array<TOption>,
|
|
64
64
|
menuRenderFn: MenuRenderFn<TOption>,
|
|
65
65
|
triggerFn: TriggerFn,
|
|
66
|
-
onOpen?: (resolution:
|
|
66
|
+
onOpen?: (resolution: MenuResolution) => void,
|
|
67
67
|
onClose?: () => void,
|
|
68
68
|
anchorClassName?: string,
|
|
69
69
|
};
|
|
@@ -71,27 +71,8 @@ export type TypeaheadMenuPluginProps<TOption> = {
|
|
|
71
71
|
export type TriggerFn = (
|
|
72
72
|
text: string,
|
|
73
73
|
editor: LexicalEditor,
|
|
74
|
-
) =>
|
|
74
|
+
) => MenuTextMatch | null;
|
|
75
75
|
|
|
76
76
|
declare export function LexicalTypeaheadMenuPlugin<TOption>(
|
|
77
77
|
options: TypeaheadMenuPluginProps<TOption>,
|
|
78
78
|
): React.MixedElement | null;
|
|
79
|
-
|
|
80
|
-
type NodeMenuPluginProps<TOption> = {
|
|
81
|
-
onSelectOption: (
|
|
82
|
-
option: TOption,
|
|
83
|
-
textNodeContainingQuery: TextNode | null,
|
|
84
|
-
closeMenu: () => void,
|
|
85
|
-
matchingString: string,
|
|
86
|
-
) => void,
|
|
87
|
-
options: Array<TOption>,
|
|
88
|
-
nodeKey: NodeKey | null,
|
|
89
|
-
onClose?: () => void,
|
|
90
|
-
onOpen?: (resolution: Resolution) => void,
|
|
91
|
-
menuRenderFn: MenuRenderFn<TOption>,
|
|
92
|
-
anchorClassName?: string,
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
declare export function LexicalNodeMenuPlugin<TOption>(
|
|
96
|
-
options: NodeMenuPluginProps<TOption>,
|
|
97
|
-
): React.MixedElement | null;
|