license import { DOCUMENT } from '@angular/common' ; import { Inject , Injectable } from '@angular/core' ; import { OverlayReference } from '../overlay-reference' ; import { Platform , _getEventTarget } from '@angular/cdk/platform' ; import { BaseOverlayDispatcher } from './base-overlay-dispatcher' ; /**
 * Service for dispatching mouse click events that land on the body to appropriate overlay ref,
 * if any. It maintains a list of attached overlays to determine best suited overlay based
 * on event target and order of overlay opens.
 */ @ Injectable ( { providedIn : 'root' } ) export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher { private _cursorOriginalValue : string ; private _cursorStyleIsSet = false ; private _pointerDownEventTarget : EventTarget | null ; constructor ( @ Inject ( DOCUMENT ) document , private _platform ) { super ( document ) ; } /** Add a new overlay to the list of attached overlay refs. */ override add ( overlayRef ) { super . add ( overlayRef ) ; if ( ! this . _isAttached ) { const body = this . _document . body ; body . addEventListener ( 'pointerdown' , this . _pointerDownListener , true ) ; body . addEventListener ( 'click' , this . _clickListener , true ) ; body . addEventListener ( 'auxclick' , this . _clickListener , true ) ; body . addEventListener ( 'contextmenu' , this . _clickListener , true ) ; if ( this . _platform . IOS && ! this . _cursorStyleIsSet ) { this . _cursorOriginalValue = body . style . cursor ; body . style . cursor = 'pointer' ; this . _cursorStyleIsSet = true ; } this . _isAttached = true ; } } /** Detaches the global keyboard event listener. */ protected detach ( ) { if ( this . _isAttached ) { const body = this . _document . body ; body . removeEventListener ( 'pointerdown' , this . _pointerDownListener , true ) ; body . removeEventListener ( 'click' , this . _clickListener , true ) ; body . removeEventListener ( 'auxclick' , this . _clickListener , true ) ; body . removeEventListener ( 'contextmenu' , this . _clickListener , true ) ; if ( this . _platform . IOS && this . _cursorStyleIsSet ) { body . style . cursor = this . _cursorOriginalValue ; this . _cursorStyleIsSet = false ; } this . _isAttached = false ; } } /** Store pointerdown event target to track origin of click. */ private _pointerDownListener = ( event ) => { this . _pointerDownEventTarget = _getEventTarget ( event ) ; } ; /** Click event listener that will be attached to the body propagate phase. */ private _clickListener = ( event ) => { const target = _getEventTarget ( event ) ; const origin = event . type === 'click' && this . _pointerDownEventTarget ? this . _pointerDownEventTarget : target ; this . _pointerDownEventTarget = null ; const overlays = this . _attachedOverlays . slice ( ) ; for ( let i = overlays . length - 1 ; i > - 1 ; i -- ) { const overlayRef = overlays [ i ] ; if ( overlayRef . _outsidePointerEvents . observers . length < 1 || ! overlayRef . hasAttached ( ) ) { continue ; } if ( overlayRef . overlayElement . contains ( target as Node ) || overlayRef . overlayElement . contains ( origin as Node ) ) { break ; } overlayRef . _outsidePointerEvents . next ( event ) ; } } ; }