import { SPACE } from '@angular/cdk/keycodes' ; import { ChangeDetectorRef , Directive , ElementRef , EventEmitter , HostBinding , HostListener , Input , OnChanges , OnDestroy , OnInit , Output , SimpleChanges } from '@angular/core' ; import { Subject , Subscription } from 'rxjs' ; import { debounceTime , map , skip , takeUntil , tap } from 'rxjs/operators' ; import { FocusIndicator , FocusIndicatorService , ManagedFocusContainerService } from '../accessibility/index' ; import { SelectionService } from './selection.service' ; @ Directive ( { selector : '[uxSelectionItem]' , exportAs : 'ux-selection-item' } ) export class SelectionItemDirective < T > implements OnInit , OnChanges , OnDestroy { /** Defines the data associated with this item. */ @ Input ( ) uxSelectionItem : T ; /** Defines whether or not this item is currently selected. */ @ Input ( ) @ HostBinding ( 'class.ux-selection-selected' ) @ HostBinding ( 'attr.aria-selected' ) set selected ( selected ) { selected ? this . select ( ) : this . deselect ( ) ; } get selected ( ) : boolean { return this . _selected ; } /** Defines the tab index of the row */ @ Input ( ) tabindex : number = null ; /** Determine whether or not this item can be selected */ @ Input ( ) set uxSelectionDisabled ( isDisabled ) { if ( this . _selected && isDisabled ) { this . deselect ( ) ; } this . _selectionService . setItemDisabled ( this . uxSelectionItem , isDisabled ) ; this . _isDisabled = isDisabled ; } /** Defines whether or not this item is currently selected. */ @ Output ( ) selectedChange = new EventEmitter < boolean > ( ) ; /** Store whether this item is the focusable item */ active : boolean = false ; /** Store the focused state of the element */ @ HostBinding ( 'class.ux-selection-focused' ) isFocused : boolean = false ; @ HostBinding ( 'attr.tabindex' ) get attrTabIndex ( ) : number { return ( this . tabindex !== null ) ? this . tabindex : this . _managedTabIndex ; } /** Store the current selected state */ private _selected : boolean = false ; /** Store the disabled state */ private _isDisabled : boolean = false ; /** Store the tab indexed if using the managed focus container */ private _managedTabIndex : number = - 1 ; /** Determine if there is a pending state change as we debounce before emitting */ private _hasPendingStateChange : boolean = false ; /** Subscription to the selection state observable. */ private _selectionStateSubscription : Subscription ; /** Automatically unsubscribe when the component is destroyed */ private readonly _onDestroy = new Subject < void > ( ) ; /** The the instance of the focus indicator */ private readonly _focusIndicator : FocusIndicator ; constructor ( private readonly _selectionService , private readonly _elementRef , readonly focusIndicatorService , private readonly _managedFocusContainerService , private readonly _changeDetector ) { this . _focusIndicator = focusIndicatorService . monitor ( _elementRef . nativeElement ) ; } ngOnInit ( ) { if ( ! this . uxSelectionItem ) { throw new Error ( 'The uxSelectionItem directive must have data associated with it.' ) ; } this . _selectionService . active$ . pipe ( takeUntil ( this . _onDestroy ) , map ( active => active === this . uxSelectionItem ) ) . subscribe ( active => { this . active = active ; if ( active === true ) { this . _selectionService . focus$ . next ( this . uxSelectionItem ) ; this . _elementRef . nativeElement . focus ( ) ; } } ) ; this . _selectionService . focus$ . pipe ( takeUntil ( this . _onDestroy ) ) . subscribe ( focusTarget => { this . _managedTabIndex = ( focusTarget === this . uxSelectionItem ) ? 0 : - 1 ; } ) ; this . _managedFocusContainerService . register ( this . _elementRef . nativeElement , this ) ; this . _focusIndicator . origin$ . pipe ( map ( origin => origin === 'keyboard' ) , takeUntil ( this . _onDestroy ) ) . subscribe ( isFocused => { this . isFocused = isFocused ; this . _changeDetector . markForCheck ( ) ; } ) ; } ngOnChanges ( changes ) { if ( changes . uxSelectionItem ) { this . updateSelectionItem ( ) ; } } ngOnDestroy ( ) { this . _onDestroy . next ( ) ; this . _onDestroy . complete ( ) ; this . _focusIndicator . destroy ( ) ; this . _managedFocusContainerService . unregister ( this . _elementRef . nativeElement , this ) ; } @ HostListener ( 'click' , [ '$event' ] ) click ( event ) { if ( ! this . _isDisabled && ! this . _hasPendingStateChange && this . _selectionService . isEnabled && this . _selectionService . isClickEnabled ) { this . _selectionService . strategy . click ( event , this . uxSelectionItem ) ; } } @ HostListener ( 'mousedown' , [ '$event' ] ) mousedown ( event ) { if ( ! this . _isDisabled && this . _selectionService . isEnabled && this . _selectionService . isClickEnabled ) { this . _selectionService . strategy . mousedown ( event , this . uxSelectionItem ) ; } } @ HostListener ( 'keydown' , [ '$event' ] ) keydown ( event ) { const isDisabled = this . _isDisabled && event . keyCode === SPACE ; if ( ! isDisabled && this . _selectionService . isEnabled && this . _selectionService . isKeyboardEnabled ) { this . _selectionService . strategy . keydown ( event , this . uxSelectionItem ) ; } } @ HostListener ( 'focus' ) focus ( ) { if ( this . _selectionService . active$ . getValue ( ) !== this . uxSelectionItem ) { this . _selectionService . activate ( this . uxSelectionItem ) ; } } /**
     * Select this item using the current strategy
     */ select ( ) { if ( ! this . _isDisabled && this . _selectionService . isEnabled ) { this . _selectionService . strategy . select ( this . uxSelectionItem ) ; } } /**
     * Deselect this item using the current strategy
     */ deselect ( ) { if ( ! this . _isDisabled && this . _selectionService . isEnabled ) { this . _selectionService . strategy . deselect ( this . uxSelectionItem ) ; } } private updateSelectionItem ( ) { if ( this . _selectionStateSubscription ) { this . _selectionStateSubscription . unsubscribe ( ) ; } this . _selectionStateSubscription = this . _selectionService . getSelectionState ( this . uxSelectionItem ) . pipe ( skip ( 1 ) , tap ( ( ) => this . _hasPendingStateChange = true ) , debounceTime ( 0 ) , takeUntil ( this . _onDestroy ) ) . subscribe ( selected => { this . _hasPendingStateChange = false ; if ( this . _selected === selected ) { return ; } this . _selected = selected ; this . selectedChange . emit ( selected ) ; this . _changeDetector . markForCheck ( ) ; } ) ; this . _selected = this . _selectionService . isSelected ( this . uxSelectionItem ) ; } }