import { AfterContentInit , Directive , EventEmitter , HostBinding , HostListener , Input , Output } from '@angular/core' ; import { ObSelectableDirective } from './selectable.directive' ; @ Directive ( { selector : '[obSelectableGroup]' , exportAs : 'obSelectableGroup' , host : { class : 'ob-selectable-group' } } ) export class ObSelectableGroupDirective implements AfterContentInit { @ HostBinding ( 'attr.role' ) role = 'group' ; @ HostBinding ( 'class.ob-selectable-group' ) readonly selectable = true ; @ Output ( ) readonly selected$ = new EventEmitter < ObSelectableDirective [ ] > ( ) ; @ Output ( ) readonly mode$ = new EventEmitter < 'checkbox' | 'radio' | 'windows' > ( ) ; private readonly selectables : ObSelectableDirective [ ] = [ ] ; private modeValue : 'checkbox' | 'radio' | 'windows' = 'checkbox' ; private focused : number ; private prevFocused : number ; private startFocused : number ; private readonly window : Window ; private readonly modeToggle = { checkbox : this . checkboxSelect . bind ( this ) , radio : this . radioSelect . bind ( this ) , windows : this . windowsSelect . bind ( this ) } ; constructor ( ) { this . window = window ; this . mode$ . subscribe ( mode => { this . role = mode === 'radio' ? 'radiogroup' : 'group' ; if ( mode === 'radio' ) { this . getSelected ( ) . slice ( 1 ) . forEach ( item => ( item . selected = false ) ) ; this . updateSelection ( ) ; } } ) ; this . updateSelection ( ) ; } ngAfterContentInit ( ) { this . window . setTimeout ( ( ) => this . updateSelection ( ) ) ; } get mode ( ) : 'checkbox' | 'radio' | 'windows' { return this . modeValue ; } @ Input ( ) set mode ( mode ) { this . modeValue = mode || 'checkbox' ; this . mode$ . emit ( this . modeValue ) ; } register ( directive ) { this . selectables . push ( directive ) ; } toggle ( directive , ctrl = false , shift = false ) { this . modeToggle [ this . mode ] ( directive , ctrl , shift ) ; this . updateSelection ( ) ; } focus ( directive ) { this . prevFocused = this . focused ; this . focused = this . selectables . findIndex ( item => item === directive ) ; } selectAll ( ) { if ( this . mode !== 'radio' ) { this . selectables . forEach ( item => ( item . selected = true ) ) ; this . updateSelection ( ) ; } } deselectAll ( ) { if ( this . mode !== 'radio' ) { this . selectables . forEach ( item => ( item . selected = false ) ) ; this . updateSelection ( ) ; } } sort ( sortFunction ) { this . selectables . sort ( sortFunction ) ; } @ HostListener ( 'keydown.arrowDown' , [ '$event' ] ) @ HostListener ( 'keydown.arrowRight' , [ '$event' ] ) onArrowDown ( $event ) { this . next ( 1 , $event ) ; } @ HostListener ( 'keydown.arrowUp' , [ '$event' ] ) @ HostListener ( 'keydown.arrowLeft' , [ '$event' ] ) onArrowUp ( $event ) { this . next ( - 1 , $event ) ; } @ HostListener ( 'keydown.shift.arrowDown' , [ '$event' ] ) @ HostListener ( 'keydown.shift.arrowRight' , [ '$event' ] ) onShiftArrowDown ( $event ) { this . add ( + 1 , $event ) ; } @ HostListener ( 'keydown.shift.arrowUp' , [ '$event' ] ) @ HostListener ( 'keydown.shift.arrowLeft' , [ '$event' ] ) onShiftArrowUp ( $event ) { this . add ( - 1 , $event ) ; } @ HostListener ( 'keydown.control.arrowDown' , [ '$event' ] ) @ HostListener ( 'keydown.control.arrowRight' , [ '$event' ] ) onCtrlArrowDown ( $event ) { this . move ( + 1 , $event ) ; } @ HostListener ( 'keydown.control.arrowUp' , [ '$event' ] ) @ HostListener ( 'keydown.control.arrowLeft' , [ '$event' ] ) onCtrlArrowUp ( $event ) { this . move ( - 1 , $event ) ; } private add ( direction , $event ) { if ( this . mode === 'windows' ) { $event . preventDefault ( ) ; const index = this . focused + direction ; if ( index > - 1 && index < this . selectables . length ) { if ( this . selectables [ index ] . selected ) { this . selectables [ this . focused ] . selected = false ; } else { this . selectables [ index ] . selected = true ; } this . selectables [ index ] . focus ( ) ; this . updateSelection ( ) ; } } } private move ( direction , $event ) { if ( this . mode === 'windows' ) { $event . preventDefault ( ) ; const index = this . focused + direction ; if ( index > - 1 && index < this . selectables . length ) { this . selectables [ index ] . focus ( ) ; } } } private next ( direction , $event ) { if ( this . mode !== 'checkbox' ) { $event . preventDefault ( ) ; const index = this . mode === 'radio' ? ( this . focused + this . selectables . length + direction ) % this . selectables . length : Math . max ( 0 , Math . min ( this . selectables . length - 1 , this . focused + direction ) ) ; this . toggle ( this . selectables [ index ] ) ; this . selectables [ index ] . focus ( ) ; this . updateSelection ( ) ; } } private checkboxSelect ( directive ) { directive . selected = ! directive . selected ; } private radioSelect ( directive ) { this . selectables . forEach ( item => ( item . selected = false ) ) ; directive . selected = true ; } private windowsSelect ( directive , ctrl = false , shift = false ) { if ( ctrl ) { this . startFocused = undefined ; if ( this . getSelected ( ) . length > 1 || ! directive . selected ) { directive . selected = ! directive . selected ; } } else if ( shift ) { this . startFocused = this . startFocused ?? this . prevFocused ; const endFocused = this . selectables . findIndex ( item => item === directive ) ; const start = Math . min ( this . startFocused , endFocused ) ; const end = Math . max ( this . startFocused , endFocused ) ; this . selectables . forEach ( ( item , i ) => ( item . selected = ! ( i < start || i > end ) ) ) ; } else { this . startFocused = undefined ; this . selectables . forEach ( item => ( item . selected = false ) ) ; directive . selected = true ; } } private updateSelection ( ) { this . selected$ . emit ( this . getSelected ( ) ) ; } private getSelected ( ) { return this . selectables . filter ( item => item . selected ) ; } }