Skip to content

Commit 144f294

Browse files
committed
fix(select): close panel on alt + arrow key presses
* Closes the select panel when pressing alt + down/up arrow, based on the native select. * Does some minor cleanup around the select tests.
1 parent d85c44b commit 144f294

File tree

2 files changed

+67
-35
lines changed

2 files changed

+67
-35
lines changed

src/lib/select/select.spec.ts

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -62,34 +62,13 @@ import {
6262
/** The debounce interval when typing letters to select an option. */
6363
const LETTER_KEY_DEBOUNCE_INTERVAL = 200;
6464

65-
const platform = new Platform();
66-
67-
6865
describe('MatSelect', () => {
6966
let overlayContainer: OverlayContainer;
7067
let overlayContainerElement: HTMLElement;
7168
let dir: {value: 'ltr'|'rtl'};
7269
let scrolledSubject = new Subject();
7370
let viewportRuler: ViewportRuler;
74-
75-
// Providers used for all mat-select tests
76-
const commonProviders = [
77-
{provide: Directionality, useFactory: () => dir = {value: 'ltr'}},
78-
{
79-
provide: ScrollDispatcher, useFactory: () => ({
80-
scrolled: () => scrolledSubject.asObservable(),
81-
}),
82-
},
83-
];
84-
85-
// NgModule imports used for all mat-select tests.
86-
const commonModuleImports = [
87-
MatFormFieldModule,
88-
MatSelectModule,
89-
ReactiveFormsModule,
90-
FormsModule,
91-
NoopAnimationsModule,
92-
];
71+
let platform: Platform;
9372

9473
/**
9574
* Configures the test module for MatSelect with the given declarations. This is broken out so
@@ -99,14 +78,28 @@ describe('MatSelect', () => {
9978
*/
10079
function configureMatSelectTestingModule(declarations) {
10180
TestBed.configureTestingModule({
102-
imports: commonModuleImports,
81+
imports: [
82+
MatFormFieldModule,
83+
MatSelectModule,
84+
ReactiveFormsModule,
85+
FormsModule,
86+
NoopAnimationsModule,
87+
],
10388
declarations: declarations,
104-
providers: commonProviders,
89+
providers: [
90+
{provide: Directionality, useFactory: () => dir = {value: 'ltr'}},
91+
{
92+
provide: ScrollDispatcher, useFactory: () => ({
93+
scrolled: () => scrolledSubject.asObservable(),
94+
}),
95+
},
96+
],
10597
}).compileComponents();
10698

107-
inject([OverlayContainer], (oc: OverlayContainer) => {
99+
inject([OverlayContainer, Platform], (oc: OverlayContainer, p: Platform) => {
108100
overlayContainer = oc;
109101
overlayContainerElement = oc.getContainerElement();
102+
platform = p;
110103
})();
111104
}
112105

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

279+
it('should should close when pressing ALT + DOWN_ARROW', fakeAsync(() => {
280+
const {select: selectInstance} = fixture.componentInstance;
281+
282+
selectInstance.open();
283+
fixture.detectChanges();
284+
285+
expect(selectInstance.panelOpen).toBe(true, 'Expected select to be open.');
286+
287+
const event = createKeyboardEvent('keydown', DOWN_ARROW);
288+
Object.defineProperty(event, 'altKey', {get: () => true});
289+
290+
dispatchEvent(select, event);
291+
292+
expect(selectInstance.panelOpen).toBe(false, 'Expected select to be closed.');
293+
expect(event.defaultPrevented).toBe(true, 'Expected default action to be prevented.');
294+
}));
295+
296+
it('should should close when pressing ALT + UP_ARROW', fakeAsync(() => {
297+
const {select: selectInstance} = fixture.componentInstance;
298+
299+
selectInstance.open();
300+
fixture.detectChanges();
301+
302+
expect(selectInstance.panelOpen).toBe(true, 'Expected select to be open.');
303+
304+
const event = createKeyboardEvent('keydown', UP_ARROW);
305+
Object.defineProperty(event, 'altKey', {get: () => true});
306+
307+
dispatchEvent(select, event);
308+
309+
expect(selectInstance.panelOpen).toBe(false, 'Expected select to be closed.');
310+
expect(event.defaultPrevented).toBe(true, 'Expected default action to be prevented.');
311+
}));
312+
286313
it('should be able to select options by typing on a closed select', fakeAsync(() => {
287314
const formControl = fixture.componentInstance.control;
288315
const options = fixture.componentInstance.options.toArray();

src/lib/select/select.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,7 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
646646
const isArrowKey = keyCode === DOWN_ARROW || keyCode === UP_ARROW;
647647
const isOpenKey = keyCode === ENTER || keyCode === SPACE;
648648

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

661664
if (keyCode === HOME || keyCode === END) {
662665
event.preventDefault();
663-
keyCode === HOME ? this._keyManager.setFirstItemActive() :
664-
this._keyManager.setLastItemActive();
665-
} else if ((keyCode === ENTER || keyCode === SPACE) && this._keyManager.activeItem) {
666+
keyCode === HOME ? manager.setFirstItemActive() : manager.setLastItemActive();
667+
} else if (isArrowKey && event.altKey) {
668+
// Close the select on ALT + arrow key to match the native <select>
669+
event.preventDefault();
670+
this.close();
671+
} else if ((keyCode === ENTER || keyCode === SPACE) && manager.activeItem) {
666672
event.preventDefault();
667-
this._keyManager.activeItem._selectViaInteraction();
673+
manager.activeItem._selectViaInteraction();
668674
} else {
669-
const isArrowKey = keyCode === DOWN_ARROW || keyCode === UP_ARROW;
670-
const previouslyFocusedIndex = this._keyManager.activeItemIndex;
675+
const previouslyFocusedIndex = manager.activeItemIndex;
671676

672-
this._keyManager.onKeydown(event);
677+
manager.onKeydown(event);
673678

674-
if (this._multiple && isArrowKey && event.shiftKey && this._keyManager.activeItem &&
675-
this._keyManager.activeItemIndex !== previouslyFocusedIndex) {
676-
this._keyManager.activeItem._selectViaInteraction();
679+
if (this._multiple && isArrowKey && event.shiftKey && manager.activeItem &&
680+
manager.activeItemIndex !== previouslyFocusedIndex) {
681+
manager.activeItem._selectViaInteraction();
677682
}
678683
}
679684
}

0 commit comments

Comments
 (0)