@@ -25,7 +25,7 @@ export interface Stat<T> {
2525 draggable : boolean ,
2626}
2727
28- export type NodeAttrs = { 'data-key' : string , 'data-level' : string , 'data-node-box' : boolean , 'data-drag-placeholder' ?: boolean } & React . HTMLProps < HTMLDivElement >
28+ export type NodeAttrs = { 'data-key' : string , 'data-level' : string , 'data-node-box' : boolean , 'data-drag-placeholder' ?: boolean , 'data-dragging' ?: boolean } & React . HTMLProps < HTMLDivElement >
2929
3030// single instance ==================================
3131const dragOverInfo = {
@@ -217,6 +217,7 @@ export function useHeTree<T extends Record<string, any>>(
217217 zIndex : '-999999999' ,
218218 visibility : 'hidden' ,
219219 } )
220+ attrs [ 'data-dragging' ] = true
220221 }
221222 attrsList . push ( attrs )
222223 visibleIds . push ( stat . id )
@@ -308,159 +309,168 @@ export function useHeTree<T extends Record<string, any>>(
308309 } , 0 )
309310 props . onDragStart ?.( e , stat )
310311 } ,
311- onDragOver ( e ) {
312- if ( isExternal && ! props . onExternalDragOver ?.( e ) ) {
313- return
314- }
315- // dragOpen ========================
316- const shouldDragOpen = ( ) => {
317- if ( ! props . dragOpen ) {
318- return false
319- }
320- if ( isPlaceholder ) {
321- return false
322- }
323- if ( stat . open ) {
324- return false
325- }
326-
327- const refresh = ( ) => Object . assign ( dragOverInfo , { id : stat . id , x : e . pageX , y : e . pageY , time : Date . now ( ) } )
328- if ( dragOverInfo . id !== stat . id ) {
329- refresh ( )
330- return false
331- }
332- if ( calculateDistance ( e . pageX , e . pageY , dragOverInfo . x , dragOverInfo . y ) > 10 ) {
333- refresh ( )
334- return false
335- }
336- const now = Date . now ( )
337- if ( now - dragOverInfo . time >= props . dragOpenDelay ! ) {
338- return true
339- }
340- }
341- if ( shouldDragOpen ( ) ) {
342- props . onDragOpen ! ( stat )
343- }
344- // dragOpen end ========================
345- let t = findClosestAndNext ( stat , isPlaceholder )
346- const { closest, next } = t
347-
348- let { atTop } = t
349- const rootEl = rootRef . current !
350- // @ts -ignore
351- const nodeBox = hp . findParent ( e . target , ( el ) => el . hasAttribute ( 'data-node-box' ) , { withSelf : true } )
352- // node start position
353- const getPlaceholderLevel = ( ) => {
354- let rect = nodeBox . getBoundingClientRect ( )
355- let pl
356- if ( ! rtl ) {
357- // ltr
358- pl = Math . ceil ( ( e . pageX - rect . x ) / indent )
359- } else {
360- pl = Math . ceil ( ( rect . right - e . pageX ) / indent )
361- }
362- return hp . between ( pl , 0 , ( closest ?. level || 0 ) + 1 )
363- }
364- let placeholderLevel = getPlaceholderLevel ( ) // use this number to detect placeholder position. >= 0: prepend. < 0: after. }
365- if ( ! atTop && ! isPlaceholder && closest . id === rootIds [ 0 ] ) {
366- // check if at top
367- const topNodeElement = rootEl . querySelector ( `[data-key="${ closest . id } "]` )
368- if ( topNodeElement ) {
369- const rect = topNodeElement . getBoundingClientRect ( )
370- atTop = rect . y + rect . height / 2 > e . pageY
371- }
312+ onDragLeave ( e ) {
313+ // dragLeave behavior is not expected. https://stackoverflow.com/questions/7110353/html5-dragleave-fired-when-hovering-a-child-element
314+ } ,
315+ }
316+ }
317+ const onDragOverRoot : React . DragEventHandler < HTMLElement > = ( e ) => {
318+ if ( isExternal && ! props . onExternalDragOver ?.( e ) ) {
319+ return
320+ }
321+ const el = getClosestEl ( )
322+ if ( el ) {
323+ // @ts -ignore
324+ const stat = getStat ( el . getAttribute ( 'data-key' ) )
325+ const isPlaceholder = ! ! el . getAttribute ( 'data-drag-placeholder' )
326+ if ( shouldDragOpen ( stat , isPlaceholder ) ) {
327+ props . onDragOpen ! ( stat )
328+ }
329+ let t = findClosestAndNext ( stat , isPlaceholder )
330+ const { closest, next } = t
331+ let { atTop } = t
332+ const rootEl = rootRef . current !
333+ // @ts -ignore
334+ const nodeBox = el
335+ // node start position
336+ const getPlaceholderLevel = ( ) => {
337+ let rect = nodeBox . getBoundingClientRect ( )
338+ let pl
339+ if ( ! rtl ) {
340+ // ltr
341+ pl = Math . ceil ( ( e . pageX - rect . x ) / indent )
342+ } else {
343+ pl = Math . ceil ( ( rect . right - e . pageX ) / indent )
372344 }
373- if ( atTop ) {
374- placeholderLevel = 0
345+ return hp . between ( pl , 0 , ( closest ?. level || 0 ) + 1 )
346+ }
347+ let placeholderLevel = getPlaceholderLevel ( ) // use this number to detect placeholder position. >= 0: prepend. < 0: after. }
348+ if ( ! atTop && ! isPlaceholder && closest . id === rootIds [ 0 ] ) {
349+ // check if at top
350+ const topNodeElement = rootEl . querySelector ( `[data-key="${ closest . id } "]` )
351+ if ( topNodeElement ) {
352+ const rect = topNodeElement . getBoundingClientRect ( )
353+ atTop = rect . y + rect . height / 2 > e . pageY
375354 }
376- //
377- let newPlaceholder : typeof placeholder
378- if ( atTop ) {
355+ }
356+ if ( atTop ) {
357+ placeholderLevel = 0
358+ }
359+ //
360+ let newPlaceholder : typeof placeholder
361+ if ( atTop ) {
362+ if ( getDroppable ( null , 0 ) ) {
379363 newPlaceholder = {
380364 ...placeholder ! ,
381365 parentStat : null ,
382366 level : 1 ,
383367 index : 0 ,
384368 }
385- } else {
386- const parentMinLevel = next ? next . level - 1 : 0
387- // find all droppable positions
388- const availablePositionsLeft : { parentStat : Stat < T > , index : number } [ ] = [ ] ;
389- const availablePositionsRight : typeof availablePositionsLeft = [ ] ;
390- let cur = closest
391- const curLevel = ( ) => cur ? cur . level : 0
392- while ( curLevel ( ) >= parentMinLevel ) {
393- const index = getTargetIndex ( cur , next )
394- if ( getDroppable ( cur , index ) ) {
395- ( placeholderLevel > curLevel ( ) ? availablePositionsLeft : availablePositionsRight ) . unshift ( {
396- parentStat : cur ,
397- index,
398- } )
399- }
400- if ( ! cur ) {
401- break
402- }
403- cur = cur . parentStat !
404- }
405- let placeholderPosition = hp . arrayLast ( availablePositionsLeft )
406- if ( ! placeholderPosition ) {
407- placeholderPosition = hp . arrayFirst ( availablePositionsRight )
369+ }
370+ } else {
371+ const parentMinLevel = next ? next . level - 1 : 0
372+ // find all droppable positions
373+ const availablePositionsLeft : { parentStat : Stat < T > , index : number } [ ] = [ ] ;
374+ const availablePositionsRight : typeof availablePositionsLeft = [ ] ;
375+ let cur = closest
376+ const curLevel = ( ) => cur ? cur . level : 0
377+ while ( curLevel ( ) >= parentMinLevel ) {
378+ const index = getTargetIndex ( cur , next )
379+ if ( getDroppable ( cur , index ) ) {
380+ ( placeholderLevel > curLevel ( ) ? availablePositionsLeft : availablePositionsRight ) . unshift ( {
381+ parentStat : cur ,
382+ index,
383+ } )
408384 }
409- if ( placeholderPosition ) {
410- newPlaceholder = {
411- ...placeholder ! ,
412- parentStat : placeholderPosition . parentStat ,
413- level : ( placeholderPosition . parentStat ?. level ?? 0 ) + 1 ,
414- index : placeholderPosition . index ,
415- }
416- } else {
417- //
385+ if ( ! cur ) {
386+ break
418387 }
388+ cur = cur . parentStat !
419389 }
420- setPlaceholder ( newPlaceholder )
421- if ( newPlaceholder ) {
422- e . preventDefault ( ) ; // call mean droppable
390+ let placeholderPosition = hp . arrayLast ( availablePositionsLeft )
391+ if ( ! placeholderPosition ) {
392+ placeholderPosition = hp . arrayFirst ( availablePositionsRight )
423393 }
424- setDragOverStat ( isPlaceholder ? undefined : stat )
425- props . onDragOver ?.( e , stat , isExternal )
426- } ,
427- onDragLeave ( e ) {
428- // dragLeave behavior is not expected. https://stackoverflow.com/questions/7110353/html5-dragleave-fired-when-hovering-a-child-element
429- } ,
430- }
431- }
432- const onDragOverRoot : React . DragEventHandler < HTMLElement > = ( e ) => {
433- if ( isExternal && ! props . onExternalDragOver ?.( e ) ) {
434- return
435- }
436- if ( getDroppable ( null , 0 ) ) {
437- setPlaceholder ( {
438- ...placeholder ! ,
439- parentStat : null ,
440- level : 1 ,
441- index : 0 ,
442- } )
443- e . preventDefault ( ) ; // droppable
444- }
445- function isAnyNodeOver ( ) {
446- let r = false
447- const el = e . target as HTMLElement
448- if ( el ) {
449- for ( const parent of walkParentsGenerator ( el , 'parentElement' , { withSelf : true } ) ) {
450- if ( parent . hasAttribute ( 'data-node-box' ) ) {
451- r = true
452- break
453- }
454- if ( parent === rootRef . current ) {
455- break
394+ if ( placeholderPosition ) {
395+ newPlaceholder = {
396+ ...placeholder ! ,
397+ parentStat : placeholderPosition . parentStat ,
398+ level : ( placeholderPosition . parentStat ?. level ?? 0 ) + 1 ,
399+ index : placeholderPosition . index ,
456400 }
401+ } else {
402+ //
457403 }
458404 }
459- return r
405+ setPlaceholder ( newPlaceholder )
406+ if ( newPlaceholder ) {
407+ e . preventDefault ( ) ; // call mean droppable
408+ }
409+ setDragOverStat ( isPlaceholder ? undefined : stat )
410+ props . onDragOver ?.( e , stat , isExternal )
411+ } else {
412+ if ( getDroppable ( null , 0 ) ) {
413+ setPlaceholder ( {
414+ ...placeholder ! ,
415+ parentStat : null ,
416+ level : 1 ,
417+ index : 0 ,
418+ } )
419+ e . preventDefault ( ) ; // droppable
420+ }
460421 }
461- function getCloest ( ) {
422+
423+ function getClosestEl ( ) {
462424 const rootEl = rootRef . current as HTMLElement
463- // rootEl.querySelectorAll([])
425+ const nodeEls = rootEl . querySelectorAll ( `[data-node-box]:not([data-dragging])` ) ;
426+ const t = hp . binarySearch (
427+ // @ts -ignore
428+ nodeEls ,
429+ ( nodeEl : HTMLElement ) =>
430+ nodeEl . getBoundingClientRect ( ) . top -
431+ e . pageY ,
432+ { returnNearestIfNoHit : true }
433+ ) ! ;
434+ let index : number | undefined
435+ if ( t . hit ) {
436+ } else {
437+ if ( t . greater ) {
438+ index = t . index - 1 ;
439+ if ( index < 0 ) {
440+ index = 0
441+ }
442+ } else {
443+ }
444+ }
445+ if ( index == null ) {
446+ index = t . index
447+ }
448+ return nodeEls [ index ]
449+ }
450+ function shouldDragOpen ( stat : any , isPlaceholder : boolean ) {
451+ if ( ! props . dragOpen ) {
452+ return false
453+ }
454+ if ( isPlaceholder ) {
455+ return false
456+ }
457+ if ( stat . open ) {
458+ return false
459+ }
460+
461+ const refresh = ( ) => Object . assign ( dragOverInfo , { id : stat . id , x : e . pageX , y : e . pageY , time : Date . now ( ) } )
462+ if ( dragOverInfo . id !== stat . id ) {
463+ refresh ( )
464+ return false
465+ }
466+ if ( calculateDistance ( e . pageX , e . pageY , dragOverInfo . x , dragOverInfo . y ) > 10 ) {
467+ refresh ( )
468+ return false
469+ }
470+ const now = Date . now ( )
471+ if ( now - dragOverInfo . time >= props . dragOpenDelay ! ) {
472+ return true
473+ }
464474 }
465475 }
466476 const onDropToRoot : React . DragEventHandler < HTMLElement > = ( e ) => {
@@ -547,7 +557,7 @@ export function useHeTree<T extends Record<string, any>>(
547557 */
548558 function findClosestAndNext ( stat : Stat < T > , isPlaceholder : boolean ) {
549559 let closest = stat
550- let index = visibleIds . indexOf ( stat . id ) // index of closest node
560+ let index = visibleIds . indexOf ( ! isPlaceholder ? stat . id : props . placeholderId ) // index of closest node
551561 let atTop = false
552562 const isPlaceholderOrDraggedNode = ( id : Id ) => id === placeholderId || getStat ( id ) === draggingStat
553563 const find = ( startIndex : number , step : number ) => {
0 commit comments