ngxsmk-tel-input 1.1.1 → 1.1.3
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ngxsmk-tel-input",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "Angular international telephone input with country flag dropdown, formatting & validation (intl-tel-input + libphonenumber). ControlValueAccessor. Supports Angular 17–19.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ngxsmk-tel-input",
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/* ---------- Theme tokens ---------- */
|
|
2
|
+
:host {
|
|
3
|
+
--tel-bg: #fff;
|
|
4
|
+
--tel-fg: #0f172a;
|
|
5
|
+
--tel-border: #c0c0c0;
|
|
6
|
+
--tel-border-hover: #9aa0a6;
|
|
7
|
+
--tel-ring: #2563eb;
|
|
8
|
+
--tel-placeholder: #9ca3af;
|
|
9
|
+
--tel-error: #ef4444;
|
|
10
|
+
--tel-radius: 12px;
|
|
11
|
+
--tel-focus-shadow: 0 0 0 3px rgba(37, 99, 235, .25);
|
|
12
|
+
|
|
13
|
+
--tel-dd-bg: var(--tel-bg);
|
|
14
|
+
--tel-dd-border: var(--tel-border);
|
|
15
|
+
--tel-dd-shadow: 0 24px 60px rgba(0, 0, 0, .18);
|
|
16
|
+
--tel-dd-radius: 12px;
|
|
17
|
+
--tel-dd-item-hover: rgba(37, 99, 235, .08);
|
|
18
|
+
--tel-dd-z: 2000;
|
|
19
|
+
--tel-dd-search-bg: rgba(148, 163, 184, .08);
|
|
20
|
+
|
|
21
|
+
display: block;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
:host-context(.dark) {
|
|
25
|
+
--tel-bg: #0b0f17;
|
|
26
|
+
--tel-fg: #e5e7eb;
|
|
27
|
+
--tel-border: #334155;
|
|
28
|
+
--tel-border-hover: #475569;
|
|
29
|
+
--tel-ring: #60a5fa;
|
|
30
|
+
--tel-placeholder: #94a3b8;
|
|
31
|
+
|
|
32
|
+
--tel-dd-bg: #0f1521;
|
|
33
|
+
--tel-dd-border: #324056;
|
|
34
|
+
--tel-dd-search-bg: rgba(148, 163, 184, .12);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* ---------- Structure ---------- */
|
|
38
|
+
.ngxsmk-tel {
|
|
39
|
+
width: 100%;
|
|
40
|
+
color: var(--tel-fg);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.ngxsmk-tel.disabled {
|
|
44
|
+
opacity: .7;
|
|
45
|
+
cursor: not-allowed;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.ngxsmk-tel__label {
|
|
49
|
+
display: inline-block;
|
|
50
|
+
margin-bottom: 6px;
|
|
51
|
+
font-size: .875rem;
|
|
52
|
+
font-weight: 500;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.ngxsmk-tel__wrap {
|
|
56
|
+
position: relative;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.ngxsmk-tel-input__wrapper,
|
|
60
|
+
:host ::ng-deep .iti {
|
|
61
|
+
width: 100%;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.ngxsmk-tel-input__control {
|
|
65
|
+
width: 100%;
|
|
66
|
+
height: 40px;
|
|
67
|
+
font: inherit;
|
|
68
|
+
color: var(--tel-fg);
|
|
69
|
+
background: var(--tel-bg);
|
|
70
|
+
border: 1px solid var(--tel-border);
|
|
71
|
+
border-radius: var(--tel-radius);
|
|
72
|
+
padding: 10px 40px 10px 12px;
|
|
73
|
+
outline: none;
|
|
74
|
+
transition: border-color .15s, box-shadow .15s, background .15s;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.ngxsmk-tel-input__control::placeholder {
|
|
78
|
+
color: var(--tel-placeholder);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.ngxsmk-tel-input__control:hover {
|
|
82
|
+
border-color: var(--tel-border-hover);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.ngxsmk-tel-input__control:focus {
|
|
86
|
+
border-color: var(--tel-ring);
|
|
87
|
+
box-shadow: var(--tel-focus-shadow);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Size presets */
|
|
91
|
+
[data-size="sm"] .ngxsmk-tel-input__control {
|
|
92
|
+
height: 34px;
|
|
93
|
+
font-size: 13px;
|
|
94
|
+
padding: 6px 36px 6px 10px;
|
|
95
|
+
border-radius: 10px;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
[data-size="lg"] .ngxsmk-tel-input__control {
|
|
99
|
+
height: 46px;
|
|
100
|
+
font-size: 16px;
|
|
101
|
+
padding: 12px 44px 12px 14px;
|
|
102
|
+
border-radius: 14px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Variants */
|
|
106
|
+
[data-variant="filled"] .ngxsmk-tel-input__control {
|
|
107
|
+
background: rgba(148, 163, 184, .08);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
[data-variant="underline"] .ngxsmk-tel-input__control {
|
|
111
|
+
border: 0;
|
|
112
|
+
border-bottom: 2px solid var(--tel-border);
|
|
113
|
+
border-radius: 0;
|
|
114
|
+
padding-left: 0;
|
|
115
|
+
padding-right: 34px;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
[data-variant="underline"] .ngxsmk-tel-input__control:focus {
|
|
119
|
+
border-bottom-color: var(--tel-ring);
|
|
120
|
+
box-shadow: none;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* ---------- intl-tel-input dropdown (deep selectors) ---------- */
|
|
124
|
+
:host ::ng-deep .iti__flag-container {
|
|
125
|
+
border-top-left-radius: var(--tel-radius);
|
|
126
|
+
border-bottom-left-radius: var(--tel-radius);
|
|
127
|
+
border: 1px solid var(--tel-border);
|
|
128
|
+
border-right: none;
|
|
129
|
+
background: var(--tel-bg);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
:host ::ng-deep .iti__selected-flag {
|
|
133
|
+
height: 100%;
|
|
134
|
+
padding: 0 10px;
|
|
135
|
+
display: inline-flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
:host ::ng-deep .iti__country-list {
|
|
140
|
+
background: var(--tel-dd-bg);
|
|
141
|
+
border: 1px solid var(--tel-dd-border);
|
|
142
|
+
border-radius: var(--tel-dd-radius);
|
|
143
|
+
box-shadow: var(--tel-dd-shadow);
|
|
144
|
+
max-height: min(50vh, 360px);
|
|
145
|
+
overflow: auto;
|
|
146
|
+
padding: 6px 0;
|
|
147
|
+
width: max(280px, 100%);
|
|
148
|
+
z-index: var(--tel-dd-z);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
:host ::ng-deep .iti--container .iti__country-list {
|
|
152
|
+
z-index: var(--tel-dd-z);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
:host ::ng-deep .iti__search-input {
|
|
156
|
+
position: sticky;
|
|
157
|
+
top: 0;
|
|
158
|
+
margin: 0;
|
|
159
|
+
padding: 10px 12px;
|
|
160
|
+
width: 100%;
|
|
161
|
+
border: 0;
|
|
162
|
+
border-bottom: 1px solid var(--tel-dd-border);
|
|
163
|
+
outline: none;
|
|
164
|
+
background: var(--tel-dd-search-bg);
|
|
165
|
+
color: var(--tel-fg);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
:host ::ng-deep .iti__country {
|
|
169
|
+
display: grid;
|
|
170
|
+
grid-template-columns: 28px 1fr auto;
|
|
171
|
+
align-items: center;
|
|
172
|
+
column-gap: .5rem;
|
|
173
|
+
padding: 10px 12px;
|
|
174
|
+
cursor: pointer;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
:host ::ng-deep .iti__dial-code {
|
|
178
|
+
color: var(--tel-placeholder);
|
|
179
|
+
font-weight: 600;
|
|
180
|
+
margin-left: 10px;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/* Clear button */
|
|
184
|
+
.ngxsmk-tel__clear {
|
|
185
|
+
position: absolute;
|
|
186
|
+
right: 8px;
|
|
187
|
+
top: 50%;
|
|
188
|
+
transform: translateY(-50%);
|
|
189
|
+
border: 0;
|
|
190
|
+
background: transparent;
|
|
191
|
+
font-size: 18px;
|
|
192
|
+
line-height: 1;
|
|
193
|
+
width: 28px;
|
|
194
|
+
height: 28px;
|
|
195
|
+
border-radius: 50%;
|
|
196
|
+
cursor: pointer;
|
|
197
|
+
color: var(--tel-placeholder);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.ngxsmk-tel__clear:hover {
|
|
201
|
+
background: rgba(148, 163, 184, .15);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* Hint & Error */
|
|
205
|
+
.ngxsmk-tel__hint {
|
|
206
|
+
margin-top: 6px;
|
|
207
|
+
font-size: 12px;
|
|
208
|
+
color: var(--tel-placeholder);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.ngxsmk-tel__error {
|
|
212
|
+
margin-top: 6px;
|
|
213
|
+
font-size: 12px;
|
|
214
|
+
color: var(--tel-error);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.ngxsmk-tel__wrap.has-error .ngxsmk-tel-input__control {
|
|
218
|
+
border-color: var(--tel-error);
|
|
219
|
+
box-shadow: 0 0 0 3px rgba(239, 68, 68, .15);
|
|
220
|
+
}
|
|
@@ -89,260 +89,45 @@ export interface IntlTelI18n {
|
|
|
89
89
|
}
|
|
90
90
|
</div>
|
|
91
91
|
`,
|
|
92
|
+
styleUrls: ['./ngxsmk-tel-input.component.scss'],
|
|
92
93
|
providers: [
|
|
93
94
|
{provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NgxsmkTelInputComponent), multi: true},
|
|
94
95
|
{provide: NG_VALIDATORS, useExisting: forwardRef(() => NgxsmkTelInputComponent), multi: true}
|
|
95
|
-
]
|
|
96
|
-
styles: [`
|
|
97
|
-
/* ---------- Theme tokens ---------- */
|
|
98
|
-
:host {
|
|
99
|
-
--tel-bg: #fff;
|
|
100
|
-
--tel-fg: #0f172a;
|
|
101
|
-
--tel-border: #c0c0c0;
|
|
102
|
-
--tel-border-hover: #9aa0a6;
|
|
103
|
-
--tel-ring: #2563eb;
|
|
104
|
-
--tel-placeholder: #9ca3af;
|
|
105
|
-
--tel-error: #ef4444;
|
|
106
|
-
--tel-radius: 12px;
|
|
107
|
-
--tel-focus-shadow: 0 0 0 3px rgba(37, 99, 235, .25);
|
|
108
|
-
|
|
109
|
-
--tel-dd-bg: var(--tel-bg);
|
|
110
|
-
--tel-dd-border: var(--tel-border);
|
|
111
|
-
--tel-dd-shadow: 0 24px 60px rgba(0, 0, 0, .18);
|
|
112
|
-
--tel-dd-radius: 12px;
|
|
113
|
-
--tel-dd-item-hover: rgba(37, 99, 235, .08);
|
|
114
|
-
--tel-dd-z: 2000;
|
|
115
|
-
--tel-dd-search-bg: rgba(148, 163, 184, .08);
|
|
116
|
-
|
|
117
|
-
display: block;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
:host-context(.dark) {
|
|
121
|
-
--tel-bg: #0b0f17;
|
|
122
|
-
--tel-fg: #e5e7eb;
|
|
123
|
-
--tel-border: #334155;
|
|
124
|
-
--tel-border-hover: #475569;
|
|
125
|
-
--tel-ring: #60a5fa;
|
|
126
|
-
--tel-placeholder: #94a3b8;
|
|
127
|
-
|
|
128
|
-
--tel-dd-bg: #0f1521;
|
|
129
|
-
--tel-dd-border: #324056;
|
|
130
|
-
--tel-dd-search-bg: rgba(148, 163, 184, .12);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/* ---------- Structure ---------- */
|
|
134
|
-
.ngxsmk-tel {
|
|
135
|
-
width: 100%;
|
|
136
|
-
color: var(--tel-fg);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
.ngxsmk-tel.disabled {
|
|
140
|
-
opacity: .7;
|
|
141
|
-
cursor: not-allowed;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
.ngxsmk-tel__label {
|
|
145
|
-
display: inline-block;
|
|
146
|
-
margin-bottom: 6px;
|
|
147
|
-
font-size: .875rem;
|
|
148
|
-
font-weight: 500;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
.ngxsmk-tel__wrap {
|
|
152
|
-
position: relative;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
.ngxsmk-tel-input__wrapper, :host ::ng-deep .iti {
|
|
156
|
-
width: 100%;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.ngxsmk-tel-input__control {
|
|
160
|
-
width: 100%;
|
|
161
|
-
height: 40px;
|
|
162
|
-
font: inherit;
|
|
163
|
-
color: var(--tel-fg);
|
|
164
|
-
background: var(--tel-bg);
|
|
165
|
-
border: 1px solid var(--tel-border);
|
|
166
|
-
border-radius: var(--tel-radius);
|
|
167
|
-
padding: 10px 40px 10px 12px;
|
|
168
|
-
outline: none;
|
|
169
|
-
transition: border-color .15s, box-shadow .15s, background .15s;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
.ngxsmk-tel-input__control::placeholder {
|
|
173
|
-
color: var(--tel-placeholder);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
.ngxsmk-tel-input__control:hover {
|
|
177
|
-
border-color: var(--tel-border-hover);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
.ngxsmk-tel-input__control:focus {
|
|
181
|
-
border-color: var(--tel-ring);
|
|
182
|
-
box-shadow: var(--tel-focus-shadow);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
[data-size="sm"] .ngxsmk-tel-input__control {
|
|
186
|
-
height: 34px;
|
|
187
|
-
font-size: 13px;
|
|
188
|
-
padding: 6px 36px 6px 10px;
|
|
189
|
-
border-radius: 10px;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
[data-size="lg"] .ngxsmk-tel-input__control {
|
|
193
|
-
height: 46px;
|
|
194
|
-
font-size: 16px;
|
|
195
|
-
padding: 12px 44px 12px 14px;
|
|
196
|
-
border-radius: 14px;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
[data-variant="filled"] .ngxsmk-tel-input__control {
|
|
200
|
-
background: rgba(148, 163, 184, .08);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
[data-variant="underline"] .ngxsmk-tel-input__control {
|
|
204
|
-
border: 0;
|
|
205
|
-
border-bottom: 2px solid var(--tel-border);
|
|
206
|
-
border-radius: 0;
|
|
207
|
-
padding-left: 0;
|
|
208
|
-
padding-right: 34px;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
[data-variant="underline"] .ngxsmk-tel-input__control:focus {
|
|
212
|
-
border-bottom-color: var(--tel-ring);
|
|
213
|
-
box-shadow: none;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/* ---------- intl-tel-input dropdown (deep selectors) ---------- */
|
|
217
|
-
:host ::ng-deep .iti__flag-container {
|
|
218
|
-
border-top-left-radius: var(--tel-radius);
|
|
219
|
-
border-bottom-left-radius: var(--tel-radius);
|
|
220
|
-
border: 1px solid var(--tel-border);
|
|
221
|
-
border-right: none;
|
|
222
|
-
background: var(--tel-bg);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
:host ::ng-deep .iti__selected-flag {
|
|
226
|
-
height: 100%;
|
|
227
|
-
padding: 0 10px;
|
|
228
|
-
display: inline-flex;
|
|
229
|
-
align-items: center;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
:host ::ng-deep .iti__country-list {
|
|
233
|
-
background: var(--tel-dd-bg);
|
|
234
|
-
border: 1px solid var(--tel-dd-border);
|
|
235
|
-
border-radius: var(--tel-dd-radius);
|
|
236
|
-
box-shadow: var(--tel-dd-shadow);
|
|
237
|
-
max-height: min(50vh, 360px);
|
|
238
|
-
overflow: auto;
|
|
239
|
-
padding: 6px 0;
|
|
240
|
-
width: max(280px, 100%);
|
|
241
|
-
z-index: var(--tel-dd-z);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
:host ::ng-deep .iti--container .iti__country-list {
|
|
245
|
-
z-index: var(--tel-dd-z);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
:host ::ng-deep .iti__search-input {
|
|
249
|
-
position: sticky;
|
|
250
|
-
top: 0;
|
|
251
|
-
margin: 0;
|
|
252
|
-
padding: 10px 12px;
|
|
253
|
-
width: 100%;
|
|
254
|
-
border: 0;
|
|
255
|
-
border-bottom: 1px solid var(--tel-dd-border);
|
|
256
|
-
outline: none;
|
|
257
|
-
background: var(--tel-dd-search-bg);
|
|
258
|
-
color: var(--tel-fg);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
:host ::ng-deep .iti__country {
|
|
262
|
-
display: grid;
|
|
263
|
-
grid-template-columns: 28px 1fr auto;
|
|
264
|
-
align-items: center;
|
|
265
|
-
column-gap: .5rem;
|
|
266
|
-
padding: 10px 12px;
|
|
267
|
-
cursor: pointer;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
:host ::ng-deep .iti__dial-code {
|
|
271
|
-
color: var(--tel-placeholder);
|
|
272
|
-
font-weight: 600;
|
|
273
|
-
margin-left: 10px;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
.ngxsmk-tel__clear {
|
|
277
|
-
position: absolute;
|
|
278
|
-
right: 8px;
|
|
279
|
-
top: 50%;
|
|
280
|
-
transform: translateY(-50%);
|
|
281
|
-
border: 0;
|
|
282
|
-
background: transparent;
|
|
283
|
-
font-size: 18px;
|
|
284
|
-
line-height: 1;
|
|
285
|
-
width: 28px;
|
|
286
|
-
height: 28px;
|
|
287
|
-
border-radius: 50%;
|
|
288
|
-
cursor: pointer;
|
|
289
|
-
color: var(--tel-placeholder);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
.ngxsmk-tel__clear:hover {
|
|
293
|
-
background: rgba(148, 163, 184, .15);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
.ngxsmk-tel__hint {
|
|
297
|
-
margin-top: 6px;
|
|
298
|
-
font-size: 12px;
|
|
299
|
-
color: var(--tel-placeholder);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
.ngxsmk-tel__error {
|
|
303
|
-
margin-top: 6px;
|
|
304
|
-
font-size: 12px;
|
|
305
|
-
color: var(--tel-error);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
.ngxsmk-tel__wrap.has-error .ngxsmk-tel-input__control {
|
|
309
|
-
border-color: var(--tel-error);
|
|
310
|
-
box-shadow: 0 0 0 3px rgba(239, 68, 68, .15);
|
|
311
|
-
}
|
|
312
|
-
`]
|
|
96
|
+
]
|
|
313
97
|
})
|
|
314
98
|
export class NgxsmkTelInputComponent implements AfterViewInit, OnChanges, OnDestroy, ControlValueAccessor, Validator {
|
|
99
|
+
|
|
315
100
|
@ViewChild('telInput', {static: true}) inputRef!: ElementRef<HTMLInputElement>;
|
|
316
101
|
|
|
317
102
|
/* Core config */
|
|
318
103
|
@Input() initialCountry: CountryCode | 'auto' = 'US';
|
|
319
104
|
@Input() preferredCountries: CountryCode[] = ['US', 'GB'];
|
|
320
105
|
@Input() onlyCountries?: CountryCode[];
|
|
321
|
-
@Input() nationalMode = false;
|
|
322
|
-
@Input() separateDialCode = false;
|
|
323
|
-
@Input() allowDropdown = true;
|
|
106
|
+
@Input() nationalMode: boolean = false;
|
|
107
|
+
@Input() separateDialCode: boolean = false;
|
|
108
|
+
@Input() allowDropdown: boolean = true;
|
|
324
109
|
|
|
325
110
|
/* UX */
|
|
326
|
-
@Input() placeholder?: string;
|
|
111
|
+
@Input() placeholder?: string;
|
|
327
112
|
@Input() autocomplete = 'tel';
|
|
328
113
|
@Input() name?: string;
|
|
329
114
|
@Input() inputId?: string;
|
|
330
|
-
@Input() disabled = false;
|
|
115
|
+
@Input() disabled: boolean = false;
|
|
331
116
|
|
|
332
117
|
@Input() label?: string;
|
|
333
118
|
@Input() hint?: string;
|
|
334
119
|
@Input() errorText?: string;
|
|
335
120
|
@Input() size: 'sm' | 'md' | 'lg' = 'md';
|
|
336
121
|
@Input() variant: 'outline' | 'filled' | 'underline' = 'outline';
|
|
337
|
-
@Input() showClear = true;
|
|
338
|
-
@Input() autoFocus = false;
|
|
339
|
-
@Input() selectOnFocus = false;
|
|
340
|
-
@Input() formatOnBlur = true;
|
|
341
|
-
@Input() showErrorWhenTouched = true;
|
|
122
|
+
@Input() showClear: boolean = true;
|
|
123
|
+
@Input() autoFocus: boolean = false;
|
|
124
|
+
@Input() selectOnFocus: boolean = false;
|
|
125
|
+
@Input() formatOnBlur: boolean = true;
|
|
126
|
+
@Input() showErrorWhenTouched: boolean = true;
|
|
342
127
|
|
|
343
128
|
/* Dropdown plumbing */
|
|
344
|
-
@Input() dropdownAttachToBody = true;
|
|
345
|
-
@Input() dropdownZIndex = 2000;
|
|
129
|
+
@Input() dropdownAttachToBody: boolean = true;
|
|
130
|
+
@Input() dropdownZIndex: number = 2000;
|
|
346
131
|
|
|
347
132
|
/* Localization + RTL */
|
|
348
133
|
@Input('i18n') i18n?: IntlTelI18n;
|
|
@@ -357,7 +142,7 @@ export class NgxsmkTelInputComponent implements AfterViewInit, OnChanges, OnDest
|
|
|
357
142
|
this.localizedCountries = v;
|
|
358
143
|
}
|
|
359
144
|
|
|
360
|
-
@Input() clearAriaLabel = 'Clear phone number';
|
|
145
|
+
@Input() clearAriaLabel: string = 'Clear phone number';
|
|
361
146
|
@Input() dir: 'ltr' | 'rtl' = 'ltr';
|
|
362
147
|
|
|
363
148
|
/* Placeholders (intl-tel-input) */
|
|
@@ -365,9 +150,11 @@ export class NgxsmkTelInputComponent implements AfterViewInit, OnChanges, OnDest
|
|
|
365
150
|
@Input() utilsScript?: string;
|
|
366
151
|
@Input() customPlaceholder?: (example: string, country: any) => string;
|
|
367
152
|
|
|
153
|
+
@Input() formatWhenValid: 'off' | 'blur' | 'typing' = 'blur';
|
|
154
|
+
|
|
368
155
|
/* Digits-only controls */
|
|
369
|
-
@Input() digitsOnly = true;
|
|
370
|
-
@Input() allowLeadingPlus = true;
|
|
156
|
+
@Input() digitsOnly: boolean = true;
|
|
157
|
+
@Input() allowLeadingPlus: boolean = true;
|
|
371
158
|
|
|
372
159
|
/* Outputs */
|
|
373
160
|
@Output() countryChange = new EventEmitter<{ iso2: CountryCode }>();
|
|
@@ -383,9 +170,9 @@ export class NgxsmkTelInputComponent implements AfterViewInit, OnChanges, OnDest
|
|
|
383
170
|
private validatorChange?: () => void;
|
|
384
171
|
private lastEmittedValid = false;
|
|
385
172
|
private pendingWrite: string | null = null;
|
|
386
|
-
private touched = false;
|
|
173
|
+
private touched: boolean = false;
|
|
387
174
|
|
|
388
|
-
readonly resolvedId = this.inputId || ('tel-' + Math.random().toString(36).slice(2));
|
|
175
|
+
readonly resolvedId: string = this.inputId || ('tel-' + Math.random().toString(36).slice(2));
|
|
389
176
|
|
|
390
177
|
constructor(
|
|
391
178
|
private readonly zone: NgZone,
|
|
@@ -394,8 +181,12 @@ export class NgxsmkTelInputComponent implements AfterViewInit, OnChanges, OnDest
|
|
|
394
181
|
) {
|
|
395
182
|
}
|
|
396
183
|
|
|
397
|
-
|
|
184
|
+
ngAfterViewInit(): void {
|
|
398
185
|
if (!isPlatformBrowser(this.platformId)) return;
|
|
186
|
+
void this.initAndWire();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private async initAndWire(): Promise<void> {
|
|
399
190
|
await this.initIntlTelInput();
|
|
400
191
|
this.bindDomListeners();
|
|
401
192
|
|
|
@@ -491,8 +282,17 @@ export class NgxsmkTelInputComponent implements AfterViewInit, OnChanges, OnDest
|
|
|
491
282
|
private async initIntlTelInput() {
|
|
492
283
|
const [{default: intlTelInput}] = await Promise.all([import('intl-tel-input')]);
|
|
493
284
|
|
|
494
|
-
const toLowerKeys = (m?: CountryMap) =>
|
|
495
|
-
|
|
285
|
+
const toLowerKeys = (m?: CountryMap) => {
|
|
286
|
+
if (!m) return undefined;
|
|
287
|
+
const out: Record<string, string> = {};
|
|
288
|
+
for (const k in m) {
|
|
289
|
+
if (Object.prototype.hasOwnProperty.call(m, k)) {
|
|
290
|
+
const v = (m as Record<string, string | undefined>)[k];
|
|
291
|
+
if (v != null) out[k.toLowerCase()] = v;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return out;
|
|
295
|
+
};
|
|
496
296
|
|
|
497
297
|
const config: any = {
|
|
498
298
|
initialCountry: this.initialCountry === 'auto' ? 'auto' : (this.initialCountry?.toLowerCase() || 'us'),
|
|
@@ -503,8 +303,8 @@ export class NgxsmkTelInputComponent implements AfterViewInit, OnChanges, OnDest
|
|
|
503
303
|
separateDialCode: this.separateDialCode,
|
|
504
304
|
geoIpLookup: (cb: (iso2: string) => void) => cb('us'),
|
|
505
305
|
|
|
506
|
-
// placeholders
|
|
507
|
-
autoPlaceholder: this.autoPlaceholder,
|
|
306
|
+
// placeholders
|
|
307
|
+
autoPlaceholder: this.autoPlaceholder,
|
|
508
308
|
utilsScript: this.utilsScript,
|
|
509
309
|
customPlaceholder: this.customPlaceholder,
|
|
510
310
|
|
|
@@ -520,7 +320,6 @@ export class NgxsmkTelInputComponent implements AfterViewInit, OnChanges, OnDest
|
|
|
520
320
|
this.iti = intlTelInput(this.inputRef.nativeElement, config);
|
|
521
321
|
});
|
|
522
322
|
|
|
523
|
-
// z-index for dropdown
|
|
524
323
|
(this.inputRef.nativeElement as HTMLElement).style.setProperty('--tel-dd-z', String(this.dropdownZIndex));
|
|
525
324
|
}
|
|
526
325
|
|
|
@@ -551,8 +350,7 @@ export class NgxsmkTelInputComponent implements AfterViewInit, OnChanges, OnDest
|
|
|
551
350
|
// ----- Input filtering (digits-only) -----
|
|
552
351
|
private sanitizeDigits(value: string): string {
|
|
553
352
|
if (!this.digitsOnly) return value;
|
|
554
|
-
let v = value.replace(/[^\d+]/g, '');
|
|
555
|
-
// allow only ONE leading + (if enabled)
|
|
353
|
+
let v = value.replace(/[^\d+]/g, '');
|
|
556
354
|
if (this.allowLeadingPlus) {
|
|
557
355
|
const hasLeadingPlus = v.startsWith('+');
|
|
558
356
|
v = (hasLeadingPlus ? '+' : '') + v.replace(/\+/g, '');
|
|
@@ -566,12 +364,9 @@ export class NgxsmkTelInputComponent implements AfterViewInit, OnChanges, OnDest
|
|
|
566
364
|
const el = this.inputRef.nativeElement;
|
|
567
365
|
|
|
568
366
|
this.zone.runOutsideAngular(() => {
|
|
569
|
-
// prevent invalid chars while typing
|
|
570
367
|
el.addEventListener('beforeinput', (ev: InputEvent) => {
|
|
571
368
|
if (!this.digitsOnly) return;
|
|
572
369
|
const data = (ev as any).data as string | null;
|
|
573
|
-
|
|
574
|
-
// allow deletions, cuts, etc.
|
|
575
370
|
if (!data || ev.inputType !== 'insertText') return;
|
|
576
371
|
|
|
577
372
|
const pos = el.selectionStart ?? 0;
|
|
@@ -581,7 +376,6 @@ export class NgxsmkTelInputComponent implements AfterViewInit, OnChanges, OnDest
|
|
|
581
376
|
if (!isDigit && !isPlusAtStart) ev.preventDefault();
|
|
582
377
|
});
|
|
583
378
|
|
|
584
|
-
// sanitize pastes
|
|
585
379
|
el.addEventListener('paste', (e: ClipboardEvent) => {
|
|
586
380
|
if (!this.digitsOnly) return;
|
|
587
381
|
e.preventDefault();
|
|
@@ -593,7 +387,6 @@ export class NgxsmkTelInputComponent implements AfterViewInit, OnChanges, OnDest
|
|
|
593
387
|
queueMicrotask(() => this.handleInput());
|
|
594
388
|
});
|
|
595
389
|
|
|
596
|
-
// catch any remaining non-digit changes (e.g., programmatic)
|
|
597
390
|
el.addEventListener('input', () => {
|
|
598
391
|
if (this.digitsOnly) {
|
|
599
392
|
const val = el.value;
|