@tiptap/react 2.5.9 → 3.0.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { BubbleMenuPlugin } from '@tiptap/extension-bubble-menu';
2
2
  import React, { forwardRef, useState, useEffect, useDebugValue, useRef, createContext, useContext } from 'react';
3
- import ReactDOM, { flushSync } from 'react-dom';
3
+ import ReactDOM, { flushSync, createPortal } from 'react-dom';
4
4
  import { Editor as Editor$1, NodeView } from '@tiptap/core';
5
5
  export * from '@tiptap/core';
6
6
  import { FloatingMenuPlugin } from '@tiptap/extension-floating-menu';
@@ -126,6 +126,17 @@ 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
+
129
140
  var shim = {exports: {}};
130
141
 
131
142
  var useSyncExternalStoreShim_production_min = {};
@@ -396,25 +407,20 @@ function requireUseSyncExternalStoreShim_development () {
396
407
  return useSyncExternalStoreShim_development;
397
408
  }
398
409
 
399
- if (process.env.NODE_ENV === 'production') {
400
- shim.exports = requireUseSyncExternalStoreShim_production_min();
401
- } else {
402
- shim.exports = requireUseSyncExternalStoreShim_development();
403
- }
410
+ var hasRequiredShim;
404
411
 
405
- var shimExports = shim.exports;
412
+ function requireShim () {
413
+ if (hasRequiredShim) return shim.exports;
414
+ hasRequiredShim = 1;
406
415
 
407
- class Editor extends Editor$1 {
408
- constructor() {
409
- super(...arguments);
410
- this.contentComponent = null;
411
- }
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;
412
422
  }
413
423
 
414
- var withSelector = {exports: {}};
415
-
416
- var withSelector_production_min = {};
417
-
418
424
  /**
419
425
  * @license React
420
426
  * use-sync-external-store-shim/with-selector.production.min.js
@@ -430,7 +436,7 @@ var hasRequiredWithSelector_production_min;
430
436
  function requireWithSelector_production_min () {
431
437
  if (hasRequiredWithSelector_production_min) return withSelector_production_min;
432
438
  hasRequiredWithSelector_production_min = 1;
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;
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;
434
440
  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]);
435
441
  u(function(){f.hasValue=!0;f.value=d;},[d]);w(d);return d};
436
442
  return withSelector_production_min;
@@ -466,7 +472,7 @@ function requireWithSelector_development () {
466
472
  __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
467
473
  }
468
474
  var React$1 = React;
469
- var shim = shimExports;
475
+ var shim = requireShim();
470
476
 
471
477
  /**
472
478
  * inlined Object.is polyfill to avoid requiring consumers ship their own
@@ -620,75 +626,71 @@ var withSelectorExports = withSelector.exports;
620
626
  * To synchronize the editor instance with the component state,
621
627
  * we need to create a separate instance that is not affected by the component re-renders.
622
628
  */
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);
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);
678
658
  return () => {
679
- currentEditor.off('transaction', fn);
659
+ subscribers.delete(callback);
680
660
  };
681
- }
682
- return undefined;
683
- }
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;
684
686
  }
685
687
  function useEditorState(options) {
686
- const [editorInstance] = useState(() => new EditorStateManager(options.editor));
688
+ const [editorInstance] = useState(() => makeEditorStateInstance(options.editor));
687
689
  // Using the `useSyncExternalStore` hook to sync the editor instance with the component state
688
690
  const selectedState = withSelectorExports.useSyncExternalStoreWithSelector(editorInstance.subscribe, editorInstance.getSnapshot, editorInstance.getServerSnapshot, options.selector, options.equalityFn);
689
691
  useEffect(() => {
690
692
  return editorInstance.watch(options.editor);
691
- }, [options.editor, editorInstance]);
693
+ }, [options.editor]);
692
694
  useDebugValue(selectedState);
693
695
  return selectedState;
694
696
  }
@@ -697,50 +699,25 @@ const isDev = process.env.NODE_ENV !== 'production';
697
699
  const isSSR = typeof window === 'undefined';
698
700
  const isNext = isSSR || Boolean(typeof window !== 'undefined' && window.next);
