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