@lexical/react 0.49.1-nightly.20260901.0 → 0.50.0

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 (65) hide show
  1. package/dist/LexicalAutoEmbedPlugin.d.ts +4 -47
  2. package/dist/LexicalAutoEmbedPlugin.dev.js +5 -31
  3. package/dist/LexicalAutoEmbedPlugin.dev.mjs +5 -30
  4. package/dist/LexicalAutoEmbedPlugin.prod.js +1 -1
  5. package/dist/LexicalAutoEmbedPlugin.prod.mjs +1 -1
  6. package/dist/LexicalAutoEmbedPluginUtils.d.ts +53 -0
  7. package/dist/LexicalAutoEmbedPluginUtils.dev.js +66 -0
  8. package/dist/LexicalAutoEmbedPluginUtils.dev.mjs +62 -0
  9. package/dist/LexicalAutoEmbedPluginUtils.js +11 -0
  10. package/dist/LexicalAutoEmbedPluginUtils.js.flow +40 -0
  11. package/dist/LexicalAutoEmbedPluginUtils.mjs +14 -0
  12. package/dist/LexicalAutoEmbedPluginUtils.node.mjs +12 -0
  13. package/dist/LexicalAutoEmbedPluginUtils.prod.js +9 -0
  14. package/dist/LexicalAutoEmbedPluginUtils.prod.mjs +9 -0
  15. package/dist/LexicalCollaborationContext.d.ts +1 -26
  16. package/dist/LexicalCollaborationContext.dev.js +5 -55
  17. package/dist/LexicalCollaborationContext.dev.mjs +4 -53
  18. package/dist/LexicalCollaborationContext.prod.js +2 -2
  19. package/dist/LexicalCollaborationContext.prod.mjs +2 -2
  20. package/dist/LexicalCollaborationContextUtils.d.ts +35 -0
  21. package/dist/LexicalCollaborationContextUtils.dev.js +83 -0
  22. package/dist/LexicalCollaborationContextUtils.dev.mjs +79 -0
  23. package/dist/LexicalCollaborationContextUtils.js +11 -0
  24. package/dist/LexicalCollaborationContextUtils.js.flow +25 -0
  25. package/dist/LexicalCollaborationContextUtils.mjs +14 -0
  26. package/dist/LexicalCollaborationContextUtils.node.mjs +12 -0
  27. package/dist/LexicalCollaborationContextUtils.prod.js +9 -0
  28. package/dist/LexicalCollaborationContextUtils.prod.mjs +9 -0
  29. package/dist/LexicalMenuOption.d.ts +29 -0
  30. package/dist/LexicalMenuOption.dev.js +51 -0
  31. package/dist/LexicalMenuOption.dev.mjs +49 -0
  32. package/dist/LexicalMenuOption.js +11 -0
  33. package/dist/LexicalMenuOption.js.flow +20 -0
  34. package/dist/LexicalMenuOption.mjs +12 -0
  35. package/dist/LexicalMenuOption.node.mjs +10 -0
  36. package/dist/LexicalMenuOption.prod.js +9 -0
  37. package/dist/LexicalMenuOption.prod.mjs +9 -0
  38. package/dist/LexicalNodeMenuPlugin.dev.js +5 -28
  39. package/dist/LexicalNodeMenuPlugin.dev.mjs +4 -27
  40. package/dist/LexicalNodeMenuPlugin.prod.js +1 -1
  41. package/dist/LexicalNodeMenuPlugin.prod.mjs +1 -1
  42. package/dist/LexicalTypeaheadMenuPlugin.d.ts +2 -34
  43. package/dist/LexicalTypeaheadMenuPlugin.dev.js +10 -78
  44. package/dist/LexicalTypeaheadMenuPlugin.dev.mjs +9 -76
  45. package/dist/LexicalTypeaheadMenuPlugin.prod.js +1 -1
  46. package/dist/LexicalTypeaheadMenuPlugin.prod.mjs +1 -1
  47. package/dist/LexicalTypeaheadMenuPluginUtils.d.ts +44 -0
  48. package/dist/LexicalTypeaheadMenuPluginUtils.dev.js +81 -0
  49. package/dist/LexicalTypeaheadMenuPluginUtils.dev.mjs +76 -0
  50. package/dist/LexicalTypeaheadMenuPluginUtils.js +11 -0
  51. package/dist/LexicalTypeaheadMenuPluginUtils.js.flow +34 -0
  52. package/dist/LexicalTypeaheadMenuPluginUtils.mjs +15 -0
  53. package/dist/LexicalTypeaheadMenuPluginUtils.node.mjs +13 -0
  54. package/dist/LexicalTypeaheadMenuPluginUtils.prod.js +9 -0
  55. package/dist/LexicalTypeaheadMenuPluginUtils.prod.mjs +9 -0
  56. package/dist/shared/LexicalMenu.d.ts +5 -19
  57. package/package.json +164 -20
  58. package/src/LexicalAutoEmbedPlugin.tsx +12 -70
  59. package/src/LexicalAutoEmbedPluginUtils.ts +80 -0
  60. package/src/LexicalCollaborationContext.tsx +11 -90
  61. package/src/LexicalCollaborationContextUtils.ts +99 -0
  62. package/src/LexicalMenuOption.ts +39 -0
  63. package/src/LexicalTypeaheadMenuPlugin.tsx +6 -80
  64. package/src/LexicalTypeaheadMenuPluginUtils.ts +93 -0
  65. package/src/shared/LexicalMenu.tsx +12 -28
