export interface DropPosition { rect : DropPositionRect ; cursor : DropPositionCursor ; } export interface DropPositionRect { top : number ; bottom : number ; left : number ; right : number ; } export interface DropPositionCursor { index : number ; y : number ; isVertical ? : boolean ; x ? : number ; height ? : number ; } export interface DropGridRow { cells : DropGridCell [ ] ; top ? : number ; bottom ? : number ; } export interface DropGridCell extends DropPositionRect { index : number ; } const defaultGapHalf = 6 ; export function getDropPositions ( dropZone , dropZoneGrid ) { const zoneRect = dropZone . getBoundingClientRect ( ) ; const positions = [ ] ; const gridItemList = Array . from ( dropZoneGrid . children ) . filter ( ( item ) => ! item . classList . contains ( "__isTemplateInternalNode" ) ) ; if ( gridItemList . length > 0 ) { let currentRow = { cells : [ ] , } ; const rows = [ currentRow ] ; gridItemList . forEach ( ( gridItem , index ) => { const rect = gridItem . getBoundingClientRect ( ) ; const cell = { index , top : rect . top , bottom : rect . bottom , left : rect . left , right : rect . right , } ; const prevCell = currentRow . cells . length > 0 && currentRow . cells [ currentRow . cells . length - 1 ] ; if ( ! prevCell || ( compareApproximately ( rect . left , prevCell . right ) > 0 && compareApproximately ( rect . top , prevCell . bottom ) < 0 ) ) { currentRow . cells . push ( cell ) ; currentRow . top = Math . min ( currentRow . top ?? Infinity , rect . top ) ; currentRow . bottom = Math . max ( currentRow . bottom ?? - Infinity , rect . bottom ) ; } else { currentRow = { cells : [ cell ] , top : rect . top , bottom : rect . bottom , } ; rows . push ( currentRow ) ; } } ) ; let lastRectBottom = zoneRect . top ; let nextCursorTop = zoneRect . top ; rows . forEach ( ( row , rowIndex ) => { if ( row . cells . length === 1 ) { const cell = row . cells [ 0 ] ; const rectBottom = ( cell . top + cell . bottom ) / 2 ; positions . push ( { rect : { top : lastRectBottom , bottom : rectBottom , left : zoneRect . left , right : zoneRect . right , } , cursor : { index : cell . index , y : ( nextCursorTop + cell . top ) / 2 - zoneRect . top , } , } ) ; lastRectBottom = rectBottom ; nextCursorTop = cell . bottom ; if ( rowIndex === rows . length - 1 ) { positions . push ( { rect : { top : lastRectBottom , bottom : zoneRect . bottom , left : zoneRect . left , right : zoneRect . right , } , cursor : { index : cell . index + 1 , y : Math . min ( cell . bottom + defaultGapHalf , ( cell . bottom + zoneRect . bottom ) / 2 ) - zoneRect . top , } , } ) ; } else if ( rows [ rowIndex + 1 ] . cells . length > 1 ) { const nextRow = rows [ rowIndex + 1 ] ; const rectBottom = ( cell . bottom + nextRow . top ) / 2 ; positions . push ( { rect : { top : lastRectBottom , bottom : rectBottom , left : zoneRect . left , right : zoneRect . right , } , cursor : { index : cell . index + 1 , y : rectBottom - zoneRect . top , } , } ) ; lastRectBottom = rectBottom ; } } else { let lastRectRight = zoneRect . left ; let nextCursorLeft = zoneRect . left ; const rectBottom = rowIndex < rows . length - 1 ? ( rows [ rowIndex + 1 ] . top + row . bottom ) / 2 : zoneRect . bottom ; const cursorDefault = { isVertical : true , y : row . top - zoneRect . top , height : row . bottom - row . top , } ; row . cells . forEach ( ( cell , cellIndex ) => { const rectRight = ( cell . left + cell . right ) / 2 ; positions . push ( { rect : { top : lastRectBottom , bottom : rectBottom , left : lastRectRight , right : rectRight , } , cursor : { ... cursorDefault , index : cell . index , x : ( nextCursorLeft + cell . left ) / 2 - zoneRect . left , } , } ) ; lastRectRight = rectRight ; nextCursorLeft = cell . right ; if ( cellIndex === row . cells . length - 1 ) { if ( rowIndex === rows . length - 1 ) { positions . push ( { rect : { top : lastRectBottom , bottom : zoneRect . bottom , left : lastRectRight , right : zoneRect . right , } , cursor : { ... cursorDefault , index : cell . index + 1 , x : Math . min ( nextCursorLeft + defaultGapHalf , zoneRect . right ) - zoneRect . left , } , } ) ; } else { positions . push ( { rect : { top : lastRectBottom , bottom : rectBottom , left : lastRectRight , right : zoneRect . right , } , cursor : { ... cursorDefault , index : cell . index + 1 , x : Math . min ( nextCursorLeft + defaultGapHalf , zoneRect . right ) - zoneRect . left , } , } ) ; } } } ) ; lastRectBottom = rectBottom ; nextCursorTop = row . bottom ; } } ) ; } return positions ; } export function getDropPosition ( x , y , dropZone , dropZoneGrid ) { const positions = getDropPositions ( dropZone , dropZoneGrid ) ; for ( const { rect , cursor } of positions ) { if ( x >= rect . left && x <= rect . right && y >= rect . top && y <= rect . bottom ) { return cursor ; } } return { index : 0 , y : 5 , } ; } function compareApproximately ( a , b ) { const diff = a - b ; return Math . abs ( diff ) < 1 ? 0 : diff ; }