From d2e11a82a7cc116c2ae9ea0c7c2b1488c6cea9e2 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Sat, 27 May 2017 14:33:59 +0200 Subject: [PATCH] fix(menu): not closing on escape when opened by mouse Fixes `md-menu` not closing when escape is pressed, if it was opened by a mouse click. The old behavior seemed to only be targeted towards keyboard users, however the escape key can also be considered a convenience feature. --- src/lib/menu/menu-directive.ts | 16 +++------------- src/lib/menu/menu-trigger.ts | 18 +++++++++++++++++- src/lib/menu/menu.html | 2 +- src/lib/menu/menu.spec.ts | 3 +-- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/lib/menu/menu-directive.ts b/src/lib/menu/menu-directive.ts index e4edc8d6e801..4113e0b7b2de 100644 --- a/src/lib/menu/menu-directive.ts +++ b/src/lib/menu/menu-directive.ts @@ -20,7 +20,6 @@ import {FocusKeyManager} from '../core/a11y/focus-key-manager'; import {MdMenuPanel} from './menu-panel'; import {Subscription} from 'rxjs/Subscription'; import {transformMenu, fadeInItems} from './menu-animations'; -import {ESCAPE} from '../core/keyboard/keycodes'; @Component({ @@ -37,13 +36,15 @@ import {ESCAPE} from '../core/keyboard/keycodes'; exportAs: 'mdMenu' }) export class MdMenu implements AfterContentInit, MdMenuPanel, OnDestroy { - private _keyManager: FocusKeyManager; private _xPosition: MenuPositionX = 'after'; private _yPosition: MenuPositionY = 'below'; /** Subscription to tab events on the menu panel */ private _tabSubscription: Subscription; + /** Manages keyboard events inside the panel. */ + _keyManager: FocusKeyManager; + /** Config object to be passed into the menu's ngClass */ _classList: any = {}; @@ -106,17 +107,6 @@ export class MdMenu implements AfterContentInit, MdMenuPanel, OnDestroy { } } - /** Handle a keyboard event from the menu, delegating to the appropriate action. */ - _handleKeydown(event: KeyboardEvent) { - switch (event.keyCode) { - case ESCAPE: - this._emitCloseEvent(); - return; - default: - this._keyManager.onKeydown(event); - } - } - /** * Focus the first item in the menu. This method is used by the menu trigger * to focus the first item when the menu is opened by the ENTER key. diff --git a/src/lib/menu/menu-trigger.ts b/src/lib/menu/menu-trigger.ts index 22e5240d72d0..4a8716d76080 100644 --- a/src/lib/menu/menu-trigger.ts +++ b/src/lib/menu/menu-trigger.ts @@ -8,6 +8,7 @@ import { Optional, Output, ViewContainerRef, + Renderer2, } from '@angular/core'; import {MdMenuPanel} from './menu-panel'; import {throwMdMenuMissingError} from './menu-errors'; @@ -24,6 +25,7 @@ import { VerticalConnectionPos, RepositionScrollStrategy, ScrollDispatcher, + ESCAPE, } from '../core'; import {Subscription} from 'rxjs/Subscription'; import {MenuPositionX, MenuPositionY} from './menu-positions'; @@ -50,6 +52,7 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy { private _menuOpen: boolean = false; private _backdropSubscription: Subscription; private _positionSubscription: Subscription; + private _globalKeydownHandler: () => void; // tracking input type is necessary so it's possible to only auto-focus // the first item of the list when the menu is opened via the keyboard @@ -81,7 +84,7 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy { constructor(private _overlay: Overlay, private _element: ElementRef, private _viewContainerRef: ViewContainerRef, @Optional() private _dir: Dir, - private _scrollDispatcher: ScrollDispatcher) { } + private _scrollDispatcher: ScrollDispatcher, private _renderer: Renderer2) { } ngAfterViewInit() { this._checkMenu(); @@ -104,6 +107,14 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy { this._createOverlay(); this._overlayRef.attach(this._portal); this._subscribeToBackdrop(); + + this._globalKeydownHandler = this._renderer.listen('document', 'keydown', + (event: KeyboardEvent) => { + if (event.keyCode === ESCAPE) { + this.closeMenu(); + } + }); + this._initMenu(); } } @@ -114,6 +125,11 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy { this._overlayRef.detach(); this._backdropSubscription.unsubscribe(); this._resetMenu(); + + if (this._globalKeydownHandler) { + this._globalKeydownHandler(); + this._globalKeydownHandler = null; + } } } diff --git a/src/lib/menu/menu.html b/src/lib/menu/menu.html index 10972059e8c5..989bf1f56acb 100644 --- a/src/lib/menu/menu.html +++ b/src/lib/menu/menu.html @@ -1,5 +1,5 @@ -
diff --git a/src/lib/menu/menu.spec.ts b/src/lib/menu/menu.spec.ts index 18e327753363..997f01cf50ea 100644 --- a/src/lib/menu/menu.spec.ts +++ b/src/lib/menu/menu.spec.ts @@ -87,8 +87,7 @@ describe('MdMenu', () => { fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); - const panel = overlayContainerElement.querySelector('.mat-menu-panel'); - dispatchKeyboardEvent(panel, 'keydown', ESCAPE); + dispatchKeyboardEvent(document, 'keydown', ESCAPE); fixture.detectChanges(); expect(overlayContainerElement.textContent).toBe('');