@lexical/react 0.4.1 → 0.5.1-next.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 (66) hide show
  1. package/DEPRECATED_useLexical.js.flow +0 -1
  2. package/DEPRECATED_useLexicalDecorators.d.ts +2 -1
  3. package/DEPRECATED_useLexicalDecorators.dev.js +125 -10
  4. package/DEPRECATED_useLexicalDecorators.js.flow +8 -0
  5. package/DEPRECATED_useLexicalDecorators.prod.js +6 -2
  6. package/DEPRECATED_useLexicalPlainText.d.ts +2 -2
  7. package/DEPRECATED_useLexicalPlainText.dev.js +4 -4
  8. package/DEPRECATED_useLexicalPlainText.js.flow +0 -1
  9. package/DEPRECATED_useLexicalPlainText.prod.js +2 -2
  10. package/DEPRECATED_useLexicalRichText.d.ts +2 -2
  11. package/DEPRECATED_useLexicalRichText.dev.js +4 -4
  12. package/DEPRECATED_useLexicalRichText.js.flow +0 -1
  13. package/DEPRECATED_useLexicalRichText.prod.js +2 -2
  14. package/LexicalAutoEmbedPlugin.d.ts +3 -13
  15. package/LexicalAutoEmbedPlugin.dev.js +3 -19
  16. package/LexicalAutoEmbedPlugin.js.flow +3 -11
  17. package/LexicalAutoEmbedPlugin.prod.js +4 -5
  18. package/LexicalAutoLinkPlugin.dev.js +49 -45
  19. package/LexicalAutoLinkPlugin.prod.js +6 -6
  20. package/LexicalBlockWithAlignableContents.dev.js +1 -1
  21. package/LexicalBlockWithAlignableContents.prod.js +1 -1
  22. package/LexicalCollaborationPlugin.d.ts +5 -1
  23. package/LexicalCollaborationPlugin.dev.js +54 -16
  24. package/LexicalCollaborationPlugin.js.flow +1 -0
  25. package/LexicalCollaborationPlugin.prod.js +9 -9
  26. package/LexicalComposer.d.ts +1 -2
  27. package/LexicalComposer.dev.js +15 -1
  28. package/LexicalComposer.js.flow +14 -7
  29. package/LexicalComposer.prod.js +3 -3
  30. package/LexicalContentEditable.dev.js +1 -1
  31. package/LexicalContentEditable.prod.js +1 -1
  32. package/LexicalHorizontalRuleNode.d.ts +4 -2
  33. package/LexicalHorizontalRuleNode.dev.js +25 -3
  34. package/LexicalHorizontalRuleNode.js.flow +0 -1
  35. package/LexicalHorizontalRuleNode.prod.js +5 -5
  36. package/LexicalNestedComposer.d.ts +4 -2
  37. package/LexicalNestedComposer.dev.js +25 -3
  38. package/LexicalNestedComposer.js.flow +1 -0
  39. package/LexicalNestedComposer.prod.js +3 -3
  40. package/LexicalOnChangePlugin.d.ts +1 -2
  41. package/LexicalOnChangePlugin.dev.js +3 -9
  42. package/LexicalOnChangePlugin.js.flow +0 -2
  43. package/LexicalOnChangePlugin.prod.js +2 -2
  44. package/LexicalPlainTextPlugin.d.ts +3 -3
  45. package/LexicalPlainTextPlugin.dev.js +111 -20
  46. package/LexicalPlainTextPlugin.js.flow +3 -1
  47. package/LexicalPlainTextPlugin.prod.js +6 -4
  48. package/LexicalRichTextPlugin.d.ts +3 -3
  49. package/LexicalRichTextPlugin.dev.js +111 -20
  50. package/LexicalRichTextPlugin.js.flow +3 -1
  51. package/LexicalRichTextPlugin.prod.js +6 -4
  52. package/LexicalTableOfContents__EXPERIMENTAL.js.flow +1 -1
  53. package/LexicalTablePlugin.dev.js +1 -1
  54. package/LexicalTablePlugin.prod.js +1 -1
  55. package/LexicalTreeView.dev.js +28 -13
  56. package/LexicalTreeView.prod.js +14 -13
  57. package/LexicalTypeaheadMenuPlugin.d.ts +20 -8
  58. package/LexicalTypeaheadMenuPlugin.dev.js +200 -57
  59. package/LexicalTypeaheadMenuPlugin.js.flow +19 -21
  60. package/LexicalTypeaheadMenuPlugin.prod.js +18 -14
  61. package/package.json +19 -19
  62. package/shared/ReactErrorBoundary.d.ts +63 -0
  63. package/shared/useDecorators.d.ts +8 -1
  64. package/shared/usePlainTextSetup.d.ts +1 -2
  65. package/shared/useRichTextSetup.d.ts +1 -2
  66. package/shared/useYjsCollaboration.d.ts +4 -1
