Skip to content

Commit c3ec94d

Browse files
crisbetojelbourn
authored andcommitted
fix(select): close panel on alt + arrow key presses (#9250)
* 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 58b665e commit c3ec94d

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
@@ -647,6 +647,7 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
647647
const isArrowKey = keyCode === DOWN_ARROW || keyCode === UP_ARROW;
648648
const isOpenKey = keyCode === ENTER || keyCode === SPACE;
649649

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

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

673-
this._keyManager.onKeydown(event);
678+
manager.onKeydown(event);
674679

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

0 commit comments

Comments
 (0)