@@ -21,6 +21,7 @@ import {BaseOverlayDispatcher} from './base-overlay-dispatcher';
2121export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
2222 private _cursorOriginalValue : string ;
2323 private _cursorStyleIsSet = false ;
24+ private _pointerDownEventTarget : EventTarget | null ;
2425
2526 constructor ( @Inject ( DOCUMENT ) document : any , private _platform : Platform ) {
2627 super ( document ) ;
@@ -38,6 +39,7 @@ export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
3839 // https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html
3940 if ( ! this . _isAttached ) {
4041 const body = this . _document . body ;
42+ body . addEventListener ( 'pointerdown' , this . _pointerDownListener , true ) ;
4143 body . addEventListener ( 'click' , this . _clickListener , true ) ;
4244 body . addEventListener ( 'auxclick' , this . _clickListener , true ) ;
4345 body . addEventListener ( 'contextmenu' , this . _clickListener , true ) ;
@@ -58,6 +60,7 @@ export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
5860 protected detach ( ) {
5961 if ( this . _isAttached ) {
6062 const body = this . _document . body ;
63+ body . removeEventListener ( 'pointerdown' , this . _pointerDownListener , true ) ;
6164 body . removeEventListener ( 'click' , this . _clickListener , true ) ;
6265 body . removeEventListener ( 'auxclick' , this . _clickListener , true ) ;
6366 body . removeEventListener ( 'contextmenu' , this . _clickListener , true ) ;
@@ -69,9 +72,26 @@ export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
6972 }
7073 }
7174
75+ /** Store pointerdown event target to track origin of click. */
76+ private _pointerDownListener = ( event : PointerEvent ) => {
77+ this . _pointerDownEventTarget = _getEventTarget ( event ) ;
78+ }
79+
7280 /** Click event listener that will be attached to the body propagate phase. */
7381 private _clickListener = ( event : MouseEvent ) => {
7482 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+
7595 // We copy the array because the original may be modified asynchronously if the
7696 // outsidePointerEvents listener decides to detach overlays resulting in index errors inside
7797 // the for loop.
@@ -88,8 +108,10 @@ export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
88108 }
89109
90110 // 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 ) ) {
93115 break ;
94116 }
95117
0 commit comments