import { FocusKeyManager } from '@angular/cdk/a11y' ; import { AfterContentInit , ContentChildren , Directive , ElementRef , Input , OnDestroy , QueryList } from '@angular/core' ; import { filter , takeUntil } from 'rxjs/operators' ; import { Subject } from 'rxjs' ; import { TabbableListItemDirective } from './tabbable-list-item.directive' ; import { TabbableListService } from './tabbable-list.service' ; @ Directive ( { selector : '[uxTabbableList]' , exportAs : 'ux-tabbable-list' , providers : [ TabbableListService ] } ) export class TabbableListDirective implements AfterContentInit , OnDestroy { /** Determine whether the up/down arrows should be used or the left/right arrows */ @ Input ( ) direction : 'horizontal' | 'vertical' = 'vertical' ; /** Indicate whether or not focus should loop back to the first element after the last */ @ Input ( ) wrap : boolean = true ; /** Indicate whether or not the first item should receive focus on show - useful for modals and popovers */ @ Input ( ) focusOnShow : boolean = false ; /** Indicate whether or not focus should be returned to the previous element (only applicable when using focusOnShow) */ @ Input ( ) returnFocus : boolean = false ; /** Enabling handling of hierarchical lists via use of the `TabbableListItemDirective.parent` property. */ @ Input ( ) set hierarchy ( value ) { this . _tabbableList . hierarchy = value ; } /** Prevent keyboard interaction when alt modifier key is pressed */ @ Input ( ) set allowAltModifier ( value ) { this . _tabbableList . allowAltModifier = value ; } /** Prevent keyboard interaction when ctrl modifier key is pressed */ @ Input ( ) set allowCtrlModifier ( value ) { this . _tabbableList . allowCtrlModifier = value ; } /** Focus the first or last item when Home or End keys are pressed */ @ Input ( ) set allowBoundaryKeys ( value ) { this . _tabbableList . allowBoundaryKeys = value ; } /** Find all tabbable list items */ @ ContentChildren ( TabbableListItemDirective , { descendants : true } ) items : QueryList < TabbableListItemDirective > ; /** Store the currently focused HTML element */ private _focusedElement : HTMLElement ; /** Store the items in a specific order */ private _orderedItems : QueryList < TabbableListItemDirective > ; /** Unsubscribe from all observables automatically on destroy */ private _onDestroy = new Subject < void > ( ) ; get focusKeyManager ( ) : FocusKeyManager < TabbableListItemDirective > { return this . _tabbableList . focusKeyManager ; } constructor ( /** Access the tabbable list service */ private _tabbableList , /** Access the native dom element */ elementRef ) { this . _tabbableList . containerRef = elementRef . nativeElement ; } ngAfterContentInit ( ) { this . _focusedElement = document . activeElement as HTMLElement ; this . _orderedItems = new QueryList < TabbableListItemDirective > ( ) ; if ( this . _tabbableList . hierarchy ) { this . _orderedItems . reset ( this . _tabbableList . sortItemsByHierarchy ( this . items ) ) ; this . items . changes . pipe ( takeUntil ( this . _onDestroy ) ) . subscribe ( ( ) => { this . _orderedItems . reset ( this . _tabbableList . sortItemsByHierarchy ( this . items ) ) ; this . _orderedItems . notifyOnChanges ( ) ; } ) ; } else { this . _orderedItems = this . items ; this . items . changes . pipe ( filter ( ( ) => this . _tabbableList . shouldFocusOnChange && this . _tabbableList . isFocused ) , takeUntil ( this . _onDestroy ) ) . subscribe ( ( items ) => { const activeItem = this . _tabbableList . focusKeyManager . activeItem ; if ( activeItem ) { const index = items . toArray ( ) . findIndex ( item => item . key === activeItem . key ) ; if ( index > - 1 ) { this . _tabbableList . shouldScrollInView = false ; this . _tabbableList . activateItemAtIndex ( index , ! this . _tabbableList . isFocused ) ; this . _tabbableList . shouldScrollInView = true ; } } } ) ; } this . _tabbableList . initialize ( this . _orderedItems , this . direction , this . wrap ) ; if ( this . focusOnShow ) { this . _tabbableList . focusKeyManager . setFirstItemActive ( ) ; } } ngOnDestroy ( ) { if ( this . returnFocus && this . _focusedElement instanceof HTMLElement ) { setTimeout ( ( ) => this . _focusedElement . focus ( ) ) ; } this . _onDestroy . next ( ) ; this . _onDestroy . complete ( ) ; } focus ( ) { if ( this . _tabbableList . focusKeyManager && this . _tabbableList . focusKeyManager . activeItem ) { this . _tabbableList . focusKeyManager . activeItem . focus ( ) ; } } focusTabbableItem ( ) { this . _tabbableList . focusTabbableItem ( ) ; } }