@@ -6,7 +6,10 @@ import {
66 TemplateRef ,
77 Directive ,
88 OnDestroy ,
9- HostBinding
9+ HostBinding ,
10+ Input ,
11+ QueryList ,
12+ ViewChildren
1013} from '@angular/core' ;
1114import { IgxInputDirective } from '../../../directives/input/input.directive' ;
1215import { DisplayDensity } from '../../../core/density' ;
@@ -24,8 +27,10 @@ import { IChangeCheckboxEventArgs, IgxCheckboxComponent } from '../../../checkbo
2427import { takeUntil } from 'rxjs/operators' ;
2528import { cloneHierarchicalArray , PlatformUtil } from '../../../core/utils' ;
2629import { BaseFilteringComponent } from './base-filtering.component' ;
27- import { ExpressionUI , FilterListItem } from './common' ;
30+ import { ActiveElement , ExpressionUI , FilterListItem } from './common' ;
2831import { IgxTreeComponent , ITreeNodeSelectionEvent } from '../../../tree/public_api' ;
32+ import { Navigate } from '../../../drop-down/drop-down.common' ;
33+ import { IgxButtonDirective } from '../../../directives/button/button.directive' ;
2934
3035@Directive ( {
3136 selector : '[igxExcelStyleLoading]'
@@ -38,6 +43,7 @@ export class IgxExcelStyleLoadingValuesTemplateDirective {
3843 constructor ( public template : TemplateRef < undefined > ) { }
3944}
4045
46+ let NEXT_ID = 0 ;
4147/**
4248 * A component used for presenting Excel style search UI.
4349 */
@@ -60,6 +66,9 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
6066 @ViewChild ( 'input' , { read : IgxInputDirective , static : true } )
6167 public searchInput : IgxInputDirective ;
6268
69+ @ViewChild ( 'cancelButton' , { read : IgxButtonDirective , static : true } )
70+ protected cancelButton : IgxButtonDirective ;
71+
6372 /**
6473 * @hidden @internal
6574 */
@@ -87,7 +96,7 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
8796 /**
8897 * @hidden @internal
8998 */
90- @ViewChild ( IgxForOfDirective , { static : true } )
99+ @ViewChild ( IgxForOfDirective )
91100 protected virtDir : IgxForOfDirective < any > ;
92101
93102 /**
@@ -96,6 +105,9 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
96105 @ViewChild ( 'defaultExcelStyleLoadingValuesTemplate' , { read : TemplateRef } )
97106 protected defaultExcelStyleLoadingValuesTemplate : TemplateRef < any > ;
98107
108+ @ViewChildren ( IgxCheckboxComponent )
109+ protected checkboxes : QueryList < IgxCheckboxComponent > ;
110+
99111 /**
100112 * @hidden @internal
101113 */
@@ -181,10 +193,14 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
181193 }
182194 }
183195
196+ protected activeDescendant = '' ;
197+
198+ private _id = `igx-excel-style-search-${ NEXT_ID ++ } ` ;
184199 private _isLoading ;
185200 private _addToCurrentFilterItem : FilterListItem ;
186201 private _selectAllItem : FilterListItem ;
187202 private _hierarchicalSelectedItems : FilterListItem [ ] ;
203+ private _focusedItem : ActiveElement = null ;
188204 private destroy$ = new Subject < boolean > ( ) ;
189205
190206 constructor ( public cdr : ChangeDetectorRef , public esf : BaseFilteringComponent , protected platform : PlatformUtil ) {
@@ -288,7 +304,6 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
288304 selectAllBtn . indeterminate = true ;
289305 }
290306 }
291- eventArgs . checkbox . nativeCheckbox . nativeElement . blur ( ) ;
292307 }
293308
294309 /**
@@ -353,6 +368,31 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
353368 return 0 ;
354369 }
355370
371+ @HostBinding ( 'attr.id' )
372+ @Input ( )
373+ protected get id ( ) : string {
374+ return this . _id ;
375+ }
376+ protected set id ( value : string ) {
377+ this . _id = value ;
378+ }
379+
380+ protected getItemId ( index : number ) : string {
381+ return `${ this . id } -item-${ index } ` ;
382+ }
383+
384+ protected setActiveDescendant ( ) : void {
385+ this . activeDescendant = this . focusedItem ?. id || '' ;
386+ }
387+
388+ protected get focusedItem ( ) : ActiveElement {
389+ return this . _focusedItem ;
390+ }
391+
392+ protected set focusedItem ( val : ActiveElement ) {
393+ this . _focusedItem = val ;
394+ }
395+
356396 /**
357397 * @hidden @internal
358398 */
@@ -561,6 +601,57 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
561601 this . esf . closeDropdown ( ) ;
562602 }
563603
604+ protected handleKeyDown ( event : KeyboardEvent ) {
605+ if ( event ) {
606+ const key = event . key . toLowerCase ( ) ;
607+ const navKeys = [ 'space' , 'spacebar' , ' ' ,
608+ 'arrowup' , 'up' , 'arrowdown' , 'down' , 'home' , 'end' ] ;
609+ if ( navKeys . indexOf ( key ) === - 1 ) { // If key has appropriate function in DD
610+ return ;
611+ }
612+ event . preventDefault ( ) ;
613+ event . stopPropagation ( ) ;
614+ switch ( key ) {
615+ case 'arrowup' :
616+ case 'up' :
617+ this . onArrowUpKeyDown ( ) ;
618+ break ;
619+ case 'arrowdown' :
620+ case 'down' :
621+ this . onArrowDownKeyDown ( ) ;
622+ break ;
623+ case 'home' :
624+ this . onHomeKeyDown ( ) ;
625+ break ;
626+ case 'end' :
627+ this . onEndKeyDown ( ) ;
628+ break ;
629+ case 'space' :
630+ case 'spacebar' :
631+ case ' ' :
632+ this . onActionKeyDown ( ) ;
633+ break ;
634+ default :
635+ return ;
636+ }
637+ }
638+ }
639+
640+ protected onFocus ( ) {
641+ const firstIndexInView = this . virtDir . state . startIndex ;
642+ this . focusedItem = {
643+ id : this . getItemId ( firstIndexInView ) ,
644+ index : firstIndexInView ,
645+ checked : this . virtDir . igxForOf [ firstIndexInView ] . isSelected
646+ } ;
647+ this . setActiveDescendant ( ) ;
648+ }
649+
650+ protected onFocusOut ( ) {
651+ this . focusedItem = null ;
652+ this . setActiveDescendant ( ) ;
653+ }
654+
564655 /**
565656 * @hidden @internal
566657 */
@@ -661,4 +752,70 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
661752 this . searchValue = this . searchInput . value ;
662753 }
663754 }
755+
756+ private onArrowUpKeyDown ( ) {
757+ if ( this . focusedItem && this . focusedItem . index === 0 && this . virtDir . state . startIndex === 0 ) {
758+ this . searchInput . focus ( ) ;
759+ this . onFocusOut ( ) ;
760+ } else {
761+ this . navigateItem ( this . focusedItem ? this . focusedItem . index - 1 : 0 ) ;
762+ }
763+ this . setActiveDescendant ( ) ;
764+ }
765+
766+ private onArrowDownKeyDown ( ) {
767+ const lastIndex = this . virtDir . igxForOf . length - 1 ;
768+ if ( this . focusedItem && this . focusedItem . index === lastIndex ) {
769+ this . cancelButton . nativeElement . focus ( ) ;
770+ this . onFocusOut ( ) ;
771+ } else {
772+ this . navigateItem ( this . focusedItem ? this . focusedItem . index + 1 : 0 ) ;
773+ }
774+ this . setActiveDescendant ( ) ;
775+ }
776+
777+ private onHomeKeyDown ( ) {
778+ this . navigateItem ( 0 ) ;
779+ this . setActiveDescendant ( ) ;
780+ }
781+
782+ private onEndKeyDown ( ) {
783+ this . navigateItem ( this . virtDir . igxForOf . length - 1 ) ;
784+ this . setActiveDescendant ( ) ;
785+ }
786+
787+ private onActionKeyDown ( ) {
788+ const dataItem = this . displayedListData [ this . focusedItem . index ] ;
789+ const args : IChangeCheckboxEventArgs = {
790+ checked : ! dataItem . isSelected ,
791+ checkbox : this . checkboxes . find ( x => x . value === dataItem )
792+ }
793+ this . onCheckboxChange ( args ) ;
794+ }
795+
796+ private navigateItem ( index : number ) {
797+ if ( index === - 1 || index >= this . virtDir . igxForOf . length ) {
798+ return ;
799+ }
800+ const direction = index > ( this . focusedItem ? this . focusedItem . index : - 1 ) ? Navigate . Down : Navigate . Up ;
801+ const scrollRequired = this . isIndexOutOfBounds ( index , direction ) ;
802+ this . focusedItem = {
803+ id : this . getItemId ( index ) ,
804+ index : index ,
805+ checked : this . virtDir . igxForOf [ index ] . isSelected
806+ } ;
807+ if ( scrollRequired ) {
808+ this . virtDir . scrollTo ( index ) ;
809+ }
810+ }
811+
812+ private isIndexOutOfBounds ( index : number , direction : Navigate ) {
813+ const virtState = this . virtDir . state ;
814+ const currentPosition = this . virtDir . getScroll ( ) . scrollTop ;
815+ const itemPosition = this . virtDir . getScrollForIndex ( index , direction === Navigate . Down ) ;
816+ const indexOutOfChunk = index < virtState . startIndex || index > virtState . chunkSize + virtState . startIndex ;
817+ const scrollNeeded = direction === Navigate . Down ? currentPosition < itemPosition : currentPosition > itemPosition ;
818+ const subRequired = indexOutOfChunk || scrollNeeded ;
819+ return subRequired ;
820+ }
664821}
0 commit comments