From 269e23cd9ba0838811dc69ef81464aa0804bd80f Mon Sep 17 00:00:00 2001 From: Karan Mistry Date: Fri, 18 Apr 2025 20:01:03 +0530 Subject: [PATCH] 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 --- goldens/cdk/menu/index.api.md | 19 ++++++++++--------- src/cdk/menu/context-menu-trigger.ts | 27 ++++----------------------- src/cdk/menu/menu-trigger-base.ts | 22 ++++++++++++++++++++++ src/cdk/menu/menu-trigger.ts | 8 +++++++- 4 files changed, 43 insertions(+), 33 deletions(-) diff --git a/goldens/cdk/menu/index.api.md b/goldens/cdk/menu/index.api.md index 1f48227d1088..1b21e9148e8d 100644 --- a/goldens/cdk/menu/index.api.md +++ b/goldens/cdk/menu/index.api.md @@ -229,6 +229,7 @@ export class CdkMenuTrigger extends CdkMenuTriggerBase implements OnChanges, OnD // @public export abstract class CdkMenuTriggerBase implements OnDestroy { protected childMenu?: Menu; + abstract close(): void; readonly closed: EventEmitter; protected readonly destroyed: Subject; protected getMenuContentPortal(): TemplatePortal; @@ -273,15 +274,6 @@ export type ContextMenuCoordinates = { y: number; }; -// @public -export class ContextMenuTracker { - update(trigger: CdkContextMenuTrigger): void; - // (undocumented) - static ɵfac: i0.ɵɵFactoryDeclaration; - // (undocumented) - static ɵprov: i0.ɵɵInjectableDeclaration; -} - // @public export interface FocusableElement { _elementRef: ElementRef; @@ -358,6 +350,15 @@ export interface MenuStackItem { menuStack?: MenuStack; } +// @public +export class MenuTracker { + update(trigger: CdkMenuTriggerBase): void; + // (undocumented) + static ɵfac: i0.ɵɵFactoryDeclaration; + // (undocumented) + static ɵprov: i0.ɵɵInjectableDeclaration; +} + // @public export const PARENT_OR_NEW_INLINE_MENU_STACK_PROVIDER: (orientation: "vertical" | "horizontal") => { provide: InjectionToken; diff --git a/src/cdk/menu/context-menu-trigger.ts b/src/cdk/menu/context-menu-trigger.ts index 0554fffb9cd7..f15398b72e62 100644 --- a/src/cdk/menu/context-menu-trigger.ts +++ b/src/cdk/menu/context-menu-trigger.ts @@ -11,7 +11,6 @@ import { ChangeDetectorRef, Directive, inject, - Injectable, Input, OnDestroy, } from '@angular/core'; @@ -26,7 +25,7 @@ import {_getEventTarget} from '../platform'; import {merge, partition} from 'rxjs'; import {skip, takeUntil, skipWhile} from 'rxjs/operators'; import {MENU_STACK, MenuStack} from './menu-stack'; -import {CdkMenuTriggerBase, MENU_TRIGGER} from './menu-trigger-base'; +import {CdkMenuTriggerBase, MENU_TRIGGER, MenuTracker} from './menu-trigger-base'; /** The preferred menu positions for the context menu. */ const CONTEXT_MENU_POSITIONS = STANDARD_DROPDOWN_BELOW_POSITIONS.map(position => { @@ -37,24 +36,6 @@ const CONTEXT_MENU_POSITIONS = STANDARD_DROPDOWN_BELOW_POSITIONS.map(position => return {...position, offsetX, offsetY}; }); -/** Tracks the last open context menu trigger across the entire application. */ -@Injectable({providedIn: 'root'}) -export class ContextMenuTracker { - /** The last open context menu trigger. */ - private static _openContextMenuTrigger?: CdkContextMenuTrigger; - - /** - * Close the previous open context menu and set the given one as being open. - * @param trigger The trigger for the currently open Context Menu. - */ - update(trigger: CdkContextMenuTrigger) { - if (ContextMenuTracker._openContextMenuTrigger !== trigger) { - ContextMenuTracker._openContextMenuTrigger?.close(); - ContextMenuTracker._openContextMenuTrigger = trigger; - } - } -} - /** The coordinates where the context menu should open. */ export type ContextMenuCoordinates = {x: number; y: number}; @@ -87,8 +68,8 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr /** The directionality of the page. */ private readonly _directionality = inject(Directionality, {optional: true}); - /** The app's context menu tracking registry */ - private readonly _contextMenuTracker = inject(ContextMenuTracker); + /** The app's menu tracking registry */ + private readonly _menuTracker = inject(MenuTracker); private readonly _changeDetectorRef = inject(ChangeDetectorRef); @@ -128,7 +109,7 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr // resulting in multiple stacked context menus being displayed. event.stopPropagation(); - this._contextMenuTracker.update(this); + this._menuTracker.update(this); this._open(event, {x: event.clientX, y: event.clientY}); // A context menu can be triggered via a mouse right click or a keyboard shortcut. diff --git a/src/cdk/menu/menu-trigger-base.ts b/src/cdk/menu/menu-trigger-base.ts index c800e0c8bb52..71759942e592 100644 --- a/src/cdk/menu/menu-trigger-base.ts +++ b/src/cdk/menu/menu-trigger-base.ts @@ -10,6 +10,7 @@ import { Directive, EventEmitter, inject, + Injectable, InjectionToken, Injector, OnDestroy, @@ -37,6 +38,24 @@ export const MENU_SCROLL_STRATEGY = new InjectionToken<() => ScrollStrategy>( }, ); +/** Tracks the last open menu trigger across the entire application. */ +@Injectable({providedIn: 'root'}) +export class MenuTracker { + /** The last open menu trigger. */ + private static _openMenuTrigger?: CdkMenuTriggerBase; + + /** + * Close the previous open menu and set the given one as being open. + * @param trigger The trigger for the currently open Menu. + */ + update(trigger: CdkMenuTriggerBase) { + if (MenuTracker._openMenuTrigger !== trigger) { + MenuTracker._openMenuTrigger?.close(); + MenuTracker._openMenuTrigger = trigger; + } + } +} + /** * Abstract directive that implements shared logic common to all menu triggers. * This class can be extended to create custom menu trigger types. @@ -78,6 +97,9 @@ export abstract class CdkMenuTriggerBase implements OnDestroy { /** Context data to be passed along to the menu template */ menuData: unknown; + /** Close the opened menu. */ + abstract close(): void; + /** A reference to the overlay which manages the triggered menu */ protected overlayRef: OverlayRef | null = null; diff --git a/src/cdk/menu/menu-trigger.ts b/src/cdk/menu/menu-trigger.ts index b6438b58188c..669406c190ff 100644 --- a/src/cdk/menu/menu-trigger.ts +++ b/src/cdk/menu/menu-trigger.ts @@ -41,7 +41,7 @@ import {takeUntil} from 'rxjs/operators'; import {CDK_MENU, Menu} from './menu-interface'; import {PARENT_OR_NEW_MENU_STACK_PROVIDER} from './menu-stack'; import {MENU_AIM} from './menu-aim'; -import {CdkMenuTriggerBase, MENU_TRIGGER} from './menu-trigger-base'; +import {CdkMenuTriggerBase, MENU_TRIGGER, MenuTracker} from './menu-trigger-base'; import {eventDispatchesNativeClick} from './event-detection'; /** @@ -84,6 +84,9 @@ export class CdkMenuTrigger extends CdkMenuTriggerBase implements OnChanges, OnD private readonly _renderer = inject(Renderer2); private _cleanupMouseenter: () => void; + /** The app's menu tracking registry */ + private readonly _menuTracker = inject(MenuTracker); + /** The parent menu this trigger belongs to. */ private readonly _parentMenu = inject(CDK_MENU, {optional: true}); @@ -107,6 +110,9 @@ export class CdkMenuTrigger extends CdkMenuTriggerBase implements OnChanges, OnD /** Open the attached menu. */ open() { + if (!this._parentMenu) { + this._menuTracker.update(this); + } if (!this.isOpen() && this.menuTemplateRef != null) { this.opened.next();