export interface SortEndArgs { src : { list : Element ; index : number ; } ; dst : { list : Element ; index : number ; } ; } type SortStartHandler = ( srcList : Element ) => unknown ; type SortEndHandler = ( srcOrDst : SortEndArgs ) => unknown ; interface DragState { didDrop : boolean ; dragging : Element ; dropzone : Element ; sourceList : Element ; sourceSibling : Element | null ; sourceIndex : number ; } const sortHandlers = new WeakMap ( ) ; let state = null ; export function isDragging ( ) { return ! ! state ; } export function sortable ( el , sortStarted , sortFinished ) { sortHandlers . set ( el , { sortStarted , sortFinished } ) ; el . addEventListener ( 'dragstart' , onDragStart ) ; el . addEventListener ( 'dragenter' , onDragEnter ) ; el . addEventListener ( 'dragend' , onDragEnd ) ; el . addEventListener ( 'drop' , onDrop ) ; el . addEventListener ( 'dragover' , onDragOver ) ; } function isBefore ( item1 , item2 ) { if ( item1 . parentNode === item2 . parentNode ) { let node = item1 ; while ( node ) { if ( node === item2 ) return true ; node = node . previousElementSibling ; } } return false ; } function isSameContainer ( item1 , item2 ) { return item1 . closest ( 'task-lists' ) === item2 . closest ( 'task-lists' ) ; } function onDragStart ( event ) { if ( event . currentTarget !== event . target ) return ; const target = event . currentTarget ; if ( ! ( target instanceof Element ) ) return ; const sourceList = target . closest ( '.contains-task-list' ) ; if ( ! sourceList ) return ; target . classList . add ( 'is-ghost' ) ; if ( event . dataTransfer ) { event . dataTransfer . setData ( 'text/plain' , ( target . textContent || '' ) . trim ( ) ) ; } if ( ! target . parentElement ) return ; const siblings = Array . from ( target . parentElement . children ) ; const sourceIndex = siblings . indexOf ( target ) ; const handlers = sortHandlers . get ( target ) ; if ( handlers ) { handlers . sortStarted ( sourceList ) ; } state = { didDrop : false , dragging : target , dropzone : target , sourceList , sourceSibling : siblings [ sourceIndex + 1 ] || null , sourceIndex } ; } function onDragEnter ( event ) { if ( ! state ) return ; const dropzone = event . currentTarget ; if ( ! ( dropzone instanceof Element ) ) return ; if ( ! isSameContainer ( state . dragging , dropzone ) ) { event . stopPropagation ( ) ; return ; } event . preventDefault ( ) ; if ( event . dataTransfer ) { event . dataTransfer . dropEffect = 'move' ; } if ( state . dropzone === dropzone ) return ; state . dragging . classList . add ( 'is-dragging' ) ; state . dropzone = dropzone ; if ( isBefore ( state . dragging , dropzone ) ) { dropzone . before ( state . dragging ) ; } else { dropzone . after ( state . dragging ) ; } } function onDrop ( event ) { if ( ! state ) return ; event . preventDefault ( ) ; event . stopPropagation ( ) ; const dropzone = event . currentTarget ; if ( ! ( dropzone instanceof Element ) ) return ; state . didDrop = true ; if ( ! state . dragging . parentElement ) return ; let newIndex = Array . from ( state . dragging . parentElement . children ) . indexOf ( state . dragging ) ; const currentList = dropzone . closest ( '.contains-task-list' ) ; if ( ! currentList ) return ; if ( state . sourceIndex === newIndex && state . sourceList === currentList ) return ; if ( state . sourceList === currentList && state . sourceIndex < newIndex ) { newIndex ++ ; } const src = { list : state . sourceList , index : state . sourceIndex } ; const dst = { list : currentList , index : newIndex } ; const handlers = sortHandlers . get ( state . dragging ) ; if ( handlers ) { handlers . sortFinished ( { src , dst } ) ; } } function onDragEnd ( ) { if ( ! state ) return ; state . dragging . classList . remove ( 'is-dragging' ) ; state . dragging . classList . remove ( 'is-ghost' ) ; if ( ! state . didDrop ) { state . sourceList . insertBefore ( state . dragging , state . sourceSibling ) ; } state = null ; } function onDragOver ( event ) { if ( ! state ) return ; const dropzone = event . currentTarget ; if ( ! ( dropzone instanceof Element ) ) return ; if ( ! isSameContainer ( state . dragging , dropzone ) ) { event . stopPropagation ( ) ; return ; } event . preventDefault ( ) ; if ( event . dataTransfer ) { event . dataTransfer . dropEffect = 'move' ; } }