@@ -18,7 +18,6 @@ import {
18
18
Injectable ,
19
19
InjectionToken ,
20
20
} from '@angular/core' ;
21
- import { DOCUMENT } from '@angular/common' ;
22
21
import { Directionality } from '@angular/cdk/bidi' ;
23
22
import {
24
23
OverlayRef ,
@@ -29,26 +28,12 @@ import {
29
28
} from '@angular/cdk/overlay' ;
30
29
import { TemplatePortal , Portal } from '@angular/cdk/portal' ;
31
30
import { coerceBooleanProperty , BooleanInput } from '@angular/cdk/coercion' ;
32
- import { fromEvent , merge , Subject } from 'rxjs' ;
31
+ import { Subject , merge } from 'rxjs' ;
33
32
import { takeUntil } from 'rxjs/operators' ;
34
33
import { CdkMenuPanel } from './menu-panel' ;
35
34
import { MenuStack , MenuStackItem } from './menu-stack' ;
36
35
import { throwExistingMenuStackError } from './menu-errors' ;
37
-
38
- /**
39
- * Check if the given element is part of the cdk menu module or nested within a cdk menu element.
40
- * @param target the element to check.
41
- * @return true if the given element is part of the menu module or nested within a cdk menu element.
42
- */
43
- function isWithinMenuElement ( target : Element | null ) {
44
- while ( target instanceof Element ) {
45
- if ( target . classList . contains ( 'cdk-menu' ) && ! target . classList . contains ( 'cdk-menu-inline' ) ) {
46
- return true ;
47
- }
48
- target = target . parentElement ;
49
- }
50
- return false ;
51
- }
36
+ import { isClickInsideMenuOverlay } from './menu-item-trigger' ;
52
37
53
38
/** Tracks the last open context menu trigger across the entire application. */
54
39
@Injectable ( { providedIn : 'root' } )
@@ -146,25 +131,19 @@ export class CdkContextMenuTrigger implements OnDestroy {
146
131
/** Emits when the element is destroyed. */
147
132
private readonly _destroyed : Subject < void > = new Subject ( ) ;
148
133
149
- /** Reference to the document. */
150
- private readonly _document : Document ;
151
-
152
- /** Emits when the document listener should stop. */
153
- private readonly _stopDocumentListener = merge ( this . closed , this . _destroyed ) ;
154
-
155
134
/** The menu stack for this trigger and its associated menus. */
156
135
private readonly _menuStack = new MenuStack ( ) ;
157
136
137
+ /** Emits when the outside pointer events listener on the overlay should be stopped. */
138
+ private readonly _stopOutsideClicksListener = merge ( this . closed , this . _destroyed ) ;
139
+
158
140
constructor (
159
141
protected readonly _viewContainerRef : ViewContainerRef ,
160
142
private readonly _overlay : Overlay ,
161
143
private readonly _contextMenuTracker : ContextMenuTracker ,
162
144
@Inject ( CDK_CONTEXT_MENU_DEFAULT_OPTIONS ) private readonly _options : ContextMenuOptions ,
163
- @Inject ( DOCUMENT ) document : any ,
164
145
@Optional ( ) private readonly _directionality ?: Directionality
165
146
) {
166
- this . _document = document ;
167
-
168
147
this . _setMenuStackListener ( ) ;
169
148
}
170
149
@@ -195,7 +174,7 @@ export class CdkContextMenuTrigger implements OnDestroy {
195
174
}
196
175
197
176
this . _overlayRef . attach ( this . _getMenuContent ( ) ) ;
198
- this . _setCloseListener ( ) ;
177
+ this . _subscribeToOutsideClicks ( ) ;
199
178
}
200
179
}
201
180
@@ -290,32 +269,6 @@ export class CdkContextMenuTrigger implements OnDestroy {
290
269
return this . _panelContent ;
291
270
}
292
271
293
- /**
294
- * Subscribe to the document click and context menu events and close out the menu when emitted.
295
- */
296
- private _setCloseListener ( ) {
297
- merge ( fromEvent ( this . _document , 'click' ) , fromEvent ( this . _document , 'contextmenu' ) )
298
- . pipe ( takeUntil ( this . _stopDocumentListener ) )
299
- . subscribe ( event => {
300
- const target = event . composedPath ? event . composedPath ( ) [ 0 ] : event . target ;
301
- // stop the default context menu from appearing if user right-clicked somewhere outside of
302
- // any context menu directive or if a user right-clicked inside of the opened menu and just
303
- // close it.
304
- if ( event . type === 'contextmenu' ) {
305
- if ( target instanceof Element && isWithinMenuElement ( target ) ) {
306
- // Prevent the native context menu from opening within any open context menu or submenu
307
- event . preventDefault ( ) ;
308
- } else {
309
- this . close ( ) ;
310
- }
311
- } else {
312
- if ( target instanceof Element && ! isWithinMenuElement ( target ) ) {
313
- this . close ( ) ;
314
- }
315
- }
316
- } ) ;
317
- }
318
-
319
272
/** Subscribe to the menu stack close events and close this menu when requested. */
320
273
private _setMenuStackListener ( ) {
321
274
this . _menuStack . closed . pipe ( takeUntil ( this . _destroyed ) ) . subscribe ( ( item : MenuStackItem ) => {
@@ -326,6 +279,23 @@ export class CdkContextMenuTrigger implements OnDestroy {
326
279
} ) ;
327
280
}
328
281
282
+ /**
283
+ * Subscribe to the overlays outside pointer events stream and handle closing out the stack if a
284
+ * click occurs outside the menus.
285
+ */
286
+ private _subscribeToOutsideClicks ( ) {
287
+ if ( this . _overlayRef ) {
288
+ this . _overlayRef
289
+ . outsidePointerEvents ( )
290
+ . pipe ( takeUntil ( this . _stopOutsideClicksListener ) )
291
+ . subscribe ( event => {
292
+ if ( ! isClickInsideMenuOverlay ( event . target as Element ) ) {
293
+ this . _menuStack . closeAll ( ) ;
294
+ }
295
+ } ) ;
296
+ }
297
+ }
298
+
329
299
ngOnDestroy ( ) {
330
300
this . _destroyOverlay ( ) ;
331
301
this . _resetPanelMenuStack ( ) ;
0 commit comments