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.
- package/fesm2022/ngxsmk-tel-input.mjs +1137 -0
- package/fesm2022/ngxsmk-tel-input.mjs.map +1 -0
- package/index.d.ts +6 -0
- package/lib/ngxsmk-tel-input.component.d.ts +133 -0
- package/lib/ngxsmk-tel-input.component.d.ts.map +1 -0
- package/lib/ngxsmk-tel-input.service.d.ts +18 -0
- package/lib/ngxsmk-tel-input.service.d.ts.map +1 -0
- package/lib/phone-input.utils.d.ts +47 -0
- package/lib/phone-input.utils.d.ts.map +1 -0
- package/lib/theme.service.d.ts +46 -0
- package/lib/theme.service.d.ts.map +1 -0
- package/lib/types.d.ts +10 -0
- package/lib/types.d.ts.map +1 -0
- package/ngxsmk-tel-input.d.ts.map +1 -0
- package/package.json +69 -59
- package/{src/public-api.ts → public-api.d.ts} +6 -3
- package/public-api.d.ts.map +1 -0
- package/.changeset/README.md +0 -8
- package/.changeset/config.json +0 -11
- package/docs/invalid.png +0 -0
- package/docs/kr.png +0 -0
- package/docs/valid.png +0 -0
- package/ng-package.json +0 -8
- package/src/lib/ngxsmk-tel-input.component.scss +0 -226
- package/src/lib/ngxsmk-tel-input.component.spec.ts +0 -24
- package/src/lib/ngxsmk-tel-input.component.ts +0 -562
- package/src/lib/ngxsmk-tel-input.service.spec.ts +0 -15
- package/src/lib/ngxsmk-tel-input.service.ts +0 -17
- package/src/lib/types.ts +0 -11
- package/tsconfig.lib.json +0 -14
- package/tsconfig.spec.json +0 -13
|
@@ -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
|