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