From a1ad82bc851efc3c057efa4b3c532852da8fbac6 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Tue, 23 Jan 2018 16:53:53 +0100 Subject: [PATCH] fix(autocomplete): close panel using alt + up arrow (#9341) [Based on the accessibility guidelines](https://www.w3.org/TR/wai-aria-practices-1.1/#textbox-keyboard-interaction), the autocomplete panel can be closed using the alt + up arrow keyboard combo. --- src/lib/autocomplete/autocomplete-trigger.ts | 14 ++++++++------ src/lib/autocomplete/autocomplete.spec.ts | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts index bef825705c39..00c7e9046365 100644 --- a/src/lib/autocomplete/autocomplete-trigger.ts +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -129,8 +129,8 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy { /** The subscription for closing actions (some are bound to document). */ private _closingActionsSubscription: Subscription; - /** Stream of escape keyboard events. */ - private _escapeEventStream = new Subject(); + /** Stream of keyboard events that can close the panel. */ + private _closeKeyEventStream = new Subject(); /** View -> model callback called when value changes */ _onChange: (value: any) => void = () => {}; @@ -152,7 +152,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy { ngOnDestroy() { this._destroyPanel(); - this._escapeEventStream.complete(); + this._closeKeyEventStream.complete(); } /* Whether or not the autocomplete panel is open. */ @@ -194,7 +194,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy { return merge( this.optionSelections, this.autocomplete._keyManager.tabOut.pipe(filter(() => this._panelOpen)), - this._escapeEventStream, + this._closeKeyEventStream, this._outsideClickStream, this._overlayRef ? this._overlayRef.detachments().pipe(filter(() => this._panelOpen)) : @@ -289,9 +289,11 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy { _handleKeydown(event: KeyboardEvent): void { const keyCode = event.keyCode; - if (keyCode === ESCAPE && this.panelOpen) { + // Close when pressing ESCAPE or ALT + UP_ARROW, based on the a11y guidelines. + // See: https://www.w3.org/TR/wai-aria-practices-1.1/#textbox-keyboard-interaction + if (this.panelOpen && (keyCode === ESCAPE || (keyCode === UP_ARROW && event.altKey))) { this._resetActiveItem(); - this._escapeEventStream.next(); + this._closeKeyEventStream.next(); event.stopPropagation(); } else if (this.activeOption && keyCode === ENTER && this.panelOpen) { this.activeOption._selectViaInteraction(); diff --git a/src/lib/autocomplete/autocomplete.spec.ts b/src/lib/autocomplete/autocomplete.spec.ts index 7a256f3f7705..8088e8beb4bf 100644 --- a/src/lib/autocomplete/autocomplete.spec.ts +++ b/src/lib/autocomplete/autocomplete.spec.ts @@ -902,6 +902,25 @@ describe('MatAutocomplete', () => { expect(stopPropagationSpy).toHaveBeenCalled(); })); + it('should close the panel when pressing ALT + UP_ARROW', fakeAsync(() => { + const trigger = fixture.componentInstance.trigger; + const upArrowEvent = createKeyboardEvent('keydown', UP_ARROW); + Object.defineProperty(upArrowEvent, 'altKey', {get: () => true}); + + input.focus(); + flush(); + fixture.detectChanges(); + + expect(document.activeElement).toBe(input, 'Expected input to be focused.'); + expect(trigger.panelOpen).toBe(true, 'Expected panel to be open.'); + + trigger._handleKeydown(upArrowEvent); + fixture.detectChanges(); + + expect(document.activeElement).toBe(input, 'Expected input to continue to be focused.'); + expect(trigger.panelOpen).toBe(false, 'Expected panel to be closed.'); + })); + it('should close the panel when tabbing away from a trigger without results', fakeAsync(() => { fixture.componentInstance.states = []; fixture.componentInstance.filteredStates = [];