699
701
  /**
700
- * This class handles the creation, destruction, and re-creation of the editor instance.
702
+ * Create a new editor instance. And attach event listeners.
701
703
  */
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) {
704
+ function createEditor(options) {
705
+ const editor = new Editor(options.current);
706
+ editor.on('beforeCreate', (...args) => { var _a, _b; return (_b = (_a = options.current).onBeforeCreate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
707
+ editor.on('blur', (...args) => { var _a, _b; return (_b = (_a = options.current).onBlur) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
708
+ editor.on('create', (...args) => { var _a, _b; return (_b = (_a = options.current).onCreate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
709
+ editor.on('destroy', (...args) => { var _a, _b; return (_b = (_a = options.current).onDestroy) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
710
+ editor.on('focus', (...args) => { var _a, _b; return (_b = (_a = options.current).onFocus) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
711
+ editor.on('selectionUpdate', (...args) => { var _a, _b; return (_b = (_a = options.current).onSelectionUpdate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
712
+ editor.on('transaction', (...args) => { var _a, _b; return (_b = (_a = options.current).onTransaction) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
713
+ editor.on('update', (...args) => { var _a, _b; return (_b = (_a = options.current).onUpdate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
714
+ editor.on('contentError', (...args) => { var _a, _b; return (_b = (_a = options.current).onContentError) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); });
715
+ return editor;
716
+ }
717
+ function useEditor(options = {}, deps = []) {
718
+ const mostRecentOptions = useRef(options);
719
+ const [editor, setEditor] = useState(() => {
720
+ if (options.immediatelyRender === undefined) {
744
721
  if (isSSR || isNext) {
745
722
  // TODO in the next major release, we should throw an error here
746
723
  if (isDev) {
@@ -754,148 +731,55 @@ class EditorInstanceManager {
754
731
  return null;
755
732
  }
756
733
  // Default to immediately rendering when client-side rendering
757
- return this.createEditor();
734
+ return createEditor(mostRecentOptions);
758
735
  }
759
- if (this.options.current.immediatelyRender && isSSR && isDev) {
736
+ if (options.immediatelyRender && isSSR && isDev) {
760
737
  // Warn in development, to make sure the developer is aware that tiptap cannot be SSR'd, set `immediatelyRender` to `false` to avoid hydration mismatches.
761
738
  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.');
762
739
  }
763
- if (this.options.current.immediatelyRender) {
764
- return this.createEditor();
740
+ if (options.immediatelyRender) {
741
+ return createEditor(mostRecentOptions);
765
742
  }
766
743
  return null;
767
- }
768
- /**
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
814
- return () => {
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);
744
+ });
745
+ const mostRecentEditor = useRef(editor);
746
+ mostRecentEditor.current = editor;
747
+ useDebugValue(editor);
748
+ // This effect will handle creating/updating the editor instance
749
+ useEffect(() => {
750
+ const destroyUnusedEditor = (editorInstance) => {
751
+ if (editorInstance) {
752
+ // We need to destroy the editor asynchronously to avoid memory leaks
753
+ // because the editor instance is still being used in the component.
754
+ setTimeout(() => {
755
+ // re-use the editor instance if it hasn't been replaced yet
756
+ // otherwise, asynchronously destroy the old editor instance
757
+ if (editorInstance !== mostRecentEditor.current && !editorInstance.isDestroyed) {
758
+ editorInstance.destroy();
759
+ }
760
+ });
830
761
  }
831
- return () => {
832
- this.isComponentMounted = false;
833
- this.scheduleDestroy();
834
- };
835
762
  };
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
- }
763
+ let editorInstance = mostRecentEditor.current;
764
+ if (!editorInstance) {
765
+ editorInstance = createEditor(mostRecentOptions);
766
+ setEditor(editorInstance);
767
+ return () => destroyUnusedEditor(editorInstance);
854
768
  }
855
- if (this.editor && !this.editor.isDestroyed) {
856
- // Destroy the editor instance if it exists
857
- this.editor.destroy();
769
+ if (!Array.isArray(deps) || deps.length === 0) {
770
+ // if the editor does exist & deps are empty, we don't need to re-initialize the editor
771
+ // we can fast-path to update the editor options on the existing instance
772
+ editorInstance.setOptions(options);
773
+ return () => destroyUnusedEditor(editorInstance);
858
774
  }
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));
775
+ // We need to destroy the editor instance and re-initialize it
776
+ // when the deps array changes
777
+ editorInstance.destroy();
778
+ // the deps array is used to re-initialize the editor instance
779
+ editorInstance = createEditor(mostRecentOptions);
780
+ setEditor(editorInstance);
781
+ return () => destroyUnusedEditor(editorInstance);
782
+ }, deps);
899
783
  // The default behavior is to re-render on each transaction
900
784
  // This is legacy behavior that will be removed in future versions
901
785
  useEditorState({
@@ -941,17 +825,16 @@ function EditorProvider({ children, slotAfter, slotBefore, ...editorOptions }) {
941
825
  }
942
826
 
943
827
  const BubbleMenu = (props) => {
944
- const [element, setElement] = useState(null);
828
+ const menuEl = useRef(document.createElement('div'));
945
829
  const { editor: currentEditor } = useCurrentEditor();
946
830
  useEffect(() => {
947
831
  var _a;
948
- if (!element) {
949
- return;
950
- }
832
+ menuEl.current.style.visibility = 'hidden';
833
+ menuEl.current.style.position = 'absolute';
951
834
  if (((_a = props.editor) === null || _a === void 0 ? void 0 : _a.isDestroyed) || (currentEditor === null || currentEditor === void 0 ? void 0 : currentEditor.isDestroyed)) {
952
835
  return;
953
836
  }
954
- const { pluginKey = 'bubbleMenu', editor, tippyOptions = {}, updateDelay, shouldShow = null, } = props;
837
+ const { pluginKey = 'bubbleMenu', editor, updateDelay, resizeDelay, shouldShow = null, } = props;
955
838
  const menuEditor = editor || currentEditor;
956
839
  if (!menuEditor) {
957
840
  console.warn('BubbleMenu component is not rendered inside of an editor component or does not have editor prop.');
@@ -959,30 +842,38 @@ const BubbleMenu = (props) => {
959
842
  }
960
843
  const plugin = BubbleMenuPlugin({
961
844
  updateDelay,
845
+ resizeDelay,
962
846
  editor: menuEditor,
963
- element,
847
+ element: menuEl.current,
964
848
  pluginKey,
965
849
  shouldShow,
966
- tippyOptions,
850
+ options: props.options,
967
851
  });
968
852
  menuEditor.registerPlugin(plugin);
969
- return () => menuEditor.unregisterPlugin(pluginKey);
970
- }, [props.editor, currentEditor, element]);
971
- return (React.createElement("div", { ref: setElement, className: props.className, style: { visibility: 'hidden' } }, props.children));
853
+ return () => {
854
+ menuEditor.unregisterPlugin(pluginKey);
855
+ window.requestAnimationFrame(() => {
856
+ if (menuEl.current.parentNode) {
857
+ menuEl.current.parentNode.removeChild(menuEl.current);
858
+ }
859
+ });
860
+ };
861
+ }, [props.editor, currentEditor]);
862
+ const portal = createPortal((React.createElement("div", { className: props.className }, props.children)), menuEl.current);
863
+ return (React.createElement(React.Fragment, null, portal));
972
864
  };
973
865
 
974
866
  const FloatingMenu = (props) => {
975
- const [element, setElement] = useState(null);
867
+ const menuEl = useRef(document.createElement('div'));
976
868
  const { editor: currentEditor } = useCurrentEditor();
977
869
  useEffect(() => {
978
870
  var _a;
979
- if (!element) {
980
- return;
981
- }
871
+ menuEl.current.style.visibility = 'hidden';
872
+ menuEl.current.style.position = 'absolute';
982
873
  if (((_a = props.editor) === null || _a === void 0 ? void 0 : _a.isDestroyed) || (currentEditor === null || currentEditor === void 0 ? void 0 : currentEditor.isDestroyed)) {
983
874
  return;
984
875
  }
985
- const { pluginKey = 'floatingMenu', editor, tippyOptions = {}, shouldShow = null, } = props;
876
+ const { pluginKey = 'floatingMenu', editor, options, shouldShow = null, } = props;
986
877
  const menuEditor = editor || currentEditor;
987
878
  if (!menuEditor) {
988
879
  console.warn('FloatingMenu component is not rendered inside of an editor component or does not have editor prop.');
@@ -991,18 +882,25 @@ const FloatingMenu = (props) => {
991
882
  const plugin = FloatingMenuPlugin({
992
883
  pluginKey,
993
884
  editor: menuEditor,
994
- element,
995
- tippyOptions,
885
+ element: menuEl.current,
886
+ options,
996
887
  shouldShow,
997
888
  });
998
889
  menuEditor.registerPlugin(plugin);
999
- return () => menuEditor.unregisterPlugin(pluginKey);
890
+ return () => {
891
+ menuEditor.unregisterPlugin(pluginKey);
892
+ window.requestAnimationFrame(() => {
893
+ if (menuEl.current.parentNode) {
894
+ menuEl.current.parentNode.removeChild(menuEl.current);
895
+ }
896
+ });
897
+ };
1000
898
  }, [
1001
899
  props.editor,
1002
900
  currentEditor,
1003
- element,
1004
901
  ]);
1005
- return (React.createElement("div", { ref: setElement, className: props.className, style: { visibility: 'hidden' } }, props.children));
902
+ const portal = createPortal((React.createElement("div", { className: props.className }, props.children)), menuEl.current);
903
+ return (React.createElement(React.Fragment, null, portal));
1006
904
  };
1007
905
 
1008
906
  const ReactNodeViewContext = createContext({