66 TemplateRef ,
77 Directive ,
88 OnDestroy ,
9- HostBinding
9+ HostBinding ,
10+ Input
1011} from '@angular/core' ;
1112import { IgxInputDirective } from '../../../directives/input/input.directive' ;
1213import { DisplayDensity } from '../../../core/density' ;
@@ -23,7 +24,7 @@ import { IChangeCheckboxEventArgs, IgxCheckboxComponent } from '../../../checkbo
2324import { takeUntil } from 'rxjs/operators' ;
2425import { cloneHierarchicalArray , PlatformUtil } from '../../../core/utils' ;
2526import { BaseFilteringComponent } from './base-filtering.component' ;
26- import { ExpressionUI , FilterListItem } from './common' ;
27+ import { ActiveElement , ExpressionUI , FilterListItem } from './common' ;
2728import { IgxButtonDirective } from '../../../directives/button/button.directive' ;
2829import { IgxCircularProgressBarComponent } from '../../../progressbar/progressbar.component' ;
2930import { IgxTreeNodeComponent } from '../../../tree/tree-node/tree-node.component' ;
@@ -38,6 +39,7 @@ import { IgxPrefixDirective } from '../../../directives/prefix/prefix.directive'
3839import { IgxIconComponent } from '../../../icon/icon.component' ;
3940import { IgxInputGroupComponent } from '../../../input-group/input-group.component' ;
4041import { ITreeNodeSelectionEvent } from '../../../tree/common' ;
42+ import { Navigate } from '../../../drop-down/drop-down.common' ;
4143
4244@Directive ( {
4345 selector : '[igxExcelStyleLoading]' ,
@@ -51,6 +53,7 @@ export class IgxExcelStyleLoadingValuesTemplateDirective {
5153 constructor ( public template : TemplateRef < undefined > ) { }
5254}
5355
56+ let NEXT_ID = 0 ;
5457/**
5558 * A component used for presenting Excel style search UI.
5659 */
@@ -75,6 +78,9 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
7578 @ViewChild ( 'input' , { read : IgxInputDirective , static : true } )
7679 public searchInput : IgxInputDirective ;
7780
81+ @ViewChild ( 'cancelButton' , { read : IgxButtonDirective , static : true } )
82+ protected cancelButton : IgxButtonDirective ;
83+
7884 /**
7985 * @hidden @internal
8086 */
@@ -102,7 +108,7 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
102108 /**
103109 * @hidden @internal
104110 */
105- @ViewChild ( IgxForOfDirective , { static : true } )
111+ @ViewChild ( IgxForOfDirective )
106112 protected virtDir : IgxForOfDirective < any > ;
107113
108114 /**
@@ -196,10 +202,14 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
196202 }
197203 }
198204
205+ protected activeDescendant = '' ;
206+
207+ private _id = `igx-excel-style-search-${ NEXT_ID ++ } ` ;
199208 private _isLoading ;
200209 private _addToCurrentFilterItem : FilterListItem ;
201210 private _selectAllItem : FilterListItem ;
202211 private _hierarchicalSelectedItems : FilterListItem [ ] ;
212+ private _focusedItem : ActiveElement = null ;
203213 private destroy$ = new Subject < boolean > ( ) ;
204214
205215 constructor ( public cdr : ChangeDetectorRef , public esf : BaseFilteringComponent , protected platform : PlatformUtil ) {
@@ -303,7 +313,6 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
303313 selectAllBtn . indeterminate = true ;
304314 }
305315 }
306- eventArgs . owner . nativeInput . nativeElement . blur ( ) ;
307316 }
308317
309318 /**
@@ -368,6 +377,31 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
368377 return 0 ;
369378 }
370379
380+ @HostBinding ( 'attr.id' )
381+ @Input ( )
382+ protected get id ( ) : string {
383+ return this . _id ;
384+ }
385+ protected set id ( value : string ) {
386+ this . _id = value ;
387+ }
388+
389+ protected getItemId ( index : number ) : string {
390+ return `${ this . id } -item-${ index } ` ;
391+ }
392+
393+ protected setActiveDescendant ( ) : void {
394+ this . activeDescendant = this . focusedItem ?. id || '' ;
395+ }
396+
397+ protected get focusedItem ( ) : ActiveElement {
398+ return this . _focusedItem ;
399+ }
400+
401+ protected set focusedItem ( val : ActiveElement ) {
402+ this . _focusedItem = val ;
403+ }
404+
371405 /**
372406 * @hidden @internal
373407 */
@@ -576,6 +610,57 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
576610 this . esf . closeDropdown ( ) ;
577611 }
578612
613+ protected handleKeyDown ( event : KeyboardEvent ) {
614+ if ( event ) {
615+ const key = event . key . toLowerCase ( ) ;
616+ const navKeys = [ 'space' , 'spacebar' , ' ' ,
617+ 'arrowup' , 'up' , 'arrowdown' , 'down' , 'home' , 'end' ] ;
618+ if ( navKeys . indexOf ( key ) === - 1 ) { // If key has appropriate function in DD
619+ return ;
620+ }
621+ event . preventDefault ( ) ;
622+ event . stopPropagation ( ) ;
623+ switch ( key ) {
624+ case 'arrowup' :
625+ case 'up' :
626+ this . onArrowUpKeyDown ( ) ;
627+ break ;
628+ case 'arrowdown' :
629+ case 'down' :
630+ this . onArrowDownKeyDown ( ) ;
631+ break ;
632+ case 'home' :
633+ this . onHomeKeyDown ( ) ;
634+ break ;
635+ case 'end' :
636+ this . onEndKeyDown ( ) ;
637+ break ;
638+ case 'space' :
639+ case 'spacebar' :
640+ case ' ' :
641+ this . onActionKeyDown ( ) ;
642+ break ;
643+ default :
644+ return ;
645+ }
646+ }
647+ }
648+
649+ protected onFocus ( ) {
650+ const firstIndexInView = this . virtDir . state . startIndex ;
651+ this . focusedItem = {
652+ id : this . getItemId ( firstIndexInView ) ,
653+ index : firstIndexInView ,
654+ checked : this . virtDir . igxForOf [ firstIndexInView ] . isSelected
655+ } ;
656+ this . setActiveDescendant ( ) ;
657+ }
658+
659+ protected onFocusOut ( ) {
660+ this . focusedItem = null ;
661+ this . setActiveDescendant ( ) ;
662+ }
663+
579664 /**
580665 * @hidden @internal
581666 */
@@ -676,4 +761,72 @@ export class IgxExcelStyleSearchComponent implements AfterViewInit, OnDestroy {
676761 this . searchValue = this . searchInput . value ;
677762 }
678763 }
764+
765+ private onArrowUpKeyDown ( ) {
766+ if ( this . focusedItem && this . focusedItem . index === 0 && this . virtDir . state . startIndex === 0 ) {
767+ this . searchInput . focus ( ) ;
768+ this . onFocusOut ( ) ;
769+ } else {
770+ this . navigateItem ( this . focusedItem ? this . focusedItem . index - 1 : 0 ) ;
771+ }
772+ this . setActiveDescendant ( ) ;
773+ }
774+
775+ private onArrowDownKeyDown ( ) {
776+ const lastIndex = this . virtDir . igxForOf . length - 1 ;
777+ if ( this . focusedItem && this . focusedItem . index === lastIndex ) {
778+ this . cancelButton . nativeElement . focus ( ) ;
779+ this . onFocusOut ( ) ;
780+ } else {
781+ this . navigateItem ( this . focusedItem ? this . focusedItem . index + 1 : 0 ) ;
782+ }
783+ this . setActiveDescendant ( ) ;
784+ }
785+
786+ private onHomeKeyDown ( ) {
787+ this . navigateItem ( 0 ) ;
788+ this . setActiveDescendant ( ) ;
789+ }
790+
791+ private onEndKeyDown ( ) {
792+ this . navigateItem ( this . virtDir . igxForOf . length - 1 ) ;
793+ this . setActiveDescendant ( ) ;
794+ }
795+
796+ private onActionKeyDown ( ) {
797+ const dataItem = this . displayedListData [ this . focusedItem . index ] ;
798+ const args : IChangeCheckboxEventArgs = {
799+ checked : ! dataItem . isSelected ,
800+ owner : {
801+ value : dataItem
802+ }
803+ }
804+ this . onCheckboxChange ( args ) ;
805+ }
806+
807+ private navigateItem ( index : number ) {
808+ if ( index === - 1 || index >= this . virtDir . igxForOf . length ) {
809+ return ;
810+ }
811+ const direction = index > ( this . focusedItem ? this . focusedItem . index : - 1 ) ? Navigate . Down : Navigate . Up ;
812+ const scrollRequired = this . isIndexOutOfBounds ( index , direction ) ;
813+ this . focusedItem = {
814+ id : this . getItemId ( index ) ,
815+ index : index ,
816+ checked : this . virtDir . igxForOf [ index ] . isSelected
817+ } ;
818+ if ( scrollRequired ) {
819+ this . virtDir . scrollTo ( index ) ;
820+ }
821+ }
822+
823+ private isIndexOutOfBounds ( index : number , direction : Navigate ) {
824+ const virtState = this . virtDir . state ;
825+ const currentPosition = this . virtDir . getScroll ( ) . scrollTop ;
826+ const itemPosition = this . virtDir . getScrollForIndex ( index , direction === Navigate . Down ) ;
827+ const indexOutOfChunk = index < virtState . startIndex || index > virtState . chunkSize + virtState . startIndex ;
828+ const scrollNeeded = direction === Navigate . Down ? currentPosition < itemPosition : currentPosition > itemPosition ;
829+ const subRequired = indexOutOfChunk || scrollNeeded ;
830+ return subRequired ;
831+ }
679832}
0 commit comments