@@ -18,7 +18,6 @@ import type {
18
18
  declare export function useLexical<EditorContext>(editorConfig?: {
19
19
  namespace?: string,
20
20
  onError: (error: Error) => void,
21
- initialEditorState?: EditorState,
22
21
  theme?: EditorThemeClasses,
23
22
  context?: EditorContext,
24
23
  nodes?: Array<Class<LexicalNode>>,
@@ -6,4 +6,5 @@
6
6
  *
7
7
  */
8
8
  import type { LexicalEditor } from 'lexical';
9
- export declare function useLexicalDecorators(editor: LexicalEditor): Array<JSX.Element>;
9
+ import { ErrorBoundaryType } from './shared/useDecorators';
10
+ export declare function useLexicalDecorators(editor: LexicalEditor, ErrorBoundary?: ErrorBoundaryType): Array<JSX.Element>;
@@ -6,9 +6,106 @@
6
6
  */
7
7
  'use strict';
8
8
 
9
- var react = require('react');
9
+ var React = require('react');
10
10
  var reactDom = require('react-dom');
11
11
 
12
+ /**
13
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
14
+ *
15
+ * This source code is licensed under the MIT license found in the
16
+ * LICENSE file in the root directory of this source tree.
17
+ *
18
+ */
19
+
20
+ const changedArray = (a = [], b = []) => a.length !== b.length || a.some((item, index) => !Object.is(item, b[index]));
21
+
22
+ const initialState = {
23
+ error: null
24
+ };
25
+
26
+ class ErrorBoundary extends React.Component {
27
+ constructor(props) {
28
+ super(props);
29
+ this.state = initialState;
30
+ this.resetErrorBoundary = this.resetErrorBoundary.bind(this);
31
+ }
32
+
33
+ static getDerivedStateFromError(error) {
34
+ return {
35
+ error
36
+ };
37
+ }
38
+
39
+ resetErrorBoundary(...args) {
40
+ // @ts-ignore
41
+ // eslint-disable-next-line no-unused-expressions
42
+ this.props.onReset && this.props.onReset(...args);
43
+ this.reset();
44
+ }
45
+
46
+ reset() {
47
+ this.setState(initialState);
48
+ }
49
+
50
+ componentDidCatch(error, info) {
51
+ // @ts-ignore
52
+ // eslint-disable-next-line no-unused-expressions
53
+ this.props.onError && this.props.onError(error, info);
54
+ }
55
+
56
+ componentDidUpdate(prevProps, prevState) {
57
+ const {
58
+ error
59
+ } = this.state;
60
+ const {
61
+ resetKeys
62
+ } = this.props; // There's an edge case where if the thing that triggered the error
63
+ // happens to *also* be in the resetKeys array, we'd end up resetting
64
+ // the error boundary immediately. This would likely trigger a second
65
+ // error to be thrown.
66
+ // So we make sure that we don't check the resetKeys on the first call
67
+ // of cDU after the error is set
68
+
69
+ if (error !== null && prevState.error !== null && changedArray(prevProps.resetKeys, resetKeys)) {
70
+ // @ts-ignore
71
+ // eslint-disable-next-line no-unused-expressions
72
+ this.props.onResetKeysChange && this.props.onResetKeysChange(prevProps.resetKeys, resetKeys);
73
+ this.reset();
74
+ }
75
+ }
76
+
77
+ render() {
78
+ const {
79
+ error
80
+ } = this.state;
81
+ const {
82
+ fallbackRender,
83
+ FallbackComponent,
84
+ fallback
85
+ } = this.props;
86
+
87
+ if (error !== null) {
88
+ const props = {
89
+ error,
90
+ resetErrorBoundary: this.resetErrorBoundary
91
+ };
92
+
93
+ if ( /*#__PURE__*/React.isValidElement(fallback)) {
94
+ return fallback;
95
+ } else if (typeof fallbackRender === 'function') {
96
+ return fallbackRender(props);
97
+ } else if (FallbackComponent) {
98
+ return /*#__PURE__*/React.createElement(FallbackComponent, props);
99
+ } else {
100
+ throw new Error('react-error-boundary requires either a fallback, fallbackRender, or FallbackComponent prop');
101
+ }
102
+ }
103
+
104
+ return this.props.children;
105
+ }
106
+
107
+ }
108
+
12
109
  /**
13
110
  * Copyright (c) Meta Platforms, Inc. and affiliates.
14
111
  *
@@ -25,7 +122,7 @@ const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !==
25
122
  * LICENSE file in the root directory of this source tree.
26
123
  *
27
124
  */
28
- const useLayoutEffectImpl = CAN_USE_DOM ? react.useLayoutEffect : react.useEffect;
125
+ const useLayoutEffectImpl = CAN_USE_DOM ? React.useLayoutEffect : React.useEffect;
29
126
  var useLayoutEffect = useLayoutEffectImpl;
30
127
 
31
128
  /**
@@ -35,8 +132,15 @@ var useLayoutEffect = useLayoutEffectImpl;
35
132
  * LICENSE file in the root directory of this source tree.
36
133
  *
37
134
  */
38
- function useDecorators(editor) {
39
- const [decorators, setDecorators] = react.useState(() => editor.getDecorators()); // Subscribe to changes
135
+ function useDecorators(editor, // TODO 0.6 Make non-optional non-default
136
+ ErrorBoundary$1 = ({
137
+ children,
138
+ onError
139
+ }) => /*#__PURE__*/React.createElement(ErrorBoundary, {
140
+ fallback: null,
141
+ onError: onError
142
+ }, children)) {
143
+ const [decorators, setDecorators] = React.useState(() => editor.getDecorators()); // Subscribe to changes
40
144
 
41
145
  useLayoutEffect(() => {
42
146
  return editor.registerDecoratorListener(nextDecorators => {
@@ -45,20 +149,24 @@ function useDecorators(editor) {
45
149
  });
46
150
  });
47
151
  }, [editor]);
48
- react.useEffect(() => {
152
+ React.useEffect(() => {
49
153
  // If the content editable mounts before the subscription is added, then
50
154
  // nothing will be rendered on initial pass. We can get around that by
51
155
  // ensuring that we set the value.
52
156
  setDecorators(editor.getDecorators());
53
157
  }, [editor]); // Return decorators defined as React Portals
54
158
 
55
- return react.useMemo(() => {
159
+ return React.useMemo(() => {
56
160
  const decoratedPortals = [];
57
161
  const decoratorKeys = Object.keys(decorators);
58
162
 
59
163
  for (let i = 0; i < decoratorKeys.length; i++) {
60
164
  const nodeKey = decoratorKeys[i];
61
- const reactDecorator = decorators[nodeKey];
165
+ const reactDecorator = /*#__PURE__*/React.createElement(ErrorBoundary$1, {
166
+ onError: e => editor._onError(e)
167
+ }, /*#__PURE__*/React.createElement(React.Suspense, {
168
+ fallback: null
169
+ }, decorators[nodeKey]));
62
170
  const element = editor.getElementByKey(nodeKey);
63
171
 
64
172
  if (element !== null) {
@@ -67,7 +175,7 @@ function useDecorators(editor) {
67
175
  }
68
176
 
69
177
  return decoratedPortals;
70
- }, [decorators, editor]);
178
+ }, [ErrorBoundary$1, decorators, editor]);
71
179
  }
72
180
 
73
181
  /**
@@ -77,8 +185,15 @@ function useDecorators(editor) {
77
185
  * LICENSE file in the root directory of this source tree.
78
186
  *
79
187
  */
80
- function useLexicalDecorators(editor) {
81
- return useDecorators(editor);
188
+ function useLexicalDecorators(editor, // TODO 0.6 Make non-optional non-default
189
+ ErrorBoundary$1 = ({
190
+ children,
191
+ onError
192
+ }) => /*#__PURE__*/React.createElement(ErrorBoundary, {
193
+ fallback: null,
194
+ onError: onError
195
+ }, children)) {
196
+ return useDecorators(editor, ErrorBoundary$1);
82
197
  }
83
198
 
84
199
  exports.useLexicalDecorators = useLexicalDecorators;
@@ -12,6 +12,14 @@ import type {LexicalEditor} from 'lexical';
12
12
 
13
13
  import * as React from 'react';
14
14
 
15
+ type ErrorBoundaryProps = $ReadOnly<{
16
+ children: React.Node,
17
+ onError?: (error: Error) => void,
18
+ }>;
19
+ export type ErrorBoundaryType = React.AbstractComponent<ErrorBoundaryProps>;
20
+
15
21
  declare export function useLexicalDecorators(
16
22
  editor: LexicalEditor,
23
+ // TODO 0.6 Make non-optional non-default
24
+ ErrorBoundary?: ErrorBoundaryType,
17
25
  ): Array<React.Node>;
@@ -4,5 +4,9 @@
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 b=require("react"),g=require("react-dom"),l="undefined"!==typeof window&&"undefined"!==typeof window.document&&"undefined"!==typeof window.document.createElement?b.useLayoutEffect:b.useEffect;
8
- function m(a){let [e,h]=b.useState(()=>a.getDecorators());l(()=>a.registerDecoratorListener(d=>{g.flushSync(()=>{h(d)})}),[a]);b.useEffect(()=>{h(a.getDecorators())},[a]);return b.useMemo(()=>{let d=[],k=Object.keys(e);for(let f=0;f<k.length;f++){var c=k[f];let n=e[c];c=a.getElementByKey(c);null!==c&&d.push(g.createPortal(n,c))}return d},[e,a])}exports.useLexicalDecorators=function(a){return m(a)}
7
+ 'use strict';var e=require("react"),k=require("react-dom");let l=(a=[],b=[])=>a.length!==b.length||a.some((c,d)=>!Object.is(c,b[d])),m={error:null};
8
+ class p extends e.Component{constructor(a){super(a);this.state=m;this.resetErrorBoundary=this.resetErrorBoundary.bind(this)}static getDerivedStateFromError(a){return{error:a}}resetErrorBoundary(...a){this.props.onReset&&this.props.onReset(...a);this.reset()}reset(){this.setState(m)}componentDidCatch(a,b){this.props.onError&&this.props.onError(a,b)}componentDidUpdate(a,b){let {error:c}=this.state,{resetKeys:d}=this.props;null!==c&&null!==b.error&&l(a.resetKeys,d)&&(this.props.onResetKeysChange&&this.props.onResetKeysChange(a.resetKeys,
9
+ d),this.reset())}render(){var {error:a}=this.state;let {fallbackRender:b,FallbackComponent:c,fallback:d}=this.props;if(null!==a){a={error:a,resetErrorBoundary:this.resetErrorBoundary};if(e.isValidElement(d))return d;if("function"===typeof b)return b(a);if(c)return e.createElement(c,a);throw Error("react-error-boundary requires either a fallback, fallbackRender, or FallbackComponent prop");}return this.props.children}}
10
+ var q="undefined"!==typeof window&&"undefined"!==typeof window.document&&"undefined"!==typeof window.document.createElement?e.useLayoutEffect:e.useEffect;
11
+ function r(a,b=({children:c,onError:d})=>e.createElement(p,{fallback:null,onError:d},c)){let [c,d]=e.useState(()=>a.getDecorators());q(()=>a.registerDecoratorListener(g=>{k.flushSync(()=>{d(g)})}),[a]);e.useEffect(()=>{d(a.getDecorators())},[a]);return e.useMemo(()=>{let g=[],n=Object.keys(c);for(let h=0;h<n.length;h++){var f=n[h];let u=e.createElement(b,{onError:t=>a._onError(t)},e.createElement(e.Suspense,{fallback:null},c[f]));f=a.getElementByKey(f);null!==f&&g.push(k.createPortal(u,f))}return g},
12
+ [b,c,a])}exports.useLexicalDecorators=function(a,b=({children:c,onError:d})=>e.createElement(p,{fallback:null,onError:d},c)){return r(a,b)}
@@ -6,5 +6,5 @@
6
6
  *
7
7
  */
8
8
  import type { HistoryState } from './DEPRECATED_useLexicalHistory';
9
- import type { EditorState, LexicalEditor } from 'lexical';
10
- export declare function useLexicalPlainText(editor: LexicalEditor, externalHistoryState?: HistoryState, initialEditorState?: null | string | EditorState | (() => void)): void;
9
+ import type { LexicalEditor } from 'lexical';
10
+ export declare function useLexicalPlainText(editor: LexicalEditor, externalHistoryState?: HistoryState): void;
@@ -63,9 +63,9 @@ var useLayoutEffect = useLayoutEffectImpl;
63
63
  * LICENSE file in the root directory of this source tree.
64
64
  *
65
65
  */
66
- function usePlainTextSetup(editor, initialEditorState) {
66
+ function usePlainTextSetup(editor) {
67
67
  useLayoutEffect(() => {
68
- return utils.mergeRegister(plainText.registerPlainText(editor, initialEditorState), dragon.registerDragonSupport(editor)); // We only do this for init
68
+ return utils.mergeRegister(plainText.registerPlainText(editor), dragon.registerDragonSupport(editor)); // We only do this for init
69
69
  // eslint-disable-next-line react-hooks/exhaustive-deps
70
70
  }, [editor]);
71
71
  }
@@ -77,8 +77,8 @@ function usePlainTextSetup(editor, initialEditorState) {
77
77
  * LICENSE file in the root directory of this source tree.
78
78
  *
79
79
  */
80
- function useLexicalPlainText(editor, externalHistoryState, initialEditorState) {
81
- usePlainTextSetup(editor, initialEditorState);
80
+ function useLexicalPlainText(editor, externalHistoryState) {
81
+ usePlainTextSetup(editor);
82
82
  useLexicalHistory(editor, externalHistoryState);
83
83
  }
84
84
 
@@ -14,5 +14,4 @@ import type {EditorState, LexicalEditor} from 'lexical';
14
14
  declare export function useLexicalPlainText(
15
15
  editor: LexicalEditor,
16
16
  externalHistoryState?: HistoryState,
17
- initialEditorState?: null | string | EditorState | (() => void),
18
17
  ): void;
@@ -4,5 +4,5 @@
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 history=require("@lexical/history"),d=require("react"),f=require("@lexical/dragon"),g=require("@lexical/plain-text"),h=require("@lexical/utils");function k(a,b,c=1E3){let e=d.useMemo(()=>b||history.createEmptyHistoryState(),[b]);d.useEffect(()=>history.registerHistory(a,e,c),[c,a,e])}function l(a,b,c=1E3){return k(a,b,c)}var m="undefined"!==typeof window&&"undefined"!==typeof window.document&&"undefined"!==typeof window.document.createElement?d.useLayoutEffect:d.useEffect;
8
- function n(a,b){m(()=>h.mergeRegister(g.registerPlainText(a,b),f.registerDragonSupport(a)),[a])}exports.useLexicalPlainText=function(a,b,c){n(a,c);l(a,b)}
7
+ 'use strict';var history=require("@lexical/history"),c=require("react"),f=require("@lexical/dragon"),g=require("@lexical/plain-text"),h=require("@lexical/utils");function k(a,b,d=1E3){let e=c.useMemo(()=>b||history.createEmptyHistoryState(),[b]);c.useEffect(()=>history.registerHistory(a,e,d),[d,a,e])}function l(a,b,d=1E3){return k(a,b,d)}var m="undefined"!==typeof window&&"undefined"!==typeof window.document&&"undefined"!==typeof window.document.createElement?c.useLayoutEffect:c.useEffect;
8
+ function n(a){m(()=>h.mergeRegister(g.registerPlainText(a),f.registerDragonSupport(a)),[a])}exports.useLexicalPlainText=function(a,b){n(a);l(a,b)}
@@ -6,5 +6,5 @@
6
6
  *
7
7
  */
8
8
  import type { HistoryState } from './DEPRECATED_useLexicalHistory';
9
- import type { EditorState, LexicalEditor } from 'lexical';
10
- export declare function useLexicalRichText(editor: LexicalEditor, externalHistoryState?: HistoryState, initialEditorState?: null | string | EditorState | (() => void)): void;
9
+ import type { LexicalEditor } from 'lexical';
10
+ export declare function useLexicalRichText(editor: LexicalEditor, externalHistoryState?: HistoryState): void;
@@ -63,9 +63,9 @@ var useLayoutEffect = useLayoutEffectImpl;
63
63
  * LICENSE file in the root directory of this source tree.
64
64
  *
65
65
  */
66
- function useRichTextSetup(editor, initialEditorState) {
66
+ function useRichTextSetup(editor) {
67
67
  useLayoutEffect(() => {
68
- return utils.mergeRegister(richText.registerRichText(editor, initialEditorState), dragon.registerDragonSupport(editor)); // We only do this for init
68
+ return utils.mergeRegister(richText.registerRichText(editor), dragon.registerDragonSupport(editor)); // We only do this for init
69
69
  // eslint-disable-next-line react-hooks/exhaustive-deps
70
70
  }, [editor]);
71
71
  }
@@ -77,8 +77,8 @@ function useRichTextSetup(editor, initialEditorState) {
77
77
  * LICENSE file in the root directory of this source tree.
78
78
  *
79
79
  */
80
- function useLexicalRichText(editor, externalHistoryState, initialEditorState) {
81
- useRichTextSetup(editor, initialEditorState);
80
+ function useLexicalRichText(editor, externalHistoryState) {
81
+ useRichTextSetup(editor);
82
82
  useLexicalHistory(editor, externalHistoryState);
83
83
  }
84
84
 
@@ -14,5 +14,4 @@ import type {EditorState, LexicalEditor} from 'lexical';
14
14
  declare export function useLexicalRichText(
15
15
  editor: LexicalEditor,
16
16
  externalHistoryState?: HistoryState,
17
- initialEditorState?: null | string | EditorState | (() => void),
18
17
  ): void;
@@ -4,5 +4,5 @@
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 history=require("@lexical/history"),d=require("react"),f=require("@lexical/dragon"),g=require("@lexical/rich-text"),h=require("@lexical/utils");function k(a,b,c=1E3){let e=d.useMemo(()=>b||history.createEmptyHistoryState(),[b]);d.useEffect(()=>history.registerHistory(a,e,c),[c,a,e])}function l(a,b,c=1E3){return k(a,b,c)}var m="undefined"!==typeof window&&"undefined"!==typeof window.document&&"undefined"!==typeof window.document.createElement?d.useLayoutEffect:d.useEffect;
8
- function n(a,b){m(()=>h.mergeRegister(g.registerRichText(a,b),f.registerDragonSupport(a)),[a])}exports.useLexicalRichText=function(a,b,c){n(a,c);l(a,b)}
7
+ 'use strict';var history=require("@lexical/history"),c=require("react"),f=require("@lexical/dragon"),g=require("@lexical/rich-text"),h=require("@lexical/utils");function k(a,b,d=1E3){let e=c.useMemo(()=>b||history.createEmptyHistoryState(),[b]);c.useEffect(()=>history.registerHistory(a,e,d),[d,a,e])}function l(a,b,d=1E3){return k(a,b,d)}var m="undefined"!==typeof window&&"undefined"!==typeof window.document&&"undefined"!==typeof window.document.createElement?c.useLayoutEffect:c.useEffect;
8
+ function n(a){m(()=>h.mergeRegister(g.registerRichText(a),f.registerDragonSupport(a)),[a])}exports.useLexicalRichText=function(a,b){n(a);l(a,b)}
@@ -6,9 +6,8 @@
6
6
  *
7
7
  */
8
8
  import type { LexicalNode } from 'lexical';
9
- import { TypeaheadOption } from '@lexical/react/LexicalTypeaheadMenuPlugin';
9
+ import { MenuRenderFn, TypeaheadOption } from '@lexical/react/LexicalTypeaheadMenuPlugin';
10
10
  import { LexicalCommand, LexicalEditor } from 'lexical';
11
- import * as React from 'react';
12
11
  export declare type EmbedMatchResult = {
13
12
  url: string;
14
13
  id: string;
@@ -20,27 +19,18 @@ export interface EmbedConfig {
20
19
  }
21
20
  export declare const URL_MATCHER: RegExp;
22
21
  export declare const INSERT_EMBED_COMMAND: LexicalCommand<EmbedConfig['type']>;
23
- export declare type EmbedMenuProps = {
24
- selectedItemIndex: number | null;
25
- onOptionClick: (option: AutoEmbedOption, index: number) => void;
26
- onOptionMouseEnter: (index: number) => void;
27
- options: Array<AutoEmbedOption>;
28
- };
29
- export declare type EmbedMenuComponent = React.ComponentType<EmbedMenuProps>;
30
22
  export declare class AutoEmbedOption extends TypeaheadOption {
31
23
  title: string;
32
- icon?: JSX.Element;
33
24
  onSelect: (targetNode: LexicalNode | null) => void;
34
25
  constructor(title: string, options: {
35
- icon?: JSX.Element;
36
26
  onSelect: (targetNode: LexicalNode | null) => void;
37
27
  });
38
28
  }
39
29
  declare type LexicalAutoEmbedPluginProps<TEmbedConfig extends EmbedConfig> = {
40
30
  embedConfigs: Array<TEmbedConfig>;
41
31
  onOpenEmbedModalForConfig: (embedConfig: TEmbedConfig) => void;
42
- menuComponent: EmbedMenuComponent;
43
32
  getMenuOptions: (activeEmbedConfig: TEmbedConfig, embedFn: () => void, dismissFn: () => void) => Array<AutoEmbedOption>;
33
+ menuRenderFn: MenuRenderFn<AutoEmbedOption>;
44
34
  };
45
- export declare function LexicalAutoEmbedPlugin<TEmbedConfig extends EmbedConfig>({ embedConfigs, onOpenEmbedModalForConfig, getMenuOptions, menuComponent: MenuComponent, }: LexicalAutoEmbedPluginProps<TEmbedConfig>): JSX.Element | null;
35
+ export declare function LexicalAutoEmbedPlugin<TEmbedConfig extends EmbedConfig>({ embedConfigs, onOpenEmbedModalForConfig, getMenuOptions, menuRenderFn, }: LexicalAutoEmbedPluginProps<TEmbedConfig>): JSX.Element | null;
46
36
  export {};
@@ -12,7 +12,6 @@ var LexicalTypeaheadMenuPlugin = require('@lexical/react/LexicalTypeaheadMenuPlu
12
12
  var utils = require('@lexical/utils');
13
13
  var lexical = require('lexical');
14
14
  var React = require('react');
15
- var ReactDOM = require('react-dom');
16
15
 
17
16
  /**
18
17
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -22,12 +21,11 @@ var ReactDOM = require('react-dom');
22
21
  *
23
22
  */
24
23
  const URL_MATCHER = /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
25
- const INSERT_EMBED_COMMAND = lexical.createCommand();
24
+ const INSERT_EMBED_COMMAND = lexical.createCommand('INSERT_EMBED_COMMAND');
26
25
  class AutoEmbedOption extends LexicalTypeaheadMenuPlugin.TypeaheadOption {
27
26
  constructor(title, options) {
28
27
  super(title);
29
28
  this.title = title;
30
- this.icon = options.icon;
31
29
  this.onSelect = options.onSelect.bind(this);
32
30
  }
33
31
 
@@ -36,7 +34,7 @@ function LexicalAutoEmbedPlugin({
36
34
  embedConfigs,
37
35
  onOpenEmbedModalForConfig,
38
36
  getMenuOptions,
39
- menuComponent: MenuComponent
37
+ menuRenderFn
40
38
  }) {
41
39
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
42
40
  const [nodeKey, setNodeKey] = React.useState(null);
@@ -132,21 +130,7 @@ function LexicalAutoEmbedPlugin({
132
130
  onClose: reset,
133
131
  onSelectOption: onSelectOption,
134
132
  options: options,
135
- menuRenderFn: (anchorElement, {
136
- selectedIndex,
137
- selectOptionAndCleanUp,
138
- setHighlightedIndex
139
- }) => anchorElement && nodeKey != null ? /*#__PURE__*/ReactDOM.createPortal( /*#__PURE__*/React.createElement(MenuComponent, {
140
- options: options,
141
- selectedItemIndex: selectedIndex,
142
- onOptionClick: (option, index) => {
143
- setHighlightedIndex(index);
144
- selectOptionAndCleanUp(option);
145
- },
146
- onOptionMouseEnter: index => {
147
- setHighlightedIndex(index);
148
- }
149
- }), anchorElement) : null
133
+ menuRenderFn: menuRenderFn
150
134
  }) : null;
151
135
  }
152
136
 
@@ -13,6 +13,7 @@ import {TypeaheadOption} from '@lexical/react/LexicalTypeaheadMenuPlugin';
13
13
  import type {LexicalCommand, LexicalEditor, NodeKey, TextNode} from 'lexical';
14
14
  import * as React from 'react';
15
15
  import {createCommand} from 'lexical';
16
+ import type {MenuRenderFn} from './LexicalTypeaheadMenuPlugin';
16
17
 
17
18
  export type EmbedMatchResult = {
18
19
  url: string,
@@ -32,26 +33,17 @@ export const URL_MATCHER: RegExp =
32
33
  /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
33
34
 
34
35
  export const INSERT_EMBED_COMMAND: LexicalCommand<EmbedConfig['type']> =
35
- createCommand();
36
-
37
- export type EmbedMenuProps = {
38
- selectedItemIndex: number | null,
39
- onOptionClick: (option: AutoEmbedOption, index: number) => void,
40
- onOptionMouseEnter: (index: number) => void,
41
- options: Array<AutoEmbedOption>,
42
- };
43
-
44
- export type EmbedMenuComponent = React.ComponentType<EmbedMenuProps>;
36
+ createCommand('INSERT_EMBED_COMMAND');
45
37
 
46
38
  type LexicalAutoEmbedPluginProps<TEmbedConfig> = {
47
39
  embedConfigs: Array<TEmbedConfig>,
48
40
  onOpenEmbedModalForConfig: (embedConfig: TEmbedConfig) => void,
49
- menuComponent: EmbedMenuComponent,
50
41
  getMenuOptions: (
51
42
  activeEmbedConfig: TEmbedConfig,
52
43
  embedFn: () => void,
53
44
  dismissFn: () => void,
54
45
  ) => Array<AutoEmbedOption>,
46
+ menuRenderFn: MenuRenderFn<AutoEmbedOption>,
55
47
  };
56
48
 
57
49
  declare export class AutoEmbedOption extends TypeaheadOption {
@@ -4,8 +4,7 @@
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 e=require("@lexical/link"),m=require("@lexical/react/LexicalComposerContext"),q=require("@lexical/react/LexicalTypeaheadMenuPlugin"),t=require("@lexical/utils"),u=require("lexical"),B=require("react"),C=require("react-dom");let D=u.createCommand();class E extends q.TypeaheadOption{constructor(g,n){super(g);this.title=g;this.icon=n.icon;this.onSelect=n.onSelect.bind(this)}}exports.AutoEmbedOption=E;exports.INSERT_EMBED_COMMAND=D;
8
- exports.LexicalAutoEmbedPlugin=function({embedConfigs:g,onOpenEmbedModalForConfig:n,getMenuOptions:v,menuComponent:F}){let [c]=m.useLexicalComposerContext(),[f,w]=B.useState(null),[h,x]=B.useState(null),p=B.useCallback(()=>{w(null);x(null)},[]),y=B.useCallback(b=>{c.getEditorState().read(()=>{const a=u.$getNodeByKey(b);if(e.$isLinkNode(a)){const d=g.find(k=>k.parseUrl(a.__url));null!=d&&(x(d),w(a.getKey()))}})},[c,g]);B.useEffect(()=>{let b=(a,{updateTags:d,dirtyLeaves:k})=>{for(const [l,r]of a)"created"===
9
- r&&d.has("paste")&&1===k.size?y(l):l===f&&p()};return t.mergeRegister(...[e.LinkNode,e.AutoLinkNode].map(a=>c.registerMutationListener(a,(...d)=>b(...d))))},[y,c,g,f,p]);B.useEffect(()=>c.registerCommand(D,b=>{let a=g.find(({type:d})=>d===b);return a?(n(a),!0):!1},u.COMMAND_PRIORITY_EDITOR),[c,g,n]);let z=B.useCallback(()=>{if(null!=h&&null!=f){const b=c.getEditorState().read(()=>{const a=u.$getNodeByKey(f);return e.$isLinkNode(a)?a:null});if(e.$isLinkNode(b)){const a=h.parseUrl(b.__url);null!=a&&
10
- (c.update(()=>{h.insertNode(c,a)}),b.isAttached()&&c.update(()=>{b.remove()}))}}},[h,c,f]),A=B.useMemo(()=>null!=h&&null!=f?v(h,z,p):[],[h,z,v,f,p]),G=B.useCallback((b,a,d)=>{c.update(()=>{b.onSelect(a);d()})},[c]);return null!=f?B.createElement(q.LexicalNodeMenuPlugin,{nodeKey:f,onClose:p,onSelectOption:G,options:A,menuRenderFn:(b,{selectedIndex:a,selectOptionAndCleanUp:d,setHighlightedIndex:k})=>b&&null!=f?C.createPortal(B.createElement(F,{options:A,selectedItemIndex:a,onOptionClick:(l,r)=>{k(r);
11
- d(l)},onOptionMouseEnter:l=>{k(l)}}),b):null}):null};exports.URL_MATCHER=/((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/
7
+ 'use strict';var d=require("@lexical/link"),k=require("@lexical/react/LexicalComposerContext"),m=require("@lexical/react/LexicalTypeaheadMenuPlugin"),q=require("@lexical/utils"),r=require("lexical"),z=require("react");let A=r.createCommand("INSERT_EMBED_COMMAND");class B extends m.TypeaheadOption{constructor(g,n){super(g);this.title=g;this.onSelect=n.onSelect.bind(this)}}exports.AutoEmbedOption=B;exports.INSERT_EMBED_COMMAND=A;
8
+ exports.LexicalAutoEmbedPlugin=function({embedConfigs:g,onOpenEmbedModalForConfig:n,getMenuOptions:t,menuRenderFn:C}){let [b]=k.useLexicalComposerContext(),[f,u]=z.useState(null),[h,v]=z.useState(null),l=z.useCallback(()=>{u(null);v(null)},[]),w=z.useCallback(c=>{b.getEditorState().read(()=>{const a=r.$getNodeByKey(c);if(d.$isLinkNode(a)){const e=g.find(p=>p.parseUrl(a.__url));null!=e&&(v(e),u(a.getKey()))}})},[b,g]);z.useEffect(()=>{let c=(a,{updateTags:e,dirtyLeaves:p})=>{for(const [x,D]of a)"created"===
9
+ D&&e.has("paste")&&1===p.size?w(x):x===f&&l()};return q.mergeRegister(...[d.LinkNode,d.AutoLinkNode].map(a=>b.registerMutationListener(a,(...e)=>c(...e))))},[w,b,g,f,l]);z.useEffect(()=>b.registerCommand(A,c=>{let a=g.find(({type:e})=>e===c);return a?(n(a),!0):!1},r.COMMAND_PRIORITY_EDITOR),[b,g,n]);let y=z.useCallback(()=>{if(null!=h&&null!=f){const c=b.getEditorState().read(()=>{const a=r.$getNodeByKey(f);return d.$isLinkNode(a)?a:null});if(d.$isLinkNode(c)){const a=h.parseUrl(c.__url);null!=a&&
10
+ (b.update(()=>{h.insertNode(b,a)}),c.isAttached()&&b.update(()=>{c.remove()}))}}},[h,b,f]),E=z.useMemo(()=>null!=h&&null!=f?t(h,y,l):[],[h,y,t,f,l]),F=z.useCallback((c,a,e)=>{b.update(()=>{c.onSelect(a);e()})},[b]);return null!=f?z.createElement(m.LexicalNodeMenuPlugin,{nodeKey:f,onClose:l,onSelectOption:F,options:E,menuRenderFn:C}):null};exports.URL_MATCHER=/((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/
@@ -32,6 +32,20 @@ function findFirstMatch(text, matchers) {
32
32
  return null;
33
33
  }
34
34
 
35
+ const PUNCTUATION_OR_SPACE = /[.,;\s]/;
36
+
37
+ function isSeparator(char) {
38
+ return PUNCTUATION_OR_SPACE.test(char);
39
+ }
40
+
41
+ function endsWithSeparator(textContent) {
42
+ return isSeparator(textContent[textContent.length - 1]);
43
+ }
44
+
45
+ function startsWithSeparator(textContent) {
46
+ return isSeparator(textContent[0]);
47
+ }
48
+
35
49
  function isPreviousNodeValid(node) {
36
50
  let previousNode = node.getPreviousSibling();
37
51
 
@@ -39,7 +53,7 @@ function isPreviousNodeValid(node) {
39
53
  previousNode = previousNode.getLastDescendant();
40
54
  }
41
55
 
42
- return previousNode === null || lexical.$isLineBreakNode(previousNode) || lexical.$isTextNode(previousNode) && previousNode.getTextContent().endsWith(' ');
56
+ return previousNode === null || lexical.$isLineBreakNode(previousNode) || lexical.$isTextNode(previousNode) && endsWithSeparator(previousNode.getTextContent());
43
57
  }
44
58
 
45
59
  function isNextNodeValid(node) {
@@ -49,63 +63,55 @@ function isNextNodeValid(node) {
49
63
  nextNode = nextNode.getFirstDescendant();
50
64
  }
51
65
 
52
- return nextNode === null || lexical.$isLineBreakNode(nextNode) || lexical.$isTextNode(nextNode) && nextNode.getTextContent().startsWith(' ');
66
+ return nextNode === null || lexical.$isLineBreakNode(nextNode) || lexical.$isTextNode(nextNode) && startsWithSeparator(nextNode.getTextContent());
67
+ }
68
+
69
+ function isContentAroundIsValid(matchStart, matchEnd, text, node) {
70
+ const contentBeforeIsValid = matchStart > 0 ? isSeparator(text[matchStart - 1]) : isPreviousNodeValid(node);
71
+
72
+ if (!contentBeforeIsValid) {
73
+ return false;
74
+ }
75
+
76
+ const contentAfterIsValid = matchEnd < text.length ? isSeparator(text[matchEnd]) : isNextNodeValid(node);
77
+ return contentAfterIsValid;
53
78
  }
54
79
 
55
80
  function handleLinkCreation(node, matchers, onChange) {
56
81
  const nodeText = node.getTextContent();
57
- const nodeTextLength = nodeText.length;
58
82
  let text = nodeText;
59
- let textOffset = 0;
60
- let lastNode = node;
83
+ let invalidMatchEnd = 0;
84
+ let remainingTextNode = node;
61
85
  let match;
62
86
 
63
87
  while ((match = findFirstMatch(text, matchers)) && match !== null) {
64
- const matchOffset = match.index;
65
- const offset = textOffset + matchOffset;
66
- const matchLength = match.length; // Previous node is valid if any of:
67
- // 1. Space before same node
68
- // 2. Space in previous simple text node
69
- // 3. Previous node is LineBreakNode
70
-
71
- let contentBeforeMatchIsValid;
72
-
73
- if (offset > 0) {
74
- contentBeforeMatchIsValid = nodeText[offset - 1] === ' ';
75
- } else {
76
- contentBeforeMatchIsValid = isPreviousNodeValid(node);
77
- } // Next node is valid if any of:
78
- // 1. Space after same node
79
- // 2. Space in next simple text node
80
- // 3. Next node is LineBreakNode
81
-
88
+ const matchStart = match.index;
89
+ const matchLength = match.length;
90
+ const matchEnd = matchStart + matchLength;
91
+ const isValid = isContentAroundIsValid(invalidMatchEnd + matchStart, invalidMatchEnd + matchEnd, nodeText, node);
82
92
 
83
- let contentAfterMatchIsValid;
93
+ if (isValid) {
94
+ let linkTextNode;
84
95
 
85
- if (offset + matchLength < nodeTextLength) {
86
- contentAfterMatchIsValid = nodeText[offset + matchLength] === ' ';
87
- } else {
88
- contentAfterMatchIsValid = isNextNodeValid(node);
89
- }
90
-
91
- if (contentBeforeMatchIsValid && contentAfterMatchIsValid) {
92
- let middleNode;
93
-
94
- if (matchOffset === 0) {
95
- [middleNode, lastNode] = lastNode.splitText(matchLength);
96
+ if (invalidMatchEnd + matchStart === 0) {
97
+ [linkTextNode, remainingTextNode] = remainingTextNode.splitText(invalidMatchEnd + matchLength);
96
98
  } else {
97
- [, middleNode, lastNode] = lastNode.splitText(matchOffset, matchOffset + matchLength);
99
+ [, linkTextNode, remainingTextNode] = remainingTextNode.splitText(invalidMatchEnd + matchStart, invalidMatchEnd + matchStart + matchLength);
98
100
  }
99
101
 
100
102
  const linkNode = link.$createAutoLinkNode(match.url);
101
- linkNode.append(lexical.$createTextNode(match.text));
102
- middleNode.replace(linkNode);
103
+ const textNode = lexical.$createTextNode(match.text);
104
+ textNode.setFormat(linkTextNode.getFormat());
105
+ textNode.setDetail(linkTextNode.getDetail());
106
+ linkNode.append(textNode);
107
+ linkTextNode.replace(linkNode);
103
108
  onChange(match.url, null);
109
+ invalidMatchEnd = 0;
110
+ } else {
111
+ invalidMatchEnd += matchEnd;
104
112
  }
105
113
 
106
- const iterationOffset = matchOffset + matchLength;
107
- text = text.substring(iterationOffset);
108
- textOffset += iterationOffset;
114
+ text = text.substring(matchEnd);
109
115
  }
110
116
  }
111
117
 
@@ -156,12 +162,12 @@ function handleBadNeighbors(textNode, onChange) {
156
162
  const nextSibling = textNode.getNextSibling();
157
163
  const text = textNode.getTextContent();
158
164
 
159
- if (link.$isAutoLinkNode(previousSibling) && !text.startsWith(' ')) {
165
+ if (link.$isAutoLinkNode(previousSibling) && !startsWithSeparator(text)) {
160
166
  replaceWithChildren(previousSibling);
161
167
  onChange(null, previousSibling.getURL());
162
168
  }
163
169
 
164
- if (link.$isAutoLinkNode(nextSibling) && !text.endsWith(' ')) {
170
+ if (link.$isAutoLinkNode(nextSibling) && !endsWithSeparator(text)) {
165
171
  replaceWithChildren(nextSibling);
166
172
  onChange(null, nextSibling.getURL());
167
173
  }
@@ -205,8 +211,6 @@ function useAutoLink(editor, matchers, onChange) {
205
211
 
206
212
  handleBadNeighbors(textNode, onChangeWrapped);
207
213
  }
208
- }), editor.registerNodeTransform(link.AutoLinkNode, linkNode => {
209
- handleLinkEdit(linkNode, matchers, onChangeWrapped);
210
214
  }));
211
215
  }, [editor, matchers, onChange]);
212
216
  }