@tiptap/react 2.5.8 → 2.6.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.
package/dist/index.js CHANGED
@@ -5,138 +5,6 @@ import { Editor as Editor$1, NodeView } from '@tiptap/core';
5
5
  export * from '@tiptap/core';
6
6
  import { FloatingMenuPlugin } from '@tiptap/extension-floating-menu';
7
7
 
8
- const mergeRefs = (...refs) => {
9
- return (node) => {
10
- refs.forEach(ref => {
11
- if (typeof ref === 'function') {
12
- ref(node);
13
- }
14
- else if (ref) {
15
- ref.current = node;
16
- }
17
- });
18
- };
19
- };
20
- const Portals = ({ renderers }) => {
21
- return (React.createElement(React.Fragment, null, Object.entries(renderers).map(([key, renderer]) => {
22
- return ReactDOM.createPortal(renderer.reactElement, renderer.element, key);
23
- })));
24
- };
25
- class PureEditorContent extends React.Component {
26
- constructor(props) {
27
- super(props);
28
- this.editorContentRef = React.createRef();
29
- this.initialized = false;
30
- this.state = {
31
- renderers: {},
32
- };
33
- }
34
- componentDidMount() {
35
- this.init();
36
- }
37
- componentDidUpdate() {
38
- this.init();
39
- }
40
- init() {
41
- const { editor } = this.props;
42
- if (editor && !editor.isDestroyed && editor.options.element) {
43
- if (editor.contentComponent) {
44
- return;
45
- }
46
- const element = this.editorContentRef.current;
47
- element.append(...editor.options.element.childNodes);
48
- editor.setOptions({
49
- element,
50
- });
51
- editor.contentComponent = this;
52
- editor.createNodeViews();
53
- this.initialized = true;
54
- }
55
- }
56
- maybeFlushSync(fn) {
57
- // Avoid calling flushSync until the editor is initialized.
58
- // Initialization happens during the componentDidMount or componentDidUpdate
59
- // lifecycle methods, and React doesn't allow calling flushSync from inside
60
- // a lifecycle method.
61
- if (this.initialized) {
62
- flushSync(fn);
63
- }
64
- else {
65
- fn();
66
- }
67
- }
68
- setRenderer(id, renderer) {
69
- this.maybeFlushSync(() => {
70
- this.setState(({ renderers }) => ({
71
- renderers: {
72
- ...renderers,
73
- [id]: renderer,
74
- },
75
- }));
76
- });
77
- }
78
- removeRenderer(id) {
79
- this.maybeFlushSync(() => {
80
- this.setState(({ renderers }) => {
81
- const nextRenderers = { ...renderers };
82
- delete nextRenderers[id];
83
- return { renderers: nextRenderers };
84
- });
85
- });
86
- }
87
- componentWillUnmount() {
88
- const { editor } = this.props;
89
- if (!editor) {
90
- return;
91
- }
92
- this.initialized = false;
93
- if (!editor.isDestroyed) {
94
- editor.view.setProps({
95
- nodeViews: {},
96
- });
97
- }
98
- editor.contentComponent = null;
99
- if (!editor.options.element.firstChild) {
100
- return;
101
- }
102
- const newElement = document.createElement('div');
103
- newElement.append(...editor.options.element.childNodes);
104
- editor.setOptions({
105
- element: newElement,
106
- });
107
- }
108
- render() {
109
- const { editor, innerRef, ...rest } = this.props;
110
- return (React.createElement(React.Fragment, null,
111
- React.createElement("div", { ref: mergeRefs(innerRef, this.editorContentRef), ...rest }),
112
- React.createElement(Portals, { renderers: this.state.renderers })));
113
- }
114
- }
115
- // EditorContent should be re-created whenever the Editor instance changes
116
- const EditorContentWithKey = forwardRef((props, ref) => {
117
- const key = React.useMemo(() => {
118
- return Math.floor(Math.random() * 0xFFFFFFFF).toString();
119
- }, [props.editor]);
120
- // Can't use JSX here because it conflicts with the type definition of Vue's JSX, so use createElement
121
- return React.createElement(PureEditorContent, {
122
- key,
123
- innerRef: ref,
124
- ...props,
125
- });
126
- });
127
- const EditorContent = React.memo(EditorContentWithKey);
128
-
129
- class Editor extends Editor$1 {
130
- constructor() {
131
- super(...arguments);
132
- this.contentComponent = null;
133
- }
134
- }
135
-
136
- var withSelector = {exports: {}};
137
-
138
- var withSelector_production_min = {};
139
-
140
8
  var shim = {exports: {}};
