mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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:
committed by
Jeremy Elbourn
parent
8830486039
commit
140c94e2d1
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user