11import { getNodeById } from "@blocknote/core" ;
2- import { ClientRectObject , VirtualElement } from "@floating-ui/react" ;
2+ import { VirtualElement } from "@floating-ui/react" ;
33import { ReactNode , useMemo , useRef } from "react" ;
44
55import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js" ;
@@ -16,40 +16,44 @@ export const BlockPopover = (
1616
1717 const editor = useBlockNoteEditor < any , any , any > ( ) ;
1818
19- // Stores the last `boundingClientRect` to use from when the block's DOM
20- // element was still mounted. This is because `DOMRect`s returned from
21- // calling `getBoundingClientRect` on unmounted elements will have x, y,
22- // height, and width of 0. This can cause issues when the popover is
23- // transitioning out.
19+ // Stores the last created `boundingClientRect` to use in case `blockId` is
20+ // not defined, or the block could not be found in the editor.
2421 // TODO: Move this logic to the `GenericPopover`.
25- const mountedBoundingClientRect = useRef < ClientRectObject > ( new DOMRect ( ) ) ;
26- const virtualElement = useMemo (
22+ const boundingClientRect = useRef < DOMRect > ( new DOMRect ( ) ) ;
23+ // Uses a virtual element instead of directly using the block's DOM element
24+ // to better handle when the block is deleted from the editor. When using the
25+ // DOM element directly, this will cause the element to unmount. FloatingUI
26+ // will then call `getBoundingClientRect` on a the unmounted DOM element,
27+ // resulting in an incorrect `DOMRect` with x, y, height, and width of 0.
28+ // With a virtual element, we can instead use the last `DOMRect` from when
29+ // the block still existed.
30+ const virtualElement = useMemo < VirtualElement > (
2731 ( ) =>
2832 editor . transact ( ( tr ) => {
29- const virtualElement : VirtualElement = {
30- getBoundingClientRect : ( ) => mountedBoundingClientRect . current ,
31- } ;
32-
3333 if ( ! blockId ) {
34- return virtualElement ;
34+ return { getBoundingClientRect : ( ) => boundingClientRect . current } ;
3535 }
3636
3737 // TODO use the location API for this
3838 const nodePosInfo = getNodeById ( blockId , tr . doc ) ;
3939 if ( ! nodePosInfo ) {
40- return virtualElement ;
40+ return { getBoundingClientRect : ( ) => boundingClientRect . current } ;
4141 }
4242
4343 const { node } = editor . prosemirrorView . domAtPos (
4444 nodePosInfo . posBeforeNode + 1 ,
4545 ) ;
4646 if ( ! ( node instanceof Element ) ) {
47- return virtualElement ;
47+ return { getBoundingClientRect : ( ) => boundingClientRect . current } ;
4848 }
4949
50- mountedBoundingClientRect . current = node . getBoundingClientRect ( ) ;
51-
52- return virtualElement ;
50+ return {
51+ getBoundingClientRect : ( ) => {
52+ boundingClientRect . current = node . getBoundingClientRect ( ) ;
53+ return boundingClientRect . current ;
54+ } ,
55+ contextElement : node ,
56+ } ;
5357 } ) ,
5458 [ editor , blockId ] ,
5559 ) ;
0 commit comments