Skip to content

Commit 0a9c5a9

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 freezes and makes the screen unresponsive. This fix will close any sibling menu if its open Fixes #30881
1 parent 357cfd3 commit 0a9c5a9

File tree

4 files changed

+41
-34
lines changed

4 files changed

+41
-34
lines changed

goldens/cdk/menu/index.api.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,8 @@ export class CdkMenuTrigger extends CdkMenuTriggerBase implements OnChanges, OnD
229229
// @public
230230
export abstract class CdkMenuTriggerBase implements OnDestroy {
231231
protected childMenu?: Menu;
232+
// (undocumented)
233+
abstract close(): void;
232234
readonly closed: EventEmitter<void>;
233235
protected readonly destroyed: Subject<void>;
234236
protected getMenuContentPortal(): TemplatePortal<any>;
@@ -273,15 +275,6 @@ export type ContextMenuCoordinates = {
273275
y: number;
274276
};
275277

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-
285278
// @public
286279
export interface FocusableElement {
287280
_elementRef: ElementRef<HTMLElement>;
@@ -358,6 +351,15 @@ export interface MenuStackItem {
358351
menuStack?: MenuStack;
359352
}
360353

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

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

Lines changed: 4 additions & 23 deletions
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

Lines changed: 21 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,
@@ -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.
@@ -60,6 +79,8 @@ export abstract class CdkMenuTriggerBase implements OnDestroy {
6079
/** Function used to configure the scroll strategy for the menu. */
6180
protected readonly menuScrollStrategy = inject(MENU_SCROLL_STRATEGY);
6281

82+
abstract close(): void;
83+
6384
/**
6485
* A list of preferred menu positions to be used when constructing the
6586
* `FlexibleConnectedPositionStrategy` for this trigger's menu.

src/cdk/menu/menu-trigger.ts

Lines changed: 5 additions & 2 deletions
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
/**
@@ -90,6 +90,9 @@ export class CdkMenuTrigger extends CdkMenuTriggerBase implements OnChanges, OnD
9090
/** The menu aim service used by this menu. */
9191
private readonly _menuAim = inject(MENU_AIM, {optional: true});
9292

93+
/** The app's menu tracking registry */
94+
private readonly _menuTracker = inject(MenuTracker);
95+
9396
constructor() {
9497
super();
9598
this._setRole();
@@ -109,7 +112,7 @@ export class CdkMenuTrigger extends CdkMenuTriggerBase implements OnChanges, OnD
109112
open() {
110113
if (!this.isOpen() && this.menuTemplateRef != null) {
111114
this.opened.next();
112-
115+
this._menuTracker.update(this);
113116
this.overlayRef = this.overlayRef || this._overlay.create(this._getOverlayConfig());
114117
this.overlayRef.attach(this.getMenuContentPortal());
115118
this._changeDetectorRef.markForCheck();

0 commit comments

Comments
 (0)