ngxsmk-tel-input
Version:
Angular international telephone input (intl-tel-input UI + libphonenumber-js validation). ControlValueAccessor. SSR-safe.
354 lines • 18.4 kB
TypeScript
import { AfterViewInit, ChangeDetectorRef, ElementRef, EventEmitter, NgZone, OnChanges, OnDestroy, SimpleChanges, Signal } from '@angular/core';
import { AbstractControl, ControlValueAccessor, ValidationErrors, Validator } from '@angular/forms';
import { CountryCode } from 'libphonenumber-js';
import { NgxsmkTelInputService } from './ngxsmk-tel-input.service';
import { CountryMap, IntlTelI18n } from './types';
import { PhoneInputState } from './signals';
import { CarrierInfo, FormatSuggestion } from './phone-intelligence.service';
import * as i0 from "@angular/core";
/**
* Interface for custom theme colors.
*/
export interface ColorPalette {
background?: string;
foreground?: string;
border?: string;
borderHover?: string;
ring?: string;
placeholder?: string;
}
/**
* Angular telephone input component with country dropdown, flags, and robust validation/formatting.
* Wraps intl-tel-input for the UI and libphonenumber-js for parsing/validation.
* Implements ControlValueAccessor for seamless integration with Angular Forms.
*
* **Compatibility**: Works with Angular 17+ and supports both:
* - Zone.js (traditional Angular change detection)
* - Zoneless Angular (Angular 18+ without Zone.js)
* - All data binding types (property, event, two-way)
* - Reactive Forms and Template-driven Forms
* - Signals (Angular 16+)
*
* **Mobile Responsive**: Fully optimized for mobile devices with:
* - Touch-friendly tap targets (44x44px minimum)
* - Prevents iOS zoom (16px font size)
* - Responsive dropdown that adapts to viewport
* - Safe area insets support for notched devices
* - Optimized touch interactions
*
* @example
* ```html
* <ngxsmk-tel-input
* formControlName="phone"
* label="Phone Number"
* [initialCountry]="'US'"
* [preferredCountries]="['US','GB']">
* </ngxsmk-tel-input>
* ```
*/
export declare class NgxsmkTelInputComponent implements AfterViewInit, OnChanges, OnDestroy, ControlValueAccessor, Validator {
private readonly zone;
private readonly tel;
private readonly cdr;
inputRef: ElementRef<HTMLInputElement>;
/** Signal-based input: Initial country to select. Use 'auto' for geo-location detection. */
initialCountrySignal: import("@angular/core").InputSignal<CountryCode | "auto">;
/** Signal-based input: Countries to show at the top of the dropdown list. */
preferredCountriesSignal: import("@angular/core").InputSignal<CountryCode[]>;
/** Signal-based input: Restrict selectable countries to this list. */
onlyCountriesSignal: import("@angular/core").InputSignal<CountryCode[] | undefined>;
/** Signal-based input: When true, shows dial code outside the input field. */
separateDialCodeSignal: import("@angular/core").InputSignal<boolean>;
/** Signal-based input: Enable or disable the country dropdown. */
allowDropdownSignal: import("@angular/core").InputSignal<boolean>;
/** Signal-based input: Display format - 'formatted' => national with spaces; 'digits' => digits only */
nationalDisplaySignal: import("@angular/core").InputSignal<"formatted" | "digits">;
/** Signal-based input: Format timing - 'typing' (live), 'blur', or 'off' */
formatWhenValidSignal: import("@angular/core").InputSignal<"off" | "blur" | "typing">;
/** Signal-based input: Size variant */
sizeSignal: import("@angular/core").InputSignal<"sm" | "md" | "lg">;
/** Signal-based input: Style variant */
variantSignal: import("@angular/core").InputSignal<"outline" | "filled" | "underline">;
/** Signal-based input: Theme preference */
themeSignal: import("@angular/core").InputSignal<"auto" | "light" | "dark">;
/** Signal-based input: Disabled state */
disabledSignal: import("@angular/core").InputSignal<boolean>;
/** Signal-based output: Emitted when country selection changes */
countryChangeSignal: import("@angular/core").OutputEmitterRef<{
iso2: CountryCode;
}>;
/** Signal-based output: Emitted when validity changes */
validityChangeSignal: import("@angular/core").OutputEmitterRef<boolean>;
/** Signal-based output: Emitted on every input change */
inputChangeSignal: import("@angular/core").OutputEmitterRef<{
raw: string;
e164: string | null;
iso2: CountryCode;
}>;
/** Internal state signal for reactive state management */
private stateSignal;
/** Computed signal: Current phone input state */
readonly state: Signal<PhoneInputState>;
/** Computed signal: Formatted display value */
readonly formattedValue: Signal<string>;
/** Computed signal: Validation status */
readonly validationStatus: Signal<{
isValid: boolean;
isInvalid: boolean;
hasErrors: boolean;
errorKeys: string[];
}>;
/** Computed signal: Phone number metadata */
readonly phoneMetadata: Signal<{
countryCode: string;
dialCode: string | null;
nationalNumber: string | null;
internationalFormat: string | null;
}>;
/** Computed signal: Whether the input is valid */
readonly isValid: Signal<boolean>;
/** Computed signal: Whether the input has errors */
readonly hasErrors: Signal<boolean>;
/** Computed signal: Whether to show error message */
readonly showError: Signal<boolean>;
/** Computed signal: Current E.164 value */
readonly e164Value: Signal<string | null>;
/** Computed signal: Current raw input value */
readonly rawValue: Signal<string>;
/** Computed signal: Current country ISO2 code */
readonly currentCountry: Signal<CountryCode>;
/** Initial country to select. Use 'auto' for geo-location detection (defaults to 'US'). */
initialCountry: CountryCode | 'auto';
/** Countries to show at the top of the dropdown list. */
preferredCountries: CountryCode[];
/** Restrict selectable countries to this list. If not provided, all countries are available. */
onlyCountries?: CountryCode[];
/** When true, shows dial code outside the input field (after the flag). */
separateDialCode: boolean;
/** Enable or disable the country dropdown. */
allowDropdown: boolean;
/** 'formatted' => national with spaces; 'digits' => digits only */
nationalDisplay: 'formatted' | 'digits';
/** 'typing' (live), 'blur', or 'off' */
formatWhenValid: 'off' | 'blur' | 'typing';
placeholder?: string;
autocomplete: string;
name?: string;
inputId?: string;
disabled: boolean;
label?: string;
hint?: string;
errorText?: string;
size: 'sm' | 'md' | 'lg';
variant: 'outline' | 'filled' | 'underline';
showClear: boolean;
autoFocus: boolean;
selectOnFocus: boolean;
showErrorWhenTouched: boolean;
dropdownAttachToBody: boolean;
dropdownZIndex: number;
i18n?: IntlTelI18n;
set telI18n(v: IntlTelI18n | undefined);
localizedCountries?: CountryMap;
set telLocalizedCountries(v: CountryMap | undefined);
clearAriaLabel: string;
dir: 'ltr' | 'rtl';
autoPlaceholder: 'off' | 'polite' | 'aggressive';
utilsScript?: string;
customPlaceholder?: (example: string, country: unknown) => string;
digitsOnly: boolean;
lockWhenValid: boolean;
theme: 'light' | 'dark' | 'auto';
/**
* Custom colors for fine-grained theme control.
*/
customColors?: {
light?: ColorPalette;
dark?: ColorPalette;
};
/** Enable carrier and number type detection */
enableIntelligence: boolean;
/** Enable format suggestions */
enableFormatSuggestions: boolean;
countryChange: EventEmitter<{
iso2: CountryCode;
}>;
validityChange: EventEmitter<boolean>;
inputChange: EventEmitter<{
raw: string;
e164: string | null;
iso2: CountryCode;
}>;
intelligenceChange: EventEmitter<CarrierInfo | null>;
formatSuggestion: EventEmitter<FormatSuggestion | null>;
private iti;
private onChange;
private onTouchedCb;
private validatorChange?;
private lastEmittedValid;
private pendingWrite;
private touched;
private isDestroyed;
private eventListeners;
private searchInputCleanupFunctions;
private allowDropdownWasTrue;
private suppressEvents;
private themeObserver;
private globalThemeObserver;
readonly resolvedId: string;
private readonly platformId;
private currentTheme;
private lastThemeConfig;
isRequired: boolean;
private readonly intelligence;
private readonly hostElementRef;
constructor(zone: NgZone | null, tel: NgxsmkTelInputService, cdr: ChangeDetectorRef);
/**
* Helper method to run code inside Angular zone if available, otherwise just run it.
* Works with both Zone.js and zoneless Angular.
*/
private runInZone;
/**
* Helper method to run code outside Angular zone if available, otherwise just run it.
* Works with both Zone.js and zoneless Angular.
*/
private runOutsideZone;
/**
* Helper method for requestAnimationFrame with fallback for older browsers.
*/
private requestAnimationFrame;
/**
* Determines if dropdown should be attached to body.
* On mobile screens (<=768px), returns false to show dropdown inline instead of as modal.
*/
private shouldAttachToBody;
/**
* Angular lifecycle hook called after the view is initialized.
* Initializes the intl-tel-input plugin and sets up event listeners.
*/
ngAfterViewInit(): void;
private initAndWire;
ngOnChanges(changes: SimpleChanges): void;
ngOnDestroy(): void;
/**
* Writes a new value to the form control.
* Called by Angular Forms when the control value changes programmatically.
* @param val - The new value (E.164 format string or null)
*/
writeValue(val: string | null): void;
/**
* Registers a callback function that is called when the control's value changes.
* @param fn - Callback function that receives the new value (string | null)
*/
registerOnChange(fn: (val: string | null) => void): void;
/**
* Registers a callback function that is called when the control is touched.
* @param fn - Callback function to be called on touch
*/
registerOnTouched(fn: () => void): void;
/**
* Sets the disabled state of the control.
* Called by Angular Forms when the control's disabled state changes.
* @param isDisabled - Whether the control should be disabled
*/
setDisabledState(isDisabled: boolean): void;
/**
* Validates the phone number input.
* Returns validation errors if the number is invalid, null if valid.
* @param _ - The form control (unused, but required by Validator interface)
* @returns ValidationErrors object with error keys, or null if valid
*/
validate(control: AbstractControl): ValidationErrors | null;
/**
* Registers a callback function that is called when validator inputs change.
* @param fn - Callback function to be called when validation should be re-run
*/
registerOnValidatorChange(fn: () => void): void;
/**
* Programmatically focuses the input field.
* If selectOnFocus is enabled, also selects all text.
*/
focus(): void;
/**
* Programmatically selects a country.
* @param iso2 - ISO 3166-1 alpha-2 country code (e.g., 'US', 'GB')
*/
selectCountry(iso2: CountryCode): void;
/**
* Clears the input field and refocuses it.
*/
clearInput(): void;
private initIntlTelInput;
private reinitPlugin;
private destroyPlugin;
private bindDomListeners;
onBlur(): void;
onFocus(): void;
private handleInput;
/** Convert any string to digits only (NSN basis). */
private toNSN;
/** Strip exactly one leading trunk '0' from national input. */
private stripLeadingZero;
/** Current country calling code (e.g. "44", "94"). */
private currentDialCode;
/** Convert E.164 (+<cc><nsn>) to NSN (never includes trunk '0'). */
private nsnFromE164;
/** Format NSN for a region (adds spaces but NEVER a trunk '0'). */
private formatNSN;
/** Compose visible value based on settings. */
private displayValue;
/**
* Gets the current raw input value (as displayed, with formatting).
* @returns The current input value as a string
*/
currentRaw(): string;
/**
* Gets the aria-describedby attribute value for accessibility.
* @returns Space-separated list of element IDs that describe this input
*/
getAriaDescribedBy(): string | null;
/**
* Gets the ARIA status message for screen readers.
* @returns Status message describing the current state
*/
getAriaStatusMessage(): string;
private currentIso2;
private setInputValue;
private isCurrentlyValid;
/** Make flag/dropdown non-interactive when disabled */
private applyDisabledUi;
/** Returns true if nsn would be TOO_LONG for the current country. */
private wouldExceedMax;
/** Clean up event listeners to prevent memory leaks */
private cleanupEventListeners;
/** Detect and apply theme based on user preference and system settings */
private detectAndApplyTheme;
/** Apply theme to the component */
private applyTheme;
private applyCustomColors;
/** Apply theme to the dropdown if it exists */
private applyThemeToDropdown;
/**
* Gets the current resolved theme (light or dark).
* @returns The current theme ('light' or 'dark')
*/
getCurrentTheme(): 'light' | 'dark';
/**
* Sets the theme programmatically.
* @param theme - Theme to apply ('light' or 'dark')
*/
setTheme(theme: 'light' | 'dark'): void;
/** Update dropdown theme when it's opened */
private updateDropdownTheme;
/** Setup clear button for search input */
private setupSearchInputClearButton;
/** Clean up search input event listeners to prevent memory leaks */
private cleanupSearchInputListeners;
/** Setup observer to watch for dropdown changes and apply theme */
private setupDropdownThemeObserver;
/** Setup observer to watch for global theme changes on html/body */
private setupGlobalThemeObserver;
static ɵfac: i0.ɵɵFactoryDeclaration<NgxsmkTelInputComponent, [{ optional: true; }, null, null]>;
static ɵcmp: i0.ɵɵComponentDeclaration<NgxsmkTelInputComponent, "ngxsmk-tel-input", never, { "initialCountrySignal": { "alias": "initialCountrySignal"; "required": false; "isSignal": true; }; "preferredCountriesSignal": { "alias": "preferredCountriesSignal"; "required": false; "isSignal": true; }; "onlyCountriesSignal": { "alias": "onlyCountriesSignal"; "required": false; "isSignal": true; }; "separateDialCodeSignal": { "alias": "separateDialCodeSignal"; "required": false; "isSignal": true; }; "allowDropdownSignal": { "alias": "allowDropdownSignal"; "required": false; "isSignal": true; }; "nationalDisplaySignal": { "alias": "nationalDisplaySignal"; "required": false; "isSignal": true; }; "formatWhenValidSignal": { "alias": "formatWhenValidSignal"; "required": false; "isSignal": true; }; "sizeSignal": { "alias": "sizeSignal"; "required": false; "isSignal": true; }; "variantSignal": { "alias": "variantSignal"; "required": false; "isSignal": true; }; "themeSignal": { "alias": "themeSignal"; "required": false; "isSignal": true; }; "disabledSignal": { "alias": "disabledSignal"; "required": false; "isSignal": true; }; "initialCountry": { "alias": "initialCountry"; "required": false; }; "preferredCountries": { "alias": "preferredCountries"; "required": false; }; "onlyCountries": { "alias": "onlyCountries"; "required": false; }; "separateDialCode": { "alias": "separateDialCode"; "required": false; }; "allowDropdown": { "alias": "allowDropdown"; "required": false; }; "nationalDisplay": { "alias": "nationalDisplay"; "required": false; }; "formatWhenValid": { "alias": "formatWhenValid"; "required": false; }; "placeholder": { "alias": "placeholder"; "required": false; }; "autocomplete": { "alias": "autocomplete"; "required": false; }; "name": { "alias": "name"; "required": false; }; "inputId": { "alias": "inputId"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "label": { "alias": "label"; "required": false; }; "hint": { "alias": "hint"; "required": false; }; "errorText": { "alias": "errorText"; "required": false; }; "size": { "alias": "size"; "required": false; }; "variant": { "alias": "variant"; "required": false; }; "showClear": { "alias": "showClear"; "required": false; }; "autoFocus": { "alias": "autoFocus"; "required": false; }; "selectOnFocus": { "alias": "selectOnFocus"; "required": false; }; "showErrorWhenTouched": { "alias": "showErrorWhenTouched"; "required": false; }; "dropdownAttachToBody": { "alias": "dropdownAttachToBody"; "required": false; }; "dropdownZIndex": { "alias": "dropdownZIndex"; "required": false; }; "i18n": { "alias": "i18n"; "required": false; }; "telI18n": { "alias": "telI18n"; "required": false; }; "localizedCountries": { "alias": "localizedCountries"; "required": false; }; "telLocalizedCountries": { "alias": "telLocalizedCountries"; "required": false; }; "clearAriaLabel": { "alias": "clearAriaLabel"; "required": false; }; "dir": { "alias": "dir"; "required": false; }; "autoPlaceholder": { "alias": "autoPlaceholder"; "required": false; }; "utilsScript": { "alias": "utilsScript"; "required": false; }; "customPlaceholder": { "alias": "customPlaceholder"; "required": false; }; "digitsOnly": { "alias": "digitsOnly"; "required": false; }; "lockWhenValid": { "alias": "lockWhenValid"; "required": false; }; "theme": { "alias": "theme"; "required": false; }; "customColors": { "alias": "customColors"; "required": false; }; "enableIntelligence": { "alias": "enableIntelligence"; "required": false; }; "enableFormatSuggestions": { "alias": "enableFormatSuggestions"; "required": false; }; }, { "countryChangeSignal": "countryChangeSignal"; "validityChangeSignal": "validityChangeSignal"; "inputChangeSignal": "inputChangeSignal"; "countryChange": "countryChange"; "validityChange": "validityChange"; "inputChange": "inputChange"; "intelligenceChange": "intelligenceChange"; "formatSuggestion": "formatSuggestion"; }, never, never, true, never>;
}
//# sourceMappingURL=ngxsmk-tel-input.component.d.ts.map