@@ -36,7 +36,7 @@ import {
3636import { normalizePassiveListenerOptions } from '@angular/cdk/platform' ;
3737import { asapScheduler , merge , of as observableOf , Subscription } from 'rxjs' ;
3838import { delay , filter , take , takeUntil } from 'rxjs/operators' ;
39- import { _MatMenuBase } from './menu' ;
39+ import { MenuCloseReason , _MatMenuBase } from './menu' ;
4040import { throwMatMenuMissingError , throwMatMenuRecursiveError } from './menu-errors' ;
4141import { MatMenuItem } from './menu-item' ;
4242import { MatMenuPanel , MAT_MENU_PANEL } from './menu-panel' ;
@@ -103,7 +103,7 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
103103
104104 // Tracking input type is necessary so it's possible to only auto-focus
105105 // the first item of the list when the menu is opened via the keyboard
106- _openedBy : Exclude < FocusOrigin , 'program' > = null ;
106+ _openedBy : Exclude < FocusOrigin , 'program' | null > | undefined = undefined ;
107107
108108 /**
109109 * @deprecated
@@ -131,15 +131,14 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
131131 throwMatMenuRecursiveError ( ) ;
132132 }
133133
134- this . _menuCloseSubscription = menu . close . subscribe (
135- ( reason : 'click' | 'tab' | 'keydown' | undefined ) => {
136- this . _destroyMenu ( ) ;
134+ this . _menuCloseSubscription = menu . close . subscribe ( ( reason : MenuCloseReason ) => {
135+ this . _destroyMenu ( reason ) ;
137136
138- // If a click closed the menu, we should close the entire chain of nested menus.
139- if ( ( reason === 'click' || reason === 'tab' ) && this . _parentMaterialMenu ) {
140- this . _parentMaterialMenu . closed . emit ( reason ) ;
141- }
142- } ) ;
137+ // If a click closed the menu, we should close the entire chain of nested menus.
138+ if ( ( reason === 'click' || reason === 'tab' ) && this . _parentMaterialMenu ) {
139+ this . _parentMaterialMenu . closed . emit ( reason ) ;
140+ }
141+ } ) ;
143142 }
144143 }
145144 private _menu : MatMenuPanel ;
@@ -284,15 +283,24 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
284283 }
285284
286285 /** Closes the menu and does the necessary cleanup. */
287- private _destroyMenu ( ) {
286+ private _destroyMenu ( reason : MenuCloseReason ) {
288287 if ( ! this . _overlayRef || ! this . menuOpen ) {
289288 return ;
290289 }
291290
292291 const menu = this . menu ;
293292 this . _closingActionsSubscription . unsubscribe ( ) ;
294293 this . _overlayRef . detach ( ) ;
295- this . _restoreFocus ( ) ;
294+
295+ // Always restore focus if the user is navigating using the keyboard or the menu was opened
296+ // programmatically. We don't restore for non-root triggers, because it can prevent focus
297+ // from making it back to the root trigger when closing a long chain of menus by clicking
298+ // on the backdrop.
299+ if ( this . restoreFocus && ( reason === 'keydown' || ! this . _openedBy || ! this . triggersSubmenu ( ) ) ) {
300+ this . focus ( this . _openedBy ) ;
301+ }
302+
303+ this . _openedBy = undefined ;
296304
297305 if ( menu instanceof _MatMenuBase ) {
298306 menu . _resetAnimation ( ) ;
@@ -350,24 +358,6 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
350358 }
351359 }
352360
353- /** Restores focus to the element that was focused before the menu was open. */
354- private _restoreFocus ( ) {
355- // We should reset focus if the user is navigating using a keyboard or
356- // if we have a top-level trigger which might cause focus to be lost
357- // when clicking on the backdrop.
358- if ( this . restoreFocus ) {
359- if ( ! this . _openedBy ) {
360- // Note that the focus style will show up both for `program` and
361- // `keyboard` so we don't have to specify which one it is.
362- this . focus ( ) ;
363- } else if ( ! this . triggersSubmenu ( ) ) {
364- this . focus ( this . _openedBy ) ;
365- }
366- }
367-
368- this . _openedBy = null ;
369- }
370-
371361 // set state rather than toggle to support triggers sharing a menu
372362 private _setIsMenuOpen ( isOpen : boolean ) : void {
373363 this . _menuOpen = isOpen ;
@@ -506,7 +496,7 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
506496 if ( ! isFakeMousedownFromScreenReader ( event ) ) {
507497 // Since right or middle button clicks won't trigger the `click` event,
508498 // we shouldn't consider the menu as opened by mouse in those cases.
509- this . _openedBy = event . button === 0 ? 'mouse' : null ;
499+ this . _openedBy = event . button === 0 ? 'mouse' : undefined ;
510500
511501 // Since clicking on the trigger won't close the menu if it opens a sub-menu,
512502 // we should prevent focus from moving onto it via click to avoid the
0 commit comments