@@ -22,44 +22,83 @@ const DROP_INDICATOR_CLASSNAME = "memo-drop-indicator";
2222// Position drag handle within the anchor's left padding (anchor has pl-8 = 32px)
2323const HANDLE_LEFT_OFFSET = 0 ;
2424// Vertical dead zone — mouse must be within this distance of a block to show handle
25- const HANDLE_DEAD_ZONE = 4 ;
26-
27- function getBlockElement (
28- anchorElem : HTMLElement ,
29- editor : LexicalEditor ,
30- event : MouseEvent
31- ) : HTMLElement | null {
32- const editorBounds = anchorElem . getBoundingClientRect ( ) ;
33- const y = event . clientY ;
34-
35- // Walk through top-level block elements in the editor
36- let blockElem : HTMLElement | null = null ;
37- let minDistance = Infinity ;
25+ const HANDLE_DEAD_ZONE = 16 ;
3826
27+ function getTopLevelBlockElements (
28+ anchorElem : HTMLElement
29+ ) : HTMLCollectionOf < Element > | NodeListOf < Element > | undefined {
3930 const children = anchorElem . querySelectorAll (
4031 "[data-lexical-editor] > *"
4132 ) ;
33+ if ( children . length > 0 ) return children ;
4234
43- // If no direct children found, try the contentEditable's children
4435 const contentEditable = anchorElem . querySelector (
4536 '[contenteditable="true"]'
4637 ) ;
47- const elements = children . length > 0 ? children : contentEditable ?. children ;
38+ return contentEditable ?. children ;
39+ }
4840
41+ function getBlockElement (
42+ anchorElem : HTMLElement ,
43+ editor : LexicalEditor ,
44+ event : MouseEvent ,
45+ /** When true (during drag), always return the nearest block regardless of dead zone */
46+ useUnboundedSearch = false
47+ ) : HTMLElement | null {
48+ const editorBounds = anchorElem . getBoundingClientRect ( ) ;
49+ const y = event . clientY ;
50+
51+ const elements = getTopLevelBlockElements ( anchorElem ) ;
4952 if ( ! elements ) return null ;
5053
54+ // First pass: find the closest block within the dead zone
55+ let blockElem : HTMLElement | null = null ;
56+ let minDistance = Infinity ;
57+
5158 for ( let i = 0 ; i < elements . length ; i ++ ) {
5259 const elem = elements [ i ] as HTMLElement ;
5360 const rect = elem . getBoundingClientRect ( ) ;
5461 const centerY = rect . top + rect . height / 2 ;
5562 const distance = Math . abs ( y - centerY ) ;
5663
57- if ( distance < minDistance && y >= rect . top - HANDLE_DEAD_ZONE && y <= rect . bottom + HANDLE_DEAD_ZONE ) {
64+ if (
65+ distance < minDistance &&
66+ y >= rect . top - HANDLE_DEAD_ZONE &&
67+ y <= rect . bottom + HANDLE_DEAD_ZONE
68+ ) {
5869 minDistance = distance ;
5970 blockElem = elem ;
6071 }
6172 }
6273
74+ // Fallback: if nothing matched within the dead zone (cursor is in a gap
75+ // between blocks or beyond the last block), find the absolute nearest block.
76+ // Always used during drag; for hover, only when cursor is within the editor
77+ // vertical bounds.
78+ if ( ! blockElem ) {
79+ const withinEditorY =
80+ y >= editorBounds . top && y <= editorBounds . bottom ;
81+
82+ if ( useUnboundedSearch || withinEditorY ) {
83+ let fallbackDistance = Infinity ;
84+ for ( let i = 0 ; i < elements . length ; i ++ ) {
85+ const elem = elements [ i ] as HTMLElement ;
86+ const rect = elem . getBoundingClientRect ( ) ;
87+ // Distance to the nearest edge of the element
88+ const dist =
89+ y < rect . top
90+ ? rect . top - y
91+ : y > rect . bottom
92+ ? y - rect . bottom
93+ : 0 ;
94+ if ( dist < fallbackDistance ) {
95+ fallbackDistance = dist ;
96+ blockElem = elem ;
97+ }
98+ }
99+ }
100+ }
101+
63102 // Verify the mouse is within the editor bounds horizontally
64103 if (
65104 blockElem &&
@@ -245,7 +284,12 @@ export function DraggableBlockPlugin({
245284
246285 event . preventDefault ( ) ;
247286
248- const blockElem = getBlockElement ( anchorElem , editor , event ) ;
287+ const blockElem = getBlockElement (
288+ anchorElem ,
289+ editor ,
290+ event ,
291+ true
292+ ) ;
249293 if ( ! blockElem || ! dropIndicatorRef . current ) return true ;
250294
251295 const blockRect = blockElem . getBoundingClientRect ( ) ;
@@ -275,7 +319,12 @@ export function DraggableBlockPlugin({
275319 const nodeKey = event . dataTransfer . getData ( DRAG_DATA_FORMAT ) ;
276320 if ( ! nodeKey ) return true ;
277321
278- const blockElem = getBlockElement ( anchorElem , editor , event ) ;
322+ const blockElem = getBlockElement (
323+ anchorElem ,
324+ editor ,
325+ event ,
326+ true
327+ ) ;
279328 if ( ! blockElem ) return true ;
280329
281330 const blockRect = blockElem . getBoundingClientRect ( ) ;
0 commit comments