import { Directive , Input , Output , EventEmitter , HostListener , ElementRef , OnInit , OnDestroy , ContentChildren , QueryList , Renderer2 } from '@angular/core' ; import { toBoolean , InputBoolean } from '../util/convert' ; import { NglDropdownItem } from './dropdown-item' ; const openEventEmitter = new EventEmitter < any > ( ) ; @ Directive ( { selector : '[nglDropdown]' , host : { '[class.slds-dropdown-trigger]' : 'true' , '[class.slds-dropdown-trigger_click]' : 'true' , } , } ) export class NglDropdown implements OnInit , OnDestroy { @ Input ( 'open' ) set isOpen ( isOpen ) { this . _isOpen = toBoolean ( isOpen ) ; if ( this . isOpen ) { this . clearGlobalClickTimeout ( ) ; this . globalClickTimeout = setTimeout ( ( ) => { if ( this . handlePageEvents ) { this . _subscribeToClickEvents ( ) ; } } ) ; this . renderer . addClass ( this . element . nativeElement , 'slds-is-open' ) ; } else { this . _unsubscribeFromClickEvents ( ) ; this . renderer . removeClass ( this . element . nativeElement , 'slds-is-open' ) ; } this . renderer . setAttribute ( this . element . nativeElement , 'aria-expanded' , `${ this . isOpen }` ) ; } get isOpen ( ) { return this . _isOpen ; } @ Input ( ) @ InputBoolean ( ) handlePageEvents = true ; @ ContentChildren ( NglDropdownItem , { descendants : true } ) items : QueryList < NglDropdownItem > ; @ Output ( 'openChange' ) isOpenChange = new EventEmitter < boolean > ( ) ; triggerFocusEventEmitter = new EventEmitter ( ) ; private _isOpen = false ; private openEventSubscription : any ; private globalClickEventUnsubscriber : Function = null ; private clickEventUnsubscriber : Function = null ; private globalClickTimeout : number ; @ HostListener ( 'keydown.esc' , [ '"esc"' ] ) @ HostListener ( 'keydown.tab' , [ '"tab"' ] ) onKeydownClose ( eventName ) { this . toggle ( false ) ; if ( eventName === 'esc' ) { this . triggerFocusEventEmitter . emit ( null ) ; } } @ HostListener ( 'keydown.arrowdown' , [ '$event' , '"next"' ] ) @ HostListener ( 'keydown.arrowup' , [ '$event' , '"previous"' ] ) onKeydownFocusNext ( $event , direction ) { $event . preventDefault ( ) ; this . focusItem ( direction ) ; } constructor ( public element , public renderer ) { } ngOnInit ( ) { this . openEventSubscription = openEventEmitter . subscribe ( this . handleDropdownOpenEvent . bind ( this ) ) ; } ngOnDestroy ( ) { this . clearGlobalClickTimeout ( ) ; if ( this . openEventSubscription ) { this . openEventSubscription . unsubscribe ( ) ; } this . _unsubscribeFromClickEvents ( ) ; } toggle ( toggle = ! this . isOpen , focus = false ) { if ( toggle === this . isOpen ) { return ; } this . isOpenChange . emit ( toggle ) ; if ( toggle ) { openEventEmitter . emit ( this ) ; if ( focus ) { this . focusItem ( 'next' ) ; } } } private handleGlobalClickEvent ( $event ) { if ( ! this . handlePageEvents || $event . $nglStop ) { return ; } this . toggle ( false ) ; } private _subscribeToClickEvents ( ) { this . _unsubscribeFromClickEvents ( ) ; this . clickEventUnsubscriber = this . renderer . listen ( this . element . nativeElement , 'click' , ( $event ) => $event . $nglStop = true ) ; this . globalClickEventUnsubscriber = this . renderer . listen ( 'document' , 'click' , this . handleGlobalClickEvent . bind ( this ) ) ; } private _unsubscribeFromClickEvents ( ) { if ( this . clickEventUnsubscriber ) { this . clickEventUnsubscriber ( ) ; this . clickEventUnsubscriber = null ; } if ( this . globalClickEventUnsubscriber ) { this . globalClickEventUnsubscriber ( ) ; this . globalClickEventUnsubscriber = null ; } } private clearGlobalClickTimeout ( ) { clearTimeout ( this . globalClickTimeout ) ; } private focusItem ( direction ) { if ( ! this . items . length ) { return ; } const items = this . items . toArray ( ) ; const activeElementIndex = items . findIndex ( item => item . hasFocus ( ) ) + ( direction === 'next' ? 1 : - 1 ) ; if ( activeElementIndex === items . length || activeElementIndex < 0 ) { return ; } items [ activeElementIndex ] . focus ( ) ; } private handleDropdownOpenEvent ( dropdown ) { if ( dropdown !== this ) { this . toggle ( false ) ; } } }