@@ -40,7 +40,7 @@ import {
40
40
} from '@angular/core' ;
41
41
import { normalizePassiveListenerOptions } from '@angular/cdk/platform' ;
42
42
import { merge , Observable , of as observableOf , Subscription } from 'rxjs' ;
43
- import { filter , takeUntil } from 'rxjs/operators' ;
43
+ import { filter , take , takeUntil } from 'rxjs/operators' ;
44
44
import { MatMenu , MenuCloseReason } from './menu' ;
45
45
import { throwMatMenuRecursiveError } from './menu-errors' ;
46
46
import { MatMenuItem } from './menu-item' ;
@@ -115,6 +115,7 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
115
115
private _closingActionsSubscription = Subscription . EMPTY ;
116
116
private _hoverSubscription = Subscription . EMPTY ;
117
117
private _menuCloseSubscription = Subscription . EMPTY ;
118
+ private _pendingRemoval : Subscription | undefined ;
118
119
119
120
/**
120
121
* We're specifically looking for a `MatMenu` here since the generic `MatMenuPanel`
@@ -247,6 +248,7 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
247
248
passiveEventListenerOptions ,
248
249
) ;
249
250
251
+ this . _pendingRemoval ?. unsubscribe ( ) ;
250
252
this . _menuCloseSubscription . unsubscribe ( ) ;
251
253
this . _closingActionsSubscription . unsubscribe ( ) ;
252
254
this . _hoverSubscription . unsubscribe ( ) ;
@@ -285,24 +287,39 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
285
287
return ;
286
288
}
287
289
290
+ this . _pendingRemoval ?. unsubscribe ( ) ;
291
+ const previousTrigger = PANELS_TO_TRIGGERS . get ( menu ) ;
292
+ PANELS_TO_TRIGGERS . set ( menu , this ) ;
293
+
294
+ // If the same menu is currently attached to another trigger,
295
+ // we need to close it so it doesn't end up in a broken state.
296
+ if ( previousTrigger && previousTrigger !== this ) {
297
+ previousTrigger . closeMenu ( ) ;
298
+ }
299
+
288
300
const overlayRef = this . _createOverlay ( menu ) ;
289
301
const overlayConfig = overlayRef . getConfig ( ) ;
290
302
const positionStrategy = overlayConfig . positionStrategy as FlexibleConnectedPositionStrategy ;
291
303
292
304
this . _setPosition ( menu , positionStrategy ) ;
293
305
overlayConfig . hasBackdrop =
294
306
menu . hasBackdrop == null ? ! this . triggersSubmenu ( ) : menu . hasBackdrop ;
295
- overlayRef . attach ( this . _getPortal ( menu ) ) ;
296
307
297
- if ( menu . lazyContent ) {
298
- menu . lazyContent . attach ( this . menuData ) ;
308
+ // We need the `hasAttached` check for the case where the user kicked off a removal animation,
309
+ // but re-entered the menu. Re-attaching the same portal will trigger an error otherwise.
310
+ if ( ! overlayRef . hasAttached ( ) ) {
311
+ overlayRef . attach ( this . _getPortal ( menu ) ) ;
312
+ menu . lazyContent ?. attach ( this . menuData ) ;
299
313
}
300
314
301
315
this . _closingActionsSubscription = this . _menuClosingActions ( ) . subscribe ( ( ) => this . closeMenu ( ) ) ;
302
- this . _initMenu ( menu ) ;
316
+ menu . parentMenu = this . triggersSubmenu ( ) ? this . _parentMaterialMenu : undefined ;
317
+ menu . direction = this . dir ;
318
+ menu . focusFirstItem ( this . _openedBy || 'program' ) ;
319
+ this . _setIsMenuOpen ( true ) ;
303
320
304
321
if ( menu instanceof MatMenu ) {
305
- menu . _startAnimation ( ) ;
322
+ menu . _setIsOpen ( true ) ;
306
323
menu . _directDescendantItems . changes . pipe ( takeUntil ( menu . close ) ) . subscribe ( ( ) => {
307
324
// Re-adjust the position without locking when the amount of items
308
325
// changes so that the overlay is allowed to pick a new optimal position.
@@ -338,12 +355,28 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
338
355
339
356
/** Closes the menu and does the necessary cleanup. */
340
357
private _destroyMenu ( reason : MenuCloseReason ) {
341
- if ( ! this . _overlayRef || ! this . menuOpen ) {
358
+ const overlayRef = this . _overlayRef ;
359
+ const menu = this . _menu ;
360
+
361
+ if ( ! overlayRef || ! this . menuOpen ) {
342
362
return ;
343
363
}
344
364
345
365
this . _closingActionsSubscription . unsubscribe ( ) ;
346
- this . _overlayRef . detach ( ) ;
366
+ this . _pendingRemoval ?. unsubscribe ( ) ;
367
+
368
+ // Note that we don't wait for the animation to finish if another trigger took
369
+ // over the menu, because the panel will end up empty which looks glitchy.
370
+ if ( menu instanceof MatMenu && this . _ownsMenu ( menu ) ) {
371
+ this . _pendingRemoval = menu . _animationDone . pipe ( take ( 1 ) ) . subscribe ( ( ) => overlayRef . detach ( ) ) ;
372
+ menu . _setIsOpen ( false ) ;
373
+ } else {
374
+ overlayRef . detach ( ) ;
375
+ }
376
+
377
+ if ( menu && this . _ownsMenu ( menu ) ) {
378
+ PANELS_TO_TRIGGERS . delete ( menu ) ;
379
+ }
347
380
348
381
// Always restore focus if the user is navigating using the keyboard or the menu was opened
349
382
// programmatically. We don't restore for non-root triggers, because it can prevent focus
@@ -355,30 +388,6 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
355
388
356
389
this . _openedBy = undefined ;
357
390
this . _setIsMenuOpen ( false ) ;
358
-
359
- if ( this . menu && this . _ownsMenu ( this . menu ) ) {
360
- PANELS_TO_TRIGGERS . delete ( this . menu ) ;
361
- }
362
- }
363
-
364
- /**
365
- * This method sets the menu state to open and focuses the first item if
366
- * the menu was opened via the keyboard.
367
- */
368
- private _initMenu ( menu : MatMenuPanel ) : void {
369
- const previousTrigger = PANELS_TO_TRIGGERS . get ( menu ) ;
370
-
371
- // If the same menu is currently attached to another trigger,
372
- // we need to close it so it doesn't end up in a broken state.
373
- if ( previousTrigger && previousTrigger !== this ) {
374
- previousTrigger . closeMenu ( ) ;
375
- }
376
-
377
- PANELS_TO_TRIGGERS . set ( menu , this ) ;
378
- menu . parentMenu = this . triggersSubmenu ( ) ? this . _parentMaterialMenu : undefined ;
379
- menu . direction = this . dir ;
380
- menu . focusFirstItem ( this . _openedBy || 'program' ) ;
381
- this . _setIsMenuOpen ( true ) ;
382
391
}
383
392
384
393
// set state rather than toggle to support triggers sharing a menu
0 commit comments