Skip to content

fix(select): announce value changes with arrow keys while closed #14540

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/lib/select/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ ng_test_library(
"@angular//packages/platform-browser/animations",
"@rxjs",
"@rxjs//operators",
"//src/cdk/a11y",
"//src/cdk/bidi",
"//src/cdk/keycodes",
"//src/cdk/overlay",
Expand Down
24 changes: 24 additions & 0 deletions src/lib/select/select.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {
import {MatFormFieldModule} from '@angular/material/form-field';
import {By} from '@angular/platform-browser';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {LiveAnnouncer} from '@angular/cdk/a11y';
import {Subject, Subscription, EMPTY, Observable} from 'rxjs';
import {map} from 'rxjs/operators';
import {MatSelectModule} from './index';
Expand Down Expand Up @@ -291,6 +292,8 @@ describe('MatSelect', () => {
expect(options[1].selected).toBe(true, 'Expected second option to be selected.');
expect(formControl.value).toBe(options[1].value,
'Expected value from second option to have been set on the model.');

flush();
}));

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

flush();
}));

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

expect(formControl.value).toBe(options[4].value);
flush();
}));

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

it('should announce changes via the keyboard on a closed select',
fakeAsync(inject([LiveAnnouncer], (liveAnnouncer: LiveAnnouncer) => {
spyOn(liveAnnouncer, 'announce');

dispatchKeyboardEvent(select, 'keydown', RIGHT_ARROW);

expect(liveAnnouncer.announce).toHaveBeenCalledWith('Steak');

flush();
})));

it('should open a single-selection select using ALT + DOWN_ARROW', fakeAsync(() => {
const {control: formControl, select: selectInstance} = fixture.componentInstance;

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

expect(formControl.value).toBe('pasta-6');
expect(fixture.componentInstance.options.toArray()[6].selected).toBe(true);
flush();
}));

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

expect(lastOption.selected).toBe(true, 'Expected last option to stay selected.');

flush();
}));

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

subscription.unsubscribe();
flush();
}));

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

expect(fixture.componentInstance.changeListener).toHaveBeenCalledTimes(1);

flush();
}));
});

Expand Down
17 changes: 15 additions & 2 deletions src/lib/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {ActiveDescendantKeyManager} from '@angular/cdk/a11y';
import {ActiveDescendantKeyManager, LiveAnnouncer} from '@angular/cdk/a11y';
import {Directionality} from '@angular/cdk/bidi';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {SelectionModel} from '@angular/cdk/collections';
Expand Down Expand Up @@ -485,7 +485,12 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
@Optional() private _parentFormField: MatFormField,
@Self() @Optional() public ngControl: NgControl,
@Attribute('tabindex') tabIndex: string,
@Inject(MAT_SELECT_SCROLL_STRATEGY) scrollStrategyFactory: any) {
@Inject(MAT_SELECT_SCROLL_STRATEGY) scrollStrategyFactory: any,
/**
* @deprecated _liveAnnouncer to be turned into a required parameter.
* @breaking-change 8.0.0
*/
private _liveAnnouncer?: LiveAnnouncer) {
super(elementRef, _defaultErrorStateMatcher, _parentForm,
_parentFormGroup, ngControl);

Expand Down Expand Up @@ -700,12 +705,20 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
event.preventDefault(); // prevents the page from scrolling down when pressing space
this.open();
} else if (!this.multiple) {
const selectedOption = this.selected;

if (keyCode === HOME || keyCode === END) {
keyCode === HOME ? manager.setFirstItemActive() : manager.setLastItemActive();
event.preventDefault();
} else {
manager.onKeydown(event);
}

// Since the value has changed, we need to announce it ourselves.
// @breaking-change 8.0.0 remove null check for _liveAnnouncer.
if (this._liveAnnouncer && selectedOption !== this.selected) {
this._liveAnnouncer.announce((this.selected as MatOption).viewValue);
}
}
}

Expand Down