@tiptap/react 2.5.7 → 2.5.9

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
@@ -125,17 +125,6 @@
125
125
  });
126
126
  const EditorContent = React.memo(EditorContentWithKey);
127
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
128
  var shim = {exports: {}};
140
129
 
141
130
  var useSyncExternalStoreShim_production_min = {};
@@ -406,20 +395,25 @@
406
395
  return useSyncExternalStoreShim_development;
407
396
  }
408
397
 
409
- var hasRequiredShim;
398
+ if (process.env.NODE_ENV === 'production') {
399
+ shim.exports = requireUseSyncExternalStoreShim_production_min();
400
+ } else {
401
+ shim.exports = requireUseSyncExternalStoreShim_development();
402
+ }
410
403
 
411
- function requireShim () {
412
- if (hasRequiredShim) return shim.exports;
413
- hasRequiredShim = 1;
404
+ var shimExports = shim.exports;
414
405
 
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;
406
+ class Editor extends core.Editor {
407
+ constructor() {
408
+ super(...arguments);
409
+ this.contentComponent = null;
410
+ }
421
411
  }
422
412
 
413
+ var withSelector = {exports: {}};
414
+
415
+ var withSelector_production_min = {};
416
+
423
417
  /**
424
418
  * @license React
425
419
  * use-sync-external-store-shim/with-selector.production.min.js
@@ -435,7 +429,7 @@
435
429
  function requireWithSelector_production_min () {
436
430
  if (hasRequiredWithSelector_production_min) return withSelector_production_min;
437
431
  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;
432
+ 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
433
  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
434
  u(function(){f.hasValue=!0;f.value=d;},[d]);w(d);return d};
441
435
  return withSelector_production_min;
@@ -471,7 +465,7 @@
471
465
  __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
472
466
  }
473
467
  var React$1 = React;
474
- var shim = requireShim();
468
+ var shim = shimExports;
475
469
 
476
470
  /**
477
471
  * inlined Object.is polyfill to avoid requiring consumers ship their own
@@ -625,71 +619,75 @@
625
619
  * To synchronize the editor instance with the component state,
626
620
  * we need to create a separate instance that is not affected by the component re-renders.
627
621
  */
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);
622
+ class EditorStateManager {
623
+ constructor(initialEditor) {
624
+ this.transactionNumber = 0;
625
+ this.lastTransactionNumber = 0;
626
+ this.subscribers = new Set();
627
+ this.editor = initialEditor;
628
+ this.lastSnapshot = { editor: initialEditor, transactionNumber: 0 };
629
+ this.getSnapshot = this.getSnapshot.bind(this);
630
+ this.getServerSnapshot = this.getServerSnapshot.bind(this);
631
+ this.watch = this.watch.bind(this);
632
+ this.subscribe = this.subscribe.bind(this);
633
+ }
634
+ /**
635
+ * Get the current editor instance.
636
+ */
637
+ getSnapshot() {
638
+ if (this.transactionNumber === this.lastTransactionNumber) {
639
+ return this.lastSnapshot;
640
+ }
641
+ this.lastTransactionNumber = this.transactionNumber;
642
+ this.lastSnapshot = { editor: this.editor, transactionNumber: this.transactionNumber };
643
+ return this.lastSnapshot;
644
+ }
645
+ /**
646
+ * Always disable the editor on the server-side.
647
+ */
648
+ getServerSnapshot() {
649
+ return { editor: null, transactionNumber: 0 };
650
+ }
651
+ /**
652
+ * Subscribe to the editor instance's changes.
653
+ */
654
+ subscribe(callback) {
655
+ this.subscribers.add(callback);
656
+ return () => {
657
+ this.subscribers.delete(callback);
658
+ };
659
+ }
660
+ /**
661
+ * Watch the editor instance for changes.
662
+ */
663
+ watch(nextEditor) {
664
+ this.editor = nextEditor;
665
+ if (this.editor) {
666
+ /**
667
+ * This will force a re-render when the editor state changes.
668
+ * This is to support things like `editor.can().toggleBold()` in components that `useEditor`.
669
+ * This could be more efficient, but it's a good trade-off for now.
670
+ */
671
+ const fn = () => {
672
+ this.transactionNumber += 1;
673
+ this.subscribers.forEach(callback => callback());
674
+ };
675
+ const currentEditor = this.editor;
676
+ currentEditor.on('transaction', fn);
657
677
  return () => {
658
- subscribers.delete(callback);
678
+ currentEditor.off('transaction', fn);
659
679
  };
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;
680
+ }
681
+ return undefined;
682
+ }
685
683
  }
