Skip to content

fix(select): close panel on alt + arrow key presses #9250

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
Jan 23, 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
77 changes: 52 additions & 25 deletions src/lib/select/select.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,34 +62,13 @@ import {
/** The debounce interval when typing letters to select an option. */
const LETTER_KEY_DEBOUNCE_INTERVAL = 200;

const platform = new Platform();


describe('MatSelect', () => {
let overlayContainer: OverlayContainer;
let overlayContainerElement: HTMLElement;
let dir: {value: 'ltr'|'rtl'};
let scrolledSubject = new Subject();
let viewportRuler: ViewportRuler;

// Providers used for all mat-select tests
const commonProviders = [
{provide: Directionality, useFactory: () => dir = {value: 'ltr'}},
{
provide: ScrollDispatcher, useFactory: () => ({
scrolled: () => scrolledSubject.asObservable(),
}),
},
];

// NgModule imports used for all mat-select tests.
const commonModuleImports = [
MatFormFieldModule,
MatSelectModule,
ReactiveFormsModule,
FormsModule,
NoopAnimationsModule,
];
let platform: Platform;

/**
* Configures the test module for MatSelect with the given declarations. This is broken out so
Expand All @@ -99,14 +78,28 @@ describe('MatSelect', () => {
*/
function configureMatSelectTestingModule(declarations) {
TestBed.configureTestingModule({
imports: commonModuleImports,
imports: [
MatFormFieldModule,
MatSelectModule,
ReactiveFormsModule,
FormsModule,
NoopAnimationsModule,
],
declarations: declarations,
providers: commonProviders,
providers: [
{provide: Directionality, useFactory: () => dir = {value: 'ltr'}},
{
provide: ScrollDispatcher, useFactory: () => ({
scrolled: () => scrolledSubject.asObservable(),
}),
},
],
}).compileComponents();

inject([OverlayContainer], (oc: OverlayContainer) => {
inject([OverlayContainer, Platform], (oc: OverlayContainer, p: Platform) => {
overlayContainer = oc;
overlayContainerElement = oc.getContainerElement();
platform = p;
})();
}

Expand Down Expand Up @@ -283,6 +276,40 @@ describe('MatSelect', () => {
expect(formControl.value).toBeFalsy('Expected value not to have changed.');
}));

it('should should close when pressing ALT + DOWN_ARROW', fakeAsync(() => {
const {select: selectInstance} = fixture.componentInstance;

selectInstance.open();
fixture.detectChanges();

expect(selectInstance.panelOpen).toBe(true, 'Expected select to be open.');

const event = createKeyboardEvent('keydown', DOWN_ARROW);
Object.defineProperty(event, 'altKey', {get: () => true});

dispatchEvent(select, event);

expect(selectInstance.panelOpen).toBe(false, 'Expected select to be closed.');
expect(event.defaultPrevented).toBe(true, 'Expected default action to be prevented.');
}));

it('should should close when pressing ALT + UP_ARROW', fakeAsync(() => {
const {select: selectInstance} = fixture.componentInstance;

selectInstance.open();
fixture.detectChanges();

expect(selectInstance.panelOpen).toBe(true, 'Expected select to be open.');

const event = createKeyboardEvent('keydown', UP_ARROW);
Object.defineProperty(event, 'altKey', {get: () => true});

dispatchEvent(select, event);

expect(selectInstance.panelOpen).toBe(false, 'Expected select to be closed.');
expect(event.defaultPrevented).toBe(true, 'Expected default action to be prevented.');
}));

it('should be able to select options by typing on a closed select', fakeAsync(() => {
const formControl = fixture.componentInstance.control;
const options = fixture.componentInstance.options.toArray();
Expand Down
25 changes: 15 additions & 10 deletions src/lib/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,7 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
const isArrowKey = keyCode === DOWN_ARROW || keyCode === UP_ARROW;
const isOpenKey = keyCode === ENTER || keyCode === SPACE;

// Open the select on ALT + arrow key to match the native <select>
if (isOpenKey || ((this.multiple || event.altKey) && isArrowKey)) {
event.preventDefault(); // prevents the page from scrolling down when pressing space
this.open();
Expand All @@ -657,23 +658,27 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
/** Handles keyboard events when the selected is open. */
private _handleOpenKeydown(event: KeyboardEvent): void {
const keyCode = event.keyCode;
const isArrowKey = keyCode === DOWN_ARROW || keyCode === UP_ARROW;
const manager = this._keyManager;

if (keyCode === HOME || keyCode === END) {
event.preventDefault();
keyCode === HOME ? this._keyManager.setFirstItemActive() :
this._keyManager.setLastItemActive();
} else if ((keyCode === ENTER || keyCode === SPACE) && this._keyManager.activeItem) {
keyCode === HOME ? manager.setFirstItemActive() : manager.setLastItemActive();
} else if (isArrowKey && event.altKey) {
// Close the select on ALT + arrow key to match the native <select>
event.preventDefault();
this.close();
} else if ((keyCode === ENTER || keyCode === SPACE) && manager.activeItem) {
event.preventDefault();
this._keyManager.activeItem._selectViaInteraction();
manager.activeItem._selectViaInteraction();
} else {
const isArrowKey = keyCode === DOWN_ARROW || keyCode === UP_ARROW;
const previouslyFocusedIndex = this._keyManager.activeItemIndex;
const previouslyFocusedIndex = manager.activeItemIndex;

this._keyManager.onKeydown(event);
manager.onKeydown(event);

if (this._multiple && isArrowKey && event.shiftKey && this._keyManager.activeItem &&
this._keyManager.activeItemIndex !== previouslyFocusedIndex) {
this._keyManager.activeItem._selectViaInteraction();
if (this._multiple && isArrowKey && event.shiftKey && manager.activeItem &&
manager.activeItemIndex !== previouslyFocusedIndex) {
manager.activeItem._selectViaInteraction();
}
}
}
Expand Down