@@ -6,7 +6,10 @@ import {
66 TemplateRef ,
77 Directive ,
88 OnDestroy ,
9- HostBinding
9+ HostBinding ,
10+ Input ,
11+ ViewChildren ,
12+ QueryList
1013} from '@angular/core' ;
1114import { IgxInputDirective } from '../../../directives/input/input.directive' ;
1215import { DisplayDensity } from '../../../core/density' ;
@@ -23,7 +26,7 @@ import { IChangeCheckboxEventArgs, IgxCheckboxComponent } from '../../../checkbo
2326import { takeUntil } from 'rxjs/operators' ;
2427import { cloneHierarchicalArray , PlatformUtil } from '../../../core/utils' ;
2528import { BaseFilteringComponent } from './base-filtering.component' ;
26- import { ExpressionUI , FilterListItem } from './common' ;
29+ import { ActiveElement , ExpressionUI , FilterListItem } from './common' ;
2730import { IgxButtonDirective } from '../../../directives/button/button.directive' ;
2831import { IgxCircularProgressBarComponent } from '../../../progressbar/progressbar.component' ;
2932import { IgxTreeNodeComponent } from '../../../tree/tree-node/tree-node.component' ;
@@ -38,6 +41,7 @@ import { IgxPrefixDirective } from '../../../directives/prefix/prefix.directive'
3841import { IgxIconComponent } from '../../../icon/icon.component' ;
3942import { IgxInputGroupComponent } from '../../../input-group/input-group.component' ;
4043import { ITreeNodeSelectionEvent } from '../../../tree/common' ;
44+ import { Navigate } from '../../../drop-down/drop-down.common' ;
4145
4246@Directive ( {
4347 selector : '[igxExcelStyleLoading]' ,
@@ -51,6 +55,7 @@ export class IgxExcelStyleLoadingValuesTemplateDirective {
5155 constructor ( public template : TemplateRef < undefined > ) { }
5256}
5357
58+ let NEXT_ID = 0 ;
5459/**
5560 * A component used for presenting Excel style search UI.
5661 */
@@ -75,6 +80,9 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
7580 @ViewChild ( 'input' , { read : IgxInputDirective , static : true } )
7681 public searchInput : IgxInputDirective ;
7782
83+ @ViewChild ( 'cancelButton' , { read : IgxButtonDirective , static : true } )
84+ protected cancelButton : IgxButtonDirective ;
85+
7886 /**
7987 * @hidden @internal
8088 */
@@ -102,7 +110,7 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
102110 /**
103111 * @hidden @internal
104112 */
105- @ViewChild ( IgxForOfDirective , { static : true } )
113+ @ViewChild ( IgxForOfDirective )
106114 protected virtDir : IgxForOfDirective < any > ;
107115
108116 /**
@@ -111,6 +119,9 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
111119 @ViewChild ( 'defaultExcelStyleLoadingValuesTemplate' , { read : TemplateRef } )
112120 protected defaultExcelStyleLoadingValuesTemplate : TemplateRef < any > ;
113121
122+ @ViewChildren ( IgxCheckboxComponent )
123+ protected checkboxes : QueryList < IgxCheckboxComponent > ;
124+
114125 /**
115126 * @hidden @internal
116127 */
@@ -196,10 +207,14 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
196207 }
197208 }
198209
210+ protected activeDescendant = '' ;
211+
212+ private _id = `igx-excel-style-search-${ NEXT_ID ++ } ` ;
199213 private _isLoading ;
200214 private _addToCurrentFilterItem : FilterListItem ;
201215 private _selectAllItem : FilterListItem ;
202216 private _hierarchicalSelectedItems : FilterListItem [ ] ;
217+ private _focusedItem : ActiveElement = null ;
203218 private destroy$ = new Subject < boolean > ( ) ;
204219
205220 constructor ( public cdr : ChangeDetectorRef , public esf : BaseFilteringComponent , protected platform : PlatformUtil ) {
@@ -303,7 +318,6 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
303318 selectAllBtn . indeterminate = true ;
304319 }
305320 }
306- eventArgs . checkbox . nativeCheckbox . nativeElement . blur ( ) ;
307321 }
308322
309323 /**
@@ -368,6 +382,31 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
368382 return 0 ;
369383 }
370384
385+ @HostBinding ( 'attr.id' )
386+ @Input ( )
387+ protected get id ( ) : string {
388+ return this . _id ;
389+ }
390+ protected set id ( value : string ) {
391+ this . _id = value ;
392+ }
393+
394+ protected getItemId ( index : number ) : string {
395+ return `${ this . id } -item-${ index } ` ;
396+ }
397+
398+ protected setActiveDescendant ( ) : void {
399+ this . activeDescendant = this . focusedItem ?. id || '' ;
400+ }
401+
402+ protected get focusedItem ( ) : ActiveElement {
403+ return this . _focusedItem ;
404+ }
405+
406+ protected set focusedItem ( val : ActiveElement ) {
407+ this . _focusedItem = val ;
408+ }
409+
371410 /**
372411 * @hidden @internal
373412 */
@@ -576,6 +615,57 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
576615 this . esf . closeDropdown ( ) ;
577616 }
578617
618+ protected handleKeyDown ( event : KeyboardEvent ) {
619+ if ( event ) {
620+ const key = event . key . toLowerCase ( ) ;
621+ const navKeys = [ 'space' , 'spacebar' , ' ' ,
622+ 'arrowup' , 'up' , 'arrowdown' , 'down' , 'home' , 'end' ] ;
623+ if ( navKeys . indexOf ( key ) === - 1 ) { // If key has appropriate function in DD
624+ return ;
625+ }
626+ event . preventDefault ( ) ;
627+ event . stopPropagation ( ) ;
628+ switch ( key ) {
629+ case 'arrowup' :
630+ case 'up' :
631+ this . onArrowUpKeyDown ( ) ;
632+ break ;
633+ case 'arrowdown' :
634+ case 'down' :
635+ this . onArrowDownKeyDown ( ) ;
636+ break ;
637+ case 'home' :
638+ this . onHomeKeyDown ( ) ;
639+ break ;
640+ case 'end' :
641+ this . onEndKeyDown ( ) ;
642+ break ;
643+ case 'space' :
644+ case 'spacebar' :
645+ case ' ' :
646+ this . onActionKeyDown ( ) ;
647+ break ;
648+ default :
649+ return ;
650+ }
651+ }
652+ }
653+
654+ protected onFocus ( ) {
655+ const firstIndexInView = this . virtDir . state . startIndex ;
656+ this . focusedItem = {
657+ id : this . getItemId ( firstIndexInView ) ,
658+ index : firstIndexInView ,
659+ checked : this . virtDir . igxForOf [ firstIndexInView ] . isSelected
660+ } ;
661+ this . setActiveDescendant ( ) ;
662+ }
663+
664+ protected onFocusOut ( ) {
665+ this . focusedItem = null ;
666+ this . setActiveDescendant ( ) ;
667+ }
668+
579669 /**
580670 * @hidden @internal
581671 */
@@ -676,4 +766,70 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
676766 this . searchValue = this . searchInput . value ;
677767 }
678768 }
769+
770+ private onArrowUpKeyDown ( ) {
771+ if ( this . focusedItem && this . focusedItem . index === 0 && this . virtDir . state . startIndex === 0 ) {
772+ this . searchInput . focus ( ) ;
773+ this . onFocusOut ( ) ;
774+ } else {
775+ this . navigateItem ( this . focusedItem ? this . focusedItem . index - 1 : 0 ) ;
776+ }
777+ this . setActiveDescendant ( ) ;
778+ }
779+
780+ private onArrowDownKeyDown ( ) {
781+ const lastIndex = this . virtDir . igxForOf . length - 1 ;
782+ if ( this . focusedItem && this . focusedItem . index === lastIndex ) {
783+ this . cancelButton . nativeElement . focus ( ) ;
784+ this . onFocusOut ( ) ;
785+ } else {
786+ this . navigateItem ( this . focusedItem ? this . focusedItem . index + 1 : 0 ) ;
787+ }
788+ this . setActiveDescendant ( ) ;
789+ }
790+
791+ private onHomeKeyDown ( ) {
792+ this . navigateItem ( 0 ) ;
793+ this . setActiveDescendant ( ) ;
794+ }
795+
796+ private onEndKeyDown ( ) {
797+ this . navigateItem ( this . virtDir . igxForOf . length - 1 ) ;
798+ this . setActiveDescendant ( ) ;
799+ }
800+
801+ private onActionKeyDown ( ) {
802+ const dataItem = this . displayedListData [ this . focusedItem . index ] ;
803+ const args : IChangeCheckboxEventArgs = {
804+ checked : ! dataItem . isSelected ,
805+ checkbox : this . checkboxes . find ( x => x . value === dataItem )
806+ }
807+ this . onCheckboxChange ( args ) ;
808+ }
809+
810+ private navigateItem ( index : number ) {
811+ if ( index === - 1 || index >= this . virtDir . igxForOf . length ) {
812+ return ;
813+ }
814+ const direction = index > ( this . focusedItem ? this . focusedItem . index : - 1 ) ? Navigate . Down : Navigate . Up ;
815+ const scrollRequired = this . isIndexOutOfBounds ( index , direction ) ;
816+ this . focusedItem = {
817+ id : this . getItemId ( index ) ,
818+ index : index ,
819+ checked : this . virtDir . igxForOf [ index ] . isSelected
820+ } ;
821+ if ( scrollRequired ) {
822+ this . virtDir . scrollTo ( index ) ;
823+ }
824+ }
825+
826+ private isIndexOutOfBounds ( index : number , direction : Navigate ) {
827+ const virtState = this . virtDir . state ;
828+ const currentPosition = this . virtDir . getScroll ( ) . scrollTop ;
829+ const itemPosition = this . virtDir . getScrollForIndex ( index , direction === Navigate . Down ) ;
830+ const indexOutOfChunk = index < virtState . startIndex || index > virtState . chunkSize + virtState . startIndex ;
831+ const scrollNeeded = direction === Navigate . Down ? currentPosition < itemPosition : currentPosition > itemPosition ;
832+ const subRequired = indexOutOfChunk || scrollNeeded ;
833+ return subRequired ;
834+ }
679835}
0 commit comments