Skip to content

Commit 59be546

Browse files
mistrykaran91crisbeto
authored andcommitted
fix(cdk/menu): close sibling triggers when opening a menu (#30894)
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 (cherry picked from commit 4230292)
1 parent 6f445d1 commit 59be546

File tree

4 files changed

+52
-31
lines changed

4 files changed

+52
-31
lines changed

goldens/cdk/menu/index.api.md

Lines changed: 12 additions & 9 deletions
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,17 @@ export interface MenuStackItem {
358350
menuStack?: MenuStack;
359351
}
360352

353+
// @public
354+
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+
export { MenuTracker as ContextMenuTracker }
362+
export { MenuTracker }
363+
361364
// @public
362365
export const PARENT_OR_NEW_INLINE_MENU_STACK_PROVIDER: (orientation: "vertical" | "horizontal") => {
363366
provide: InjectionToken<MenuStack>;

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

Lines changed: 11 additions & 21 deletions
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,23 +38,11 @@ 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-
}
41+
/**
42+
* @deprecated Will be removed. Use `MenuTracker` instead.
43+
* @breaking-change 22.0.0
44+
*/
45+
export {MenuTracker as ContextMenuTracker};
5946

6047
/** The coordinates where the context menu should open. */
6148
export type ContextMenuCoordinates = {x: number; y: number};
@@ -85,7 +72,10 @@ export type ContextMenuCoordinates = {x: number; y: number};
8572
export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestroy {
8673
private readonly _injector = inject(Injector);
8774
private readonly _directionality = inject(Directionality, {optional: true});
88-
private readonly _contextMenuTracker = inject(ContextMenuTracker);
75+
76+
/** The app's menu tracking registry */
77+
private readonly _menuTracker = inject(MenuTracker);
78+
8979
private readonly _changeDetectorRef = inject(ChangeDetectorRef);
9080

9181
/** Whether the context menu is disabled. */
@@ -124,7 +114,7 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
124114
// resulting in multiple stacked context menus being displayed.
125115
event.stopPropagation();
126116

127-
this._contextMenuTracker.update(this);
117+
this._menuTracker.update(this);
128118
this._open(event, {x: event.clientX, y: event.clientY});
129119

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

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

Lines changed: 22 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,
@@ -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

Lines changed: 7 additions & 1 deletion
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)