import { MatFormFieldControl } from '@angular/material/form-field' ; import { Component , DoCheck , ElementRef , EventEmitter , HostBinding , Input , OnDestroy , OnInit , Optional , Output , Self , ViewChild , ChangeDetectionStrategy , ChangeDetectorRef } from '@angular/core' ; import { FormGroupDirective , NG_VALIDATORS , NgControl , NgForm } from '@angular/forms' ; import { CountryCode , Examples } from './data/country-code' ; import { phoneNumberValidator } from './ngx-mat-intl-tel-input.validator' ; import { Country } from './model/country.model' ; import { PhoneNumberFormat } from './model/phone-number-format.model' ; import { AsYouType , CountryCode as CC , E164Number , getExampleNumber , parsePhoneNumberFromString , PhoneNumber } from 'libphonenumber-js' ; import { coerceBooleanProperty } from '@angular/cdk/coercion' ; import { Subject } from 'rxjs' ; import { FocusMonitor } from '@angular/cdk/a11y' ; import { CanUpdateErrorState , ErrorStateMatcher , mixinErrorState } from '@angular/material/core' ; import { MatMenu } from '@angular/material/menu' ; import { AbstractConstructor , Constructor } from '@angular/material/core/common-behaviors/constructor' ; class NgxMatIntlTelInputBase { constructor ( public _defaultErrorStateMatcher , public _parentForm , public _parentFormGroup , docs-private public ngControl ) { } } declare type CanUpdateErrorStateCtor = Constructor < CanUpdateErrorState > & AbstractConstructor < CanUpdateErrorState > ; const _NgxMatIntlTelInputMixinBase = mixinErrorState ( NgxMatIntlTelInputBase ) ; @ Component ( { selector : 'ngx-mat-intl-tel-input' , templateUrl : './ngx-mat-intl-tel-input.component.html' , styleUrls : [ './ngx-mat-intl-tel-input.component.css' ] , providers : [ CountryCode , { provide : MatFormFieldControl , useExisting : NgxMatIntlTelInputComponent } , { provide : NG_VALIDATORS , useValue : phoneNumberValidator , multi : true , } ] , changeDetection : ChangeDetectionStrategy . OnPush } ) export class NgxMatIntlTelInputComponent extends _NgxMatIntlTelInputMixinBase implements OnInit , OnDestroy , DoCheck , CanUpdateErrorState , MatFormFieldControl < any > { static nextId = 0 ; @ Input ( ) preferredCountries : Array < string > = [ ] ; @ Input ( ) enablePlaceholder = true ; @ Input ( ) inputPlaceholder : string ; @ Input ( ) cssClass ; @ Input ( ) name : string ; @ Input ( ) onlyCountries : Array < string > = [ ] ; @ Input ( ) errorStateMatcher : ErrorStateMatcher ; @ Input ( ) enableSearch = false ; @ Input ( ) searchPlaceholder : string ; @ Input ( ) describedBy = '' ; @ Input ( ) get format ( ) : PhoneNumberFormat { return this . _format ; } set format ( value ) { this . _format = value ; this . phoneNumber = this . formattedPhoneNumber ; this . stateChanges . next ( ) ; } @ ViewChild ( MatMenu ) matMenu : MatMenu ; private _placeholder : string ; private _required = false ; private _disabled = false ; stateChanges = new Subject < void > ( ) ; focused = false ; @ HostBinding ( ) id = `ngx-mat-intl-tel-input-${ NgxMatIntlTelInputComponent . nextId ++ }` ; phoneNumber : E164Number = '' ; allCountries : Array < Country > = [ ] ; preferredCountriesInDropDown : Array < Country > = [ ] ; selectedCountry : Country ; numberInstance : PhoneNumber ; value ; searchCriteria : string ; @ Output ( ) countryChanged : EventEmitter < Country > = new EventEmitter < Country > ( ) ; private previousFormattedNumber : string ; private _format : PhoneNumberFormat = 'default' ; static getPhoneNumberPlaceHolder ( countryISOCode ) { try { return getExampleNumber ( countryISOCode , Examples ) . number . toString ( ) ; } catch ( e ) { return e ; } } onTouched = ( ) => { } ; propagateChange = ( _ ) => { } ; constructor ( private _changeDetectorRef , private countryCodeData , private fm , private elRef , @ Optional ( ) @ Self ( ) public ngControl , @ Optional ( ) _parentForm , @ Optional ( ) _parentFormGroup , _defaultErrorStateMatcher ) { super ( _defaultErrorStateMatcher , _parentForm , _parentFormGroup , ngControl ) ; fm . monitor ( elRef , true ) . subscribe ( origin => { if ( this . focused && ! origin ) { this . onTouched ( ) ; } this . focused = ! ! origin ; this . stateChanges . next ( ) ; } ) ; this . fetchCountryData ( ) ; if ( this . ngControl != null ) { this . ngControl . valueAccessor = this ; } } ngOnInit ( ) { if ( ! this . searchPlaceholder ) { this . searchPlaceholder = 'Search ...' ; } if ( this . preferredCountries . length ) { this . preferredCountries . forEach ( iso2 => { const preferredCountry = this . allCountries . filter ( ( c ) => c . iso2 === iso2 ) . shift ( ) ; this . preferredCountriesInDropDown . push ( preferredCountry ) ; } ) ; } if ( this . onlyCountries . length ) { this . allCountries = this . allCountries . filter ( c => this . onlyCountries . includes ( c . iso2 ) ) ; } if ( this . numberInstance && this . numberInstance . country ) { this . selectedCountry = this . getCountry ( this . numberInstance . country ) ; } else { if ( this . preferredCountriesInDropDown . length ) { this . selectedCountry = this . preferredCountriesInDropDown [ 0 ] ; } else { this . selectedCountry = this . allCountries [ 0 ] ; } } this . countryChanged . emit ( this . selectedCountry ) ; this . _changeDetectorRef . markForCheck ( ) ; this . stateChanges . next ( ) ; } ngDoCheck ( ) { if ( this . ngControl ) { this . updateErrorState ( ) ; } } public onPhoneNumberChange ( ) { try { this . numberInstance = parsePhoneNumberFromString ( this . phoneNumber . toString ( ) , this . selectedCountry . iso2 . toUpperCase ( ) as CC ) ; this . formatAsYouTypeIfEnabled ( ) ; this . value = this . numberInstance . number ; if ( this . numberInstance && this . numberInstance . isValid ( ) ) { if ( this . phoneNumber !== this . formattedPhoneNumber ) { this . phoneNumber = this . formattedPhoneNumber ; } if ( this . selectedCountry . iso2 !== this . numberInstance . country ) { this . selectedCountry = this . getCountry ( this . numberInstance . country ) ; this . countryChanged . emit ( this . selectedCountry ) ; } } } catch ( e ) { this . value = this . phoneNumber . toString ( ) ; } this . propagateChange ( this . value ) ; this . _changeDetectorRef . markForCheck ( ) ; } public onCountrySelect ( country , el ) { if ( this . phoneNumber ) { this . phoneNumber = this . numberInstance . nationalNumber ; } this . selectedCountry = country ; this . countryChanged . emit ( this . selectedCountry ) ; this . onPhoneNumberChange ( ) ; el . focus ( ) ; } public getCountry ( code ) { return this . allCountries . find ( c => c . iso2 === code . toLowerCase ( ) ) || { name : 'UN' , iso2 : 'UN' , dialCode : undefined , priority : 0 , areaCodes : undefined , flagClass : 'UN' , placeHolder : '' } ; } public onInputKeyPress ( event ) { const pattern = /[0-9+\- ]/ ; if ( ! pattern . test ( event . key ) ) { event . preventDefault ( ) ; } } protected fetchCountryData ( ) { this . countryCodeData . allCountries . forEach ( c => { const country = { name : c [ 0 ] . toString ( ) , iso2 : c [ 1 ] . toString ( ) , dialCode : c [ 2 ] . toString ( ) , priority : + c [ 3 ] || 0 , areaCodes : c [ 4 ] as string [ ] || undefined , flagClass : c [ 1 ] . toString ( ) . toUpperCase ( ) , placeHolder : '' } ; if ( this . enablePlaceholder ) { country . placeHolder = NgxMatIntlTelInputComponent . getPhoneNumberPlaceHolder ( country . iso2 . toUpperCase ( ) ) ; } this . allCountries . push ( country ) ; } ) ; } registerOnChange ( fn ) { this . propagateChange = fn ; } registerOnTouched ( fn ) { this . onTouched = fn ; } setDisabledState ( isDisabled ) { this . disabled = isDisabled ; this . _changeDetectorRef . markForCheck ( ) ; this . stateChanges . next ( ) ; } writeValue ( value ) { if ( value ) { this . numberInstance = parsePhoneNumberFromString ( value ) ; if ( this . numberInstance ) { const countryCode = this . numberInstance . country ; this . phoneNumber = this . formattedPhoneNumber ; if ( ! countryCode ) { return ; } setTimeout ( ( ) => { this . selectedCountry = this . getCountry ( countryCode ) ; if ( this . selectedCountry . dialCode && ! this . preferredCountries . includes ( this . selectedCountry . iso2 ) ) { this . preferredCountriesInDropDown . push ( this . selectedCountry ) ; } this . countryChanged . emit ( this . selectedCountry ) ; this . _changeDetectorRef . markForCheck ( ) ; this . stateChanges . next ( ) ; } , 1 ) ; } else { this . phoneNumber = value ; } } this . _changeDetectorRef . markForCheck ( ) ; this . stateChanges . next ( ) ; } get empty ( ) { return ! this . phoneNumber ; } @ HostBinding ( 'class.ngx-floating' ) get shouldLabelFloat ( ) { return this . focused || ! this . empty ; } @ Input ( ) get placeholder ( ) : string { return this . _placeholder ; } set placeholder ( value ) { this . _placeholder = value ; this . stateChanges . next ( ) ; } @ Input ( ) get required ( ) : boolean { return this . _required ; } set required ( value ) { this . _required = coerceBooleanProperty ( value ) ; this . stateChanges . next ( ) ; } @ Input ( ) get disabled ( ) : boolean { return this . _disabled ; } set disabled ( value ) { this . _disabled = coerceBooleanProperty ( value ) ; this . stateChanges . next ( ) ; } setDescribedByIds ( ids ) { this . describedBy = ids . join ( ' ' ) ; } onContainerClick ( event ) { if ( ( event . target as Element ) . tagName . toLowerCase ( ) !== 'input' ) { this . elRef . nativeElement . querySelector ( 'input' ) ! . focus ( ) ; } } reset ( ) { this . phoneNumber = '' ; this . propagateChange ( null ) ; this . _changeDetectorRef . markForCheck ( ) ; this . stateChanges . next ( ) ; } ngOnDestroy ( ) { this . stateChanges . complete ( ) ; this . fm . stopMonitoring ( this . elRef ) ; } private get formattedPhoneNumber ( ) : string { if ( ! this . numberInstance ) { return this . phoneNumber . toString ( ) ; } switch ( this . format ) { case 'national' : return this . numberInstance . formatNational ( ) ; case 'international' : return this . numberInstance . formatInternational ( ) ; default : return this . numberInstance . nationalNumber . toString ( ) ; } } private formatAsYouTypeIfEnabled ( ) { if ( this . format === 'default' ) { return ; } const asYouType = new AsYouType ( this . selectedCountry . iso2 . toUpperCase ( ) as CC ) ; if ( this . phoneNumber . toString ( ) . startsWith ( this . previousFormattedNumber || '' ) ) { this . phoneNumber = asYouType . input ( this . phoneNumber . toString ( ) ) ; } this . previousFormattedNumber = this . phoneNumber . toString ( ) ; } }