svelte-tel-input 4.0.1 → 4.1.0
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 +65 -8
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/types/index.d.ts +14 -5
- package/dist/utils/{directives/countryHelpers.d.ts → countryHelpers.d.ts} +1 -1
- package/dist/utils/{directives/countryHelpers.js → countryHelpers.js} +1 -1
- package/dist/utils/directives/telInputAction.js +2 -2
- package/dist/utils/helpers.js +1 -1
- package/dist/utils/index.d.ts +1 -3
- package/dist/utils/index.js +1 -3
- package/package.json +14 -13
- package/dist/utils/directives/clickOutsideAction.d.ts +0 -3
- package/dist/utils/directives/clickOutsideAction.js +0 -18
- /package/dist/utils/{directives/cursorPosition.d.ts → cursorPosition.d.ts} +0 -0
- /package/dist/utils/{directives/cursorPosition.js → cursorPosition.js} +0 -0
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
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount, untrack } from 'svelte';
|
|
3
3
|
import { ParseError } from 'libphonenumber-js/max';
|
|
4
|
-
import { generatePlaceholder
|
|
4
|
+
import { generatePlaceholder } from '../../utils/index.js';
|
|
5
|
+
import { parsePhoneInput } from '../../utils/helpers.js';
|
|
6
|
+
import { telInputAction } from '../../utils/directives/telInputAction.js';
|
|
7
|
+
import { getCountry, guessCountryByPartialNumber } from '../../utils/countryHelpers.js';
|
|
5
8
|
import type { CountryCode, TelInputOptions, Props, ValidationError } from '../../types';
|
|
6
|
-
import { getCountry, guessCountryByPartialNumber } from '../../utils/directives/countryHelpers';
|
|
7
9
|
|
|
8
10
|
const defaultOptions = {
|
|
9
11
|
autoPlaceholder: true,
|
|
@@ -26,12 +28,13 @@
|
|
|
26
28
|
onValidityChange,
|
|
27
29
|
onValueChange,
|
|
28
30
|
onError,
|
|
31
|
+
initialFormat = 'international',
|
|
29
32
|
value = $bindable(''),
|
|
30
33
|
country = $bindable(null),
|
|
31
34
|
defaultCountry = null,
|
|
32
35
|
detailedValue = $bindable(null),
|
|
33
36
|
valid = $bindable(true),
|
|
34
|
-
validationError = $bindable<ValidationError
|
|
37
|
+
validationError = $bindable<Readonly<ValidationError>>(null),
|
|
35
38
|
options = defaultOptions,
|
|
36
39
|
el = $bindable(undefined),
|
|
37
40
|
'aria-invalid': ariaInvalidProp = undefined,
|
|
@@ -125,6 +128,17 @@
|
|
|
125
128
|
try {
|
|
126
129
|
const details = parsePhoneInput(value, countryObj);
|
|
127
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
|
+
}
|
|
128
142
|
return spaces ? (details.formattedNumber ?? value) : value;
|
|
129
143
|
} catch {
|
|
130
144
|
return value;
|
|
@@ -147,6 +161,11 @@
|
|
|
147
161
|
let _lastWrittenCountry: CountryCode | null | undefined = untrack(() => country);
|
|
148
162
|
// let isInitialized = $state(false);
|
|
149
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
|
+
|
|
150
169
|
// Merge options into default opts, to be able to set just one config option.
|
|
151
170
|
const combinedOptions = $derived({
|
|
152
171
|
...defaultOptions,
|
|
@@ -155,6 +174,8 @@
|
|
|
155
174
|
|
|
156
175
|
const handleInputAction = (value: string) => {
|
|
157
176
|
if (disabled || readonly) return;
|
|
177
|
+
// First user keystroke exits the "initial national format" display mode.
|
|
178
|
+
_showNationalFormat = false;
|
|
158
179
|
handleParsePhoneNumber(value, country, combinedOptions.validateOn !== 'blur');
|
|
159
180
|
};
|
|
160
181
|
|
|
@@ -355,9 +376,27 @@
|
|
|
355
376
|
}
|
|
356
377
|
|
|
357
378
|
// `inputValue` is the displayed value (must stay in sync with the directive's formatting).
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
+
}
|
|
361
400
|
|
|
362
401
|
// `value` is the stored value (E.164 when possible).
|
|
363
402
|
value = detailedValue?.e164 ?? rawInput;
|
|
@@ -385,11 +424,24 @@
|
|
|
385
424
|
: placeholder
|
|
386
425
|
);
|
|
387
426
|
|
|
388
|
-
// 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.
|
|
389
429
|
$effect(() => {
|
|
390
430
|
const spaces = combinedOptions.spaces;
|
|
391
431
|
untrack(() => {
|
|
392
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
|
+
}
|
|
393
445
|
inputValue = spaces
|
|
394
446
|
? (detailedValue.formattedNumber ?? inputValue)
|
|
395
447
|
: (detailedValue.e164 ?? inputValue);
|
|
@@ -397,13 +449,16 @@
|
|
|
397
449
|
});
|
|
398
450
|
});
|
|
399
451
|
|
|
400
|
-
//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 '').
|
|
401
453
|
//The shadow `_lastWrittenValue` is stamped on every internal write inside
|
|
402
454
|
//handleParsePhoneNumber, so any difference here means the parent changed it.
|
|
403
455
|
$effect(() => {
|
|
404
456
|
const currentValue = value;
|
|
405
457
|
untrack(() => {
|
|
406
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';
|
|
407
462
|
handleParsePhoneNumber(currentValue, country ?? null);
|
|
408
463
|
}
|
|
409
464
|
});
|
|
@@ -434,6 +489,8 @@
|
|
|
434
489
|
if (value) {
|
|
435
490
|
// If country is specified, use it; otherwise, use the initial country (which is figured out from value)
|
|
436
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.
|
|
437
494
|
handleParsePhoneNumber(value, currentCountry);
|
|
438
495
|
}
|
|
439
496
|
// isInitialized = true;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
export { default as TelInput } from './components/input/TelInput.svelte';
|
|
2
|
-
export {
|
|
3
|
-
export { parsePhoneNumberWithError, ParseError } from 'libphonenumber-js/max';
|
|
2
|
+
export { parse, normalizeToE164, pickCountries } from './utils/index.js';
|
|
4
3
|
export { countries } from './assets/index.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
export { default as TelInput } from './components/input/TelInput.svelte';
|
|
2
|
-
export {
|
|
3
|
-
export { parsePhoneNumberWithError, ParseError } from 'libphonenumber-js/max';
|
|
2
|
+
export { parse, normalizeToE164, pickCountries } from './utils/index.js';
|
|
4
3
|
export { countries } from './assets/index.js';
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CountryCode } from 'libphonenumber-js';
|
|
2
2
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
3
|
|
|
4
4
|
export interface Country {
|
|
@@ -38,7 +38,7 @@ export interface DetailedValue {
|
|
|
38
38
|
uri: string | null;
|
|
39
39
|
e164: string | null;
|
|
40
40
|
/** Granular validation error when `isValid` is `false`. */
|
|
41
|
-
validationError
|
|
41
|
+
validationError: ValidationError;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
@@ -133,7 +133,7 @@ export interface Props extends HTMLInputAttributes {
|
|
|
133
133
|
/** Validity of the input based on the config settings. */
|
|
134
134
|
valid?: boolean;
|
|
135
135
|
/** The reason the current value is invalid, or `null` when valid. */
|
|
136
|
-
validationError?: ValidationError
|
|
136
|
+
validationError?: Readonly<ValidationError>;
|
|
137
137
|
/** You can turn on and off certain features by this object */
|
|
138
138
|
options?: TelInputOptions;
|
|
139
139
|
/** Binding to the underlying `<input>` element */
|
|
@@ -148,7 +148,7 @@ export interface Props extends HTMLInputAttributes {
|
|
|
148
148
|
* @param newValidity The new validity state.
|
|
149
149
|
* @param error The validation error type.
|
|
150
150
|
*/
|
|
151
|
-
onValidityChange?: (newValidity: boolean, error: ValidationError) => void;
|
|
151
|
+
onValidityChange?: (newValidity: boolean, error: Readonly<ValidationError>) => void;
|
|
152
152
|
/**
|
|
153
153
|
* Callback fired when the value or details change.
|
|
154
154
|
* @param newValue The new E164 value.
|
|
@@ -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
|
-
export type {
|
|
178
|
+
export type { CountryCode };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { countries as normalizedCountries } from '
|
|
1
|
+
import { countries as normalizedCountries } from '../assets/index.js';
|
|
2
2
|
export const getCountry = ({ field, value, countries = normalizedCountries }) => {
|
|
3
3
|
if (['priority'].includes(field)) {
|
|
4
4
|
throw new Error(`Field "${field}" is not supported`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { getCountry } from '
|
|
1
|
+
import { getCountry } from '../countryHelpers.js';
|
|
2
2
|
import { parsePhoneInput } from '../helpers.js';
|
|
3
|
-
import { calculateCursorPosition } from '
|
|
3
|
+
import { calculateCursorPosition } from '../cursorPosition.js';
|
|
4
4
|
let inputState = null;
|
|
5
5
|
const normalizeUserInput = (input) => {
|
|
6
6
|
let value = '';
|
package/dist/utils/helpers.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AsYouType, getExampleNumber, formatIncompletePhoneNumber, validatePhoneNumberLength } from 'libphonenumber-js/max';
|
|
2
2
|
import { examplePhoneNumbers, countries } from '../assets/index.js';
|
|
3
|
-
import { getCountry } from './
|
|
3
|
+
import { getCountry } from './countryHelpers.js';
|
|
4
4
|
const whiteSpaceRegex = new RegExp('[\\t\\n\\v\\f\\r \\u00a0\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u200b\\u2028\\u2029\\u3000]', 'g');
|
|
5
5
|
const plusSignRegex = new RegExp('\\+', 'g');
|
|
6
6
|
export const generatePlaceholder = (country, { spaces } = {
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,3 +1 @@
|
|
|
1
|
-
export { generatePlaceholder,
|
|
2
|
-
export * from './directives/clickOutsideAction.js';
|
|
3
|
-
export * from './directives/telInputAction.js';
|
|
1
|
+
export { generatePlaceholder, parse, normalizeToE164, pickCountries } from './helpers.js';
|
package/dist/utils/index.js
CHANGED
|
@@ -1,3 +1 @@
|
|
|
1
|
-
export { generatePlaceholder,
|
|
2
|
-
export * from './directives/clickOutsideAction.js';
|
|
3
|
-
export * from './directives/telInputAction.js';
|
|
1
|
+
export { generatePlaceholder, parse, normalizeToE164, pickCountries } from './helpers.js';
|
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.0
|
|
4
|
+
"version": "4.1.0",
|
|
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.
|
|
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",
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export const clickOutsideAction = (node, handler, skipPrevented = true) => {
|
|
2
|
-
const handleClick = async (event) => {
|
|
3
|
-
if (skipPrevented) {
|
|
4
|
-
if (!node.contains(event.target) && !event.defaultPrevented)
|
|
5
|
-
handler();
|
|
6
|
-
}
|
|
7
|
-
else {
|
|
8
|
-
if (!node.contains(event.target))
|
|
9
|
-
handler();
|
|
10
|
-
}
|
|
11
|
-
};
|
|
12
|
-
document.addEventListener('click', handleClick, true);
|
|
13
|
-
return {
|
|
14
|
-
destroy() {
|
|
15
|
-
document.removeEventListener('click', handleClick, true);
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
};
|
|
File without changes
|
|
File without changes
|