UNPKG

ngxsmk-tel-input

Version:

Angular international telephone input (intl-tel-input UI + libphonenumber-js validation). ControlValueAccessor. SSR-safe.

354 lines 18.4 kB
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