Skip to content

Commit

Permalink
fix(autocomplete): handle escape key
Browse files Browse the repository at this point in the history
* Handles the escape key in the autocomplete by closing the panel, if it was open, or blurring the input. I've based the functionality on the autocomplete from Google Drive.
* Switches the autocomplete unit tests to using the common utility for creating fake keyboard events.
  • Loading branch information
crisbeto committed May 21, 2017
1 parent c946631 commit a84b4e0
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 19 deletions.
6 changes: 4 additions & 2 deletions src/lib/autocomplete/autocomplete-trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {PositionStrategy} from '../core/overlay/position/position-strategy';
import {ConnectedPositionStrategy} from '../core/overlay/position/connected-position-strategy';
import {Observable} from 'rxjs/Observable';
import {MdOptionSelectionChange, MdOption} from '../core/option/option';
import {ENTER, UP_ARROW, DOWN_ARROW} from '../core/keyboard/keycodes';
import {ENTER, UP_ARROW, DOWN_ARROW, ESCAPE} from '../core/keyboard/keycodes';
import {Dir} from '../core/rtl/dir';
import {MdInputContainer} from '../input/input-container';
import {ScrollDispatcher} from '../core/overlay/scroll/scroll-dispatcher';
Expand Down Expand Up @@ -231,7 +231,9 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
}

