Skip to content

Commit 4f5b2a3

Browse files
committed
refactor: move escape key logic to connected overlay directive
1 parent c7d4038 commit 4f5b2a3

File tree

7 files changed

+44
-49
lines changed

7 files changed

+44
-49
lines changed

src/lib/core/a11y/list-key-manager.spec.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {QueryList} from '@angular/core';
22
import {fakeAsync, tick} from '@angular/core/testing';
33
import {FocusKeyManager} from './focus-key-manager';
4-
import {DOWN_ARROW, UP_ARROW, TAB, HOME, END, ESCAPE} from '../keyboard/keycodes';
4+
import {DOWN_ARROW, UP_ARROW, TAB, HOME, END} from '../keyboard/keycodes';
55
import {ListKeyManager} from './list-key-manager';
66
import {ActiveDescendantKeyManager} from './activedescendant-key-manager';
77

@@ -39,7 +39,6 @@ describe('Key managers', () => {
3939
let TAB_EVENT: KeyboardEvent;
4040
let HOME_EVENT: KeyboardEvent;
4141
let END_EVENT: KeyboardEvent;
42-
let ESCAPE_EVENT: KeyboardEvent;
4342

4443
beforeEach(() => {
4544
itemList = new FakeQueryList<any>();
@@ -49,7 +48,6 @@ describe('Key managers', () => {
4948
TAB_EVENT = new FakeEvent(TAB) as KeyboardEvent;
5049
HOME_EVENT = new FakeEvent(HOME) as KeyboardEvent;
5150
END_EVENT = new FakeEvent(END) as KeyboardEvent;
52-
ESCAPE_EVENT = new FakeEvent(ESCAPE) as KeyboardEvent;
5351
});
5452

5553

@@ -242,14 +240,6 @@ describe('Key managers', () => {
242240
expect(TAB_EVENT.defaultPrevented).toBe(false);
243241
});
244242

245-
it('should emit an event when escape is pressed', () => {
246-
let spy = jasmine.createSpy('escape spy');
247-
keyManager.escape.first().subscribe(spy);
248-
keyManager.onKeydown(ESCAPE_EVENT);
249-
250-
expect(spy).toHaveBeenCalled();
251-
});
252-
253243
it('should activate the first item when pressing down on a clean key manager', () => {
254244
keyManager = new ListKeyManager<FakeFocusable>(itemList);
255245

src/lib/core/a11y/list-key-manager.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {QueryList} from '@angular/core';
2-
import {UP_ARROW, DOWN_ARROW, TAB, HOME, END, ESCAPE} from '../core';
2+
import {UP_ARROW, DOWN_ARROW, TAB, HOME, END} from '../core';
33
import {Observable} from 'rxjs/Observable';
44
import {Subject} from 'rxjs/Subject';
55

@@ -19,7 +19,6 @@ export class ListKeyManager<T extends CanDisable> {
1919
private _activeItemIndex: number = null;
2020
private _activeItem: T;
2121
private _tabOut = new Subject<void>();
22-
private _escape = new Subject<void>();
2322
private _wrap: boolean = false;
2423

2524
constructor(private _items: QueryList<T>) {
@@ -64,9 +63,6 @@ export class ListKeyManager<T extends CanDisable> {
6463
case END:
6564
this.setLastItemActive();
6665
break;
67-
case ESCAPE:
68-
this._escape.next(null);
69-
break;
7066
case TAB:
7167
// Note that we shouldn't prevent the default action on tab.
7268
this._tabOut.next(null);
@@ -125,14 +121,6 @@ export class ListKeyManager<T extends CanDisable> {
125121
return this._tabOut.asObservable();
126122
}
127123

128-
/**
129-
* Observable that emits whenever the escape key is pressed, which usually indicates
130-
* that the component should be closed.
131-
*/
132-
get escape(): Observable<void> {
133-
return this._escape.asObservable();
134-
}
135-
136124
/**
137125
* This method sets the active item, given a list of items and the delta between the
138126
* currently active item and the new active item. It will calculate differently

src/lib/core/overlay/overlay-directives.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {OverlayContainer} from './overlay-container';
66
import {ConnectedPositionStrategy} from './position/connected-position-strategy';
77
import {ConnectedOverlayPositionChange} from './position/connected-position';
88
import {Dir} from '../rtl/dir';
9+
import {dispatchKeyboardEvent} from '../testing/dispatch-events';
10+
import {ESCAPE} from '../keyboard/keycodes';
911

1012

1113
describe('Overlay directives', () => {
@@ -98,6 +100,17 @@ describe('Overlay directives', () => {
98100
expect(getPaneElement().getAttribute('dir')).toBe('ltr');
99101
});
100102

103+
it('should close when pressing escape', () => {
104+
fixture.componentInstance.isOpen = true;
105+
fixture.detectChanges();
106+
107+
dispatchKeyboardEvent(document, 'keydown', ESCAPE);
108+
fixture.detectChanges();
109+
110+
expect(overlayContainerElement.textContent.trim()).toBe('',
111+
'Expected overlay to have been detached.');
112+
});
113+
101114
describe('inputs', () => {
102115

103116
it('should set the width', () => {

src/lib/core/overlay/overlay-directives.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
Input,
1010
OnDestroy,
1111
Output,
12-
ElementRef
12+
ElementRef,
13+
Renderer2,
1314
} from '@angular/core';
1415
import {Overlay, OVERLAY_PROVIDERS} from './overlay';
1516
import {OverlayRef} from './overlay-ref';
@@ -21,10 +22,12 @@ import {
2122
} from './position/connected-position';
2223
import {PortalModule} from '../portal/portal-directives';
2324
import {ConnectedPositionStrategy} from './position/connected-position-strategy';
24-
import {Subscription} from 'rxjs/Subscription';
2525
import {Dir, LayoutDirection} from '../rtl/dir';
2626
import {Scrollable} from './scroll/scrollable';
2727
import {coerceBooleanProperty} from '../coercion/boolean-property';
28+
import {ESCAPE} from '../keyboard/keycodes';
29+
import {Subscription} from 'rxjs/Subscription';
30+
2831

2932
/** Default set of positions for the overlay. Follows the behavior of a dropdown. */
3033
let defaultPositionList = [
@@ -68,6 +71,7 @@ export class ConnectedOverlayDirective implements OnDestroy {
6871
private _offsetX: number = 0;
6972
private _offsetY: number = 0;
7073
private _position: ConnectedPositionStrategy;
74+
private _closeKeyListener: Function;
7175

7276
/** Origin for the connected overlay. */
7377
@Input() origin: OverlayOrigin;
@@ -152,6 +156,7 @@ export class ConnectedOverlayDirective implements OnDestroy {
152156

153157
constructor(
154158
private _overlay: Overlay,
159+
private _renderer: Renderer2,
155160
templateRef: TemplateRef<any>,
156161
viewContainerRef: ViewContainerRef,
157162
@Optional() private _dir: Dir) {
@@ -249,6 +254,12 @@ export class ConnectedOverlayDirective implements OnDestroy {
249254

250255
this._position.withDirection(this.dir);
251256
this._overlayRef.getState().direction = this.dir;
257+
this._closeKeyListener = this._renderer.listen('document', 'keydown',
258+
(event: KeyboardEvent) => {
259+
if (event.keyCode === ESCAPE) {
260+
this._detachOverlay();
261+
}
262+
});
252263

253264
if (!this._overlayRef.hasAttached()) {
254265
this._overlayRef.attach(this._templatePortal);
@@ -273,6 +284,10 @@ export class ConnectedOverlayDirective implements OnDestroy {
273284
this._backdropSubscription.unsubscribe();
274285
this._backdropSubscription = null;
275286
}
287+
288+
if (this._closeKeyListener) {
289+
this._closeKeyListener();
290+
}
276291
}
277292

278293
/** Destroys the overlay created by this directive. */
@@ -284,9 +299,14 @@ export class ConnectedOverlayDirective implements OnDestroy {
284299
if (this._backdropSubscription) {
285300
this._backdropSubscription.unsubscribe();
286301
}
302+
287303
if (this._positionSubscription) {
288304
this._positionSubscription.unsubscribe();
289305
}
306+
307+
if (this._closeKeyListener) {
308+
this._closeKeyListener();
309+
}
290310
}
291311
}
292312

src/lib/select/select.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
<ng-template cdk-connected-overlay [origin]="origin" [open]="panelOpen" hasBackdrop (backdropClick)="close()"
1717
backdropClass="cdk-overlay-transparent-backdrop" [positions]="_positions" [minWidth]="_triggerWidth"
18-
[offsetY]="_offsetY" [offsetX]="_offsetX" (attach)="_setScrollTop()">
18+
[offsetY]="_offsetY" [offsetX]="_offsetX" (attach)="_setScrollTop()" (detach)="close()">
1919
<div class="mat-select-panel" [@transformPanel]="'showing'" (@transformPanel.done)="_onPanelDone()"
2020
(keydown)="_keyManager.onKeydown($event)" [style.transformOrigin]="_transformOrigin"
2121
[class.mat-select-panel-done-animating]="_panelDoneAnimating">

src/lib/select/select.spec.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
2323
import {dispatchFakeEvent, dispatchKeyboardEvent} from '../core/testing/dispatch-events';
2424
import {wrappedErrorMessage} from '../core/testing/wrapped-error-message';
25-
import {TAB, ESCAPE} from '../core/keyboard/keycodes';
25+
import {TAB} from '../core/keyboard/keycodes';
2626

2727

2828
describe('MdSelect', () => {
@@ -220,20 +220,6 @@ describe('MdSelect', () => {
220220
});
221221
}));
222222

223-
it('should close the panel when pressing escape', async(() => {
224-
trigger.click();
225-
fixture.detectChanges();
226-
expect(fixture.componentInstance.select.panelOpen).toBe(true);
227-
228-
const panel = overlayContainerElement.querySelector('.mat-select-panel');
229-
dispatchKeyboardEvent(panel, 'keydown', ESCAPE);
230-
fixture.detectChanges();
231-
232-
fixture.whenStable().then(() => {
233-
expect(fixture.componentInstance.select.panelOpen).toBe(false);
234-
});
235-
}));
236-
237223
});
238224

239225
describe('selection logic', () => {

src/lib/select/select.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal
130130
/** Subscription to changes in the option list. */
131131
private _changeSubscription: Subscription;
132132

133-
/** Subscription to tab and escape presses while overlay is focused. */
134-
private _closeKeySubscription: Subscription;
133+
/** Subscription to tab events while overlay is focused. */
134+
private _tabSubscription: Subscription;
135135

136136
/** Whether filling out the select is required in the form. */
137137
private _required: boolean = false;
@@ -337,8 +337,8 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal
337337
this._changeSubscription.unsubscribe();
338338
}
339339

340-
if (this._closeKeySubscription) {
341-
this._closeKeySubscription.unsubscribe();
340+
if (this._tabSubscription) {
341+
this._tabSubscription.unsubscribe();
342342
}
343343
}
344344

@@ -569,9 +569,7 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal
569569
/** Sets up a key manager to listen to keyboard events on the overlay panel. */
570570
private _initKeyManager() {
571571
this._keyManager = new FocusKeyManager(this.options);
572-
this._closeKeySubscription = Observable
573-
.merge(this._keyManager.tabOut, this._keyManager.escape)
574-
.subscribe(() => this.close());
572+
this._tabSubscription = this._keyManager.tabOut.subscribe(() => this.close());
575573
}
576574

577575
/** Drops current option subscriptions and IDs and resets from scratch. */

0 commit comments

Comments
 (0)