Skip to content

Commit dfa40fc

Browse files
crisbetojelbourn
authored andcommitted
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.
1 parent 413fe33 commit dfa40fc

File tree

3 files changed

+42
-7
lines changed

3 files changed

+42
-7
lines changed

src/lib/menu/menu-trigger.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,13 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
137137
/** Data to be passed along to any lazily-rendered content. */
138138
@Input('matMenuTriggerData') menuData: any;
139139

140+
/**
141+
* Whether focus should be restored when the menu is closed.
142+
* Note that disabling this option can have accessibility implications
143+
* and it's up to you to manage focus, if you decide to turn it off.
144+
*/
145+
@Input('matMenuTriggerRestoreFocus') restoreFocus: boolean = true;
146+
140147
/** Event emitted when the associated menu is opened. */
141148
@Output() readonly menuOpened: EventEmitter<void> = new EventEmitter<void>();
142149

@@ -339,12 +346,14 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
339346
// We should reset focus if the user is navigating using a keyboard or
340347
// if we have a top-level trigger which might cause focus to be lost
341348
// when clicking on the backdrop.
342-
if (!this._openedBy) {
343-
// Note that the focus style will show up both for `program` and
344-
// `keyboard` so we don't have to specify which one it is.
345-
this.focus();
346-
} else if (!this.triggersSubmenu()) {
347-
this.focus(this._openedBy);
349+
if (this.restoreFocus) {
350+
if (!this._openedBy) {
351+
// Note that the focus style will show up both for `program` and
352+
// `keyboard` so we don't have to specify which one it is.
353+
this.focus();
354+
} else if (!this.triggersSubmenu()) {
355+
this.focus(this._openedBy);
356+
}
348357
}
349358

350359
this._openedBy = null;

src/lib/menu/menu.spec.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,27 @@ describe('MatMenu', () => {
159159
expect(document.activeElement).toBe(triggerEl);
160160
}));
161161

162+
it('should not restore focus to the trigger if focus restoration is disabled', fakeAsync(() => {
163+
const fixture = createComponent(SimpleMenu, [], [FakeIcon]);
164+
fixture.detectChanges();
165+
const triggerEl = fixture.componentInstance.triggerEl.nativeElement;
166+
167+
fixture.componentInstance.restoreFocus = false;
168+
fixture.detectChanges();
169+
170+
// A click without a mousedown before it is considered a keyboard open.
171+
triggerEl.click();
172+
fixture.detectChanges();
173+
174+
expect(overlayContainerElement.querySelector('.mat-menu-panel')).toBeTruthy();
175+
176+
fixture.componentInstance.trigger.closeMenu();
177+
fixture.detectChanges();
178+
tick(500);
179+
180+
expect(document.activeElement).not.toBe(triggerEl);
181+
}));
182+
162183
it('should be able to set a custom class on the backdrop', fakeAsync(() => {
163184
const fixture = createComponent(SimpleMenu, [], [FakeIcon]);
164185

@@ -1976,7 +1997,10 @@ describe('MatMenu default overrides', () => {
19761997

19771998
@Component({
19781999
template: `
1979-
<button [matMenuTriggerFor]="menu" #triggerEl>Toggle menu</button>
2000+
<button
2001+
[matMenuTriggerFor]="menu"
2002+
[matMenuTriggerRestoreFocus]="restoreFocus"
2003+
#triggerEl>Toggle menu</button>
19802004
<mat-menu
19812005
#menu="matMenu"
19822006
[class]="panelClass"
@@ -2002,6 +2026,7 @@ class SimpleMenu {
20022026
closeCallback = jasmine.createSpy('menu closed callback');
20032027
backdropClass: string;
20042028
panelClass: string;
2029+
restoreFocus = true;
20052030
}
20062031

20072032
@Component({

tools/public_api_guard/lib/menu.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ export declare class MatMenuTrigger implements AfterContentInit, OnDestroy {
112112
readonly menuOpened: EventEmitter<void>;
113113
readonly onMenuClose: EventEmitter<void>;
114114
readonly onMenuOpen: EventEmitter<void>;
115+
restoreFocus: boolean;
115116
constructor(_overlay: Overlay, _element: ElementRef<HTMLElement>, _viewContainerRef: ViewContainerRef, scrollStrategy: any, _parentMenu: MatMenu, _menuItemInstance: MatMenuItem, _dir: Directionality, _focusMonitor?: FocusMonitor | undefined);
116117
_handleClick(event: MouseEvent): void;
117118
_handleKeydown(event: KeyboardEvent): void;

0 commit comments

Comments
 (0)