Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 92 additions & 11 deletions packages/core/src/extensions/SideMenu/SideMenuPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ export class SideMenuView<

public isDragOrigin = false;

public useHandleDOMEvents = false;

constructor(
private readonly editor: BlockNoteEditor<BSchema, I, S>,
private readonly pmView: EditorView,
Expand Down Expand Up @@ -171,12 +173,13 @@ export class SideMenuView<
true,
);

// Shows or updates menu position whenever the cursor moves, if the menu isn't frozen.
this.pmView.root.addEventListener(
"mousemove",
this.onMouseMove as EventListener,
true,
);
if (!this.useHandleDOMEvents) {
this.pmView.root.addEventListener(
"mousemove",
this.onMouseMove as EventListener,
true,
);
}

// Hides and unfreezes the menu whenever the user presses a key.
this.pmView.root.addEventListener(
Expand Down Expand Up @@ -561,6 +564,11 @@ export class SideMenuView<

this.mousePos = { x: event.clientX, y: event.clientY };

if (this.useHandleDOMEvents) {
this.updateStateFromMousePos();
return;
}

// We want the full area of the editor to check if the cursor is hovering
// above it though.
const editorOuterBoundingBox = this.pmView.dom.getBoundingClientRect();
Expand Down Expand Up @@ -598,6 +606,30 @@ export class SideMenuView<
this.updateStateFromMousePos();
};

onMouseLeave = (event: MouseEvent) => {
if (this.menuFrozen) {
return false;
}

const editorOuterBoundingBox = this.pmView.dom.getBoundingClientRect();
const cursorWithinEditor =
event.clientX > editorOuterBoundingBox.left &&
event.clientX < editorOuterBoundingBox.right &&
event.clientY > editorOuterBoundingBox.top &&
event.clientY < editorOuterBoundingBox.bottom;

if (cursorWithinEditor) {
return false;
}

if (this.state?.show) {
this.state.show = false;
this.emitUpdate(this.state);
}

return false;
};

private dispatchSyntheticEvent(event: DragEvent) {
const evt = new Event(event.type as "dragover", event) as any;
const dropPointBoundingBox = (
Expand Down Expand Up @@ -648,11 +680,15 @@ export class SideMenuView<
this.state.show = false;
this.emitUpdate(this.state);
}
this.pmView.root.removeEventListener(
"mousemove",
this.onMouseMove as EventListener,
true,
);

if (!this.useHandleDOMEvents) {
this.pmView.root.removeEventListener(
"mousemove",
this.onMouseMove as EventListener,
true,
);
}

this.pmView.root.removeEventListener(
"dragstart",
this.onDragStart as EventListener,
Expand All @@ -678,6 +714,26 @@ export class SideMenuView<
);
this.pmView.root.removeEventListener("scroll", this.onScroll, true);
}

setUseHandleDOMEvents = (value: boolean) => {
if (!this.useHandleDOMEvents && value) {
this.pmView.root.removeEventListener(
"mousemove",
this.onMouseMove as EventListener,
true,
);
}

if (this.useHandleDOMEvents && !value) {
this.pmView.root.addEventListener(
"mousemove",
this.onMouseMove as EventListener,
true,
);
}

this.useHandleDOMEvents = value;
};
}

export const sideMenuPluginKey = new PluginKey("SideMenuPlugin");
Expand All @@ -704,6 +760,22 @@ export class SideMenuProsemirrorPlugin<
});
return this.view;
},
props: {
handleDOMEvents: {
mousemove: (_view, event) => {
if (this.view?.useHandleDOMEvents) {
this.view.onMouseMove(event);
}
return false;
},
mouseleave: (_view, event) => {
if (this.view?.useHandleDOMEvents) {
this.view.onMouseLeave(event);
}
return false;
},
},
},
}),
);
}
Expand Down Expand Up @@ -739,6 +811,15 @@ export class SideMenuProsemirrorPlugin<
this.view.isDragOrigin = false;
}
};
/**
* Sets whether to use ProseMirror's handleDOMEvents for mousemove tracking instead of addEventListener.
*
* - When `true`: Uses handleDOMEvents (mousemove + mouseleave) - scoped to ProseMirror
* - When `false` (default): Uses addEventListener on root element - original behavior
*/
setUseHandleDOMEvents = (value: boolean) => {
this.view?.setUseHandleDOMEvents(value);
};
/**
* Freezes the side menu. When frozen, the side menu will stay
* attached to the same block regardless of which block is hovered by the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
InlineContentSchema,
StyleSchema,
} from "@blocknote/core";
import { FC } from "react";
import { FC, useEffect } from "react";

import { UseFloatingOptions } from "@floating-ui/react";
import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js";
Expand All @@ -22,6 +22,7 @@ export const SideMenuController = <
>(props: {
sideMenu?: FC<SideMenuProps<BSchema, I, S>>;
floatingOptions?: Partial<UseFloatingOptions>;
useHandleDOMEvents?: boolean;
}) => {
const editor = useBlockNoteEditor<BSchema, I, S>();

Expand All @@ -32,6 +33,12 @@ export const SideMenuController = <
unfreezeMenu: editor.sideMenu.unfreezeMenu,
};

useEffect(() => {
if (props.useHandleDOMEvents) {
editor.sideMenu.setUseHandleDOMEvents(props.useHandleDOMEvents);
}
}, [editor.sideMenu, props.useHandleDOMEvents]);

const state = useUIPluginState(
editor.sideMenu.onUpdate.bind(editor.sideMenu),
);
Expand Down
Loading