@@ -17,7 +17,7 @@ import {
17
17
import { BooleanInput , coerceBooleanProperty } from '@angular/cdk/coercion' ;
18
18
import { _getEventTarget } from '@angular/cdk/platform' ;
19
19
import { merge , partition } from 'rxjs' ;
20
- import { skip , takeUntil } from 'rxjs/operators' ;
20
+ import { skip , takeUntil , skipWhile } from 'rxjs/operators' ;
21
21
import { MENU_STACK , MenuStack } from './menu-stack' ;
22
22
import { CdkMenuTriggerBase , MENU_TRIGGER } from './menu-trigger-base' ;
23
23
@@ -104,7 +104,7 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
104
104
* @param coordinates where to open the context menu
105
105
*/
106
106
open ( coordinates : ContextMenuCoordinates ) {
107
- this . _open ( coordinates , false ) ;
107
+ this . _open ( null , coordinates ) ;
108
108
}
109
109
110
110
/** Close the currently opened context menu. */
@@ -127,7 +127,7 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
127
127
event . stopPropagation ( ) ;
128
128
129
129
this . _contextMenuTracker . update ( this ) ;
130
- this . _open ( { x : event . clientX , y : event . clientY } , true ) ;
130
+ this . _open ( event , { x : event . clientX , y : event . clientY } ) ;
131
131
132
132
// A context menu can be triggered via a mouse right click or a keyboard shortcut.
133
133
if ( event . button === 2 ) {
@@ -180,17 +180,31 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
180
180
/**
181
181
* Subscribe to the overlays outside pointer events stream and handle closing out the stack if a
182
182
* 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.
184
184
*/
185
- private _subscribeToOutsideClicks ( ignoreFirstAuxClick : boolean ) {
185
+ private _subscribeToOutsideClicks ( userEvent : MouseEvent | null ) {
186
186
if ( this . overlayRef ) {
187
187
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 ) {
191
190
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
+ ) ;
193
206
}
207
+
194
208
outsideClicks . pipe ( takeUntil ( this . stopOutsideClicksListener ) ) . subscribe ( event => {
195
209
if ( ! this . isElementInsideMenuStack ( _getEventTarget ( event ) ! ) ) {
196
210
this . menuStack . closeAll ( ) ;
@@ -201,10 +215,10 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
201
215
202
216
/**
203
217
* Open the attached menu at the specified location.
218
+ * @param userEvent User-generated event that opened the menu
204
219
* @param coordinates where to open the context menu
205
- * @param ignoreFirstOutsideAuxClick Whether to ignore the first auxclick outside the menu after opening.
206
220
*/
207
- private _open ( coordinates : ContextMenuCoordinates , ignoreFirstOutsideAuxClick : boolean ) {
221
+ private _open ( userEvent : MouseEvent | null , coordinates : ContextMenuCoordinates ) {
208
222
if ( this . disabled ) {
209
223
return ;
210
224
}
@@ -230,7 +244,7 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
230
244
}
231
245
232
246
this . overlayRef . attach ( this . getMenuContentPortal ( ) ) ;
233
- this . _subscribeToOutsideClicks ( ignoreFirstOutsideAuxClick ) ;
247
+ this . _subscribeToOutsideClicks ( userEvent ) ;
234
248
}
235
249
}
236
250
}
0 commit comments