Skip to content

Commit 5a9dafd

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 357cfd3 commit 5a9dafd

File tree

4 files changed

+41
-33
lines changed

4 files changed

+41
-33
lines changed

Diff for: 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>;

Diff for: 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.

Diff for: 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.

Diff for: 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)