Skip to content

Commit f27f31b

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 makes the screen unresponsive. This fix will close any sibling menu if its open Fixes #30881
1 parent 7791972 commit f27f31b

File tree

4 files changed

+45
-32
lines changed

4 files changed

+45
-32
lines changed

goldens/cdk/menu/index.api.md

+10-9
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ export class CdkMenuTrigger extends CdkMenuTriggerBase implements OnChanges, OnD
229229
// @public
230230
export abstract class CdkMenuTriggerBase implements OnDestroy {
231231
protected childMenu?: Menu;
232+
abstract close(): void;
232233
readonly closed: EventEmitter<void>;
233234
protected readonly destroyed: Subject<void>;
234235
protected getMenuContentPortal(): TemplatePortal<any>;
@@ -273,15 +274,6 @@ export type ContextMenuCoordinates = {
273274
y: number;
274275
};
275276

276-
// @public
277-
export class ContextMenuTracker {
278-
update(trigger: CdkContextMenuTrigger): void;
279-
// (undocumented)
280-
static ɵfac: i0.ɵɵFactoryDeclaration<ContextMenuTracker, never>;
281-
// (undocumented)
282-
static ɵprov: i0.ɵɵInjectableDeclaration<ContextMenuTracker>;
283-
}
284-
285277
// @public
286278
export interface FocusableElement {
287279
_elementRef: ElementRef<HTMLElement>;
@@ -358,6 +350,15 @@ export interface MenuStackItem {
358350
menuStack?: MenuStack;
359351
}
360352

353+
// @public
354+
export class MenuTracker {
355+
update(trigger: CdkMenuTriggerBase): void;
356+
// (undocumented)
357+
static ɵfac: i0.ɵɵFactoryDeclaration<MenuTracker, never>;
358+
// (undocumented)
359+
static ɵprov: i0.ɵɵInjectableDeclaration<MenuTracker>;
360+
}
361+
361362
// @public
362363
export const PARENT_OR_NEW_INLINE_MENU_STACK_PROVIDER: (orientation: "vertical" | "horizontal") => {
363364
provide: InjectionToken<MenuStack>;

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

+6-22
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
ChangeDetectorRef,
1212
Directive,
1313
inject,
14-
Injectable,
1514
Injector,
1615
Input,
1716
OnDestroy,
@@ -28,7 +27,7 @@ import {_getEventTarget} from '../platform';
2827
import {merge, partition} from 'rxjs';
2928
import {skip, takeUntil, skipWhile} from 'rxjs/operators';
3029
import {MENU_STACK, MenuStack} from './menu-stack';
31-
import {CdkMenuTriggerBase, MENU_TRIGGER} from './menu-trigger-base';
30+
import {CdkMenuTriggerBase, MENU_TRIGGER, MenuTracker} from './menu-trigger-base';
3231

3332
/** The preferred menu positions for the context menu. */
3433
const CONTEXT_MENU_POSITIONS = STANDARD_DROPDOWN_BELOW_POSITIONS.map(position => {
@@ -39,24 +38,6 @@ const CONTEXT_MENU_POSITIONS = STANDARD_DROPDOWN_BELOW_POSITIONS.map(position =>
3938
return {...position, offsetX, offsetY};
4039
});
4140

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

@@ -85,7 +66,10 @@ export type ContextMenuCoordinates = {x: number; y: number};
8566
export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestroy {
8667
private readonly _injector = inject(Injector);
8768
private readonly _directionality = inject(Directionality, {optional: true});
88-
private readonly _contextMenuTracker = inject(ContextMenuTracker);
69+
70+
/** The app's menu tracking registry */
71+
private readonly _menuTracker = inject(MenuTracker);
72+
8973
private readonly _changeDetectorRef = inject(ChangeDetectorRef);
9074

9175
/** Whether the context menu is disabled. */
@@ -124,7 +108,7 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
124108
// resulting in multiple stacked context menus being displayed.
125109
event.stopPropagation();
126110

127-
this._contextMenuTracker.update(this);
111+
this._menuTracker.update(this);
128112
this._open(event, {x: event.clientX, y: event.clientY});
129113

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

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

+22
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,
@@ -42,6 +43,24 @@ export const MENU_SCROLL_STRATEGY = new InjectionToken<() => ScrollStrategy>(
4243
},
4344
);
4445

46+
/** Tracks the last open menu trigger across the entire application. */
47+
@Injectable({providedIn: 'root'})
48+
export class MenuTracker {
49+
/** The last open menu trigger. */
50+
private static _openMenuTrigger?: CdkMenuTriggerBase;
51+
52+
/**
53+
* Close the previous open menu and set the given one as being open.
54+
* @param trigger The trigger for the currently open Menu.
55+
*/
56+
update(trigger: CdkMenuTriggerBase) {
57+
if (MenuTracker._openMenuTrigger !== trigger) {
58+
MenuTracker._openMenuTrigger?.close();
59+
MenuTracker._openMenuTrigger = trigger;
60+
}
61+
}
62+
}
63+
4564
/**
4665
* Abstract directive that implements shared logic common to all menu triggers.
4766
* This class can be extended to create custom menu trigger types.
@@ -83,6 +102,9 @@ export abstract class CdkMenuTriggerBase implements OnDestroy {
83102
/** Context data to be passed along to the menu template */
84103
menuData: unknown;
85104

105+
/** Close the opened menu. */
106+
abstract close(): void;
107+
86108
/** A reference to the overlay which manages the triggered menu */
87109
protected overlayRef: OverlayRef | null = null;
88110

src/cdk/menu/menu-trigger.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import {takeUntil} from 'rxjs/operators';
4343
import {CDK_MENU, Menu} from './menu-interface';
4444
import {PARENT_OR_NEW_MENU_STACK_PROVIDER} from './menu-stack';
4545
import {MENU_AIM} from './menu-aim';
46-
import {CdkMenuTriggerBase, MENU_TRIGGER} from './menu-trigger-base';
46+
import {CdkMenuTriggerBase, MENU_TRIGGER, MenuTracker} from './menu-trigger-base';
4747
import {eventDispatchesNativeClick} from './event-detection';
4848

4949
/**
@@ -86,6 +86,9 @@ export class CdkMenuTrigger extends CdkMenuTriggerBase implements OnChanges, OnD
8686
private readonly _injector = inject(Injector);
8787
private _cleanupMouseenter: () => void;
8888

89+
/** The app's menu tracking registry */
90+
private readonly _menuTracker = inject(MenuTracker);
91+
8992
/** The parent menu this trigger belongs to. */
9093
private readonly _parentMenu = inject(CDK_MENU, {optional: true});
9194

@@ -109,6 +112,9 @@ export class CdkMenuTrigger extends CdkMenuTriggerBase implements OnChanges, OnD
109112

110113
/** Open the attached menu. */
111114
open() {
115+
if (!this._parentMenu) {
116+
this._menuTracker.update(this);
117+
}
112118
if (!this.isOpen() && this.menuTemplateRef != null) {
113119
this.opened.next();
114120

0 commit comments

Comments
 (0)