@@ -16,7 +16,7 @@ import {
16
16
} from '@angular/cdk/overlay' ;
17
17
import { _getEventTarget } from '@angular/cdk/platform' ;
18
18
import { merge , partition } from 'rxjs' ;
19
- import { skip , takeUntil } from 'rxjs/operators' ;
19
+ import { skip , takeUntil , skipWhile } from 'rxjs/operators' ;
20
20
import { MENU_STACK , MenuStack } from './menu-stack' ;
21
21
import { CdkMenuTriggerBase , MENU_TRIGGER } from './menu-trigger-base' ;
22
22
@@ -96,7 +96,7 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
96
96
* @param coordinates where to open the context menu
97
97
*/
98
98
open ( coordinates : ContextMenuCoordinates ) {
99
- this . _open ( coordinates , false ) ;
99
+ this . _open ( null , coordinates ) ;
100
100
}
101
101
102
102
/** Close the currently opened context menu. */
@@ -119,7 +119,7 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
119
119
event . stopPropagation ( ) ;
120
120
121
121
this . _contextMenuTracker . update ( this ) ;
122
- this . _open ( { x : event . clientX , y : event . clientY } , true ) ;
122
+ this . _open ( event , { x : event . clientX , y : event . clientY } ) ;
123
123
124
124
// A context menu can be triggered via a mouse right click or a keyboard shortcut.
125
125
if ( event . button === 2 ) {
@@ -172,17 +172,31 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
172
172
/**
173
173
* Subscribe to the overlays outside pointer events stream and handle closing out the stack if a
174
174
* 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.
176
176
*/
177
- private _subscribeToOutsideClicks ( ignoreFirstAuxClick : boolean ) {
177
+ private _subscribeToOutsideClicks ( userEvent : MouseEvent | null ) {
178
178
if ( this . overlayRef ) {
179
179
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 ) {
183
182
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
+ ) ;
185
198
}
199
+
186
200
outsideClicks . pipe ( takeUntil ( this . stopOutsideClicksListener ) ) . subscribe ( event => {
187
201
if ( ! this . isElementInsideMenuStack ( _getEventTarget ( event ) ! ) ) {
188
202
this . menuStack . closeAll ( ) ;
@@ -193,10 +207,10 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
193
207
194
208
/**
195
209
* Open the attached menu at the specified location.
210
+ * @param userEvent User-generated event that opened the menu
196
211
* @param coordinates where to open the context menu
197
- * @param ignoreFirstOutsideAuxClick Whether to ignore the first auxclick outside the menu after opening.
198
212
*/
199
- private _open ( coordinates : ContextMenuCoordinates , ignoreFirstOutsideAuxClick : boolean ) {
213
+ private _open ( userEvent : MouseEvent | null , coordinates : ContextMenuCoordinates ) {
200
214
if ( this . disabled ) {
201
215
return ;
202
216
}
@@ -222,7 +236,7 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
222
236
}
223
237
224
238
this . overlayRef . attach ( this . getMenuContentPortal ( ) ) ;
225
- this . _subscribeToOutsideClicks ( ignoreFirstOutsideAuxClick ) ;
239
+ this . _subscribeToOutsideClicks ( userEvent ) ;
226
240
}
227
241
}
228
242
}
0 commit comments