Skip to content

Commit ee2ac70

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 ee2ac70

File tree

4 files changed

+41
-32
lines changed

4 files changed

+41
-32
lines changed

goldens/cdk/menu/index.api.md

+9-9
Original file line numberDiff line numberDiff line change
@@ -273,15 +273,6 @@ export type ContextMenuCoordinates = {
273273
y: number;
274274
};
275275

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-
285276
// @public
286277
export interface FocusableElement {
287278
_elementRef: ElementRef<HTMLElement>;
@@ -358,6 +349,15 @@ export interface MenuStackItem {
358349
menuStack?: MenuStack;
359350
}
360351

352+
// @public
353+
export class MenuTracker<T extends CdkContextMenuTrigger | CdkMenuTrigger> {
354+
update(trigger: T): void;
355+
// (undocumented)
356+
static ɵfac: i0.ɵɵFactoryDeclaration<MenuTracker<any>, never>;
357+
// (undocumented)
358+
static ɵprov: i0.ɵɵInjectableDeclaration<MenuTracker<any>>;
359+
}
360+
361361
// @public
362362
export const PARENT_OR_NEW_INLINE_MENU_STACK_PROVIDER: (orientation: "vertical" | "horizontal") => {
363363
provide: InjectionToken<MenuStack>;

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

+4-22
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

+21
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,
@@ -21,6 +22,8 @@ import {MENU_STACK, MenuStack} from './menu-stack';
2122
import {ConnectedPosition, Overlay, OverlayRef, ScrollStrategy} from '../overlay';
2223
import {TemplatePortal} from '../portal';
2324
import {merge, Subject} from 'rxjs';
25+
import {CdkContextMenuTrigger} from './context-menu-trigger';
26+
import {CdkMenuTrigger} from './menu-trigger';
2427

2528
/** Injection token used for an implementation of MenuStack. */
2629
export const MENU_TRIGGER = new InjectionToken<CdkMenuTriggerBase>('cdk-menu-trigger');
@@ -37,6 +40,24 @@ export const MENU_SCROLL_STRATEGY = new InjectionToken<() => ScrollStrategy>(
3740
},
3841
);
3942

43+
/** Tracks the last open menu trigger across the entire application. */
44+
@Injectable({providedIn: 'root'})
45+
export class MenuTracker<T extends CdkContextMenuTrigger | CdkMenuTrigger> {
46+
/** The last open menu trigger. */
47+
private static _openMenuTrigger?: CdkContextMenuTrigger | CdkMenuTrigger;
48+
49+
/**
50+
* Close the previous open menu and set the given one as being open.
51+
* @param trigger The trigger for the currently open Menu.
52+
*/
53+
update(trigger: T) {
54+
if (MenuTracker._openMenuTrigger !== trigger) {
55+
MenuTracker._openMenuTrigger?.close();
56+
MenuTracker._openMenuTrigger = trigger;
57+
}
58+
}
59+
}
60+
4061
/**
4162
* Abstract directive that implements shared logic common to all menu triggers.
4263
* This class can be extended to create custom menu trigger types.

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)