From 140c94e2d1dec24b8fe235b37261d0723d98f82d Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Tue, 6 Mar 2018 10:43:49 -0500 Subject: [PATCH] fix(menu): closed nested menu chain when tabbing away (#7750) Currently when tabbing inside a sub-menu, only the sub-menu is closed which restores focus to its parent. This is wrong, because it ends up sending the user back in the tab order. These changes close the whole chain of nested menus instead. For referece: https://www.w3.org/TR/wai-aria-practices-1.1/#menu --- src/lib/menu/menu-directive.ts | 6 +++--- src/lib/menu/menu-panel.ts | 2 +- src/lib/menu/menu-trigger.ts | 2 +- src/lib/menu/menu.spec.ts | 24 +++++++++++++++++++++++- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/lib/menu/menu-directive.ts b/src/lib/menu/menu-directive.ts index 3863103b3f4..9ff5f9aeb15 100644 --- a/src/lib/menu/menu-directive.ts +++ b/src/lib/menu/menu-directive.ts @@ -196,8 +196,8 @@ export class MatMenu implements OnInit, AfterContentInit, MatMenuPanel, OnDestro set classList(classes: string) { this.panelClass = classes; } /** Event emitted when the menu is closed. */ - @Output() readonly closed: EventEmitter = - new EventEmitter(); + @Output() readonly closed: EventEmitter = + new EventEmitter(); /** * Event emitted when the menu is closed. @@ -217,7 +217,7 @@ export class MatMenu implements OnInit, AfterContentInit, MatMenuPanel, OnDestro ngAfterContentInit() { this._keyManager = new FocusKeyManager(this.items).withWrap().withTypeAhead(); - this._tabSubscription = this._keyManager.tabOut.subscribe(() => this.close.emit('keydown')); + this._tabSubscription = this._keyManager.tabOut.subscribe(() => this.close.emit('tab')); } ngOnDestroy() { diff --git a/src/lib/menu/menu-panel.ts b/src/lib/menu/menu-panel.ts index 9e140a1d819..344841a81f5 100644 --- a/src/lib/menu/menu-panel.ts +++ b/src/lib/menu/menu-panel.ts @@ -21,7 +21,7 @@ export interface MatMenuPanel { yPosition: MenuPositionY; overlapTrigger: boolean; templateRef: TemplateRef; - close: EventEmitter; + close: EventEmitter; parentMenu?: MatMenuPanel | undefined; direction?: Direction; focusFirstItem: (origin?: FocusOrigin) => void; diff --git a/src/lib/menu/menu-trigger.ts b/src/lib/menu/menu-trigger.ts index ea5fc92a6ff..46e42754ddc 100644 --- a/src/lib/menu/menu-trigger.ts +++ b/src/lib/menu/menu-trigger.ts @@ -155,7 +155,7 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy { this._destroyMenu(); // If a click closed the menu, we should close the entire chain of nested menus. - if (reason === 'click' && this._parentMenu) { + if ((reason === 'click' || reason === 'tab') && this._parentMenu) { this._parentMenu.closed.emit(reason); } }); diff --git a/src/lib/menu/menu.spec.ts b/src/lib/menu/menu.spec.ts index 55676f8d6fd..39209f5fbd5 100644 --- a/src/lib/menu/menu.spec.ts +++ b/src/lib/menu/menu.spec.ts @@ -15,7 +15,7 @@ import { } from '@angular/core'; import {Direction, Directionality} from '@angular/cdk/bidi'; import {OverlayContainer, Overlay} from '@angular/cdk/overlay'; -import {ESCAPE, LEFT_ARROW, RIGHT_ARROW} from '@angular/cdk/keycodes'; +import {ESCAPE, LEFT_ARROW, RIGHT_ARROW, TAB} from '@angular/cdk/keycodes'; import { MAT_MENU_DEFAULT_OPTIONS, MatMenu, @@ -1168,6 +1168,28 @@ describe('MatMenu', () => { expect(overlay.querySelectorAll('.mat-menu-panel').length).toBe(0, 'Expected no open menus'); })); + it('should close all of the menus when the user tabs away', fakeAsync(() => { + compileTestComponent(); + instance.rootTriggerEl.nativeElement.click(); + fixture.detectChanges(); + + instance.levelOneTrigger.openMenu(); + fixture.detectChanges(); + + instance.levelTwoTrigger.openMenu(); + fixture.detectChanges(); + + const menus = overlay.querySelectorAll('.mat-menu-panel'); + + expect(menus.length).toBe(3, 'Expected three open menus'); + + dispatchKeyboardEvent(menus[menus.length - 1], 'keydown', TAB); + fixture.detectChanges(); + tick(500); + + expect(overlay.querySelectorAll('.mat-menu-panel').length).toBe(0, 'Expected no open menus'); + })); + it('should set a class on the menu items that trigger a sub-menu', () => { compileTestComponent(); instance.rootTrigger.openMenu();