license import { Platform , normalizePassiveListenerOptions } from '@angular/cdk/platform' ; import { Directive , ElementRef , EventEmitter , Injectable , NgZone , OnDestroy , Optional , Output , SkipSelf , } from '@angular/core' ; import { Observable , of as observableOf , Subject , Subscription } from 'rxjs' ; import { coerceElement } from '@angular/cdk/coercion' ; export const TOUCH_BUFFER_MS = 650 ; export type FocusOrigin = 'touch' | 'mouse' | 'keyboard' | 'program' | null ; /**
 * Corresponds to the options that can be passed to the native `focus` event.
 * via https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus
 */ export interface FocusOptions { /** Whether the browser should scroll to the element when it is focused. */ preventScroll ? : boolean ; } type MonitoredElementInfo = { unlisten : Function ; checkChildren : boolean ; subject : Subject < FocusOrigin > ; } ; /**
 * Event listener options that enable capturing and also
 * mark the the listener as passive if the browser supports it.
 */ const captureEventListenerOptions = normalizePassiveListenerOptions ( { passive : true , capture : true } ) ; /** Monitors mouse and keyboard events to determine the cause of focus events. */ @ Injectable ( { providedIn : 'root' } ) export class FocusMonitor implements OnDestroy { /** The focus origin that the next focus event is a result of. */ private _origin : FocusOrigin = null ; /** The FocusOrigin of the last focus event tracked by the FocusMonitor. */ private _lastFocusOrigin : FocusOrigin ; /** Whether the window has just been focused. */ private _windowFocused = false ; /** The target of the last touch event. */ private _lastTouchTarget : EventTarget | null ; /** The timeout id of the touch timeout, used to cancel timeout later. */ private _touchTimeoutId : number ; /** The timeout id of the window focus timeout. */ private _windowFocusTimeoutId : number ; /** The timeout id of the origin clearing timeout. */ private _originTimeoutId : number ; /** Map of elements being monitored to their info. */ private _elementInfo = new Map < HTMLElement , MonitoredElementInfo > ( ) ; /** The number of elements currently being monitored. */ private _monitoredElementCount = 0 ; /**
     * Event listener for `keydown` events on the document.
     * Needs to be an arrow function in order to preserve the context when it gets bound.
     */ private _documentKeydownListener = ( ) => { this . _lastTouchTarget = null ; this . _setOriginForCurrentEventQueue ( 'keyboard' ) ; } ; /**
     * Event listener for `mousedown` events on the document.
     * Needs to be an arrow function in order to preserve the context when it gets bound.
     */ private _documentMousedownListener = ( ) => { if ( ! this . _lastTouchTarget ) { this . _setOriginForCurrentEventQueue ( 'mouse' ) ; } } ; /**
     * Event listener for `touchstart` events on the document.
     * Needs to be an arrow function in order to preserve the context when it gets bound.
     */ private _documentTouchstartListener = ( event ) => { if ( this . _touchTimeoutId != null ) { clearTimeout ( this . _touchTimeoutId ) ; } this . _lastTouchTarget = event . target ; this . _touchTimeoutId = setTimeout ( ( ) => this . _lastTouchTarget = null , TOUCH_BUFFER_MS ) ; } ; /**
     * Event listener for `focus` events on the window.
     * Needs to be an arrow function in order to preserve the context when it gets bound.
     */ private _windowFocusListener = ( ) => { this . _windowFocused = true ; this . _windowFocusTimeoutId = setTimeout ( ( ) => this . _windowFocused = false ) ; } ; constructor ( private _ngZone , private _platform ) { } param element param checkChildren returns monitor ( element , checkChildren ? ) ; param element param checkChildren returns monitor ( element , checkChildren ? ) ; monitor ( element , checkChildren = false ) { if ( ! this . _platform . isBrowser ) { return observableOf ( null ) ; } const nativeElement = coerceElement ( element ) ; if ( this . _elementInfo . has ( nativeElement ) ) { let cachedInfo = this . _elementInfo . get ( nativeElement ) ; cachedInfo ! . checkChildren = checkChildren ; return cachedInfo ! . subject . asObservable ( ) ; } let info = { unlisten : ( ) => { } , checkChildren : checkChildren , subject : new Subject < FocusOrigin > ( ) } ; this . _elementInfo . set ( nativeElement , info ) ; this . _incrementMonitoredElementCount ( ) ; let focusListener = ( event ) => this . _onFocus ( event , nativeElement ) ; let blurListener = ( event ) => this . _onBlur ( event , nativeElement ) ; this . _ngZone . runOutsideAngular ( ( ) => { nativeElement . addEventListener ( 'focus' , focusListener , true ) ; nativeElement . addEventListener ( 'blur' , blurListener , true ) ; } ) ; info . unlisten = ( ) => { nativeElement . removeEventListener ( 'focus' , focusListener , true ) ; nativeElement . removeEventListener ( 'blur' , blurListener , true ) ; } ; return info . subject . asObservable ( ) ; } param element stopMonitoring ( element ) ; param element stopMonitoring ( element ) ; stopMonitoring ( element ) { const nativeElement = coerceElement ( element ) ; const elementInfo = this . _elementInfo . get ( nativeElement ) ; if ( elementInfo ) { elementInfo . unlisten ( ) ; elementInfo . subject . complete ( ) ; this . _setClasses ( nativeElement ) ; this . _elementInfo . delete ( nativeElement ) ; this . _decrementMonitoredElementCount ( ) ; } } param element param origin param options focusVia ( element , origin , options ? ) ; param element param origin param options focusVia ( element , origin , options ? ) ; focusVia ( element , origin , options ? ) { const nativeElement = coerceElement ( element ) ; this . _setOriginForCurrentEventQueue ( origin ) ; if ( typeof nativeElement . focus === 'function' ) { ( nativeElement as any ) . focus ( options ) ; } } ngOnDestroy ( ) { this . _elementInfo . forEach ( ( _info , element ) => this . stopMonitoring ( element ) ) ; } private _toggleClass ( element , className , shouldSet ) { if ( shouldSet ) { element . classList . add ( className ) ; } else { element . classList . remove ( className ) ; } } param element param origin private _setClasses ( element , origin ? ) { const elementInfo = this . _elementInfo . get ( element ) ; if ( elementInfo ) { this . _toggleClass ( element , 'cdk-focused' , ! ! origin ) ; this . _toggleClass ( element , 'cdk-touch-focused' , origin === 'touch' ) ; this . _toggleClass ( element , 'cdk-keyboard-focused' , origin === 'keyboard' ) ; this . _toggleClass ( element , 'cdk-mouse-focused' , origin === 'mouse' ) ; this . _toggleClass ( element , 'cdk-program-focused' , origin === 'program' ) ; } } param origin private _setOriginForCurrentEventQueue ( origin ) { this . _ngZone . runOutsideAngular ( ( ) => { this . _origin = origin ; this . _originTimeoutId = setTimeout ( ( ) => this . _origin = null , 1 ) ; } ) ; } param event returns private _wasCausedByTouch ( event ) { let focusTarget = event . target ; return this . _lastTouchTarget instanceof Node && focusTarget instanceof Node && ( focusTarget === this . _lastTouchTarget || focusTarget . contains ( this . _lastTouchTarget ) ) ; } param event param element private _onFocus ( event , element ) { const elementInfo = this . _elementInfo . get ( element ) ; if ( ! elementInfo || ( ! elementInfo . checkChildren && element !== event . target ) ) { return ; } let origin = this . _origin ; if ( ! origin ) { if ( this . _windowFocused && this . _lastFocusOrigin ) { origin = this . _lastFocusOrigin ; } else if ( this . _wasCausedByTouch ( event ) ) { origin = 'touch' ; } else { origin = 'program' ; } } this . _setClasses ( element , origin ) ; this . _emitOrigin ( elementInfo . subject , origin ) ; this . _lastFocusOrigin = origin ; } param event param element _onBlur ( event , element ) { const elementInfo = this . _elementInfo . get ( element ) ; if ( ! elementInfo || ( elementInfo . checkChildren && event . relatedTarget instanceof Node && element . contains ( event . relatedTarget ) ) ) { return ; } this . _setClasses ( element ) ; this . _emitOrigin ( elementInfo . subject , null ) ; } private _emitOrigin ( subject , origin ) { this . _ngZone . run ( ( ) => subject . next ( origin ) ) ; } private _incrementMonitoredElementCount ( ) { if ( ++ this . _monitoredElementCount == 1 && this . _platform . isBrowser ) { this . _ngZone . runOutsideAngular ( ( ) => { document . addEventListener ( 'keydown' , this . _documentKeydownListener , captureEventListenerOptions ) ; document . addEventListener ( 'mousedown' , this . _documentMousedownListener , captureEventListenerOptions ) ; document . addEventListener ( 'touchstart' , this . _documentTouchstartListener , captureEventListenerOptions ) ; window . addEventListener ( 'focus' , this . _windowFocusListener ) ; } ) ; } } private _decrementMonitoredElementCount ( ) { if ( ! -- this . _monitoredElementCount ) { document . removeEventListener ( 'keydown' , this . _documentKeydownListener , captureEventListenerOptions ) ; document . removeEventListener ( 'mousedown' , this . _documentMousedownListener , captureEventListenerOptions ) ; document . removeEventListener ( 'touchstart' , this . _documentTouchstartListener , captureEventListenerOptions ) ; window . removeEventListener ( 'focus' , this . _windowFocusListener ) ; clearTimeout ( this . _windowFocusTimeoutId ) ; clearTimeout ( this . _touchTimeoutId ) ; clearTimeout ( this . _originTimeoutId ) ; } } } /**
 * Directive that determines how a particular element was focused (via keyboard, mouse, touch, or
 * programmatically) and adds corresponding classes to the element.
 *
 * There are two variants of this directive:
 * 1) cdkMonitorElementFocus: does not consider an element to be focused if one of its children is
 *    focused.
 * 2) cdkMonitorSubtreeFocus: considers an element focused if it or any of its children are focused.
 */ @ Directive ( { selector : '[cdkMonitorElementFocus], [cdkMonitorSubtreeFocus]' , } ) export class CdkMonitorFocus implements OnDestroy { private _monitorSubscription : Subscription ; @ Output ( ) cdkFocusChange = new EventEmitter < FocusOrigin > ( ) ; constructor ( private _elementRef , private _focusMonitor ) { this . _monitorSubscription = this . _focusMonitor . monitor ( this . _elementRef , this . _elementRef . nativeElement . hasAttribute ( 'cdkMonitorSubtreeFocus' ) ) . subscribe ( origin => this . cdkFocusChange . emit ( origin ) ) ; } ngOnDestroy ( ) { this . _focusMonitor . stopMonitoring ( this . _elementRef ) ; this . _monitorSubscription . unsubscribe ( ) ; } } docs-private deprecated breaking-change export function FOCUS_MONITOR_PROVIDER_FACTORY ( parentDispatcher , ngZone , platform ) { return parentDispatcher || new FocusMonitor ( ngZone , platform ) ; } docs-private deprecated breaking-change export const FOCUS_MONITOR_PROVIDER = { provide : FocusMonitor , deps : [ [ new Optional ( ) , new SkipSelf ( ) , FocusMonitor ] , NgZone , Platform ] , useFactory : FOCUS_MONITOR_PROVIDER_FACTORY } ;