license import { Platform , normalizePassiveListenerOptions } from '@angular/cdk/platform' ; import { Directive , ElementRef , EventEmitter , Injectable , NgZone , OnDestroy , OnInit , Output , } from '@angular/core' ; import { coerceElement } from '@angular/cdk/coercion' ; import { EMPTY , Observable , Subject } from 'rxjs' ; /** An event that is emitted when the autofill state of an input changes. */ export type AutofillEvent = { /** The element whose autofill state changes. */ target : Element ; /** Whether the element is currently autofilled. */ isAutofilled : boolean ; } ; /** Used to track info about currently monitored elements. */ type MonitoredElementInfo = { subject : Subject < AutofillEvent > ; unlisten : ( ) => void ; } ; /** Options to pass to the animationstart listener. */ const listenerOptions = normalizePassiveListenerOptions ( { passive : true } ) ; /**
 * An injectable service that can be used to monitor the autofill state of an input.
 * Based on the following blog post:
 * https://medium.com/@brunn/detecting-autofilled-fields-in-javascript-aed598d25da7
 */ @ Injectable ( { providedIn : 'root' } ) export class AutofillMonitor implements OnDestroy { private _monitoredElements = new Map < Element , MonitoredElementInfo > ( ) ; constructor ( private _platform , private _ngZone ) { } param element return monitor ( element ) ; param element return monitor ( element ) ; monitor ( elementOrRef ) { if ( ! this . _platform . isBrowser ) { return EMPTY ; } const element = coerceElement ( elementOrRef ) ; const info = this . _monitoredElements . get ( element ) ; if ( info ) { return info . subject . asObservable ( ) ; } const result = new Subject < AutofillEvent > ( ) ; const cssClass = 'cdk-text-field-autofilled' ; const listener = ( ( event ) => { if ( event . animationName === 'cdk-text-field-autofill-start' && ! element . classList . contains ( cssClass ) ) { element . classList . add ( cssClass ) ; this . _ngZone . run ( ( ) => result . next ( { target : event . target as Element , isAutofilled : true } ) ) ; } else if ( event . animationName === 'cdk-text-field-autofill-end' && element . classList . contains ( cssClass ) ) { element . classList . remove ( cssClass ) ; this . _ngZone . run ( ( ) => result . next ( { target : event . target as Element , isAutofilled : false } ) ) ; } } ) as EventListenerOrEventListenerObject ; this . _ngZone . runOutsideAngular ( ( ) => { element . addEventListener ( 'animationstart' , listener , listenerOptions ) ; element . classList . add ( 'cdk-text-field-autofill-monitored' ) ; } ) ; this . _monitoredElements . set ( element , { subject : result , unlisten : ( ) => { element . removeEventListener ( 'animationstart' , listener , listenerOptions ) ; } } ) ; return result . asObservable ( ) ; } param element stopMonitoring ( element ) ; param element stopMonitoring ( element ) ; stopMonitoring ( elementOrRef ) { const element = coerceElement ( elementOrRef ) ; const info = this . _monitoredElements . get ( element ) ; if ( info ) { info . unlisten ( ) ; info . subject . complete ( ) ; element . classList . remove ( 'cdk-text-field-autofill-monitored' ) ; element . classList . remove ( 'cdk-text-field-autofilled' ) ; this . _monitoredElements . delete ( element ) ; } } ngOnDestroy ( ) { this . _monitoredElements . forEach ( ( _info , element ) => this . stopMonitoring ( element ) ) ; } } /** A directive that can be used to monitor the autofill state of an input. */ @ Directive ( { selector : '[cdkAutofill]' , } ) export class CdkAutofill implements OnDestroy , OnInit { /** Emits when the autofill state of the element changes. */ @ Output ( ) cdkAutofill : EventEmitter < AutofillEvent > = new EventEmitter < AutofillEvent > ( ) ; constructor ( private _elementRef , private _autofillMonitor ) { } ngOnInit ( ) { this . _autofillMonitor . monitor ( this . _elementRef ) . subscribe ( event => this . cdkAutofill . emit ( event ) ) ; } ngOnDestroy ( ) { this . _autofillMonitor . stopMonitoring ( this . _elementRef ) ; } }