feat(menu): allow focus restoration to be disabled (#15205)

Allows for focus restoration to be disabled in a menu, similarly to some other components. In some cases restoring focus automatically might not make sense (e.g. if the user closes the menu by selecting something which then opens a dialog) and this allows consumers to customize the behavior as needed.

Fixes #15168.
This commit is contained in:
Kristiyan Kostadinov
2019-02-22 16:10:49 +01:00
committed by Jeremy Elbourn
parent 413fe33c73
commit dfa40fc7fb
3 changed files with 42 additions and 7 deletions
+15 -6
View File
@@ -137,6 +137,13 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
/** Data to be passed along to any lazily-rendered content. */
@Input('matMenuTriggerData') menuData: any;
/**
* Whether focus should be restored when the menu is closed.
* Note that disabling this option can have accessibility implications
* and it's up to you to manage focus, if you decide to turn it off.
*/
@Input('matMenuTriggerRestoreFocus') restoreFocus: boolean = true;
/** Event emitted when the associated menu is opened. */
@Output() readonly menuOpened: EventEmitter<void> = new EventEmitter<void>();
@@ -339,12 +346,14 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
// We should reset focus if the user is navigating using a keyboard or
// if we have a top-level trigger which might cause focus to be lost
// when clicking on the backdrop.
if (!this._openedBy) {
// Note that the focus style will show up both for `program` and
// `keyboard` so we don't have to specify which one it is.
this.focus();
} else if (!this.triggersSubmenu()) {
this.focus(this._openedBy);
if (this.restoreFocus) {
if (!this._openedBy) {
// Note that the focus style will show up both for `program` and
// `keyboard` so we don't have to specify which one it is.
this.focus();
} else if (!this.triggersSubmenu()) {
this.focus(this._openedBy);
}
}
this._openedBy = null;
+26 -1
View File
@@ -159,6 +159,27 @@ describe('MatMenu', () => {
expect(document.activeElement).toBe(triggerEl);
}));
it('should not restore focus to the trigger if focus restoration is disabled', fakeAsync(() => {
const fixture = createComponent(SimpleMenu, [], [FakeIcon]);
fixture.detectChanges();
const triggerEl = fixture.componentInstance.triggerEl.nativeElement;
fixture.componentInstance.restoreFocus = false;
fixture.detectChanges();
// A click without a mousedown before it is considered a keyboard open.
triggerEl.click();
fixture.detectChanges();
expect(overlayContainerElement.querySelector('.mat-menu-panel')).toBeTruthy();
fixture.componentInstance.trigger.closeMenu();
fixture.detectChanges();
tick(500);
expect(document.activeElement).not.toBe(triggerEl);
}));
it('should be able to set a custom class on the backdrop', fakeAsync(() => {
const fixture = createComponent(SimpleMenu, [], [FakeIcon]);
@@ -1976,7 +1997,10 @@ describe('MatMenu default overrides', () => {
@Component({
template: `
<button [matMenuTriggerFor]="menu" #triggerEl>Toggle menu</button>
<button
[matMenuTriggerFor]="menu"
[matMenuTriggerRestoreFocus]="restoreFocus"
#triggerEl>Toggle menu</button>
<mat-menu
#menu="matMenu"
[class]="panelClass"
@@ -2002,6 +2026,7 @@ class SimpleMenu {
closeCallback = jasmine.createSpy('menu closed callback');
backdropClass: string;
panelClass: string;
restoreFocus = true;
}
@Component({
+1
View File
@@ -112,6 +112,7 @@ export declare class MatMenuTrigger implements AfterContentInit, OnDestroy {
readonly menuOpened: EventEmitter<void>;
readonly onMenuClose: EventEmitter<void>;
readonly onMenuOpen: EventEmitter<void>;
restoreFocus: boolean;
constructor(_overlay: Overlay, _element: ElementRef<HTMLElement>, _viewContainerRef: ViewContainerRef, scrollStrategy: any, _parentMenu: MatMenu, _menuItemInstance: MatMenuItem, _dir: Directionality, _focusMonitor?: FocusMonitor | undefined);
_handleClick(event: MouseEvent): void;
_handleKeydown(event: KeyboardEvent): void;