@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.
Files changed (45) hide show
  1. package/LexicalAutoEmbedPlugin.d.ts +2 -2
  2. package/LexicalAutoEmbedPlugin.dev.js +4 -4
  3. package/LexicalAutoEmbedPlugin.js.flow +2 -2
  4. package/LexicalAutoEmbedPlugin.prod.js +2 -2
  5. package/LexicalCharacterLimitPlugin.dev.js +4 -3
  6. package/LexicalCharacterLimitPlugin.prod.js +6 -6
  7. package/LexicalClickableLinkPlugin.dev.js +12 -9
  8. package/LexicalClickableLinkPlugin.prod.js +3 -4
  9. package/LexicalCollaborationPlugin.d.ts +2 -1
  10. package/LexicalCollaborationPlugin.dev.js +11 -10
  11. package/LexicalCollaborationPlugin.js.flow +13 -3
  12. package/LexicalCollaborationPlugin.prod.js +9 -9
  13. package/LexicalContentEditable.d.ts +1 -1
  14. package/LexicalContentEditable.dev.js +21 -10
  15. package/LexicalContentEditable.prod.js +3 -3
  16. package/LexicalContextMenuPlugin.d.ts +29 -0
  17. package/LexicalContextMenuPlugin.dev.js +508 -0
  18. package/LexicalContextMenuPlugin.js +9 -0
  19. package/LexicalContextMenuPlugin.prod.js +21 -0
  20. package/LexicalDecoratorBlockNode.d.ts +1 -0
  21. package/LexicalDecoratorBlockNode.dev.js +4 -0
  22. package/LexicalDecoratorBlockNode.prod.js +1 -1
  23. package/LexicalNodeEventPlugin.d.ts +1 -1
  24. package/LexicalNodeEventPlugin.dev.js +26 -33
  25. package/LexicalNodeEventPlugin.prod.js +2 -2
  26. package/LexicalNodeMenuPlugin.d.ts +22 -0
  27. package/LexicalNodeMenuPlugin.dev.js +521 -0
  28. package/LexicalNodeMenuPlugin.js +9 -0
  29. package/LexicalNodeMenuPlugin.js.flow +64 -0
  30. package/LexicalNodeMenuPlugin.prod.js +21 -0
  31. package/LexicalPlainTextPlugin.dev.js +1 -1
  32. package/LexicalPlainTextPlugin.prod.js +4 -4
  33. package/LexicalRichTextPlugin.dev.js +1 -1
  34. package/LexicalRichTextPlugin.prod.js +4 -4
  35. package/LexicalTabIndentationPlugin.d.ts +2 -0
  36. package/LexicalTabIndentationPlugin.dev.js +69 -10
  37. package/LexicalTabIndentationPlugin.js.flow +6 -0
  38. package/LexicalTabIndentationPlugin.prod.js +3 -1
  39. package/LexicalTypeaheadMenuPlugin.d.ts +10 -40
  40. package/LexicalTypeaheadMenuPlugin.dev.js +237 -210
  41. package/LexicalTypeaheadMenuPlugin.js.flow +7 -26
  42. package/LexicalTypeaheadMenuPlugin.prod.js +20 -20
  43. package/package.json +19 -19
  44. package/shared/LexicalMenu.d.ts +49 -0
  45. 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
- const PUNCTUATION = '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;';
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(editor, match) {
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
- }, [resolution.match.matchingString]);
232
+ }, [matchingString]);
316
233
  const selectOptionAndCleanUp = React.useCallback(selectedEntry => {
317
234
  editor.update(() => {
318
- const textNodeContainingQuery = splitNodeContainingQuery(editor, resolution.match);
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
- }, [close, editor, resolution.match, onSelectOption]);
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(LexicalPopoverMenu, {
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
- onSelectOption: onSelectOption
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 QueryMatch = {
13
+ export type MenuTextMatch = {
14
14
  leadOffset: number,
15
15
  matchingString: string,
16
16
  replaceableString: string,
17
17
  };
18
18
 
19
- export type Resolution = {
20
- match: QueryMatch,
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 TypeaheadOption {
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: TypeaheadOption,
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: Resolution) => void,
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
- ) => QueryMatch | null;
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;