From 504fca3e5bef033d0eebe9a98c28c83218049257 Mon Sep 17 00:00:00 2001 From: Samuel Renier Date: Wed, 13 Nov 2019 09:23:15 +0100 Subject: [PATCH 01/11] Add id to typeahead-container --- src/typeahead/typeahead-container.component.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/typeahead/typeahead-container.component.ts b/src/typeahead/typeahead-container.component.ts index 8fb5e7afd1..2f61052f19 100644 --- a/src/typeahead/typeahead-container.component.ts +++ b/src/typeahead/typeahead-container.component.ts @@ -20,6 +20,7 @@ import { TYPEAHEAD_ANIMATION_TIMING, typeaheadAnimation } from './typeahead-anim import { delay, take, tap } from 'rxjs/operators'; +let nextWindowId = 0; @Component({ selector: 'typeahead-container', @@ -60,6 +61,7 @@ export class TypeaheadContainerComponent { animationState: string; visibility = 'hidden'; height = 0; + popupId = `ngb-typeahead-${nextWindowId++}`; get isBs4(): boolean { return !isBs3(); @@ -74,11 +76,14 @@ export class TypeaheadContainerComponent { @ViewChildren('liElements') private liElements: QueryList; + constructor( private positionService: PositioningService, private renderer: Renderer2, public element: ElementRef - ) { } + ) { + this.element.nativeElement.id = this.popupId; + } get active(): TypeaheadMatch { return this._active; From 55c8d55ff64b07f4e4f0af87113d56a71ba4027b Mon Sep 17 00:00:00 2001 From: Samuel Renier Date: Wed, 13 Nov 2019 16:00:08 +0100 Subject: [PATCH 02/11] Add attribute aria activeDescendant --- .../typeahead-container.component.ts | 33 +++++++++++++++---- src/typeahead/typeahead.directive.ts | 11 ++++++- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/typeahead/typeahead-container.component.ts b/src/typeahead/typeahead-container.component.ts index 2f61052f19..718937c2ac 100644 --- a/src/typeahead/typeahead-container.component.ts +++ b/src/typeahead/typeahead-container.component.ts @@ -7,7 +7,9 @@ import { Renderer2, TemplateRef, ViewChild, - ViewChildren + ViewChildren, + Output, + EventEmitter } from '@angular/core'; import { isBs3, Utils } from 'ngx-bootstrap/utils'; @@ -48,6 +50,10 @@ let nextWindowId = 0; animations: [typeaheadAnimation] }) export class TypeaheadContainerComponent { + + // tslint:disable-next-line: no-output-rename + @Output('activeChange') activeChangeEvent = new EventEmitter(); + parent: TypeaheadDirective; query: string[] | string; isFocused = false; @@ -89,6 +95,11 @@ export class TypeaheadContainerComponent { return this._active; } + set active(active: TypeaheadMatch) { + this._active = active; + this.activeChanged(); + } + get matches(): TypeaheadMatch[] { return this._matches; } @@ -132,7 +143,7 @@ export class TypeaheadContainerComponent { } if (this.typeaheadIsFirstItemActive && this._matches.length > 0) { - this._active = this._matches[0]; + this.active = this._matches[0]; if (this._active.isHeader()) { this.nextActiveMatch(); @@ -148,7 +159,7 @@ export class TypeaheadContainerComponent { return; } - this._active = null; + this.active = null; } } @@ -195,10 +206,16 @@ export class TypeaheadContainerComponent { } } + activeChanged(): void { + const index = this.matches.indexOf(this._active); + this.activeChangeEvent.emit(`${this.popupId}-${index}`); + } + prevActiveMatch(): void { + const index = this.matches.indexOf(this._active); - this._active = this.matches[ + this.active = this.matches[ index - 1 < 0 ? this.matches.length - 1 : index - 1 ]; @@ -212,12 +229,14 @@ export class TypeaheadContainerComponent { } nextActiveMatch(): void { + const index = this.matches.indexOf(this._active); - this._active = this.matches[ + this.active = this.matches[ index + 1 > this.matches.length - 1 ? 0 : index + 1 ]; + if (this._active.isHeader()) { this.nextActiveMatch(); } @@ -229,7 +248,7 @@ export class TypeaheadContainerComponent { selectActive(value: TypeaheadMatch): void { this.isFocused = true; - this._active = value; + this.active = value; } highlight(match: TypeaheadMatch, query: string[] | string): string { @@ -276,7 +295,7 @@ export class TypeaheadContainerComponent { } isActive(value: TypeaheadMatch): boolean { - return this._active === value; + return this.active === value; } selectMatch(value: TypeaheadMatch, e: Event = void 0): boolean { diff --git a/src/typeahead/typeahead.directive.ts b/src/typeahead/typeahead.directive.ts index 4fd7e2034b..db5d1e7154 100644 --- a/src/typeahead/typeahead.directive.ts +++ b/src/typeahead/typeahead.directive.ts @@ -24,7 +24,13 @@ import { TypeaheadConfig } from './typeahead.config'; import { getValueFromObject, latinize, tokenize } from './typeahead-utils'; import { debounceTime, filter, mergeMap, switchMap, toArray } from 'rxjs/operators'; -@Directive({selector: '[typeahead]', exportAs: 'bs-typeahead'}) +@Directive({ + selector: '[typeahead]', + exportAs: 'bs-typeahead', + host: { + '[attr.aria-activedescendant]': 'activeDescendant' + } +}) export class TypeaheadDirective implements OnInit, OnDestroy { /** options source, can be Array of strings, objects or * an Observable for external matching process @@ -134,6 +140,7 @@ export class TypeaheadDirective implements OnInit, OnDestroy { /** if false don't focus the input element the typeahead directive is associated with on selection */ // @Input() protected typeaheadFocusOnSelect:boolean; + activeDescendant: string; _container: TypeaheadContainerComponent; isActiveItemChanged = false; isTypeaheadOptionsListActive = false; @@ -360,6 +367,8 @@ export class TypeaheadDirective implements OnInit, OnDestroy { this._container.matches = this._matches; this.element.nativeElement.focus(); + + this._container.activeChangeEvent.subscribe((activeId: string) => this.activeDescendant = activeId); } hide(): void { From 628913890ab906b0a9c46df58ef3875c912440af Mon Sep 17 00:00:00 2001 From: Samuel Renier Date: Thu, 14 Nov 2019 08:48:40 +0100 Subject: [PATCH 03/11] Add aria-aria-owns and aria-aria-expanded --- src/typeahead/typeahead.directive.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/typeahead/typeahead.directive.ts b/src/typeahead/typeahead.directive.ts index db5d1e7154..b144a00904 100644 --- a/src/typeahead/typeahead.directive.ts +++ b/src/typeahead/typeahead.directive.ts @@ -28,7 +28,9 @@ import { debounceTime, filter, mergeMap, switchMap, toArray } from 'rxjs/operato selector: '[typeahead]', exportAs: 'bs-typeahead', host: { - '[attr.aria-activedescendant]': 'activeDescendant' + '[attr.aria-activedescendant]': 'activeDescendant', + '[attr.aria-aria-owns]': 'isOpen ? this._container.popupId : null', + '[attr.aria-aria-expanded]': 'isOpen' } }) export class TypeaheadDirective implements OnInit, OnDestroy { @@ -141,6 +143,8 @@ export class TypeaheadDirective implements OnInit, OnDestroy { // @Input() protected typeaheadFocusOnSelect:boolean; activeDescendant: string; + popupId: string; + isOpen = false; _container: TypeaheadContainerComponent; isActiveItemChanged = false; isTypeaheadOptionsListActive = false; @@ -368,7 +372,11 @@ export class TypeaheadDirective implements OnInit, OnDestroy { this._container.matches = this._matches; this.element.nativeElement.focus(); - this._container.activeChangeEvent.subscribe((activeId: string) => this.activeDescendant = activeId); + this._container.activeChangeEvent.subscribe((activeId: string) => { + this.activeDescendant = activeId; + this.changeDetection.markForCheck(); + }); + this.isOpen = true; } hide(): void { @@ -376,6 +384,8 @@ export class TypeaheadDirective implements OnInit, OnDestroy { this._typeahead.hide(); this._outsideClickListener(); this._container = null; + this.isOpen = false; + this.changeDetection.markForCheck(); } } From 2d6a455375f28a52ab0929a87b4b7a4005e35eb2 Mon Sep 17 00:00:00 2001 From: Samuel Renier Date: Thu, 14 Nov 2019 08:49:43 +0100 Subject: [PATCH 04/11] Add role="option" and role="listbox" --- src/typeahead/typeahead-container.component.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/typeahead/typeahead-container.component.html b/src/typeahead/typeahead-container.component.html index 1e7f1ac115..fce6385882 100644 --- a/src/typeahead/typeahead-container.component.html +++ b/src/typeahead/typeahead-container.component.html @@ -10,12 +10,14 @@