Skip to content

Commit d7e7092

Browse files
committed
Fixed SideMenu/BlockPositioner updating bug
1 parent e85065b commit d7e7092

File tree

3 files changed

+23
-33
lines changed

3 files changed

+23
-33
lines changed

packages/core/src/extensions/SideMenu/SideMenu.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -187,11 +187,6 @@ export class SideMenuView<
187187
this.onKeyDown as EventListener,
188188
true,
189189
);
190-
191-
// Setting capture=true ensures that any parent container of the editor that
192-
// gets scrolled will trigger the scroll event. Scroll events do not bubble
193-
// and so won't propagate to the document by default.
194-
pmView.root.addEventListener("scroll", this.onScroll, true);
195190
}
196191

197192
updateState = (state: SideMenuState<BSchema, I, S>) => {
@@ -624,14 +619,6 @@ export class SideMenuView<
624619
this.pmView.dom.dispatchEvent(evt);
625620
}
626621

627-
onScroll = () => {
628-
if (this.state?.show) {
629-
this.state.referencePos = this.hoveredBlock!.getBoundingClientRect();
630-
this.emitUpdate(this.state);
631-
}
632-
this.updateStateFromMousePos();
633-
};
634-
635622
// Needed in cases where the editor state updates without the mouse cursor
636623
// moving, as some state updates can require a side menu update. For example,
637624
// adding a button to the side menu which removes the block can cause the
@@ -679,7 +666,6 @@ export class SideMenuView<
679666
this.onKeyDown as EventListener,
680667
true,
681668
);
682-
this.pmView.root.removeEventListener("scroll", this.onScroll, true);
683669
}
684670
}
685671

packages/react/src/components/Popovers/BlockPopover.tsx

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getNodeById } from "@blocknote/core";
2-
import { ClientRectObject, VirtualElement } from "@floating-ui/react";
2+
import { VirtualElement } from "@floating-ui/react";
33
import { ReactNode, useMemo, useRef } from "react";
44

55
import { 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
);

packages/react/src/components/SideMenu/SideMenuController.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const SideMenuController = (props: {
4646
const Component = props.sideMenu || SideMenu;
4747

4848
return (
49-
<BlockPopover blockId={block?.id} {...floatingUIOptions}>
49+
<BlockPopover blockId={show ? block?.id : undefined} {...floatingUIOptions}>
5050
{block?.id && <Component />}
5151
</BlockPopover>
5252
);

0 commit comments

Comments
 (0)