686
684
  function useEditorState(options) {
687
- const [editorInstance] = React.useState(() => makeEditorStateInstance(options.editor));
685
+ const [editorInstance] = React.useState(() => new EditorStateManager(options.editor));
688
686
  // Using the `useSyncExternalStore` hook to sync the editor instance with the component state
689
687
  const selectedState = withSelectorExports.useSyncExternalStoreWithSelector(editorInstance.subscribe, editorInstance.getSnapshot, editorInstance.getServerSnapshot, options.selector, options.equalityFn);
690
688
  React.useEffect(() => {
691
689
  return editorInstance.watch(options.editor);
692
- }, [options.editor]);
690
+ }, [options.editor, editorInstance]);
693
691
  React.useDebugValue(selectedState);
694
692
  return selectedState;
695
693
  }
@@ -697,10 +695,51 @@
697
695
  const isDev = process.env.NODE_ENV !== 'production';
698
696
  const isSSR = typeof window === 'undefined';
699
697
  const isNext = isSSR || Boolean(typeof window !== 'undefined' && window.next);
700
- function useEditor(options = {}, deps = []) {
701
- const isMounted = React.useRef(false);
702
- const [editor, setEditor] = React.useState(() => {
703
- if (options.immediatelyRender === undefined) {
698
+ /**
699
+ * This class handles the creation, destruction, and re-creation of the editor instance.
700
+ */
701
+ class EditorInstanceManager {
702
+ constructor(options) {
703
+ /**
704
+ * The current editor instance.
705
+ */
706
+ this.editor = null;
707
+ /**
708
+ * The subscriptions to notify when the editor instance
709
+ * has been created or destroyed.
710
+ */
711
+ this.subscriptions = new Set();
712
+ /**
713
+ * Whether the editor has been mounted.
714
+ */
715
+ this.isComponentMounted = false;
716
+ /**
717
+ * The most recent dependencies array.
718
+ */
719
+ this.previousDeps = null;
720
+ /**
721
+ * The unique instance ID. This is used to identify the editor instance. And will be re-generated for each new instance.
722
+ */
723
+ this.instanceId = '';
724
+ this.options = options;
725
+ this.subscriptions = new Set();
726
+ this.setEditor(this.getInitialEditor());
727
+ this.getEditor = this.getEditor.bind(this);
728
+ this.getServerSnapshot = this.getServerSnapshot.bind(this);
729
+ this.subscribe = this.subscribe.bind(this);
730
+ this.refreshEditorInstance = this.refreshEditorInstance.bind(this);
731
+ this.scheduleDestroy = this.scheduleDestroy.bind(this);
732
+ this.onRender = this.onRender.bind(this);
733
+ this.createEditor = this.createEditor.bind(this);
734
+ }
735
+ setEditor(editor) {
736
+ this.editor = editor;
737
+ this.instanceId = Math.random().toString(36).slice(2, 9);
738
+ // Notify all subscribers that the editor instance has been created
739
+ this.subscriptions.forEach(cb => cb());
740
+ }
741
+ getInitialEditor() {
742
+ if (this.options.current.immediatelyRender === undefined) {
704
743
  if (isSSR || isNext) {
705
744
  // TODO in the next major release, we should throw an error here
706
745
  if (isDev) {
@@ -714,137 +753,148 @@
714
753
  return null;
715
754
  }
716
755
  // Default to immediately rendering when client-side rendering
717
- return new Editor(options);
756
+ return this.createEditor();
718
757
  }
719
- if (options.immediatelyRender && isSSR && isDev) {
758
+ if (this.options.current.immediatelyRender && isSSR && isDev) {
720
759
  // Warn in development, to make sure the developer is aware that tiptap cannot be SSR'd, set `immediatelyRender` to `false` to avoid hydration mismatches.
721
760
  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.');
722
761
  }
723
- if (options.immediatelyRender) {
724
- return new Editor(options);
762
+ if (this.options.current.immediatelyRender) {
763
+ return this.createEditor();
725
764
  }
726
765
  return null;
727
- });
728
- React.useDebugValue(editor);
729
- // This effect will handle creating/updating the editor instance
730
- React.useEffect(() => {
731
- let editorInstance = editor;
732
- if (!editorInstance) {
733
- editorInstance = new Editor(options);
734
- // instantiate the editor if it doesn't exist
735
- // for ssr, this is the first time the editor is created
736
- setEditor(editorInstance);
737
- }
738
- else if (Array.isArray(deps) && deps.length) {
739
- // We need to destroy the editor instance and re-initialize it
740
- // when the deps array changes
741
- editorInstance.destroy();
742
- // the deps array is used to re-initialize the editor instance
743
- editorInstance = new Editor(options);
744
- setEditor(editorInstance);
745
- }
746
- else {
747
- // if the editor does exist & deps are empty, we don't need to re-initialize the editor
748
- // we can fast-path to update the editor options on the existing instance
749
- editorInstance.setOptions(options);
750
- }
751
- }, deps);
752
- const { onBeforeCreate, onBlur, onCreate, onDestroy, onFocus, onSelectionUpdate, onTransaction, onUpdate, onContentError, } = options;
753
- const onBeforeCreateRef = React.useRef(onBeforeCreate);
754
- const onBlurRef = React.useRef(onBlur);
755
- const onCreateRef = React.useRef(onCreate);
756
- const onDestroyRef = React.useRef(onDestroy);
757
- const onFocusRef = React.useRef(onFocus);
758
- const onSelectionUpdateRef = React.useRef(onSelectionUpdate);
759
- const onTransactionRef = React.useRef(onTransaction);
760
- const onUpdateRef = React.useRef(onUpdate);
761
- const onContentErrorRef = React.useRef(onContentError);
762
- // This effect will handle updating the editor instance
763
- // when the event handlers change.
764
- React.useEffect(() => {
765
- if (!editor) {
766
- return;
767
- }
768
- if (onBeforeCreate) {
769
- editor.off('beforeCreate', onBeforeCreateRef.current);
770
- editor.on('beforeCreate', onBeforeCreate);
771
- onBeforeCreateRef.current = onBeforeCreate;
772
- }
773
- if (onBlur) {
774
- editor.off('blur', onBlurRef.current);
775
- editor.on('blur', onBlur);
776
- onBlurRef.current = onBlur;
777
- }
778
- if (onCreate) {
779
- editor.off('create', onCreateRef.current);
780
- editor.on('create', onCreate);
781
- onCreateRef.current = onCreate;
782
- }
783
- if (onDestroy) {
784
- editor.off('destroy', onDestroyRef.current);
785
- editor.on('destroy', onDestroy);
786
- onDestroyRef.current = onDestroy;
787
- }
788
- if (onFocus) {
789
- editor.off('focus', onFocusRef.current);
790
- editor.on('focus', onFocus);
791
- onFocusRef.current = onFocus;
792
- }
793
- if (onSelectionUpdate) {
794
- editor.off('selectionUpdate', onSelectionUpdateRef.current);
795
- editor.on('selectionUpdate', onSelectionUpdate);
796
- onSelectionUpdateRef.current = onSelectionUpdate;
797
- }
798
- if (onTransaction) {
799
- editor.off('transaction', onTransactionRef.current);
800
- editor.on('transaction', onTransaction);
801
- onTransactionRef.current = onTransaction;
802
- }
803
- if (onUpdate) {
804
- editor.off('update', onUpdateRef.current);
805
- editor.on('update', onUpdate);
806
- onUpdateRef.current = onUpdate;
807
- }
808
- if (onContentError) {
809
- editor.off('contentError', onContentErrorRef.current);
810
- editor.on('contentError', onContentError);
811
- onContentErrorRef.current = onContentError;
812
- }
813
- }, [
814
- onBeforeCreate,
815
- onBlur,
816
- onCreate,
817
- onDestroy,
818
- onFocus,
819
- onSelectionUpdate,
820
- onTransaction,
821
- onUpdate,
822
- onContentError,
823
- editor,
824
- ]);
766
+ }
825
767
  /**
826
- * Destroy the editor instance when the component completely unmounts
827
- * As opposed to the cleanup function in the effect above, this will
828
- * only be called when the component is removed from the DOM, since it has no deps.
829
- * */
830
- React.useEffect(() => {
831
- isMounted.current = true;
768
+ * Create a new editor instance. And attach event listeners.
769
+ */
770
+ createEditor() {
771
+ const editor = new Editor(this.options.current);
772
+ // Always call the most recent version of the callback function by default
773
+ editor.on('beforeCreate', (...args) => { var _a, _b; return (_b = (_a = this.options.current).onBeforeCreate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
774
+ editor.on('blur', (...args) => { var _a, _b; return (_b = (_a = this.options.current).onBlur) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
775
+ editor.on('create', (...args) => { var _a, _b; return (_b = (_a = this.options.current).onCreate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
776
+ editor.on('destroy', (...args) => { var _a, _b; return (_b = (_a = this.options.current).onDestroy) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
777
+ editor.on('focus', (...args) => { var _a, _b; return (_b = (_a = this.options.current).onFocus) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
778
+ editor.on('selectionUpdate', (...args) => { var _a, _b; return (_b = (_a = this.options.current).onSelectionUpdate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
779
+ editor.on('transaction', (...args) => { var _a, _b; return (_b = (_a = this.options.current).onTransaction) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
780
+ editor.on('update', (...args) => { var _a, _b; return (_b = (_a = this.options.current).onUpdate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
781
+ editor.on('contentError', (...args) => { var _a, _b; return (_b = (_a = this.options.current).onContentError) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
782
+ // no need to keep track of the event listeners, they will be removed when the editor is destroyed
783
+ return editor;
784
+ }
785
+ /**
786
+ * Get the current editor instance.
787
+ */
788
+ getEditor() {
789
+ return this.editor;
790
+ }
791
+ /**
792
+ * Always disable the editor on the server-side.
793
+ */
794
+ getServerSnapshot() {
795
+ return null;
796
+ }
797
+ /**
798
+ * Subscribe to the editor instance's changes.
799
+ */
800
+ subscribe(onStoreChange) {
801
+ this.subscriptions.add(onStoreChange);
802
+ return () => {
803
+ this.subscriptions.delete(onStoreChange);
804
+ };
805
+ }
806
+ /**
807
+ * On each render, we will create, update, or destroy the editor instance.
808
+ * @param deps The dependencies to watch for changes
809
+ * @returns A cleanup function
810
+ */
811
+ onRender(deps) {
812
+ // The returned callback will run on each render
832
813
  return () => {
833
- isMounted.current = false;
834
- if (editor) {
835
- // We need to destroy the editor asynchronously to avoid memory leaks
836
- // because the editor instance is still being used in the component.
837
- setTimeout(() => {
838
- // re-use the editor instance if it hasn't been destroyed yet
839
- // and the component is still mounted
840
- // otherwise, asynchronously destroy the editor instance
841
- if (!isMounted.current && !editor.isDestroyed) {
842
- editor.destroy();
843
- }
844
- });
814
+ this.isComponentMounted = true;
815
+ // Cleanup any scheduled destructions, since we are currently rendering
816
+ clearTimeout(this.scheduledDestructionTimeout);
817
+ if (this.editor && !this.editor.isDestroyed && deps.length === 0) {
818
+ // if the editor does exist & deps are empty, we don't need to re-initialize the editor
819
+ // we can fast-path to update the editor options on the existing instance
820
+ this.editor.setOptions(this.options.current);
821
+ }
822
+ else {
823
+ // When the editor:
824
+ // - does not yet exist
825
+ // - is destroyed
826
+ // - the deps array changes
827
+ // We need to destroy the editor instance and re-initialize it
828
+ this.refreshEditorInstance(deps);
845
829
  }
830
+ return () => {
831
+ this.isComponentMounted = false;
832
+ this.scheduleDestroy();
833
+ };
846
834
  };
847
- }, []);
835
+ }
836
+ /**
837
+ * Recreate the editor instance if the dependencies have changed.
838
+ */
839
+ refreshEditorInstance(deps) {
840
+ if (this.editor && !this.editor.isDestroyed) {
841
+ // Editor instance already exists
842
+ if (this.previousDeps === null) {
843
+ // If lastDeps has not yet been initialized, reuse the current editor instance
844
+ this.previousDeps = deps;
845
+ return;
846
+ }
847
+ const depsAreEqual = this.previousDeps.length === deps.length
848
+ && this.previousDeps.every((dep, index) => dep === deps[index]);
849
+ if (depsAreEqual) {
850
+ // deps exist and are equal, no need to recreate
851
+ return;
852
+ }
853
+ }
854
+ if (this.editor && !this.editor.isDestroyed) {
855
+ // Destroy the editor instance if it exists
856
+ this.editor.destroy();
857
+ }
858
+ this.setEditor(this.createEditor());
859
+ // Update the lastDeps to the current deps
860
+ this.previousDeps = deps;
861
+ }
862
+ /**
863
+ * Schedule the destruction of the editor instance.
864
+ * This will only destroy the editor if it was not mounted on the next tick.
865
+ * This is to avoid destroying the editor instance when it's actually still mounted.
866
+ */
867
+ scheduleDestroy() {
868
+ const currentInstanceId = this.instanceId;
869
+ const currentEditor = this.editor;
870
+ // Wait a tick to see if the component is still mounted
871
+ this.scheduledDestructionTimeout = setTimeout(() => {
872
+ if (this.isComponentMounted && this.instanceId === currentInstanceId) {
873
+ // If still mounted on the next tick, with the same instanceId, do not destroy the editor
874
+ if (currentEditor) {
875
+ // just re-apply options as they might have changed
876
+ currentEditor.setOptions(this.options.current);
877
+ }
878
+ return;
879
+ }
880
+ if (currentEditor && !currentEditor.isDestroyed) {
881
+ currentEditor.destroy();
882
+ if (this.instanceId === currentInstanceId) {
883
+ this.setEditor(null);
884
+ }
885
+ }
886
+ }, 0);
887
+ }
888
+ }
889
+ function useEditor(options = {}, deps = []) {
890
+ const mostRecentOptions = React.useRef(options);
891
+ mostRecentOptions.current = options;
892
+ const [instanceManager] = React.useState(() => new EditorInstanceManager(mostRecentOptions));
893
+ const editor = shimExports.useSyncExternalStore(instanceManager.subscribe, instanceManager.getEditor, instanceManager.getServerSnapshot);
894
+ React.useDebugValue(editor);
895
+ // This effect will handle creating/updating the editor instance
896
+ // eslint-disable-next-line react-hooks/exhaustive-deps
897
+ React.useEffect(instanceManager.onRender(deps));
848
898
  // The default behavior is to re-render on each transaction
849
899
  // This is legacy behavior that will be removed in future versions
850
900
  useEditorState({