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
This commit is contained in:
Kristiyan Kostadinov
2018-03-06 10:43:49 -05:00
committed by Jeremy Elbourn
parent 8830486039
commit 140c94e2d1
4 changed files with 28 additions and 6 deletions
+3 -3
View File
@@ -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<void | 'click' | 'keydown'> =
new EventEmitter<void | 'click' | 'keydown'>();
@Output() readonly closed: EventEmitter<void | 'click' | 'keydown' | 'tab'> =
new EventEmitter<void | 'click' | 'keydown' | 'tab'>();
/**
* Event emitted when the menu is closed.
@@ -217,7 +217,7 @@ export class MatMenu implements OnInit, AfterContentInit, MatMenuPanel, OnDestro
ngAfterContentInit() {
this._keyManager = new FocusKeyManager<MatMenuItem>(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() {
+1 -1
View File
@@ -21,7 +21,7 @@ export interface MatMenuPanel {
yPosition: MenuPositionY;
overlapTrigger: boolean;
templateRef: TemplateRef<any>;
close: EventEmitter<void | 'click' | 'keydown'>;
close: EventEmitter<void | 'click' | 'keydown' | 'tab'>;
parentMenu?: MatMenuPanel | undefined;
direction?: Direction;
focusFirstItem: (origin?: FocusOrigin) => void;
+1 -1
View File
@@ -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);
}
});
+23 -1
View File
@@ -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();