@@ -21,6 +21,7 @@ import {BaseOverlayDispatcher} from './base-overlay-dispatcher';
21
21
export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
22
22
private _cursorOriginalValue : string ;
23
23
private _cursorStyleIsSet = false ;
24
+ private _pointerDownEventTarget : EventTarget | null ;
24
25
25
26
constructor ( @Inject ( DOCUMENT ) document : any , private _platform : Platform ) {
26
27
super ( document ) ;
@@ -38,6 +39,7 @@ export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
38
39
// https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html
39
40
if ( ! this . _isAttached ) {
40
41
const body = this . _document . body ;
42
+ body . addEventListener ( 'pointerdown' , this . _pointerDownListener , true ) ;
41
43
body . addEventListener ( 'click' , this . _clickListener , true ) ;
42
44
body . addEventListener ( 'auxclick' , this . _clickListener , true ) ;
43
45
body . addEventListener ( 'contextmenu' , this . _clickListener , true ) ;
@@ -58,6 +60,7 @@ export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
58
60
protected detach ( ) {
59
61
if ( this . _isAttached ) {
60
62
const body = this . _document . body ;
63
+ body . removeEventListener ( 'pointerdown' , this . _pointerDownListener , true ) ;
61
64
body . removeEventListener ( 'click' , this . _clickListener , true ) ;
62
65
body . removeEventListener ( 'auxclick' , this . _clickListener , true ) ;
63
66
body . removeEventListener ( 'contextmenu' , this . _clickListener , true ) ;
@@ -69,9 +72,26 @@ export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
69
72
}
70
73
}
71
74
75
+ /** Store pointerdown event target to track origin of click. */
76
+ private _pointerDownListener = ( event : PointerEvent ) => {
77
+ this . _pointerDownEventTarget = _getEventTarget ( event ) ;
78
+ }
79
+
72
80
/** Click event listener that will be attached to the body propagate phase. */
73
81
private _clickListener = ( event : MouseEvent ) => {
74
82
const target = _getEventTarget ( event ) ;
83
+ // In case of a click event, we want to check the origin of the click
84
+ // (e.g. in case where a user starts a click inside the overlay and
85
+ // releases the click outside of it).
86
+ // This is done by using the event target of the preceding pointerdown event.
87
+ // Every click event caused by a pointer device has a preceding pointerdown
88
+ // event, unless the click was programmatically triggered (e.g. in a unit test).
89
+ const origin = event . type === 'click' && this . _pointerDownEventTarget
90
+ ? this . _pointerDownEventTarget : target ;
91
+ // Reset the stored pointerdown event target, to avoid having it interfere
92
+ // in subsequent events.
93
+ this . _pointerDownEventTarget = null ;
94
+
75
95
// We copy the array because the original may be modified asynchronously if the
76
96
// outsidePointerEvents listener decides to detach overlays resulting in index errors inside
77
97
// the for loop.
@@ -88,8 +108,10 @@ export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
88
108
}
89
109
90
110
// If it's a click inside the overlay, just break - we should do nothing
91
- // If it's an outside click dispatch the mouse event, and proceed with the next overlay
92
- if ( overlayRef . overlayElement . contains ( target as Node ) ) {
111
+ // If it's an outside click (both origin and target of the click) dispatch the mouse event,
112
+ // and proceed with the next overlay
113
+ if ( overlayRef . overlayElement . contains ( target as Node ) ||
114
+ overlayRef . overlayElement . contains ( origin as Node ) ) {
93
115
break ;
94
116
}
95
117
0 commit comments