Skip to content

Commit 97aea0d

Browse files
committed
fix(cdk/menu): close sibling triggers when opening a menu
Currently, when any sibling menu is opened then it overlaps and in cases it freezes and makes the screen unresponsive. This fix will close any sibling menu if its open Fixes #30881
1 parent 357cfd3 commit 97aea0d

File tree

3 files changed

+30
-24
lines changed

3 files changed

+30
-24
lines changed

src/cdk/menu/context-menu-trigger.ts

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {_getEventTarget} from '../platform';
2626
import {merge, partition} from 'rxjs';
2727
import {skip, takeUntil, skipWhile} from 'rxjs/operators';
2828
import {MENU_STACK, MenuStack} from './menu-stack';
29-
import {CdkMenuTriggerBase, MENU_TRIGGER} from './menu-trigger-base';
29+
import {CdkMenuTriggerBase, MENU_TRIGGER, MenuTracker} from './menu-trigger-base';
3030

3131
/** The preferred menu positions for the context menu. */
3232
const CONTEXT_MENU_POSITIONS = STANDARD_DROPDOWN_BELOW_POSITIONS.map(position => {
@@ -37,24 +37,6 @@ const CONTEXT_MENU_POSITIONS = STANDARD_DROPDOWN_BELOW_POSITIONS.map(position =>
3737
return {...position, offsetX, offsetY};
3838
});
3939

40-
/** Tracks the last open context menu trigger across the entire application. */
41-
@Injectable({providedIn: 'root'})
42-
export class ContextMenuTracker {
43-
/** The last open context menu trigger. */
44-
private static _openContextMenuTrigger?: CdkContextMenuTrigger;
45-
46-
/**
47-
* Close the previous open context menu and set the given one as being open.
48-
* @param trigger The trigger for the currently open Context Menu.
49-
*/
50-
update(trigger: CdkContextMenuTrigger) {
51-
if (ContextMenuTracker._openContextMenuTrigger !== trigger) {
52-
ContextMenuTracker._openContextMenuTrigger?.close();
53-
ContextMenuTracker._openContextMenuTrigger = trigger;
54-
}
55-
}
56-
}
57-
5840
/** The coordinates where the context menu should open. */
5941
export type ContextMenuCoordinates = {x: number; y: number};
6042

@@ -87,8 +69,8 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
8769
/** The directionality of the page. */
8870
private readonly _directionality = inject(Directionality, {optional: true});
8971

90-
/** The app's context menu tracking registry */
91-
private readonly _contextMenuTracker = inject(ContextMenuTracker);
72+
/** The app's menu tracking registry */
73+
private readonly _menuTracker = inject(MenuTracker);
9274

9375
private readonly _changeDetectorRef = inject(ChangeDetectorRef);
9476

@@ -128,7 +110,7 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
128110
// resulting in multiple stacked context menus being displayed.
129111
event.stopPropagation();
130112

131-
this._contextMenuTracker.update(this);
113+
this._menuTracker.update(this);
132114
this._open(event, {x: event.clientX, y: event.clientY});
133115

134116
// A context menu can be triggered via a mouse right click or a keyboard shortcut.

src/cdk/menu/menu-trigger-base.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
Directive,
1111
EventEmitter,
1212
inject,
13+
Injectable,
1314
InjectionToken,
1415
Injector,
1516
OnDestroy,
@@ -37,6 +38,24 @@ export const MENU_SCROLL_STRATEGY = new InjectionToken<() => ScrollStrategy>(
3738
},
3839
);
3940

41+
/** Tracks the last open menu trigger across the entire application. */
42+
@Injectable({providedIn: 'root'})
43+
export class MenuTracker {
44+
/** The last open menu trigger. */
45+
private static _openMenuTrigger?: CdkMenuTriggerBase;
46+
47+
/**
48+
* Close the previous open menu and set the given one as being open.
49+
* @param trigger The trigger for the currently open Menu.
50+
*/
51+
update(trigger: CdkMenuTriggerBase) {
52+
if (MenuTracker._openMenuTrigger !== trigger) {
53+
MenuTracker._openMenuTrigger?.close();
54+
MenuTracker._openMenuTrigger = trigger;
55+
}
56+
}
57+
}
58+
4059
/**
4160
* Abstract directive that implements shared logic common to all menu triggers.
4261
* This class can be extended to create custom menu trigger types.
@@ -60,6 +79,8 @@ export abstract class CdkMenuTriggerBase implements OnDestroy {
6079
/** Function used to configure the scroll strategy for the menu. */
6180
protected readonly menuScrollStrategy = inject(MENU_SCROLL_STRATEGY);
6281

82+
abstract close(): void;
83+
6384
/**
6485
* A list of preferred menu positions to be used when constructing the
6586
* `FlexibleConnectedPositionStrategy` for this trigger's menu.

src/cdk/menu/menu-trigger.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import {takeUntil} from 'rxjs/operators';
4141
import {CDK_MENU, Menu} from './menu-interface';
4242
import {PARENT_OR_NEW_MENU_STACK_PROVIDER} from './menu-stack';
4343
import {MENU_AIM} from './menu-aim';
44-
import {CdkMenuTriggerBase, MENU_TRIGGER} from './menu-trigger-base';
44+
import {CdkMenuTriggerBase, MENU_TRIGGER, MenuTracker} from './menu-trigger-base';
4545
import {eventDispatchesNativeClick} from './event-detection';
4646

4747
/**
@@ -90,6 +90,9 @@ export class CdkMenuTrigger extends CdkMenuTriggerBase implements OnChanges, OnD
9090
/** The menu aim service used by this menu. */
9191
private readonly _menuAim = inject(MENU_AIM, {optional: true});
9292

93+
/** The app's menu tracking registry */
94+
private readonly _menuTracker = inject(MenuTracker);
95+
9396
constructor() {
9497
super();
9598
this._setRole();
@@ -109,7 +112,7 @@ export class CdkMenuTrigger extends CdkMenuTriggerBase implements OnChanges, OnD
109112
open() {
110113
if (!this.isOpen() && this.menuTemplateRef != null) {
111114
this.opened.next();
112-
115+
this._menuTracker.update(this);
113116
this.overlayRef = this.overlayRef || this._overlay.create(this._getOverlayConfig());
114117
this.overlayRef.attach(this.getMenuContentPortal());
115118
this._changeDetectorRef.markForCheck();

0 commit comments

Comments
 (0)