import { WiredBase , BaseCSS , Point } from './wired-base' ; import { rectangle } from './wired-lib' ; import { css , TemplateResult , html , CSSResultArray } from 'lit' ; import { customElement , property } from 'lit/decorators.js' ; interface WiredComboItem extends HTMLElement { value : string ; selected : boolean ; } interface ListboxValue { value : string ; text : string ; } @ customElement ( 'wired-listbox' ) export class WiredListbox extends WiredBase { @ property ( { type : Object } ) value ? : ListboxValue ; @ property ( { type : String } ) selected ? : string ; @ property ( { type : Boolean } ) horizontal = false ; private itemNodes : WiredComboItem [ ] = [ ] ; private lastSelectedItem ? : WiredComboItem ; private itemClickHandler = this . onItemClick . bind ( this ) ; static get styles ( ) : CSSResultArray { return [ BaseCSS , css `
      :host {
        display: inline-block;
        font-family: inherit;
        position: relative;
        padding: 5px;
        outline: none;
      }
      :host(:focus) path {
        stroke-width: 1.5;
      }
      ::slotted(wired-item) {
        display: block;
      }
      :host(.wired-horizontal) ::slotted(wired-item) {
        display: inline-block;
      }
      ` ] ; } render ( ) { return html `
    <slot id="slot" @slotchange="${ ( ) => this . requestUpdate ( ) }"></slot>
    <div id="overlay">
      <svg id="svg"></svg>
    </div>
    ` ; } firstUpdated ( ) { this . setAttribute ( 'role' , 'listbox' ) ; this . tabIndex = + ( ( this . getAttribute ( 'tabindex' ) || 0 ) ) ; this . refreshSelection ( ) ; this . addEventListener ( 'click' , this . itemClickHandler ) ; this . addEventListener ( 'keydown' , ( event ) => { switch ( event . keyCode ) { case 37 : case 38 : event . preventDefault ( ) ; this . selectPrevious ( ) ; break ; case 39 : case 40 : event . preventDefault ( ) ; this . selectNext ( ) ; break ; } } ) ; } updated ( ) { super . updated ( ) ; if ( this . horizontal ) { this . classList . add ( 'wired-horizontal' ) ; } else { this . classList . remove ( 'wired-horizontal' ) ; } if ( ! this . itemNodes . length ) { this . itemNodes = [ ] ; const nodes = ( this . shadowRoot ! . getElementById ( 'slot' ) as HTMLSlotElement ) . assignedNodes ( ) ; if ( nodes && nodes . length ) { for ( let i = 0 ; i < nodes . length ; i ++ ) { const element = nodes [ i ] as WiredComboItem ; if ( element . tagName === 'WIRED-ITEM' ) { element . setAttribute ( 'role' , 'option' ) ; this . itemNodes . push ( element ) ; } } } } } private onItemClick ( event ) { event . stopPropagation ( ) ; this . selected = ( event . target as WiredComboItem ) . value ; this . refreshSelection ( ) ; this . fireSelected ( ) ; } private refreshSelection ( ) { if ( this . lastSelectedItem ) { this . lastSelectedItem . selected = false ; this . lastSelectedItem . removeAttribute ( 'aria-selected' ) ; } const slot = this . shadowRoot ! . getElementById ( 'slot' ) as HTMLSlotElement ; const nodes = slot . assignedNodes ( ) ; if ( nodes ) { let selectedItem = null ; for ( let i = 0 ; i < nodes . length ; i ++ ) { const element = nodes [ i ] as WiredComboItem ; if ( element . tagName === 'WIRED-ITEM' ) { const value = element . value || '' ; if ( this . selected && ( value === this . selected ) ) { selectedItem = element ; break ; } } } this . lastSelectedItem = selectedItem || undefined ; if ( this . lastSelectedItem ) { this . lastSelectedItem . selected = true ; this . lastSelectedItem . setAttribute ( 'aria-selected' , 'true' ) ; } if ( selectedItem ) { this . value = { value : selectedItem . value || '' , text : selectedItem . textContent || '' } ; } else { this . value = undefined ; } } } private fireSelected ( ) { this . fire ( 'selected' , { selected : this . selected } ) ; } private selectPrevious ( ) { const list = this . itemNodes ; if ( list . length ) { let index = - 1 ; for ( let i = 0 ; i < list . length ; i ++ ) { if ( list [ i ] === this . lastSelectedItem ) { index = i ; break ; } } if ( index < 0 ) { index = 0 ; } else if ( index === 0 ) { index = list . length - 1 ; } else { index -- ; } this . selected = list [ index ] . value || '' ; this . refreshSelection ( ) ; this . fireSelected ( ) ; } } private selectNext ( ) { const list = this . itemNodes ; if ( list . length ) { let index = - 1 ; for ( let i = 0 ; i < list . length ; i ++ ) { if ( list [ i ] === this . lastSelectedItem ) { index = i ; break ; } } if ( index < 0 ) { index = 0 ; } else if ( index >= ( list . length - 1 ) ) { index = 0 ; } else { index ++ ; } this . selected = list [ index ] . value || '' ; this . refreshSelection ( ) ; this . fireSelected ( ) ; } } protected canvasSize ( ) { const s = this . getBoundingClientRect ( ) ; return [ s . width , s . height ] ; } protected draw ( svg , size ) { rectangle ( svg , 0 , 0 , size [ 0 ] , size [ 1 ] , this . seed ) ; } }