svelte-tel-input 4.0.0-next.4 → 4.0.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 CHANGED
@@ -17,8 +17,6 @@ Store in [E.164](https://en.wikipedia.org/wiki/E.164). Ship with any CSS framewo
17
17
 
18
18
  ## Features
19
19
 
20
- - **Headless & unstyled** — zero built-in styles; bring Tailwind, CSS Modules, or plain CSS
21
- - **Fully customizable UI** — you own the markup; the library owns the logic
22
20
  - **E.164 storage** — one canonical format, always searchable and SMS-ready
23
21
  - **Auto-detect country** from dial code (`+44` → `GB`)
24
22
  - **Smart formatting** — international display with cursor position preservation
@@ -30,7 +28,8 @@ Store in [E.164](https://en.wikipedia.org/wiki/E.164). Ship with any CSS framewo
30
28
  - **SSR / SSG** compatible
31
29
  - **Svelte 5 runes** — `$bindable` props, `$state`, `$derived`
32
30
  - **Accessible** — `aria-invalid` auto-set, ARIA attribute passthrough, `type="tel"`
33
- - **Unstyled** — bring your own CSS or Tailwind classes
31
+ - **Headless & unstyled** — zero built-in styles; bring Tailwind, CSS Modules, or plain CSS
32
+ - **Fully customizable UI** — you own the markup; the library owns the logic
34
33
 
35
34
  ## Install
36
35
 
@@ -161,8 +161,18 @@
161
161
  const getValidationError = (
162
162
  isEmpty: boolean,
163
163
  parseValid: boolean,
164
- resolvedCountry: CountryCode | null | undefined
164
+ resolvedCountry: CountryCode | null | undefined,
165
+ currentCountry: CountryCode | null | undefined = country
165
166
  ): ValidationError => {
167
+ if (
168
+ combinedOptions.lockCountry &&
169
+ currentCountry != null &&
170
+ resolvedCountry != null &&
171
+ resolvedCountry !== currentCountry
172
+ ) {
173
+ return 'COUNTRY_NOT_ALLOWED';
174
+ }
175
+
166
176
  const allowed = combinedOptions.allowedCountries;
167
177
  if (allowed?.length && resolvedCountry != null && !allowed.includes(resolvedCountry)) {
168
178
  return 'COUNTRY_NOT_ALLOWED';
@@ -176,9 +186,10 @@
176
186
  const applyValidity = (
177
187
  isEmpty: boolean,
178
188
  parseValid: boolean,
179
- resolvedCountry?: CountryCode | null
189
+ resolvedCountry?: CountryCode | null,
190
+ currentCountry: CountryCode | null | undefined = country
180
191
  ) => {
181
- const error = getValidationError(isEmpty, parseValid, resolvedCountry);
192
+ const error = getValidationError(isEmpty, parseValid, resolvedCountry, currentCountry);
182
193
  valid = error === null;
183
194
  validationError = error;
184
195
  onValidityChange?.(valid, error);
@@ -193,7 +204,12 @@
193
204
  if (inputValue === '') {
194
205
  applyValidity(true, false, country);
195
206
  } else {
196
- applyValidity(false, detailedValue?.isValid ?? false, country);
207
+ applyValidity(
208
+ false,
209
+ detailedValue?.isPhoneValid ?? false,
210
+ detailedValue?.countryCode ?? country,
211
+ country
212
+ );
197
213
  }
198
214
  }
199
215
  const { onblur } = rest;
@@ -319,21 +335,22 @@
319
335
  }
320
336
  }
321
337
 
322
- // If the resolved country is not in allowedCountries, the phone number is
323
- // intrinsically valid (libphonenumber-js says so) but the application rejects
324
- // it. Patch detailedValue to reflect this so that detailedValue.isValid is
325
- // consistent with the component's `valid` prop and `validationError`.
326
- const _allowedCountries = combinedOptions.allowedCountries;
327
- if (
328
- detailedValue &&
329
- _allowedCountries?.length &&
330
- detailedValue.countryCode != null &&
331
- !_allowedCountries.includes(detailedValue.countryCode)
332
- ) {
338
+ const resolvedCountry =
339
+ detailedValue?.countryCode ?? numberHasCountry?.iso2 ?? country ?? null;
340
+ const componentValidationError = detailedValue
341
+ ? getValidationError(
342
+ false,
343
+ detailedValue.isPhoneValid ?? false,
344
+ resolvedCountry,
345
+ currCountry ?? country
346
+ )
347
+ : null;
348
+
349
+ if (detailedValue && componentValidationError !== detailedValue.validationError) {
333
350
  detailedValue = {
334
351
  ...detailedValue,
335
- isValid: false,
336
- validationError: 'COUNTRY_NOT_ALLOWED'
352
+ isValid: componentValidationError === null && detailedValue.isPhoneValid,
353
+ validationError: componentValidationError
337
354
  };
338
355
  }
339
356
 
@@ -348,7 +365,12 @@
348
365
  onValueChange?.(value, detailedValue);
349
366
 
350
367
  if (shouldValidate) {
351
- applyValidity(false, detailedValue?.isValid ?? false, country);
368
+ applyValidity(
369
+ false,
370
+ detailedValue?.isPhoneValid ?? false,
371
+ resolvedCountry,
372
+ currCountry ?? country
373
+ );
352
374
  }
353
375
  };
354
376
 
@@ -44,7 +44,7 @@ export interface DetailedValue {
44
44
  /**
45
45
  * The reason the current phone number input is invalid.
46
46
  * - `'REQUIRED'` — field is empty and `required` is `true`
47
- * - `'COUNTRY_NOT_ALLOWED'` — the resolved country is not in `options.allowedCountries`
47
+ * - `'COUNTRY_NOT_ALLOWED'` — the resolved country is not in `options.allowedCountries`, or `options.lockCountry` rejects an international number that resolves to a different country than the locked one
48
48
  * - `'TOO_SHORT'` — number has too few digits
49
49
  * - `'TOO_LONG'` — number has too many digits
50
50
  * - `'NOT_A_NUMBER'` — input does not look like a phone number at all
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.0-next.4",
4
+ "version": "4.0.1",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/gyurielf/svelte-tel-input.git"
@@ -55,7 +55,7 @@
55
55
  "typescript": "^5.9.3",
56
56
  "valibot": "^1.3.1",
57
57
  "vite": "^7.3.1",
58
- "vitest": "^4.1.0",
58
+ "vitest": "^4.1.1",
59
59
  "zod": "^4.3.6"
60
60
  },
61
61
  "type": "module",