@@ -16,7 +16,7 @@ import {
1616} from '@angular/cdk/overlay' ;
1717import { _getEventTarget } from '@angular/cdk/platform' ;
1818import { merge , partition } from 'rxjs' ;
19- import { skip , takeUntil } from 'rxjs/operators' ;
19+ import { skip , takeUntil , skipWhile } from 'rxjs/operators' ;
2020import { MENU_STACK , MenuStack } from './menu-stack' ;
2121import { CdkMenuTriggerBase , MENU_TRIGGER } from './menu-trigger-base' ;
2222
@@ -96,7 +96,7 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
9696 * @param coordinates where to open the context menu
9797 */
9898 open ( coordinates : ContextMenuCoordinates ) {
99- this . _open ( coordinates , false ) ;
99+ this . _open ( null , coordinates ) ;
100100 }
101101
102102 /** Close the currently opened context menu. */
@@ -119,7 +119,7 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
119119 event . stopPropagation ( ) ;
120120
121121 this . _contextMenuTracker . update ( this ) ;
122- this . _open ( { x : event . clientX , y : event . clientY } , true ) ;
122+ this . _open ( event , { x : event . clientX , y : event . clientY } ) ;
123123
124124 // A context menu can be triggered via a mouse right click or a keyboard shortcut.
125125 if ( event . button === 2 ) {
@@ -172,17 +172,31 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
172172 /**
173173 * Subscribe to the overlays outside pointer events stream and handle closing out the stack if a
174174 * click occurs outside the menus.
175- * @param ignoreFirstAuxClick Whether to ignore the first auxclick event outside the menu.
175+ * @param userEvent User-generated event that opened the menu.
176176 */
177- private _subscribeToOutsideClicks ( ignoreFirstAuxClick : boolean ) {
177+ private _subscribeToOutsideClicks ( userEvent : MouseEvent | null ) {
178178 if ( this . overlayRef ) {
179179 let outsideClicks = this . overlayRef . outsidePointerEvents ( ) ;
180- // If the menu was triggered by the `contextmenu` event, skip the first `auxclick` event
181- // because it fires when the mouse is released on the same click that opened the menu.
182- if ( ignoreFirstAuxClick ) {
180+
181+ if ( userEvent ) {
183182 const [ auxClicks , nonAuxClicks ] = partition ( outsideClicks , ( { type} ) => type === 'auxclick' ) ;
184- outsideClicks = merge ( nonAuxClicks , auxClicks . pipe ( skip ( 1 ) ) ) ;
183+ outsideClicks = merge (
184+ // Using a mouse, the `contextmenu` event can fire either when pressing the right button
185+ // or left button + control. Most browsers won't dispatch a `click` event right after
186+ // a `contextmenu` event triggered by left button + control, but Safari will (see #27832).
187+ // This closes the menu immediately. To work around it, we check that both the triggering
188+ // event and the current outside click event both had the control key pressed, and that
189+ // that this is the first outside click event.
190+ nonAuxClicks . pipe (
191+ skipWhile ( ( event , index ) => userEvent . ctrlKey && index === 0 && event . ctrlKey ) ,
192+ ) ,
193+
194+ // If the menu was triggered by the `contextmenu` event, skip the first `auxclick` event
195+ // because it fires when the mouse is released on the same click that opened the menu.
196+ auxClicks . pipe ( skip ( 1 ) ) ,
197+ ) ;
185198 }
199+
186200 outsideClicks . pipe ( takeUntil ( this . stopOutsideClicksListener ) ) . subscribe ( event => {
187201 if ( ! this . isElementInsideMenuStack ( _getEventTarget ( event ) ! ) ) {
188202 this . menuStack . closeAll ( ) ;
@@ -193,10 +207,10 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
193207
194208 /**
195209 * Open the attached menu at the specified location.
210+ * @param userEvent User-generated event that opened the menu
196211 * @param coordinates where to open the context menu
197- * @param ignoreFirstOutsideAuxClick Whether to ignore the first auxclick outside the menu after opening.
198212 */
199- private _open ( coordinates : ContextMenuCoordinates , ignoreFirstOutsideAuxClick : boolean ) {
213+ private _open ( userEvent : MouseEvent | null , coordinates : ContextMenuCoordinates ) {
200214 if ( this . disabled ) {
201215 return ;
202216 }
@@ -222,7 +236,7 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
222236 }
223237
224238 this . overlayRef . attach ( this . getMenuContentPortal ( ) ) ;
225- this . _subscribeToOutsideClicks ( ignoreFirstOutsideAuxClick ) ;
239+ this . _subscribeToOutsideClicks ( userEvent ) ;
226240 }
227241 }
228242}
0 commit comments