import { Output } from '@angular/core' ; import { Directive , AfterViewInit , ElementRef , OnDestroy } from '@angular/core' ; import { KeyCodes } from 'app/shared/models/constants' ; import { Dom } from '../../shared/Utilities/dom' ; import { Subject } from 'rxjs/Subject' ; @ Directive ( { selector : '[flex-list]' , } ) export class FlexListDirective implements AfterViewInit , OnDestroy { @ Output ( ) onEnterKeyPressed = new Subject < HTMLElement > ( ) ; private _nativeElement : HTMLElement ; private _observer : MutationObserver ; constructor ( elementRef ) { this . _nativeElement = elementRef . nativeElement ; this . _nativeElement . addEventListener ( 'keydown' , this . _onKeyDown . bind ( this ) , true ) ; this . _nativeElement . addEventListener ( 'click' , this . _onClick . bind ( this ) , true ) ; this . _observer = new MutationObserver ( this . _mutationCallback . bind ( this ) ) ; this . _observer . observe ( this . _nativeElement , { childList : true } ) ; } ngAfterViewInit ( ) { this . _initFirstChildAsFocusable ( ) ; } ngOnDestroy ( ) { this . _nativeElement . removeEventListener ( 'keydown' , this . _onKeyDown . bind ( this ) , true ) ; this . _nativeElement . removeEventListener ( 'click' , this . _onClick . bind ( this ) , true ) ; this . _observer . disconnect ( ) ; } private _mutationCallback ( mutationsList ) { const updateMutation = mutationsList . find ( m => m . type === 'childList' ) ; if ( updateMutation ) { let existingFocusableChild = false ; for ( let i = 0 ; i < this . _nativeElement . children . length ; i ++ ) { if ( ( < HTMLElement > this . _nativeElement . children [ i ] ) . tabIndex > - 1 ) { existingFocusableChild = true ; break ; } } if ( ! existingFocusableChild ) { this . _initFirstChildAsFocusable ( ) ; } } } private _initFirstChildAsFocusable ( ) { if ( this . _nativeElement . children . length > 0 ) { ( < HTMLElement > this . _nativeElement . children [ 0 ] ) . tabIndex = 0 ; } } private _onClick ( event ) { const child = this . _findFlexItemElement ( event . srcElement ) ; if ( child ) { for ( let i = 0 ; i < this . _nativeElement . children . length ; i ++ ) { ( < HTMLElement > this . _nativeElement . children [ i ] ) . tabIndex = - 1 ; } ( < HTMLElement > child ) . tabIndex = 0 ; } } private _findFlexItemElement ( element ) { do { if ( element . parentElement === this . _nativeElement ) { return element ; } element = element . parentElement ; } while ( element . parentElement !== document . documentElement ) ; return null ; } private _onKeyDown ( event ) { if ( event . keyCode !== KeyCodes . arrowDown && event . keyCode !== KeyCodes . arrowUp && event . keyCode !== KeyCodes . arrowLeft && event . keyCode !== KeyCodes . arrowRight && event . keyCode !== KeyCodes . enter ) { return ; } const children = this . _nativeElement . children ; let focusedIndex = this . _getFocusedChildIndex ( ) ; if ( focusedIndex < 0 ) { this . _initFirstChildAsFocusable ( ) ; return ; } if ( event . keyCode === KeyCodes . enter ) { this . onEnterKeyPressed . next ( < HTMLElement > event . srcElement ) ; } else if ( event . keyCode === KeyCodes . arrowDown ) { const nextIndex = this . _findNextVerticleChildDown ( children , focusedIndex ) ; this . _clearFocusOnItem ( children [ focusedIndex ] ) ; this . _setFocusOnChild ( children , nextIndex ) ; this . _scrollIntoView ( children [ nextIndex ] ) ; } else if ( event . keyCode === KeyCodes . arrowUp ) { const nextIndex = this . _findNextVerticleChildUp ( children , focusedIndex ) ; this . _clearFocusOnItem ( children [ focusedIndex ] ) ; this . _setFocusOnChild ( children , nextIndex ) ; this . _scrollIntoView ( children [ nextIndex ] ) ; } else if ( event . keyCode === KeyCodes . arrowLeft ) { this . _clearFocusOnItem ( children [ focusedIndex ] ) ; focusedIndex = this . _setFocusOnChild ( children , focusedIndex - 1 ) ; this . _scrollIntoView ( children [ focusedIndex ] ) ; } else if ( event . keyCode === KeyCodes . arrowRight ) { this . _clearFocusOnItem ( children [ focusedIndex ] ) ; focusedIndex = this . _setFocusOnChild ( children , focusedIndex + 1 ) ; this . _scrollIntoView ( children [ focusedIndex ] ) ; } event . preventDefault ( ) ; } private _findNextVerticleChildDown ( children , index ) { let nextRowPosition = 0 ; let foundNextRowPosition = false ; let closestchildIndex = index ; let closestchildDistance = 0 ; const curChild = < HTMLElement > children [ index ] ; const curChildPosition = Dom . getElementCoordinates ( curChild ) ; for ( let i = index + 1 ; i < children . length ; i ++ ) { const nextchildPosition = Dom . getElementCoordinates ( < HTMLElement > children [ i ] ) ; if ( ! foundNextRowPosition && nextchildPosition . top > curChildPosition . top ) { nextRowPosition = nextchildPosition . top ; foundNextRowPosition = true ; closestchildIndex = i ; closestchildDistance = Math . abs ( curChildPosition . left - nextchildPosition . left ) ; if ( closestchildDistance < curChild . clientWidth ) { return closestchildIndex ; } continue ; } if ( foundNextRowPosition ) { if ( nextchildPosition . top === nextRowPosition && Math . abs ( curChildPosition . left - nextchildPosition . left ) < closestchildDistance ) { closestchildDistance = Math . abs ( curChildPosition . left - nextchildPosition . left ) ; closestchildIndex = i ; if ( closestchildDistance < curChild . clientWidth ) { return closestchildIndex ; } } else { return closestchildIndex ; } } } if ( ! foundNextRowPosition ) { for ( let i = 0 ; i < index ; i ++ ) { const nextchildPosition = Dom . getElementCoordinates ( < HTMLElement > children [ i ] ) ; if ( nextchildPosition . top <= curChildPosition . top && Math . abs ( nextchildPosition . left - curChildPosition . left ) < curChild . clientWidth ) { closestchildIndex = i ; return closestchildIndex ; } } } return closestchildIndex ; } private _findNextVerticleChildUp ( children , index ) { let nextRowPosition = 0 ; let foundNextRowPosition = false ; let closestchildIndex = index ; let closestchildDistance = 0 ; const curChild = < HTMLElement > children [ index ] ; const currentchildPosition = Dom . getElementCoordinates ( curChild ) ; for ( let i = index - 1 ; i >= 0 ; i -- ) { const nextchildPosition = Dom . getElementCoordinates ( < HTMLElement > children [ i ] ) ; if ( nextchildPosition . top <= currentchildPosition . top && Math . abs ( nextchildPosition . left - currentchildPosition . left ) < curChild . clientWidth ) { closestchildIndex = i ; return closestchildIndex ; } } for ( let i = children . length - 1 ; i > index ; i -- ) { const nextchildPosition = Dom . getElementCoordinates ( < HTMLElement > children [ i ] ) ; if ( ! foundNextRowPosition && nextchildPosition . top > currentchildPosition . top ) { nextRowPosition = nextchildPosition . top ; foundNextRowPosition = true ; closestchildIndex = i ; closestchildDistance = Math . abs ( currentchildPosition . left - nextchildPosition . left ) ; if ( closestchildDistance < curChild . clientWidth ) { return closestchildIndex ; } continue ; } if ( foundNextRowPosition ) { if ( nextchildPosition . top === nextRowPosition && Math . abs ( currentchildPosition . left - nextchildPosition . left ) < closestchildDistance ) { closestchildDistance = Math . abs ( currentchildPosition . left - nextchildPosition . left ) ; closestchildIndex = i ; if ( closestchildDistance < curChild . clientWidth ) { return closestchildIndex ; } } else { return closestchildDistance ; } } } return closestchildIndex ; } private _getFocusedChildIndex ( ) { const children = this . _nativeElement . children ; for ( let i = 0 ; i < children . length ; i ++ ) { if ( ( < HTMLElement > children [ i ] ) . tabIndex >= 0 ) { return i ; } } return - 1 ; } private _clearFocusOnItem ( item ) { Dom . clearFocus ( < HTMLElement > item ) ; } private _setFocusOnChild ( children , targetIndex ) { if ( children . length === 0 ) { targetIndex = - 1 ; } else if ( targetIndex < 0 ) { targetIndex = children . length - 1 ; } else if ( targetIndex >= children . length ) { targetIndex = 0 ; } if ( targetIndex > - 1 ) { Dom . setFocus ( < HTMLElement > children [ targetIndex ] ) ; } return targetIndex ; } private _scrollIntoView ( elem ) { Dom . scrollIntoView ( < HTMLElement > elem , window . document . body ) ; } }