@@ -7,10 +7,14 @@
7
7
  */
8
8
 
9
9
  import {$isLinkNode, AutoLinkNode, LinkNode} from '@lexical/link';
10
+ import {
11
+ type AutoEmbedOption,
12
+ type EmbedConfig,
13
+ INSERT_EMBED_COMMAND,
14
+ } from '@lexical/react/LexicalAutoEmbedPluginUtils';
10
15
  import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
11
16
  import {
12
17
  LexicalNodeMenuPlugin,
13
- MenuOption,
14
18
  type MenuRenderFn,
15
19
  } from '@lexical/react/LexicalNodeMenuPlugin';
16
20
  import {objectKlassEquals} from '@lexical/utils';
@@ -22,10 +26,6 @@ import {
22
26
  COMMAND_PRIORITY_EDITOR,
23
27
  COMMAND_PRIORITY_LOW,
24
28
  type CommandListenerPriority,
25
- createCommand,
26
- type LexicalCommand,
27
- type LexicalEditor,
28
- type LexicalNode,
29
29
  mergeRegister,
30
30
  type MutationListener,
31
31
  type NodeKey,
@@ -35,71 +35,13 @@ import {
35
35
  } from 'lexical';
36
36
  import {type JSX, useCallback, useEffect, useMemo, useState} from 'react';
37
37
 
38
- /**
39
- * The result of matching a URL for an embed: the matched `url`, an `id`
40
- * identifying the embedded resource, and optional provider-specific `data`.
41
- */
42
- export type EmbedMatchResult<TEmbedMatchResult = unknown> = {
43
- url: string;
44
- id: string;
45
- data?: TEmbedMatchResult;
46
- };
47
-
48
- /**
49
- * Describes a kind of embed (for example YouTube, a tweet, or Google Maps) that
50
- * {@link LexicalAutoEmbedPlugin} can detect and insert. Each config has a `type`
51
- * identifier, a `parseUrl` function that decides whether a URL matches and
52
- * extracts its data, and an `insertNode` function that inserts the corresponding
53
- * Lexical node.
54
- */
55
- export interface EmbedConfig<
56
- TEmbedMatchResultData = unknown,
57
- TEmbedMatchResult = EmbedMatchResult<TEmbedMatchResultData>,
58
- > {
59
- // Used to identify this config e.g. youtube, tweet, google-maps.
60
- type: string;
61
- // Determine if a given URL is a match and return url data.
62
- parseUrl: (
63
- text: string,
64
- ) => Promise<TEmbedMatchResult | null> | TEmbedMatchResult | null;
65
- // Create the Lexical embed node from the url data.
66
- insertNode: (editor: LexicalEditor, result: TEmbedMatchResult) => void;
67
- }
68
-
69
- /**
70
- * A general-purpose regular expression for detecting URLs, provided as a
71
- * convenience for implementing an {@link EmbedConfig}'s `parseUrl`.
72
- */
73
- export const URL_MATCHER =
74
- /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
75
-
76
- /**
77
- * Command dispatched to start inserting an embed. Its payload is the `type` of
78
- * the {@link EmbedConfig} to use; {@link LexicalAutoEmbedPlugin} listens for it
79
- * and runs that config's URL detection flow.
80
- */
81
- export const INSERT_EMBED_COMMAND: LexicalCommand<EmbedConfig['type']> =
82
- createCommand('INSERT_EMBED_COMMAND');
83
-
84
- /**
85
- * A {@link MenuOption} for the auto-embed menu, pairing a display `title` with
86
- * an `onSelect` callback invoked when the user chooses to embed the detected
87
- * URL.
88
- */
89
- export class AutoEmbedOption extends MenuOption {
90
- title: string;
91
- onSelect: (targetNode: LexicalNode | null) => void;
92
- constructor(
93
- title: string,
94
- options: {
95
- onSelect: (targetNode: LexicalNode | null) => void;
96
- },
97
- ) {
98
- super(title);
99
- this.title = title;
100
- this.onSelect = options.onSelect.bind(this);
101
- }
102
- }
38
+ export {
39
+ AutoEmbedOption,
40
+ type EmbedConfig,
41
+ type EmbedMatchResult,
42
+ INSERT_EMBED_COMMAND,
43
+ URL_MATCHER,
44
+ } from '@lexical/react/LexicalAutoEmbedPluginUtils';
103
45
 
104
46
  type LexicalAutoEmbedPluginProps<TEmbedConfig extends EmbedConfig> = {
105
47
  /**
@@ -0,0 +1,80 @@
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
+
9
+ import {MenuOption} from '@lexical/react/LexicalMenuOption';
10
+ import {
11
+ createCommand,
12
+ type LexicalCommand,
13
+ type LexicalEditor,
14
+ type LexicalNode,
15
+ } from 'lexical';
16
+
17
+ /**
18
+ * The result of matching a URL for an embed: the matched `url`, an `id`
19
+ * identifying the embedded resource, and optional provider-specific `data`.
20
+ */
21
+ export type EmbedMatchResult<TEmbedMatchResult = unknown> = {
22
+ url: string;
23
+ id: string;
24
+ data?: TEmbedMatchResult;
25
+ };
26
+
27
+ /**
28
+ * Describes a kind of embed (for example YouTube, a tweet, or Google Maps) that
29
+ * {@link LexicalAutoEmbedPlugin} can detect and insert. Each config has a `type`
30
+ * identifier, a `parseUrl` function that decides whether a URL matches and
31
+ * extracts its data, and an `insertNode` function that inserts the corresponding
32
+ * Lexical node.
33
+ */
34
+ export interface EmbedConfig<
35
+ TEmbedMatchResultData = unknown,
36
+ TEmbedMatchResult = EmbedMatchResult<TEmbedMatchResultData>,
37
+ > {
38
+ type: string;
39
+ // Determine if a given URL is a match and return url data.
40
+ parseUrl: (
41
+ text: string,
42
+ ) => Promise<TEmbedMatchResult | null> | TEmbedMatchResult | null;
43
+ // Create the Lexical embed node from the url data.
44
+ insertNode: (editor: LexicalEditor, result: TEmbedMatchResult) => void;
45
+ }
46
+
47
+ /**
48
+ * A general-purpose regular expression for detecting URLs, provided as a
49
+ * convenience for implementing an {@link EmbedConfig}'s `parseUrl`.
50
+ */
51
+ export const URL_MATCHER =
52
+ /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
53
+
54
+ /**
55
+ * Command dispatched to start inserting an embed. Its payload is the `type` of
56
+ * the {@link EmbedConfig} to use; {@link LexicalAutoEmbedPlugin} listens for it
57
+ * and runs that config's URL detection flow.
58
+ */
59
+ export const INSERT_EMBED_COMMAND: LexicalCommand<EmbedConfig['type']> =
60
+ createCommand('INSERT_EMBED_COMMAND');
61
+
62
+ /**
63
+ * A {@link MenuOption} for the auto-embed menu, pairing a display `title` with
64
+ * an `onSelect` callback invoked when the user chooses to embed the detected
65
+ * URL.
66
+ */
67
+ export class AutoEmbedOption extends MenuOption {
68
+ title: string;
69
+ onSelect: (targetNode: LexicalNode | null) => void;
70
+ constructor(
71
+ title: string,
72
+ options: {
73
+ onSelect: (targetNode: LexicalNode | null) => void;
74
+ },
75
+ ) {
76
+ super(title);
77
+ this.title = title;
78
+ this.onSelect = options.onSelect.bind(this);
79
+ }
80
+ }
@@ -6,64 +6,17 @@
6
6
  *
7
7
  */
8
8
 
9
- import type {Doc} from 'yjs';
10
-
11
- import devInvariant from '@lexical/internal/devInvariant';
12
- import {createContext, useContext, useMemo} from 'react';
13
-
14
- /**
15
- * The value stored in the {@link CollaborationContext}: the local user's
16
- * display `name` and cursor `color`, whether collaboration is currently active,
17
- * and the map of Yjs documents shared by the editors under this provider.
18
- */
19
- export type CollaborationContextType = {
20
- color: string;
21
- isCollabActive: boolean;
22
- name: string;
23
- yjsDocMap: Map<string, Doc>;
24
- };
25
-
26
- const entries = [
27
- ['Cat', 'rgb(125, 50, 0)'],
28
- ['Dog', 'rgb(100, 0, 0)'],
29
- ['Rabbit', 'rgb(150, 0, 0)'],
30
- ['Frog', 'rgb(200, 0, 0)'],
31
- ['Fox', 'rgb(200, 75, 0)'],
32
- ['Hedgehog', 'rgb(0, 75, 0)'],
33
- ['Pigeon', 'rgb(0, 125, 0)'],
34
- ['Squirrel', 'rgb(75, 100, 0)'],
35
- ['Bear', 'rgb(125, 100, 0)'],
36
- ['Tiger', 'rgb(0, 0, 150)'],
37
- ['Leopard', 'rgb(0, 0, 200)'],
38
- ['Zebra', 'rgb(0, 0, 250)'],
39
- ['Wolf', 'rgb(0, 100, 150)'],
40
- ['Owl', 'rgb(0, 100, 100)'],
41
- ['Gull', 'rgb(100, 0, 100)'],
42
- ['Squid', 'rgb(150, 0, 150)'],
43
- ];
44
-
45
- const randomEntry = entries[Math.floor(Math.random() * entries.length)];
46
-
47
- /**
48
- * The React context that holds the shared {@link CollaborationContextType} for
49
- * collaborative editors. Provide it with {@link LexicalCollaboration} and read
50
- * it with {@link useCollaborationContext}.
51
- */
52
- export const CollaborationContext =
53
- createContext<CollaborationContextType | null>(null);
54
-
55
- function newContext() {
56
- return {
57
- color: randomEntry[1],
58
- isCollabActive: false,
59
- name: randomEntry[0],
60
- yjsDocMap: new Map(),
61
- };
62
- }
63
-
64
- // This is here to help the transition post-#7818, however should be removed in a future release as
65
- // a shared context across editors is likely to lead to bugs.
66
- const UNSAFE_GLOBAL_CONTEXT = newContext();
9
+ import {
10
+ CollaborationContext,
11
+ newContext,
12
+ } from '@lexical/react/LexicalCollaborationContextUtils';
13
+ import {useMemo} from 'react';
14
+
15
+ export {
16
+ CollaborationContext,
17
+ type CollaborationContextType,
18
+ useCollaborationContext,
19
+ } from '@lexical/react/LexicalCollaborationContextUtils';
67
20
 
68
21
  /**
69
22
  * A provider component that creates a fresh {@link CollaborationContextType}
@@ -82,35 +35,3 @@ export function LexicalCollaboration({children}: {children: React.ReactNode}) {
82
35
  </CollaborationContext.Provider>
83
36
  );
84
37
  }
85
-
86
- /**
87
- * Reads the current {@link CollaborationContextType} from the nearest
88
- * {@link LexicalCollaboration} provider. Optionally pass `username` and `color`
89
- * to set the local user's display name and cursor color.
90
- *
91
- * @returns The active collaboration context.
92
- */
93
- export function useCollaborationContext(
94
- username?: string,
95
- color?: string,
96
- ): CollaborationContextType {
97
- let collabContext = useContext(CollaborationContext);
98
- devInvariant(
99
- collabContext != null,
100
- 'useCollaborationContext: no context provider found',
101
- );
102
-
103
- collabContext = collabContext ?? UNSAFE_GLOBAL_CONTEXT;
104
-
105
- if (username != null) {
106
- // eslint-disable-next-line react-hooks/immutability
107
- collabContext.name = username;
108
- }
109
-
110
- if (color != null) {
111
- // eslint-disable-next-line react-hooks/immutability
112
- collabContext.color = color;
113
- }
114
-
115
- return collabContext;
116
- }
@@ -0,0 +1,99 @@
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
+
9
+ import type {Doc} from 'yjs';
10
+
11
+ import devInvariant from '@lexical/internal/devInvariant';
12
+ import {createContext, useContext} from 'react';
13
+
14
+ /**
15
+ * The value stored in the {@link CollaborationContext}: the local user's
16
+ * display `name` and cursor `color`, whether collaboration is currently active,
17
+ * and the map of Yjs documents shared by the editors under this provider.
18
+ */
19
+ export type CollaborationContextType = {
20
+ color: string;
21
+ isCollabActive: boolean;
22
+ name: string;
23
+ yjsDocMap: Map<string, Doc>;
24
+ };
25
+
26
+ const entries = [
27
+ ['Cat', 'rgb(125, 50, 0)'],
28
+ ['Dog', 'rgb(100, 0, 0)'],
29
+ ['Rabbit', 'rgb(150, 0, 0)'],
30
+ ['Frog', 'rgb(200, 0, 0)'],
31
+ ['Fox', 'rgb(200, 75, 0)'],
32
+ ['Hedgehog', 'rgb(0, 75, 0)'],
33
+ ['Pigeon', 'rgb(0, 125, 0)'],
34
+ ['Squirrel', 'rgb(75, 100, 0)'],
35
+ ['Bear', 'rgb(125, 100, 0)'],
36
+ ['Tiger', 'rgb(0, 0, 150)'],
37
+ ['Leopard', 'rgb(0, 0, 200)'],
38
+ ['Zebra', 'rgb(0, 0, 250)'],
39
+ ['Wolf', 'rgb(0, 100, 150)'],
40
+ ['Owl', 'rgb(0, 100, 100)'],
41
+ ['Gull', 'rgb(100, 0, 100)'],
42
+ ['Squid', 'rgb(150, 0, 150)'],
43
+ ];
44
+
45
+ const randomEntry = entries[Math.floor(Math.random() * entries.length)];
46
+
47
+ /**
48
+ * The React context that holds the shared {@link CollaborationContextType} for
49
+ * collaborative editors. Provide it with {@link LexicalCollaboration} and read
50
+ * it with {@link useCollaborationContext}.
51
+ */
52
+ export const CollaborationContext =
53
+ createContext<CollaborationContextType | null>(null);
54
+
55
+ /** @internal */
56
+ export function newContext(): CollaborationContextType {
57
+ return {
58
+ color: randomEntry[1],
59
+ isCollabActive: false,
60
+ name: randomEntry[0],
61
+ yjsDocMap: new Map(),
62
+ };
63
+ }
64
+
65
+ // This is here to help the transition post-#7818, however should be removed in a future release as
66
+ // a shared context across editors is likely to lead to bugs.
67
+ const UNSAFE_GLOBAL_CONTEXT = newContext();
68
+
69
+ /**
70
+ * Reads the current {@link CollaborationContextType} from the nearest
71
+ * {@link LexicalCollaboration} provider. Optionally pass `username` and `color`
72
+ * to set the local user's display name and cursor color.
73
+ *
74
+ * @returns The active collaboration context.
75
+ */
76
+ export function useCollaborationContext(
77
+ username?: string,
78
+ color?: string,
79
+ ): CollaborationContextType {
80
+ let collabContext = useContext(CollaborationContext);
81
+ devInvariant(
82
+ collabContext != null,
83
+ 'useCollaborationContext: no context provider found',
84
+ );
85
+
86
+ collabContext = collabContext ?? UNSAFE_GLOBAL_CONTEXT;
87
+
88
+ if (username != null) {
89
+ // eslint-disable-next-line react-hooks/immutability
90
+ collabContext.name = username;
91
+ }
92
+
93
+ if (color != null) {
94
+ // eslint-disable-next-line react-hooks/immutability
95
+ collabContext.color = color;
96
+ }
97
+
98
+ return collabContext;
99
+ }
@@ -0,0 +1,39 @@
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
+
9
+ import type {JSX, RefObject} from 'react';
10
+
11
+ /**
12
+ * The base class for an item shown in a {@link LexicalTypeaheadMenuPlugin} or
13
+ * {@link LexicalNodeMenuPlugin} menu. Each option has a unique `key` and a `ref`
14
+ * to its rendered element (used for scrolling and keyboard navigation).
15
+ * Subclass it to attach your own data such as a label or callback.
16
+ *
17
+ * Its own module, and not `shared/LexicalMenu`, because that module is not an
18
+ * entry point: it is inlined into every entry that reaches it, which would give
19
+ * each of them a different `MenuOption` class. Its own module rather than one
20
+ * of the plugin entry points, because those export React components — anything
21
+ * importing this from one of them sits behind that component's Fast Refresh
22
+ * boundary and is invalidated whenever the component changes.
23
+ */
24
+ export class MenuOption {
25
+ key: string;
26
+ ref?: RefObject<HTMLElement | null>;
27
+ icon?: JSX.Element;
28
+ title?: JSX.Element | string;
29
+
30
+ constructor(key: string) {
31
+ this.key = key;
32
+ this.ref = {current: null};
33
+ this.setRefElement = this.setRefElement.bind(this);
34
+ }
35
+
36
+ setRefElement(element: HTMLElement | null) {
37
+ this.ref = {current: element};
38
+ }
39
+ }
@@ -7,17 +7,14 @@
7
7
  */
8
8
 
9
9
  import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
10
- import {getScrollParent as getScrollParent_} from '@lexical/utils';
11
10
  import {
12
11
  $getSelection,
13
12
  $isRangeSelection,
14
13
  $isTextNode,
15
14
  COMMAND_PRIORITY_LOW,
16
15
  type CommandListenerPriority,
17
- createCommand,
18
16
  getDOMSelection,
19
17
  getDOMSelectionPoints,
20
- type LexicalCommand,
21
18
  type LexicalEditor,
22
19
  type RangeSelection,
23
20
  type TextNode,
@@ -40,14 +37,6 @@ import {
40
37
  useMenuAnchorRef,
41
38
  } from './shared/LexicalMenu';
42
39
 
43
- /**
44
- * The default set of punctuation characters (as a character-class fragment)
45
- * that terminate a typeahead query. Used as the default `punctuation` option of
46
- * {@link useBasicTypeaheadTriggerMatch}.
47
- */
48
- export const PUNCTUATION =
49
- '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;';
50
-
51
40
  function getTextUpToAnchor(selection: RangeSelection): string | null {
52
41
  const anchor = selection.anchor;
53
42
  if (anchor.type !== 'text') {
@@ -122,75 +111,12 @@ function isSelectionOnEntityBoundary(
122
111
  }
123
112
 
124
113
  export {useDynamicPositioning} from './shared/LexicalMenu';
125
- /** @deprecated Moved to `@lexical/utils`. Import `getScrollParent` from there. */
126
- export const getScrollParent = getScrollParent_;
127
-
128
- /**
129
- * Command dispatched while the typeahead menu is open to scroll the option at
130
- * the given `index` into view. The default menu renderer listens for it; custom
131
- * {@link MenuRenderFn}s can handle it to implement their own scrolling.
132
- */
133
- export const SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND: LexicalCommand<{
134
- index: number;
135
- option: MenuOption;
136
- }> = createCommand('SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND');
137
-
138
- /**
139
- * Builds a {@link TriggerFn} for the common case of a single-character
140
- * `trigger` (such as `@` or `#`) followed by a query. The returned function
141
- * matches when the trigger is preceded by whitespace or the start of the line
142
- * and is followed by between `minLength` and `maxLength` non-`punctuation`
143
- * characters (optionally allowing whitespace).
144
- *
145
- * @returns A memoized trigger function for {@link LexicalTypeaheadMenuPlugin}.
146
- */
147
- export function useBasicTypeaheadTriggerMatch(
148
- trigger: string,
149
- {
150
- minLength = 1,
151
- maxLength = 75,
152
- punctuation = PUNCTUATION,
153
- allowWhitespace = false,
154
- }: {
155
- minLength?: number;
156
- maxLength?: number;
157
- punctuation?: string;
158
- allowWhitespace?: boolean;
159
- },
160
- ): TriggerFn {
161
- return useCallback(
162
- (text: string) => {
163
- const validCharsSuffix = allowWhitespace ? '' : '\\s';
164
- const validChars = '[^' + trigger + punctuation + validCharsSuffix + ']';
165
- const TypeaheadTriggerRegex = new RegExp(
166
- '(^|\\s|\\()(' +
167
- '[' +
168
- trigger +
169
- ']' +
170
- '((?:' +
171
- validChars +
172
- '){0,' +
173
- maxLength +
174
- '})' +
175
- ')$',
176
- );
177
- const match = TypeaheadTriggerRegex.exec(text);
178
- if (match !== null) {
179
- const maybeLeadingWhitespace = match[1];
180
- const matchingString = match[3];
181
- if (matchingString.length >= minLength) {
182
- return {
183
- leadOffset: match.index + maybeLeadingWhitespace.length,
184
- matchingString,
185
- replaceableString: match[2],
186
- };
187
- }
188
- }
189
- return null;
190
- },
191
- [allowWhitespace, trigger, punctuation, maxLength, minLength],
192
- );
193
- }
114
+ export {
115
+ getScrollParent,
116
+ PUNCTUATION,
117
+ SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND,
118
+ useBasicTypeaheadTriggerMatch,
119
+ } from '@lexical/react/LexicalTypeaheadMenuPluginUtils';
194
120
 
195
121
  /**
196
122
  * Props for the {@link LexicalTypeaheadMenuPlugin} component.
@@ -0,0 +1,93 @@
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
+
9
+ import type {MenuOption, TriggerFn} from './shared/LexicalMenu';
10
+
11
+ import {getScrollParent as getScrollParent_} from '@lexical/utils';
12
+ import {createCommand, type LexicalCommand} from 'lexical';
13
+ import {useCallback} from 'react';
14
+
15
+ /**
16
+ * The default set of punctuation characters (as a character-class fragment)
17
+ * that terminate a typeahead query. Used as the default `punctuation` option of
18
+ * {@link useBasicTypeaheadTriggerMatch}.
19
+ */
20
+ export const PUNCTUATION =
21
+ '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;';
22
+
23
+ /**
24
+ * Command dispatched while the typeahead menu is open to scroll the option at
25
+ * the given `index` into view. The built-in menu hook registers a default
26
+ * handler; custom implementations can register their own handler at a higher
27
+ * priority to override the scrolling behavior.
28
+ */
29
+ export const SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND: LexicalCommand<{
30
+ index: number;
31
+ option: MenuOption;
32
+ }> = createCommand('SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND');
33
+
34
+ /** @deprecated Moved to `@lexical/utils`. Import `getScrollParent` from there. */
35
+ export const getScrollParent = getScrollParent_;
36
+
37
+ /**
38
+ * Builds a {@link TriggerFn} for the common case of a single-character
39
+ * `trigger` (such as `@` or `#`) followed by a query. The returned function
40
+ * matches when the trigger is preceded by whitespace, an open parenthesis,
41
+ * or the start of the line
42
+ * and is followed by between `minLength` and `maxLength` non-`punctuation`
43
+ * characters (optionally allowing whitespace).
44
+ *
45
+ * @returns A memoized trigger function for {@link LexicalTypeaheadMenuPlugin}.
46
+ */
47
+ export function useBasicTypeaheadTriggerMatch(
48
+ trigger: string,
49
+ {
50
+ minLength = 1,
51
+ maxLength = 75,
52
+ punctuation = PUNCTUATION,
53
+ allowWhitespace = false,
54
+ }: {
55
+ minLength?: number;
56
+ maxLength?: number;
57
+ punctuation?: string;
58
+ allowWhitespace?: boolean;
59
+ },
60
+ ): TriggerFn {
61
+ return useCallback(
62
+ (text: string) => {
63
+ const validCharsSuffix = allowWhitespace ? '' : '\\s';
64
+ const validChars = '[^' + trigger + punctuation + validCharsSuffix + ']';
65
+ const TypeaheadTriggerRegex = new RegExp(
66
+ '(^|\\s|\\()(' +
67
+ '[' +
68
+ trigger +
69
+ ']' +
70
+ '((?:' +
71
+ validChars +
72
+ '){0,' +
73
+ maxLength +
74
+ '})' +
75
+ ')$',
76
+ );
77
+ const match = TypeaheadTriggerRegex.exec(text);
78
+ if (match !== null) {
79
+ const maybeLeadingWhitespace = match[1];
80
+ const matchingString = match[3];
81
+ if (matchingString.length >= minLength) {
82
+ return {
83
+ leadOffset: match.index + maybeLeadingWhitespace.length,
84
+ matchingString,
85
+ replaceableString: match[2],
86
+ };
87
+ }
88
+ }
89
+ return null;
90
+ },
91
+ [allowWhitespace, trigger, punctuation, maxLength, minLength],
92
+ );
93
+ }