Skip to content

Commit 3f9a125

Browse files
crisbetojelbourn
authored andcommittedDec 20, 2018
fix(select): announce value changes with arrow keys while closed (#14540)
We support the functionality from the native select where people can change the value while the dropdown is closed via the arrow keys, however for screen reader users there is no feedback that the value has changed. These changes announce the select value if it changes.
1 parent fe7f95e commit 3f9a125

File tree

3 files changed

+40
-2
lines changed

3 files changed

+40
-2
lines changed
 

‎src/lib/select/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ ng_test_library(
5252
"@angular//packages/platform-browser/animations",
5353
"@rxjs",
5454
"@rxjs//operators",
55+
"//src/cdk/a11y",
5556
"//src/cdk/bidi",
5657
"//src/cdk/keycodes",
5758
"//src/cdk/overlay",

‎src/lib/select/select.spec.ts

+24
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import {
5959
import {MatFormFieldModule} from '@angular/material/form-field';
6060
import {By} from '@angular/platform-browser';
6161
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
62+
import {LiveAnnouncer} from '@angular/cdk/a11y';
6263
import {Subject, Subscription, EMPTY, Observable} from 'rxjs';
6364
import {map} from 'rxjs/operators';
6465
import {MatSelectModule} from './index';
@@ -291,6 +292,8 @@ describe('MatSelect', () => {
291292
expect(options[1].selected).toBe(true, 'Expected second option to be selected.');
292293
expect(formControl.value).toBe(options[1].value,
293294
'Expected value from second option to have been set on the model.');
295+
296+
flush();
294297
}));
295298

296299
it('should select first/last options via the HOME/END keys on a closed select',
@@ -314,6 +317,8 @@ describe('MatSelect', () => {
314317
expect(firstOption.selected).toBe(true, 'Expected first option to be selected.');
315318
expect(formControl.value).toBe(firstOption.value,
316319
'Expected value from first option to have been set on the model.');
320+
321+
flush();
317322
}));
318323

319324
it('should resume focus from selected item after selecting via click', fakeAsync(() => {
@@ -336,6 +341,7 @@ describe('MatSelect', () => {
336341
fixture.detectChanges();
337342

338343
expect(formControl.value).toBe(options[4].value);
344+
flush();
339345
}));
340346

341347
it('should select options via LEFT/RIGHT arrow keys on a closed select', fakeAsync(() => {
@@ -363,8 +369,20 @@ describe('MatSelect', () => {
363369
expect(options[1].selected).toBe(true, 'Expected second option to be selected.');
364370
expect(formControl.value).toBe(options[1].value,
365371
'Expected value from second option to have been set on the model.');
372+
flush();
366373
}));
367374

375+
it('should announce changes via the keyboard on a closed select',
376+
fakeAsync(inject([LiveAnnouncer], (liveAnnouncer: LiveAnnouncer) => {
377+
spyOn(liveAnnouncer, 'announce');
378+
379+
dispatchKeyboardEvent(select, 'keydown', RIGHT_ARROW);
380+
381+
expect(liveAnnouncer.announce).toHaveBeenCalledWith('Steak');
382+
383+
flush();
384+
})));
385+
368386
it('should open a single-selection select using ALT + DOWN_ARROW', fakeAsync(() => {
369387
const {control: formControl, select: selectInstance} = fixture.componentInstance;
370388

@@ -534,6 +552,7 @@ describe('MatSelect', () => {
534552

535553
expect(formControl.value).toBe('pasta-6');
536554
expect(fixture.componentInstance.options.toArray()[6].selected).toBe(true);
555+
flush();
537556
}));
538557

539558
it('should not shift focus when the selected options are updated programmatically ' +
@@ -583,6 +602,8 @@ describe('MatSelect', () => {
583602
dispatchKeyboardEvent(select, 'keydown', DOWN_ARROW);
584603

585604
expect(lastOption.selected).toBe(true, 'Expected last option to stay selected.');
605+
606+
flush();
586607
}));
587608

588609
it('should not open a multiple select when tabbing through', fakeAsync(() => {
@@ -694,6 +715,7 @@ describe('MatSelect', () => {
694715
expect(spy).toHaveBeenCalledWith(true);
695716

696717
subscription.unsubscribe();
718+
flush();
697719
}));
698720

699721
it('should be able to focus the select trigger', fakeAsync(() => {
@@ -1898,6 +1920,8 @@ describe('MatSelect', () => {
18981920
dispatchKeyboardEvent(select, 'keydown', DOWN_ARROW);
18991921

19001922
expect(fixture.componentInstance.changeListener).toHaveBeenCalledTimes(1);
1923+
1924+
flush();
19011925
}));
19021926
});
19031927

‎src/lib/select/select.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {ActiveDescendantKeyManager} from '@angular/cdk/a11y';
9+
import {ActiveDescendantKeyManager, LiveAnnouncer} from '@angular/cdk/a11y';
1010
import {Directionality} from '@angular/cdk/bidi';
1111
import {coerceBooleanProperty} from '@angular/cdk/coercion';
1212
import {SelectionModel} from '@angular/cdk/collections';
@@ -485,7 +485,12 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
485485
@Optional() private _parentFormField: MatFormField,
486486
@Self() @Optional() public ngControl: NgControl,
487487
@Attribute('tabindex') tabIndex: string,
488-
@Inject(MAT_SELECT_SCROLL_STRATEGY) scrollStrategyFactory: any) {
488+
@Inject(MAT_SELECT_SCROLL_STRATEGY) scrollStrategyFactory: any,
489+
/**
490+
* @deprecated _liveAnnouncer to be turned into a required parameter.
491+
* @breaking-change 8.0.0
492+
*/
493+
private _liveAnnouncer?: LiveAnnouncer) {
489494
super(elementRef, _defaultErrorStateMatcher, _parentForm,
490495
_parentFormGroup, ngControl);
491496

@@ -700,12 +705,20 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
700705
event.preventDefault(); // prevents the page from scrolling down when pressing space
701706
this.open();
702707
} else if (!this.multiple) {
708+
const selectedOption = this.selected;
709+
703710
if (keyCode === HOME || keyCode === END) {
704711
keyCode === HOME ? manager.setFirstItemActive() : manager.setLastItemActive();
705712
event.preventDefault();
706713
} else {
707714
manager.onKeydown(event);
708715
}
716+
717+
// Since the value has changed, we need to announce it ourselves.
718+
// @breaking-change 8.0.0 remove null check for _liveAnnouncer.
719+
if (this._liveAnnouncer && selectedOption !== this.selected) {
720+
this._liveAnnouncer.announce((this.selected as MatOption).viewValue);
721+
}
709722
}
710723
}
711724

0 commit comments

Comments
 (0)
Please sign in to comment.