Skip to content

Commit

Permalink
fix(cdk-experimental/menu): keep context menus open when mouse is rel…
Browse files Browse the repository at this point in the history
…eased (#24308)
  • Loading branch information
mmalerba committed Feb 1, 2022
1 parent a948ec4 commit 75cda48
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 25 deletions.
28 changes: 26 additions & 2 deletions src/cdk-experimental/menu/context-menu.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('CdkContextMenuTrigger', () => {

/** Get the context in which the context menu should trigger. */
function getMenuContext() {
return fixture.componentInstance.trigger.nativeElement;
return fixture.componentInstance.triggerElement.nativeElement;
}

/** Open up the context menu and run change detection. */
Expand Down Expand Up @@ -73,6 +73,29 @@ describe('CdkContextMenuTrigger', () => {
expect(getContextMenu()).not.toBeDefined();
});

it('should not close the menu on first auxclick after opening via contextmenu event', () => {
openContextMenu();

fixture.nativeElement.querySelector('#other').dispatchEvent(new MouseEvent('auxclick'));
fixture.detectChanges();

expect(getContextMenu()).toBeDefined();

fixture.nativeElement.querySelector('#other').dispatchEvent(new MouseEvent('auxclick'));
fixture.detectChanges();

expect(getContextMenu()).not.toBeDefined();
});

it('should close the menu on first auxclick after opening programmatically', () => {
fixture.componentInstance.trigger.open({x: 0, y: 0});

fixture.nativeElement.querySelector('#other').dispatchEvent(new MouseEvent('auxclick'));
fixture.detectChanges();

expect(getContextMenu()).not.toBeDefined();
});

it('should close out the context menu when clicking a menu item', () => {
openContextMenu();

Expand Down Expand Up @@ -397,7 +420,8 @@ describe('CdkContextMenuTrigger', () => {
`,
})
class SimpleContextMenu {
@ViewChild(CdkContextMenuTrigger, {read: ElementRef}) trigger: ElementRef<HTMLElement>;
@ViewChild(CdkContextMenuTrigger) trigger: CdkContextMenuTrigger;
@ViewChild(CdkContextMenuTrigger, {read: ElementRef}) triggerElement: ElementRef<HTMLElement>;
@ViewChild(CdkMenu) menu?: CdkMenu;
@ViewChild(CdkMenu, {read: ElementRef}) nativeMenu?: ElementRef<HTMLElement>;

Expand Down
54 changes: 31 additions & 23 deletions src/cdk-experimental/menu/context-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,28 @@

import {
Directive,
Input,
ViewContainerRef,
Output,
EventEmitter,
Optional,
OnDestroy,
Inject,
Injectable,
InjectionToken,
Input,
OnDestroy,
Optional,
Output,
ViewContainerRef,
} from '@angular/core';
import {Directionality} from '@angular/cdk/bidi';
import {
OverlayRef,
ConnectedPosition,
FlexibleConnectedPositionStrategy,
Overlay,
OverlayConfig,
FlexibleConnectedPositionStrategy,
ConnectedPosition,
OverlayRef,
} from '@angular/cdk/overlay';
import {TemplatePortal, Portal} from '@angular/cdk/portal';
import {coerceBooleanProperty, BooleanInput} from '@angular/cdk/coercion';
import {Subject, merge} from 'rxjs';
import {takeUntil} from 'rxjs/operators';
import {Portal, TemplatePortal} from '@angular/cdk/portal';
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
import {merge, partition, Subject} from 'rxjs';
import {skip, takeUntil} from 'rxjs/operators';
import {CdkMenuPanel} from './menu-panel';
import {MenuStack} from './menu-stack';
import {throwExistingMenuStackError} from './menu-errors';
Expand Down Expand Up @@ -152,6 +152,10 @@ export class CdkContextMenuTrigger implements OnDestroy {
* @param coordinates where to open the context menu
*/
open(coordinates: ContextMenuCoordinates) {
this._open(coordinates, false);
}

private _open(coordinates: ContextMenuCoordinates, ignoreFirstOutsideAuxClick: boolean) {
if (this.disabled) {
return;
} else if (this.isOpen()) {
Expand All @@ -176,7 +180,7 @@ export class CdkContextMenuTrigger implements OnDestroy {
}

this._overlayRef.attach(this._getMenuContent());
this._subscribeToOutsideClicks();
this._subscribeToOutsideClicks(ignoreFirstOutsideAuxClick);
}
}

Expand All @@ -200,7 +204,7 @@ export class CdkContextMenuTrigger implements OnDestroy {
event.stopPropagation();

this._contextMenuTracker.update(this);
this.open({x: event.clientX, y: event.clientY});
this._open({x: event.clientX, y: event.clientY}, true);

// A context menu can be triggered via a mouse right click or a keyboard shortcut.
if (event.button === 2) {
Expand Down Expand Up @@ -285,16 +289,20 @@ export class CdkContextMenuTrigger implements OnDestroy {
* Subscribe to the overlays outside pointer events stream and handle closing out the stack if a
* click occurs outside the menus.
*/
private _subscribeToOutsideClicks() {
private _subscribeToOutsideClicks(ignoreFirstAuxClick: boolean) {
if (this._overlayRef) {
this._overlayRef
.outsidePointerEvents()
.pipe(takeUntil(this._stopOutsideClicksListener))
.subscribe(event => {
if (!isClickInsideMenuOverlay(event.target as Element)) {
this._menuStack.closeAll();
}
});
let outsideClicks = this._overlayRef.outsidePointerEvents();
// If the menu was triggered by the `contextmenu` event, skip the first `auxclick` event
// because it fires when the mouse is released on the same click that opened the menu.
if (ignoreFirstAuxClick) {
const [auxClicks, nonAuxClicks] = partition(outsideClicks, ({type}) => type === 'auxclick');
outsideClicks = merge(nonAuxClicks, auxClicks.pipe(skip(1)));
}
outsideClicks.pipe(takeUntil(this._stopOutsideClicksListener)).subscribe(event => {
if (!isClickInsideMenuOverlay(event.target as Element)) {
this._menuStack.closeAll();
}
});
}
}

Expand Down

0 comments on commit 75cda48

Please sign in to comment.