141
9
 
142
10
  var useSyncExternalStoreShim_production_min = {};
@@ -407,19 +275,173 @@ function requireUseSyncExternalStoreShim_development () {
407
275
  return useSyncExternalStoreShim_development;
408
276
  }
409
277
 
410
- var hasRequiredShim;
278
+ if (process.env.NODE_ENV === 'production') {
279
+ shim.exports = requireUseSyncExternalStoreShim_production_min();
280
+ } else {
281
+ shim.exports = requireUseSyncExternalStoreShim_development();
282
+ }
411
283
 
412
- function requireShim () {
413
- if (hasRequiredShim) return shim.exports;
414
- hasRequiredShim = 1;
284
+ var shimExports = shim.exports;
415
285
 
416
- if (process.env.NODE_ENV === 'production') {
417
- shim.exports = requireUseSyncExternalStoreShim_production_min();
418
- } else {
419
- shim.exports = requireUseSyncExternalStoreShim_development();
420
- }
421
- return shim.exports;
286
+ const mergeRefs = (...refs) => {
287
+ return (node) => {
288
+ refs.forEach(ref => {
289
+ if (typeof ref === 'function') {
290
+ ref(node);
291
+ }
292
+ else if (ref) {
293
+ ref.current = node;
294
+ }
295
+ });
296
+ };
297
+ };
298
+ /**
299
+ * This component renders all of the editor's node views.
300
+ */
301
+ const Portals = ({ contentComponent, }) => {
302
+ // For performance reasons, we render the node view portals on state changes only
303
+ const renderers = shimExports.useSyncExternalStore(contentComponent.subscribe, contentComponent.getSnapshot, contentComponent.getServerSnapshot);
304
+ // This allows us to directly render the portals without any additional wrapper
305
+ return (React.createElement(React.Fragment, null, Object.values(renderers)));
306
+ };
307
+ function getInstance() {
308
+ const subscribers = new Set();
309
+ let renderers = {};
310
+ return {
311
+ /**
312
+ * Subscribe to the editor instance's changes.
313
+ */
314
+ subscribe(callback) {
315
+ subscribers.add(callback);
316
+ return () => {
317
+ subscribers.delete(callback);
318
+ };
319
+ },
320
+ getSnapshot() {
321
+ return renderers;
322
+ },
323
+ getServerSnapshot() {
324
+ return renderers;
325
+ },
326
+ /**
327
+ * Adds a new NodeView Renderer to the editor.
328
+ */
329
+ setRenderer(id, renderer) {
330
+ renderers = {
331
+ ...renderers,
332
+ [id]: ReactDOM.createPortal(renderer.reactElement, renderer.element, id),
333
+ };
334
+ subscribers.forEach(subscriber => subscriber());
335
+ },
336
+ /**
337
+ * Removes a NodeView Renderer from the editor.
338
+ */
339
+ removeRenderer(id) {
340
+ const nextRenderers = { ...renderers };
341
+ delete nextRenderers[id];
342
+ renderers = nextRenderers;
343
+ subscribers.forEach(subscriber => subscriber());
344
+ },
345
+ };
422
346
  }
347
+ class PureEditorContent extends React.Component {
348
+ constructor(props) {
349
+ var _a;
350
+ super(props);
351
+ this.editorContentRef = React.createRef();
352
+ this.initialized = false;
353
+ this.state = {
354
+ hasContentComponentInitialized: Boolean((_a = props.editor) === null || _a === void 0 ? void 0 : _a.contentComponent),
355
+ };
356
+ }
357
+ componentDidMount() {
358
+ this.init();
359
+ }
360
+ componentDidUpdate() {
361
+ this.init();
362
+ }
363
+ init() {
364
+ const { editor } = this.props;
365
+ if (editor && !editor.isDestroyed && editor.options.element) {
366
+ if (editor.contentComponent) {
367
+ return;
368
+ }
369
+ const element = this.editorContentRef.current;
370
+ element.append(...editor.options.element.childNodes);
371
+ editor.setOptions({
372
+ element,
373
+ });
374
+ editor.contentComponent = getInstance();
375
+ // Has the content component been initialized?
376
+ if (!this.state.hasContentComponentInitialized) {
377
+ // Subscribe to the content component
378
+ this.unsubscribeToContentComponent = editor.contentComponent.subscribe(() => {
379
+ this.setState(prevState => {
380
+ if (!prevState.hasContentComponentInitialized) {
381
+ return {
382
+ hasContentComponentInitialized: true,
383
+ };
384
+ }
385
+ return prevState;
386
+ });
387
+ // Unsubscribe to previous content component
388
+ if (this.unsubscribeToContentComponent) {
389
+ this.unsubscribeToContentComponent();
390
+ }
391
+ });
392
+ }
393
+ editor.createNodeViews();
394
+ this.initialized = true;
395
+ }
396
+ }
397
+ componentWillUnmount() {
398
+ const { editor } = this.props;
399
+ if (!editor) {
400
+ return;
401
+ }
402
+ this.initialized = false;
403
+ if (!editor.isDestroyed) {
404
+ editor.view.setProps({
405
+ nodeViews: {},
406
+ });
407
+ }
408
+ if (this.unsubscribeToContentComponent) {
409
+ this.unsubscribeToContentComponent();
410
+ }
411
+ editor.contentComponent = null;
412
+ if (!editor.options.element.firstChild) {
413
+ return;
414
+ }
415
+ const newElement = document.createElement('div');
416
+ newElement.append(...editor.options.element.childNodes);
417
+ editor.setOptions({
418
+ element: newElement,
419
+ });
420
+ }
421
+ render() {
422
+ const { editor, innerRef, ...rest } = this.props;
423
+ return (React.createElement(React.Fragment, null,
424
+ React.createElement("div", { ref: mergeRefs(innerRef, this.editorContentRef), ...rest }),
425
+ (editor === null || editor === void 0 ? void 0 : editor.contentComponent) && React.createElement(Portals, { contentComponent: editor.contentComponent })));
426
+ }
427
+ }
428
+ // EditorContent should be re-created whenever the Editor instance changes
429
+ const EditorContentWithKey = forwardRef((props, ref) => {
430
+ const key = React.useMemo(() => {
431
+ return Math.floor(Math.random() * 0xffffffff).toString();
432
+ }, [props.editor]);
433
+ // Can't use JSX here because it conflicts with the type definition of Vue's JSX, so use createElement
434
+ return React.createElement(PureEditorContent, {
435
+ key,
436
+ innerRef: ref,
437
+ ...props,
438
+ });
439
+ });
440
+ const EditorContent = React.memo(EditorContentWithKey);
441
+
442
+ var withSelector = {exports: {}};
443
+
444
+ var withSelector_production_min = {};
423
445
 
424
446
  /**
425
447
  * @license React
@@ -436,7 +458,7 @@ var hasRequiredWithSelector_production_min;
436
458
  function requireWithSelector_production_min () {
437
459
  if (hasRequiredWithSelector_production_min) return withSelector_production_min;
438
460
  hasRequiredWithSelector_production_min = 1;
439
- var h=React,n=requireShim();function p(a,b){return a===b&&(0!==a||1/a===1/b)||a!==a&&b!==b}var q="function"===typeof Object.is?Object.is:p,r=n.useSyncExternalStore,t=h.useRef,u=h.useEffect,v=h.useMemo,w=h.useDebugValue;
461
+ var h=React,n=shimExports;function p(a,b){return a===b&&(0!==a||1/a===1/b)||a!==a&&b!==b}var q="function"===typeof Object.is?Object.is:p,r=n.useSyncExternalStore,t=h.useRef,u=h.useEffect,v=h.useMemo,w=h.useDebugValue;
440
462
  withSelector_production_min.useSyncExternalStoreWithSelector=function(a,b,e,l,g){var c=t(null);if(null===c.current){var f={hasValue:!1,value:null};c.current=f;}else f=c.current;c=v(function(){function a(a){if(!c){c=!0;d=a;a=l(a);if(void 0!==g&&f.hasValue){var b=f.value;if(g(b,a))return k=b}return k=a}b=k;if(q(d,a))return b;var e=l(a);if(void 0!==g&&g(b,e))return b;d=a;return k=e}var c=!1,d,k,m=void 0===e?null:e;return [function(){return a(b())},null===m?void 0:function(){return a(m())}]},[b,e,l,g]);var d=r(a,c[0],c[1]);
441
463
  u(function(){f.hasValue=!0;f.value=d;},[d]);w(d);return d};
442
464
  return withSelector_production_min;
@@ -472,7 +494,7 @@ function requireWithSelector_development () {
472
494
  __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
473
495
  }
474
496
  var React$1 = React;
475
- var shim = requireShim();
497
+ var shim = shimExports;
476
498
 
477
499
  /**
478
500
  * inlined Object.is polyfill to avoid requiring consumers ship their own
@@ -626,71 +648,75 @@ var withSelectorExports = withSelector.exports;
626
648
  * To synchronize the editor instance with the component state,
627
649
  * we need to create a separate instance that is not affected by the component re-renders.
628
650
  */
629
- function makeEditorStateInstance(initialEditor) {
630
- let transactionNumber = 0;
631
- let lastTransactionNumber = 0;
632
- let lastSnapshot = { editor: initialEditor, transactionNumber: 0 };
633
- let editor = initialEditor;
634
- const subscribers = new Set();
635
- const editorInstance = {
636
- /**
637
- * Get the current editor instance.
638
- */
639
- getSnapshot() {
640
- if (transactionNumber === lastTransactionNumber) {
641
- return lastSnapshot;
642
- }
643
- lastTransactionNumber = transactionNumber;
644
- lastSnapshot = { editor, transactionNumber };
645
- return lastSnapshot;
646
- },
647
- /**
648
- * Always disable the editor on the server-side.
649
- */
650
- getServerSnapshot() {
651
- return { editor: null, transactionNumber: 0 };
652
- },
653
- /**
654
- * Subscribe to the editor instance's changes.
655
- */
656
- subscribe(callback) {
657
- subscribers.add(callback);
651
+ class EditorStateManager {
652
+ constructor(initialEditor) {
653
+ this.transactionNumber = 0;
654
+ this.lastTransactionNumber = 0;
655
+ this.subscribers = new Set();
656
+ this.editor = initialEditor;
657
+ this.lastSnapshot = { editor: initialEditor, transactionNumber: 0 };
658
+ this.getSnapshot = this.getSnapshot.bind(this);
659
+ this.getServerSnapshot = this.getServerSnapshot.bind(this);
660
+ this.watch = this.watch.bind(this);
661
+ this.subscribe = this.subscribe.bind(this);
662
+ }
663
+ /**
664
+ * Get the current editor instance.
665
+ */
666
+ getSnapshot() {
667
+ if (this.transactionNumber === this.lastTransactionNumber) {
668
+ return this.lastSnapshot;
669
+ }
670
+ this.lastTransactionNumber = this.transactionNumber;
671
+ this.lastSnapshot = { editor: this.editor, transactionNumber: this.transactionNumber };
672
+ return this.lastSnapshot;
673
+ }
674
+ /**
675
+ * Always disable the editor on the server-side.
676
+ */
677
+ getServerSnapshot() {
678
+ return { editor: null, transactionNumber: 0 };
679
+ }
680
+ /**
681
+ * Subscribe to the editor instance's changes.
682
+ */
683
+ subscribe(callback) {
684
+ this.subscribers.add(callback);
685
+ return () => {
686
+ this.subscribers.delete(callback);
687
+ };
688
+ }
689
+ /**
690
+ * Watch the editor instance for changes.
691
+ */
692
+ watch(nextEditor) {
693
+ this.editor = nextEditor;
694
+ if (this.editor) {
695
+ /**
696
+ * This will force a re-render when the editor state changes.
697
+ * This is to support things like `editor.can().toggleBold()` in components that `useEditor`.
698
+ * This could be more efficient, but it's a good trade-off for now.
699
+ */
700
+ const fn = () => {
701
+ this.transactionNumber += 1;
702
+ this.subscribers.forEach(callback => callback());
703
+ };
704
+ const currentEditor = this.editor;
705
+ currentEditor.on('transaction', fn);
658
706
  return () => {
659
- subscribers.delete(callback);
707
+ currentEditor.off('transaction', fn);
660
708
  };
661
- },
662
- /**
663
- * Watch the editor instance for changes.
664
- */
665
- watch(nextEditor) {
666
- editor = nextEditor;
667
- if (editor) {
668
- /**
669
- * This will force a re-render when the editor state changes.
670
- * This is to support things like `editor.can().toggleBold()` in components that `useEditor`.
671
- * This could be more efficient, but it's a good trade-off for now.
672
- */
673
- const fn = () => {
674
- transactionNumber += 1;
675
- subscribers.forEach(callback => callback());
676
- };
677
- const currentEditor = editor;
678
- currentEditor.on('transaction', fn);
679
- return () => {
680
- currentEditor.off('transaction', fn);
681
- };
682
- }
683
- },
684
- };
685
- return editorInstance;
709
+ }
710
+ return undefined;
711
+ }
686
712
  }
687
713
  function useEditorState(options) {
688
- const [editorInstance] = useState(() => makeEditorStateInstance(options.editor));
714
+ const [editorInstance] = useState(() => new EditorStateManager(options.editor));
689
715
  // Using the `useSyncExternalStore` hook to sync the editor instance with the component state
690
716
  const selectedState = withSelectorExports.useSyncExternalStoreWithSelector(editorInstance.subscribe, editorInstance.getSnapshot, editorInstance.getServerSnapshot, options.selector, options.equalityFn);
691
717
  useEffect(() => {
692
718
  return editorInstance.watch(options.editor);
693
- }, [options.editor]);
719
+ }, [options.editor, editorInstance]);
694
720
  useDebugValue(selectedState);
695
721
  return selectedState;
696
722
  }
