ngxsmk-tel-input
Version:
Angular international telephone input (intl-tel-input UI + libphonenumber-js validation). ControlValueAccessor. SSR-safe.
3,331 lines • 179 kB
JavaScript
import * as i0 from '@angular/core';
import { signal, computed, Injectable, input, output, EventEmitter, inject, PLATFORM_ID, ElementRef, effect, forwardRef, Output, Input, ViewChild, Optional, ChangeDetectionStrategy, Component, InjectionToken, NgModule, Inject } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
import { parsePhoneNumberFromString, getNumberType, AsYouType, validatePhoneNumberLength } from 'libphonenumber-js';
import { BehaviorSubject, from } from 'rxjs';
import * as i1 from '@angular/common/http';
import { HttpHeaders } from '@angular/common/http';
import { map, catchError } from 'rxjs/operators';
/**
* Signal-based API utilities for ngxsmk-tel-input.
* Provides reactive signal-based interfaces for modern Angular applications.
*/
/**
* Creates a default phone input state signal.
*/
function createPhoneInputState() {
return signal({
raw: '',
e164: null,
iso2: 'US',
isValid: false,
touched: false,
errors: null
});
}
/**
* Computed signal for formatted phone number display.
*/
function createFormattedValueSignal(state, nationalDisplay) {
return computed(() => {
const current = state();
if (!current.raw)
return '';
if (nationalDisplay() === 'formatted' && current.e164) {
// Return formatted national format
return current.raw;
}
return current.raw;
});
}
/**
* Computed signal for validation status.
*/
function createValidationStatusSignal(state) {
return computed(() => {
const current = state();
const errors = current.errors || {};
const errorKeys = Object.keys(errors);
return {
isValid: current.isValid,
isInvalid: !current.isValid && current.touched,
hasErrors: errorKeys.length > 0,
errorKeys
};
});
}
/**
* Computed signal for phone number metadata.
*/
function createPhoneMetadataSignal(state) {
return computed(() => {
const current = state();
if (!current.e164) {
return {
countryCode: current.iso2,
dialCode: null,
nationalNumber: null,
internationalFormat: null
};
}
// Extract dial code from E.164
const dialCodeMatch = current.e164.match(/^\+\d{1,3}/);
const dialCode = dialCodeMatch ? dialCodeMatch[0].slice(1) : null;
return {
countryCode: current.iso2,
dialCode,
nationalNumber: current.raw,
internationalFormat: current.e164
};
});
}
/**
* Phone intelligence service for advanced features
* Provides carrier detection, number type detection, and smart formatting
*/
class PhoneIntelligenceService {
/**
* Detect carrier and number type
*/
detectCarrierAndType(phoneNumber, country) {
try {
const parsed = parsePhoneNumberFromString(phoneNumber, country);
if (!parsed || !parsed.isValid()) {
return null;
}
// getNumberType can accept PhoneNumber object directly (it's compatible with ParsedNumber)
// Use type assertion to work around TypeScript type mismatch
const numberType = getNumberType(parsed) || 'UNKNOWN';
return {
country: parsed.country,
type: numberType,
isMobile: numberType === 'MOBILE' || numberType === 'FIXED_LINE_OR_MOBILE',
isLandline: numberType === 'FIXED_LINE' || numberType === 'FIXED_LINE_OR_MOBILE',
isTollFree: numberType === 'TOLL_FREE',
isPremiumRate: numberType === 'PREMIUM_RATE',
isVoip: numberType === 'VOIP'
};
}
catch {
return null;
}
}
/**
* Get number type description
*/
getNumberTypeDescription(type) {
const descriptions = {
'MOBILE': 'Mobile phone',
'FIXED_LINE': 'Landline',
'FIXED_LINE_OR_MOBILE': 'Mobile or landline',
'TOLL_FREE': 'Toll-free number',
'PREMIUM_RATE': 'Premium rate number',
'VOIP': 'VoIP number',
'UNKNOWN': 'Unknown type'
};
return descriptions[type] || 'Unknown';
}
/**
* Suggest format corrections
*/
suggestFormatCorrection(input, country) {
if (!input || input.length < 3) {
return null;
}
const digits = input.replace(/\D/g, '');
if (digits.length < 3) {
return null;
}
try {
// Try to parse as-is
let parsed = parsePhoneNumberFromString(input, country);
if (parsed && parsed.isValid()) {
return null; // Already valid
}
// Common corrections
const suggestions = [];
// Remove leading zeros
if (digits.startsWith('0') && digits.length > 1) {
const withoutZero = digits.substring(1);
const testParsed = parsePhoneNumberFromString(withoutZero, country);
if (testParsed && testParsed.isValid()) {
suggestions.push({
original: input,
suggested: testParsed.formatNational(),
confidence: 0.8,
reason: 'Removed leading zero'
});
}
}
// Add country code if missing
if (!input.startsWith('+') && !input.startsWith('00')) {
const withPlus = `+${digits}`;
const testParsed = parsePhoneNumberFromString(withPlus);
if (testParsed && testParsed.isValid()) {
suggestions.push({
original: input,
suggested: testParsed.formatNational(),
confidence: 0.7,
reason: 'Added country code'
});
}
}
// Fix common formatting issues
const formatted = this.fixCommonFormattingIssues(input, country);
if (formatted && formatted !== input) {
const testParsed = parsePhoneNumberFromString(formatted, country);
if (testParsed && testParsed.isValid()) {
suggestions.push({
original: input,
suggested: testParsed.formatNational(),
confidence: 0.6,
reason: 'Fixed formatting'
});
}
}
// Return highest confidence suggestion
if (suggestions.length > 0) {
return suggestions.sort((a, b) => b.confidence - a.confidence)[0];
}
return null;
}
catch {
return null;
}
}
/**
* Fix common formatting issues
*/
fixCommonFormattingIssues(input, country) {
let fixed = input;
// Remove spaces and dashes, keep only digits and +
fixed = fixed.replace(/[^\d+]/g, '');
// Remove multiple plus signs
if (fixed.indexOf('+') !== fixed.lastIndexOf('+')) {
fixed = '+' + fixed.replace(/\+/g, '');
}
// Remove leading zeros after country code
if (fixed.startsWith('+')) {
const parts = fixed.split('+');
if (parts.length > 1) {
const numberPart = parts[1].replace(/^0+/, '');
fixed = '+' + numberPart;
}
}
return fixed;
}
/**
* Get timezone for phone number
*/
getTimezone(phoneNumber, country) {
try {
const parsed = parsePhoneNumberFromString(phoneNumber, country);
if (!parsed) {
return null;
}
// Basic timezone mapping (simplified)
const timezoneMap = {
'US': 'America/New_York',
'GB': 'Europe/London',
'AU': 'Australia/Sydney',
'CA': 'America/Toronto',
'DE': 'Europe/Berlin',
'FR': 'Europe/Paris',
'JP': 'Asia/Tokyo',
'CN': 'Asia/Shanghai',
'IN': 'Asia/Kolkata',
'BR': 'America/Sao_Paulo',
'MX': 'America/Mexico_City',
'RU': 'Europe/Moscow'
};
return timezoneMap[parsed.country || country] || null;
}
catch {
return null;
}
}
/**
* Check if number is likely spam (basic heuristic)
*/
isLikelySpam(phoneNumber, country) {
try {
const parsed = parsePhoneNumberFromString(phoneNumber, country);
if (!parsed) {
return false;
}
const nationalNumber = parsed.nationalNumber;
// Check for suspicious patterns
const suspiciousPatterns = [
/^(\d)\1{6,}$/, // Same digit repeated 7+ times
/^123456789/, // Sequential
/^987654321/, // Reverse sequential
/^0000000/, // All zeros
];
return suspiciousPatterns.some(pattern => pattern.test(nationalNumber));
}
catch {
return false;
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PhoneIntelligenceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PhoneIntelligenceService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PhoneIntelligenceService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
/**
* Service for parsing and validating phone numbers using libphonenumber-js.
* Provides caching for improved performance and enhanced validation features.
*
* @example
* ```typescript
* constructor(private telService: NgxsmkTelInputService) {}
*
* validatePhone(input: string, country: CountryCode) {
* const result = this.telService.parse(input, country);
* return result.isValid;
* }
* ```
*/
class NgxsmkTelInputService {
constructor() {
this.parseCache = new Map();
this.parseWithInvalidCache = new Map();
this.validationCache = new Map();
this.CACHE_SIZE_LIMIT = 1000;
}
/**
* Parses a phone number string and returns formatted results.
* Results are cached for performance.
*
* @param input - The phone number string to parse (can include formatting)
* @param iso2 - ISO 3166-1 alpha-2 country code (e.g., 'US', 'GB')
* @returns ParseResult with E.164 format, national format, and validity status
*
* @example
* ```typescript
* const result = service.parse('202-555-1234', 'US');
* // result.e164 = '+12025551234'
* // result.national = '(202) 555-1234'
* // result.isValid = true
* ```
*/
parse(input, iso2) {
const cacheKey = `${input || ''}|${iso2}`;
if (this.parseCache.has(cacheKey)) {
return this.parseCache.get(cacheKey);
}
try {
const phone = parsePhoneNumberFromString(input || '', iso2);
if (!phone) {
const result = { e164: null, national: null, isValid: false };
this.setCacheValue(this.parseCache, cacheKey, result);
return result;
}
const isValid = phone.isValid();
const result = {
e164: isValid ? phone.number : null,
national: phone.formatNational(),
isValid
};
this.setCacheValue(this.parseCache, cacheKey, result);
return result;
}
catch {
const result = { e164: null, national: null, isValid: false };
this.setCacheValue(this.parseCache, cacheKey, result);
return result;
}
}
/**
* Validates whether a phone number string is valid for the given country.
* Results are cached for performance.
*
* @param input - The phone number string to validate
* @param iso2 - ISO 3166-1 alpha-2 country code
* @returns true if the phone number is valid, false otherwise
*
* @example
* ```typescript
* const isValid = service.isValid('202-555-1234', 'US'); // true
* const isInvalid = service.isValid('123', 'US'); // false
* ```
*/
isValid(input, iso2) {
const cacheKey = `${input || ''}|${iso2}`;
if (this.validationCache.has(cacheKey)) {
return this.validationCache.get(cacheKey);
}
try {
const phone = parsePhoneNumberFromString(input || '', iso2);
const result = !!phone && phone.isValid();
this.setCacheValue(this.validationCache, cacheKey, result);
return result;
}
catch {
this.setCacheValue(this.validationCache, cacheKey, false);
return false;
}
}
/**
* Sets a value in the cache, implementing LRU eviction when cache is full.
* @param cache - The cache Map to update
* @param key - The cache key
* @param value - The value to cache
*/
setCacheValue(cache, key, value) {
if (cache.size >= this.CACHE_SIZE_LIMIT) {
const firstKey = cache.keys().next().value;
if (firstKey)
cache.delete(firstKey);
}
cache.set(key, value);
}
/**
* Clears all caches. Useful for memory management or testing.
*/
clearCache() {
this.parseCache.clear();
this.parseWithInvalidCache.clear();
this.validationCache.clear();
}
/**
* Checks if the input appears to be an international number with an invalid country code.
* This helps detect cases like "1123456789" where "11" is not a valid country code.
*
* @param input - The phone number string to check
* @returns true if the input appears to be an invalid international number
* @private
*/
isInvalidInternationalNumber(input) {
if (!input || input.length < 3)
return false;
const digits = input.replace(/\D/g, '');
if (digits.length < 3)
return false;
for (let i = 1; i <= 3 && i <= digits.length; i++) {
const potentialCountryCode = digits.substring(0, i);
const remainingDigits = digits.substring(i);
if (remainingDigits.length >= 3) {
try {
const internationalNumber = `+${potentialCountryCode}${remainingDigits}`;
const phone = parsePhoneNumberFromString(internationalNumber);
if (!phone) {
if (input.startsWith('+') || (potentialCountryCode.length >= 1 && potentialCountryCode.length <= 3)) {
return true;
}
}
}
catch {
return true;
}
}
}
return false;
}
/**
* Enhanced parse method that detects invalid international numbers.
* This is useful for providing better error messages to users.
* Results are cached for performance.
*
* @param input - The phone number string to parse
* @param iso2 - ISO 3166-1 alpha-2 country code
* @returns ParseWithInvalidResult with additional invalid country code detection
*
* @example
* ```typescript
* const result = service.parseWithInvalidDetection('1123456789', 'US');
* // result.isInvalidInternational = true (because "11" is not a valid country code)
* // result.isValid = false
* ```
*/
parseWithInvalidDetection(input, iso2) {
const cacheKey = `${input || ''}|${iso2}`;
if (this.parseWithInvalidCache.has(cacheKey)) {
return this.parseWithInvalidCache.get(cacheKey);
}
try {
const phone = parsePhoneNumberFromString(input || '', iso2);
const isInvalidInternational = this.isInvalidInternationalNumber(input);
if (!phone) {
const result = {
e164: null,
national: null,
isValid: false,
isInvalidInternational
};
this.setCacheValue(this.parseWithInvalidCache, cacheKey, result);
return result;
}
const isValid = phone.isValid();
const result = {
e164: isValid ? phone.number : null,
national: phone.formatNational(),
isValid,
isInvalidInternational
};
this.setCacheValue(this.parseWithInvalidCache, cacheKey, result);
return result;
}
catch {
const isInvalidInternational = this.isInvalidInternationalNumber(input);
const result = {
e164: null,
national: null,
isValid: false,
isInvalidInternational
};
this.setCacheValue(this.parseWithInvalidCache, cacheKey, result);
return result;
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgxsmkTelInputService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgxsmkTelInputService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgxsmkTelInputService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
/**
* 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>
* ```
*/
class NgxsmkTelInputComponent {
set telI18n(v) { this.i18n = v; }
set telLocalizedCountries(v) { this.localizedCountries = v; }
constructor(zone, tel, cdr) {
this.zone = zone;
this.tel = tel;
this.cdr = cdr;
// ========== Signal-based API (Angular 17+) ==========
/** Signal-based input: Initial country to select. Use 'auto' for geo-location detection. */
this.initialCountrySignal = input('US');
/** Signal-based input: Countries to show at the top of the dropdown list. */
this.preferredCountriesSignal = input(['US', 'GB']);
/** Signal-based input: Restrict selectable countries to this list. */
this.onlyCountriesSignal = input(undefined);
/** Signal-based input: When true, shows dial code outside the input field. */
this.separateDialCodeSignal = input(true);
/** Signal-based input: Enable or disable the country dropdown. */
this.allowDropdownSignal = input(true);
/** Signal-based input: Display format - 'formatted' => national with spaces; 'digits' => digits only */
this.nationalDisplaySignal = input('formatted');
/** Signal-based input: Format timing - 'typing' (live), 'blur', or 'off' */
this.formatWhenValidSignal = input('typing');
/** Signal-based input: Size variant */
this.sizeSignal = input('md');
/** Signal-based input: Style variant */
this.variantSignal = input('outline');
/** Signal-based input: Theme preference */
this.themeSignal = input('auto');
/** Signal-based input: Disabled state */
this.disabledSignal = input(false);
/** Signal-based output: Emitted when country selection changes */
this.countryChangeSignal = output();
/** Signal-based output: Emitted when validity changes */
this.validityChangeSignal = output();
/** Signal-based output: Emitted on every input change */
this.inputChangeSignal = output();
/** Internal state signal for reactive state management */
this.stateSignal = createPhoneInputState();
/** Computed signal: Current phone input state */
this.state = this.stateSignal.asReadonly();
/** Computed signal: Formatted display value */
this.formattedValue = createFormattedValueSignal(this.stateSignal, this.nationalDisplaySignal);
/** Computed signal: Validation status */
this.validationStatus = createValidationStatusSignal(this.stateSignal);
/** Computed signal: Phone number metadata */
this.phoneMetadata = createPhoneMetadataSignal(this.stateSignal);
/** Computed signal: Whether the input is valid */
this.isValid = computed(() => this.stateSignal().isValid);
/** Computed signal: Whether the input has errors */
this.hasErrors = computed(() => {
const errors = this.stateSignal().errors;
return errors !== null && Object.keys(errors).length > 0;
});
/** Computed signal: Whether to show error message */
this.showError = computed(() => {
if (this.isDestroyed)
return false;
const state = this.stateSignal();
const hasErrors = state.errors !== null && Object.keys(state.errors).length > 0;
return this.showErrorWhenTouched ? (state.touched && hasErrors) : hasErrors;
});
/** Computed signal: Current E.164 value */
this.e164Value = computed(() => this.stateSignal().e164);
/** Computed signal: Current raw input value */
this.rawValue = computed(() => this.stateSignal().raw);
/** Computed signal: Current country ISO2 code */
this.currentCountry = computed(() => this.stateSignal().iso2);
// ========== Traditional @Input/@Output API (backward compatibility) ==========
/** Initial country to select. Use 'auto' for geo-location detection (defaults to 'US'). */
this.initialCountry = 'US';
/** Countries to show at the top of the dropdown list. */
this.preferredCountries = ['US', 'GB'];
/** When true, shows dial code outside the input field (after the flag). */
this.separateDialCode = true;
/** Enable or disable the country dropdown. */
this.allowDropdown = true;
/** 'formatted' => national with spaces; 'digits' => digits only */
this.nationalDisplay = 'formatted';
/** 'typing' (live), 'blur', or 'off' */
this.formatWhenValid = 'typing';
this.autocomplete = 'tel';
this.disabled = false;
this.size = 'md';
this.variant = 'outline';
this.showClear = true;
this.autoFocus = false;
this.selectOnFocus = false;
this.showErrorWhenTouched = true;
this.dropdownAttachToBody = true;
this.dropdownZIndex = 2000;
this.clearAriaLabel = 'Clear phone number';
this.dir = 'ltr';
this.autoPlaceholder = 'off';
this.digitsOnly = true;
this.lockWhenValid = true;
this.theme = 'auto';
/** Enable carrier and number type detection */
this.enableIntelligence = false;
/** Enable format suggestions */
this.enableFormatSuggestions = false;
this.countryChange = new EventEmitter();
this.validityChange = new EventEmitter();
this.inputChange = new EventEmitter();
this.intelligenceChange = new EventEmitter();
this.formatSuggestion = new EventEmitter();
this.iti = null;
this.onChange = () => { };
this.onTouchedCb = () => { };
this.lastEmittedValid = false;
this.pendingWrite = null;
this.touched = false;
this.isDestroyed = false;
this.eventListeners = [];
this.searchInputCleanupFunctions = [];
this.allowDropdownWasTrue = false;
this.suppressEvents = false;
this.themeObserver = null;
this.globalThemeObserver = null;
this.resolvedId = this.inputId || ('tel-' + Math.random().toString(36).slice(2));
this.platformId = inject(PLATFORM_ID);
this.currentTheme = 'light';
this.lastThemeConfig = null;
this.isRequired = false;
this.intelligence = inject(PhoneIntelligenceService, { optional: true });
this.hostElementRef = inject(ElementRef);
// Watch for theme signal changes
effect(() => {
if (!isPlatformBrowser(this.platformId) || this.isDestroyed)
return;
// Check both signal and traditional input
const theme = this.themeSignal() || this.theme;
if (theme !== undefined && theme !== this.lastThemeConfig) {
this.lastThemeConfig = theme;
// Apply theme immediately (works even before plugin is initialized)
this.detectAndApplyTheme();
}
});
}
/**
* Helper method to run code inside Angular zone if available, otherwise just run it.
* Works with both Zone.js and zoneless Angular.
*/
runInZone(fn) {
if (this.zone) {
this.zone.run(fn);
}
else {
fn();
this.cdr.markForCheck();
}
}
/**
* Helper method to run code outside Angular zone if available, otherwise just run it.
* Works with both Zone.js and zoneless Angular.
*/
runOutsideZone(fn) {
if (this.zone) {
this.zone.runOutsideAngular(fn);
}
else {
fn();
}
}
/**
* Helper method for requestAnimationFrame with fallback for older browsers.
*/
requestAnimationFrame(fn) {
if (typeof window !== 'undefined') {
const raf = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame ||
((callback) => setTimeout(callback, 16));
raf(fn);
}
else {
fn();
}
}
/**
* Determines if dropdown should be attached to body.
* On mobile screens (<=768px), returns false to show dropdown inline instead of as modal.
*/
shouldAttachToBody() {
if (!this.dropdownAttachToBody)
return false;
if (typeof window === 'undefined')
return false;
// On mobile, don't attach to body to avoid modal popup behavior
// Use fallback for older browsers
const width = window.innerWidth || window.clientWidth || 1024;
return width > 768;
}
/**
* Angular lifecycle hook called after the view is initialized.
* Initializes the intl-tel-input plugin and sets up event listeners.
*/
ngAfterViewInit() {
if (!isPlatformBrowser(this.platformId) || this.isDestroyed)
return;
// Ensure theme is applied on initial render
// Use setTimeout to ensure host element is fully available
setTimeout(() => {
if (!this.isDestroyed) {
this.detectAndApplyTheme();
}
}, 0);
this.setupDropdownThemeObserver();
this.setupGlobalThemeObserver();
void this.initAndWire();
}
async initAndWire() {
if (this.isDestroyed)
return;
await this.initIntlTelInput();
if (this.isDestroyed)
return;
this.bindDomListeners();
if (this.pendingWrite !== null && !this.isDestroyed) {
const v = this.pendingWrite;
this.pendingWrite = null;
this.writeValue(v);
}
if (this.autoFocus && !this.isDestroyed) {
this.requestAnimationFrame(() => {
if (!this.isDestroyed)
this.focus();
});
}
}
ngOnChanges(changes) {
if (!isPlatformBrowser(this.platformId) || this.isDestroyed)
return;
// Check for signal changes or traditional input changes
const configChanged = [
'initialCountry', 'preferredCountries', 'onlyCountries',
'separateDialCode', 'allowDropdown',
'i18n', 'localizedCountries', 'dir',
'autoPlaceholder', 'utilsScript', 'customPlaceholder'
].some(k => k in changes && !changes[k]?.firstChange);
// Also check if any signal inputs have changed (signals trigger change detection automatically)
if (configChanged && this.iti && !this.isDestroyed) {
this.reinitPlugin();
this.validatorChange?.();
}
// Handle theme changes from traditional @Input
if ('theme' in changes && !changes['theme'].firstChange) {
const theme = this.theme;
if (theme !== this.lastThemeConfig) {
this.lastThemeConfig = theme;
this.detectAndApplyTheme();
// Mark for check to ensure view updates with OnPush
this.cdr.markForCheck();
}
}
}
ngOnDestroy() {
this.isDestroyed = true;
this.destroyPlugin();
this.cleanupEventListeners();
this.cleanupSearchInputListeners();
if (this.themeObserver) {
this.themeObserver.disconnect();
this.themeObserver = null;
}
if (this.globalThemeObserver) {
this.globalThemeObserver.disconnect();
this.globalThemeObserver = null;
}
}
/**
* 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) {
if (!this.inputRef || this.isDestroyed)
return;
if (!this.iti) {
this.pendingWrite = val ?? '';
return;
}
// Get current state for comparison (will be checked after setNumber with correct country)
const currentE164 = this.stateSignal().e164;
this.suppressEvents = true;
try {
// Call setNumber() FIRST to let intl-tel-input detect and set the correct country
// This is critical when the incoming value has a different country code
this.iti.setNumber(val || '');
// Now get the country code AFTER setNumber has potentially changed it
const iso2 = this.currentIso2();
// Parse with the correct country code
const parsed = this.tel.parseWithInvalidDetection(val ?? '', iso2);
const incomingE164 = parsed.e164;
// Avoid unnecessary updates if E.164 value hasn't changed
if (currentE164 === incomingE164) {
// Even if E.164 is the same, update if the display value would be different
// (e.g., user changed format settings or country changed)
const currentRaw = this.stateSignal().raw;
const currentIso2 = this.stateSignal().iso2;
// If country changed, we need to update
if (currentIso2 !== iso2) {
// Country changed, proceed with update
}
else {
// Same country, check if display value is different
const nsn = incomingE164
? this.nsnFromE164(incomingE164, iso2)
: this.stripLeadingZero(this.toNSN(parsed.national ?? (val ?? '')));
const display = this.displayValue(nsn, iso2);
if (currentRaw === display) {
return; // Nothing to update
}
}
}
const nsn = incomingE164
? this.nsnFromE164(incomingE164, iso2)
: this.stripLeadingZero(this.toNSN(parsed.national ?? (val ?? '')));
const display = this.displayValue(nsn, iso2);
this.setInputValue(display);
// Update state signal without triggering Angular Forms onChange
// (writeValue is called BY Angular Forms, so we shouldn't notify it back)
this.stateSignal.update(state => ({
...state,
raw: display,
e164: incomingE164,
iso2,
isValid: parsed.isValid && !parsed.isInvalidInternational,
errors: (parsed.isValid && !parsed.isInvalidInternational) ? null : (parsed.isInvalidInternational ? { phoneInvalidCountryCode: true, phoneInvalid: false } : { phoneInvalid: true, phoneInvalidCountryCode: false })
}));
// Emit inputChange for external listeners (but NOT onChange to avoid loop)
this.runInZone(() => {
this.inputChange.emit({ raw: display, e164: incomingE164, iso2 });
this.inputChangeSignal.emit({ raw: display, e164: incomingE164, iso2 });
});
// Mark for check to ensure view updates (works in both zone and zoneless)
this.cdr.markForCheck();
}
finally {
this.suppressEvents = false;
}
}
/**
* 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) { this.onChange = fn; }
/**
* Registers a callback function that is called when the control is touched.
* @param fn - Callback function to be called on touch
*/
registerOnTouched(fn) { this.onTouchedCb = fn; }
/**
* 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) {
if (this.isDestroyed)
return;
this.disabled = isDisabled;
if (this.inputRef)
this.inputRef.nativeElement.disabled = isDisabled;
if (this.iti) {
if (isDisabled && this.allowDropdown) {
this.allowDropdownWasTrue = true;
this.allowDropdown = false;
this.reinitPlugin(); // closes popup & removes handlers
}
else if (!isDisabled && this.allowDropdownWasTrue) {
this.allowDropdown = true;
this.allowDropdownWasTrue = false;
this.reinitPlugin();
}
else {
this.applyDisabledUi(isDisabled);
}
}
else {
this.applyDisabledUi(isDisabled);
}
}
/**
* 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) {
if (this.isDestroyed)
return null;
// Update isRequired based on control validators
// Check if control has required validator by testing with null value
try {
if (control.validator) {
const testControl = { value: null };
const errors = control.validator(testControl);
this.isRequired = !!(errors && errors['required']);
}
else {
this.isRequired = false;
}
}
catch (e) {
// Fallback to not required if validator check fails
this.isRequired = false;
}
const raw = this.currentRaw();
if (!raw)
return null;
const parsed = this.tel.parseWithInvalidDetection(raw, this.currentIso2());
const valid = parsed.isValid && !parsed.isInvalidInternational;
if (valid !== this.lastEmittedValid) {
this.lastEmittedValid = valid;
// Update state signal
this.stateSignal.update(state => ({
...state,
isValid: valid,
errors: valid ? null : (parsed.isInvalidInternational ? { phoneInvalidCountryCode: true, phoneInvalid: false } : { phoneInvalid: true, phoneInvalidCountryCode: false })
}));
// Emit both traditional and signal-based outputs
this.validityChange.emit(valid);
this.validityChangeSignal.emit(valid);
// Mark for check when validity changes
this.cdr.markForCheck();
}
if (!valid) {
if (parsed.isInvalidInternational) {
return { phoneInvalidCountryCode: true };
}
return { phoneInvalid: true };
}
return 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) { this.validatorChange = fn; }
/**
* Programmatically focuses the input field.
* If selectOnFocus is enabled, also selects all text.
*/
focus() {
if (this.isDestroyed || !this.inputRef)
return;
this.inputRef.nativeElement.focus();
if (this.selectOnFocus) {
const el = this.inputRef.nativeElement;
this.requestAnimationFrame(() => {
if (!this.isDestroyed) {
try {
el.setSelectionRange(0, el.value.length);
}
catch (e) {
// Fallback for browsers that don't support setSelectionRange
if (el.select) {
el.select();
}
}
}
});
}
}
/**
* Programmatically selects a country.
* @param iso2 - ISO 3166-1 alpha-2 country code (e.g., 'US', 'GB')
*/
selectCountry(iso2) {
if (this.iti && !this.isDestroyed) {
this.iti.setCountry(iso2.toLowerCase());
this.handleInput();
}
}
/**
* Clears the input field and refocuses it.
*/
clearInput() {
if (this.isDestroyed)
return;
this.setInputValue('');
this.handleInput();
this.inputRef.nativeElement.focus();
}
async initIntlTelInput() {
if (this.isDestroyed)
return;
const [{ default: intlTelInput }] = await Promise.all([import('intl-tel-input')]);
const toLowerKeys = (m) => {
if (!m)
return undefined;
const out = {};
for (const k in m)
if (Object.prototype.hasOwnProperty.call(m, k)) {
const v = m[k];
if (v != null)
out[k.toLowerCase()] = v;
}
return out;
};
// Use signal values if available, fallback to traditional inputs
const initialCountry = this.initialCountrySignal() || this.initialCountry;
const preferredCountries = this.preferredCountriesSignal() || this.preferredCountries;
const onlyCountries = this.onlyCountriesSignal() ?? this.onlyCountries;
const allowDropdown = this.allowDropdownSignal() ?? this.allowDropdown;
const separateDialCode = this.separateDialCodeSignal() ?? this.separateDialCode;
const config = {
initialCountry: initialCountry === 'auto' ? 'auto' : (initialCountry?.toLowerCase() || 'us'),
preferredCountries: (preferredCountries ?? []).map(c => c.toLowerCase()),
onlyCountries: (onlyCountries ?? []).map(c => c.toLowerCase()),
nationalMode: true,
allowDropdown,
separateDialCode,
geoIpLookup: (cb) => cb('us'),
autoPlaceholder: this.autoPlaceholder,
utilsScript: this.utilsScript,
customPlaceholder: this.customPlaceholder,
i18n: this.i18n,
localizedCountries: toLowerKeys(this.localizedCountries),
// On mobile, don't attach to body to avoid modal behavior
dropdownContainer: this.shouldAttachToBody() ? (typeof document !== 'undefined' ? document.body : undefined) : undefined
};
this.runOutsideZone(() => {
if (!this.isDestroyed) {
this.iti = intlTelInput(this.inputRef.nativeElement, config);
}
});
if (!this.isDestroyed) {
this.inputRef.nativeElement.style.setProperty('--tel-dd-z', String(this.dropdownZIndex));
this.applyDisabledUi(this.disabled);
}
}
async reinitPlugin() {
if (this.isDestroyed)
return;
const prevIso2 = (this.iti?.getSelectedCountryData?.().iso2 || this.initialCountry || 'US').toString().toLowerCase();
const prevValue = this.currentRaw();
this.destroyPlugin();
await this.initIntlTelInput();
this.bindDomListeners();
if (!this.isDestroyed) {
try {
this.iti?.setCountry(prevIso2);
}
catch (e) {
// Ignore errors when setting country (e.g., invalid country code)
}
if (prevValue) {
this.setInputValue(prevValue);
this.handleInput();
}
this.applyDisabledUi(this.disabled);
}
}
destroyPlugin() {
if (this.iti) {
this.iti.destroy();
this.iti = null;
}
}
// ---------- Input listeners ----------
bindDomListeners() {
if (this.isDestroyed || !this.inputRef)
return;
const el = this.inputRef.nativeElement;
this.runOutsideZone(() => {
const beforeInputHandler = (ev) => {
if (this.isDestroyed || !this.digitsOnly)
return;
const inputEvent = ev;
const data = inputEvent.data;
if (this.lockWhenValid && this.isCurrentlyValid()) {
const selStart = el.selectionStart ?? 0;
const selEnd = el.selectionEnd ?? 0;
const selCollapsed = selStart === selEnd;
const isDigit = !!data && inputEvent.inputType === 'insertText' && data >= '0' && data <= '9';
if (selCollapsed && isDigit) {
ev.preventDefault();
return;
}
}
if (!data || inputEvent.inputType !== 'insertText')
return;
const isDigit = data >= '0' && data <= '9';
if (!isDigit) {
ev.preventDefault();
return;
}
const start = el.selectionStart ?? el.value.length;
const end = el.selectionEnd ?? el.value.length;
const prospective = el.value.slice(0, start) + data + el.value.slice(end);
const nsn = this.stripLeadingZero(this.toNSN(prospective));
const iso2 = this.currentIso2();
if (this.wouldExceedMax(nsn, iso2)) {
ev.preventDefault();
return;
}
};
const pasteHandler = (e) => {
if (this.isDestroyed)
return;
const clipboardEvent = e;
// Support both standard ClipboardEvent and IE11 fallback
const clipboardData = clipboardEvent.clipboardData ||
(window.clipboardData) ||
null;
let text = '';
if (clipboardData) {
try {
text = clipboardData.getData('text') || '';
}
catch (err) {
// Fallback for browsers without clipboardData support
text = '';
}
}
e.preventDefault();
const iso2 = this.currentIso2();
let digits = this.stripLeadingZero(this.toNSN(text));
while (this.wouldExceedMax(digits, iso2)) {
digits = digits.slice(0, -1);
if (!digits)
break;
}
const start = el.selectionStart ?? el.value.length;
const end = el.selectionEnd ?? el.value.length;
// Use setRangeText with fallback for older browsers
if (el.setRangeText) {
try {
el.setRangeText(digits, start, end, 'end');
}
catch (e) {
// Fallback for browsers without setRangeText support
el.value = el.value.substring(0, start) + digits + el.value.substring(end);
}
}
else {
el.value = el.value.substring(0, start) + digits + el.value.substring(end);
}
this.requestAnimationFrame(() => {
if (!this.isDestroyed)
this.handleInput();
});
};
const inputHandler = () => {
if (!this.isDestroyed)
this.handleInput();
};
const countryChangeHandler = () => {
if (this.isDestroyed || this.suppressEvents)
return;
const iso2 = this.currentIso2();
// Update state signal
this.stateSignal.update(state => ({
...state,
iso2
}));
this.runInZone(() => {
const changeEvent = { iso2 };
// Emit both traditional and signal-based outputs
this.countryChange.emit(changeEvent);
this.countryChangeSignal.emit(changeEvent);
this.validatorChange?.();
});
this.handleInput();
};
const blurHandler = () => {
if (!this.isDestroyed)
this.onBlur();
};
// Store listeners for cleanup
this.eventListeners = [
{ element: el, event: 'beforeinput', handler: beforeInputHandler },
{ element: el, event: 'paste', handler: pasteHandler },
{ element: el, event: 'input', handler: inputHandler },
{ element: el, event: 'countrychange', handler: countryChangeHandler },
{ element: el, event: 'blur', handler: blurHandler }
];
this.eventListeners.forEach(({ element, event, handler }) => {
element.addEventListener(event, handler);
});
});
}
onBlur() {
if (this.isDestroyed)
return;
this.touched = true;
// Update state signal
this.stateSignal.update(state => ({
...state,
touched: true
}));
this.runInZone(() => {
this.onTouchedCb();
// Trigger validation on blur
this.validatorChange?.();
});
// Mark for check to update view with OnPush (works in both zone and zoneless)
this.cdr.markForCheck();
const formatWhenValidBlur = this.formatWhenValidSignal() || this.formatWhenValid;
if (formatWhenValidBlur === 'off')
return;
const iso2 = this.currentIso2();
const digits = this.stripLeadingZero(this.toNSN(this.currentRaw()));
const parsed = this.tel.parseWithInvalidDetection(digits, iso2);
if (!parsed.e164 && !parsed.isValid)
return;
const nsn = parsed.e164 ? this.nsnFromE164(parsed.e164, iso2) : digits;
if (formatWhenValidBlur !== 'typing') {
this.setInputValue(this.displayValue(nsn, iso2));
}
}
onFocus() {
if (this.isDestroyed || !this.selectOnFocus || !this.inputRef)
return;
const el = this.inputRef.nativeElement;
this.requestAnimationFrame(() => {
if (!this.isDestroyed) {
try {
if (el.setSelectionRange) {
el.setSelectionRange(0, el.value.length);
}
else if (el.select) {
// Fallback for browsers that don't support setSelectionRange
el.select();
}
}
catch (e) {
// Ignore errors in older browsers
}
}
});
}
handleInput() {
if (this.suppressEvents || this.isDestroyed)
return;
// Cache currentRaw() to avoid multiple DOM reads
const rawValue = this.currentRaw();
const iso2 = this.currentIso2();
const digits = this.stripLeadingZero(this.toNSN(rawValue));
const parsed = this.tel.parseWithInvalidDetection(digits, iso2);
const isValid = parsed.isValid && !parsed.isInvalidInternational;
// Batch state signal update
this.stateSignal.update(state => ({
...state,
raw: rawValue,
e164: parsed.e164,
iso2,
isValid,
errors: isValid ? null : (parsed.isInvalidInternational ? { phoneInvalidCountryCode: true, phoneInvalid: false } : { phoneInvalid: true, phoneInvalidCountryCode: false })
}));
// Intelligence features
if (this.enableIntelligence && this.intelligence && parsed.e164) {
const carrierInfo = this.intelligence.detectCarrierAndType(parsed.e164, iso2);
if (carrierInfo) {
this.intelligenceChange.emit(carrierInfo);
}
}
// Format suggestions
if (this.enableFormatSuggestions && this.intelligence && !parsed.isValid) {
const suggestion = this.intelligence.suggestFormatCorrection(rawValue, iso2);
if (suggestion) {
this.formatSuggestion.emit(suggestion);
}
}
// Batch zone operations and emissions
this.runInZone(() => {
this.onChange(parsed.e164);
const changeEvent = { raw: rawValue, e164: parsed.e164, iso2 };
// Emit both traditional and signal-based outputs
this.inputChange.emit(changeEvent);
this.inputChangeSignal.emit(changeEvent);
// Trigger validation when input changes
this.validatorChange?.();
});
// Mark for check to update view with OnPush (works in both zone and zoneless)
this.cdr.markForCheck();
const nsn = parsed.e164 ? this.nsnFromE164(parsed.e164, iso2) : digits;
const formatWhenValid = this.formatWhenValidSignal() || this.formatWhenValid;
const display = formatWhenValid === 'typing' ? this.displayValue(nsn, iso2) : nsn;
if (display !== rawValue)
this.setInputValue(display);
}
/** Convert any string to digits only (NSN basis). */
toNSN(v) {
return (v ?? '').replace(/\D/g, '');
}
/** Strip exactly one leading trunk '0' from national input. */
stripLeadingZero(nsn) {
return nsn.replace(/^0/, '');
}
/** Current country calling code (e.g. "44", "94"). */
currentDialCode() {
try {
return (this.iti?.getSelectedCountryData?.()?.dialCode ?? '').toString();
}
catch (e) {
// Return empty string if country data is unavailable
return '';
}
}
/** Convert E.164 (+<cc><nsn>) to NSN (never includes trunk '0'). */
nsnFromE164(e164, iso2) {
const dial = this.currentDialCode();
if (!e164 || !dial)
return this.toNSN(e164);
if (e164.startsWith('+' + dial))
return e164.slice(dial.length + 1);
return this.toNSN(e164);
}
/** Format NSN for a region (adds spaces but NEVER a trunk '0'). */
formatNSN(nsn, iso2) {
try {
const fmt = new AsYouType(iso2);
return fmt.input(nsn);
}
catch (e) {
// Return unformatted NSN if formatting fails
return nsn;
}
}
/** Compose visible value based on settings. */
displayValue(nsn, iso2) {
const nationalDisplay = this.nationalDisplaySignal() || this.nationalDisplay;
return nationalDisplay === 'formatted' ? this.formatNSN(nsn, iso2) : nsn;
}
/**
* Gets the current raw input value (as displayed, with formatting).
* @returns The current input value as a string
*/
currentRaw() {
return this.isDestroyed ? '' : (this.inputRef?.nativeElement.value ?? '').trim();
}
/**
* Gets the aria-describedby attribute value for accessibility.
* @returns Space-separated list of element IDs that describe this input
*/
getAriaDescribedBy() {
const ids = [];
const hasError = this.showError();
if (this.hint && !hasError) {
ids.push(this.resolvedId + '-hint');
}
if (hasError && this.errorText) {
ids.push(this.resolvedId + '-error');
}
ids.push(this.resolvedId + '-status');
return ids.length > 0 ? ids.join(' ') : null;
}
/**
* Gets the ARIA status message for screen readers.
* @returns Status message describing the current state
*/
getAriaStatusMessage() {
if (this.isDestroyed)
return '';
const raw = this.currentRaw();
if (!raw)
return '';
const iso2 = this.currentIso2();
const parsed = this.tel.parseWithInvalidDetection(raw, iso2);
if (parsed.isValid && parsed.e164) {
return `Valid phone number: ${parsed.e164}`;
}
else if (parsed.isInvalidInternational) {
return 'Invalid country code';
}
else {
return 'Invalid phone number';
}
}
currentIso2() {
if (this.isDestroyed)
return 'US';
try {
const iso2 = (this.iti?.getSelectedCountryData?.()?.iso2 ?? this.initialCountry ?? 'US')
.toString().toUpperCase();
return iso2;
}
catch (e) {
// Fallback to initial country or default if country data is unavailable
return (this.initialCountry ?? 'US');
}
}
setInputValue(v) {
if (!this.isDestroyed && this.inputRef) {
this.inputRef.nativeElement.value = v ?? '';
}
}
isCurrentlyValid() {
return this.isDestroyed ? false : this.tel.isValid(this.currentRaw(), this.currentIso2());
}
/** Make flag/dropdown non-interactive when disabled */
applyDisabledUi(disabled) {
if (this.isDestroyed)
return;
const input = this.inputRef?.nativeElement;
if (!input)
return;
const flag = input.parentElement?.querySelector('.iti__selected-flag');
if (flag) {
flag.tabIndex = disabled ? -1 : 0;
flag.setAttribute('aria-disabled', String(disabled));
}
}
/** Returns true if nsn would be TOO_LONG for the current country. */
wouldExceedMax(nsn, iso2) {
if (this.isDestroyed)
return false;
try {
const res = validatePhoneNumberLength(nsn, iso2);
return res === 'TOO_LONG';
}
catch (e) {
// Return false if validation fails (assume not too long)
return false;
}
}
/** Clean up event listeners to prevent memory leaks */
cleanupEventListeners() {
this.eventListeners.forEach(({ element, event, handler }) => {
element.removeEventListener(event, handler);
});
this.eventListeners = [];
}
/** Detect and apply theme based on user preference and system settings */
detectAndApplyTheme() {
if (!isPlatformBrowser(this.platformId))
return;
let detectedTheme = 'light';
// Fix precedence: Check signal first, but if it's 'auto' (default), fallback to legacy input
// This allows [theme]="'light'" to work even if signal defaults to 'auto'
const signalTheme = this.themeSignal();
const theme = signalTheme !== 'auto' ? signalTheme : this.theme;
if (theme === 'auto') {
// Check for matchMedia support (not available in older browsers)
try {
if (window.matchMedia?.('(prefers-color-scheme: dark)').matches) {
detectedTheme = 'dark';
}
}
catch (e) {
// Fallback for browsers without matchMedia support
}
// Check for dark class on document element
if (document.documentElement?.classList?.contains('dark')) {
detectedTheme = 'dark';
}
// Check for data-theme attribute
else if (document.documentElement?.getAttribute('data-theme') === 'dark') {
detectedTheme = 'dark';
}
}
else {
detectedTheme = theme;
}
// Always apply theme (even if same) to ensure it's set on the element
console.log(`[NgxsmkTelInput] Applying theme: ${detectedTheme} (Config: ${theme}, System Dark: ${typeof window !== 'undefined' && window.matchMedia?.('(prefers-color-scheme: dark)').matches})`);
this.currentTheme = detectedTheme;
this.applyTheme(detectedTheme);
}
/** Apply theme to the component */
applyTheme(theme) {
if (!isPlatformBrowser(this.platformId))
return;
const hostElement = this.hostElementRef?.nativeElement;
if (hostElement) {
// Set the theme attribute - this is the primary way CSS selects the theme
hostElement.setAttribute('data-theme', theme);
// Also add/remove dark class for additional CSS selector support
if (theme === 'dark') {
hostElement.classList.add('dark');
hostElement.classList.remove('light');
}
else {
hostElement.classList.add('light');
hostElement.classList.remove('dark');
}
// Force style recalculation to ensure CSS is applied
void hostElement.offsetHeight; // Trigger reflow
// Mark for check to update view with OnPush strategy
this.cdr.markForCheck();
}
else {
// Retry if host element not available yet
this.requestAnimationFrame(() => {
if (!this.isDestroyed) {
this.applyTheme(theme);
}
});
return;
}
this.applyCustomColors(theme);
this.applyThemeToDropdown(theme);
}
applyCustomColors(theme) {
if (!this.customColors || !this.customColors[theme])
return;
const p = this.customColors[theme];
// Use hostElementRef since we used it above
const hostElement = this.hostElementRef?.nativeElement;
if (!hostElement)
return;
const style = hostElement.style;
const set = (k, v) => { if (v)
style.setProperty(k, v, 'important'); };
set('--tel-bg', p.background);
set('--tel-fg', p.foreground);
set('--tel-border', p.border);
set('--tel-border-hover', p.borderHover);
set('--tel-ring', p.ring);
set('--tel-placeholder', p.placeholder);
}
/** Apply theme to the dropdown if it exists */
applyThemeToDropdown(theme) {
if (!isPlatformBrowser(this.platformId))
return;
if (typeof document === 'undefined' || !document.querySelector)
return;
try {
const dropdown = document.querySelector('.iti__country-list');
if (dropdown && dropdown.setAttribute) {
dropdown.setAttribute('data-theme', theme);
}
}
catch (e) {
// Fallback for browsers without querySelector support
}
}
/**
* Gets the current resolved theme (light or dark).
* @returns The current theme ('light' or 'dark')
*/
getCurrentTheme() {
return this.currentTheme;
}
/**
* Sets the theme programmatically.
* @param theme - Theme to apply ('light' or 'dark')
*/
setTheme(theme) {
this.theme = theme;
this.detectAndApplyTheme();
}
/** Update dropdown theme when it's opened */
updateDropdownTheme() {
if (!isPlatformBrowser(this.platformId))
return;
setTimeout(() => {
const dropdown = document.querySelector('.iti__country-list');
if (dropdown) {
dropdown.setAttribute('data-theme', this.currentTheme);
if (this.currentTheme === 'dark') {
dropdown.classList.add('dark-theme');
document.documentElement.classList.add('dark');
document.body.classList.add('dark');
}
else {
dropdown.classList.remove('dark-theme');
document.documentElement.classList.remove('dark');
document.body.classList.remove('dark');
}
const searchInput = dropdown.querySelector('.iti__search-input');
if (searchInput) {
searchInput.setAttribute('data-theme', this.currentTheme);
if (this.currentTheme === 'dark') {
searchInput.classList.add('dark-theme');
}
else {
searchInput.classList.remove('dark-theme');
}
searchInput.style.pointerEvents = 'auto';
searchInput.style.opacity = '1';
searchInput.disabled = false;
// Add clear button functionality
this.setupSearchInputClearButton(searchInput);
}
}
}, 10);
}
/** Setup clear button for search input */
setupSearchInputClearButton(searchInput) {
if (!isPlatformBrowser(this.platformId))
return;
// Clean up any existing listeners first
this.cleanupSearchInputListeners();
// Find the search container (could be parent or a wrapper)
let searchContainer = searchInput.parentElement;
if (!searchContainer)
return;
// Remove existing clear button if any
const existingClear = searchContainer.querySelector('.iti__search-clear');
if (existingClear) {
existingClear.remove();
}
// Create clear button
const clearButton = document.createElement('button');
clearButton.type = 'button';
clearButton.className = 'iti__search-clear';
clearButton.setAttribute('aria-label', 'Clear search');
clearButton.innerHTML = '×';
clearButton.setAttribute('tabindex', '-1');
// Make search input container relative if needed
const containerStyle = window.getComputedStyle(searchContainer);
if (containerStyle.position === 'static') {
searchContainer.style.position = 'relative';
}
// Append clear button to container
searchContainer.appendChild(clearButton);
// Show/hide clear button based on input value
const updateClearButton = () => {
if (searchInput.value && searchInput.value.trim()) {
clearButton.style.display = 'flex';
}
else {
clearButton.style.display = 'none';
}
};
// Clear input when button is clicked
const handleClear = (e) => {
e.preventDefault();
e.stopPropagation();
searchInput.value = '';
// Trigger input event to update the country list
searchInput.dispatchEvent(new Event('input', { bubbles: true }));
searchInput.dispatchEvent(new Event('keyup', { bubbles: true }));
searchInput.focus();
updateClearButton();
};
const handleMousedown = (e) => e.preventDefault();
const handlePaste = () => {
setTimeout(updateClearButton, 0);
};
// Add event listeners and track cleanup functions
clearButton.addEventListener('click', handleClear);
this.searchInputCleanupFunctions.push(() => clearButton.removeEventListener('click', handleClear));
clearButton.addEventListener('mousedown', handleMousedown);
this.searchInputCleanupFunctions.push(() => clearButton.removeEventListener('mousedown', handleMousedown));
// Update button visibility on input
searchInput.addEventListener('input', updateClearButton);
this.searchInputCleanupFunctions.push(() => searchInput.removeEventListener('input', updateClearButton));
searchInput.addEventListener('keyup', updateClearButton);
this.searchInputCleanupFunctions.push(() => searchInput.removeEventListener('keyup', updateClearButton));
searchInput.addEventListener('focus', updateClearButton);
this.searchInputCleanupFunctions.push(() => searchInput.removeEventListener('focus', updateClearButton));
searchInput.addEventListener('paste', handlePaste);
this.searchInputCleanupFunctions.push(() => searchInput.removeEventListener('paste', handlePaste));
// Initial state
updateClearButton();
}
/** Clean up search input event listeners to prevent memory leaks */
cleanupSearchInputListeners() {
this.searchInputCleanupFunctions.forEach(cleanup => cleanup());
this.searchInputCleanupFunctions = [];
}
/** Setup observer to watch for dropdown changes and apply theme */
setupDropdownThemeObserver() {
if (!isPlatformBrowser(this.platformId))
return;
if (typeof window === 'undefined' || !window.MutationObserver)
return;
// Only create observer if it doesn't already exist
if (this.themeObserver)
return;
try {
this.themeObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList' && mutation.addedNodes) {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE || node.nodeType === 1) {
const element = node;
if (element.classList && element.classList.contains('iti__country-list')) {
this.updateDropdownTheme();
}
}
});
}
});
});
if (this.themeObserver && document.body) {
this.themeObserver.observe(document.body, {
childList: true,
subtree: true
});
}
}
catch (e) {
// Fallback for browsers without MutationObserver support
this.themeObserver = null;
}
}
/** Setup observer to watch for global theme changes on html/body */
setupGlobalThemeObserver() {
if (!isPlatformBrowser(this.platformId))
return;
// Watch for class changes on document.documentElement (html tag)
// This supports Tailwind and other class-based theming systems
const target = document.documentElement;
if (!target || !window.MutationObserver)
return;
this.globalThemeObserver = new MutationObserver((mutations) => {
// Only re-check if we are in auto mode
const themeConfig = this.themeSignal() || this.theme;
if (themeConfig === 'auto') {
this.detectAndApplyTheme();
}
});
this.globalThemeObserver.observe(target, {
attributes: true,
attributeFilter: ['class', 'data-theme']
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgxsmkTelInputComponent, deps: [{ token: i0.NgZone, optional: true }, { token: NgxsmkTelInputService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: NgxsmkTelInputComponent, isStandalone: true, selector: "ngxsmk-tel-input", inputs: { initialCountrySignal: { classPropertyName: "initialCountrySignal", publicName: "initialCountrySignal", isSignal: true, isRequired: false, transformFunction: null }, preferredCountriesSignal: { classPropertyName: "preferredCountriesSignal", publicName: "preferredCountriesSignal", isSignal: true, isRequired: false, transformFunction: null }, onlyCountriesSignal: { classPropertyName: "onlyCountriesSignal", publicName: "onlyCountriesSignal", isSignal: true, isRequired: false, transformFunction: null }, separateDialCodeSignal: { classPropertyName: "separateDialCodeSignal", publicName: "separateDialCodeSignal", isSignal: true, isRequired: false, transformFunction: null }, allowDropdownSignal: { classPropertyName: "allowDropdownSignal", publicName: "allowDropdownSignal", isSignal: true, isRequired: false, transformFunction: null }, nationalDisplaySignal: { classPropertyName: "nationalDisplaySignal", publicName: "nationalDisplaySignal", isSignal: true, isRequired: false, transformFunction: null }, formatWhenValidSignal: { classPropertyName: "formatWhenValidSignal", publicName: "formatWhenValidSignal", isSignal: true, isRequired: false, transformFunction: null }, sizeSignal: { classPropertyName: "sizeSignal", publicName: "sizeSignal", isSignal: true, isRequired: false, transformFunction: null }, variantSignal: { classPropertyName: "variantSignal", publicName: "variantSignal", isSignal: true, isRequired: false, transformFunction: null }, themeSignal: { classPropertyName: "themeSignal", publicName: "themeSignal", isSignal: true, isRequired: false, transformFunction: null }, disabledSignal: { classPropertyName: "disabledSignal", publicName: "disabledSignal", isSignal: true, isRequired: false, transformFunction: null }, initialCountry: { classPropertyName: "initialCountry", publicName: "initialCountry", isSignal: false, isRequired: false, transformFunction: null }, preferredCountries: { classPropertyName: "preferredCountries", publicName: "preferredCountries", isSignal: false, isRequired: false, transformFunction: null }, onlyCountries: { classPropertyName: "onlyCountries", publicName: "onlyCountries", isSignal: false, isRequired: false, transformFunction: null }, separateDialCode: { classPropertyName: "separateDialCode", publicName: "separateDialCode", isSignal: false, isRequired: false, transformFunction: null }, allowDropdown: { classPropertyName: "allowDropdown", publicName: "allowDropdown", isSignal: false, isRequired: false, transformFunction: null }, nationalDisplay: { classPropertyName: "nationalDisplay", publicName: "nationalDisplay", isSignal: false, isRequired: false, transformFunction: null }, formatWhenValid: { classPropertyName: "formatWhenValid", publicName: "formatWhenValid", isSignal: false, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: false, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: false, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: false, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: false, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: false, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: false, isRequired: false, transformFunction: null }, errorText: { classPropertyName: "errorText", publicName: "errorText", isSignal: false, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: false, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: false, isRequired: false, transformFunction: null }, showClear: { classPropertyName: "showClear", publicName: "showClear", isSignal: false, isRequired: false, transformFunction: null }, autoFocus: { classPropertyName: "autoFocus", publicName: "autoFocus", isSignal: false, isRequired: false, transformFunction: null }, selectOnFocus: { classPropertyName: "selectOnFocus", publicName: "selectOnFocus", isSignal: false, isRequired: false, transformFunction: null }, showErrorWhenTouched: { classPropertyName: "showErrorWhenTouched", publicName: "showErrorWhenTouched", isSignal: false, isRequired: false, transformFunction: null }, dropdownAttachToBody: { classPropertyName: "dropdownAttachToBody", publicName: "dropdownAttachToBody", isSignal: false, isRequired: false, transformFunction: null }, dropdownZIndex: { classPropertyName: "dropdownZIndex", publicName: "dropdownZIndex", isSignal: false, isRequired: false, transformFunction: null }, i18n: { classPropertyName: "i18n", publicName: "i18n", isSignal: false, isRequired: false, transformFunction: null }, telI18n: { classPropertyName: "telI18n", publicName: "telI18n", isSignal: false, isRequired: false, transformFunction: null }, localizedCountries: { classPropertyName: "localizedCountries", publicName: "localizedCountries", isSignal: false, isRequired: false, transformFunction: null }, telLocalizedCountries: { classPropertyName: "telLocalizedCountries", publicName: "telLocalizedCountries", isSignal: false, isRequired: false, transformFunction: null }, clearAriaLabel: { classPropertyName: "clearAriaLabel", publicName: "clearAriaLabel", isSignal: false, isRequired: false, transformFunction: null }, dir: { classPropertyName: "dir", publicName: "dir", isSignal: false, isRequired: false, transformFunction: null }, autoPlaceholder: { classPropertyName: "autoPlaceholder", publicName: "autoPlaceholder", isSignal: false, isRequired: false, transformFunction: null }, utilsScript: { classPropertyName: "utilsScript", publicName: "utilsScript", isSignal: false, isRequired: false, transformFunction: null }, customPlaceholder: { classPropertyName: "customPlaceholder", publicName: "customPlaceholder", isSignal: false, isRequired: false, transformFunction: null }, digitsOnly: { classPropertyName: "digitsOnly", publicName: "digitsOnly", isSignal: false, isRequired: false, transformFunction: null }, lockWhenValid: { classPropertyName: "lockWhenValid", publicName: "lockWhenValid", isSignal: false, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: false, isRequired: false, transformFunction: null }, customColors: { classPropertyName: "customColors", publicName: "customColors", isSignal: false, isRequired: false, transformFunction: null }, enableIntelligence: { classPropertyName: "enableIntelligence", publicName: "enableIntelligence", isSignal: false, isRequired: false, transformFunction: null }, enableFormatSuggestions: { classPropertyName: "enableFormatSuggestions", publicName: "enableFormatSuggestions", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { countryChangeSignal: "countryChangeSignal", validityChangeSignal: "validityChangeSignal", inputChangeSignal: "inputChangeSignal", countryChange: "countryChange", validityChange: "validityChange", inputChange: "inputChange", intelligenceChange: "intelligenceChange", formatSuggestion: "formatSuggestion" }, providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NgxsmkTelInputComponent), multi: true },
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => NgxsmkTelInputComponent), multi: true }
], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["telInput"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: `
<div class="ngxsmk-tel"
[class.disabled]="disabledSignal() || disabled"
[attr.data-size]="sizeSignal() || size"
[attr.data-variant]="variantSignal() || variant"
[attr.dir]="dir"
[attr.aria-label]="label || 'Phone number input'">
@if (label) {
<label class="ngxsmk-tel__label" [for]="resolvedId">{{ label }}</label>
}
<div class="ngxsmk-tel__wrap"
[class.has-error]="showError()"
[attr.aria-describedby]="getAriaDescribedBy()">
<div class="ngxsmk-tel-input__wrapper">
<input
#telInput
type="tel"
class="ngxsmk-tel-input__control"
[id]="resolvedId"
[attr.name]="name || null"
[attr.placeholder]="placeholder || null"
[attr.autocomplete]="autocomplete"
[attr.inputmode]="digitsOnly ? 'numeric' : 'tel'"
[disabled]="disabledSignal() || disabled"
[attr.aria-invalid]="showError() ? 'true' : 'false'"
[attr.aria-required]="isRequired ? 'true' : null"
[attr.aria-describedby]="getAriaDescribedBy()"
[attr.aria-errormessage]="showError() && errorText ? resolvedId + '-error' : null"
(blur)="onBlur()"
(focus)="onFocus()"
/>
</div>
@if (showClear && (rawValue() || currentRaw())) {
<button type="button"
class="ngxsmk-tel__clear"
(click)="clearInput()"
[attr.aria-label]="clearAriaLabel"
[attr.aria-describedby]="resolvedId">
×
</button>
}
</div>
@if (hint && !showError()) {
<div class="ngxsmk-tel__hint" [id]="resolvedId + '-hint'">{{ hint }}</div>
}
@if (showError() && errorText) {
<div class="ngxsmk-tel__error"
[id]="resolvedId + '-error'"
role="alert"
[attr.aria-live]="'polite'">{{ errorText }}</div>
}
<div [id]="resolvedId + '-status'"
class="sr-only"
role="status"
[attr.aria-live]="'polite'"
[attr.aria-atomic]="true">{{ getAriaStatusMessage() }}</div>
</div>
`, isInline: true, styles: [":host{--tel-bg: #fff;--tel-fg: #0f172a;--tel-border: #c0c0c0;--tel-border-hover: #9aa0a6;--tel-ring: #2563eb;--tel-placeholder: #9ca3af;--tel-error: #ef4444;--tel-success: #10b981;--tel-warning: #f59e0b;--tel-radius: 8px;--tel-focus-shadow: 0 0 0 2px rgba(37, 99, 235, .2);--tel-dd-bg: #fff;--tel-dd-border: var(--tel-border);--tel-dd-shadow: 0 24px 60px rgba(0, 0, 0, .18);--tel-dd-radius: 8px;--tel-dd-item-hover: rgba(37, 99, 235, .08);--tel-dd-z: 2000;--tel-dd-search-bg: rgba(148, 163, 184, .08);display:block;contain:layout style;isolation:isolate}:host-context(.dark):not([data-theme=light]):not(.light),:host([data-theme=dark]),:host(.dark){--tel-bg: #0f172a;--tel-fg: #e2e8f0;--tel-border: #334155;--tel-border-hover: #475569;--tel-ring: #60a5fa;--tel-placeholder: #94a3b8;--tel-error: #f87171;--tel-success: #34d399;--tel-warning: #fbbf24;--tel-focus-shadow: 0 0 0 .2rem rgba(96, 165, 250, .25);--tel-dd-bg: #1e293b;--tel-dd-border: #334155;--tel-dd-shadow: 0 24px 60px rgba(0, 0, 0, .4);--tel-dd-search-bg: rgba(148, 163, 184, .12)}:host([data-theme=light]),:host(.light){--tel-bg: #fff !important;--tel-fg: #0f172a !important;--tel-border: #c0c0c0 !important;--tel-border-hover: #9aa0a6 !important;--tel-ring: #2563eb !important;--tel-placeholder: #9ca3af !important;--tel-error: #ef4444 !important;--tel-success: #10b981 !important;--tel-warning: #f59e0b !important;--tel-focus-shadow: 0 0 0 2px rgba(37, 99, 235, .2) !important;--tel-dd-bg: #fff !important;--tel-dd-border: #c0c0c0 !important;--tel-dd-shadow: 0 24px 60px rgba(0, 0, 0, .18) !important;--tel-dd-search-bg: rgba(148, 163, 184, .08) !important}:host-context(.dark):not([data-theme=light]):not(.light) ::ng-deep .iti__country-list,:host([data-theme=dark]) ::ng-deep .iti__country-list,:host(.dark) ::ng-deep .iti__country-list{background:var(--tel-dd-bg)!important;border-color:var(--tel-dd-border)!important;color:#e2e8f0!important}:host-context(.dark):not([data-theme=light]):not(.light) ::ng-deep .iti__search-input,:host([data-theme=dark]) ::ng-deep .iti__search-input,:host(.dark) ::ng-deep .iti__search-input{background:var(--tel-dd-search-bg)!important;color:#fff!important;border-bottom-color:var(--tel-dd-border)!important}:host-context(.dark):not([data-theme=light]):not(.light) ::ng-deep .iti__country,:host([data-theme=dark]) ::ng-deep .iti__country,:host(.dark) ::ng-deep .iti__country{color:#e2e8f0!important}:host-context(.dark):not([data-theme=light]):not(.light) ::ng-deep .iti__country.iti__highlight,:host([data-theme=dark]) ::ng-deep .iti__country.iti__highlight,:host(.dark) ::ng-deep .iti__country.iti__highlight{background-color:var(--tel-dd-item-hover)!important}:host-context(.dark):not([data-theme=light]):not(.light) ::ng-deep .iti__country-name,:host([data-theme=dark]) ::ng-deep .iti__country-name,:host(.dark) ::ng-deep .iti__country-name{color:#e2e8f0!important}:host-context(.dark):not([data-theme=light]):not(.light) ::ng-deep .iti__country-code,:host([data-theme=dark]) ::ng-deep .iti__country-code,:host(.dark) ::ng-deep .iti__country-code{color:#94a3b8!important}:host-context(.dark):not([data-theme=light]) ::ng-deep .iti__dial-code,:host([data-theme=dark]) ::ng-deep .iti__dial-code,:host(.dark) ::ng-deep .iti__dial-code{color:#94a3b8!important}:host-context(.dark):not([data-theme=light]):not(.light) ::ng-deep .iti__arrow,:host([data-theme=dark]) ::ng-deep .iti__arrow,:host(.dark) ::ng-deep .iti__arrow{border-top-color:#e2e8f0!important;opacity:1}:host-context(.dark):not([data-theme=light]):not(.light) ::ng-deep .iti__arrow.iti__arrow--up,:host([data-theme=dark]) ::ng-deep .iti__arrow.iti__arrow--up,:host(.dark) ::ng-deep .iti__arrow.iti__arrow--up{border-top-color:transparent!important;border-bottom-color:#e2e8f0!important}.ngxsmk-tel{width:100%;color:var(--tel-fg)}.ngxsmk-tel.disabled{opacity:.7;cursor:not-allowed}.ngxsmk-tel__label{display:inline-block;margin-bottom:8px;font-size:.875rem;font-weight:500;color:var(--tel-fg)}.ngxsmk-tel__wrap{position:relative}.ngxsmk-tel-input__wrapper,:host ::ng-deep .iti{width:100%}.ngxsmk-tel-input__control{width:100%;height:40px;font:inherit;color:var(--tel-fg);background:var(--tel-bg);border:1px solid var(--tel-border);-webkit-border-radius:var(--tel-radius);-moz-border-radius:var(--tel-radius);border-radius:var(--tel-radius);padding:10px 40px 10px 12px;outline:none;-webkit-transition:border-color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),background .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1);-moz-transition:border-color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),background .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1);-o-transition:border-color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),background .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1);transition:border-color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),background .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1);will-change:border-color,box-shadow,background,transform;-webkit-transform:translateZ(0);transform:translateZ(0);box-shadow:0 1px 3px #0000001a;font-size:16px;line-height:1.5;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.ngxsmk-tel-input__control::placeholder{color:var(--tel-placeholder)}.ngxsmk-tel-input__control:hover:not(:disabled):not(:focus){border-color:var(--tel-border-hover);box-shadow:0 2px 4px #00000014,0 1px 2px #0000000a;transform:translateY(-1px)}.ngxsmk-tel-input__control:focus{border-color:var(--tel-ring);box-shadow:var(--tel-focus-shadow),0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;background:var(--tel-bg);color:var(--tel-fg);transform:translateY(0)}[data-size=sm] .ngxsmk-tel-input__control{height:34px;font-size:13px;padding:6px 36px 6px 10px;border-radius:6px}[data-size=sm] :host ::ng-deep .iti__country-list,[data-size=sm] :host ::ng-deep .iti__search-input,[data-size=sm] :host ::ng-deep .iti__country,[data-size=sm] :host ::ng-deep .iti__country-name,[data-size=sm] :host ::ng-deep .iti__country-code,[data-size=sm] :host ::ng-deep .iti__dial-code{font-size:13px}[data-size=lg] .ngxsmk-tel-input__control{height:46px;font-size:16px;padding:12px 44px 12px 14px;border-radius:10px}[data-size=lg] :host ::ng-deep .iti__country-list,[data-size=lg] :host ::ng-deep .iti__search-input,[data-size=lg] :host ::ng-deep .iti__country,[data-size=lg] :host ::ng-deep .iti__country-name,[data-size=lg] :host ::ng-deep .iti__country-code,[data-size=lg] :host ::ng-deep .iti__dial-code{font-size:16px}:host ::ng-deep .iti__country-list,:host ::ng-deep .iti__search-input,:host ::ng-deep .iti__country,:host ::ng-deep .iti__country-name,:host ::ng-deep .iti__country-code,:host ::ng-deep .iti__dial-code{font-size:15px}[data-variant=filled] .ngxsmk-tel-input__control{background:#94a3b814}[data-variant=underline] .ngxsmk-tel-input__control{border:0;border-bottom:2px solid var(--tel-border);border-radius:0;padding-left:0;padding-right:34px}[data-variant=underline] .ngxsmk-tel-input__control:focus{border-bottom-color:var(--tel-ring);box-shadow:none}:host ::ng-deep .iti__flag-container{border-top-left-radius:var(--tel-radius);border-bottom-left-radius:var(--tel-radius);border:1px solid var(--tel-border);border-right:none;background:var(--tel-bg)}:host ::ng-deep .iti__selected-flag{height:100%;padding:0 10px;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;outline:none!important;border:none!important;box-shadow:none!important}:host ::ng-deep .iti__selected-flag:focus,:host ::ng-deep .iti__selected-flag:active,:host ::ng-deep .iti__selected-flag:focus-visible{outline:none!important;border:none!important;box-shadow:none!important}:host ::ng-deep .iti__selected-country{z-index:1;position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:100%;background:none;border:0;margin:0;padding:0;font-family:inherit;font-size:inherit;color:inherit;border-radius:0;font-weight:inherit;line-height:inherit;text-decoration:none;outline:none!important}:host ::ng-deep .iti__selected-country:focus,:host ::ng-deep .iti__selected-country:active,:host ::ng-deep .iti__selected-country:focus-visible{outline:none!important}:host ::ng-deep .iti__country-list{background:var(--tel-dd-bg);border:1px solid var(--tel-dd-border);-webkit-border-radius:var(--tel-dd-radius);-moz-border-radius:var(--tel-dd-radius);border-radius:var(--tel-dd-radius);box-shadow:var(--tel-dd-shadow);max-height:360px;max-height:min(50vh,360px);overflow:auto;padding:6px 0;width:100%;min-width:0;box-sizing:border-box;z-index:var(--tel-dd-z);contain:layout style;isolation:isolate;will-change:transform;color:var(--tel-fg);display:block;animation:slideDown .2s cubic-bezier(.4,0,.2,1)}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}:host ::ng-deep .iti--container .iti__country-list{z-index:var(--tel-dd-z);width:100%;min-width:0;box-sizing:border-box}:host ::ng-deep .iti__search-input{position:-webkit-sticky;position:sticky;top:0;margin:0;padding:10px 36px 10px 12px;width:100%;border:0;border-bottom:1px solid var(--tel-dd-border);outline:none;background:var(--tel-dd-search-bg);color:var(--tel-fg);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:host ::ng-deep .iti__search-box,:host ::ng-deep .iti__country-list>div:first-child{position:relative}:host ::ng-deep .iti__search-clear{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:transparent;border:0;cursor:pointer;font-size:18px;line-height:1;width:24px;height:24px;display:flex;align-items:center;justify-content:center;color:var(--tel-placeholder);padding:0;z-index:10;transition:color .2s ease;-webkit-tap-highlight-color:transparent}:host ::ng-deep .iti__search-clear:hover{color:var(--tel-fg)}:host ::ng-deep .iti__search-clear:active{opacity:.7}:host ::ng-deep .iti__search-input::placeholder{color:var(--tel-placeholder)}:host ::ng-deep .iti__country{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:10px 12px;cursor:pointer;color:var(--tel-fg);transition:background-color .15s cubic-bezier(.4,0,.2,1),transform .15s cubic-bezier(.4,0,.2,1)}@supports (display: grid){:host ::ng-deep .iti__country{display:grid;grid-template-columns:28px 1fr auto;align-items:center;column-gap:.5rem}}:host ::ng-deep .iti__country.iti__highlight{background-color:var(--tel-dd-item-hover);transform:scale(.99)}:host ::ng-deep .iti__dial-code{color:var(--tel-placeholder);font-weight:600;margin-left:10px}:host ::ng-deep .iti__country-name{color:var(--tel-fg)}:host ::ng-deep .iti__country-code{color:var(--tel-placeholder);font-weight:500}.ngxsmk-tel__clear{position:absolute;right:8px;top:50%;transform:translateY(-50%);border:0;background:transparent;font-size:18px;line-height:1;width:28px;height:28px;border-radius:50%;cursor:pointer;color:var(--tel-placeholder);transition:color .2s ease}.ngxsmk-tel__clear:hover{color:var(--tel-fg)}.ngxsmk-tel__hint{margin-top:8px;font-size:12px;color:var(--tel-placeholder)}.ngxsmk-tel__error{margin-top:8px;font-size:12px;color:var(--tel-error)}.ngxsmk-tel__wrap.has-error .ngxsmk-tel-input__control{border-color:var(--tel-error);box-shadow:0 0 0 3px #ef444426}.ngxsmk-tel.disabled .iti__flag-container,.ngxsmk-tel.disabled .iti__selected-flag{pointer-events:none;opacity:.6}:host-context(.dark):not([data-theme=light]) .ngxsmk-tel-input__control,:host([data-theme=dark]) .ngxsmk-tel-input__control{background:#0f172a!important;color:#e2e8f0!important;border-color:#334155!important;box-shadow:0 1px 3px #0000004d!important}:host-context(.dark):not([data-theme=light]) .ngxsmk-tel-input__control:focus,:host([data-theme=dark]) .ngxsmk-tel-input__control:focus{background:#0f172a!important;color:#e2e8f0!important;border-color:#60a5fa!important;box-shadow:0 0 0 .2rem #60a5fa40!important}:host-context(.dark):not([data-theme=light]) .ngxsmk-tel-input__control::placeholder,:host([data-theme=dark]) .ngxsmk-tel-input__control::placeholder{color:#94a3b8!important;opacity:1}:host-context(.dark):not([data-theme=light]) .ngxsmk-tel__label,:host([data-theme=dark]) .ngxsmk-tel__label{color:#e2e8f0!important}:host-context(.dark):not([data-theme=light]) .ngxsmk-tel__hint,:host([data-theme=dark]) .ngxsmk-tel__hint{color:#94a3b8!important;opacity:1}:host-context(.dark):not([data-theme=light]) .ngxsmk-tel__error,:host([data-theme=dark]) .ngxsmk-tel__error{color:#f87171!important}:host-context(.dark):not([data-theme=light]) .ngxsmk-tel__clear,:host([data-theme=dark]) .ngxsmk-tel__clear{color:#94a3b8!important;opacity:1}:host-context(.dark):not([data-theme=light]) .ngxsmk-tel__clear:hover,:host([data-theme=dark]) .ngxsmk-tel__clear:hover{color:#e2e8f0!important;opacity:1}:host-context(.dark):not([data-theme=light]) ::ng-deep .iti__search-input::placeholder,:host([data-theme=dark]) ::ng-deep .iti__search-input::placeholder{color:#94a3b8!important;opacity:1}:host-context(.dark):not([data-theme=light]) ::ng-deep .iti__search-clear,:host([data-theme=dark]) ::ng-deep .iti__search-clear{color:#94a3b8!important;opacity:1}:host-context(.dark):not([data-theme=light]) ::ng-deep .iti__search-clear:hover,:host([data-theme=dark]) ::ng-deep .iti__search-clear:hover{color:#e2e8f0!important;opacity:1}:host-context(.dark):not([data-theme=light]) ::ng-deep .iti__selected-dial-code,:host([data-theme=dark]) ::ng-deep .iti__selected-dial-code{color:#e2e8f0!important}:host-context(.dark):not([data-theme=light]) ::ng-deep .iti__selected-flag,:host([data-theme=dark]) ::ng-deep .iti__selected-flag{color:#e2e8f0!important}:host([data-theme=light]) .ngxsmk-tel-input__control{background:#fff!important;background-color:#fff!important;color:#0f172a!important;border-color:silver!important;box-shadow:0 1px 3px #0000001a!important}:host([data-theme=light]) .ngxsmk-tel-input__control:focus{background:#fff!important;color:#0f172a!important;border-color:#2563eb!important;box-shadow:0 0 0 3px #2563eb40!important}:host([data-theme=light]) .ngxsmk-tel-input__control::placeholder{color:#9ca3af!important;opacity:1}:host([data-theme=light]) .ngxsmk-tel__label{color:#0f172a!important}:host([data-theme=light]) .ngxsmk-tel__hint{color:#6b7280!important;opacity:1}:host([data-theme=light]) .ngxsmk-tel__error{color:#ef4444!important}:host([data-theme=light]) .ngxsmk-tel__clear{color:#6b7280!important;opacity:1}:host([data-theme=light]) .ngxsmk-tel__clear:hover{color:#0f172a!important;opacity:1}:host([data-theme=light]) ::ng-deep .iti__search-input{background:#94a3b814!important;color:#0f172a!important;border-bottom-color:silver!important}:host([data-theme=light]) ::ng-deep .iti__search-input::placeholder{color:#9ca3af!important;opacity:1}:host([data-theme=light]) ::ng-deep .iti__search-clear{color:#6b7280!important;opacity:1}:host([data-theme=light]) ::ng-deep .iti__search-clear:hover{color:#0f172a!important;opacity:1}:host([data-theme=light]) ::ng-deep .iti__selected-dial-code{color:#6b7280!important}:host([data-theme=light]) ::ng-deep .iti__selected-flag{color:#0f172a!important}:host([data-theme=light]) ::ng-deep .iti__arrow{border-top-color:#0f172a!important;opacity:1}:host([data-theme=light]) ::ng-deep .iti__arrow:hover{opacity:1}:host([data-theme=light]) ::ng-deep .iti--allow-dropdown .iti__arrow{border-top-color:#0f172a!important}:host([data-theme=light]) ::ng-deep .iti__country-list{background:#fff!important;border-color:silver!important;color:#0f172a!important}:host([data-theme=light]) ::ng-deep .iti__country{color:#0f172a!important}:host([data-theme=light]) ::ng-deep .iti__country-name{color:#0f172a!important}:host([data-theme=light]) ::ng-deep .iti__country-code{color:#6b7280!important}:host([data-theme=light]) ::ng-deep .iti__dial-code{color:#6b7280!important}:host([data-theme=light]) ::ng-deep .iti__flag-container{background:#fff!important;border-color:silver!important}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}@media (max-width: 768px){.ngxsmk-tel__wrap{position:relative;width:100%;max-width:100%;overflow:visible}:host{width:100%;max-width:100%;display:block}.ngxsmk-tel{width:100%;max-width:100%}.ngxsmk-tel-input__wrapper,:host ::ng-deep .iti{width:100%!important;max-width:100%!important;box-sizing:border-box}.ngxsmk-tel-input__control{font-size:16px!important;padding:12px 44px 12px 12px;min-height:44px;width:100%!important;max-width:100%!important;box-sizing:border-box}.ngxsmk-tel__clear{width:44px;height:44px;min-width:44px;min-height:44px;right:4px;padding:8px;display:flex;align-items:center;justify-content:center;font-size:18px}:host ::ng-deep .iti__selected-flag{min-width:44px;min-height:44px;padding:0 12px;touch-action:manipulation;font-size:14px}:host ::ng-deep .iti__flag-box{width:20px;height:15px}:host ::ng-deep .iti__country-list{width:100%!important;max-width:100%!important;min-width:0!important;max-height:400px;max-height:min(70vh,400px);-webkit-overflow-scrolling:touch;overflow-x:hidden;position:absolute!important;top:100%!important;left:0!important;right:0!important;transform:none!important;margin-top:4px!important;box-shadow:0 4px 12px #00000026!important;box-sizing:border-box}:host ::ng-deep .iti--container{position:relative!important;width:100%!important;max-width:100%!important;box-sizing:border-box}:host ::ng-deep .iti__dropdown{position:relative!important;width:100%!important;box-sizing:border-box}:host ::ng-deep .iti__country-list{width:100%!important;min-width:0!important;max-width:100%!important;box-sizing:border-box}:host ::ng-deep .iti__search-input{padding-right:36px!important}:host ::ng-deep .iti__search-clear{width:28px!important;height:28px!important;font-size:20px!important;right:6px!important}:host ::ng-deep .iti__country{min-height:48px;padding:12px 14px;font-size:15px;box-sizing:border-box;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}@supports (display: grid){:host ::ng-deep .iti__country{display:grid;grid-template-columns:28px 1fr auto;column-gap:.625rem}}:host ::ng-deep .iti__country-name{font-size:15px!important;line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ::ng-deep .iti__country-code{font-size:13px!important;line-height:1.4}:host ::ng-deep .iti__dial-code{font-size:14px!important;line-height:1.4;margin-left:8px;white-space:nowrap}:host ::ng-deep .iti__search-input{font-size:16px!important;padding:12px 14px;min-height:44px;width:100%!important;box-sizing:border-box}.ngxsmk-tel__label{font-size:.9375rem;margin-bottom:8px;line-height:1.4}.ngxsmk-tel__hint,.ngxsmk-tel__error{font-size:.8125rem;margin-top:8px;line-height:1.4}[data-size=sm] .ngxsmk-tel-input__control{min-height:40px;font-size:16px!important;padding:10px 40px 10px 10px;width:100%!important;max-width:100%!important;box-sizing:border-box}[data-size=lg] .ngxsmk-tel-input__control{min-height:48px;font-size:16px!important;padding:14px 48px 14px 14px;width:100%!important;max-width:100%!important;box-sizing:border-box}[data-variant=underline] .ngxsmk-tel-input__control{padding-right:40px;min-height:44px;width:100%!important;max-width:100%!important;box-sizing:border-box}:host ::ng-deep .iti__flag-container{max-width:100%;overflow:hidden}:host ::ng-deep .iti__selected-dial-code{font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}}@media (max-width: 480px){:host{width:100%;max-width:100%}.ngxsmk-tel,.ngxsmk-tel__wrap{width:100%;max-width:100%}.ngxsmk-tel-input__wrapper,:host ::ng-deep .iti{width:100%!important;max-width:100%!important}.ngxsmk-tel-input__control{padding:10px 40px 10px 10px;font-size:16px!important;width:100%!important;max-width:100%!important;box-sizing:border-box}:host ::ng-deep .iti__selected-flag{padding:0 8px;min-width:40px;font-size:13px}:host ::ng-deep .iti__flag-box{width:18px;height:14px}:host ::ng-deep .iti__country-list{max-height:350px;max-height:min(60vh,350px);border-radius:8px;position:absolute!important;top:100%!important;left:0!important;right:0!important;transform:none!important;margin-top:4px!important;width:100%!important;max-width:100%!important;box-sizing:border-box}:host ::ng-deep .iti__search-input{padding-right:36px!important}:host ::ng-deep .iti__search-clear{width:24px!important;height:24px!important;font-size:18px!important;right:6px!important}:host ::ng-deep .iti__country{padding:10px 12px;min-height:44px;font-size:14px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}@supports (display: grid){:host ::ng-deep .iti__country{display:grid;grid-template-columns:26px 1fr auto;column-gap:.5rem}}:host ::ng-deep .iti__country-name{font-size:14px!important}:host ::ng-deep .iti__country-code{font-size:12px!important}:host ::ng-deep .iti__dial-code{font-size:13px!important;margin-left:6px}:host ::ng-deep .iti__search-input{font-size:16px!important;padding:10px 12px;min-height:44px}.ngxsmk-tel__clear{width:40px;height:40px;min-width:40px;min-height:40px;font-size:16px;right:2px}.ngxsmk-tel__label{font-size:.875rem;margin-bottom:6px;line-height:1.4}.ngxsmk-tel__hint,.ngxsmk-tel__error{font-size:.75rem;line-height:1.4}:host ::ng-deep .iti__flag-container{max-width:100%;overflow:hidden}:host ::ng-deep .iti__selected-dial-code{font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:60px}[data-size=sm] :host ::ng-deep .iti__country-name,[data-size=sm] :host ::ng-deep .iti__country-code,[data-size=sm] :host ::ng-deep .iti__dial-code{font-size:13px!important}[data-size=lg] :host ::ng-deep .iti__country-name,[data-size=lg] :host ::ng-deep .iti__country-code,[data-size=lg] :host ::ng-deep .iti__dial-code{font-size:15px!important}}@media (max-width: 768px) and (orientation: landscape){:host ::ng-deep .iti__country-list{max-height:300px;max-height:min(50vh,300px)}}@media (min-width: 481px) and (max-width: 1024px){.ngxsmk-tel-input__control{padding:11px 42px 11px 12px}:host ::ng-deep .iti__country-list{max-height:400px;max-height:min(60vh,400px)}}@media (hover: none) and (pointer: coarse){.ngxsmk-tel__clear{width:44px;height:44px;min-width:44px;min-height:44px}:host ::ng-deep .iti__selected-flag{min-width:44px;min-height:44px}:host ::ng-deep .iti__country{min-height:48px}:host ::ng-deep .iti__country:active{background-color:var(--tel-dd-item-hover)}.ngxsmk-tel__clear:active{background-color:#0000001a;transform:translateY(-50%) scale(.95)}}@media (-webkit-min-device-pixel-ratio: 2),(min-resolution: 192dpi){.ngxsmk-tel-input__control{border-width:.5px}}@media screen and (max-width: 768px){.ngxsmk-tel-input__control{-webkit-text-size-adjust:100%;text-size-adjust:100%}}@supports (padding: max(0px)){@media (max-width: 768px){:host ::ng-deep .iti__country-list{padding-left:max(6px,env(safe-area-inset-left));padding-right:max(6px,env(safe-area-inset-right))}}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgxsmkTelInputComponent, decorators: [{
type: Component,
args: [{ selector: 'ngxsmk-tel-input', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: `
<div class="ngxsmk-tel"
[class.disabled]="disabledSignal() || disabled"
[attr.data-size]="sizeSignal() || size"
[attr.data-variant]="variantSignal() || variant"
[attr.dir]="dir"
[attr.aria-label]="label || 'Phone number input'">
@if (label) {
<label class="ngxsmk-tel__label" [for]="resolvedId">{{ label }}</label>
}
<div class="ngxsmk-tel__wrap"
[class.has-error]="showError()"
[attr.aria-describedby]="getAriaDescribedBy()">
<div class="ngxsmk-tel-input__wrapper">
<input
#telInput
type="tel"
class="ngxsmk-tel-input__control"
[id]="resolvedId"
[attr.name]="name || null"
[attr.placeholder]="placeholder || null"
[attr.autocomplete]="autocomplete"
[attr.inputmode]="digitsOnly ? 'numeric' : 'tel'"
[disabled]="disabledSignal() || disabled"
[attr.aria-invalid]="showError() ? 'true' : 'false'"
[attr.aria-required]="isRequired ? 'true' : null"
[attr.aria-describedby]="getAriaDescribedBy()"
[attr.aria-errormessage]="showError() && errorText ? resolvedId + '-error' : null"
(blur)="onBlur()"
(focus)="onFocus()"
/>
</div>
@if (showClear && (rawValue() || currentRaw())) {
<button type="button"
class="ngxsmk-tel__clear"
(click)="clearInput()"
[attr.aria-label]="clearAriaLabel"
[attr.aria-describedby]="resolvedId">
×
</button>
}
</div>
@if (hint && !showError()) {
<div class="ngxsmk-tel__hint" [id]="resolvedId + '-hint'">{{ hint }}</div>
}
@if (showError() && errorText) {
<div class="ngxsmk-tel__error"
[id]="resolvedId + '-error'"
role="alert"
[attr.aria-live]="'polite'">{{ errorText }}</div>
}
<div [id]="resolvedId + '-status'"
class="sr-only"
role="status"
[attr.aria-live]="'polite'"
[attr.aria-atomic]="true">{{ getAriaStatusMessage() }}</div>
</div>
`, providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NgxsmkTelInputComponent), multi: true },
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => NgxsmkTelInputComponent), multi: true }
], styles: [":host{--tel-bg: #fff;--tel-fg: #0f172a;--tel-border: #c0c0c0;--tel-border-hover: #9aa0a6;--tel-ring: #2563eb;--tel-placeholder: #9ca3af;--tel-error: #ef4444;--tel-success: #10b981;--tel-warning: #f59e0b;--tel-radius: 8px;--tel-focus-shadow: 0 0 0 2px rgba(37, 99, 235, .2);--tel-dd-bg: #fff;--tel-dd-border: var(--tel-border);--tel-dd-shadow: 0 24px 60px rgba(0, 0, 0, .18);--tel-dd-radius: 8px;--tel-dd-item-hover: rgba(37, 99, 235, .08);--tel-dd-z: 2000;--tel-dd-search-bg: rgba(148, 163, 184, .08);display:block;contain:layout style;isolation:isolate}:host-context(.dark):not([data-theme=light]):not(.light),:host([data-theme=dark]),:host(.dark){--tel-bg: #0f172a;--tel-fg: #e2e8f0;--tel-border: #334155;--tel-border-hover: #475569;--tel-ring: #60a5fa;--tel-placeholder: #94a3b8;--tel-error: #f87171;--tel-success: #34d399;--tel-warning: #fbbf24;--tel-focus-shadow: 0 0 0 .2rem rgba(96, 165, 250, .25);--tel-dd-bg: #1e293b;--tel-dd-border: #334155;--tel-dd-shadow: 0 24px 60px rgba(0, 0, 0, .4);--tel-dd-search-bg: rgba(148, 163, 184, .12)}:host([data-theme=light]),:host(.light){--tel-bg: #fff !important;--tel-fg: #0f172a !important;--tel-border: #c0c0c0 !important;--tel-border-hover: #9aa0a6 !important;--tel-ring: #2563eb !important;--tel-placeholder: #9ca3af !important;--tel-error: #ef4444 !important;--tel-success: #10b981 !important;--tel-warning: #f59e0b !important;--tel-focus-shadow: 0 0 0 2px rgba(37, 99, 235, .2) !important;--tel-dd-bg: #fff !important;--tel-dd-border: #c0c0c0 !important;--tel-dd-shadow: 0 24px 60px rgba(0, 0, 0, .18) !important;--tel-dd-search-bg: rgba(148, 163, 184, .08) !important}:host-context(.dark):not([data-theme=light]):not(.light) ::ng-deep .iti__country-list,:host([data-theme=dark]) ::ng-deep .iti__country-list,:host(.dark) ::ng-deep .iti__country-list{background:var(--tel-dd-bg)!important;border-color:var(--tel-dd-border)!important;color:#e2e8f0!important}:host-context(.dark):not([data-theme=light]):not(.light) ::ng-deep .iti__search-input,:host([data-theme=dark]) ::ng-deep .iti__search-input,:host(.dark) ::ng-deep .iti__search-input{background:var(--tel-dd-search-bg)!important;color:#fff!important;border-bottom-color:var(--tel-dd-border)!important}:host-context(.dark):not([data-theme=light]):not(.light) ::ng-deep .iti__country,:host([data-theme=dark]) ::ng-deep .iti__country,:host(.dark) ::ng-deep .iti__country{color:#e2e8f0!important}:host-context(.dark):not([data-theme=light]):not(.light) ::ng-deep .iti__country.iti__highlight,:host([data-theme=dark]) ::ng-deep .iti__country.iti__highlight,:host(.dark) ::ng-deep .iti__country.iti__highlight{background-color:var(--tel-dd-item-hover)!important}:host-context(.dark):not([data-theme=light]):not(.light) ::ng-deep .iti__country-name,:host([data-theme=dark]) ::ng-deep .iti__country-name,:host(.dark) ::ng-deep .iti__country-name{color:#e2e8f0!important}:host-context(.dark):not([data-theme=light]):not(.light) ::ng-deep .iti__country-code,:host([data-theme=dark]) ::ng-deep .iti__country-code,:host(.dark) ::ng-deep .iti__country-code{color:#94a3b8!important}:host-context(.dark):not([data-theme=light]) ::ng-deep .iti__dial-code,:host([data-theme=dark]) ::ng-deep .iti__dial-code,:host(.dark) ::ng-deep .iti__dial-code{color:#94a3b8!important}:host-context(.dark):not([data-theme=light]):not(.light) ::ng-deep .iti__arrow,:host([data-theme=dark]) ::ng-deep .iti__arrow,:host(.dark) ::ng-deep .iti__arrow{border-top-color:#e2e8f0!important;opacity:1}:host-context(.dark):not([data-theme=light]):not(.light) ::ng-deep .iti__arrow.iti__arrow--up,:host([data-theme=dark]) ::ng-deep .iti__arrow.iti__arrow--up,:host(.dark) ::ng-deep .iti__arrow.iti__arrow--up{border-top-color:transparent!important;border-bottom-color:#e2e8f0!important}.ngxsmk-tel{width:100%;color:var(--tel-fg)}.ngxsmk-tel.disabled{opacity:.7;cursor:not-allowed}.ngxsmk-tel__label{display:inline-block;margin-bottom:8px;font-size:.875rem;font-weight:500;color:var(--tel-fg)}.ngxsmk-tel__wrap{position:relative}.ngxsmk-tel-input__wrapper,:host ::ng-deep .iti{width:100%}.ngxsmk-tel-input__control{width:100%;height:40px;font:inherit;color:var(--tel-fg);background:var(--tel-bg);border:1px solid var(--tel-border);-webkit-border-radius:var(--tel-radius);-moz-border-radius:var(--tel-radius);border-radius:var(--tel-radius);padding:10px 40px 10px 12px;outline:none;-webkit-transition:border-color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),background .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1);-moz-transition:border-color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),background .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1);-o-transition:border-color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),background .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1);transition:border-color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),background .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1);will-change:border-color,box-shadow,background,transform;-webkit-transform:translateZ(0);transform:translateZ(0);box-shadow:0 1px 3px #0000001a;font-size:16px;line-height:1.5;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.ngxsmk-tel-input__control::placeholder{color:var(--tel-placeholder)}.ngxsmk-tel-input__control:hover:not(:disabled):not(:focus){border-color:var(--tel-border-hover);box-shadow:0 2px 4px #00000014,0 1px 2px #0000000a;transform:translateY(-1px)}.ngxsmk-tel-input__control:focus{border-color:var(--tel-ring);box-shadow:var(--tel-focus-shadow),0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;background:var(--tel-bg);color:var(--tel-fg);transform:translateY(0)}[data-size=sm] .ngxsmk-tel-input__control{height:34px;font-size:13px;padding:6px 36px 6px 10px;border-radius:6px}[data-size=sm] :host ::ng-deep .iti__country-list,[data-size=sm] :host ::ng-deep .iti__search-input,[data-size=sm] :host ::ng-deep .iti__country,[data-size=sm] :host ::ng-deep .iti__country-name,[data-size=sm] :host ::ng-deep .iti__country-code,[data-size=sm] :host ::ng-deep .iti__dial-code{font-size:13px}[data-size=lg] .ngxsmk-tel-input__control{height:46px;font-size:16px;padding:12px 44px 12px 14px;border-radius:10px}[data-size=lg] :host ::ng-deep .iti__country-list,[data-size=lg] :host ::ng-deep .iti__search-input,[data-size=lg] :host ::ng-deep .iti__country,[data-size=lg] :host ::ng-deep .iti__country-name,[data-size=lg] :host ::ng-deep .iti__country-code,[data-size=lg] :host ::ng-deep .iti__dial-code{font-size:16px}:host ::ng-deep .iti__country-list,:host ::ng-deep .iti__search-input,:host ::ng-deep .iti__country,:host ::ng-deep .iti__country-name,:host ::ng-deep .iti__country-code,:host ::ng-deep .iti__dial-code{font-size:15px}[data-variant=filled] .ngxsmk-tel-input__control{background:#94a3b814}[data-variant=underline] .ngxsmk-tel-input__control{border:0;border-bottom:2px solid var(--tel-border);border-radius:0;padding-left:0;padding-right:34px}[data-variant=underline] .ngxsmk-tel-input__control:focus{border-bottom-color:var(--tel-ring);box-shadow:none}:host ::ng-deep .iti__flag-container{border-top-left-radius:var(--tel-radius);border-bottom-left-radius:var(--tel-radius);border:1px solid var(--tel-border);border-right:none;background:var(--tel-bg)}:host ::ng-deep .iti__selected-flag{height:100%;padding:0 10px;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;outline:none!important;border:none!important;box-shadow:none!important}:host ::ng-deep .iti__selected-flag:focus,:host ::ng-deep .iti__selected-flag:active,:host ::ng-deep .iti__selected-flag:focus-visible{outline:none!important;border:none!important;box-shadow:none!important}:host ::ng-deep .iti__selected-country{z-index:1;position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:100%;background:none;border:0;margin:0;padding:0;font-family:inherit;font-size:inherit;color:inherit;border-radius:0;font-weight:inherit;line-height:inherit;text-decoration:none;outline:none!important}:host ::ng-deep .iti__selected-country:focus,:host ::ng-deep .iti__selected-country:active,:host ::ng-deep .iti__selected-country:focus-visible{outline:none!important}:host ::ng-deep .iti__country-list{background:var(--tel-dd-bg);border:1px solid var(--tel-dd-border);-webkit-border-radius:var(--tel-dd-radius);-moz-border-radius:var(--tel-dd-radius);border-radius:var(--tel-dd-radius);box-shadow:var(--tel-dd-shadow);max-height:360px;max-height:min(50vh,360px);overflow:auto;padding:6px 0;width:100%;min-width:0;box-sizing:border-box;z-index:var(--tel-dd-z);contain:layout style;isolation:isolate;will-change:transform;color:var(--tel-fg);display:block;animation:slideDown .2s cubic-bezier(.4,0,.2,1)}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}:host ::ng-deep .iti--container .iti__country-list{z-index:var(--tel-dd-z);width:100%;min-width:0;box-sizing:border-box}:host ::ng-deep .iti__search-input{position:-webkit-sticky;position:sticky;top:0;margin:0;padding:10px 36px 10px 12px;width:100%;border:0;border-bottom:1px solid var(--tel-dd-border);outline:none;background:var(--tel-dd-search-bg);color:var(--tel-fg);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:host ::ng-deep .iti__search-box,:host ::ng-deep .iti__country-list>div:first-child{position:relative}:host ::ng-deep .iti__search-clear{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:transparent;border:0;cursor:pointer;font-size:18px;line-height:1;width:24px;height:24px;display:flex;align-items:center;justify-content:center;color:var(--tel-placeholder);padding:0;z-index:10;transition:color .2s ease;-webkit-tap-highlight-color:transparent}:host ::ng-deep .iti__search-clear:hover{color:var(--tel-fg)}:host ::ng-deep .iti__search-clear:active{opacity:.7}:host ::ng-deep .iti__search-input::placeholder{color:var(--tel-placeholder)}:host ::ng-deep .iti__country{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:10px 12px;cursor:pointer;color:var(--tel-fg);transition:background-color .15s cubic-bezier(.4,0,.2,1),transform .15s cubic-bezier(.4,0,.2,1)}@supports (display: grid){:host ::ng-deep .iti__country{display:grid;grid-template-columns:28px 1fr auto;align-items:center;column-gap:.5rem}}:host ::ng-deep .iti__country.iti__highlight{background-color:var(--tel-dd-item-hover);transform:scale(.99)}:host ::ng-deep .iti__dial-code{color:var(--tel-placeholder);font-weight:600;margin-left:10px}:host ::ng-deep .iti__country-name{color:var(--tel-fg)}:host ::ng-deep .iti__country-code{color:var(--tel-placeholder);font-weight:500}.ngxsmk-tel__clear{position:absolute;right:8px;top:50%;transform:translateY(-50%);border:0;background:transparent;font-size:18px;line-height:1;width:28px;height:28px;border-radius:50%;cursor:pointer;color:var(--tel-placeholder);transition:color .2s ease}.ngxsmk-tel__clear:hover{color:var(--tel-fg)}.ngxsmk-tel__hint{margin-top:8px;font-size:12px;color:var(--tel-placeholder)}.ngxsmk-tel__error{margin-top:8px;font-size:12px;color:var(--tel-error)}.ngxsmk-tel__wrap.has-error .ngxsmk-tel-input__control{border-color:var(--tel-error);box-shadow:0 0 0 3px #ef444426}.ngxsmk-tel.disabled .iti__flag-container,.ngxsmk-tel.disabled .iti__selected-flag{pointer-events:none;opacity:.6}:host-context(.dark):not([data-theme=light]) .ngxsmk-tel-input__control,:host([data-theme=dark]) .ngxsmk-tel-input__control{background:#0f172a!important;color:#e2e8f0!important;border-color:#334155!important;box-shadow:0 1px 3px #0000004d!important}:host-context(.dark):not([data-theme=light]) .ngxsmk-tel-input__control:focus,:host([data-theme=dark]) .ngxsmk-tel-input__control:focus{background:#0f172a!important;color:#e2e8f0!important;border-color:#60a5fa!important;box-shadow:0 0 0 .2rem #60a5fa40!important}:host-context(.dark):not([data-theme=light]) .ngxsmk-tel-input__control::placeholder,:host([data-theme=dark]) .ngxsmk-tel-input__control::placeholder{color:#94a3b8!important;opacity:1}:host-context(.dark):not([data-theme=light]) .ngxsmk-tel__label,:host([data-theme=dark]) .ngxsmk-tel__label{color:#e2e8f0!important}:host-context(.dark):not([data-theme=light]) .ngxsmk-tel__hint,:host([data-theme=dark]) .ngxsmk-tel__hint{color:#94a3b8!important;opacity:1}:host-context(.dark):not([data-theme=light]) .ngxsmk-tel__error,:host([data-theme=dark]) .ngxsmk-tel__error{color:#f87171!important}:host-context(.dark):not([data-theme=light]) .ngxsmk-tel__clear,:host([data-theme=dark]) .ngxsmk-tel__clear{color:#94a3b8!important;opacity:1}:host-context(.dark):not([data-theme=light]) .ngxsmk-tel__clear:hover,:host([data-theme=dark]) .ngxsmk-tel__clear:hover{color:#e2e8f0!important;opacity:1}:host-context(.dark):not([data-theme=light]) ::ng-deep .iti__search-input::placeholder,:host([data-theme=dark]) ::ng-deep .iti__search-input::placeholder{color:#94a3b8!important;opacity:1}:host-context(.dark):not([data-theme=light]) ::ng-deep .iti__search-clear,:host([data-theme=dark]) ::ng-deep .iti__search-clear{color:#94a3b8!important;opacity:1}:host-context(.dark):not([data-theme=light]) ::ng-deep .iti__search-clear:hover,:host([data-theme=dark]) ::ng-deep .iti__search-clear:hover{color:#e2e8f0!important;opacity:1}:host-context(.dark):not([data-theme=light]) ::ng-deep .iti__selected-dial-code,:host([data-theme=dark]) ::ng-deep .iti__selected-dial-code{color:#e2e8f0!important}:host-context(.dark):not([data-theme=light]) ::ng-deep .iti__selected-flag,:host([data-theme=dark]) ::ng-deep .iti__selected-flag{color:#e2e8f0!important}:host([data-theme=light]) .ngxsmk-tel-input__control{background:#fff!important;background-color:#fff!important;color:#0f172a!important;border-color:silver!important;box-shadow:0 1px 3px #0000001a!important}:host([data-theme=light]) .ngxsmk-tel-input__control:focus{background:#fff!important;color:#0f172a!important;border-color:#2563eb!important;box-shadow:0 0 0 3px #2563eb40!important}:host([data-theme=light]) .ngxsmk-tel-input__control::placeholder{color:#9ca3af!important;opacity:1}:host([data-theme=light]) .ngxsmk-tel__label{color:#0f172a!important}:host([data-theme=light]) .ngxsmk-tel__hint{color:#6b7280!important;opacity:1}:host([data-theme=light]) .ngxsmk-tel__error{color:#ef4444!important}:host([data-theme=light]) .ngxsmk-tel__clear{color:#6b7280!important;opacity:1}:host([data-theme=light]) .ngxsmk-tel__clear:hover{color:#0f172a!important;opacity:1}:host([data-theme=light]) ::ng-deep .iti__search-input{background:#94a3b814!important;color:#0f172a!important;border-bottom-color:silver!important}:host([data-theme=light]) ::ng-deep .iti__search-input::placeholder{color:#9ca3af!important;opacity:1}:host([data-theme=light]) ::ng-deep .iti__search-clear{color:#6b7280!important;opacity:1}:host([data-theme=light]) ::ng-deep .iti__search-clear:hover{color:#0f172a!important;opacity:1}:host([data-theme=light]) ::ng-deep .iti__selected-dial-code{color:#6b7280!important}:host([data-theme=light]) ::ng-deep .iti__selected-flag{color:#0f172a!important}:host([data-theme=light]) ::ng-deep .iti__arrow{border-top-color:#0f172a!important;opacity:1}:host([data-theme=light]) ::ng-deep .iti__arrow:hover{opacity:1}:host([data-theme=light]) ::ng-deep .iti--allow-dropdown .iti__arrow{border-top-color:#0f172a!important}:host([data-theme=light]) ::ng-deep .iti__country-list{background:#fff!important;border-color:silver!important;color:#0f172a!important}:host([data-theme=light]) ::ng-deep .iti__country{color:#0f172a!important}:host([data-theme=light]) ::ng-deep .iti__country-name{color:#0f172a!important}:host([data-theme=light]) ::ng-deep .iti__country-code{color:#6b7280!important}:host([data-theme=light]) ::ng-deep .iti__dial-code{color:#6b7280!important}:host([data-theme=light]) ::ng-deep .iti__flag-container{background:#fff!important;border-color:silver!important}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}@media (max-width: 768px){.ngxsmk-tel__wrap{position:relative;width:100%;max-width:100%;overflow:visible}:host{width:100%;max-width:100%;display:block}.ngxsmk-tel{width:100%;max-width:100%}.ngxsmk-tel-input__wrapper,:host ::ng-deep .iti{width:100%!important;max-width:100%!important;box-sizing:border-box}.ngxsmk-tel-input__control{font-size:16px!important;padding:12px 44px 12px 12px;min-height:44px;width:100%!important;max-width:100%!important;box-sizing:border-box}.ngxsmk-tel__clear{width:44px;height:44px;min-width:44px;min-height:44px;right:4px;padding:8px;display:flex;align-items:center;justify-content:center;font-size:18px}:host ::ng-deep .iti__selected-flag{min-width:44px;min-height:44px;padding:0 12px;touch-action:manipulation;font-size:14px}:host ::ng-deep .iti__flag-box{width:20px;height:15px}:host ::ng-deep .iti__country-list{width:100%!important;max-width:100%!important;min-width:0!important;max-height:400px;max-height:min(70vh,400px);-webkit-overflow-scrolling:touch;overflow-x:hidden;position:absolute!important;top:100%!important;left:0!important;right:0!important;transform:none!important;margin-top:4px!important;box-shadow:0 4px 12px #00000026!important;box-sizing:border-box}:host ::ng-deep .iti--container{position:relative!important;width:100%!important;max-width:100%!important;box-sizing:border-box}:host ::ng-deep .iti__dropdown{position:relative!important;width:100%!important;box-sizing:border-box}:host ::ng-deep .iti__country-list{width:100%!important;min-width:0!important;max-width:100%!important;box-sizing:border-box}:host ::ng-deep .iti__search-input{padding-right:36px!important}:host ::ng-deep .iti__search-clear{width:28px!important;height:28px!important;font-size:20px!important;right:6px!important}:host ::ng-deep .iti__country{min-height:48px;padding:12px 14px;font-size:15px;box-sizing:border-box;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}@supports (display: grid){:host ::ng-deep .iti__country{display:grid;grid-template-columns:28px 1fr auto;column-gap:.625rem}}:host ::ng-deep .iti__country-name{font-size:15px!important;line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ::ng-deep .iti__country-code{font-size:13px!important;line-height:1.4}:host ::ng-deep .iti__dial-code{font-size:14px!important;line-height:1.4;margin-left:8px;white-space:nowrap}:host ::ng-deep .iti__search-input{font-size:16px!important;padding:12px 14px;min-height:44px;width:100%!important;box-sizing:border-box}.ngxsmk-tel__label{font-size:.9375rem;margin-bottom:8px;line-height:1.4}.ngxsmk-tel__hint,.ngxsmk-tel__error{font-size:.8125rem;margin-top:8px;line-height:1.4}[data-size=sm] .ngxsmk-tel-input__control{min-height:40px;font-size:16px!important;padding:10px 40px 10px 10px;width:100%!important;max-width:100%!important;box-sizing:border-box}[data-size=lg] .ngxsmk-tel-input__control{min-height:48px;font-size:16px!important;padding:14px 48px 14px 14px;width:100%!important;max-width:100%!important;box-sizing:border-box}[data-variant=underline] .ngxsmk-tel-input__control{padding-right:40px;min-height:44px;width:100%!important;max-width:100%!important;box-sizing:border-box}:host ::ng-deep .iti__flag-container{max-width:100%;overflow:hidden}:host ::ng-deep .iti__selected-dial-code{font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}}@media (max-width: 480px){:host{width:100%;max-width:100%}.ngxsmk-tel,.ngxsmk-tel__wrap{width:100%;max-width:100%}.ngxsmk-tel-input__wrapper,:host ::ng-deep .iti{width:100%!important;max-width:100%!important}.ngxsmk-tel-input__control{padding:10px 40px 10px 10px;font-size:16px!important;width:100%!important;max-width:100%!important;box-sizing:border-box}:host ::ng-deep .iti__selected-flag{padding:0 8px;min-width:40px;font-size:13px}:host ::ng-deep .iti__flag-box{width:18px;height:14px}:host ::ng-deep .iti__country-list{max-height:350px;max-height:min(60vh,350px);border-radius:8px;position:absolute!important;top:100%!important;left:0!important;right:0!important;transform:none!important;margin-top:4px!important;width:100%!important;max-width:100%!important;box-sizing:border-box}:host ::ng-deep .iti__search-input{padding-right:36px!important}:host ::ng-deep .iti__search-clear{width:24px!important;height:24px!important;font-size:18px!important;right:6px!important}:host ::ng-deep .iti__country{padding:10px 12px;min-height:44px;font-size:14px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}@supports (display: grid){:host ::ng-deep .iti__country{display:grid;grid-template-columns:26px 1fr auto;column-gap:.5rem}}:host ::ng-deep .iti__country-name{font-size:14px!important}:host ::ng-deep .iti__country-code{font-size:12px!important}:host ::ng-deep .iti__dial-code{font-size:13px!important;margin-left:6px}:host ::ng-deep .iti__search-input{font-size:16px!important;padding:10px 12px;min-height:44px}.ngxsmk-tel__clear{width:40px;height:40px;min-width:40px;min-height:40px;font-size:16px;right:2px}.ngxsmk-tel__label{font-size:.875rem;margin-bottom:6px;line-height:1.4}.ngxsmk-tel__hint,.ngxsmk-tel__error{font-size:.75rem;line-height:1.4}:host ::ng-deep .iti__flag-container{max-width:100%;overflow:hidden}:host ::ng-deep .iti__selected-dial-code{font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:60px}[data-size=sm] :host ::ng-deep .iti__country-name,[data-size=sm] :host ::ng-deep .iti__country-code,[data-size=sm] :host ::ng-deep .iti__dial-code{font-size:13px!important}[data-size=lg] :host ::ng-deep .iti__country-name,[data-size=lg] :host ::ng-deep .iti__country-code,[data-size=lg] :host ::ng-deep .iti__dial-code{font-size:15px!important}}@media (max-width: 768px) and (orientation: landscape){:host ::ng-deep .iti__country-list{max-height:300px;max-height:min(50vh,300px)}}@media (min-width: 481px) and (max-width: 1024px){.ngxsmk-tel-input__control{padding:11px 42px 11px 12px}:host ::ng-deep .iti__country-list{max-height:400px;max-height:min(60vh,400px)}}@media (hover: none) and (pointer: coarse){.ngxsmk-tel__clear{width:44px;height:44px;min-width:44px;min-height:44px}:host ::ng-deep .iti__selected-flag{min-width:44px;min-height:44px}:host ::ng-deep .iti__country{min-height:48px}:host ::ng-deep .iti__country:active{background-color:var(--tel-dd-item-hover)}.ngxsmk-tel__clear:active{background-color:#0000001a;transform:translateY(-50%) scale(.95)}}@media (-webkit-min-device-pixel-ratio: 2),(min-resolution: 192dpi){.ngxsmk-tel-input__control{border-width:.5px}}@media screen and (max-width: 768px){.ngxsmk-tel-input__control{-webkit-text-size-adjust:100%;text-size-adjust:100%}}@supports (padding: max(0px)){@media (max-width: 768px){:host ::ng-deep .iti__country-list{padding-left:max(6px,env(safe-area-inset-left));padding-right:max(6px,env(safe-area-inset-right))}}}\n"] }]
}], ctorParameters: () => [{ type: i0.NgZone, decorators: [{
type: Optional
}] }, { type: NgxsmkTelInputService }, { type: i0.ChangeDetectorRef }], propDecorators: { inputRef: [{
type: ViewChild,
args: ['telInput', { static: true }]
}], initialCountry: [{
type: Input
}], preferredCountries: [{
type: Input
}], onlyCountries: [{
type: Input
}], separateDialCode: [{
type: Input
}], allowDropdown: [{
type: Input
}], nationalDisplay: [{
type: Input
}], formatWhenValid: [{
type: Input
}], placeholder: [{
type: Input
}], autocomplete: [{
type: Input
}], name: [{
type: Input
}], inputId: [{
type: Input
}], disabled: [{
type: Input
}], label: [{
type: Input
}], hint: [{
type: Input
}], errorText: [{
type: Input
}], size: [{
type: Input
}], variant: [{
type: Input
}], showClear: [{
type: Input
}], autoFocus: [{
type: Input
}], selectOnFocus: [{
type: Input
}], showErrorWhenTouched: [{
type: Input
}], dropdownAttachToBody: [{
type: Input
}], dropdownZIndex: [{
type: Input
}], i18n: [{
type: Input,
args: ['i18n']
}], telI18n: [{
type: Input,
args: ['telI18n']
}], localizedCountries: [{
type: Input,
args: ['localizedCountries']
}], telLocalizedCountries: [{
type: Input,
args: ['telLocalizedCountries']
}], clearAriaLabel: [{
type: Input
}], dir: [{
type: Input
}], autoPlaceholder: [{
type: Input
}], utilsScript: [{
type: Input
}], customPlaceholder: [{
type: Input
}], digitsOnly: [{
type: Input
}], lockWhenValid: [{
type: Input
}], theme: [{
type: Input
}], customColors: [{
type: Input
}], enableIntelligence: [{
type: Input
}], enableFormatSuggestions: [{
type: Input
}], countryChange: [{
type: Output
}], validityChange: [{
type: Output
}], inputChange: [{
type: Output
}], intelligenceChange: [{
type: Output
}], formatSuggestion: [{
type: Output
}] } });
class ThemeService {
constructor() {
this.platformId = inject(PLATFORM_ID);
this.themeSubject = new BehaviorSubject('auto');
this.currentThemeSubject = new BehaviorSubject('light');
this.theme$ = this.themeSubject.asObservable();
this.currentTheme$ = this.currentThemeSubject.asObservable();
if (isPlatformBrowser(this.platformId)) {
this.initializeTheme();
this.setupSystemThemeListener();
}
}
/** Get current theme preference */
getTheme() {
return this.themeSubject.value;
}
/** Get current resolved theme (light or dark) */
getCurrentTheme() {
return this.currentThemeSubject.value;
}
/** Set theme preference */
setTheme(theme) {
if (!isPlatformBrowser(this.platformId))
return;
this.themeSubject.next(theme);
this.applyTheme(theme);
this.saveThemePreference(theme);
}
/** Toggle between light and dark themes */
toggleTheme() {
const current = this.getCurrentTheme();
this.setTheme(current === 'light' ? 'dark' : 'light');
}
/** Initialize theme from saved preference or system */
initializeTheme() {
const savedTheme = this.getSavedThemePreference();
const theme = savedTheme || 'auto';
this.setTheme(theme);
}
/** Apply theme to document and components */
applyTheme(theme) {
const resolvedTheme = this.resolveTheme(theme);
this.currentThemeSubject.next(resolvedTheme);
// Apply to document
document.documentElement.setAttribute('data-theme', resolvedTheme);
if (resolvedTheme === 'dark') {
document.documentElement.classList.add('dark');
}
else {
document.documentElement.classList.remove('dark');
}
// Update CSS custom properties
this.updateCSSVariables(resolvedTheme);
// Force update of all tel-input components
this.updateTelInputComponents(resolvedTheme);
}
/** Update all tel-input components with the new theme */
updateTelInputComponents(theme) {
const telInputComponents = document.querySelectorAll('ngxsmk-tel-input');
telInputComponents.forEach(component => {
const element = component;
element.setAttribute('data-theme', theme);
});
}
/** Resolve theme to light or dark */
resolveTheme(theme) {
if (theme === 'auto') {
return this.detectSystemTheme();
}
return theme;
}
/** Detect system theme preference */
detectSystemTheme() {
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
}
/** Setup listener for system theme changes */
setupSystemThemeListener() {
if (window.matchMedia) {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', () => {
if (this.getTheme() === 'auto') {
this.applyTheme('auto');
}
});
}
}
/** Update CSS custom properties for theme */
updateCSSVariables(theme) {
const root = document.documentElement;
if (theme === 'dark') {
root.style.setProperty('--tel-bg', '#212121');
root.style.setProperty('--tel-fg', '#ffffff');
root.style.setProperty('--tel-border', '#334155');
root.style.setProperty('--tel-border-hover', '#475569');
root.style.setProperty('--tel-ring', '#60a5fa');
root.style.setProperty('--tel-placeholder', '#ffffff');
root.style.setProperty('--tel-error', '#f87171');
root.style.setProperty('--tel-success', '#34d399');
root.style.setProperty('--tel-warning', '#fbbf24');
root.style.setProperty('--tel-dd-bg', '#212121');
root.style.setProperty('--tel-dd-border', '#324056');
root.style.setProperty('--tel-dd-shadow', '0 24px 60px rgba(0, 0, 0, .4)');
root.style.setProperty('--tel-dd-search-bg', 'rgba(148, 163, 184, .12)');
}
else {
root.style.setProperty('--tel-bg', '#fff');
root.style.setProperty('--tel-fg', '#0f172a');
root.style.setProperty('--tel-border', '#c0c0c0');
root.style.setProperty('--tel-border-hover', '#9aa0a6');
root.style.setProperty('--tel-ring', '#2563eb');
root.style.setProperty('--tel-placeholder', '#9ca3af');
root.style.setProperty('--tel-error', '#ef4444');
root.style.setProperty('--tel-success', '#10b981');
root.style.setProperty('--tel-warning', '#f59e0b');
root.style.setProperty('--tel-dd-bg', '#fff');
root.style.setProperty('--tel-dd-border', '#c0c0c0');
root.style.setProperty('--tel-dd-shadow', '0 24px 60px rgba(0, 0, 0, .18)');
root.style.setProperty('--tel-dd-search-bg', 'rgba(148, 163, 184, .08)');
}
}
/** Save theme preference to localStorage */
saveThemePreference(theme) {
try {
localStorage.setItem('ngxsmk-tel-input-theme', theme);
}
catch (error) {
console.warn('Failed to save theme preference:', error);
}
}
/** Get saved theme preference from localStorage */
getSavedThemePreference() {
try {
const saved = localStorage.getItem('ngxsmk-tel-input-theme');
return saved;
}
catch (error) {
console.warn('Failed to load theme preference:', error);
return null;
}
}
/** Check if dark theme is active */
isDarkTheme() {
return this.getCurrentTheme() === 'dark';
}
/** Check if light theme is active */
isLightTheme() {
return this.getCurrentTheme() === 'light';
}
/** Check if auto theme is set */
isAutoTheme() {
return this.getTheme() === 'auto';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ThemeService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ThemeService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [] });
/**
* Utility functions for phone number input optimization and performance.
* Provides helper methods for common operations like debouncing, throttling, and DOM manipulation.
*/
class PhoneInputUtils {
/**
* Converts any string to digits only (NSN - National Significant Number basis).
* Removes all non-digit characters.
*
* @param v - Input string that may contain formatting characters
* @returns String containing only digits
*
* @example
* ```typescript
* PhoneInputUtils.toNSN('(202) 555-1234'); // Returns '2025551234'
* PhoneInputUtils.toNSN('+1 202-555-1234'); // Returns '12025551234'
* ```
*/
static toNSN(v) {
return (v ?? '').replace(/\D/g, '');
}
/**
* Strips exactly one leading trunk '0' from national input.
* Used for countries like Sri Lanka where national format includes a leading 0.
*
* @param nsn - National Significant Number string
* @returns NSN with leading zero removed (if present)
*
* @example
* ```typescript
* PhoneInputUtils.stripLeadingZero('0712345678'); // Returns '712345678'
* PhoneInputUtils.stripLeadingZero('712345678'); // Returns '712345678'
* ```
*/
static stripLeadingZero(nsn) {
return nsn.replace(/^0/, '');
}
/**
* Creates a cache key for phone number operations.
* Combines input and country code into a unique key string.
*
* @param input - Phone number input string
* @param iso2 - ISO 3166-1 alpha-2 country code
* @returns Cache key string in format "input|iso2"
*
* @example
* ```typescript
* PhoneInputUtils.createCacheKey('2025551234', 'US'); // Returns '2025551234|US'
* ```
*/
static createCacheKey(input, iso2) {
return `${input || ''}|${iso2}`;
}
/**
* Creates a debounced version of a function.
* The debounced function delays execution until after wait milliseconds have elapsed
* since the last time it was invoked.
*
* @param func - Function to debounce
* @param wait - Number of milliseconds to wait
* @returns Debounced function
*
* @example
* ```typescript
* const debouncedHandler = PhoneInputUtils.debounce((value: string) => {
* console.log(value);
* }, 300);
*
* debouncedHandler('a'); // Waits 300ms
* debouncedHandler('ab'); // Cancels previous, waits 300ms
* debouncedHandler('abc'); // Cancels previous, waits 300ms, then logs 'abc'
* ```
*/
static debounce(func, wait) {
let timeout = null;
return (...args) => {
if (timeout)
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
/**
* Creates a throttled version of a function.
* The throttled function will only execute once per limit milliseconds.
*
* @param func - Function to throttle
* @param limit - Number of milliseconds between executions
* @returns Throttled function
*
* @example
* ```typescript
* const throttledHandler = PhoneInputUtils.throttle((value: string) => {
* console.log(value);
* }, 100);
*
* throttledHandler('a'); // Executes immediately
* throttledHandler('b'); // Ignored (within 100ms)
* throttledHandler('c'); // Ignored (within 100ms)
* // After 100ms, next call will execute
* ```
*/
static throttle(func, limit) {
let inThrottle;
return (...args) => {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
/**
* Checks if a string contains only digits.
*
* @param str - String to check
* @returns true if string contains only digits, false otherwise
*
* @example
* ```typescript
* PhoneInputUtils.isDigitsOnly('12345'); // Returns true
* PhoneInputUtils.isDigitsOnly('123-45'); // Returns false
* ```
*/
static isDigitsOnly(str) {
return /^\d+$/.test(str);
}
/**
* Safely gets an element's value with fallback to empty string.
*
* @param element - HTML input element (may be null)
* @returns Element value or empty string if element is null/undefined
*
* @example
* ```typescript
* const value = PhoneInputUtils.getElementValue(inputElement); // Returns value or ''
* ```
*/
static getElementValue(element) {
return element?.value?.trim() || '';
}
/**
* Checks if an element is currently visible in the viewport.
* Useful for performance optimization (e.g., lazy loading).
*
* @param element - HTML element to check
* @returns true if element is in viewport, false otherwise
*/
static isInViewport(element) {
const rect = element.getBoundingClientRect();
return (rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth));
}
/**
* Creates an optimized event listener with cleanup function.
* Returns a function that removes the event listener when called.
*
* @param element - HTML element to attach listener to
* @param event - Event name (e.g., 'click', 'input')
* @param handler - Event handler function
* @param options - Optional AddEventListenerOptions
* @returns Cleanup function that removes the event listener
*
* @example
* ```typescript
* const cleanup = PhoneInputUtils.addEventListener(element, 'click', handler);
* // Later...
* cleanup(); // Removes the event listener
* ```
*/
static createEventListener(element, event, handler, options) {
element.addEventListener(event, handler, options);
return () => element.removeEventListener(event, handler, options);
}
/**
* Batches DOM operations for better performance.
* Executes all operations in a single requestAnimationFrame callback.
*
* @param operations - Array of functions that perform DOM operations
*
* @example
* ```typescript
* PhoneInputUtils.batchDOMOperations([
* () => element1.style.display = 'none',
* () => element2.classList.add('active'),
* () => element3.textContent = 'Updated'
* ]);
* ```
*/
static batchDOMOperations(operations) {
requestAnimationFrame(() => {
operations.forEach(op => op());
});
}
}
/**
* Angular Material theme configuration helper
*/
const MATERIAL_THEME_CONFIG = new InjectionToken('MATERIAL_THEME_CONFIG');
/**
* Provides Material theme configuration
*/
function provideMaterialTheme(config = {}) {
return {
provide: MATERIAL_THEME_CONFIG,
useValue: {
primaryColor: '#3f51b5',
accentColor: '#ff4081',
warnColor: '#f44336',
density: 'comfortable',
...config
}
};
}
/**
* PrimeNG theme configuration helper
*/
const PRIMENG_THEME_CONFIG = new InjectionToken('PRIMENG_THEME_CONFIG');
/**
* Provides PrimeNG theme configuration
*/
function providePrimeNGTheme(config = {}) {
return {
provide: PRIMENG_THEME_CONFIG,
useValue: {
theme: 'lara-light',
ripple: true,
inputStyle: 'outlined',
...config
}
};
}
/**
* Mock phone service for testing
*/
/**
* Mock implementation of NgxsmkTelInputService for testing
*/
class MockNgxsmkTelInputService extends NgxsmkTelInputService {
constructor() {
super(...arguments);
this.mockValidNumbers = new Set();
this.mockInvalidNumbers = new Set();
this.mockParseResults = new Map();
this.shouldThrowError = false;
this.errorMessage = 'Mock error';
}
/**
* Add a valid phone number for testing
*/
addValidNumber(number, country = 'US') {
this.mockValidNumbers.add(`${number}|${country}`);
}
/**
* Add an invalid phone number for testing
*/
addInvalidNumber(number, country = 'US') {
this.mockInvalidNumbers.add(`${number}|${country}`);
}
/**
* Set a custom parse result for a specific input
*/
setParseResult(input, country, result) {
const key = `${input}|${country}`;
this.mockParseResults.set(key, result);
}
/**
* Configure the service to throw an error
*/
setShouldThrowError(shouldThrow, message = 'Mock error') {
this.shouldThrowError = shouldThrow;
this.errorMessage = message;
}
/**
* Clear all mock data
*/
clearMocks() {
this.mockValidNumbers.clear();
this.mockInvalidNumbers.clear();
this.mockParseResults.clear();
this.shouldThrowError = false;
this.clearCache();
}
parse(input, iso2) {
if (this.shouldThrowError) {
throw new Error(this.errorMessage);
}
const key = `${input || ''}|${iso2}`;
// Check for custom parse result
if (this.mockParseResults.has(key)) {
return this.mockParseResults.get(key);
}
// Check if it's a known valid/invalid number
const isValid = this.mockValidNumbers.has(key);
const isInvalid = this.mockInvalidNumbers.has(key);
if (isValid) {
return {
e164: input.startsWith('+') ? input : `+1${input}`,
national: input,
isValid: true
};
}
if (isInvalid) {
return {
e164: null,
national: null,
isValid: false
};
}
// Fallback to parent implementation
return super.parse(input, iso2);
}
isValid(input, iso2) {
if (this.shouldThrowError) {
throw new Error(this.errorMessage);
}
const key = `${input || ''}|${iso2}`;
if (this.mockValidNumbers.has(key)) {
return true;
}
if (this.mockInvalidNumbers.has(key)) {
return false;
}
// Fallback to parent implementation
return super.isValid(input, iso2);
}
parseWithInvalidDetection(input, iso2) {
if (this.shouldThrowError) {
throw new Error(this.errorMessage);
}
const parseResult = this.parse(input, iso2);
return {
...parseResult,
isInvalidInternational: !parseResult.isValid && input.length > 3
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MockNgxsmkTelInputService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MockNgxsmkTelInputService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MockNgxsmkTelInputService, decorators: [{
type: Injectable
}] });
/**
* Test fixtures for ngxsmk-tel-input
*/
/**
* Test data fixtures
*/
const TEST_PHONE_NUMBERS = {
valid: {
US: '+12025551234',
US_NATIONAL: '2025551234',
GB: '+442071234567',
GB_NATIONAL: '02071234567',
AU: '+61234567890',
AU_NATIONAL: '0234567890',
CA: '+14165551234',
CA_NATIONAL: '4165551234'
},
invalid: {
TOO_SHORT: '123',
TOO_LONG: '12345678901234567890',
INVALID_COUNTRY_CODE: '1123456789',
INVALID_FORMAT: 'abc123def'
}
};
const TEST_COUNTRIES = ['US', 'GB', 'AU', 'CA', 'DE', 'FR', 'JP', 'CN'];
/**
* Helper to create a test component fixture
*/
function createTestComponentFixture(fixture) {
fixture.detectChanges();
return fixture;
}
/**
* Helper to get phone input component from fixture
*/
function getPhoneInputComponent(fixture) {
const component = fixture.componentInstance;
const phoneInput = component.phoneInput || component.telInput;
if (!phoneInput) {
throw new Error('Phone input component not found. Use ViewChild with reference name "phoneInput" or "telInput"');
}
return phoneInput;
}
/**
* Helper to set phone input value
*/
function setPhoneInputValue(component, value) {
component.writeValue(value);
}
/**
* Helper to select country
*/
function selectCountry(component, country) {
component.selectCountry(country);
}
/**
* Helper to trigger input event
*/
function triggerInputEvent(fixture, value) {
const input = fixture.nativeElement.querySelector('input[type="tel"]');
if (input) {
input.value = value;
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
}
}
/**
* Helper to trigger blur event
*/
function triggerBlurEvent(fixture) {
const input = fixture.nativeElement.querySelector('input[type="tel"]');
if (input) {
input.dispatchEvent(new Event('blur'));
fixture.detectChanges();
}
}
/**
* Helper to trigger focus event
*/
function triggerFocusEvent(fixture) {
const input = fixture.nativeElement.querySelector('input[type="tel"]');
if (input) {
input.dispatchEvent(new Event('focus'));
fixture.detectChanges();
}
}
/**
* Helper to get current input value
*/
function getInputValue(fixture) {
const input = fixture.nativeElement.querySelector('input[type="tel"]');
return input ? input.value : '';
}
/**
* Helper to check if input is valid
*/
function isInputValid(component) {
return component.isValid();
}
/**
* Helper to check if input has errors
*/
function hasInputErrors(component) {
return component.hasErrors();
}
/**
* Helper to get validation errors
*/
function getValidationErrors(component) {
return component.validationStatus().errorKeys;
}
/**
* Helper to wait for async operations
*/
function waitForAsync(fn) {
return new Promise((resolve) => {
setTimeout(() => {
fn();
resolve();
}, 0);
});
}
/**
* Helper to create test form control
*/
function createTestFormControl(value = null) {
const control = {
value,
setValue: function (val) {
control.value = val;
},
patchValue: function (val) {
control.value = val;
},
reset: function (val = null) {
control.value = val;
}
};
return control;
}
/**
* Test scenarios
*/
const TEST_SCENARIOS = {
validUSNumber: {
input: TEST_PHONE_NUMBERS.valid.US_NATIONAL,
country: 'US',
expectedE164: TEST_PHONE_NUMBERS.valid.US,
expectedValid: true
},
validUKNumber: {
input: TEST_PHONE_NUMBERS.valid.GB_NATIONAL,
country: 'GB',
expectedE164: TEST_PHONE_NUMBERS.valid.GB,
expectedValid: true
},
invalidNumber: {
input: TEST_PHONE_NUMBERS.invalid.TOO_SHORT,
country: 'US',
expectedE164: null,
expectedValid: false
},
invalidCountryCode: {
input: TEST_PHONE_NUMBERS.invalid.INVALID_COUNTRY_CODE,
country: 'US',
expectedE164: null,
expectedValid: false,
expectedError: 'phoneInvalidCountryCode'
}
};
/**
* E2E testing helpers for ngxsmk-tel-input
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const by = { css: (selector) => ({ selector }) };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const element = (selector) => selector;
/**
* E2E selectors
*/
const E2E_SELECTORS = {
phoneInput: 'ngxsmk-tel-input input[type="tel"]',
phoneInputWrapper: 'ngxsmk-tel-input',
countryDropdown: '.iti__selected-flag',
countryList: '.iti__country-list',
clearButton: '.ngxsmk-tel__clear',
errorMessage: '.ngxsmk-tel__error',
hint: '.ngxsmk-tel__hint',
label: '.ngxsmk-tel__label'
};
/**
* Get phone input element
*/
function getPhoneInput() {
return element(by.css(E2E_SELECTORS.phoneInput));
}
/**
* Get phone input wrapper
*/
function getPhoneInputWrapper() {
return element(by.css(E2E_SELECTORS.phoneInputWrapper));
}
/**
* Type phone number
*/
async function typePhoneNumber(value) {
const input = getPhoneInput();
await input.clear();
await input.sendKeys(value);
}
/**
* Clear phone input
*/
async function clearPhoneInput() {
const input = getPhoneInput();
await input.clear();
}
/**
* Get phone input value
*/
async function getPhoneInputValue() {
const input = getPhoneInput();
return await input.getAttribute('value');
}
/**
* Click country dropdown
*/
async function clickCountryDropdown() {
const dropdown = element(by.css(E2E_SELECTORS.countryDropdown));
await dropdown.click();
}
/**
* Select country from dropdown
*/
async function selectCountryFromDropdown(countryCode) {
await clickCountryDropdown();
const countryItem = element(by.css(`.iti__country[data-country-code="${countryCode.toLowerCase()}"]`));
await countryItem.click();
}
/**
* Click clear button
*/
async function clickClearButton() {
const clearBtn = element(by.css(E2E_SELECTORS.clearButton));
if (await clearBtn.isPresent()) {
await clearBtn.click();
}
}
/**
* Get error message
*/
async function getErrorMessage() {
const errorEl = element(by.css(E2E_SELECTORS.errorMessage));
if (await errorEl.isPresent()) {
return await errorEl.getText();
}
return '';
}
/**
* Check if error is displayed
*/
async function isErrorDisplayed() {
const errorEl = element(by.css(E2E_SELECTORS.errorMessage));
return await errorEl.isPresent();
}
/**
* Get hint text
*/
async function getHintText() {
const hintEl = element(by.css(E2E_SELECTORS.hint));
if (await hintEl.isPresent()) {
return await hintEl.getText();
}
return '';
}
/**
* Get label text
*/
async function getLabelText() {
const labelEl = element(by.css(E2E_SELECTORS.label));
if (await labelEl.isPresent()) {
return await labelEl.getText();
}
return '';
}
/**
* Check if input is disabled
*/
async function isInputDisabled() {
const input = getPhoneInput();
return await input.getAttribute('disabled').then((value) => value !== null, () => false);
}
/**
* Focus phone input
*/
async function focusPhoneInput() {
const input = getPhoneInput();
await input.click();
}
/**
* Blur phone input
*/
async function blurPhoneInput() {
// Click outside the input
const body = element(by.tagName('body'));
await body.click();
}
/**
* Wait for validation
*/
async function waitForValidation(timeout = 1000) {
await new Promise(resolve => setTimeout(resolve, timeout));
}
/**
* Check if country dropdown is open
*/
async function isCountryDropdownOpen() {
const countryList = element(by.css(E2E_SELECTORS.countryList));
return await countryList.isPresent() && await countryList.isDisplayed();
}
/**
* Search for country in dropdown
*/
async function searchCountryInDropdown(searchTerm) {
await clickCountryDropdown();
const searchInput = element(by.css('.iti__search-input'));
if (await searchInput.isPresent()) {
await searchInput.clear();
await searchInput.sendKeys(searchTerm);
}
}
/**
* Get selected country code
*/
async function getSelectedCountryCode() {
const flag = element(by.css(E2E_SELECTORS.countryDropdown));
if (await flag.isPresent()) {
const countryCode = await flag.getAttribute('data-country-code');
return countryCode ? countryCode.toUpperCase() : '';
}
return '';
}
/**
* E2E test scenarios
*/
const E2E_SCENARIOS = {
/**
* Test valid phone number input
*/
async testValidPhoneInput(phoneNumber, countryCode = 'US') {
await selectCountryFromDropdown(countryCode);
await typePhoneNumber(phoneNumber);
await waitForValidation();
const errorDisplayed = await isErrorDisplayed();
if (errorDisplayed) {
throw new Error(`Expected valid input but error was displayed: ${await getErrorMessage()}`);
}
},
/**
* Test invalid phone number input
*/
async testInvalidPhoneInput(phoneNumber, countryCode = 'US') {
await selectCountryFromDropdown(countryCode);
await typePhoneNumber(phoneNumber);
await waitForValidation();
const errorDisplayed = await isErrorDisplayed();
if (!errorDisplayed) {
throw new Error('Expected error to be displayed for invalid input');
}
},
/**
* Test country selection
*/
async testCountrySelection(countryCode) {
await selectCountryFromDropdown(countryCode);
await waitForValidation();
const selectedCode = await getSelectedCountryCode();
if (selectedCode !== countryCode.toUpperCase()) {
throw new Error(`Expected country ${countryCode} but got ${selectedCode}`);
}
},
/**
* Test clear button
*/
async testClearButton() {
await typePhoneNumber('1234567890');
await clickClearButton();
await waitForValidation();
const value = await getPhoneInputValue();
if (value !== '') {
throw new Error(`Expected empty value after clear but got: ${value}`);
}
}
};
/**
* Testing module for ngxsmk-tel-input
*/
/**
* Testing module that provides mock services
* Note: NgxsmkTelInputComponent is standalone and should be imported directly in test components
*/
class NgxsmkTelInputTestingModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgxsmkTelInputTestingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: NgxsmkTelInputTestingModule }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgxsmkTelInputTestingModule, providers: [
{
provide: NgxsmkTelInputService,
useClass: MockNgxsmkTelInputService
}
] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgxsmkTelInputTestingModule, decorators: [{
type: NgModule,
args: [{
declarations: [],
imports: [],
exports: [],
providers: [
{
provide: NgxsmkTelInputService,
useClass: MockNgxsmkTelInputService
}
]
}]
}] });
/**
* Base verification service interface
*/
class VerificationService {
}
/**
* Twilio verification service integration
*/
const TWILIO_CONFIG = 'TWILIO_CONFIG';
class TwilioVerificationService extends VerificationService {
constructor(http, config) {
super();
this.http = http;
this.apiUrl = 'https://verify.twilio.com/v2/Services';
this.config = null;
this.config = config || null;
}
setConfig(config) {
this.config = config;
}
verify(request) {
if (!this.config) {
throw new Error('Twilio configuration not provided');
}
const serviceSid = this.config.serviceSid;
if (!serviceSid) {
throw new Error('Twilio Service SID not configured');
}
const url = `${this.apiUrl}/${serviceSid}/Verifications`;
const headers = this.getAuthHeaders();
const body = {
To: request.phoneNumber,
Channel: request.method === 'whatsapp' ? 'whatsapp' : request.method
};
return this.http.post(url, body, { headers }).pipe(map(response => ({
success: true,
sid: response.sid,
expiresAt: new Date(Date.now() + 600000) // 10 minutes default
})), catchError(error => {
return from([{
success: false,
error: error.error?.message || 'Verification failed'
}]);
}));
}
check(request) {
if (!this.config) {
throw new Error('Twilio configuration not provided');
}
const serviceSid = this.config.serviceSid;
if (!serviceSid) {
throw new Error('Twilio Service SID not configured');
}
const url = `${this.apiUrl}/${serviceSid}/VerificationCheck`;
const headers = this.getAuthHeaders();
const body = {
To: request.code,
Code: request.code
};
return this.http.post(url, body, { headers }).pipe(map(response => ({
success: true,
verified: response.status === 'approved'
})), catchError(error => {
return from([{
success: false,
verified: false,
error: error.error?.message || 'Verification check failed'
}]);
}));
}
getAuthHeaders() {
if (!this.config) {
throw new Error('Twilio configuration not provided');
}
const auth = btoa(`${this.config.accountSid}:${this.config.authToken}`);
return new HttpHeaders({
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/x-www-form-urlencoded'
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TwilioVerificationService, deps: [{ token: i1.HttpClient }, { token: TWILIO_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TwilioVerificationService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TwilioVerificationService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [TWILIO_CONFIG]
}] }] });
/**
* Provide Twilio verification service
*/
function provideTwilioVerification(config) {
return [
TwilioVerificationService,
{
provide: TWILIO_CONFIG,
useValue: config
}
];
}
/**
* Vonage (Nexmo) verification service integration
*/
const VONAGE_CONFIG = 'VONAGE_CONFIG';
class VonageVerificationService extends VerificationService {
constructor(http, config) {
super();
this.http = http;
this.apiUrl = 'https://api.nexmo.com/verify';
this.config = null;
this.config = config || null;
}
setConfig(config) {
this.config = config;
}
verify(request) {
if (!this.config) {
throw new Error('Vonage configuration not provided');
}
const url = `${this.apiUrl}/json`;
const body = new URLSearchParams({
api_key: this.config.apiKey,
api_secret: this.config.apiSecret,
number: request.phoneNumber,
brand: this.config.brand || 'App',
workflow_id: request.method === 'voice' ? '6' : '1'
});
const headers = new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded'
});
return this.http.post(url, body.toString(), { headers }).pipe(map(response => {
if (response.status === '0') {
return {
success: true,
sid: response.request_id,
expiresAt: new Date(Date.now() + 300000) // 5 minutes default
};
}
else {
return {
success: false,
error: response.error_text || 'Verification failed'
};
}
}), catchError(error => {
return from([{
success: false,
error: error.error?.message || 'Verification failed'
}]);
}));
}
check(request) {
if (!this.config) {
throw new Error('Vonage configuration not provided');
}
const url = `${this.apiUrl}/check/json`;
const body = new URLSearchParams({
api_key: this.config.apiKey,
api_secret: this.config.apiSecret,
request_id: request.sid,
code: request.code
});
const headers = new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded'
});
return this.http.post(url, body.toString(), { headers }).pipe(map(response => {
if (response.status === '0') {
return {
success: true,
verified: true
};
}
else {
return {
success: false,
verified: false,
error: response.error_text || 'Verification check failed'
};
}
}), catchError(error => {
return from([{
success: false,
verified: false,
error: error.error?.message || 'Verification check failed'
}]);
}));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: VonageVerificationService, deps: [{ token: i1.HttpClient }, { token: VONAGE_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: VonageVerificationService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: VonageVerificationService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [VONAGE_CONFIG]
}] }] });
/**
* Provide Vonage verification service
*/
function provideVonageVerification(config) {
return [
VonageVerificationService,
{
provide: VONAGE_CONFIG,
useValue: config
}
];
}
/**
* AWS SNS verification service integration
*/
const AWS_SNS_CONFIG = 'AWS_SNS_CONFIG';
class AwsSnsVerificationService extends VerificationService {
constructor(http, config) {
super();
this.http = http;
this.config = null;
this.config = config || null;
}
setConfig(config) {
this.config = config;
}
verify(request) {
if (!this.config) {
throw new Error('AWS SNS configuration not provided');
}
// Generate OTP code (6 digits)
const code = Math.floor(100000 + Math.random() * 900000).toString();
// In a real implementation, you would:
// 1. Store the code in a database/cache with the phone number
// 2. Send SMS via AWS SNS
// 3. Return the verification ID
// For now, we'll simulate the API call
const message = `Your verification code is: ${code}`;
// Note: Actual AWS SNS integration requires AWS SDK and proper authentication
// This is a simplified example
return from(this.sendSmsViaSns(request.phoneNumber, message)).pipe(map(() => ({
success: true,
sid: `aws-sns-${Date.now()}`,
expiresAt: new Date(Date.now() + 600000) // 10 minutes
})), catchError(error => {
return from([{
success: false,
error: error.message || 'SMS sending failed'
}]);
}));
}
check(request) {
// In a real implementation, you would:
// 1. Retrieve the stored code for the verification ID
// 2. Compare with the provided code
// 3. Return verification result
// This is a simplified example
return from([{
success: true,
verified: true // In real implementation, check against stored code
}]);
}
async sendSmsViaSns(phoneNumber, message) {
// Actual AWS SNS implementation would use AWS SDK
// This is a placeholder
if (!this.config) {
throw new Error('AWS SNS configuration not provided');
}
// Example AWS SNS API call (simplified)
// const sns = new AWS.SNS({
// accessKeyId: this.config.accessKeyId,
// secretAccessKey: this.config.secretAccessKey,
// region: this.config.region
// });
//
// await sns.publish({
// PhoneNumber: phoneNumber,
// Message: message
// }).promise();
// For now, just simulate success
return Promise.resolve();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AwsSnsVerificationService, deps: [{ token: i1.HttpClient }, { token: AWS_SNS_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AwsSnsVerificationService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AwsSnsVerificationService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [AWS_SNS_CONFIG]
}] }] });
/**
* Provide AWS SNS verification service
*/
function provideAwsSnsVerification(config) {
return [
AwsSnsVerificationService,
{
provide: AWS_SNS_CONFIG,
useValue: config
}
];
}
/**
* Enhanced TypeScript types and type guards for ngxsmk-tel-input
*/
/**
* Type guard to check if a value is a valid CountryCode
*/
function isCountryCode(value) {
if (typeof value !== 'string') {
return false;
}
// Basic validation - ISO 3166-1 alpha-2 codes are 2 uppercase letters
return /^[A-Z]{2}$/.test(value);
}
/**
* Type guard to check if a value is a valid phone number string
*/
function isPhoneNumberString(value) {
if (typeof value !== 'string') {
return false;
}
// Basic validation - contains digits and optionally + or spaces/dashes
return /^[\d\s\-+()]+$/.test(value) && value.replace(/\D/g, '').length >= 3;
}
/**
* Type guard to check if ParseResult is valid
*/
function isValidParseResult(result) {
return result.isValid === true && result.e164 !== null;
}
/**
* Type guard to check if value is E.164 format
*/
function isE164Format(value) {
if (typeof value !== 'string') {
return false;
}
// E.164 format: + followed by 1-15 digits
return /^\+[1-9]\d{1,14}$/.test(value);
}
/**
* Type guard to check if CarrierInfo is complete
*/
function isCompleteCarrierInfo(info) {
return info !== null && info.type !== 'UNKNOWN';
}
/**
* Type guard to check if FormatSuggestion has high confidence
*/
function isHighConfidenceSuggestion(suggestion) {
return suggestion !== null && suggestion.confidence >= 0.7;
}
/**
* Create a branded E.164 phone number
*/
function createE164PhoneNumber(value) {
if (isE164Format(value)) {
return value;
}
return null;
}
/**
* Create a branded national phone number
*/
function createNationalPhoneNumber(value, country) {
if (isPhoneNumberString(value)) {
return value;
}
return null;
}
/**
* Create a typed validation result
*/
function createTypedValidationResult(parseResult, country) {
const errors = [];
if (!parseResult.isValid) {
if (parseResult.e164 === null) {
errors.push({
code: 'INVALID',
message: 'Invalid phone number format'
});
}
}
return {
isValid: parseResult.isValid,
e164: parseResult.e164 ? parseResult.e164 : null,
national: parseResult.national ? parseResult.national : null,
country,
errors: errors
};
}
/**
* Assert that a value is a CountryCode (throws if not)
*/
function assertCountryCode(value) {
if (!isCountryCode(value)) {
throw new TypeError(`Expected CountryCode, got ${typeof value}: ${value}`);
}
}
/**
* Assert that a value is E.164 format (throws if not)
*/
function assertE164Format(value) {
if (!isE164Format(value)) {
throw new TypeError(`Expected E.164 format phone number, got: ${value}`);
}
}
/**
* Generated bundle index. Do not edit.
*/
export { AwsSnsVerificationService, E2E_SCENARIOS, E2E_SELECTORS, MATERIAL_THEME_CONFIG, MockNgxsmkTelInputService, NgxsmkTelInputComponent, NgxsmkTelInputService, NgxsmkTelInputTestingModule, PRIMENG_THEME_CONFIG, PhoneInputUtils, PhoneIntelligenceService, TEST_COUNTRIES, TEST_PHONE_NUMBERS, TEST_SCENARIOS, ThemeService, TwilioVerificationService, VerificationService, VonageVerificationService, assertCountryCode, assertE164Format, clearPhoneInput, clickClearButton, clickCountryDropdown, createE164PhoneNumber, createFormattedValueSignal, createNationalPhoneNumber, createPhoneInputState, createPhoneMetadataSignal, createTestComponentFixture, createTypedValidationResult, createValidationStatusSignal, getErrorMessage, getInputValue, getPhoneInput, getPhoneInputComponent, getPhoneInputValue, getValidationErrors, hasInputErrors, isCompleteCarrierInfo, isCountryCode, isE164Format, isErrorDisplayed, isHighConfidenceSuggestion, isInputValid, isPhoneNumberString, isValidParseResult, provideAwsSnsVerification, provideMaterialTheme, providePrimeNGTheme, provideTwilioVerification, provideVonageVerification, selectCountry, selectCountryFromDropdown, setPhoneInputValue, triggerBlurEvent, triggerFocusEvent, triggerInputEvent, typePhoneNumber };
//# sourceMappingURL=ngxsmk-tel-input.mjs.map