From dfa40fc7fb60fa60a5a057cb25b888903d470cf5 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Fri, 22 Feb 2019 16:10:49 +0100 Subject: [PATCH] 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. --- src/lib/menu/menu-trigger.ts | 21 +++++++++++++++------ src/lib/menu/menu.spec.ts | 27 ++++++++++++++++++++++++++- tools/public_api_guard/lib/menu.d.ts | 1 + 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/lib/menu/menu-trigger.ts b/src/lib/menu/menu-trigger.ts index 1f2dffc3002..16264cf885c 100644 --- a/src/lib/menu/menu-trigger.ts +++ b/src/lib/menu/menu-trigger.ts @@ -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 = new EventEmitter(); @@ -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; diff --git a/src/lib/menu/menu.spec.ts b/src/lib/menu/menu.spec.ts index 3d047007bad..a48d104baa6 100644 --- a/src/lib/menu/menu.spec.ts +++ b/src/lib/menu/menu.spec.ts @@ -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: ` - + ; readonly onMenuClose: EventEmitter; readonly onMenuOpen: EventEmitter; + restoreFocus: boolean; constructor(_overlay: Overlay, _element: ElementRef, _viewContainerRef: ViewContainerRef, scrollStrategy: any, _parentMenu: MatMenu, _menuItemInstance: MatMenuItem, _dir: Directionality, _focusMonitor?: FocusMonitor | undefined); _handleClick(event: MouseEvent): void; _handleKeydown(event: KeyboardEvent): void;