_handleKeydown(event: KeyboardEvent): void {
if (this.activeOption && event.keyCode === ENTER) {
if (event.keyCode === ESCAPE) {
this.panelOpen ? this.closePanel() : this._element.nativeElement.blur();
} else if (this.activeOption && event.keyCode === ENTER) {
this.activeOption._selectViaInteraction();
event.preventDefault();
} else {
Expand Down
50 changes: 33 additions & 17 deletions src/lib/autocomplete/autocomplete.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ import {MdInputModule} from '../input/index';
import {Dir, LayoutDirection} from '../core/rtl/dir';
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
import {Subscription} from 'rxjs/Subscription';
import {ENTER, DOWN_ARROW, SPACE, UP_ARROW, HOME, END} from '../core/keyboard/keycodes';
import {ENTER, DOWN_ARROW, SPACE, UP_ARROW, HOME, END, ESCAPE} from '../core/keyboard/keycodes';
import {MdOption} from '../core/option/option';
import {MdAutocomplete} from './autocomplete';
import {MdInputContainer} from '../input/input-container';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
import {dispatchFakeEvent} from '../core/testing/dispatch-events';
import {createKeyboardEvent} from '../core/testing/event-objects';
import {typeInElement} from '../core/testing/type-in-element';
import {ScrollDispatcher} from '../core/overlay/scroll/scroll-dispatcher';

Expand Down Expand Up @@ -535,8 +536,8 @@ describe('MdAutocomplete', () => {
fixture.detectChanges();

input = fixture.debugElement.query(By.css('input')).nativeElement;
DOWN_ARROW_EVENT = new MockKeyboardEvent(DOWN_ARROW) as KeyboardEvent;
ENTER_EVENT = new MockKeyboardEvent(ENTER) as KeyboardEvent;
DOWN_ARROW_EVENT = createKeyboardEvent('keydown', DOWN_ARROW);
ENTER_EVENT = createKeyboardEvent('keydown', ENTER);

fixture.componentInstance.trigger.openPanel();
fixture.detectChanges();
Expand Down Expand Up @@ -594,7 +595,7 @@ describe('MdAutocomplete', () => {
const optionEls =
overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;

const UP_ARROW_EVENT = new MockKeyboardEvent(UP_ARROW) as KeyboardEvent;
const UP_ARROW_EVENT = createKeyboardEvent('keydown', UP_ARROW);
fixture.componentInstance.trigger._handleKeydown(UP_ARROW_EVENT);
tick();
fixture.detectChanges();
Expand Down Expand Up @@ -668,7 +669,7 @@ describe('MdAutocomplete', () => {
typeInElement('New', input);
fixture.detectChanges();

const SPACE_EVENT = new MockKeyboardEvent(SPACE) as KeyboardEvent;
const SPACE_EVENT = createKeyboardEvent('keydown', SPACE);
fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT);

fixture.whenStable().then(() => {
Expand Down Expand Up @@ -748,7 +749,7 @@ describe('MdAutocomplete', () => {
const scrollContainer =
document.querySelector('.cdk-overlay-pane .mat-autocomplete-panel');

const UP_ARROW_EVENT = new MockKeyboardEvent(UP_ARROW) as KeyboardEvent;
const UP_ARROW_EVENT = createKeyboardEvent('keydown', UP_ARROW);
fixture.componentInstance.trigger._handleKeydown(UP_ARROW_EVENT);
tick();
fixture.detectChanges();
Expand All @@ -762,7 +763,7 @@ describe('MdAutocomplete', () => {
const scrollContainer =
document.querySelector('.cdk-overlay-pane .mat-autocomplete-panel');

const END_EVENT = new MockKeyboardEvent(END) as KeyboardEvent;
const END_EVENT = createKeyboardEvent('keydown', END);
fixture.componentInstance.trigger._handleKeydown(END_EVENT);
tick();
fixture.detectChanges();
Expand All @@ -779,14 +780,36 @@ describe('MdAutocomplete', () => {
scrollContainer.scrollTop = 100;
fixture.detectChanges();

const HOME_EVENT = new MockKeyboardEvent(HOME) as KeyboardEvent;
const HOME_EVENT = createKeyboardEvent('keydown', HOME);
fixture.componentInstance.trigger._handleKeydown(HOME_EVENT);
tick();
fixture.detectChanges();

expect(scrollContainer.scrollTop).toEqual(0, 'Expected panel to reveal the first option.');
}));

it('should close the panel when pressing escape, and blur the input when pressing escape again',
async(() => {
const trigger = fixture.componentInstance.trigger;
const escapeEvent = createKeyboardEvent('keydown', ESCAPE);

input.focus();

fixture.whenStable().then(() => {
expect(document.activeElement).toBe(input, 'Expected input to be focused.');
expect(trigger.panelOpen).toBe(true, 'Expected panel to be open.');

trigger._handleKeydown(escapeEvent);

expect(document.activeElement).toBe(input, 'Expected input to continue to be focused.');
expect(trigger.panelOpen).toBe(false, 'Expected panel to be closed.');

trigger._handleKeydown(escapeEvent);

expect(document.activeElement).not.toBe(input, 'Expected input to no longer be focused.');
});
}));

});

describe('aria', () => {
Expand Down Expand Up @@ -832,7 +855,7 @@ describe('MdAutocomplete', () => {
expect(input.hasAttribute('aria-activedescendant'))
.toBe(false, 'Expected aria-activedescendant to be absent if no active item.');

const DOWN_ARROW_EVENT = new MockKeyboardEvent(DOWN_ARROW) as KeyboardEvent;
const DOWN_ARROW_EVENT = createKeyboardEvent('keydown', DOWN_ARROW);
fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT);

fixture.whenStable().then(() => {
Expand Down Expand Up @@ -1140,7 +1163,7 @@ describe('MdAutocomplete', () => {
tick();
fixture.detectChanges();

const DOWN_ARROW_EVENT = new MockKeyboardEvent(DOWN_ARROW) as KeyboardEvent;
const DOWN_ARROW_EVENT = createKeyboardEvent('keydown', DOWN_ARROW);
fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT);
tick();
fixture.detectChanges();
Expand Down Expand Up @@ -1418,10 +1441,3 @@ class AutocompleteWithNativeInput {
});
}
}


/** This is a mock keyboard event to test keyboard events in the autocomplete. */
class MockKeyboardEvent {
constructor(public keyCode: number) {}
preventDefault() {}
}

0 comments on commit a84b4e0

Please sign in to comment.