ngxsmk-tel-input 1.6.5 → 1.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1137 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Injectable, EventEmitter, inject, PLATFORM_ID, forwardRef, Output, Input, ViewChild, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { isPlatformBrowser } from '@angular/common';
4
+ import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
5
+ import { parsePhoneNumberFromString, AsYouType, validatePhoneNumberLength } from 'libphonenumber-js';
6
+ import { BehaviorSubject } from 'rxjs';
7
+
8
+ class NgxsmkTelInputService {
9
+ constructor() {
10
+ this.parseCache = new Map();
11
+ this.validationCache = new Map();
12
+ this.CACHE_SIZE_LIMIT = 1000;
13
+ }
14
+ parse(input, iso2) {
15
+ const cacheKey = `${input || ''}|${iso2}`;
16
+ if (this.parseCache.has(cacheKey)) {
17
+ return this.parseCache.get(cacheKey);
18
+ }
19
+ const phone = parsePhoneNumberFromString(input || '', iso2);
20
+ if (!phone) {
21
+ const result = { e164: null, national: null, isValid: false };
22
+ this.setCacheValue(this.parseCache, cacheKey, result);
23
+ return result;
24
+ }
25
+ const isValid = phone.isValid();
26
+ const result = {
27
+ e164: isValid ? phone.number : null,
28
+ national: phone.formatNational(),
29
+ isValid
30
+ };
31
+ this.setCacheValue(this.parseCache, cacheKey, result);
32
+ return result;
33
+ }
34
+ isValid(input, iso2) {
35
+ const cacheKey = `${input || ''}|${iso2}`;
36
+ if (this.validationCache.has(cacheKey)) {
37
+ return this.validationCache.get(cacheKey);
38
+ }
39
+ const phone = parsePhoneNumberFromString(input || '', iso2);
40
+ const result = !!phone && phone.isValid();
41
+ this.setCacheValue(this.validationCache, cacheKey, result);
42
+ return result;
43
+ }
44
+ setCacheValue(cache, key, value) {
45
+ if (cache.size >= this.CACHE_SIZE_LIMIT) {
46
+ const firstKey = cache.keys().next().value;
47
+ if (firstKey)
48
+ cache.delete(firstKey);
49
+ }
50
+ cache.set(key, value);
51
+ }
52
+ clearCache() {
53
+ this.parseCache.clear();
54
+ this.validationCache.clear();
55
+ }
56
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgxsmkTelInputService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
57
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgxsmkTelInputService, providedIn: 'root' }); }
58
+ }
59
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgxsmkTelInputService, decorators: [{
60
+ type: Injectable,
61
+ args: [{ providedIn: 'root' }]
62
+ }] });
63
+
64
+ class NgxsmkTelInputComponent {
65
+ set telI18n(v) { this.i18n = v; }
66
+ set telLocalizedCountries(v) { this.localizedCountries = v; }
67
+ constructor(zone, tel) {
68
+ this.zone = zone;
69
+ this.tel = tel;
70
+ /* Core config */
71
+ this.initialCountry = 'US';
72
+ this.preferredCountries = ['US', 'GB'];
73
+ /** Dropdown shows dial code; input will NEVER show dial code */
74
+ this.separateDialCode = true;
75
+ this.allowDropdown = true;
76
+ /* Display / formatting */
77
+ /** 'formatted' => national with spaces; 'digits' => digits only */
78
+ this.nationalDisplay = 'formatted';
79
+ /** 'typing' (live), 'blur', or 'off' */
80
+ this.formatWhenValid = 'typing';
81
+ this.autocomplete = 'tel';
82
+ this.disabled = false;
83
+ this.size = 'md';
84
+ this.variant = 'outline';
85
+ this.showClear = true;
86
+ this.autoFocus = false;
87
+ this.selectOnFocus = false;
88
+ this.showErrorWhenTouched = true;
89
+ /* Dropdown plumbing */
90
+ this.dropdownAttachToBody = true;
91
+ this.dropdownZIndex = 2000;
92
+ this.clearAriaLabel = 'Clear phone number';
93
+ this.dir = 'ltr';
94
+ /* Placeholders (intl-tel-input) */
95
+ this.autoPlaceholder = 'off';
96
+ /* Input behavior */
97
+ this.digitsOnly = true; // Still insert spaces when formatted
98
+ this.lockWhenValid = true; // optional UX guard
99
+ /* Theme */
100
+ this.theme = 'auto';
101
+ /* Outputs */
102
+ this.countryChange = new EventEmitter();
103
+ this.validityChange = new EventEmitter();
104
+ this.inputChange = new EventEmitter();
105
+ /* Internal */
106
+ this.iti = null;
107
+ this.onChange = () => { };
108
+ this.onTouchedCb = () => { };
109
+ this.lastEmittedValid = false;
110
+ this.pendingWrite = null;
111
+ this.touched = false;
112
+ this.isDestroyed = false;
113
+ this.eventListeners = [];
114
+ this.allowDropdownWasTrue = false;
115
+ this.suppressEvents = false;
116
+ this.resolvedId = this.inputId || ('tel-' + Math.random().toString(36).slice(2));
117
+ this.platformId = inject(PLATFORM_ID);
118
+ this.currentTheme = 'light';
119
+ }
120
+ // ---------- Lifecycle ----------
121
+ ngAfterViewInit() {
122
+ if (!isPlatformBrowser(this.platformId) || this.isDestroyed)
123
+ return;
124
+ this.detectAndApplyTheme();
125
+ this.setupDropdownThemeObserver();
126
+ void this.initAndWire();
127
+ }
128
+ async initAndWire() {
129
+ if (this.isDestroyed)
130
+ return;
131
+ await this.initIntlTelInput();
132
+ this.bindDomListeners();
133
+ if (this.pendingWrite !== null) {
134
+ const v = this.pendingWrite;
135
+ this.pendingWrite = null;
136
+ this.writeValue(v);
137
+ }
138
+ if (this.autoFocus && !this.isDestroyed) {
139
+ requestAnimationFrame(() => {
140
+ if (!this.isDestroyed)
141
+ this.focus();
142
+ });
143
+ }
144
+ }
145
+ ngOnChanges(changes) {
146
+ if (!isPlatformBrowser(this.platformId) || this.isDestroyed)
147
+ return;
148
+ const configChanged = [
149
+ 'initialCountry', 'preferredCountries', 'onlyCountries',
150
+ 'separateDialCode', 'allowDropdown',
151
+ 'i18n', 'localizedCountries', 'dir',
152
+ 'autoPlaceholder', 'utilsScript', 'customPlaceholder'
153
+ ].some(k => k in changes && !changes[k]?.firstChange);
154
+ if (configChanged && this.iti && !this.isDestroyed) {
155
+ this.reinitPlugin();
156
+ this.validatorChange?.();
157
+ }
158
+ // Handle theme changes
159
+ if ('theme' in changes && !changes['theme']?.firstChange) {
160
+ this.detectAndApplyTheme();
161
+ }
162
+ }
163
+ ngOnDestroy() {
164
+ this.isDestroyed = true;
165
+ this.destroyPlugin();
166
+ this.cleanupEventListeners();
167
+ }
168
+ // ---------- CVA ----------
169
+ writeValue(val) {
170
+ if (!this.inputRef || this.isDestroyed)
171
+ return;
172
+ if (!this.iti) { // not ready yet
173
+ this.pendingWrite = val ?? '';
174
+ return;
175
+ }
176
+ this.suppressEvents = true;
177
+ try {
178
+ // Let the plugin infer the country from E.164 (if provided).
179
+ this.iti.setNumber(val || '');
180
+ const iso2 = this.currentIso2();
181
+ const parsed = this.tel.parse(val ?? '', iso2);
182
+ // Visible input: ALWAYS NSN (no dial code, no trunk '0')
183
+ const nsn = parsed.e164
184
+ ? this.nsnFromE164(parsed.e164, iso2)
185
+ : this.stripLeadingZero(this.toNSN(parsed.national ?? (val ?? '')));
186
+ const display = this.displayValue(nsn, iso2);
187
+ this.setInputValue(display);
188
+ // FormControl value: ALWAYS E.164 (or null) - batch zone runs
189
+ this.zone.run(() => {
190
+ this.onChange(parsed.e164);
191
+ this.inputChange.emit({ raw: display, e164: parsed.e164, iso2 });
192
+ });
193
+ }
194
+ finally {
195
+ this.suppressEvents = false;
196
+ }
197
+ }
198
+ registerOnChange(fn) { this.onChange = fn; }
199
+ registerOnTouched(fn) { this.onTouchedCb = fn; }
200
+ setDisabledState(isDisabled) {
201
+ if (this.isDestroyed)
202
+ return;
203
+ this.disabled = isDisabled;
204
+ // 1) native input
205
+ if (this.inputRef)
206
+ this.inputRef.nativeElement.disabled = isDisabled;
207
+ // 2) toggle dropdown by re-init with allowDropdown=false when disabled
208
+ if (this.iti) {
209
+ if (isDisabled && this.allowDropdown) {
210
+ this.allowDropdownWasTrue = true;
211
+ this.allowDropdown = false;
212
+ this.reinitPlugin(); // closes popup & removes handlers
213
+ }
214
+ else if (!isDisabled && this.allowDropdownWasTrue) {
215
+ this.allowDropdown = true;
216
+ this.allowDropdownWasTrue = false;
217
+ this.reinitPlugin(); // restore dropdown
218
+ }
219
+ else {
220
+ this.applyDisabledUi(isDisabled);
221
+ }
222
+ }
223
+ else {
224
+ this.applyDisabledUi(isDisabled);
225
+ }
226
+ }
227
+ // ---------- Validator ----------
228
+ validate(_) {
229
+ if (this.isDestroyed)
230
+ return null;
231
+ const raw = this.currentRaw();
232
+ if (!raw)
233
+ return null;
234
+ const valid = this.tel.isValid(raw, this.currentIso2());
235
+ if (valid !== this.lastEmittedValid) {
236
+ this.lastEmittedValid = valid;
237
+ this.validityChange.emit(valid);
238
+ }
239
+ return valid ? null : { phoneInvalid: true };
240
+ }
241
+ registerOnValidatorChange(fn) { this.validatorChange = fn; }
242
+ // ---------- Public helpers ----------
243
+ focus() {
244
+ if (this.isDestroyed || !this.inputRef)
245
+ return;
246
+ this.inputRef.nativeElement.focus();
247
+ if (this.selectOnFocus) {
248
+ const el = this.inputRef.nativeElement;
249
+ requestAnimationFrame(() => {
250
+ if (!this.isDestroyed)
251
+ el.setSelectionRange(0, el.value.length);
252
+ });
253
+ }
254
+ }
255
+ selectCountry(iso2) {
256
+ if (this.iti && !this.isDestroyed) {
257
+ this.iti.setCountry(iso2.toLowerCase());
258
+ this.handleInput();
259
+ }
260
+ }
261
+ clearInput() {
262
+ if (this.isDestroyed)
263
+ return;
264
+ this.setInputValue('');
265
+ this.handleInput();
266
+ this.inputRef.nativeElement.focus();
267
+ }
268
+ // ---------- intl-tel-input wiring ----------
269
+ async initIntlTelInput() {
270
+ if (this.isDestroyed)
271
+ return;
272
+ const [{ default: intlTelInput }] = await Promise.all([import('intl-tel-input')]);
273
+ const toLowerKeys = (m) => {
274
+ if (!m)
275
+ return undefined;
276
+ const out = {};
277
+ for (const k in m)
278
+ if (Object.prototype.hasOwnProperty.call(m, k)) {
279
+ const v = m[k];
280
+ if (v != null)
281
+ out[k.toLowerCase()] = v;
282
+ }
283
+ return out;
284
+ };
285
+ const config = {
286
+ initialCountry: this.initialCountry === 'auto' ? 'auto' : (this.initialCountry?.toLowerCase() || 'us'),
287
+ preferredCountries: (this.preferredCountries ?? []).map(c => c.toLowerCase()),
288
+ onlyCountries: (this.onlyCountries ?? []).map(c => c.toLowerCase()),
289
+ nationalMode: true, // Control the visible value; prevents '+' in the input
290
+ allowDropdown: this.allowDropdown,
291
+ separateDialCode: this.separateDialCode,
292
+ geoIpLookup: (cb) => cb('us'),
293
+ autoPlaceholder: this.autoPlaceholder,
294
+ utilsScript: this.utilsScript,
295
+ customPlaceholder: this.customPlaceholder,
296
+ i18n: this.i18n,
297
+ localizedCountries: toLowerKeys(this.localizedCountries),
298
+ dropdownContainer: this.dropdownAttachToBody && typeof document !== 'undefined' ? document.body : undefined
299
+ };
300
+ this.zone.runOutsideAngular(() => {
301
+ if (!this.isDestroyed) {
302
+ this.iti = intlTelInput(this.inputRef.nativeElement, config);
303
+ }
304
+ });
305
+ if (!this.isDestroyed) {
306
+ this.inputRef.nativeElement.style.setProperty('--tel-dd-z', String(this.dropdownZIndex));
307
+ this.applyDisabledUi(this.disabled);
308
+ }
309
+ }
310
+ async reinitPlugin() {
311
+ if (this.isDestroyed)
312
+ return;
313
+ const prevIso2 = (this.iti?.getSelectedCountryData?.().iso2 || this.initialCountry || 'US').toString().toLowerCase();
314
+ const prevValue = this.currentRaw();
315
+ this.destroyPlugin();
316
+ await this.initIntlTelInput();
317
+ this.bindDomListeners();
318
+ if (!this.isDestroyed) {
319
+ try {
320
+ this.iti?.setCountry(prevIso2);
321
+ }
322
+ catch { }
323
+ if (prevValue) {
324
+ this.setInputValue(prevValue);
325
+ this.handleInput();
326
+ }
327
+ this.applyDisabledUi(this.disabled);
328
+ }
329
+ }
330
+ destroyPlugin() {
331
+ if (this.iti) {
332
+ this.iti.destroy();
333
+ this.iti = null;
334
+ }
335
+ // Optimize DOM manipulation - only clone if necessary
336
+ if (this.inputRef?.nativeElement && this.iti) {
337
+ const el = this.inputRef.nativeElement;
338
+ const clone = el.cloneNode(true);
339
+ // preserve state across clone
340
+ clone.disabled = this.disabled;
341
+ clone.id = this.resolvedId;
342
+ clone.name = this.name ?? clone.name;
343
+ clone.value = el.value;
344
+ el.parentNode?.replaceChild(clone, el);
345
+ this.inputRef.nativeElement = clone;
346
+ }
347
+ }
348
+ // ---------- Input listeners ----------
349
+ bindDomListeners() {
350
+ if (this.isDestroyed || !this.inputRef)
351
+ return;
352
+ const el = this.inputRef.nativeElement;
353
+ this.zone.runOutsideAngular(() => {
354
+ const beforeInputHandler = (ev) => {
355
+ if (this.isDestroyed || !this.digitsOnly)
356
+ return;
357
+ const data = ev.data;
358
+ // If already valid, block extra digit insertions when no selection
359
+ if (this.lockWhenValid && this.isCurrentlyValid()) {
360
+ const selCollapsed = (el.selectionStart ?? 0) === (el.selectionEnd ?? 0);
361
+ const isDigit = !!data && ev.inputType === 'insertText' && data >= '0' && data <= '9';
362
+ if (selCollapsed && isDigit) {
363
+ ev.preventDefault();
364
+ return;
365
+ }
366
+ }
367
+ if (!data || ev.inputType !== 'insertText')
368
+ return;
369
+ const isDigit = data >= '0' && data <= '9';
370
+ if (!isDigit) {
371
+ ev.preventDefault();
372
+ return;
373
+ }
374
+ // NEW: block if this digit would make the NSN too long for the current country
375
+ const start = el.selectionStart ?? el.value.length;
376
+ const end = el.selectionEnd ?? el.value.length;
377
+ const prospective = el.value.slice(0, start) + data + el.value.slice(end);
378
+ const nsn = this.stripLeadingZero(this.toNSN(prospective));
379
+ const iso2 = this.currentIso2();
380
+ if (this.wouldExceedMax(nsn, iso2)) {
381
+ ev.preventDefault();
382
+ return;
383
+ }
384
+ };
385
+ const pasteHandler = (e) => {
386
+ if (this.isDestroyed)
387
+ return;
388
+ const text = (e.clipboardData || window.clipboardData).getData('text') || '';
389
+ e.preventDefault();
390
+ const iso2 = this.currentIso2();
391
+ // digits-only, strip any leading trunk '0'
392
+ let digits = this.stripLeadingZero(this.toNSN(text));
393
+ // NEW: trim pasted digits until not TOO_LONG for the selected country
394
+ while (this.wouldExceedMax(digits, iso2)) {
395
+ digits = digits.slice(0, -1);
396
+ if (!digits)
397
+ break;
398
+ }
399
+ const start = el.selectionStart ?? el.value.length;
400
+ const end = el.selectionEnd ?? el.value.length;
401
+ el.setRangeText(digits, start, end, 'end');
402
+ requestAnimationFrame(() => {
403
+ if (!this.isDestroyed)
404
+ this.handleInput();
405
+ });
406
+ };
407
+ const inputHandler = () => {
408
+ if (!this.isDestroyed)
409
+ this.handleInput();
410
+ };
411
+ const countryChangeHandler = () => {
412
+ if (this.isDestroyed)
413
+ return;
414
+ const iso2 = this.currentIso2();
415
+ this.zone.run(() => {
416
+ this.countryChange.emit({ iso2 });
417
+ this.validatorChange?.();
418
+ });
419
+ this.handleInput();
420
+ };
421
+ const blurHandler = () => {
422
+ if (!this.isDestroyed)
423
+ this.onBlur();
424
+ };
425
+ // Store listeners for cleanup
426
+ this.eventListeners = [
427
+ { element: el, event: 'beforeinput', handler: beforeInputHandler },
428
+ { element: el, event: 'paste', handler: pasteHandler },
429
+ { element: el, event: 'input', handler: inputHandler },
430
+ { element: el, event: 'countrychange', handler: countryChangeHandler },
431
+ { element: el, event: 'blur', handler: blurHandler }
432
+ ];
433
+ this.eventListeners.forEach(({ element, event, handler }) => {
434
+ element.addEventListener(event, handler);
435
+ });
436
+ });
437
+ }
438
+ // ---------- UX handlers ----------
439
+ onBlur() {
440
+ if (this.isDestroyed)
441
+ return;
442
+ this.touched = true;
443
+ this.zone.run(() => this.onTouchedCb());
444
+ if (this.formatWhenValid === 'off')
445
+ return;
446
+ const iso2 = this.currentIso2();
447
+ const digits = this.stripLeadingZero(this.toNSN(this.currentRaw()));
448
+ const parsed = this.tel.parse(digits, iso2);
449
+ if (!parsed.e164 && !parsed.isValid)
450
+ return;
451
+ const nsn = parsed.e164 ? this.nsnFromE164(parsed.e164, iso2) : digits;
452
+ if (this.formatWhenValid !== 'typing') {
453
+ this.setInputValue(this.displayValue(nsn, iso2));
454
+ }
455
+ }
456
+ onFocus() {
457
+ if (this.isDestroyed || !this.selectOnFocus || !this.inputRef)
458
+ return;
459
+ const el = this.inputRef.nativeElement;
460
+ requestAnimationFrame(() => {
461
+ if (!this.isDestroyed)
462
+ el.setSelectionRange(0, el.value.length);
463
+ });
464
+ }
465
+ // ---------- Core input pipeline ----------
466
+ handleInput() {
467
+ if (this.suppressEvents || this.isDestroyed)
468
+ return;
469
+ const iso2 = this.currentIso2();
470
+ // Users type national digits; remove any separators and a single trunk '0'
471
+ let digits = this.stripLeadingZero(this.toNSN(this.currentRaw()));
472
+ const parsed = this.tel.parse(digits, iso2);
473
+ // Emit E.164 to the form (or null if incomplete) - batch zone runs
474
+ this.zone.run(() => {
475
+ this.onChange(parsed.e164);
476
+ this.inputChange.emit({ raw: this.currentRaw(), e164: parsed.e164, iso2 });
477
+ });
478
+ // Keep visible value as NSN (optionally formatted)
479
+ const nsn = parsed.e164 ? this.nsnFromE164(parsed.e164, iso2) : digits;
480
+ const display = this.formatWhenValid === 'typing' ? this.displayValue(nsn, iso2) : nsn;
481
+ if (display !== this.currentRaw())
482
+ this.setInputValue(display);
483
+ }
484
+ // ---------- Utilities ----------
485
+ /** Convert any string to digits only (NSN basis). */
486
+ toNSN(v) {
487
+ return (v ?? '').replace(/\D/g, '');
488
+ }
489
+ /** Strip exactly one leading trunk '0' from national input. */
490
+ stripLeadingZero(nsn) {
491
+ return nsn.replace(/^0/, '');
492
+ }
493
+ /** Current country calling code (e.g. "44", "94"). */
494
+ currentDialCode() {
495
+ return (this.iti?.getSelectedCountryData?.().dialCode ?? '').toString();
496
+ }
497
+ /** Convert E.164 (+<cc><nsn>) to NSN (never includes trunk '0'). */
498
+ nsnFromE164(e164, iso2) {
499
+ const dial = this.currentDialCode();
500
+ if (!e164 || !dial)
501
+ return this.toNSN(e164);
502
+ if (e164.startsWith('+' + dial))
503
+ return e164.slice(dial.length + 1);
504
+ return this.toNSN(e164);
505
+ }
506
+ /** Format NSN for a region (adds spaces but NEVER a trunk '0'). */
507
+ formatNSN(nsn, iso2) {
508
+ try {
509
+ const fmt = new AsYouType(iso2);
510
+ return fmt.input(nsn);
511
+ }
512
+ catch {
513
+ return nsn;
514
+ }
515
+ }
516
+ /** Compose visible value based on settings. */
517
+ displayValue(nsn, iso2) {
518
+ return this.nationalDisplay === 'formatted' ? this.formatNSN(nsn, iso2) : nsn;
519
+ // (spaces in formatted mode; digits only otherwise)
520
+ }
521
+ currentRaw() {
522
+ return this.isDestroyed ? '' : (this.inputRef?.nativeElement.value ?? '').trim();
523
+ }
524
+ currentIso2() {
525
+ if (this.isDestroyed)
526
+ return 'US';
527
+ const iso2 = (this.iti?.getSelectedCountryData?.().iso2 ?? this.initialCountry ?? 'US')
528
+ .toString().toUpperCase();
529
+ return iso2;
530
+ }
531
+ setInputValue(v) {
532
+ if (!this.isDestroyed && this.inputRef) {
533
+ this.inputRef.nativeElement.value = v ?? '';
534
+ }
535
+ }
536
+ get showError() {
537
+ if (this.isDestroyed)
538
+ return false;
539
+ const invalid = !!this.validate({});
540
+ return this.showErrorWhenTouched ? (this.touched && invalid) : invalid;
541
+ }
542
+ isCurrentlyValid() {
543
+ return this.isDestroyed ? false : this.tel.isValid(this.currentRaw(), this.currentIso2());
544
+ }
545
+ /** Make flag/dropdown non-interactive when disabled */
546
+ applyDisabledUi(disabled) {
547
+ if (this.isDestroyed)
548
+ return;
549
+ const input = this.inputRef?.nativeElement;
550
+ if (!input)
551
+ return;
552
+ const flag = input.parentElement?.querySelector('.iti__selected-flag');
553
+ if (flag) {
554
+ flag.tabIndex = disabled ? -1 : 0;
555
+ flag.setAttribute('aria-disabled', String(disabled));
556
+ }
557
+ }
558
+ /** Returns true if nsn would be TOO_LONG for the current country. */
559
+ wouldExceedMax(nsn, iso2) {
560
+ if (this.isDestroyed)
561
+ return false;
562
+ try {
563
+ const res = validatePhoneNumberLength(nsn, iso2);
564
+ return res === 'TOO_LONG';
565
+ }
566
+ catch {
567
+ return false;
568
+ }
569
+ }
570
+ /** Clean up event listeners to prevent memory leaks */
571
+ cleanupEventListeners() {
572
+ this.eventListeners.forEach(({ element, event, handler }) => {
573
+ element.removeEventListener(event, handler);
574
+ });
575
+ this.eventListeners = [];
576
+ }
577
+ /** Detect and apply theme based on user preference and system settings */
578
+ detectAndApplyTheme() {
579
+ if (!isPlatformBrowser(this.platformId))
580
+ return;
581
+ let detectedTheme = 'light';
582
+ if (this.theme === 'auto') {
583
+ // Check for system preference
584
+ if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
585
+ detectedTheme = 'dark';
586
+ }
587
+ // Check for document class
588
+ else if (document.documentElement.classList.contains('dark')) {
589
+ detectedTheme = 'dark';
590
+ }
591
+ // Check for data attribute
592
+ else if (document.documentElement.getAttribute('data-theme') === 'dark') {
593
+ detectedTheme = 'dark';
594
+ }
595
+ }
596
+ else {
597
+ detectedTheme = this.theme;
598
+ }
599
+ this.currentTheme = detectedTheme;
600
+ this.applyTheme(detectedTheme);
601
+ }
602
+ /** Apply theme to the component */
603
+ applyTheme(theme) {
604
+ if (!isPlatformBrowser(this.platformId))
605
+ return;
606
+ const hostElement = this.inputRef?.nativeElement?.closest('ngxsmk-tel-input');
607
+ if (hostElement) {
608
+ hostElement.setAttribute('data-theme', theme);
609
+ }
610
+ // Also apply theme to any existing dropdown
611
+ this.applyThemeToDropdown(theme);
612
+ }
613
+ /** Apply theme to the dropdown if it exists */
614
+ applyThemeToDropdown(theme) {
615
+ if (!isPlatformBrowser(this.platformId))
616
+ return;
617
+ // Find the dropdown in the document
618
+ const dropdown = document.querySelector('.iti__country-list');
619
+ if (dropdown) {
620
+ dropdown.setAttribute('data-theme', theme);
621
+ }
622
+ }
623
+ /** Get current theme */
624
+ getCurrentTheme() {
625
+ return this.currentTheme;
626
+ }
627
+ /** Set theme programmatically */
628
+ setTheme(theme) {
629
+ this.theme = theme;
630
+ this.detectAndApplyTheme();
631
+ }
632
+ /** Update dropdown theme when it's opened */
633
+ updateDropdownTheme() {
634
+ if (!isPlatformBrowser(this.platformId))
635
+ return;
636
+ // Use a small delay to ensure dropdown is rendered
637
+ setTimeout(() => {
638
+ const dropdown = document.querySelector('.iti__country-list');
639
+ if (dropdown) {
640
+ dropdown.setAttribute('data-theme', this.currentTheme);
641
+ // Force apply dark theme classes
642
+ if (this.currentTheme === 'dark') {
643
+ dropdown.classList.add('dark-theme');
644
+ document.documentElement.classList.add('dark');
645
+ document.body.classList.add('dark');
646
+ }
647
+ else {
648
+ dropdown.classList.remove('dark-theme');
649
+ document.documentElement.classList.remove('dark');
650
+ document.body.classList.remove('dark');
651
+ }
652
+ // Also update the search input if it exists
653
+ const searchInput = dropdown.querySelector('.iti__search-input');
654
+ if (searchInput) {
655
+ searchInput.setAttribute('data-theme', this.currentTheme);
656
+ if (this.currentTheme === 'dark') {
657
+ searchInput.classList.add('dark-theme');
658
+ }
659
+ else {
660
+ searchInput.classList.remove('dark-theme');
661
+ }
662
+ // Ensure search input is focusable and functional
663
+ searchInput.style.pointerEvents = 'auto';
664
+ searchInput.style.opacity = '1';
665
+ searchInput.disabled = false;
666
+ }
667
+ }
668
+ }, 10);
669
+ }
670
+ /** Setup observer to watch for dropdown changes and apply theme */
671
+ setupDropdownThemeObserver() {
672
+ if (!isPlatformBrowser(this.platformId))
673
+ return;
674
+ // Watch for dropdown appearance in the DOM
675
+ const observer = new MutationObserver((mutations) => {
676
+ mutations.forEach((mutation) => {
677
+ if (mutation.type === 'childList') {
678
+ mutation.addedNodes.forEach((node) => {
679
+ if (node.nodeType === Node.ELEMENT_NODE) {
680
+ const element = node;
681
+ if (element.classList.contains('iti__country-list')) {
682
+ this.updateDropdownTheme();
683
+ }
684
+ }
685
+ });
686
+ }
687
+ });
688
+ });
689
+ // Start observing
690
+ observer.observe(document.body, {
691
+ childList: true,
692
+ subtree: true
693
+ });
694
+ // Store observer for cleanup
695
+ this.eventListeners.push({
696
+ element: document.body,
697
+ event: 'observer',
698
+ handler: () => observer.disconnect()
699
+ });
700
+ }
701
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgxsmkTelInputComponent, deps: [{ token: i0.NgZone }, { token: NgxsmkTelInputService }], target: i0.ɵɵFactoryTarget.Component }); }
702
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: NgxsmkTelInputComponent, isStandalone: true, selector: "ngxsmk-tel-input", inputs: { initialCountry: "initialCountry", preferredCountries: "preferredCountries", onlyCountries: "onlyCountries", separateDialCode: "separateDialCode", allowDropdown: "allowDropdown", nationalDisplay: "nationalDisplay", formatWhenValid: "formatWhenValid", placeholder: "placeholder", autocomplete: "autocomplete", name: "name", inputId: "inputId", disabled: "disabled", label: "label", hint: "hint", errorText: "errorText", size: "size", variant: "variant", showClear: "showClear", autoFocus: "autoFocus", selectOnFocus: "selectOnFocus", showErrorWhenTouched: "showErrorWhenTouched", dropdownAttachToBody: "dropdownAttachToBody", dropdownZIndex: "dropdownZIndex", i18n: "i18n", telI18n: "telI18n", localizedCountries: "localizedCountries", telLocalizedCountries: "telLocalizedCountries", clearAriaLabel: "clearAriaLabel", dir: "dir", autoPlaceholder: "autoPlaceholder", utilsScript: "utilsScript", customPlaceholder: "customPlaceholder", digitsOnly: "digitsOnly", lockWhenValid: "lockWhenValid", theme: "theme" }, outputs: { countryChange: "countryChange", validityChange: "validityChange", inputChange: "inputChange" }, providers: [
703
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NgxsmkTelInputComponent), multi: true },
704
+ { provide: NG_VALIDATORS, useExisting: forwardRef(() => NgxsmkTelInputComponent), multi: true }
705
+ ], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["telInput"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: `
706
+ <div class="ngxsmk-tel"
707
+ [class.disabled]="disabled"
708
+ [attr.data-size]="size"
709
+ [attr.data-variant]="variant"
710
+ [attr.dir]="dir">
711
+ @if (label) {
712
+ <label class="ngxsmk-tel__label" [for]="resolvedId">{{ label }}</label>
713
+ }
714
+
715
+ <div class="ngxsmk-tel__wrap" [class.has-error]="showError">
716
+ <div class="ngxsmk-tel-input__wrapper">
717
+ <input
718
+ #telInput
719
+ type="tel"
720
+ class="ngxsmk-tel-input__control"
721
+ [id]="resolvedId"
722
+ [attr.name]="name || null"
723
+ [attr.placeholder]="placeholder || null"
724
+ [attr.autocomplete]="autocomplete"
725
+ [attr.inputmode]="digitsOnly ? 'numeric' : 'tel'"
726
+ [disabled]="disabled"
727
+ [attr.aria-invalid]="showError ? 'true' : 'false'"
728
+ (blur)="onBlur()"
729
+ (focus)="onFocus()"
730
+ />
731
+ </div>
732
+
733
+ @if (showClear && currentRaw()) {
734
+ <button type="button"
735
+ class="ngxsmk-tel__clear"
736
+ (click)="clearInput()"
737
+ [attr.aria-label]="clearAriaLabel">
738
+ ×
739
+ </button>
740
+ }
741
+ </div>
742
+
743
+ @if (hint && !showError) {
744
+ <div class="ngxsmk-tel__hint">{{ hint }}</div>
745
+ }
746
+ </div>
747
+ `, 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: 12px;--tel-focus-shadow: 0 0 0 3px rgba(37, 99, 235, .25);--tel-dd-bg: var(--tel-bg);--tel-dd-border: var(--tel-border);--tel-dd-shadow: 0 24px 60px rgba(0, 0, 0, .18);--tel-dd-radius: 12px;--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}:host-context(.dark),:host([data-theme=dark]){--tel-bg: #0b0f17;--tel-fg: #e5e7eb;--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 3px rgba(96, 165, 250, .25);--tel-dd-bg: #0f1521;--tel-dd-border: #324056;--tel-dd-shadow: 0 24px 60px rgba(0, 0, 0, .4);--tel-dd-search-bg: rgba(148, 163, 184, .12)}:host([data-theme=light]){--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-focus-shadow: 0 0 0 3px rgba(37, 99, 235, .25);--tel-dd-bg: var(--tel-bg);--tel-dd-border: var(--tel-border);--tel-dd-shadow: 0 24px 60px rgba(0, 0, 0, .18);--tel-dd-search-bg: rgba(148, 163, 184, .08)}:host-context(.dark) ::ng-deep .iti__country-list,:host([data-theme=dark]) ::ng-deep .iti__country-list{background:var(--tel-dd-bg)!important;border-color:var(--tel-dd-border)!important;color:var(--tel-fg)!important}:host-context(.dark) ::ng-deep .iti__search-input,:host([data-theme=dark]) ::ng-deep .iti__search-input{background:var(--tel-dd-search-bg)!important;color:var(--tel-fg)!important;border-bottom-color:var(--tel-dd-border)!important}:host-context(.dark) ::ng-deep .iti__country,:host([data-theme=dark]) ::ng-deep .iti__country{color:var(--tel-fg)!important}:host-context(.dark) ::ng-deep .iti__country.iti__highlight,:host([data-theme=dark]) ::ng-deep .iti__country.iti__highlight{background-color:var(--tel-dd-item-hover)!important}:host-context(.dark) ::ng-deep .iti__country-name,:host([data-theme=dark]) ::ng-deep .iti__country-name{color:var(--tel-fg)!important}:host-context(.dark) ::ng-deep .iti__country-code,:host([data-theme=dark]) ::ng-deep .iti__country-code{color:var(--tel-placeholder)!important}:host-context(.dark) ::ng-deep .iti__dial-code,:host([data-theme=dark]) ::ng-deep .iti__dial-code{color:var(--tel-placeholder)!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:6px;font-size:.875rem;font-weight:500}.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);border-radius:var(--tel-radius);padding:10px 40px 10px 12px;outline:none;transition:border-color .15s ease,box-shadow .15s ease,background .15s ease;will-change:border-color,box-shadow,background;box-shadow:0 1px 3px #0000001a;font-size:16px;line-height:1.5}.ngxsmk-tel-input__control::placeholder{color:var(--tel-placeholder)}.ngxsmk-tel-input__control:focus{border-color:var(--tel-ring);box-shadow:var(--tel-focus-shadow);background:var(--tel-bg);color:var(--tel-fg)}[data-size=sm] .ngxsmk-tel-input__control{height:34px;font-size:13px;padding:6px 36px 6px 10px;border-radius:10px}[data-size=lg] .ngxsmk-tel-input__control{height:46px;font-size:16px;padding:12px 44px 12px 14px;border-radius:14px}[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:inline-flex;align-items:center}:host ::ng-deep .iti__country-list{background:var(--tel-dd-bg);border:1px solid var(--tel-dd-border);border-radius:var(--tel-dd-radius);box-shadow:var(--tel-dd-shadow);max-height:min(50vh,360px);overflow:auto;padding:6px 0;width:max(280px,100%);z-index:var(--tel-dd-z);contain:layout style;will-change:transform;color:var(--tel-fg)}:host ::ng-deep .iti--container .iti__country-list{z-index:var(--tel-dd-z)}:host ::ng-deep .iti__search-input{position:sticky;top:0;margin:0;padding: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)}:host ::ng-deep .iti__search-input::placeholder{color:var(--tel-placeholder)}:host ::ng-deep .iti__country{display:grid;grid-template-columns:28px 1fr auto;align-items:center;column-gap:.5rem;padding:10px 12px;cursor:pointer;color:var(--tel-fg)}:host ::ng-deep .iti__country.iti__highlight{background-color:var(--tel-dd-item-hover)}: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)}.ngxsmk-tel__hint{margin-top:6px;font-size:12px;color:var(--tel-placeholder)}.ngxsmk-tel__error{margin-top:6px;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) .ngxsmk-tel-input__control,:host([data-theme=dark]) .ngxsmk-tel-input__control{background:#1e293b!important;color:#f1f5f9!important;border-color:#475569!important;box-shadow:0 1px 3px #0000004d!important}:host-context(.dark) .ngxsmk-tel-input__control:focus,:host([data-theme=dark]) .ngxsmk-tel-input__control:focus{background:#1e293b!important;color:#f1f5f9!important;border-color:#3b82f6!important;box-shadow:0 0 0 3px #3b82f633!important}:host-context(.dark) .ngxsmk-tel-input__control::placeholder,:host([data-theme=dark]) .ngxsmk-tel-input__control::placeholder{color:#94a3b8!important}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
748
+ }
749
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgxsmkTelInputComponent, decorators: [{
750
+ type: Component,
751
+ args: [{ selector: 'ngxsmk-tel-input', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: `
752
+ <div class="ngxsmk-tel"
753
+ [class.disabled]="disabled"
754
+ [attr.data-size]="size"
755
+ [attr.data-variant]="variant"
756
+ [attr.dir]="dir">
757
+ @if (label) {
758
+ <label class="ngxsmk-tel__label" [for]="resolvedId">{{ label }}</label>
759
+ }
760
+
761
+ <div class="ngxsmk-tel__wrap" [class.has-error]="showError">
762
+ <div class="ngxsmk-tel-input__wrapper">
763
+ <input
764
+ #telInput
765
+ type="tel"
766
+ class="ngxsmk-tel-input__control"
767
+ [id]="resolvedId"
768
+ [attr.name]="name || null"
769
+ [attr.placeholder]="placeholder || null"
770
+ [attr.autocomplete]="autocomplete"
771
+ [attr.inputmode]="digitsOnly ? 'numeric' : 'tel'"
772
+ [disabled]="disabled"
773
+ [attr.aria-invalid]="showError ? 'true' : 'false'"
774
+ (blur)="onBlur()"
775
+ (focus)="onFocus()"
776
+ />
777
+ </div>
778
+
779
+ @if (showClear && currentRaw()) {
780
+ <button type="button"
781
+ class="ngxsmk-tel__clear"
782
+ (click)="clearInput()"
783
+ [attr.aria-label]="clearAriaLabel">
784
+ ×
785
+ </button>
786
+ }
787
+ </div>
788
+
789
+ @if (hint && !showError) {
790
+ <div class="ngxsmk-tel__hint">{{ hint }}</div>
791
+ }
792
+ </div>
793
+ `, providers: [
794
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NgxsmkTelInputComponent), multi: true },
795
+ { provide: NG_VALIDATORS, useExisting: forwardRef(() => NgxsmkTelInputComponent), multi: true }
796
+ ], 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: 12px;--tel-focus-shadow: 0 0 0 3px rgba(37, 99, 235, .25);--tel-dd-bg: var(--tel-bg);--tel-dd-border: var(--tel-border);--tel-dd-shadow: 0 24px 60px rgba(0, 0, 0, .18);--tel-dd-radius: 12px;--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}:host-context(.dark),:host([data-theme=dark]){--tel-bg: #0b0f17;--tel-fg: #e5e7eb;--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 3px rgba(96, 165, 250, .25);--tel-dd-bg: #0f1521;--tel-dd-border: #324056;--tel-dd-shadow: 0 24px 60px rgba(0, 0, 0, .4);--tel-dd-search-bg: rgba(148, 163, 184, .12)}:host([data-theme=light]){--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-focus-shadow: 0 0 0 3px rgba(37, 99, 235, .25);--tel-dd-bg: var(--tel-bg);--tel-dd-border: var(--tel-border);--tel-dd-shadow: 0 24px 60px rgba(0, 0, 0, .18);--tel-dd-search-bg: rgba(148, 163, 184, .08)}:host-context(.dark) ::ng-deep .iti__country-list,:host([data-theme=dark]) ::ng-deep .iti__country-list{background:var(--tel-dd-bg)!important;border-color:var(--tel-dd-border)!important;color:var(--tel-fg)!important}:host-context(.dark) ::ng-deep .iti__search-input,:host([data-theme=dark]) ::ng-deep .iti__search-input{background:var(--tel-dd-search-bg)!important;color:var(--tel-fg)!important;border-bottom-color:var(--tel-dd-border)!important}:host-context(.dark) ::ng-deep .iti__country,:host([data-theme=dark]) ::ng-deep .iti__country{color:var(--tel-fg)!important}:host-context(.dark) ::ng-deep .iti__country.iti__highlight,:host([data-theme=dark]) ::ng-deep .iti__country.iti__highlight{background-color:var(--tel-dd-item-hover)!important}:host-context(.dark) ::ng-deep .iti__country-name,:host([data-theme=dark]) ::ng-deep .iti__country-name{color:var(--tel-fg)!important}:host-context(.dark) ::ng-deep .iti__country-code,:host([data-theme=dark]) ::ng-deep .iti__country-code{color:var(--tel-placeholder)!important}:host-context(.dark) ::ng-deep .iti__dial-code,:host([data-theme=dark]) ::ng-deep .iti__dial-code{color:var(--tel-placeholder)!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:6px;font-size:.875rem;font-weight:500}.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);border-radius:var(--tel-radius);padding:10px 40px 10px 12px;outline:none;transition:border-color .15s ease,box-shadow .15s ease,background .15s ease;will-change:border-color,box-shadow,background;box-shadow:0 1px 3px #0000001a;font-size:16px;line-height:1.5}.ngxsmk-tel-input__control::placeholder{color:var(--tel-placeholder)}.ngxsmk-tel-input__control:focus{border-color:var(--tel-ring);box-shadow:var(--tel-focus-shadow);background:var(--tel-bg);color:var(--tel-fg)}[data-size=sm] .ngxsmk-tel-input__control{height:34px;font-size:13px;padding:6px 36px 6px 10px;border-radius:10px}[data-size=lg] .ngxsmk-tel-input__control{height:46px;font-size:16px;padding:12px 44px 12px 14px;border-radius:14px}[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:inline-flex;align-items:center}:host ::ng-deep .iti__country-list{background:var(--tel-dd-bg);border:1px solid var(--tel-dd-border);border-radius:var(--tel-dd-radius);box-shadow:var(--tel-dd-shadow);max-height:min(50vh,360px);overflow:auto;padding:6px 0;width:max(280px,100%);z-index:var(--tel-dd-z);contain:layout style;will-change:transform;color:var(--tel-fg)}:host ::ng-deep .iti--container .iti__country-list{z-index:var(--tel-dd-z)}:host ::ng-deep .iti__search-input{position:sticky;top:0;margin:0;padding: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)}:host ::ng-deep .iti__search-input::placeholder{color:var(--tel-placeholder)}:host ::ng-deep .iti__country{display:grid;grid-template-columns:28px 1fr auto;align-items:center;column-gap:.5rem;padding:10px 12px;cursor:pointer;color:var(--tel-fg)}:host ::ng-deep .iti__country.iti__highlight{background-color:var(--tel-dd-item-hover)}: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)}.ngxsmk-tel__hint{margin-top:6px;font-size:12px;color:var(--tel-placeholder)}.ngxsmk-tel__error{margin-top:6px;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) .ngxsmk-tel-input__control,:host([data-theme=dark]) .ngxsmk-tel-input__control{background:#1e293b!important;color:#f1f5f9!important;border-color:#475569!important;box-shadow:0 1px 3px #0000004d!important}:host-context(.dark) .ngxsmk-tel-input__control:focus,:host([data-theme=dark]) .ngxsmk-tel-input__control:focus{background:#1e293b!important;color:#f1f5f9!important;border-color:#3b82f6!important;box-shadow:0 0 0 3px #3b82f633!important}:host-context(.dark) .ngxsmk-tel-input__control::placeholder,:host([data-theme=dark]) .ngxsmk-tel-input__control::placeholder{color:#94a3b8!important}\n"] }]
797
+ }], ctorParameters: () => [{ type: i0.NgZone }, { type: NgxsmkTelInputService }], propDecorators: { inputRef: [{
798
+ type: ViewChild,
799
+ args: ['telInput', { static: true }]
800
+ }], initialCountry: [{
801
+ type: Input
802
+ }], preferredCountries: [{
803
+ type: Input
804
+ }], onlyCountries: [{
805
+ type: Input
806
+ }], separateDialCode: [{
807
+ type: Input
808
+ }], allowDropdown: [{
809
+ type: Input
810
+ }], nationalDisplay: [{
811
+ type: Input
812
+ }], formatWhenValid: [{
813
+ type: Input
814
+ }], placeholder: [{
815
+ type: Input
816
+ }], autocomplete: [{
817
+ type: Input
818
+ }], name: [{
819
+ type: Input
820
+ }], inputId: [{
821
+ type: Input
822
+ }], disabled: [{
823
+ type: Input
824
+ }], label: [{
825
+ type: Input
826
+ }], hint: [{
827
+ type: Input
828
+ }], errorText: [{
829
+ type: Input
830
+ }], size: [{
831
+ type: Input
832
+ }], variant: [{
833
+ type: Input
834
+ }], showClear: [{
835
+ type: Input
836
+ }], autoFocus: [{
837
+ type: Input
838
+ }], selectOnFocus: [{
839
+ type: Input
840
+ }], showErrorWhenTouched: [{
841
+ type: Input
842
+ }], dropdownAttachToBody: [{
843
+ type: Input
844
+ }], dropdownZIndex: [{
845
+ type: Input
846
+ }], i18n: [{
847
+ type: Input,
848
+ args: ['i18n']
849
+ }], telI18n: [{
850
+ type: Input,
851
+ args: ['telI18n']
852
+ }], localizedCountries: [{
853
+ type: Input,
854
+ args: ['localizedCountries']
855
+ }], telLocalizedCountries: [{
856
+ type: Input,
857
+ args: ['telLocalizedCountries']
858
+ }], clearAriaLabel: [{
859
+ type: Input
860
+ }], dir: [{
861
+ type: Input
862
+ }], autoPlaceholder: [{
863
+ type: Input
864
+ }], utilsScript: [{
865
+ type: Input
866
+ }], customPlaceholder: [{
867
+ type: Input
868
+ }], digitsOnly: [{
869
+ type: Input
870
+ }], lockWhenValid: [{
871
+ type: Input
872
+ }], theme: [{
873
+ type: Input
874
+ }], countryChange: [{
875
+ type: Output
876
+ }], validityChange: [{
877
+ type: Output
878
+ }], inputChange: [{
879
+ type: Output
880
+ }] } });
881
+
882
+ class ThemeService {
883
+ constructor() {
884
+ this.platformId = inject(PLATFORM_ID);
885
+ this.themeSubject = new BehaviorSubject('auto');
886
+ this.currentThemeSubject = new BehaviorSubject('light');
887
+ this.theme$ = this.themeSubject.asObservable();
888
+ this.currentTheme$ = this.currentThemeSubject.asObservable();
889
+ if (isPlatformBrowser(this.platformId)) {
890
+ this.initializeTheme();
891
+ this.setupSystemThemeListener();
892
+ }
893
+ }
894
+ /** Get current theme preference */
895
+ getTheme() {
896
+ return this.themeSubject.value;
897
+ }
898
+ /** Get current resolved theme (light or dark) */
899
+ getCurrentTheme() {
900
+ return this.currentThemeSubject.value;
901
+ }
902
+ /** Set theme preference */
903
+ setTheme(theme) {
904
+ if (!isPlatformBrowser(this.platformId))
905
+ return;
906
+ this.themeSubject.next(theme);
907
+ this.applyTheme(theme);
908
+ this.saveThemePreference(theme);
909
+ }
910
+ /** Toggle between light and dark themes */
911
+ toggleTheme() {
912
+ const current = this.getCurrentTheme();
913
+ this.setTheme(current === 'light' ? 'dark' : 'light');
914
+ }
915
+ /** Initialize theme from saved preference or system */
916
+ initializeTheme() {
917
+ const savedTheme = this.getSavedThemePreference();
918
+ const theme = savedTheme || 'auto';
919
+ this.setTheme(theme);
920
+ }
921
+ /** Apply theme to document and components */
922
+ applyTheme(theme) {
923
+ const resolvedTheme = this.resolveTheme(theme);
924
+ this.currentThemeSubject.next(resolvedTheme);
925
+ // Apply to document
926
+ document.documentElement.setAttribute('data-theme', resolvedTheme);
927
+ if (resolvedTheme === 'dark') {
928
+ document.documentElement.classList.add('dark');
929
+ }
930
+ else {
931
+ document.documentElement.classList.remove('dark');
932
+ }
933
+ // Update CSS custom properties
934
+ this.updateCSSVariables(resolvedTheme);
935
+ // Force update of all tel-input components
936
+ this.updateTelInputComponents(resolvedTheme);
937
+ }
938
+ /** Update all tel-input components with the new theme */
939
+ updateTelInputComponents(theme) {
940
+ const telInputComponents = document.querySelectorAll('ngxsmk-tel-input');
941
+ telInputComponents.forEach(component => {
942
+ const element = component;
943
+ element.setAttribute('data-theme', theme);
944
+ });
945
+ }
946
+ /** Resolve theme to light or dark */
947
+ resolveTheme(theme) {
948
+ if (theme === 'auto') {
949
+ return this.detectSystemTheme();
950
+ }
951
+ return theme;
952
+ }
953
+ /** Detect system theme preference */
954
+ detectSystemTheme() {
955
+ if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
956
+ return 'dark';
957
+ }
958
+ return 'light';
959
+ }
960
+ /** Setup listener for system theme changes */
961
+ setupSystemThemeListener() {
962
+ if (window.matchMedia) {
963
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
964
+ mediaQuery.addEventListener('change', () => {
965
+ if (this.getTheme() === 'auto') {
966
+ this.applyTheme('auto');
967
+ }
968
+ });
969
+ }
970
+ }
971
+ /** Update CSS custom properties for theme */
972
+ updateCSSVariables(theme) {
973
+ const root = document.documentElement;
974
+ if (theme === 'dark') {
975
+ root.style.setProperty('--tel-bg', '#0b0f17');
976
+ root.style.setProperty('--tel-fg', '#e5e7eb');
977
+ root.style.setProperty('--tel-border', '#334155');
978
+ root.style.setProperty('--tel-border-hover', '#475569');
979
+ root.style.setProperty('--tel-ring', '#60a5fa');
980
+ root.style.setProperty('--tel-placeholder', '#94a3b8');
981
+ root.style.setProperty('--tel-error', '#f87171');
982
+ root.style.setProperty('--tel-success', '#34d399');
983
+ root.style.setProperty('--tel-warning', '#fbbf24');
984
+ root.style.setProperty('--tel-dd-bg', '#0f1521');
985
+ root.style.setProperty('--tel-dd-border', '#324056');
986
+ root.style.setProperty('--tel-dd-shadow', '0 24px 60px rgba(0, 0, 0, .4)');
987
+ root.style.setProperty('--tel-dd-search-bg', 'rgba(148, 163, 184, .12)');
988
+ }
989
+ else {
990
+ root.style.setProperty('--tel-bg', '#fff');
991
+ root.style.setProperty('--tel-fg', '#0f172a');
992
+ root.style.setProperty('--tel-border', '#c0c0c0');
993
+ root.style.setProperty('--tel-border-hover', '#9aa0a6');
994
+ root.style.setProperty('--tel-ring', '#2563eb');
995
+ root.style.setProperty('--tel-placeholder', '#9ca3af');
996
+ root.style.setProperty('--tel-error', '#ef4444');
997
+ root.style.setProperty('--tel-success', '#10b981');
998
+ root.style.setProperty('--tel-warning', '#f59e0b');
999
+ root.style.setProperty('--tel-dd-bg', '#fff');
1000
+ root.style.setProperty('--tel-dd-border', '#c0c0c0');
1001
+ root.style.setProperty('--tel-dd-shadow', '0 24px 60px rgba(0, 0, 0, .18)');
1002
+ root.style.setProperty('--tel-dd-search-bg', 'rgba(148, 163, 184, .08)');
1003
+ }
1004
+ }
1005
+ /** Save theme preference to localStorage */
1006
+ saveThemePreference(theme) {
1007
+ try {
1008
+ localStorage.setItem('ngxsmk-tel-input-theme', theme);
1009
+ }
1010
+ catch (error) {
1011
+ console.warn('Failed to save theme preference:', error);
1012
+ }
1013
+ }
1014
+ /** Get saved theme preference from localStorage */
1015
+ getSavedThemePreference() {
1016
+ try {
1017
+ const saved = localStorage.getItem('ngxsmk-tel-input-theme');
1018
+ return saved;
1019
+ }
1020
+ catch (error) {
1021
+ console.warn('Failed to load theme preference:', error);
1022
+ return null;
1023
+ }
1024
+ }
1025
+ /** Check if dark theme is active */
1026
+ isDarkTheme() {
1027
+ return this.getCurrentTheme() === 'dark';
1028
+ }
1029
+ /** Check if light theme is active */
1030
+ isLightTheme() {
1031
+ return this.getCurrentTheme() === 'light';
1032
+ }
1033
+ /** Check if auto theme is set */
1034
+ isAutoTheme() {
1035
+ return this.getTheme() === 'auto';
1036
+ }
1037
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1038
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ThemeService, providedIn: 'root' }); }
1039
+ }
1040
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ThemeService, decorators: [{
1041
+ type: Injectable,
1042
+ args: [{
1043
+ providedIn: 'root'
1044
+ }]
1045
+ }], ctorParameters: () => [] });
1046
+
1047
+ /**
1048
+ * Utility functions for phone number input optimization
1049
+ */
1050
+ class PhoneInputUtils {
1051
+ /**
1052
+ * Convert any string to digits only (NSN basis)
1053
+ */
1054
+ static toNSN(v) {
1055
+ return (v ?? '').replace(/\D/g, '');
1056
+ }
1057
+ /**
1058
+ * Strip exactly one leading trunk '0' from national input
1059
+ */
1060
+ static stripLeadingZero(nsn) {
1061
+ return nsn.replace(/^0/, '');
1062
+ }
1063
+ /**
1064
+ * Create cache key for phone number operations
1065
+ */
1066
+ static createCacheKey(input, iso2) {
1067
+ return `${input || ''}|${iso2}`;
1068
+ }
1069
+ /**
1070
+ * Debounce function for performance optimization
1071
+ */
1072
+ static debounce(func, wait) {
1073
+ let timeout = null;
1074
+ return (...args) => {
1075
+ if (timeout)
1076
+ clearTimeout(timeout);
1077
+ timeout = setTimeout(() => func.apply(this, args), wait);
1078
+ };
1079
+ }
1080
+ /**
1081
+ * Throttle function for performance optimization
1082
+ */
1083
+ static throttle(func, limit) {
1084
+ let inThrottle;
1085
+ return (...args) => {
1086
+ if (!inThrottle) {
1087
+ func.apply(this, args);
1088
+ inThrottle = true;
1089
+ setTimeout(() => inThrottle = false, limit);
1090
+ }
1091
+ };
1092
+ }
1093
+ /**
1094
+ * Check if a string contains only digits
1095
+ */
1096
+ static isDigitsOnly(str) {
1097
+ return /^\d+$/.test(str);
1098
+ }
1099
+ /**
1100
+ * Safely get element value with fallback
1101
+ */
1102
+ static getElementValue(element) {
1103
+ return element?.value?.trim() || '';
1104
+ }
1105
+ /**
1106
+ * Check if element is in viewport for performance optimization
1107
+ */
1108
+ static isInViewport(element) {
1109
+ const rect = element.getBoundingClientRect();
1110
+ return (rect.top >= 0 &&
1111
+ rect.left >= 0 &&
1112
+ rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
1113
+ rect.right <= (window.innerWidth || document.documentElement.clientWidth));
1114
+ }
1115
+ /**
1116
+ * Create optimized event listener with cleanup
1117
+ */
1118
+ static createEventListener(element, event, handler, options) {
1119
+ element.addEventListener(event, handler, options);
1120
+ return () => element.removeEventListener(event, handler, options);
1121
+ }
1122
+ /**
1123
+ * Batch DOM operations for better performance
1124
+ */
1125
+ static batchDOMOperations(operations) {
1126
+ requestAnimationFrame(() => {
1127
+ operations.forEach(op => op());
1128
+ });
1129
+ }
1130
+ }
1131
+
1132
+ /**
1133
+ * Generated bundle index. Do not edit.
1134
+ */
1135
+
1136
+ export { NgxsmkTelInputComponent, NgxsmkTelInputService, PhoneInputUtils, ThemeService };
1137
+ //# sourceMappingURL=ngxsmk-tel-input.mjs.map