@@ -175,6 +175,54 @@ function isOnMenu(element: HTMLElement): boolean {
175175 return ! ! element . closest ( `.${ DRAGGABLE_BLOCK_MENU_CLASSNAME } ` ) ;
176176}
177177
178+ /**
179+ * Helper function to find the scrolling container and get scroll offsets.
180+ * Checks if the element itself has scrollable overflow, then checks parent.
181+ */
182+ function getScrollingContainer ( elem : HTMLElement ) : {
183+ element : HTMLElement ;
184+ scrollTop : number ;
185+ scrollLeft : number ;
186+ } {
187+ // Check if the element itself is scrollable
188+ const style = window . getComputedStyle ( elem ) ;
189+ const hasScrollY = style . overflowY === 'auto' || style . overflowY === 'scroll' ;
190+ const hasScrollX = style . overflowX === 'auto' || style . overflowX === 'scroll' ;
191+
192+ if ( hasScrollY || hasScrollX ) {
193+ return {
194+ element : elem ,
195+ scrollTop : elem . scrollTop || 0 ,
196+ scrollLeft : elem . scrollLeft || 0 ,
197+ } ;
198+ }
199+
200+ // Check parent element
201+ const parent = elem . parentElement ;
202+ if ( parent ) {
203+ const parentStyle = window . getComputedStyle ( parent ) ;
204+ const parentHasScrollY =
205+ parentStyle . overflowY === 'auto' || parentStyle . overflowY === 'scroll' ;
206+ const parentHasScrollX =
207+ parentStyle . overflowX === 'auto' || parentStyle . overflowX === 'scroll' ;
208+
209+ if ( parentHasScrollY || parentHasScrollX ) {
210+ return {
211+ element : parent ,
212+ scrollTop : parent . scrollTop || 0 ,
213+ scrollLeft : parent . scrollLeft || 0 ,
214+ } ;
215+ }
216+ }
217+
218+ // No scrolling container found
219+ return {
220+ element : elem ,
221+ scrollTop : 0 ,
222+ scrollLeft : 0 ,
223+ } ;
224+ }
225+
178226function setMenuPosition (
179227 targetElem : HTMLElement | null ,
180228 floatingElem : HTMLElement ,
@@ -191,11 +239,12 @@ function setMenuPosition(
191239 const floatingElemRect = floatingElem . getBoundingClientRect ( ) ;
192240 const anchorElementRect = anchorElem . getBoundingClientRect ( ) ;
193241
194- const top =
195- targetRect . top +
196- ( parseInt ( targetStyle . lineHeight , 10 ) - floatingElemRect . height ) / 2 -
197- anchorElementRect . top ;
242+ const { scrollTop } = getScrollingContainer ( anchorElem ) ;
198243
244+ // Calculate position with scroll compensation
245+ const lineHeight = parseInt ( targetStyle . lineHeight , 10 ) ;
246+ const topOffset = ( lineHeight - floatingElemRect . height ) / 2 ;
247+ const top = targetRect . top + topOffset - anchorElementRect . top + scrollTop ;
199248 const left = SPACE ;
200249
201250 floatingElem . style . opacity = '1' ;
@@ -228,14 +277,17 @@ function setTargetLine(
228277 const { top : anchorTop , width : anchorWidth } =
229278 anchorElem . getBoundingClientRect ( ) ;
230279 const { marginTop, marginBottom } = getCollapsedMargins ( targetBlockElem ) ;
280+
281+ const { scrollTop } = getScrollingContainer ( anchorElem ) ;
282+
231283 let lineTop = targetBlockElemTop ;
232284 if ( mouseY >= targetBlockElemTop ) {
233285 lineTop += targetBlockElemHeight + marginBottom / 2 ;
234286 } else {
235287 lineTop -= marginTop / 2 ;
236288 }
237289
238- const top = lineTop - anchorTop - TARGET_LINE_HALF_HEIGHT ;
290+ const top = lineTop - anchorTop - TARGET_LINE_HALF_HEIGHT + scrollTop ;
239291 const left = TEXT_BOX_HORIZONTAL_PADDING - SPACE ;
240292
241293 targetLineElem . style . transform = `translate(${ left } px, ${ top } px)` ;
@@ -257,14 +309,17 @@ function useDraggableBlockMenu(
257309 anchorElem : HTMLElement ,
258310 isEditable : boolean ,
259311) : JSX . Element {
260- const scrollerElem = anchorElem . parentElement ;
261-
262312 const menuRef = useRef < HTMLDivElement > ( null ) ;
263313 const targetLineRef = useRef < HTMLDivElement > ( null ) ;
264314 const isDraggingBlockRef = useRef < boolean > ( false ) ;
265315 const [ draggableBlockElem , setDraggableBlockElem ] =
266316 useState < HTMLElement | null > ( null ) ;
267317
318+ // Get theme classes from editor config
319+ const theme = editor . _config . theme ;
320+ const menuThemeClass = theme . draggableBlockMenu || '' ;
321+ const targetLineThemeClass = theme . draggableBlockTargetLine || '' ;
322+
268323 useEffect ( ( ) => {
269324 function onMouseMove ( event : MouseEvent ) {
270325 const target = event . target ;
@@ -273,6 +328,11 @@ function useDraggableBlockMenu(
273328 return ;
274329 }
275330
331+ // Performance optimization: Early exit if mouse is outside editor area
332+ if ( ! anchorElem . contains ( target ) ) {
333+ return ;
334+ }
335+
276336 if ( isOnMenu ( target ) ) {
277337 return ;
278338 }
@@ -286,14 +346,17 @@ function useDraggableBlockMenu(
286346 setDraggableBlockElem ( null ) ;
287347 }
288348
289- scrollerElem ?. addEventListener ( 'mousemove' , onMouseMove ) ;
290- scrollerElem ?. addEventListener ( 'mouseleave' , onMouseLeave ) ;
349+ // BUGFIX: Attach to document instead of scrollerElem to ensure mouse events
350+ // work even after scrolling. The scrollerElem approach fails when content scrolls
351+ // because the mouse event coordinates become misaligned with element positions.
352+ document . addEventListener ( 'mousemove' , onMouseMove ) ;
353+ document . addEventListener ( 'mouseleave' , onMouseLeave ) ;
291354
292355 return ( ) => {
293- scrollerElem ? .removeEventListener ( 'mousemove' , onMouseMove ) ;
294- scrollerElem ? .removeEventListener ( 'mouseleave' , onMouseLeave ) ;
356+ document . removeEventListener ( 'mousemove' , onMouseMove ) ;
357+ document . removeEventListener ( 'mouseleave' , onMouseLeave ) ;
295358 } ;
296- } , [ scrollerElem , anchorElem , editor ] ) ;
359+ } , [ anchorElem , editor ] ) ;
297360
298361 useEffect ( ( ) => {
299362 if ( menuRef . current ) {
@@ -412,15 +475,18 @@ function useDraggableBlockMenu(
412475 return createPortal (
413476 < >
414477 < div
415- className = " icon draggable-block-menu"
478+ className = { ` icon draggable-block-menu ${ menuThemeClass } ` }
416479 ref = { menuRef }
417480 draggable
418481 onDragStart = { onDragStart }
419482 onDragEnd = { onDragEnd }
420483 >
421484 < div className = { isEditable ? 'icon' : '' } />
422485 </ div >
423- < div className = "draggable-block-target-line" ref = { targetLineRef } />
486+ < div
487+ className = { `draggable-block-target-line ${ targetLineThemeClass } ` }
488+ ref = { targetLineRef }
489+ />
424490 </ > ,
425491 anchorElem ,
426492 ) ;
0 commit comments