From ec550fd14fd0fa3f309bf4fb7a76562646a2f5e8 Mon Sep 17 00:00:00 2001 From: naaajii Date: Sun, 13 Oct 2024 20:58:35 +0500 Subject: [PATCH 1/3] feat(cdk/menu): not to close on outside click this commit adds new input in menu trigger which allows users to disable closing menu on outside clicks fixes #29072 --- src/cdk/menu/context-menu-trigger.spec.ts | 15 ++++++++++++++- src/cdk/menu/context-menu-trigger.ts | 9 ++++++++- tools/public_api_guard/cdk/menu.md | 5 ++++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/cdk/menu/context-menu-trigger.spec.ts b/src/cdk/menu/context-menu-trigger.spec.ts index 99757aaa2737..266c82edec7d 100644 --- a/src/cdk/menu/context-menu-trigger.spec.ts +++ b/src/cdk/menu/context-menu-trigger.spec.ts @@ -143,6 +143,16 @@ describe('CdkContextMenuTrigger', () => { fixture.detectChanges(); expect(getContextMenu()).toBeDefined(); }); + + it('should stay open with disable close on outside click', () => { + fixture.componentInstance.shoudCloseOnOutsideClicks = true; + openContextMenu(); + expect(getContextMenu()).toBeDefined(); + + fixture.nativeElement.querySelector('#other').click(); + fixture.detectChanges(); + expect(getContextMenu()).toBeDefined(); + }); }); describe('nested context menu triggers', () => { @@ -442,7 +452,8 @@ describe('CdkContextMenuTrigger', () => { @Component({ template: ` -
+
@@ -459,6 +470,8 @@ class SimpleContextMenu { @ViewChild(CdkMenu, {read: ElementRef}) nativeMenu?: ElementRef; @ViewChildren(CdkMenu) menus: QueryList; + + shoudCloseOnOutsideClicks = false; } @Component({ diff --git a/src/cdk/menu/context-menu-trigger.ts b/src/cdk/menu/context-menu-trigger.ts index 4a86b9c02aec..58be18bff098 100644 --- a/src/cdk/menu/context-menu-trigger.ts +++ b/src/cdk/menu/context-menu-trigger.ts @@ -96,6 +96,10 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr /** Whether the context menu is disabled. */ @Input({alias: 'cdkContextMenuDisabled', transform: booleanAttribute}) disabled: boolean = false; + /** Whether on clicking outside of menu should close it */ + @Input({alias: 'cdkContextMenuDisableCloseOnOutsideClick', transform: booleanAttribute}) + disableCloseOnOutsideClick: boolean = false; + constructor() { super(); this._setMenuStackCloseListener(); @@ -210,7 +214,10 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr outsideClicks.pipe(takeUntil(this.stopOutsideClicksListener)).subscribe(event => { if (!this.isElementInsideMenuStack(_getEventTarget(event)!)) { - this.menuStack.closeAll(); + // We do not want to close menu if user does not want to on outside clicks. + if (!this.disableCloseOnOutsideClick) { + this.menuStack.closeAll(); + } } }); } diff --git a/tools/public_api_guard/cdk/menu.md b/tools/public_api_guard/cdk/menu.md index ffab9c7ed73d..9cf3323e0f9b 100644 --- a/tools/public_api_guard/cdk/menu.md +++ b/tools/public_api_guard/cdk/menu.md @@ -35,13 +35,16 @@ export const CDK_MENU: InjectionToken; export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestroy { constructor(); close(): void; + disableCloseOnOutsideClick: boolean; disabled: boolean; // (undocumented) + static ngAcceptInputType_disableCloseOnOutsideClick: unknown; + // (undocumented) static ngAcceptInputType_disabled: unknown; open(coordinates: ContextMenuCoordinates): void; _openOnContextMenu(event: MouseEvent): void; // (undocumented) - static ɵdir: i0.ɵɵDirectiveDeclaration; + static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } From 0b94aee4a291fc263c16ed74757509d3814da534 Mon Sep 17 00:00:00 2001 From: naaajii Date: Sun, 13 Oct 2024 21:05:07 +0500 Subject: [PATCH 2/3] feat(cdk/menu): add output event for outside click of menu this commit exposes an output which emits whenever the user clicks outside open menu fixes #29072 --- src/cdk/menu/context-menu-trigger.spec.ts | 12 ++++++++++++ src/cdk/menu/context-menu-trigger.ts | 9 ++++++++- src/cdk/menu/menu-trigger-base.ts | 3 +++ tools/public_api_guard/cdk/menu.md | 1 + 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/cdk/menu/context-menu-trigger.spec.ts b/src/cdk/menu/context-menu-trigger.spec.ts index 266c82edec7d..6c30233b43c4 100644 --- a/src/cdk/menu/context-menu-trigger.spec.ts +++ b/src/cdk/menu/context-menu-trigger.spec.ts @@ -153,6 +153,18 @@ describe('CdkContextMenuTrigger', () => { fixture.detectChanges(); expect(getContextMenu()).toBeDefined(); }); + + it('should emit that menu had click outside of it', () => { + openContextMenu(); + expect(getContextMenu()).toBeDefined(); + spyOn(fixture.componentInstance.trigger.outsideClicked, 'emit'); + + fixture.nativeElement.querySelector('#other').click(); + fixture.detectChanges(); + + expect(fixture.componentInstance.trigger.outsideClicked.emit).toHaveBeenCalled(); + expect(getContextMenu()).not.toBeDefined(); + }); }); describe('nested context menu triggers', () => { diff --git a/src/cdk/menu/context-menu-trigger.ts b/src/cdk/menu/context-menu-trigger.ts index 58be18bff098..c4602a8c9c26 100644 --- a/src/cdk/menu/context-menu-trigger.ts +++ b/src/cdk/menu/context-menu-trigger.ts @@ -75,7 +75,11 @@ export type ContextMenuCoordinates = {x: number; y: number}; {name: 'menuPosition', alias: 'cdkContextMenuPosition'}, {name: 'menuData', alias: 'cdkContextMenuTriggerData'}, ], - outputs: ['opened: cdkContextMenuOpened', 'closed: cdkContextMenuClosed'], + outputs: [ + 'opened: cdkContextMenuOpened', + 'closed: cdkContextMenuClosed', + 'outsideClicked: cdkContextMenuOutsideClicked', + ], providers: [ {provide: MENU_TRIGGER, useExisting: CdkContextMenuTrigger}, {provide: MENU_STACK, useClass: MenuStack}, @@ -218,6 +222,9 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr if (!this.disableCloseOnOutsideClick) { this.menuStack.closeAll(); } + + // Emit that we had a click outside the menu. + this.outsideClicked.emit(event); } }); } diff --git a/src/cdk/menu/menu-trigger-base.ts b/src/cdk/menu/menu-trigger-base.ts index 840ca2f65a1d..af7a205f750b 100644 --- a/src/cdk/menu/menu-trigger-base.ts +++ b/src/cdk/menu/menu-trigger-base.ts @@ -73,6 +73,9 @@ export abstract class CdkMenuTriggerBase implements OnDestroy { /** Emits when the attached menu is requested to close */ readonly closed: EventEmitter = new EventEmitter(); + /** Emits when the attached menu has click outside of it in open state */ + readonly outsideClicked: EventEmitter = new EventEmitter(); + /** Template reference variable to the menu this trigger opens */ menuTemplateRef: TemplateRef | null; diff --git a/tools/public_api_guard/cdk/menu.md b/tools/public_api_guard/cdk/menu.md index 9cf3323e0f9b..4d601ccb1583 100644 --- a/tools/public_api_guard/cdk/menu.md +++ b/tools/public_api_guard/cdk/menu.md @@ -235,6 +235,7 @@ export abstract class CdkMenuTriggerBase implements OnDestroy { // (undocumented) ngOnDestroy(): void; readonly opened: EventEmitter; + readonly outsideClicked: EventEmitter; protected overlayRef: OverlayRef | null; registerChildMenu(child: Menu): void; protected readonly stopOutsideClicksListener: Observable; From dfb194cdde944983dc18ddf10322f6cd9857d387 Mon Sep 17 00:00:00 2001 From: naaajii Date: Sun, 13 Oct 2024 21:27:25 +0500 Subject: [PATCH 3/3] feat(cdk/menu): add output event for `contextmenu` this commit exposes an output that emits whenever user triggers `contextmenu` event which contains `clientX` and `clientY` position respectively fixes #29072 --- src/cdk/menu/context-menu-trigger.spec.ts | 9 +++++++++ src/cdk/menu/context-menu-trigger.ts | 4 ++++ src/cdk/menu/menu-trigger-base.ts | 3 +++ tools/public_api_guard/cdk/menu.md | 6 +++++- 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/cdk/menu/context-menu-trigger.spec.ts b/src/cdk/menu/context-menu-trigger.spec.ts index 6c30233b43c4..79f2ec79d5a8 100644 --- a/src/cdk/menu/context-menu-trigger.spec.ts +++ b/src/cdk/menu/context-menu-trigger.spec.ts @@ -165,6 +165,15 @@ describe('CdkContextMenuTrigger', () => { expect(fixture.componentInstance.trigger.outsideClicked.emit).toHaveBeenCalled(); expect(getContextMenu()).not.toBeDefined(); }); + + it('should emit that menu was triggered', () => { + fixture.detectChanges(); + spyOn(fixture.componentInstance.trigger.triggered, 'emit'); + + openContextMenu(); + + expect(fixture.componentInstance.trigger.triggered.emit).toHaveBeenCalled(); + }); }); describe('nested context menu triggers', () => { diff --git a/src/cdk/menu/context-menu-trigger.ts b/src/cdk/menu/context-menu-trigger.ts index c4602a8c9c26..9d56d1c877d9 100644 --- a/src/cdk/menu/context-menu-trigger.ts +++ b/src/cdk/menu/context-menu-trigger.ts @@ -79,6 +79,7 @@ export type ContextMenuCoordinates = {x: number; y: number}; 'opened: cdkContextMenuOpened', 'closed: cdkContextMenuClosed', 'outsideClicked: cdkContextMenuOutsideClicked', + 'triggered: cdkContextMenuTriggered', ], providers: [ {provide: MENU_TRIGGER, useExisting: CdkContextMenuTrigger}, @@ -148,6 +149,9 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr } else { this.childMenu?.focusFirstItem('program'); } + + // Emit that the user have triggered contextmenu event. + this.triggered.emit({x: event.clientX, y: event.clientY}); } } diff --git a/src/cdk/menu/menu-trigger-base.ts b/src/cdk/menu/menu-trigger-base.ts index af7a205f750b..a597d0fcbb09 100644 --- a/src/cdk/menu/menu-trigger-base.ts +++ b/src/cdk/menu/menu-trigger-base.ts @@ -76,6 +76,9 @@ export abstract class CdkMenuTriggerBase implements OnDestroy { /** Emits when the attached menu has click outside of it in open state */ readonly outsideClicked: EventEmitter = new EventEmitter(); + /** Emits when the user triggers context menu */ + readonly triggered: EventEmitter<{x: number; y: number}> = new EventEmitter(); + /** Template reference variable to the menu this trigger opens */ menuTemplateRef: TemplateRef | null; diff --git a/tools/public_api_guard/cdk/menu.md b/tools/public_api_guard/cdk/menu.md index 4d601ccb1583..fa321319ffc3 100644 --- a/tools/public_api_guard/cdk/menu.md +++ b/tools/public_api_guard/cdk/menu.md @@ -44,7 +44,7 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr open(coordinates: ContextMenuCoordinates): void; _openOnContextMenu(event: MouseEvent): void; // (undocumented) - static ɵdir: i0.ɵɵDirectiveDeclaration; + static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } @@ -239,6 +239,10 @@ export abstract class CdkMenuTriggerBase implements OnDestroy { protected overlayRef: OverlayRef | null; registerChildMenu(child: Menu): void; protected readonly stopOutsideClicksListener: Observable; + readonly triggered: EventEmitter<{ + x: number; + y: number; + }>; protected readonly viewContainerRef: ViewContainerRef; // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration;