@@ -17,7 +17,7 @@ import {
1717import { BooleanInput , coerceBooleanProperty } from '@angular/cdk/coercion' ;
1818import { _getEventTarget } from '@angular/cdk/platform' ;
1919import { merge , partition } from 'rxjs' ;
20- import { skip , takeUntil } from 'rxjs/operators' ;
20+ import { skip , takeUntil , skipWhile } from 'rxjs/operators' ;
2121import { MENU_STACK , MenuStack } from './menu-stack' ;
2222import { CdkMenuTriggerBase , MENU_TRIGGER } from './menu-trigger-base' ;
2323
@@ -104,7 +104,7 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
104104 * @param coordinates where to open the context menu
105105 */
106106 open ( coordinates : ContextMenuCoordinates ) {
107- this . _open ( coordinates , false ) ;
107+ this . _open ( null , coordinates ) ;
108108 }
109109
110110 /** Close the currently opened context menu. */
@@ -127,7 +127,7 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
127127 event . stopPropagation ( ) ;
128128
129129 this . _contextMenuTracker . update ( this ) ;
130- this . _open ( { x : event . clientX , y : event . clientY } , true ) ;
130+ this . _open ( event , { x : event . clientX , y : event . clientY } ) ;
131131
132132 // A context menu can be triggered via a mouse right click or a keyboard shortcut.
133133 if ( event . button === 2 ) {
@@ -180,17 +180,31 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
180180 /**
181181 * Subscribe to the overlays outside pointer events stream and handle closing out the stack if a
182182 * click occurs outside the menus.
183- * @param ignoreFirstAuxClick Whether to ignore the first auxclick event outside the menu.
183+ * @param userEvent User-generated event that opened the menu.
184184 */
185- private _subscribeToOutsideClicks ( ignoreFirstAuxClick : boolean ) {
185+ private _subscribeToOutsideClicks ( userEvent : MouseEvent | null ) {
186186 if ( this . overlayRef ) {
187187 let outsideClicks = this . overlayRef . outsidePointerEvents ( ) ;
188- // If the menu was triggered by the `contextmenu` event, skip the first `auxclick` event
189- // because it fires when the mouse is released on the same click that opened the menu.
190- if ( ignoreFirstAuxClick ) {
188+
189+ if ( userEvent ) {
191190 const [ auxClicks , nonAuxClicks ] = partition ( outsideClicks , ( { type} ) => type === 'auxclick' ) ;
192- outsideClicks = merge ( nonAuxClicks , auxClicks . pipe ( skip ( 1 ) ) ) ;
191+ outsideClicks = merge (
192+ // Using a mouse, the `contextmenu` event can fire either when pressing the right button
193+ // or left button + control. Most browsers won't dispatch a `click` event right after
194+ // a `contextmenu` event triggered by left button + control, but Safari will (see #27832).
195+ // This closes the menu immediately. To work around it, we check that both the triggering
196+ // event and the current outside click event both had the control key pressed, and that
197+ // that this is the first outside click event.
198+ nonAuxClicks . pipe (
199+ skipWhile ( ( event , index ) => userEvent . ctrlKey && index === 0 && event . ctrlKey ) ,
200+ ) ,
201+
202+ // If the menu was triggered by the `contextmenu` event, skip the first `auxclick` event
203+ // because it fires when the mouse is released on the same click that opened the menu.
204+ auxClicks . pipe ( skip ( 1 ) ) ,
205+ ) ;
193206 }
207+
194208 outsideClicks . pipe ( takeUntil ( this . stopOutsideClicksListener ) ) . subscribe ( event => {
195209 if ( ! this . isElementInsideMenuStack ( _getEventTarget ( event ) ! ) ) {
196210 this . menuStack . closeAll ( ) ;
@@ -201,10 +215,10 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
201215
202216 /**
203217 * Open the attached menu at the specified location.
218+ * @param userEvent User-generated event that opened the menu
204219 * @param coordinates where to open the context menu
205- * @param ignoreFirstOutsideAuxClick Whether to ignore the first auxclick outside the menu after opening.
206220 */
207- private _open ( coordinates : ContextMenuCoordinates , ignoreFirstOutsideAuxClick : boolean ) {
221+ private _open ( userEvent : MouseEvent | null , coordinates : ContextMenuCoordinates ) {
208222 if ( this . disabled ) {
209223 return ;
210224 }
@@ -230,7 +244,7 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
230244 }
231245
232246 this . overlayRef . attach ( this . getMenuContentPortal ( ) ) ;
233- this . _subscribeToOutsideClicks ( ignoreFirstOutsideAuxClick ) ;
247+ this . _subscribeToOutsideClicks ( userEvent ) ;
234248 }
235249 }
236250}
0 commit comments