diff --git a/src/cdk/menu/context-menu-trigger.spec.ts b/src/cdk/menu/context-menu-trigger.spec.ts
index 99757aaa2737..79f2ec79d5a8 100644
--- a/src/cdk/menu/context-menu-trigger.spec.ts
+++ b/src/cdk/menu/context-menu-trigger.spec.ts
@@ -143,6 +143,37 @@ 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();
+ });
+
+ 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();
+ });
+
+ 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', () => {
@@ -442,7 +473,8 @@ describe('CdkContextMenuTrigger', () => {
@Component({
template: `
-
+
@@ -459,6 +491,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..9d56d1c877d9 100644
--- a/src/cdk/menu/context-menu-trigger.ts
+++ b/src/cdk/menu/context-menu-trigger.ts
@@ -75,7 +75,12 @@ 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',
+ 'triggered: cdkContextMenuTriggered',
+ ],
providers: [
{provide: MENU_TRIGGER, useExisting: CdkContextMenuTrigger},
{provide: MENU_STACK, useClass: MenuStack},
@@ -96,6 +101,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();
@@ -140,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});
}
}
@@ -210,7 +222,13 @@ 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();
+ }
+
+ // 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..a597d0fcbb09 100644
--- a/src/cdk/menu/menu-trigger-base.ts
+++ b/src/cdk/menu/menu-trigger-base.ts
@@ -73,6 +73,12 @@ 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();
+
+ /** 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 ffab9c7ed73d..fa321319ffc3 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