Skip to content

Commit 269e23c

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 angular#30881
1 parent 357cfd3 commit 269e23c

File tree

4 files changed

+43
-33
lines changed

4 files changed

+43
-33
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

+4-23
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
ChangeDetectorRef,
1212
Directive,
1313
inject,
14-
Injectable,
1514
Input,
1615
OnDestroy,
1716
} from '@angular/core';
@@ -26,7 +25,7 @@ import {_getEventTarget} from '../platform';
2625
import {merge, partition} from 'rxjs';
2726
import {skip, takeUntil, skipWhile} from 'rxjs/operators';
2827
import {MENU_STACK, MenuStack} from './menu-stack';
29-
import {CdkMenuTriggerBase, MENU_TRIGGER} from './menu-trigger-base';
28+
import {CdkMenuTriggerBase, MENU_TRIGGER, MenuTracker} from './menu-trigger-base';
3029

3130
/** The preferred menu positions for the context menu. */
3231
const CONTEXT_MENU_POSITIONS = STANDARD_DROPDOWN_BELOW_POSITIONS.map(position => {
@@ -37,24 +36,6 @@ const CONTEXT_MENU_POSITIONS = STANDARD_DROPDOWN_BELOW_POSITIONS.map(position =>
3736
return {...position, offsetX, offsetY};
3837
});
3938

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-
5839
/** The coordinates where the context menu should open. */
5940
export type ContextMenuCoordinates = {x: number; y: number};
6041

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

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

9374
private readonly _changeDetectorRef = inject(ChangeDetectorRef);
9475

@@ -128,7 +109,7 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
128109
// resulting in multiple stacked context menus being displayed.
129110
event.stopPropagation();
130111

131-
this._contextMenuTracker.update(this);
112+
this._menuTracker.update(this);
132113
this._open(event, {x: event.clientX, y: event.clientY});
133114

134115
// 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,
@@ -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.
@@ -78,6 +97,9 @@ export abstract class CdkMenuTriggerBase implements OnDestroy {
7897
/** Context data to be passed along to the menu template */
7998
menuData: unknown;
8099

100+
/** Close the opened menu. */
101+
abstract close(): void;
102+
81103
/** A reference to the overlay which manages the triggered menu */
82104
protected overlayRef: OverlayRef | null = null;
83105

src/cdk/menu/menu-trigger.ts

+7-1
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
/**
@@ -84,6 +84,9 @@ export class CdkMenuTrigger extends CdkMenuTriggerBase implements OnChanges, OnD
8484
private readonly _renderer = inject(Renderer2);
8585
private _cleanupMouseenter: () => void;
8686

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

@@ -107,6 +110,9 @@ export class CdkMenuTrigger extends CdkMenuTriggerBase implements OnChanges, OnD
107110

108111
/** Open the attached menu. */
109112
open() {
113+
if (!this._parentMenu) {
114+
this._menuTracker.update(this);
115+
}
110116
if (!this.isOpen() && this.menuTemplateRef != null) {
111117
this.opened.next();
112118

0 commit comments

Comments
 (0)