import { SortEndArgs , isDragging , sortable } from './sortable' ; const observers = new WeakMap ( ) ; export default class TaskListsElement extends HTMLElement { connectedCallback ( ) { this . addEventListener ( 'change' , ( event ) => { const checkbox = event . target ; if ( ! ( checkbox instanceof HTMLInputElement ) ) return ; if ( ! checkbox . classList . contains ( 'task-list-item-checkbox' ) ) return ; this . dispatchEvent ( new CustomEvent ( 'task-lists-check' , { bubbles : true , detail : { position : position ( checkbox ) , checked : checkbox . checked } } ) ) ; } ) ; const observer = new MutationObserver ( syncState . bind ( null , this ) ) ; observers . set ( this , observer ) ; observer . observe ( this , { childList : true , subtree : true } ) ; syncState ( this ) ; } disconnectedCallback ( ) { const observer = observers . get ( this ) ; if ( observer ) { observer . disconnect ( ) ; } } get disabled ( ) : boolean { return this . hasAttribute ( 'disabled' ) ; } set disabled ( value ) { if ( value ) { this . setAttribute ( 'disabled' , '' ) ; } else { this . removeAttribute ( 'disabled' ) ; } } get sortable ( ) : boolean { return this . hasAttribute ( 'sortable' ) ; } set sortable ( value ) { if ( value ) { this . setAttribute ( 'sortable' , '' ) ; } else { this . removeAttribute ( 'sortable' ) ; } } static get observedAttributes ( ) : string [ ] { return [ 'disabled' ] ; } attributeChangedCallback ( name , oldValue , newValue ) { if ( oldValue === newValue ) return ; switch ( name ) { case 'disabled' : syncDisabled ( this ) ; break ; } } } const handleTemplate = document . createElement ( 'template' ) ; handleTemplate . innerHTML = `
  <span class="handle">
    <svg class="drag-handle" aria-hidden="true" width="16" height="16">
      <path d="M10 13a1 1 0 100-2 1 1 0 000 2zm-4 0a1 1 0 100-2 1 1 0 000 2zm1-5a1 1 0 11-2 0 1 1 0 012 0zm3 1a1 1 0 100-2 1 1 0 000 2zm1-5a1 1 0 11-2 0 1 1 0 012 0zM6 5a1 1 0 100-2 1 1 0 000 2z"/>
    </svg>
  </span>` ; const initialized = new WeakMap ( ) ; function initItem ( el ) { if ( initialized . get ( el ) ) return ; initialized . set ( el , true ) ; const currentTaskList = el . closest ( 'task-lists' ) ; if ( ! ( currentTaskList instanceof TaskListsElement ) ) return ; if ( currentTaskList . querySelectorAll ( '.task-list-item' ) . length <= 1 ) return ; const fragment = handleTemplate . content . cloneNode ( true ) ; const handle = ( fragment as DocumentFragment ) . querySelector < HTMLElement > ( '.handle' ) ; el . prepend ( fragment ) ; if ( ! handle ) throw new Error ( 'handle not found' ) ; handle . addEventListener ( 'mouseenter' , onHandleMouseOver ) ; handle . addEventListener ( 'mouseleave' , onHandleMouseOut ) ; sortable ( el , onSortStart , onSorted ) ; el . addEventListener ( 'mouseenter' , onListItemMouseOver ) ; el . addEventListener ( 'mouseleave' , onListItemMouseOut ) ; } function onListItemMouseOver ( event ) { const item = event . currentTarget ; if ( ! ( item instanceof Element ) ) return ; const list = item . closest ( 'task-lists' ) ; if ( ! ( list instanceof TaskListsElement ) ) return ; if ( list . sortable && ! list . disabled ) { item . classList . add ( 'hovered' ) ; } } function onListItemMouseOut ( event ) { const item = event . currentTarget ; if ( ! ( item instanceof Element ) ) return ; item . classList . remove ( 'hovered' ) ; } function position ( checkbox ) { const list = taskList ( checkbox ) ; if ( ! list ) throw new Error ( '.contains-task-list not found' ) ; const item = checkbox . closest ( '.task-list-item' ) ; const listItems = Array . from ( list . children ) . filter ( el => el . tagName === 'LI' ) ; const index = item ? listItems . indexOf ( item ) : - 1 ; return [ listIndex ( list ) , index ] ; } function taskList ( el ) { const parent = el . parentElement ; return parent ? parent . closest ( '.contains-task-list' ) : null ; } function isRootTaskList ( el ) { return taskList ( el ) === rootTaskList ( el ) ; } function rootTaskList ( node ) { const list = taskList ( node ) ; return list ? rootTaskList ( list ) || list : null ; } function syncState ( list ) { const items = list . querySelectorAll < HTMLElement > ( '.contains-task-list > .task-list-item' ) ; for ( const el of items ) { if ( isRootTaskList ( el ) ) { initItem ( el ) ; } } syncDisabled ( list ) ; } function syncDisabled ( list ) { for ( const el of list . querySelectorAll ( '.task-list-item' ) ) { el . classList . toggle ( 'enabled' , ! list . disabled ) ; } for ( const el of list . querySelectorAll ( '.task-list-item-checkbox' ) ) { if ( el instanceof HTMLInputElement ) { el . disabled = list . disabled ; } } } function listIndex ( list ) { const container = list . closest ( 'task-lists' ) ; if ( ! container ) throw new Error ( 'parent not found' ) ; return Array . from ( container . querySelectorAll ( 'ol, ul' ) ) . indexOf ( list ) ; } const originalLists = new WeakMap ( ) ; function onSortStart ( srcList ) { const container = srcList . closest ( 'task-lists' ) ; if ( ! container ) throw new Error ( 'parent not found' ) ; originalLists . set ( container , Array . from ( container . querySelectorAll ( 'ol, ul' ) ) ) ; } function onSorted ( { src , dst } ) { const container = src . list . closest ( 'task-lists' ) ; if ( ! container ) return ; const lists = originalLists . get ( container ) ; if ( ! lists ) return ; originalLists . delete ( container ) ; container . dispatchEvent ( new CustomEvent ( 'task-lists-move' , { bubbles : true , detail : { src : [ lists . indexOf ( src . list ) , src . index ] , dst : [ lists . indexOf ( dst . list ) , dst . index ] } } ) ) ; } function onHandleMouseOver ( event ) { const target = event . currentTarget ; if ( ! ( target instanceof Element ) ) return ; const item = target . closest ( '.task-list-item' ) ; if ( ! item ) return ; const list = item . closest ( 'task-lists' ) ; if ( ! ( list instanceof TaskListsElement ) ) return ; if ( list . sortable && ! list . disabled ) { item . setAttribute ( 'draggable' , 'true' ) ; } } function onHandleMouseOut ( event ) { if ( isDragging ( ) ) return ; const target = event . currentTarget ; if ( ! ( target instanceof Element ) ) return ; const item = target . closest ( '.task-list-item' ) ; if ( ! item ) return ; item . setAttribute ( 'draggable' , 'false' ) ; } declare global { interface Window { TaskListsElement : typeof TaskListsElement ; } interface HTMLElementTagNameMap { 'task-lists' : TaskListsElement ; } } if ( ! window . customElements . get ( 'task-lists' ) ) { window . TaskListsElement = TaskListsElement ; window . customElements . define ( 'task-lists' , TaskListsElement ) ; }