@@ -139,6 +139,8 @@ export class SideMenuView<
139139
140140 public isDragOrigin = false ;
141141
142+ public useHandleDOMEvents = false ;
143+
142144 constructor (
143145 private readonly editor : BlockNoteEditor < BSchema , I , S > ,
144146 private readonly pmView : EditorView ,
@@ -171,12 +173,13 @@ export class SideMenuView<
171173 true ,
172174 ) ;
173175
174- // Shows or updates menu position whenever the cursor moves, if the menu isn't frozen.
175- this . pmView . root . addEventListener (
176- "mousemove" ,
177- this . onMouseMove as EventListener ,
178- true ,
179- ) ;
176+ if ( ! this . useHandleDOMEvents ) {
177+ this . pmView . root . addEventListener (
178+ "mousemove" ,
179+ this . onMouseMove as EventListener ,
180+ true ,
181+ ) ;
182+ }
180183
181184 // Hides and unfreezes the menu whenever the user presses a key.
182185 this . pmView . root . addEventListener (
@@ -561,6 +564,11 @@ export class SideMenuView<
561564
562565 this . mousePos = { x : event . clientX , y : event . clientY } ;
563566
567+ if ( this . useHandleDOMEvents ) {
568+ this . updateStateFromMousePos ( ) ;
569+ return ;
570+ }
571+
564572 // We want the full area of the editor to check if the cursor is hovering
565573 // above it though.
566574 const editorOuterBoundingBox = this . pmView . dom . getBoundingClientRect ( ) ;
@@ -598,6 +606,30 @@ export class SideMenuView<
598606 this . updateStateFromMousePos ( ) ;
599607 } ;
600608
609+ onMouseLeave = ( event : MouseEvent ) => {
610+ if ( this . menuFrozen ) {
611+ return false ;
612+ }
613+
614+ const editorOuterBoundingBox = this . pmView . dom . getBoundingClientRect ( ) ;
615+ const cursorWithinEditor =
616+ event . clientX > editorOuterBoundingBox . left &&
617+ event . clientX < editorOuterBoundingBox . right &&
618+ event . clientY > editorOuterBoundingBox . top &&
619+ event . clientY < editorOuterBoundingBox . bottom ;
620+
621+ if ( cursorWithinEditor ) {
622+ return false ;
623+ }
624+
625+ if ( this . state ?. show ) {
626+ this . state . show = false ;
627+ this . emitUpdate ( this . state ) ;
628+ }
629+
630+ return false ;
631+ } ;
632+
601633 private dispatchSyntheticEvent ( event : DragEvent ) {
602634 const evt = new Event ( event . type as "dragover" , event ) as any ;
603635 const dropPointBoundingBox = (
@@ -648,11 +680,15 @@ export class SideMenuView<
648680 this . state . show = false ;
649681 this . emitUpdate ( this . state ) ;
650682 }
651- this . pmView . root . removeEventListener (
652- "mousemove" ,
653- this . onMouseMove as EventListener ,
654- true ,
655- ) ;
683+
684+ if ( ! this . useHandleDOMEvents ) {
685+ this . pmView . root . removeEventListener (
686+ "mousemove" ,
687+ this . onMouseMove as EventListener ,
688+ true ,
689+ ) ;
690+ }
691+
656692 this . pmView . root . removeEventListener (
657693 "dragstart" ,
658694 this . onDragStart as EventListener ,
@@ -678,6 +714,26 @@ export class SideMenuView<
678714 ) ;
679715 this . pmView . root . removeEventListener ( "scroll" , this . onScroll , true ) ;
680716 }
717+
718+ setUseHandleDOMEvents = ( value : boolean ) => {
719+ if ( ! this . useHandleDOMEvents && value ) {
720+ this . pmView . root . removeEventListener (
721+ "mousemove" ,
722+ this . onMouseMove as EventListener ,
723+ true ,
724+ ) ;
725+ }
726+
727+ if ( this . useHandleDOMEvents && ! value ) {
728+ this . pmView . root . addEventListener (
729+ "mousemove" ,
730+ this . onMouseMove as EventListener ,
731+ true ,
732+ ) ;
733+ }
734+
735+ this . useHandleDOMEvents = value ;
736+ } ;
681737}
682738
683739export const sideMenuPluginKey = new PluginKey ( "SideMenuPlugin" ) ;
@@ -704,6 +760,22 @@ export class SideMenuProsemirrorPlugin<
704760 } ) ;
705761 return this . view ;
706762 } ,
763+ props : {
764+ handleDOMEvents : {
765+ mousemove : ( _view , event ) => {
766+ if ( this . view ?. useHandleDOMEvents ) {
767+ this . view . onMouseMove ( event ) ;
768+ }
769+ return false ;
770+ } ,
771+ mouseleave : ( _view , event ) => {
772+ if ( this . view ?. useHandleDOMEvents ) {
773+ this . view . onMouseLeave ( event ) ;
774+ }
775+ return false ;
776+ } ,
777+ } ,
778+ } ,
707779 } ) ,
708780 ) ;
709781 }
@@ -739,6 +811,15 @@ export class SideMenuProsemirrorPlugin<
739811 this . view . isDragOrigin = false ;
740812 }
741813 } ;
814+ /**
815+ * Sets whether to use ProseMirror's handleDOMEvents for mousemove tracking instead of addEventListener.
816+ *
817+ * - When `true`: Uses handleDOMEvents (mousemove + mouseleave) - scoped to ProseMirror
818+ * - When `false` (default): Uses addEventListener on root element - original behavior
819+ */
820+ setUseHandleDOMEvents = ( value : boolean ) => {
821+ this . view ?. setUseHandleDOMEvents ( value ) ;
822+ } ;
742823 /**
743824 * Freezes the side menu. When frozen, the side menu will stay
744825 * attached to the same block regardless of which block is hovered by the
0 commit comments