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