svelte-tel-input 4.0.2 → 4.1.1
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/README.md +4 -1
- package/dist/components/input/TelInput.svelte +60 -5
- package/dist/types/index.d.ts +9 -0
- package/dist/utils/helpers.js +18 -3
- package/package.json +14 -13
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ A **headless**, fully customizable **Svelte 5** phone input toolkit.
|
|
|
9
9
|
All the ingredients to parse, format, and validate international phone numbers — you build the UI.
|
|
10
10
|
Store in [E.164](https://en.wikipedia.org/wiki/E.164). Ship with any CSS framework, design system, or custom markup.
|
|
11
11
|
|
|
12
|
-
[Documentation](https://svelte-tel-input.vercel.app/) · [Playground](https://svelte-tel-input.vercel.app/playground/) · [Changelog](CHANGELOG.md)
|
|
12
|
+
[Documentation](https://svelte-tel-input.vercel.app/) · [Playground](https://svelte-tel-input.vercel.app/playground/) · [Changelog](https://github.com/gyurielf/svelte-tel-input/blob/main/packages/svelte-tel-input/CHANGELOG.md)
|
|
13
13
|
|
|
14
14
|
</div>
|
|
15
15
|
|
|
@@ -80,7 +80,10 @@ Full API reference, options, events, types, and examples are on the **[documenta
|
|
|
80
80
|
- [Options](https://svelte-tel-input.vercel.app/reference/options/)
|
|
81
81
|
- [Events / Callbacks](https://svelte-tel-input.vercel.app/reference/events/)
|
|
82
82
|
- [API Methods](https://svelte-tel-input.vercel.app/reference/api/)
|
|
83
|
+
- [Validators](https://svelte-tel-input.vercel.app/reference/validators/)
|
|
84
|
+
- [Utilities](https://svelte-tel-input.vercel.app/reference/utilities/)
|
|
83
85
|
- [Types](https://svelte-tel-input.vercel.app/reference/types/)
|
|
86
|
+
- [Assets](https://svelte-tel-input.vercel.app/reference/assets/)
|
|
84
87
|
- [Playground](https://svelte-tel-input.vercel.app/playground/)
|
|
85
88
|
|
|
86
89
|
## Support
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
onValidityChange,
|
|
29
29
|
onValueChange,
|
|
30
30
|
onError,
|
|
31
|
+
initialFormat = 'international',
|
|
31
32
|
value = $bindable(''),
|
|
32
33
|
country = $bindable(null),
|
|
33
34
|
defaultCountry = null,
|
|
@@ -127,6 +128,17 @@
|
|
|
127
128
|
try {
|
|
128
129
|
const details = parsePhoneInput(value, countryObj);
|
|
129
130
|
const spaces = options?.spaces ?? defaultOptions.spaces;
|
|
131
|
+
if (
|
|
132
|
+
initialFormat === 'national' &&
|
|
133
|
+
details.formattedNumber &&
|
|
134
|
+
details.countryCallingCode
|
|
135
|
+
) {
|
|
136
|
+
const prefix = `+${details.countryCallingCode} `;
|
|
137
|
+
if (details.formattedNumber.startsWith(prefix)) {
|
|
138
|
+
const national = details.formattedNumber.slice(prefix.length);
|
|
139
|
+
return spaces ? national : national.replace(/\s/g, '');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
130
142
|
return spaces ? (details.formattedNumber ?? value) : value;
|
|
131
143
|
} catch {
|
|
132
144
|
return value;
|
|
@@ -149,6 +161,11 @@
|
|
|
149
161
|
let _lastWrittenCountry: CountryCode | null | undefined = untrack(() => country);
|
|
150
162
|
// let isInitialized = $state(false);
|
|
151
163
|
|
|
164
|
+
// When true, `handleParsePhoneNumber` and the spaces-effect will display the
|
|
165
|
+
// national (dial-code-stripped) format. Starts as `true` when
|
|
166
|
+
// `initialFormat='national'` and is cleared the first time the user types.
|
|
167
|
+
let _showNationalFormat = untrack(() => initialFormat === 'national');
|
|
168
|
+
|
|
152
169
|
// Merge options into default opts, to be able to set just one config option.
|
|
153
170
|
const combinedOptions = $derived({
|
|
154
171
|
...defaultOptions,
|
|
@@ -157,6 +174,8 @@
|
|
|
157
174
|
|
|
158
175
|
const handleInputAction = (value: string) => {
|
|
159
176
|
if (disabled || readonly) return;
|
|
177
|
+
// First user keystroke exits the "initial national format" display mode.
|
|
178
|
+
_showNationalFormat = false;
|
|
160
179
|
handleParsePhoneNumber(value, country, combinedOptions.validateOn !== 'blur');
|
|
161
180
|
};
|
|
162
181
|
|
|
@@ -357,9 +376,27 @@
|
|
|
357
376
|
}
|
|
358
377
|
|
|
359
378
|
// `inputValue` is the displayed value (must stay in sync with the directive's formatting).
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
379
|
+
// When `_showNationalFormat` is true (initial render, user hasn't typed yet),
|
|
380
|
+
// strip the dial-code prefix so the value appears in national format.
|
|
381
|
+
if (
|
|
382
|
+
_showNationalFormat &&
|
|
383
|
+
detailedValue?.formattedNumber &&
|
|
384
|
+
detailedValue?.countryCallingCode
|
|
385
|
+
) {
|
|
386
|
+
const _prefix = `+${detailedValue.countryCallingCode} `;
|
|
387
|
+
if (detailedValue.formattedNumber.startsWith(_prefix)) {
|
|
388
|
+
const _national = detailedValue.formattedNumber.slice(_prefix.length);
|
|
389
|
+
inputValue = combinedOptions.spaces ? _national : _national.replace(/\s/g, '');
|
|
390
|
+
} else {
|
|
391
|
+
inputValue = combinedOptions.spaces
|
|
392
|
+
? (detailedValue.formattedNumber ?? rawInput)
|
|
393
|
+
: rawInput;
|
|
394
|
+
}
|
|
395
|
+
} else {
|
|
396
|
+
inputValue = combinedOptions.spaces
|
|
397
|
+
? (detailedValue?.formattedNumber ?? rawInput)
|
|
398
|
+
: rawInput;
|
|
399
|
+
}
|
|
363
400
|
|
|
364
401
|
// `value` is the stored value (E.164 when possible).
|
|
365
402
|
value = detailedValue?.e164 ?? rawInput;
|
|
@@ -387,11 +424,24 @@
|
|
|
387
424
|
: placeholder
|
|
388
425
|
);
|
|
389
426
|
|
|
390
|
-
// Re-format displayed value when spaces option changes
|
|
427
|
+
// Re-format displayed value when spaces option changes.
|
|
428
|
+
// Also respects _showNationalFormat so the initial national rendering is preserved.
|
|
391
429
|
$effect(() => {
|
|
392
430
|
const spaces = combinedOptions.spaces;
|
|
393
431
|
untrack(() => {
|
|
394
432
|
if (inputValue !== '' && detailedValue) {
|
|
433
|
+
if (
|
|
434
|
+
_showNationalFormat &&
|
|
435
|
+
detailedValue.formattedNumber &&
|
|
436
|
+
detailedValue.countryCallingCode
|
|
437
|
+
) {
|
|
438
|
+
const prefix = `+${detailedValue.countryCallingCode} `;
|
|
439
|
+
if (detailedValue.formattedNumber.startsWith(prefix)) {
|
|
440
|
+
const national = detailedValue.formattedNumber.slice(prefix.length);
|
|
441
|
+
inputValue = spaces ? national : national.replace(/\s/g, '');
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
395
445
|
inputValue = spaces
|
|
396
446
|
? (detailedValue.formattedNumber ?? inputValue)
|
|
397
447
|
: (detailedValue.e164 ?? inputValue);
|
|
@@ -399,13 +449,16 @@
|
|
|
399
449
|
});
|
|
400
450
|
});
|
|
401
451
|
|
|
402
|
-
//Detect externally driven value changes (e.g. parent sets bind:value, or resets to
|
|
452
|
+
//Detect externally driven value changes (e.g. parent sets bind:value, or resets to '').
|
|
403
453
|
//The shadow `_lastWrittenValue` is stamped on every internal write inside
|
|
404
454
|
//handleParsePhoneNumber, so any difference here means the parent changed it.
|
|
405
455
|
$effect(() => {
|
|
406
456
|
const currentValue = value;
|
|
407
457
|
untrack(() => {
|
|
408
458
|
if (currentValue !== _lastWrittenValue) {
|
|
459
|
+
// External change (parent set value): re-apply initialFormat so the
|
|
460
|
+
// display respects it, just like on initial load.
|
|
461
|
+
_showNationalFormat = initialFormat === 'national';
|
|
409
462
|
handleParsePhoneNumber(currentValue, country ?? null);
|
|
410
463
|
}
|
|
411
464
|
});
|
|
@@ -436,6 +489,8 @@
|
|
|
436
489
|
if (value) {
|
|
437
490
|
// If country is specified, use it; otherwise, use the initial country (which is figured out from value)
|
|
438
491
|
const currentCountry = country || initialCountry;
|
|
492
|
+
// handleParsePhoneNumber respects _showNationalFormat internally,
|
|
493
|
+
// so national format is applied automatically when initialFormat='national'. Later maybe this could be optional.
|
|
439
494
|
handleParsePhoneNumber(value, currentCountry);
|
|
440
495
|
}
|
|
441
496
|
// isInitialized = true;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -164,6 +164,15 @@ export interface Props extends HTMLInputAttributes {
|
|
|
164
164
|
* Callback fired after component initialization.
|
|
165
165
|
*/
|
|
166
166
|
onLoad?: () => void;
|
|
167
|
+
/**
|
|
168
|
+
* Controls how the initial `value` is displayed in the input field.
|
|
169
|
+
* - `'international'` — displays the number with the country dial code prefix (e.g. `+36 20 123 4567`). This is the default.
|
|
170
|
+
* - `'national'` — displays the national part only (e.g. `20 123 4567`), while `value` remains E164. The `country` prop will be auto-detected from the value if not explicitly set.
|
|
171
|
+
*
|
|
172
|
+
* This only affects the initial render and outside change from the provided `value` prop; user input behaviour is unchanged.
|
|
173
|
+
* @default 'international'
|
|
174
|
+
*/
|
|
175
|
+
initialFormat?: 'international' | 'national';
|
|
167
176
|
}
|
|
168
177
|
|
|
169
178
|
export type { CountryCode };
|
package/dist/utils/helpers.js
CHANGED
|
@@ -108,7 +108,7 @@ export const inputParser = (input, { allowSpaces }) => {
|
|
|
108
108
|
return value;
|
|
109
109
|
};
|
|
110
110
|
// ---------------------------------------------------------------------------
|
|
111
|
-
// Phone-number normalizer
|
|
111
|
+
// Phone-number normalizer
|
|
112
112
|
// ---------------------------------------------------------------------------
|
|
113
113
|
const normalizeForLibphonenumber = (input) => {
|
|
114
114
|
let value = '';
|
|
@@ -193,8 +193,23 @@ export const parsePhoneInput = (input, country) => {
|
|
|
193
193
|
}
|
|
194
194
|
else if (defaultCountryIso2) {
|
|
195
195
|
const formatted = formatIncompletePhoneNumber(capped, defaultCountryIso2);
|
|
196
|
-
if (formatted === capped &&
|
|
197
|
-
|
|
196
|
+
if (formatted === capped && country?.dialCode) {
|
|
197
|
+
if (phone && phone.nationalNumber !== capped) {
|
|
198
|
+
// Trunk-prefixed complete number (e.g. GB "07947…" → nationalNumber
|
|
199
|
+
// "7947…"). Synthesis would be invalid; use the actual international
|
|
200
|
+
// format which correctly omits the trunk prefix.
|
|
201
|
+
formattedNumber =
|
|
202
|
+
formatInternational && countryCallingCode
|
|
203
|
+
? formatInternational.slice(countryCallingCode.length + 1).trim()
|
|
204
|
+
: stripSpecialChars(capped);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
// National significant digits (partial or complete, no trunk prefix).
|
|
208
|
+
// Synthesize an international string to get consistent spacing from
|
|
209
|
+
// the first digit, then strip "+{dialCode} ".
|
|
210
|
+
const intl = formatIncompletePhoneNumber(`+${country.dialCode}${capped}`);
|
|
211
|
+
formattedNumber = stripSpecialChars(intl.slice(country.dialCode.length + 1).trim());
|
|
212
|
+
}
|
|
198
213
|
}
|
|
199
214
|
else {
|
|
200
215
|
formattedNumber = stripSpecialChars(formatted || capped);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-tel-input",
|
|
3
3
|
"description": "svelte-tel-input",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.1.1",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/gyurielf/svelte-tel-input.git"
|
|
@@ -30,32 +30,33 @@
|
|
|
30
30
|
"svelte": "^5.0.0"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"libphonenumber-js": "1.12.
|
|
33
|
+
"libphonenumber-js": "1.12.41"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@sveltejs/adapter-auto": "7.0.1",
|
|
37
|
-
"@sveltejs/kit": "^2.
|
|
37
|
+
"@sveltejs/kit": "^2.57.1",
|
|
38
38
|
"@sveltejs/package": "^2.5.7",
|
|
39
39
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
40
|
-
"@tailwindcss/vite": "^4.2.
|
|
40
|
+
"@tailwindcss/vite": "^4.2.3",
|
|
41
41
|
"@testing-library/jest-dom": "6.9.1",
|
|
42
42
|
"@testing-library/svelte": "^5.3.1",
|
|
43
43
|
"@testing-library/user-event": "^14.6.1",
|
|
44
44
|
"@types/micromatch": "^4.0.10",
|
|
45
|
-
"
|
|
46
|
-
"
|
|
45
|
+
"@types/node": "^22.17.0",
|
|
46
|
+
"dotenv": "^17.4.2",
|
|
47
|
+
"jsdom": "^29.0.2",
|
|
47
48
|
"micromatch": "^4.0.8",
|
|
48
|
-
"postcss": "^8.5.
|
|
49
|
+
"postcss": "^8.5.10",
|
|
49
50
|
"publint": "^0.3.18",
|
|
50
|
-
"svelte": "^5.55.
|
|
51
|
-
"svelte-check": "^4.4.
|
|
52
|
-
"svelte2tsx": "^0.7.
|
|
53
|
-
"tailwindcss": "^4.2.
|
|
51
|
+
"svelte": "^5.55.4",
|
|
52
|
+
"svelte-check": "^4.4.6",
|
|
53
|
+
"svelte2tsx": "^0.7.53",
|
|
54
|
+
"tailwindcss": "^4.2.3",
|
|
54
55
|
"tslib": "^2.8.1",
|
|
55
56
|
"typescript": "^5.9.3",
|
|
56
57
|
"valibot": "^1.3.1",
|
|
57
|
-
"vite": "^7.3.
|
|
58
|
-
"vitest": "^4.1.
|
|
58
|
+
"vite": "^7.3.2",
|
|
59
|
+
"vitest": "^4.1.4",
|
|
59
60
|
"zod": "^4.3.6"
|
|
60
61
|
},
|
|
61
62
|
"type": "module",
|