@@ -699,25 +725,50 @@ const isDev = process.env.NODE_ENV !== 'production';
699
725
  const isSSR = typeof window === 'undefined';
700
726
  const isNext = isSSR || Boolean(typeof window !== 'undefined' && window.next);
701
727
  /**
702
- * Create a new editor instance. And attach event listeners.
728
+ * This class handles the creation, destruction, and re-creation of the editor instance.
703
729
  */
704
- function createEditor(options) {
705
- const editor = new Editor(options.current);
706
- editor.on('beforeCreate', (...args) => { var _a, _b; return (_b = (_a = options.current).onBeforeCreate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
707
- editor.on('blur', (...args) => { var _a, _b; return (_b = (_a = options.current).onBlur) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
708
- editor.on('create', (...args) => { var _a, _b; return (_b = (_a = options.current).onCreate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
709
- editor.on('destroy', (...args) => { var _a, _b; return (_b = (_a = options.current).onDestroy) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
710
- editor.on('focus', (...args) => { var _a, _b; return (_b = (_a = options.current).onFocus) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
711
- editor.on('selectionUpdate', (...args) => { var _a, _b; return (_b = (_a = options.current).onSelectionUpdate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
712
- editor.on('transaction', (...args) => { var _a, _b; return (_b = (_a = options.current).onTransaction) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
713
- editor.on('update', (...args) => { var _a, _b; return (_b = (_a = options.current).onUpdate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
714
- editor.on('contentError', (...args) => { var _a, _b; return (_b = (_a = options.current).onContentError) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
715
- return editor;
716
- }
717
- function useEditor(options = {}, deps = []) {
718
- const mostRecentOptions = useRef(options);
719
- const [editor, setEditor] = useState(() => {
720
- if (options.immediatelyRender === undefined) {
730
+ class EditorInstanceManager {
731
+ constructor(options) {
732
+ /**
733
+ * The current editor instance.
734
+ */
735
+ this.editor = null;
736
+ /**
737
+ * The subscriptions to notify when the editor instance
738
+ * has been created or destroyed.
739
+ */
740
+ this.subscriptions = new Set();
741
+ /**
742
+ * Whether the editor has been mounted.
743
+ */
744
+ this.isComponentMounted = false;
745
+ /**
746
+ * The most recent dependencies array.
747
+ */
748
+ this.previousDeps = null;
749
+ /**
750
+ * The unique instance ID. This is used to identify the editor instance. And will be re-generated for each new instance.
751
+ */
752
+ this.instanceId = '';
753
+ this.options = options;
754
+ this.subscriptions = new Set();
755
+ this.setEditor(this.getInitialEditor());
756
+ this.getEditor = this.getEditor.bind(this);
757
+ this.getServerSnapshot = this.getServerSnapshot.bind(this);
758
+ this.subscribe = this.subscribe.bind(this);
759
+ this.refreshEditorInstance = this.refreshEditorInstance.bind(this);
760
+ this.scheduleDestroy = this.scheduleDestroy.bind(this);
761
+ this.onRender = this.onRender.bind(this);
762
+ this.createEditor = this.createEditor.bind(this);
763
+ }
764
+ setEditor(editor) {
765
+ this.editor = editor;
766
+ this.instanceId = Math.random().toString(36).slice(2, 9);
767
+ // Notify all subscribers that the editor instance has been created
768
+ this.subscriptions.forEach(cb => cb());
769
+ }
770
+ getInitialEditor() {
771
+ if (this.options.current.immediatelyRender === undefined) {
721
772
  if (isSSR || isNext) {
722
773
  // TODO in the next major release, we should throw an error here
723
774
  if (isDev) {
@@ -731,55 +782,151 @@ function useEditor(options = {}, deps = []) {
731
782
  return null;
732
783
  }
733
784
  // Default to immediately rendering when client-side rendering
734
- return createEditor(mostRecentOptions);
785
+ return this.createEditor();
735
786
  }
736
- if (options.immediatelyRender && isSSR && isDev) {
787
+ if (this.options.current.immediatelyRender && isSSR && isDev) {
737
788
  // Warn in development, to make sure the developer is aware that tiptap cannot be SSR'd, set `immediatelyRender` to `false` to avoid hydration mismatches.
738
789
  throw new Error('Tiptap Error: SSR has been detected, and `immediatelyRender` has been set to `true` this is an unsupported configuration that may result in errors, explicitly set `immediatelyRender` to `false` to avoid hydration mismatches.');
739
790
  }
740
- if (options.immediatelyRender) {
741
- return createEditor(mostRecentOptions);
791
+ if (this.options.current.immediatelyRender) {
792
+ return this.createEditor();
742
793
  }
743
794
  return null;
744
- });
745
- const mostRecentEditor = useRef(editor);
746
- mostRecentEditor.current = editor;
747
- useDebugValue(editor);
748
- // This effect will handle creating/updating the editor instance
749
- useEffect(() => {
750
- const destroyUnusedEditor = (editorInstance) => {
751
- if (editorInstance) {
752
- // We need to destroy the editor asynchronously to avoid memory leaks
753
- // because the editor instance is still being used in the component.
754
- setTimeout(() => {
755
- // re-use the editor instance if it hasn't been replaced yet
756
- // otherwise, asynchronously destroy the old editor instance
757
- if (editorInstance !== mostRecentEditor.current && !editorInstance.isDestroyed) {
758
- editorInstance.destroy();
759
- }
760
- });
795
+ }
796
+ /**
797
+ * Create a new editor instance. And attach event listeners.
798
+ */
799
+ createEditor() {
800
+ const optionsToApply = {
801
+ ...this.options.current,
802
+ // Always call the most recent version of the callback function by default
803
+ onBeforeCreate: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onBeforeCreate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
804
+ onBlur: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onBlur) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
805
+ onCreate: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onCreate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
806
+ onDestroy: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onDestroy) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
807
+ onFocus: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onFocus) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
808
+ onSelectionUpdate: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onSelectionUpdate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
809
+ onTransaction: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onTransaction) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
810
+ onUpdate: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onUpdate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
811
+ onContentError: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onContentError) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); },
812
+ };
813
+ const editor = new Editor$1(optionsToApply);
814
+ // no need to keep track of the event listeners, they will be removed when the editor is destroyed
815
+ return editor;
816
+ }
817
+ /**
818
+ * Get the current editor instance.
819
+ */
820
+ getEditor() {
821
+ return this.editor;
822
+ }
823
+ /**
824
+ * Always disable the editor on the server-side.
825
+ */
826
+ getServerSnapshot() {
827
+ return null;
828
+ }
829
+ /**
830
+ * Subscribe to the editor instance's changes.
831
+ */
832
+ subscribe(onStoreChange) {
833
+ this.subscriptions.add(onStoreChange);
834
+ return () => {
835
+ this.subscriptions.delete(onStoreChange);
836
+ };
837
+ }
838
+ /**
839
+ * On each render, we will create, update, or destroy the editor instance.
840
+ * @param deps The dependencies to watch for changes
841
+ * @returns A cleanup function
842
+ */
843
+ onRender(deps) {
844
+ // The returned callback will run on each render
845
+ return () => {
846
+ this.isComponentMounted = true;
847
+ // Cleanup any scheduled destructions, since we are currently rendering
848
+ clearTimeout(this.scheduledDestructionTimeout);
849
+ if (this.editor && !this.editor.isDestroyed && deps.length === 0) {
850
+ // if the editor does exist & deps are empty, we don't need to re-initialize the editor
851
+ // we can fast-path to update the editor options on the existing instance
852
+ this.editor.setOptions(this.options.current);
853
+ }
854
+ else {
855
+ // When the editor:
856
+ // - does not yet exist
857
+ // - is destroyed
858
+ // - the deps array changes
859
+ // We need to destroy the editor instance and re-initialize it
860
+ this.refreshEditorInstance(deps);
761
861
  }
862
+ return () => {
863
+ this.isComponentMounted = false;
864
+ this.scheduleDestroy();
865
+ };
762
866
  };
763
- let editorInstance = mostRecentEditor.current;
764
- if (!editorInstance) {
765
- editorInstance = createEditor(mostRecentOptions);
766
- setEditor(editorInstance);
767
- return () => destroyUnusedEditor(editorInstance);
867
+ }
868
+ /**
869
+ * Recreate the editor instance if the dependencies have changed.
870
+ */
871
+ refreshEditorInstance(deps) {
872
+ if (this.editor && !this.editor.isDestroyed) {
873
+ // Editor instance already exists
874
+ if (this.previousDeps === null) {
875
+ // If lastDeps has not yet been initialized, reuse the current editor instance
876
+ this.previousDeps = deps;
877
+ return;
878
+ }
879
+ const depsAreEqual = this.previousDeps.length === deps.length
880
+ && this.previousDeps.every((dep, index) => dep === deps[index]);
881
+ if (depsAreEqual) {
882
+ // deps exist and are equal, no need to recreate
883
+ return;
884
+ }
768
885
  }
769
- if (!Array.isArray(deps) || deps.length === 0) {
770
- // if the editor does exist & deps are empty, we don't need to re-initialize the editor
771
- // we can fast-path to update the editor options on the existing instance
772
- editorInstance.setOptions(options);
773
- return () => destroyUnusedEditor(editorInstance);
886
+ if (this.editor && !this.editor.isDestroyed) {
887
+ // Destroy the editor instance if it exists
888
+ this.editor.destroy();
774
889
  }
775
- // We need to destroy the editor instance and re-initialize it
776
- // when the deps array changes
777
- editorInstance.destroy();
778
- // the deps array is used to re-initialize the editor instance
779
- editorInstance = createEditor(mostRecentOptions);
780
- setEditor(editorInstance);
781
- return () => destroyUnusedEditor(editorInstance);
782
- }, deps);
890
+ this.setEditor(this.createEditor());
891
+ // Update the lastDeps to the current deps
892
+ this.previousDeps = deps;
893
+ }
894
+ /**
895
+ * Schedule the destruction of the editor instance.
896
+ * This will only destroy the editor if it was not mounted on the next tick.
897
+ * This is to avoid destroying the editor instance when it's actually still mounted.
898
+ */
899
+ scheduleDestroy() {
900
+ const currentInstanceId = this.instanceId;
901
+ const currentEditor = this.editor;
902
+ // Wait a tick to see if the component is still mounted
903
+ this.scheduledDestructionTimeout = setTimeout(() => {
904
+ if (this.isComponentMounted && this.instanceId === currentInstanceId) {
905
+ // If still mounted on the next tick, with the same instanceId, do not destroy the editor
906
+ if (currentEditor) {
907
+ // just re-apply options as they might have changed
908
+ currentEditor.setOptions(this.options.current);
909
+ }
910
+ return;
911
+ }
912
+ if (currentEditor && !currentEditor.isDestroyed) {
913
+ currentEditor.destroy();
914
+ if (this.instanceId === currentInstanceId) {
915
+ this.setEditor(null);
916
+ }
917
+ }
918
+ }, 0);
919
+ }
920
+ }
921
+ function useEditor(options = {}, deps = []) {
922
+ const mostRecentOptions = useRef(options);
923
+ mostRecentOptions.current = options;
924
+ const [instanceManager] = useState(() => new EditorInstanceManager(mostRecentOptions));
925
+ const editor = shimExports.useSyncExternalStore(instanceManager.subscribe, instanceManager.getEditor, instanceManager.getServerSnapshot);
926
+ useDebugValue(editor);
927
+ // This effect will handle creating/updating the editor instance
928
+ // eslint-disable-next-line react-hooks/exhaustive-deps
929
+ useEffect(instanceManager.onRender(deps));
783
930
  // The default behavior is to re-render on each transaction
784
931
  // This is legacy behavior that will be removed in future versions
785
932
  useEditorState({
@@ -855,6 +1002,13 @@ const BubbleMenu = (props) => {
855
1002
  return (React.createElement("div", { ref: setElement, className: props.className, style: { visibility: 'hidden' } }, props.children));
856
1003
  };
857
1004
 
1005
+ class Editor extends Editor$1 {
1006
+ constructor() {
1007
+ super(...arguments);
1008
+ this.contentComponent = null;
1009
+ }
1010
+ }
1011
+
858
1012
  const FloatingMenu = (props) => {
859
1013
  const [element, setElement] = useState(null);
860
1014
  const { editor: currentEditor } = useCurrentEditor();
@@ -906,7 +1060,9 @@ const NodeViewContent = props => {
906
1060
  const NodeViewWrapper = React.forwardRef((props, ref) => {
907
1061
  const { onDragStart } = useReactNodeView();
908
1062
  const Tag = props.as || 'div';
909
- return (React.createElement(Tag, { ...props, ref: ref, "data-node-view-wrapper": "", onDragStart: onDragStart, style: {
1063
+ return (
1064
+ // @ts-ignore
1065
+ React.createElement(Tag, { ...props, ref: ref, "data-node-view-wrapper": "", onDragStart: onDragStart, style: {
910
1066
  whiteSpace: 'normal',
911
1067
  ...props.style,
912
1068
  } }));
@@ -960,7 +1116,16 @@ class ReactRenderer {
960
1116
  this.element.setAttribute(key, attrs[key]);
961
1117
  });
962
1118
  }
963
- this.render();
1119
+ if (this.editor.isInitialized) {
1120
+ // On first render, we need to flush the render synchronously
1121
+ // Renders afterwards can be async, but this fixes a cursor positioning issue
1122
+ flushSync(() => {
1123
+ this.render();
1124
+ });
1125
+ }
1126
+ else {
1127
+ this.render();
1128
+ }
964
1129
  }
965
1130
  render() {
966
1131
  var _a, _b;
@@ -971,7 +1136,7 @@ class ReactRenderer {
971
1136
  this.ref = ref;
972
1137
  };
973
1138
  }
974
- this.reactElement = React.createElement(Component, { ...props });
1139
+ this.reactElement = React.createElement(Component, props);
975
1140
  (_b = (_a = this.editor) === null || _a === void 0 ? void 0 : _a.contentComponent) === null || _b === void 0 ? void 0 : _b.setRenderer(this.id, this);
976
1141
  }
977
1142
  updateProps(props = {}) {
@@ -1005,18 +1170,19 @@ class ReactNodeView extends NodeView {
1005
1170
  };
1006
1171
  this.component.displayName = capitalizeFirstChar(this.extension.name);
1007
1172
  }
1008
- const ReactNodeViewProvider = componentProps => {
1009
- const Component = this.component;
1010
- const onDragStart = this.onDragStart.bind(this);
1011
- const nodeViewContentRef = element => {
1012
- if (element && this.contentDOMElement && element.firstChild !== this.contentDOMElement) {
1013
- element.appendChild(this.contentDOMElement);
1014
- }
1015
- };
1016
- return (React.createElement(React.Fragment, null,
1017
- React.createElement(ReactNodeViewContext.Provider, { value: { onDragStart, nodeViewContentRef } },
1018
- React.createElement(Component, { ...componentProps }))));
1173
+ const onDragStart = this.onDragStart.bind(this);
1174
+ const nodeViewContentRef = element => {
1175
+ if (element && this.contentDOMElement && element.firstChild !== this.contentDOMElement) {
1176
+ element.appendChild(this.contentDOMElement);
1177
+ }
1019
1178
  };
1179
+ const context = { onDragStart, nodeViewContentRef };
1180
+ const Component = this.component;
1181
+ // For performance reasons, we memoize the provider component
1182
+ // And all of the things it requires are declared outside of the component, so it doesn't need to re-render
1183
+ const ReactNodeViewProvider = React.memo(componentProps => {
1184
+ return (React.createElement(ReactNodeViewContext.Provider, { value: context }, React.createElement(Component, componentProps)));
1185
+ });
1020
1186
  ReactNodeViewProvider.displayName = 'ReactNodeView';
1021
1187
  if (this.node.isLeaf) {
1022
1188
  this.contentDOMElement = null;