@alikhalilll/a-tel-input 1.0.2 → 1.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.
Files changed (42) hide show
  1. package/.media/README.md +3 -0
  2. package/.media/hero.png +0 -0
  3. package/README.md +597 -72
  4. package/dist/_chunks/types.d.ts +661 -0
  5. package/dist/_chunks/types.js +52 -0
  6. package/dist/_chunks/types.js.map +1 -0
  7. package/dist/_chunks/usePhoneValidation.js +539 -0
  8. package/dist/_chunks/usePhoneValidation.js.map +1 -0
  9. package/dist/index.cjs +471 -695
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +122 -587
  12. package/dist/index.d.ts +122 -587
  13. package/dist/index.js +454 -658
  14. package/dist/index.js.map +1 -1
  15. package/dist/styles.css +20 -5
  16. package/dist/vee-validate/index.cjs +113 -0
  17. package/dist/vee-validate/index.cjs.map +1 -0
  18. package/dist/vee-validate/index.d.cts +86 -0
  19. package/dist/vee-validate/index.d.ts +86 -0
  20. package/dist/vee-validate/index.js +112 -0
  21. package/dist/vee-validate/index.js.map +1 -0
  22. package/dist/zod/index.cjs +211 -0
  23. package/dist/zod/index.cjs.map +1 -0
  24. package/dist/zod/index.d.cts +65 -0
  25. package/dist/zod/index.d.ts +65 -0
  26. package/dist/zod/index.js +208 -0
  27. package/dist/zod/index.js.map +1 -0
  28. package/package.json +34 -3
  29. package/src/components/ACountrySelect.vue +17 -3
  30. package/src/components/ATelInput.vue +206 -66
  31. package/src/composables/useCountryDetection.ts +28 -11
  32. package/src/composables/useCountryMatching.ts +160 -20
  33. package/src/composables/useCountrySelection.ts +71 -0
  34. package/src/composables/usePhoneValidation.ts +81 -18
  35. package/src/composables/useSyncedModel.ts +80 -0
  36. package/src/composables/useTelInputValidation.ts +50 -11
  37. package/src/index.ts +2 -0
  38. package/src/types.ts +80 -0
  39. package/src/vee-validate/index.ts +2 -0
  40. package/src/vee-validate/useTelField.ts +202 -0
  41. package/src/zod/index.ts +259 -0
  42. package/web-types.json +44 -1
package/dist/index.cjs CHANGED
@@ -1,33 +1,10 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- //#region \0rolldown/runtime.js
3
- var __create = Object.create;
4
- var __defProp$1 = Object.defineProperty;
5
- var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropNames$1 = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
- var __hasOwnProp$1 = Object.prototype.hasOwnProperty;
9
- var __copyProps$1 = (to, from, except, desc) => {
10
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames$1(from), i = 0, n = keys.length, key; i < n; i++) {
11
- key = keys[i];
12
- if (!__hasOwnProp$1.call(to, key) && key !== except) __defProp$1(to, key, {
13
- get: ((k) => from[k]).bind(null, key),
14
- enumerable: !(desc = __getOwnPropDesc$1(from, key)) || desc.enumerable
15
- });
16
- }
17
- return to;
18
- };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps$1(isNodeMode || !mod || !mod.__esModule ? __defProp$1(target, "default", {
20
- value: mod,
21
- enumerable: true
22
- }) : target, mod));
23
- //#endregion
2
+ const require_usePhoneValidation = require("./_chunks/usePhoneValidation.js");
3
+ const require_types = require("./_chunks/types.js");
24
4
  let vue = require("vue");
25
- vue = __toESM(vue, 1);
5
+ vue = require_usePhoneValidation.__toESM(vue, 1);
26
6
  let libphonenumber_js = require("libphonenumber-js");
27
- let libphonenumber_js_examples_mobile_json = require("libphonenumber-js/examples.mobile.json");
28
- libphonenumber_js_examples_mobile_json = __toESM(libphonenumber_js_examples_mobile_json, 1);
29
7
  let _vueuse_core = require("@vueuse/core");
30
- let class_variance_authority = require("class-variance-authority");
31
8
  let aria_hidden = require("aria-hidden");
32
9
  //#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
33
10
  function r$2(e) {
@@ -3428,508 +3405,6 @@ function cn$2(...inputs) {
3428
3405
  return twMerge$2(clsx$2(inputs));
3429
3406
  }
3430
3407
  //#endregion
3431
- //#region src/utils/digits.ts
3432
- /**
3433
- * Alternative-numeral support. Phone numbers are routinely entered with the digits of the
3434
- * user's own script — Arabic-Indic, Persian/Urdu, Devanagari, Bengali. `libphonenumber-js`
3435
- * and our own `\d` cleanup only understand ASCII `0-9`, so anything else silently becomes
3436
- * an empty number. `normalizeDigits` folds those scripts down to ASCII before validation.
3437
- */
3438
- /**
3439
- * Base code points of contiguous decimal-digit blocks. Each block runs `base`‥`base+9`
3440
- * for digit `0`‥`9`, so the ASCII digit is `codePoint - base`. Add a script by appending
3441
- * one entry here.
3442
- */
3443
- const LOCALE_DIGIT_RANGES = [
3444
- {
3445
- name: "arabic-indic",
3446
- base: 1632
3447
- },
3448
- {
3449
- name: "extended-arabic",
3450
- base: 1776
3451
- },
3452
- {
3453
- name: "devanagari",
3454
- base: 2406
3455
- },
3456
- {
3457
- name: "bengali",
3458
- base: 2534
3459
- }
3460
- ];
3461
- /** Lookup of every non-ASCII digit code point → its ASCII character. */
3462
- const DIGIT_MAP = (() => {
3463
- const map = /* @__PURE__ */ new Map();
3464
- for (const { base } of LOCALE_DIGIT_RANGES) for (let d = 0; d <= 9; d++) map.set(base + d, String(d));
3465
- return map;
3466
- })();
3467
- /**
3468
- * Replace any supported non-ASCII decimal digit with its ASCII equivalent. Every other
3469
- * character (spaces, `+`, separators, letters) is left untouched — callers still run their
3470
- * own `\D` cleanup afterwards.
3471
- */
3472
- function normalizeDigits(input) {
3473
- const str = String(input ?? "");
3474
- let out = "";
3475
- for (const ch of str) {
3476
- const cp = ch.codePointAt(0);
3477
- out += cp != null && DIGIT_MAP.get(cp) || ch;
3478
- }
3479
- return out;
3480
- }
3481
- //#endregion
3482
- //#region src/composables/usePhoneValidation.ts
3483
- /**
3484
- * Country list + phone validation, framework-agnostic.
3485
- *
3486
- * Ported from the reference @pkgs/ui ATelInput composable with these cleanups:
3487
- * - Drop Nuxt-only `process.client` checks → use plain `typeof window !== 'undefined'`.
3488
- * - Drop Arabic default placeholder; let consumers pass their own.
3489
- * - Expand the offline fallback list from 2 → ~20 of the most-populated countries.
3490
- * - Keep REST Countries fetch + localStorage cache + libphonenumber-js examples + fast `search_key`.
3491
- */
3492
- const STORAGE_KEY = "ali_ui_phone_countries_v1";
3493
- const REST_COUNTRIES_URL = "https://restcountries.com/v3.1/all?fields=name,cca2,idd,flags";
3494
- const EX = libphonenumber_js_examples_mobile_json.default;
3495
- const isBrowser$1 = () => typeof window !== "undefined";
3496
- function toDigits(v) {
3497
- return normalizeDigits(String(v ?? "")).replace(/\D/g, "");
3498
- }
3499
- /**
3500
- * Render an ASCII digit string in a locale's numeral system (e.g. `'ar'` → `٠-٩`).
3501
- * Used only for display hints — falls back to ASCII if the locale is unknown.
3502
- */
3503
- function localizeDigits(digits, locale) {
3504
- if (!locale) return digits;
3505
- try {
3506
- const fmt = new Intl.NumberFormat(locale, { useGrouping: false });
3507
- return digits.replace(/[0-9]/g, (d) => fmt.format(Number(d)));
3508
- } catch {
3509
- return digits;
3510
- }
3511
- }
3512
- function ensurePlusDial(dial) {
3513
- const d = toDigits(dial);
3514
- return d ? `+${d}` : "";
3515
- }
3516
- function normalizeIso2(iso2) {
3517
- return String(iso2 ?? "").trim().toUpperCase();
3518
- }
3519
- function dropLeadingZeros(digits) {
3520
- return String(digits ?? "").replace(/^0+/, "");
3521
- }
3522
- function buildFullE164(dial, digits) {
3523
- const dialClean = ensurePlusDial(dial);
3524
- const nsn = dropLeadingZeros(toDigits(digits));
3525
- return dialClean && nsn ? `${dialClean}${nsn}` : null;
3526
- }
3527
- function inferLengthFromExample(national) {
3528
- const d = toDigits(national);
3529
- if (!d) return {
3530
- min: null,
3531
- max: null
3532
- };
3533
- const n = d.length;
3534
- return {
3535
- min: Math.max(4, n - 2),
3536
- max: n + 2
3537
- };
3538
- }
3539
- function buildDialCode(idd) {
3540
- const root = idd?.root?.trim();
3541
- if (!root || !root.startsWith("+")) return null;
3542
- const out = `${root}${idd?.suffixes?.[0]?.trim() ?? ""}`;
3543
- return out.startsWith("+") ? out : null;
3544
- }
3545
- function normalizeSearchKey(input) {
3546
- return String(input ?? "").toLowerCase().replace(/\s+/g, " ").trim().replace(/[^\p{L}\p{N}+ ]/gu, "");
3547
- }
3548
- /**
3549
- * Return a copy of the country list with display names localized to `locale` via
3550
- * `Intl.DisplayNames`. `search_key` is rebuilt (keeping the English name too) so search
3551
- * still matches either spelling. Unknown locales / regions fall back to the English name.
3552
- */
3553
- function localizeCountries(list, locale) {
3554
- if (!locale) return list;
3555
- let display;
3556
- try {
3557
- display = new Intl.DisplayNames([locale], { type: "region" });
3558
- } catch {
3559
- return list;
3560
- }
3561
- return list.map((c) => {
3562
- let localized = c.raw_data.name;
3563
- try {
3564
- localized = display.of(c.raw_data.iso2) || c.raw_data.name;
3565
- } catch {}
3566
- if (localized === c.raw_data.name) return c;
3567
- const dial = c.raw_data.dial_code;
3568
- return {
3569
- ...c,
3570
- label: `${localized} (${dial})`,
3571
- search_key: normalizeSearchKey(`${localized} ${c.raw_data.name} ${dial} ${c.raw_data.iso2} ${c.raw_data.dial_digits}`),
3572
- raw_data: {
3573
- ...c.raw_data,
3574
- name: localized
3575
- }
3576
- };
3577
- });
3578
- }
3579
- function makeFallback(iso2, name, dial) {
3580
- const dialDigits = toDigits(dial);
3581
- return {
3582
- label: `${name} (+${dialDigits})`,
3583
- value: iso2,
3584
- search_key: normalizeSearchKey(`${name} +${dialDigits} ${iso2}`),
3585
- raw_data: {
3586
- iso2,
3587
- dial_code: `+${dialDigits}`,
3588
- dial_digits: dialDigits,
3589
- name,
3590
- flag: `https://flagcdn.com/w40/${iso2.toLowerCase()}.png`,
3591
- source: "fallback",
3592
- original: {}
3593
- }
3594
- };
3595
- }
3596
- const FALLBACK_COUNTRIES = [
3597
- makeFallback("SA", "Saudi Arabia", "+966"),
3598
- makeFallback("EG", "Egypt", "+20"),
3599
- makeFallback("AE", "United Arab Emirates", "+971"),
3600
- makeFallback("US", "United States", "+1"),
3601
- makeFallback("GB", "United Kingdom", "+44"),
3602
- makeFallback("DE", "Germany", "+49"),
3603
- makeFallback("FR", "France", "+33"),
3604
- makeFallback("ES", "Spain", "+34"),
3605
- makeFallback("IT", "Italy", "+39"),
3606
- makeFallback("TR", "Turkey", "+90"),
3607
- makeFallback("RU", "Russia", "+7"),
3608
- makeFallback("CN", "China", "+86"),
3609
- makeFallback("IN", "India", "+91"),
3610
- makeFallback("JP", "Japan", "+81"),
3611
- makeFallback("KR", "South Korea", "+82"),
3612
- makeFallback("BR", "Brazil", "+55"),
3613
- makeFallback("MX", "Mexico", "+52"),
3614
- makeFallback("CA", "Canada", "+1"),
3615
- makeFallback("AU", "Australia", "+61"),
3616
- makeFallback("NG", "Nigeria", "+234"),
3617
- makeFallback("PK", "Pakistan", "+92"),
3618
- makeFallback("ID", "Indonesia", "+62")
3619
- ];
3620
- function usePhoneValidation() {
3621
- const countries = (0, vue.ref)([]);
3622
- const isCountriesLoading = (0, vue.ref)(false);
3623
- const byValue = (0, vue.ref)(/* @__PURE__ */ new Map());
3624
- const byDialDigits = (0, vue.ref)(/* @__PURE__ */ new Map());
3625
- function rebuildIndexes(list) {
3626
- const valueMap = /* @__PURE__ */ new Map();
3627
- const dialMap = /* @__PURE__ */ new Map();
3628
- for (const item of list) {
3629
- valueMap.set(item.value, item);
3630
- const dial = item.raw_data.dial_digits;
3631
- if (dial) {
3632
- const bucket = dialMap.get(dial) ?? [];
3633
- bucket.push(item);
3634
- dialMap.set(dial, bucket);
3635
- }
3636
- }
3637
- byValue.value = valueMap;
3638
- byDialDigits.value = dialMap;
3639
- }
3640
- function upsertCountries(list) {
3641
- countries.value = list;
3642
- rebuildIndexes(list);
3643
- }
3644
- function normalizeRestCountries(list) {
3645
- const out = [];
3646
- for (const c of list) {
3647
- const name = c?.name?.common?.trim();
3648
- const iso2 = normalizeIso2(c?.cca2);
3649
- const dial = buildDialCode(c?.idd);
3650
- const flag = c?.flags?.png?.trim() || c?.flags?.svg?.trim() || null;
3651
- if (!name || !iso2 || !dial) continue;
3652
- const dialDigits = toDigits(dial);
3653
- const search_key = normalizeSearchKey(`${name} ${dial} ${iso2} ${dialDigits}`);
3654
- out.push({
3655
- label: `${name} (${dial})`,
3656
- value: iso2,
3657
- search_key,
3658
- raw_data: {
3659
- iso2,
3660
- dial_code: dial,
3661
- dial_digits: dialDigits,
3662
- name,
3663
- flag,
3664
- source: "restcountries",
3665
- original: c
3666
- }
3667
- });
3668
- }
3669
- const map = /* @__PURE__ */ new Map();
3670
- for (const item of out) {
3671
- const prev = map.get(item.value);
3672
- if (!prev) {
3673
- map.set(item.value, item);
3674
- continue;
3675
- }
3676
- const prevScore = (prev.raw_data.flag ? 1 : 0) + (prev.raw_data.dial_code ? 1 : 0);
3677
- if ((item.raw_data.flag ? 1 : 0) + (item.raw_data.dial_code ? 1 : 0) > prevScore) map.set(item.value, item);
3678
- }
3679
- return Array.from(map.values()).sort((a, b) => a.raw_data.name.localeCompare(b.raw_data.name));
3680
- }
3681
- async function getCountries(options) {
3682
- const force = Boolean(options?.force);
3683
- if (!force && countries.value.length) return countries.value;
3684
- if (!force && isBrowser$1()) try {
3685
- const cached = localStorage.getItem(STORAGE_KEY);
3686
- if (cached) {
3687
- const parsed = JSON.parse(cached);
3688
- if (Array.isArray(parsed) && parsed.length) {
3689
- upsertCountries(parsed);
3690
- return countries.value;
3691
- }
3692
- }
3693
- } catch {}
3694
- isCountriesLoading.value = true;
3695
- try {
3696
- const res = await fetch(REST_COUNTRIES_URL);
3697
- if (!res.ok) throw new Error(`Failed to fetch countries: ${res.status}`);
3698
- const normalized = normalizeRestCountries(await res.json());
3699
- upsertCountries(normalized.length ? normalized : FALLBACK_COUNTRIES);
3700
- if (isBrowser$1()) try {
3701
- localStorage.setItem(STORAGE_KEY, JSON.stringify(countries.value));
3702
- } catch {}
3703
- return countries.value;
3704
- } catch {
3705
- upsertCountries(FALLBACK_COUNTRIES);
3706
- return countries.value;
3707
- } finally {
3708
- isCountriesLoading.value = false;
3709
- }
3710
- }
3711
- function searchCountries(keyword, limit = 50) {
3712
- const q = normalizeSearchKey(keyword);
3713
- if (!q) return countries.value.slice(0, limit);
3714
- const res = [];
3715
- for (const item of countries.value) if (item.search_key.includes(q)) {
3716
- res.push(item);
3717
- if (res.length >= limit) break;
3718
- }
3719
- return res;
3720
- }
3721
- function getCountryByValue(value) {
3722
- return byValue.value.get(normalizeIso2(value)) ?? null;
3723
- }
3724
- function getCountriesByDial(dial) {
3725
- return byDialDigits.value.get(toDigits(dial)) ?? [];
3726
- }
3727
- function getRequiredInfo(country, locale) {
3728
- const iso2 = normalizeIso2(country.iso2);
3729
- if (!iso2) return null;
3730
- try {
3731
- const example = (0, libphonenumber_js.getExampleNumber)(iso2, EX);
3732
- const exampleNational = example?.formatNational?.() ?? "";
3733
- const exampleE164 = example?.format?.("E.164") ?? "";
3734
- const inferred = inferLengthFromExample(exampleNational);
3735
- const dial_code = country.dial_code ? ensurePlusDial(country.dial_code) : exampleE164 ? `+${example?.countryCallingCode}` : "";
3736
- const digitsExample = toDigits(exampleNational);
3737
- return {
3738
- iso2,
3739
- dial_code,
3740
- placeholder: "",
3741
- example_national: exampleNational,
3742
- example_e164: exampleE164,
3743
- national_number_length: inferred,
3744
- format_hint: digitsExample ? `e.g. ${localizeDigits(digitsExample, locale)}` : ""
3745
- };
3746
- } catch {
3747
- return null;
3748
- }
3749
- }
3750
- function validate(input) {
3751
- const country = input.country ?? null;
3752
- if (!country?.iso2) return {
3753
- ok: false,
3754
- reason: "missing_country",
3755
- country: null,
3756
- phone: {
3757
- raw: ("phone" in input ? input.phone : null) ?? null,
3758
- digits: ""
3759
- },
3760
- full_phone: null,
3761
- required: null
3762
- };
3763
- const iso2 = normalizeIso2(country.iso2);
3764
- const required = getRequiredInfo({
3765
- iso2,
3766
- dial_code: country.dial_code
3767
- }, input.locale);
3768
- if (!required) return {
3769
- ok: false,
3770
- reason: "country_not_supported",
3771
- country: {
3772
- iso2,
3773
- dial_code: ensurePlusDial(country.dial_code)
3774
- },
3775
- phone: {
3776
- raw: ("phone" in input ? input.phone : null) ?? null,
3777
- digits: ""
3778
- },
3779
- full_phone: null,
3780
- required: null
3781
- };
3782
- if (!("phone" in input)) return {
3783
- ok: true,
3784
- reason: null,
3785
- country: {
3786
- iso2: required.iso2,
3787
- dial_code: required.dial_code
3788
- },
3789
- phone: {
3790
- raw: null,
3791
- digits: ""
3792
- },
3793
- full_phone: null,
3794
- required
3795
- };
3796
- const raw = input.phone;
3797
- const digits = toDigits(raw);
3798
- if (!raw || !String(raw).trim()) return {
3799
- ok: true,
3800
- reason: null,
3801
- country: {
3802
- iso2: required.iso2,
3803
- dial_code: required.dial_code
3804
- },
3805
- phone: {
3806
- raw: raw ?? null,
3807
- digits: ""
3808
- },
3809
- full_phone: null,
3810
- required
3811
- };
3812
- if (String(raw).replace(/\s+/g, "").match(/[^\d+]/)) return {
3813
- ok: false,
3814
- reason: "phone_has_non_digits",
3815
- country: {
3816
- iso2: required.iso2,
3817
- dial_code: required.dial_code
3818
- },
3819
- phone: {
3820
- raw,
3821
- digits
3822
- },
3823
- full_phone: buildFullE164(required.dial_code, digits),
3824
- required
3825
- };
3826
- const nsn = dropLeadingZeros(digits);
3827
- const { min, max } = required.national_number_length;
3828
- if (min !== null && nsn.length < min) return {
3829
- ok: false,
3830
- reason: "too_short",
3831
- country: {
3832
- iso2: required.iso2,
3833
- dial_code: required.dial_code
3834
- },
3835
- phone: {
3836
- raw,
3837
- digits
3838
- },
3839
- full_phone: buildFullE164(required.dial_code, digits),
3840
- required,
3841
- details: {
3842
- min,
3843
- actual: nsn.length
3844
- }
3845
- };
3846
- if (max !== null && nsn.length > max) return {
3847
- ok: false,
3848
- reason: "too_long",
3849
- country: {
3850
- iso2: required.iso2,
3851
- dial_code: required.dial_code
3852
- },
3853
- phone: {
3854
- raw,
3855
- digits
3856
- },
3857
- full_phone: buildFullE164(required.dial_code, digits),
3858
- required,
3859
- details: {
3860
- max,
3861
- actual: nsn.length
3862
- }
3863
- };
3864
- const full = buildFullE164(required.dial_code, digits) ?? String(raw);
3865
- try {
3866
- if (!(0, libphonenumber_js.isValidPhoneNumber)(full, iso2)) {
3867
- const parsed = (0, libphonenumber_js.parsePhoneNumberFromString)(full, iso2);
3868
- return {
3869
- ok: false,
3870
- reason: "invalid_phone",
3871
- country: {
3872
- iso2: required.iso2,
3873
- dial_code: required.dial_code
3874
- },
3875
- phone: {
3876
- raw,
3877
- digits
3878
- },
3879
- full_phone: parsed?.number ?? null,
3880
- required,
3881
- details: {
3882
- type: parsed?.getType?.() ?? null,
3883
- possible: parsed?.isPossible?.() ?? null,
3884
- country: parsed?.country ?? null
3885
- }
3886
- };
3887
- }
3888
- const parsed = (0, libphonenumber_js.parsePhoneNumberFromString)(full, iso2);
3889
- return {
3890
- ok: true,
3891
- reason: null,
3892
- country: {
3893
- iso2: required.iso2,
3894
- dial_code: required.dial_code
3895
- },
3896
- phone: {
3897
- raw,
3898
- digits
3899
- },
3900
- full_phone: parsed?.number ?? full,
3901
- required
3902
- };
3903
- } catch (e) {
3904
- return {
3905
- ok: false,
3906
- reason: "parse_failed",
3907
- country: {
3908
- iso2: required.iso2,
3909
- dial_code: required.dial_code
3910
- },
3911
- phone: {
3912
- raw,
3913
- digits
3914
- },
3915
- full_phone: buildFullE164(required.dial_code, digits),
3916
- required,
3917
- details: { error: e?.message ?? String(e) }
3918
- };
3919
- }
3920
- }
3921
- return {
3922
- countries,
3923
- isCountriesLoading,
3924
- getCountries,
3925
- searchCountries,
3926
- getCountryByValue,
3927
- getCountriesByDial,
3928
- getRequiredInfo,
3929
- validate
3930
- };
3931
- }
3932
- //#endregion
3933
3408
  //#region src/composables/useCountryDetection.ts
3934
3409
  /**
3935
3410
  * Best-effort country detection chain: IP geolocation → timezone → navigator.language → fallback.
@@ -4039,24 +3514,32 @@ function tryLocale() {
4039
3514
  return null;
4040
3515
  }
4041
3516
  }
3517
+ const inflightIpFetch = /* @__PURE__ */ new Map();
4042
3518
  async function tryIp(endpoint, timeoutMs) {
4043
3519
  if (!isBrowser() || typeof fetch !== "function") return null;
3520
+ const existing = inflightIpFetch.get(endpoint);
3521
+ if (existing) return existing;
4044
3522
  const controller = new AbortController();
4045
3523
  const timer = setTimeout(() => controller.abort(), timeoutMs);
4046
- try {
4047
- const res = await fetch(endpoint, {
4048
- signal: controller.signal,
4049
- credentials: "omit"
4050
- });
4051
- if (!res.ok) return null;
4052
- const data = await res.json();
4053
- const code = (data.country_code ?? data.country ?? "").toString().toUpperCase();
4054
- return /^[A-Z]{2}$/.test(code) ? code : null;
4055
- } catch {
4056
- return null;
4057
- } finally {
4058
- clearTimeout(timer);
4059
- }
3524
+ const promise = (async () => {
3525
+ try {
3526
+ const res = await fetch(endpoint, {
3527
+ signal: controller.signal,
3528
+ credentials: "omit"
3529
+ });
3530
+ if (!res.ok) return null;
3531
+ const data = await res.json();
3532
+ const code = (data.country_code ?? data.country ?? "").toString().toUpperCase();
3533
+ return /^[A-Z]{2}$/.test(code) ? code : null;
3534
+ } catch {
3535
+ return null;
3536
+ } finally {
3537
+ clearTimeout(timer);
3538
+ inflightIpFetch.delete(endpoint);
3539
+ }
3540
+ })();
3541
+ inflightIpFetch.set(endpoint, promise);
3542
+ return promise;
4060
3543
  }
4061
3544
  function readCache() {
4062
3545
  if (!isBrowser()) return null;
@@ -4122,6 +3605,11 @@ function useCountryDetection(opts = {}) {
4122
3605
  }
4123
3606
  //#endregion
4124
3607
  //#region src/composables/useCountryMatching.ts
3608
+ /** Cached snapshot of every country libphonenumber knows about (~250 ISO2 codes).
3609
+ * Used by tier 2 of `matchLeadingDialCode` as the last-resort iteration so detection
3610
+ * works for *every* country, not just the popular ones in the bundled fallback list.
3611
+ * Cached at module load — `getCountries()` is a static metadata table, no I/O. */
3612
+ const ALL_LIBPHONENUMBER_ISO2 = (0, libphonenumber_js.getCountries)();
4125
3613
  /** Synchronous dial-digit → ISO2 fallback for common countries, used when the async
4126
3614
  * REST Countries fetch hasn't populated `getCountriesByDial`'s index yet at setup. */
4127
3615
  const DIAL_TO_ISO2_FALLBACK = {
@@ -4188,6 +3676,58 @@ const DIAL_TO_ISO2_FALLBACK = {
4188
3676
  /** localStorage key for the user's most recently picked countries. Used as a
4189
3677
  * tie-breaker when multiple countries share a dial code (e.g. all NANP). */
4190
3678
  const COUNTRY_RECENTS_KEY = "ali_ui_country_recents_v1";
3679
+ /** ISO2 codes iterated by tier 2 of `matchLeadingDialCode` when looking for a country
3680
+ * that accepts a local-format input as valid. Mirrors the `FALLBACK_COUNTRIES` list in
3681
+ * {@link usePhoneValidation} (kept in sync by tests + by being short and obvious).
3682
+ * Order matters — earlier entries get priority when multiple countries would each
3683
+ * validate the same input. Built around the most-populated / most-likely countries. */
3684
+ const FALLBACK_ISO2_LIST = [
3685
+ "SA",
3686
+ "EG",
3687
+ "AE",
3688
+ "US",
3689
+ "GB",
3690
+ "DE",
3691
+ "FR",
3692
+ "ES",
3693
+ "IT",
3694
+ "TR",
3695
+ "RU",
3696
+ "CN",
3697
+ "IN",
3698
+ "JP",
3699
+ "KR",
3700
+ "BR",
3701
+ "MX",
3702
+ "CA",
3703
+ "AU",
3704
+ "NG",
3705
+ "PK",
3706
+ "ID"
3707
+ ];
3708
+ /** Build a minimal `CountryOption` from libphonenumber metadata when the async REST
3709
+ * Countries list hasn't loaded the entry yet. Used so country **detection** works
3710
+ * generically for any libphonenumber country, not just the ~22 in the offline
3711
+ * fallback list. The picker will overwrite this synthetic record with the real one
3712
+ * (with localized name + flag) as soon as `getCountries()` resolves. */
3713
+ function buildSyntheticCountry(iso2, dialDigits) {
3714
+ const ISO2 = iso2.toUpperCase();
3715
+ const digits = String(dialDigits).replace(/\D/g, "");
3716
+ return {
3717
+ label: `${ISO2} (+${digits})`,
3718
+ value: ISO2,
3719
+ search_key: `${ISO2.toLowerCase()} +${digits} ${digits}`,
3720
+ raw_data: {
3721
+ iso2: ISO2,
3722
+ dial_code: `+${digits}`,
3723
+ dial_digits: digits,
3724
+ name: ISO2,
3725
+ flag: `https://flagcdn.com/w40/${ISO2.toLowerCase()}.png`,
3726
+ source: "fallback",
3727
+ original: {}
3728
+ }
3729
+ };
3730
+ }
4191
3731
  function readRecents() {
4192
3732
  if (typeof window === "undefined") return [];
4193
3733
  try {
@@ -4233,38 +3773,75 @@ function useCountryMatching(deps) {
4233
3773
  const n = Number(digits);
4234
3774
  return Number.isFinite(n) ? n : null;
4235
3775
  }
3776
+ const MATCHER_CACHE_MAX = 128;
3777
+ const matcherCache = /* @__PURE__ */ new Map();
3778
+ function readMatcherCache(key) {
3779
+ if (!matcherCache.has(key)) return void 0;
3780
+ const value = matcherCache.get(key);
3781
+ matcherCache.delete(key);
3782
+ matcherCache.set(key, value);
3783
+ return value;
3784
+ }
3785
+ function writeMatcherCache(key, value) {
3786
+ if (matcherCache.size >= MATCHER_CACHE_MAX) {
3787
+ const oldest = matcherCache.keys().next().value;
3788
+ if (oldest !== void 0) matcherCache.delete(oldest);
3789
+ }
3790
+ matcherCache.set(key, value);
3791
+ }
4236
3792
  /** Three-tier match of the leading digits to a country:
4237
3793
  * 1. libphonenumber international parse (handles NANP disambiguation).
4238
- * 2. libphonenumber national-format parse using `hintCountry` (handles local
4239
- * formats like Egyptian `01066105963` with no dial-code prefix).
3794
+ * 2. libphonenumber national-format parse, iterating through candidate hint
3795
+ * countries (handles local formats like Egyptian `01066105963` with no
3796
+ * dial-code prefix). Universal coverage via `getCountries()`.
4240
3797
  * 3. Longest-prefix match against the dial-digits index, with the current
4241
- * selection / recents as tie-breakers when multiple countries share a code. */
3798
+ * selection / recents as tie-breakers when multiple countries share a code.
3799
+ *
3800
+ * Results are LRU-cached per input + context to avoid re-paying tier-2 iteration
3801
+ * cost when the user backspaces and retypes the same prefix. */
4242
3802
  function matchLeadingDialCode(digits, options = {}) {
4243
3803
  if (!digits) return null;
4244
3804
  const { hintCountry, currentIso2 } = options;
3805
+ const cacheKey = `${digits}|${hintCountry ?? ""}|${currentIso2 ?? ""}`;
3806
+ const cached = readMatcherCache(cacheKey);
3807
+ if (cached !== void 0) return cached;
3808
+ const result = runMatch(digits, hintCountry, currentIso2);
3809
+ writeMatcherCache(cacheKey, result);
3810
+ return result;
3811
+ }
3812
+ function runMatch(digits, hintCountry, currentIso2) {
4245
3813
  try {
4246
3814
  const parsed = (0, libphonenumber_js.parsePhoneNumberFromString)(`+${digits}`);
4247
- if (parsed?.country && parsed.countryCallingCode) {
4248
- const parsedCountry = getCountryByValue(parsed.country);
4249
- if (parsedCountry) return {
4250
- country: parsedCountry,
4251
- nationalNumber: String(parsed.nationalNumber ?? "")
4252
- };
4253
- }
4254
- } catch {}
4255
- if (hintCountry && digits.length >= 4) try {
4256
- const parsed = (0, libphonenumber_js.parsePhoneNumberFromString)(digits, hintCountry);
4257
- if (parsed?.isValid()) {
4258
- const matched = getCountryByValue(parsed.country || hintCountry);
4259
- if (matched) return {
4260
- country: matched,
4261
- nationalNumber: String(parsed.nationalNumber ?? "")
4262
- };
4263
- }
3815
+ if (parsed?.country && parsed.countryCallingCode) return {
3816
+ country: getCountryByValue(parsed.country) ?? buildSyntheticCountry(parsed.country, String(parsed.countryCallingCode)),
3817
+ nationalNumber: String(parsed.nationalNumber ?? "")
3818
+ };
4264
3819
  } catch {}
3820
+ if (digits.length >= 4) {
3821
+ const candidates = /* @__PURE__ */ new Set();
3822
+ if (hintCountry) candidates.add(hintCountry.toUpperCase());
3823
+ if (currentIso2) candidates.add(currentIso2.toUpperCase());
3824
+ for (const recent of readRecents()) candidates.add(recent.toUpperCase());
3825
+ for (const fallback of FALLBACK_ISO2_LIST) candidates.add(fallback);
3826
+ for (const all of ALL_LIBPHONENUMBER_ISO2) candidates.add(all);
3827
+ for (const iso2 of candidates) try {
3828
+ const parsed = (0, libphonenumber_js.parsePhoneNumberFromString)(digits, iso2);
3829
+ if (parsed?.isValid()) {
3830
+ const resolvedIso2 = parsed.country || iso2;
3831
+ return {
3832
+ country: getCountryByValue(resolvedIso2) ?? buildSyntheticCountry(resolvedIso2, String(parsed.countryCallingCode ?? "")),
3833
+ nationalNumber: String(parsed.nationalNumber ?? "")
3834
+ };
3835
+ }
3836
+ } catch {}
3837
+ }
4265
3838
  for (let len = Math.min(3, digits.length); len >= 1; len--) {
4266
3839
  const prefix = digits.slice(0, len);
4267
- const group = getCountriesByDial(prefix);
3840
+ let group = getCountriesByDial(prefix);
3841
+ if (!group.length) {
3842
+ const iso2 = DIAL_TO_ISO2_FALLBACK[prefix];
3843
+ if (iso2) group = [getCountryByValue(iso2) ?? buildSyntheticCountry(iso2, prefix)];
3844
+ }
4268
3845
  if (!group.length) continue;
4269
3846
  const nationalNumber = digits.slice(prefix.length);
4270
3847
  if (group.length === 1) return {
@@ -4336,18 +3913,36 @@ function useTelInputValidation(deps, inputs, config) {
4336
3913
  phone: inputs.phone.value ?? "",
4337
3914
  locale: config.locale()
4338
3915
  }));
3916
+ const externalErrorActive = (0, vue.computed)(() => {
3917
+ const e = config.externalError();
3918
+ return typeof e === "string" && e.length > 0;
3919
+ });
4339
3920
  const validationState = (0, vue.computed)(() => {
3921
+ if (externalErrorActive.value) return "error";
4340
3922
  if (!inputs.phone.value) return "idle";
4341
3923
  return validation.value.ok ? "valid" : "error";
4342
3924
  });
4343
- const visibleValidationState = (0, vue.computed)(() => inputs.hasFinishedTyping.value ? validationState.value : "idle");
3925
+ const visibleValidationState = (0, vue.computed)(() => {
3926
+ if (externalErrorActive.value) return "error";
3927
+ const mode = config.validateOn() ?? "change";
3928
+ if (mode === "eager") return validationState.value;
3929
+ if (mode === "blur" && !inputs.hasBlurred.value) return "idle";
3930
+ return inputs.hasFinishedTyping.value ? validationState.value : "idle";
3931
+ });
4344
3932
  const errorMessage = (0, vue.computed)(() => {
3933
+ const ext = config.externalError();
3934
+ if (typeof ext === "string" && ext.length > 0) return ext;
4345
3935
  const v = validation.value;
4346
3936
  if (v.ok || !v.reason) return null;
4347
3937
  if (!inputs.phone.value) return null;
4348
3938
  return config.errorMessages()?.[v.reason] ?? inputs.messages.value.errorMessages[v.reason];
4349
3939
  });
4350
- const showError = (0, vue.computed)(() => Boolean(config.showValidation() && inputs.hasFinishedTyping.value && errorMessage.value));
3940
+ const showError = (0, vue.computed)(() => {
3941
+ if (!errorMessage.value) return false;
3942
+ if (externalErrorActive.value) return true;
3943
+ if (!config.showValidation()) return false;
3944
+ return visibleValidationState.value === "error";
3945
+ });
4351
3946
  return {
4352
3947
  validation,
4353
3948
  required,
@@ -4363,54 +3958,88 @@ function useTelInputValidation(deps, inputs, config) {
4363
3958
  };
4364
3959
  }
4365
3960
  //#endregion
4366
- //#region src/types.ts
4367
- const aTelInputVariants = (0, class_variance_authority.cva)("a-tel-input__field", {
4368
- variants: { size: {
4369
- xs: "",
4370
- sm: "",
4371
- md: "",
4372
- lg: "",
4373
- xl: ""
4374
- } },
4375
- defaultVariants: { size: "md" }
4376
- });
4377
- const DEFAULT_ERROR_MESSAGES = {
4378
- missing_country: "Please select a country.",
4379
- country_not_supported: "This country is not supported.",
4380
- phone_has_non_digits: "Phone number can only contain digits.",
4381
- too_short: "Phone number is too short.",
4382
- too_long: "Phone number is too long.",
4383
- invalid_phone: "Phone number is invalid.",
4384
- parse_failed: "Could not parse phone number."
4385
- };
4386
- /** English defaults for every {@link TelInputMessages} key. */
4387
- const DEFAULT_MESSAGES = {
4388
- searchPlaceholder: "Search country or +code…",
4389
- emptyText: "No countries found.",
4390
- loadingText: "Loading countries…",
4391
- suggestedLabel: "Suggested",
4392
- allCountriesLabel: "All countries",
4393
- errorMessages: DEFAULT_ERROR_MESSAGES,
4394
- countryLabel: "Country",
4395
- selectCountryLabel: "Select country",
4396
- phoneInputLabel: "Phone number"
4397
- };
3961
+ //#region src/composables/useCountrySelection.ts
4398
3962
  /**
4399
- * Merge a partial `messages` override onto the English defaults. Used internally by
4400
- * `ATelInput` to resolve a complete {@link TelInputMessages} object.
3963
+ * The picker selection state machine for {@link ATelInput}, consolidated into a
3964
+ * single composable so the component doesn't have to juggle three boolean flags
3965
+ * (`userPickedCountry` / `autoSettingCountry` / `inputDetectionApplied`) and
3966
+ * reason about their pairwise interactions.
3967
+ *
3968
+ * Every write to the selection goes through {@link UseCountrySelectionReturn.set},
3969
+ * which records both the new ISO2 and the *origin* of the change. That makes the
3970
+ * downstream decision — should detection re-route the picker on the next typed-input
3971
+ * burst? — a one-liner: `if (detectionLocked.value) return;`.
4401
3972
  */
4402
- function resolveMessages(input) {
4403
- if (!input) return DEFAULT_MESSAGES;
3973
+ function useCountrySelection() {
3974
+ const iso2 = (0, vue.ref)("");
3975
+ const source = (0, vue.ref)("none");
3976
+ function set(nextIso2, nextSource) {
3977
+ iso2.value = nextIso2;
3978
+ source.value = nextSource;
3979
+ }
3980
+ function clear() {
3981
+ iso2.value = "";
3982
+ source.value = "none";
3983
+ }
4404
3984
  return {
4405
- ...DEFAULT_MESSAGES,
4406
- ...input,
4407
- errorMessages: {
4408
- ...DEFAULT_ERROR_MESSAGES,
4409
- ...input.errorMessages
4410
- }
3985
+ iso2,
3986
+ source,
3987
+ set,
3988
+ clear,
3989
+ detectionLocked: (0, vue.computed)(() => source.value === "picker" || source.value === "input")
4411
3990
  };
4412
3991
  }
4413
3992
  //#endregion
3993
+ //#region src/composables/useSyncedModel.ts
3994
+ /**
3995
+ * Two-way bidirectional sync between a `defineModel` ref and internal component
3996
+ * state — with the **echo-loop guard** built in. Solves a recurring class of
3997
+ * bugs in this component where two watchers (external→internal and
3998
+ * internal→external) would fight each other and rewrite values the user just
3999
+ * typed.
4000
+ *
4001
+ * Mechanics:
4002
+ *
4003
+ * 1. When any of `triggers` change AND we're not currently applying an
4004
+ * external write, recompute the model value via `compose()` and write it
4005
+ * into `model`. Stamp `lastEmitted` first so we recognise the echo.
4006
+ * 2. When `model` changes AND the new value isn't the echo of our last emit,
4007
+ * apply it into internal state via `apply()`. The `applying` flag is held
4008
+ * for the duration of `apply()` so step (1) skips while we mutate.
4009
+ *
4010
+ * Used for:
4011
+ * - `modelValue` (E.164 string) ↔ `phone` + `selectedIso2`.
4012
+ * - `country` (dial-number) ↔ `selectedIso2`.
4013
+ *
4014
+ * The hand-rolled equivalents (`applyingModelValue` / `lastEmittedModelValue`
4015
+ * plus the country↔iso2 watcher pair with `autoSettingCountry`) collapse into
4016
+ * two calls to this helper.
4017
+ */
4018
+ function useSyncedModel(options) {
4019
+ const { model, triggers, compose, apply } = options;
4020
+ const isEqual = options.isEqual ?? Object.is;
4021
+ let applying = false;
4022
+ let lastEmitted = { __unset: true };
4023
+ const isEcho = (v) => typeof lastEmitted === "object" && lastEmitted !== null && "__unset" in lastEmitted ? false : isEqual(v, lastEmitted);
4024
+ (0, vue.watch)(model, (next) => {
4025
+ if (isEcho(next)) return;
4026
+ applying = true;
4027
+ try {
4028
+ apply(next);
4029
+ } finally {
4030
+ applying = false;
4031
+ }
4032
+ }, { immediate: true });
4033
+ (0, vue.watch)(triggers, () => {
4034
+ if (applying) return;
4035
+ const next = compose();
4036
+ if (!isEqual(next, model.value)) {
4037
+ lastEmitted = next;
4038
+ model.value = next;
4039
+ }
4040
+ }, { flush: "post" });
4041
+ }
4042
+ //#endregion
4414
4043
  //#region ../AResponsivePopover/dist/index.js
4415
4044
  var __defProp = Object.defineProperty;
4416
4045
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -16975,11 +16604,6 @@ const _sfc_main$2$2 = /* @__PURE__ */ (0, vue.defineComponent)({
16975
16604
  const open = (0, vue.useModel)(__props, "open");
16976
16605
  const isDesktop = (0, _vueuse_core.useMediaQuery)(() => props.breakpoint);
16977
16606
  /**
16978
- * Pre-imported on both branches — do NOT lazy-load. Switching the component identity at runtime
16979
- * means we still hydrate the right tree client-side.
16980
- */
16981
- const Root = (0, vue.computed)(() => isDesktop.value ? APopover_default : ADrawer_default);
16982
- /**
16983
16607
  * Per-branch `modal` resolution — the two roots interpret the prop differently:
16984
16608
  *
16985
16609
  * APopover (desktop, reka-ui): `modal=true` triggers `PopoverContentModal` + its
@@ -16996,7 +16620,7 @@ const _sfc_main$2$2 = /* @__PURE__ */ (0, vue.defineComponent)({
16996
16620
  return props.scrollLock === "body";
16997
16621
  });
16998
16622
  const drawerModal = (0, vue.computed)(() => props.modal !== false);
16999
- const rootModal = (0, vue.computed)(() => isDesktop.value ? rekaModal.value : drawerModal.value);
16623
+ const drawerNoBodyStyles = (0, vue.computed)(() => props.scrollLock !== "body");
17000
16624
  provideResponsivePopoverContext({
17001
16625
  open: (0, vue.computed)(() => open.value ?? false),
17002
16626
  isDesktop: (0, vue.computed)(() => isDesktop.value),
@@ -17006,10 +16630,15 @@ const _sfc_main$2$2 = /* @__PURE__ */ (0, vue.defineComponent)({
17006
16630
  props,
17007
16631
  open,
17008
16632
  isDesktop,
17009
- Root,
17010
16633
  rekaModal,
17011
16634
  drawerModal,
17012
- rootModal
16635
+ drawerNoBodyStyles,
16636
+ get APopover() {
16637
+ return APopover_default;
16638
+ },
16639
+ get ADrawer() {
16640
+ return ADrawer_default;
16641
+ }
17013
16642
  };
17014
16643
  Object.defineProperty(__returned__, "__isScriptSetup", {
17015
16644
  enumerable: false,
@@ -17019,15 +16648,30 @@ const _sfc_main$2$2 = /* @__PURE__ */ (0, vue.defineComponent)({
17019
16648
  }
17020
16649
  });
17021
16650
  function _sfc_render$2$2(_ctx, _cache, $props, $setup, $data, $options) {
17022
- return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)($setup.Root), {
16651
+ return $setup.isDesktop ? ((0, vue.openBlock)(), (0, vue.createBlock)($setup["APopover"], {
16652
+ key: 0,
17023
16653
  open: $setup.open,
17024
16654
  "onUpdate:open": _cache[0] || (_cache[0] = ($event) => $setup.open = $event),
17025
- modal: $setup.rootModal,
16655
+ modal: $setup.rekaModal,
16656
+ "data-slot": "responsive-popover"
16657
+ }, {
16658
+ default: (0, vue.withCtx)(() => [(0, vue.renderSlot)(_ctx.$slots, "default", { isDesktop: true })]),
16659
+ _: 3
16660
+ }, 8, ["open", "modal"])) : ((0, vue.openBlock)(), (0, vue.createBlock)($setup["ADrawer"], {
16661
+ key: 1,
16662
+ open: $setup.open,
16663
+ "onUpdate:open": _cache[1] || (_cache[1] = ($event) => $setup.open = $event),
16664
+ modal: $setup.drawerModal,
16665
+ "no-body-styles": $setup.drawerNoBodyStyles,
17026
16666
  "data-slot": "responsive-popover"
17027
16667
  }, {
17028
- default: (0, vue.withCtx)(() => [(0, vue.renderSlot)(_ctx.$slots, "default", { isDesktop: $setup.isDesktop })]),
16668
+ default: (0, vue.withCtx)(() => [(0, vue.renderSlot)(_ctx.$slots, "default", { isDesktop: false })]),
17029
16669
  _: 3
17030
- }, 40, ["open", "modal"]);
16670
+ }, 8, [
16671
+ "open",
16672
+ "modal",
16673
+ "no-body-styles"
16674
+ ]));
17031
16675
  }
17032
16676
  var AResponsivePopover_default = /* @__PURE__ */ export_helper_default$3(_sfc_main$2$2, [["render", _sfc_render$2$2], ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/AResponsivePopover/src/components/AResponsivePopover.vue"]]);
17033
16677
  const _sfc_main$1$2 = /* @__PURE__ */ (0, vue.defineComponent)({
@@ -17140,7 +16784,7 @@ const _sfc_main$9 = /* @__PURE__ */ (0, vue.defineComponent)({
17140
16784
  if (typeof document === "undefined") return [];
17141
16785
  return Array.from(document.querySelectorAll("[data-responsive-popover-scroll-container=\"true\"]"));
17142
16786
  },
17143
- active: (0, vue.computed)(() => !!ctx?.open.value && isDesktop.value && scrollLockMode.value === "events")
16787
+ active: (0, vue.computed)(() => !!ctx?.open.value && scrollLockMode.value === "events")
17144
16788
  });
17145
16789
  const __returned__ = {
17146
16790
  props,
@@ -17186,7 +16830,8 @@ function _sfc_render$9(_ctx, _cache, $props, $setup, $data, $options) {
17186
16830
  ])) : ((0, vue.openBlock)(), (0, vue.createBlock)($setup["ADrawerContent"], {
17187
16831
  key: 1,
17188
16832
  class: (0, vue.normalizeClass)($setup.mergedClass),
17189
- "data-slot": "responsive-popover-content"
16833
+ "data-slot": "responsive-popover-content",
16834
+ "data-responsive-popover-scroll-container": "true"
17190
16835
  }, {
17191
16836
  default: (0, vue.withCtx)(() => [(0, vue.renderSlot)(_ctx.$slots, "default")]),
17192
16837
  _: 3
@@ -17597,11 +17242,11 @@ const _sfc_main$1 = /* @__PURE__ */ (0, vue.defineComponent)({
17597
17242
  setup(__props, { expose: __expose }) {
17598
17243
  const props = __props;
17599
17244
  const selected = (0, vue.useModel)(__props, "selected");
17600
- const { countries: internalCountries, isCountriesLoading, getCountries, searchCountries: defaultSearch, getCountryByValue: lookupInternal } = usePhoneValidation();
17245
+ const { countries: internalCountries, isCountriesLoading, getCountries, searchCountries: defaultSearch, getCountryByValue: lookupInternal } = require_usePhoneValidation.usePhoneValidation();
17601
17246
  const open = (0, vue.ref)(false);
17602
17247
  const search = (0, vue.ref)("");
17603
17248
  getCountries();
17604
- const effectiveCountries = (0, vue.computed)(() => props.countries && props.countries.length ? props.countries : localizeCountries(internalCountries.value, props.locale));
17249
+ const effectiveCountries = (0, vue.computed)(() => props.countries && props.countries.length ? props.countries : require_usePhoneValidation.localizeCountries(internalCountries.value, props.locale));
17605
17250
  const effectiveByValue = (0, vue.computed)(() => new Map(effectiveCountries.value.map((c) => [c.value, c])));
17606
17251
  function lookup(iso2) {
17607
17252
  if (!iso2) return null;
@@ -17845,7 +17490,7 @@ const _hoisted_6$1 = {
17845
17490
  };
17846
17491
  const _hoisted_7$1 = { class: "a-country-select__list" };
17847
17492
  const _hoisted_8$1 = { class: "a-country-select__loading" };
17848
- const _hoisted_9 = { class: "a-country-select__empty" };
17493
+ const _hoisted_9$1 = { class: "a-country-select__empty" };
17849
17494
  const _hoisted_10 = {
17850
17495
  key: 0,
17851
17496
  "data-slot": "country-select-group",
@@ -17943,7 +17588,7 @@ function _sfc_render$1(_ctx, _cache, $props, $setup, $data, $options) {
17943
17588
  (0, vue.createElementVNode)("div", _hoisted_7$1, [$setup.isCountriesLoading && $setup.effectiveCountries.length === 0 ? (0, vue.renderSlot)(_ctx.$slots, "loading", { key: 0 }, () => [(0, vue.createElementVNode)("div", _hoisted_8$1, (0, vue.toDisplayString)($setup.props.loadingText), 1)], true) : $setup.isSearching && $setup.filtered.length === 0 ? (0, vue.renderSlot)(_ctx.$slots, "empty", {
17944
17589
  key: 1,
17945
17590
  query: $setup.search
17946
- }, () => [(0, vue.createElementVNode)("div", _hoisted_9, (0, vue.toDisplayString)($setup.props.emptyText), 1)], true) : ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 2 }, [
17591
+ }, () => [(0, vue.createElementVNode)("div", _hoisted_9$1, (0, vue.toDisplayString)($setup.props.emptyText), 1)], true) : ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 2 }, [
17947
17592
  (0, vue.createCommentVNode)(" Suggested group "),
17948
17593
  $setup.suggested.length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("section", _hoisted_10, [(0, vue.renderSlot)(_ctx.$slots, "group-header", {
17949
17594
  label: $setup.props.suggestedLabel,
@@ -18072,6 +17717,27 @@ const _sfc_main = /* @__PURE__ */ (0, vue.defineComponent)({
18072
17717
  required: false,
18073
17718
  skipCheck: true
18074
17719
  },
17720
+ modelValue: {
17721
+ type: String,
17722
+ required: false
17723
+ },
17724
+ name: {
17725
+ type: String,
17726
+ required: false
17727
+ },
17728
+ error: {
17729
+ type: [String, null],
17730
+ required: false
17731
+ },
17732
+ validating: {
17733
+ type: Boolean,
17734
+ required: false
17735
+ },
17736
+ validateOn: {
17737
+ type: String,
17738
+ required: false,
17739
+ default: "change"
17740
+ },
18075
17741
  placeholder: {
18076
17742
  type: String,
18077
17743
  required: false,
@@ -18214,28 +17880,65 @@ const _sfc_main = /* @__PURE__ */ (0, vue.defineComponent)({
18214
17880
  type: [Number, null],
18215
17881
  default: null
18216
17882
  },
18217
- "countryModifiers": {}
17883
+ "countryModifiers": {},
17884
+ "modelValue": {
17885
+ type: String,
17886
+ default: ""
17887
+ },
17888
+ "modelModifiers": {}
18218
17889
  }),
18219
- emits: ["update:phone", "update:country"],
18220
- setup(__props, { expose: __expose }) {
17890
+ emits: /* @__PURE__ */ (0, vue.mergeModels)([
17891
+ "update:modelValue",
17892
+ "update:phone",
17893
+ "update:country",
17894
+ "blur",
17895
+ "focus"
17896
+ ], [
17897
+ "update:phone",
17898
+ "update:country",
17899
+ "update:modelValue"
17900
+ ]),
17901
+ setup(__props, { expose: __expose, emit: __emit }) {
18221
17902
  const props = __props;
17903
+ const emit = __emit;
18222
17904
  const phone = (0, vue.useModel)(__props, "phone");
18223
17905
  /** Public `v-model:country` — the **dial number** (e.g. `20` for Egypt, `44` for the UK,
18224
17906
  * `1` for the NANP block). `null` means no country selected. Internally the component
18225
17907
  * tracks a richer ISO2 code (`selectedIso2`) because dial codes alone can't disambiguate
18226
17908
  * NANP (`+1` covers 25+ countries) — the picker still needs an exact country. */
18227
17909
  const country = (0, vue.useModel)(__props, "country");
18228
- /** Internal source of truth — the ISO2 alpha-2 code of the picker selection. Synced with
18229
- * `country` (dial number) via watchers below. */
18230
- const selectedIso2 = (0, vue.ref)("");
18231
- const { getCountries, validate, getRequiredInfo, getCountryByValue, getCountriesByDial } = usePhoneValidation();
17910
+ /**
17911
+ * Default v-model the canonical **E.164** string (e.g. `'+201066105963'`).
17912
+ *
17913
+ * Single-string contract for VeeValidate's `<Field v-slot="{ field }">` pattern
17914
+ * (`v-bind="field"`), native `<form>` submission, or any `v-model="phoneE164"`
17915
+ * consumer. Bind it with:
17916
+ *
17917
+ * <ATelInput v-model="phoneE164" />
17918
+ *
17919
+ * <VeeField v-slot="{ field, errors }" name="phone">
17920
+ * <ATelInput v-bind="field" :error="errors[0]" />
17921
+ * </VeeField>
17922
+ *
17923
+ * When set externally, the value is parsed via libphonenumber-js → the country
17924
+ * picker and the digits-only `phone` model are derived from it. When the user
17925
+ * types or picks a country, the composed E.164 is written back out. Stays in
17926
+ * sync with `v-model:phone` / `v-model:country` — you can use either contract.
17927
+ */
17928
+ const modelValue = (0, vue.useModel)(__props, "modelValue");
17929
+ /** The picker selection state machine — `iso2` is the internal source of truth, `source`
17930
+ * records where the current selection came from, `detectionLocked` answers "should
17931
+ * typed-input detection re-route the picker on the next burst?". Single mutator: `set`.
17932
+ * Replaces the historical flag soup (`userPickedCountry` / `autoSettingCountry` /
17933
+ * `inputDetectionApplied`). */
17934
+ const selection = useCountrySelection();
17935
+ const selectedIso2 = selection.iso2;
17936
+ const { getCountries, validate, getRequiredInfo, getCountryByValue, getCountriesByDial } = require_usePhoneValidation.usePhoneValidation();
18232
17937
  const { resolveCountryIdentifier, dialNumberFor, matchLeadingDialCode } = useCountryMatching({
18233
17938
  getCountryByValue,
18234
17939
  getCountriesByDial
18235
17940
  });
18236
17941
  getCountries();
18237
- const userPickedCountry = (0, vue.ref)(false);
18238
- const autoSettingCountry = (0, vue.ref)(false);
18239
17942
  /** Silently resolved via IP/timezone/locale when `detectFromInput` is on — used as a hint
18240
17943
  * so local-format numbers (e.g. Egyptian `01066105963`) can be parsed without a `+` prefix.
18241
17944
  * Seeded from `defaultCountry` so it has a usable value before async detection resolves. */
@@ -18249,18 +17952,28 @@ const _sfc_main = /* @__PURE__ */ (0, vue.defineComponent)({
18249
17952
  currentIso2: selectedIso2.value
18250
17953
  });
18251
17954
  }
17955
+ /** User explicitly picked a country from the picker — locks the selection so subsequent
17956
+ * typed-input detection cannot churn the picker. */
17957
+ function onPickerPick(iso2) {
17958
+ selection.set(iso2, "picker");
17959
+ }
18252
17960
  const typing = useTypingPhase({
18253
17961
  debounceMs: (0, vue.computed)(() => Math.max(0, props.detectDebounceMs)),
18254
17962
  onSettle: () => {
18255
17963
  if (!props.detectFromInput) return;
18256
- if (userPickedCountry.value || selectedIso2.value) return;
17964
+ if (selection.detectionLocked.value) return;
18257
17965
  const current = phone.value;
18258
17966
  if (!current) return;
17967
+ const typedInternational = (displayValue.value ?? "").trimStart().startsWith("+");
17968
+ if (selectedIso2.value && !typedInternational) return;
18259
17969
  typing.markDetectionAttempt();
18260
17970
  const match = tryMatchPhone(current);
18261
17971
  if (!match) return;
18262
- autoSettingCountry.value = true;
18263
- selectedIso2.value = match.country.value;
17972
+ if (match.country.value === selectedIso2.value && match.nationalNumber === phone.value) {
17973
+ selection.source.value = "input";
17974
+ return;
17975
+ }
17976
+ selection.set(match.country.value, "input");
18264
17977
  phone.value = match.nationalNumber;
18265
17978
  }
18266
17979
  });
@@ -18271,8 +17984,7 @@ const _sfc_main = /* @__PURE__ */ (0, vue.defineComponent)({
18271
17984
  const seed = resolveCountryIdentifier(props.defaultCountry);
18272
17985
  if (seed) {
18273
17986
  inferredCountry.value = seed;
18274
- autoSettingCountry.value = true;
18275
- selectedIso2.value = seed;
17987
+ selection.set(seed, "default");
18276
17988
  return;
18277
17989
  }
18278
17990
  }
@@ -18291,43 +18003,31 @@ const _sfc_main = /* @__PURE__ */ (0, vue.defineComponent)({
18291
18003
  const iso2 = detected ? detected.toUpperCase() : "";
18292
18004
  if (props.detectFromInput) {
18293
18005
  inferredCountry.value = iso2;
18294
- if (phone.value && !userPickedCountry.value && !selectedIso2.value) {
18006
+ if (phone.value && !selection.detectionLocked.value && !selectedIso2.value) {
18295
18007
  const match = tryMatchPhone(phone.value);
18296
18008
  if (match) {
18297
- autoSettingCountry.value = true;
18298
- selectedIso2.value = match.country.value;
18009
+ selection.set(match.country.value, "input");
18299
18010
  phone.value = match.nationalNumber;
18300
18011
  }
18301
18012
  }
18302
18013
  return;
18303
18014
  }
18304
- if (!selectedIso2.value && iso2) {
18305
- autoSettingCountry.value = true;
18306
- selectedIso2.value = iso2;
18307
- }
18015
+ if (!selectedIso2.value && iso2) selection.set(iso2, "env");
18308
18016
  });
18309
- /** External → internal: when the caller mutates `v-model:country` (dial number), resolve
18310
- * it to an ISO2. If the current ISO2 already maps to this dial (e.g. user has Canada
18311
- * selected and the caller writes back `1`), keep the existing selection — don't churn it. */
18312
- (0, vue.watch)(country, (next) => {
18313
- if (next == null) {
18314
- if (selectedIso2.value) selectedIso2.value = "";
18315
- return;
18017
+ useSyncedModel({
18018
+ model: country,
18019
+ triggers: [selectedIso2],
18020
+ compose: () => selectedIso2.value ? dialNumberFor(selectedIso2.value) : null,
18021
+ apply: (next) => {
18022
+ if (next == null) {
18023
+ selection.clear();
18024
+ return;
18025
+ }
18026
+ if (dialNumberFor(selectedIso2.value) === next) return;
18027
+ const iso2 = resolveCountryIdentifier(String(next));
18028
+ if (iso2) selection.set(iso2, "external");
18316
18029
  }
18317
- if (dialNumberFor(selectedIso2.value) === next) return;
18318
- const iso2 = resolveCountryIdentifier(String(next));
18319
- if (iso2) selectedIso2.value = iso2;
18320
- }, { immediate: true });
18321
- /** Internal → external: keep `country` (dial number) in lockstep with `selectedIso2`, and
18322
- * flag "user manually picked from picker" when the change isn't one we initiated.
18323
- * `flush: 'sync'` so the `autoSettingCountry` guard is reliable. */
18324
- (0, vue.watch)(selectedIso2, (iso2, prev) => {
18325
- const wasAutoSet = autoSettingCountry.value;
18326
- autoSettingCountry.value = false;
18327
- const nextDial = dialNumberFor(iso2);
18328
- if (country.value !== nextDial) country.value = nextDial;
18329
- if (!wasAutoSet && props.detectFromInput && iso2 && prev !== iso2) userPickedCountry.value = true;
18330
- }, { flush: "sync" });
18030
+ });
18331
18031
  /** The string shown in the `<input>`. Deliberately decoupled from `phone` (the digits-only
18332
18032
  * model) so the visible field is NOT rewritten mid-edit — non-digits / alternative numerals
18333
18033
  * are normalized into `phone` immediately, but the displayed value is only cleaned up once
@@ -18336,6 +18036,29 @@ const _sfc_main = /* @__PURE__ */ (0, vue.defineComponent)({
18336
18036
  /** Set when the in-flight `phone` change came from the user typing — tells the `phone`
18337
18037
  * watcher to leave `displayValue` alone (the user is still editing it). */
18338
18038
  let phoneEditedByInput = false;
18039
+ useSyncedModel({
18040
+ model: modelValue,
18041
+ triggers: [phone, selectedIso2],
18042
+ compose: () => {
18043
+ if (!selectedIso2.value || !phone.value) return "";
18044
+ return validate({
18045
+ country: { iso2: selectedIso2.value },
18046
+ phone: phone.value
18047
+ }).full_phone ?? "";
18048
+ },
18049
+ apply: (next) => {
18050
+ const trimmed = String(next ?? "").trim();
18051
+ if (!trimmed) {
18052
+ if (phone.value !== "") phone.value = "";
18053
+ if (selectedIso2.value !== "") selection.clear();
18054
+ return;
18055
+ }
18056
+ const parsed = (0, libphonenumber_js.parsePhoneNumberFromString)(trimmed.startsWith("+") ? trimmed : `+${trimmed.replace(/^\+/, "")}`);
18057
+ if (!parsed || !parsed.country) return;
18058
+ if (selectedIso2.value !== parsed.country) selection.set(parsed.country, "external");
18059
+ if (phone.value !== parsed.nationalNumber) phone.value = parsed.nationalNumber;
18060
+ }
18061
+ });
18339
18062
  function commitPhone(value) {
18340
18063
  phoneEditedByInput = true;
18341
18064
  phone.value = value;
@@ -18343,14 +18066,10 @@ const _sfc_main = /* @__PURE__ */ (0, vue.defineComponent)({
18343
18066
  function handlePhoneInput(e) {
18344
18067
  const target = e.target;
18345
18068
  displayValue.value = target.value;
18346
- const cleaned = normalizeDigits(target.value).replace(/\D/g, "");
18069
+ const cleaned = require_usePhoneValidation.normalizeDigits(target.value).replace(/\D/g, "");
18347
18070
  if (!cleaned) {
18348
18071
  typing.reset();
18349
- if (props.detectFromInput) {
18350
- autoSettingCountry.value = true;
18351
- selectedIso2.value = "";
18352
- userPickedCountry.value = false;
18353
- }
18072
+ if (props.detectFromInput) selection.clear();
18354
18073
  commitPhone("");
18355
18074
  return;
18356
18075
  }
@@ -18361,10 +18080,10 @@ const _sfc_main = /* @__PURE__ */ (0, vue.defineComponent)({
18361
18080
  * value — fold alternative numerals to ASCII and drop any stray non-digits. */
18362
18081
  function handlePhoneChange(e) {
18363
18082
  const target = e.target;
18364
- displayValue.value = normalizeDigits(target.value).replace(/\D/g, "");
18083
+ displayValue.value = require_usePhoneValidation.normalizeDigits(target.value).replace(/\D/g, "");
18365
18084
  }
18366
18085
  (0, vue.watch)(() => phone.value, (next) => {
18367
- const cleaned = normalizeDigits(String(next ?? "")).replace(/\D/g, "");
18086
+ const cleaned = require_usePhoneValidation.normalizeDigits(String(next ?? "")).replace(/\D/g, "");
18368
18087
  if (cleaned !== next) {
18369
18088
  phone.value = cleaned;
18370
18089
  return;
@@ -18377,12 +18096,14 @@ const _sfc_main = /* @__PURE__ */ (0, vue.defineComponent)({
18377
18096
  }, { flush: "post" });
18378
18097
  /** Resolved UI strings — `messages` prop merged onto English defaults. The individual
18379
18098
  * string props still win when both are set (see `errorMessage` / template bindings). */
18380
- const messages = (0, vue.computed)(() => resolveMessages(props.messages));
18099
+ const messages = (0, vue.computed)(() => require_types.resolveMessages(props.messages));
18381
18100
  /** `dir` of the outer wrapper — drives the hint/error text alignment and the country
18382
18101
  * picker popover. Explicit `'ltr'`/`'rtl'` is applied; `'auto'` or an omitted prop yields
18383
18102
  * `undefined` so it inherits from the page. The field row itself is always LTR so the
18384
18103
  * dial prefix / digits / flag trigger keep a consistent order. */
18385
18104
  const dirAttr = (0, vue.computed)(() => props.dir === "ltr" || props.dir === "rtl" ? props.dir : void 0);
18105
+ /** Set to `true` the first time the input is blurred. Drives `validateOn: 'blur'`. */
18106
+ const hasBlurred = (0, vue.ref)(false);
18386
18107
  const { validation, required, validationState, visibleValidationState, errorMessage, showError, showHint, selectedDialCode } = useTelInputValidation({
18387
18108
  validate,
18388
18109
  getRequiredInfo,
@@ -18391,15 +18112,35 @@ const _sfc_main = /* @__PURE__ */ (0, vue.defineComponent)({
18391
18112
  phone,
18392
18113
  selectedIso2,
18393
18114
  hasFinishedTyping,
18115
+ hasBlurred,
18394
18116
  messages
18395
18117
  }, {
18396
18118
  locale: () => props.locale,
18397
18119
  showValidation: () => props.showValidation,
18398
- errorMessages: () => props.errorMessages
18120
+ errorMessages: () => props.errorMessages,
18121
+ validateOn: () => props.validateOn,
18122
+ externalError: () => props.error
18399
18123
  });
18400
18124
  const effectivePlaceholder = (0, vue.computed)(() => props.placeholder || required.value?.format_hint || messages.value.phoneInputLabel);
18401
18125
  const helperId = (0, vue.useId)();
18402
18126
  const describedBy = (0, vue.computed)(() => showError.value || showHint.value ? helperId : void 0);
18127
+ const inputRef = (0, vue.ref)(null);
18128
+ function handleBlur(e) {
18129
+ hasBlurred.value = true;
18130
+ emit("blur", e);
18131
+ }
18132
+ function handleFocus(e) {
18133
+ emit("focus", e);
18134
+ }
18135
+ function focus(options) {
18136
+ inputRef.value?.focus(options);
18137
+ }
18138
+ function blur() {
18139
+ inputRef.value?.blur();
18140
+ }
18141
+ function select() {
18142
+ inputRef.value?.select();
18143
+ }
18403
18144
  __expose({
18404
18145
  validation,
18405
18146
  required,
@@ -18408,12 +18149,18 @@ const _sfc_main = /* @__PURE__ */ (0, vue.defineComponent)({
18408
18149
  visibleValidationState,
18409
18150
  isDetecting,
18410
18151
  hasFinishedTyping,
18411
- detectionAttempted
18152
+ detectionAttempted,
18153
+ focus,
18154
+ blur,
18155
+ select
18412
18156
  });
18413
18157
  const __returned__ = {
18414
18158
  props,
18159
+ emit,
18415
18160
  phone,
18416
18161
  country,
18162
+ modelValue,
18163
+ selection,
18417
18164
  selectedIso2,
18418
18165
  getCountries,
18419
18166
  validate,
@@ -18423,10 +18170,9 @@ const _sfc_main = /* @__PURE__ */ (0, vue.defineComponent)({
18423
18170
  resolveCountryIdentifier,
18424
18171
  dialNumberFor,
18425
18172
  matchLeadingDialCode,
18426
- userPickedCountry,
18427
- autoSettingCountry,
18428
18173
  inferredCountry,
18429
18174
  tryMatchPhone,
18175
+ onPickerPick,
18430
18176
  typing,
18431
18177
  isDetecting,
18432
18178
  hasFinishedTyping,
@@ -18443,6 +18189,7 @@ const _sfc_main = /* @__PURE__ */ (0, vue.defineComponent)({
18443
18189
  handlePhoneChange,
18444
18190
  messages,
18445
18191
  dirAttr,
18192
+ hasBlurred,
18446
18193
  validation,
18447
18194
  required,
18448
18195
  validationState,
@@ -18454,6 +18201,12 @@ const _sfc_main = /* @__PURE__ */ (0, vue.defineComponent)({
18454
18201
  effectivePlaceholder,
18455
18202
  helperId,
18456
18203
  describedBy,
18204
+ inputRef,
18205
+ handleBlur,
18206
+ handleFocus,
18207
+ focus,
18208
+ blur,
18209
+ select,
18457
18210
  get cn() {
18458
18211
  return cn$2;
18459
18212
  },
@@ -18495,25 +18248,34 @@ const _hoisted_4 = {
18495
18248
  };
18496
18249
  const _hoisted_5 = [
18497
18250
  "value",
18251
+ "name",
18498
18252
  "disabled",
18499
18253
  "placeholder",
18500
18254
  "aria-label",
18501
18255
  "aria-invalid",
18502
18256
  "aria-describedby",
18257
+ "aria-errormessage",
18258
+ "aria-busy",
18503
18259
  "data-has-dial"
18504
18260
  ];
18505
18261
  const _hoisted_6 = {
18262
+ key: 0,
18263
+ class: "a-tel-input__validating",
18264
+ "data-slot": "tel-input-validating",
18265
+ "aria-hidden": "true"
18266
+ };
18267
+ const _hoisted_7 = {
18506
18268
  key: 0,
18507
18269
  class: "a-tel-input__detecting",
18508
18270
  "aria-hidden": "true",
18509
18271
  "data-slot": "tel-input-detecting"
18510
18272
  };
18511
- const _hoisted_7 = {
18273
+ const _hoisted_8 = {
18512
18274
  key: 0,
18513
18275
  class: "a-tel-input__country-wrapper",
18514
18276
  "data-slot": "tel-input-country-wrapper"
18515
18277
  };
18516
- const _hoisted_8 = ["id"];
18278
+ const _hoisted_9 = ["id"];
18517
18279
  function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
18518
18280
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
18519
18281
  class: (0, vue.normalizeClass)($setup.cn("a-tel-input", _ctx.$attrs.class)),
@@ -18531,31 +18293,41 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
18531
18293
  (0, vue.renderSlot)(_ctx.$slots, "prefix", {}, void 0, true),
18532
18294
  $setup.selectedDialCode ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_4, (0, vue.toDisplayString)($setup.selectedDialCode), 1)) : (0, vue.createCommentVNode)("v-if", true),
18533
18295
  (0, vue.createElementVNode)("input", {
18296
+ ref: "inputRef",
18534
18297
  value: $setup.displayValue,
18535
18298
  type: "tel",
18536
18299
  inputmode: "numeric",
18537
18300
  autocomplete: "tel",
18538
18301
  dir: "ltr",
18539
18302
  "data-slot": "tel-input-field",
18303
+ name: $setup.props.name,
18540
18304
  disabled: $setup.props.disabled || $setup.props.loading,
18541
18305
  placeholder: $setup.effectivePlaceholder,
18542
18306
  "aria-label": $setup.messages.phoneInputLabel,
18543
18307
  "aria-invalid": $setup.visibleValidationState === "error" || void 0,
18544
18308
  "aria-describedby": $setup.describedBy,
18309
+ "aria-errormessage": $setup.visibleValidationState === "error" ? $setup.helperId : void 0,
18310
+ "aria-busy": $setup.props.validating || void 0,
18545
18311
  class: (0, vue.normalizeClass)($setup.cn("a-tel-input__input", $setup.props.inputClass)),
18546
18312
  "data-has-dial": $setup.selectedDialCode ? "" : void 0,
18547
18313
  onInput: $setup.handlePhoneInput,
18548
- onChange: $setup.handlePhoneChange
18314
+ onChange: $setup.handlePhoneChange,
18315
+ onBlur: $setup.handleBlur,
18316
+ onFocus: $setup.handleFocus
18549
18317
  }, null, 42, _hoisted_5),
18318
+ (0, vue.createCommentVNode)(" Async-validation spinner (e.g. server-side \"phone exists?\" check). Independent\n of `isDetecting` (which is for country detection) so both can be shown without\n interfering. Lives next to the input and never disables it. "),
18319
+ (0, vue.createVNode)(vue.Transition, { name: "a-tell-detect" }, {
18320
+ default: (0, vue.withCtx)(() => [$setup.props.validating ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_6, [(0, vue.renderSlot)(_ctx.$slots, "validating", {}, () => [(0, vue.createVNode)($setup["SpinnerIcon"], { class: "a-tel-input__detecting-icon" })], true)])) : (0, vue.createCommentVNode)("v-if", true)]),
18321
+ _: 3
18322
+ }),
18550
18323
  (0, vue.createCommentVNode)(" Detection-in-flight spinner — shown only during the first debounce window,\n before the picker has appeared. Once the picker is visible (success OR a failed\n attempt that revealed the empty picker) we stop re-flashing on every keystroke. "),
18551
18324
  (0, vue.createVNode)(vue.Transition, { name: "a-tell-detect" }, {
18552
- default: (0, vue.withCtx)(() => [$setup.isDetecting && !$setup.selectedIso2 && !$setup.detectionAttempted ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_6, [(0, vue.renderSlot)(_ctx.$slots, "detecting", {}, () => [(0, vue.createVNode)($setup["SpinnerIcon"], { class: "a-tel-input__detecting-icon" })], true)])) : (0, vue.createCommentVNode)("v-if", true)]),
18325
+ default: (0, vue.withCtx)(() => [$setup.isDetecting && !$setup.selectedIso2 && !$setup.detectionAttempted ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_7, [(0, vue.renderSlot)(_ctx.$slots, "detecting", {}, () => [(0, vue.createVNode)($setup["SpinnerIcon"], { class: "a-tel-input__detecting-icon" })], true)])) : (0, vue.createCommentVNode)("v-if", true)]),
18553
18326
  _: 3
18554
18327
  }),
18555
18328
  (0, vue.createVNode)(vue.Transition, { name: "a-tell-country" }, {
18556
- default: (0, vue.withCtx)(() => [!$setup.props.detectFromInput || $setup.selectedIso2 || $setup.detectionAttempted ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_7, [(0, vue.createVNode)($setup["ACountrySelect"], {
18329
+ default: (0, vue.withCtx)(() => [!$setup.props.detectFromInput || $setup.selectedIso2 || $setup.detectionAttempted ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_8, [(0, vue.createVNode)($setup["ACountrySelect"], {
18557
18330
  selected: $setup.selectedIso2,
18558
- "onUpdate:selected": _cache[0] || (_cache[0] = ($event) => $setup.selectedIso2 = $event),
18559
18331
  "allowed-dial-codes": $setup.props.allowedDialCodes,
18560
18332
  disabled: $setup.props.disabled || $setup.props.loading,
18561
18333
  size: $setup.props.size,
@@ -18566,6 +18338,7 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
18566
18338
  "suggested-label": $setup.messages.suggestedLabel,
18567
18339
  "all-countries-label": $setup.messages.allCountriesLabel,
18568
18340
  "country-label": $setup.messages.countryLabel,
18341
+ "onUpdate:selected": $setup.onPickerPick,
18569
18342
  "select-country-label": $setup.messages.selectCountryLabel,
18570
18343
  "flag-url": $setup.props.flagUrl,
18571
18344
  searcher: $setup.props.searcher,
@@ -18672,7 +18445,7 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
18672
18445
  }, () => [(0, vue.createElementVNode)("p", {
18673
18446
  "data-slot": "tel-input-hint",
18674
18447
  class: (0, vue.normalizeClass)($setup.cn("a-tel-input__hint", $setup.props.hintClass))
18675
- }, (0, vue.toDisplayString)($setup.required.format_hint), 3)], true) : (0, vue.createCommentVNode)("v-if", true)], 8, _hoisted_8)
18448
+ }, (0, vue.toDisplayString)($setup.required.format_hint), 3)], true) : (0, vue.createCommentVNode)("v-if", true)], 8, _hoisted_9)
18676
18449
  ], 10, _hoisted_1);
18677
18450
  }
18678
18451
  var ATelInput_default = /* @__PURE__ */ export_helper_default(_sfc_main, [
@@ -18685,19 +18458,22 @@ exports.ACountryFlag = ACountryFlag_default;
18685
18458
  exports.ACountrySelect = ACountrySelect_default;
18686
18459
  exports.ATelInput = ATelInput_default;
18687
18460
  exports.COUNTRY_RECENTS_KEY = COUNTRY_RECENTS_KEY;
18688
- exports.DEFAULT_ERROR_MESSAGES = DEFAULT_ERROR_MESSAGES;
18689
- exports.DEFAULT_MESSAGES = DEFAULT_MESSAGES;
18461
+ exports.DEFAULT_ERROR_MESSAGES = require_types.DEFAULT_ERROR_MESSAGES;
18462
+ exports.DEFAULT_MESSAGES = require_types.DEFAULT_MESSAGES;
18690
18463
  exports.DIAL_TO_ISO2_FALLBACK = DIAL_TO_ISO2_FALLBACK;
18691
- exports.LOCALE_DIGIT_RANGES = LOCALE_DIGIT_RANGES;
18692
- exports.aTelInputVariants = aTelInputVariants;
18464
+ exports.FALLBACK_ISO2_LIST = FALLBACK_ISO2_LIST;
18465
+ exports.LOCALE_DIGIT_RANGES = require_usePhoneValidation.LOCALE_DIGIT_RANGES;
18466
+ exports.aTelInputVariants = require_types.aTelInputVariants;
18693
18467
  exports.defaultFlagUrl = defaultFlagUrl;
18694
18468
  exports.detectCountry = detectCountry;
18695
- exports.localizeCountries = localizeCountries;
18696
- exports.normalizeDigits = normalizeDigits;
18697
- exports.resolveMessages = resolveMessages;
18469
+ exports.localizeCountries = require_usePhoneValidation.localizeCountries;
18470
+ exports.normalizeDigits = require_usePhoneValidation.normalizeDigits;
18471
+ exports.resolveMessages = require_types.resolveMessages;
18698
18472
  exports.useCountryDetection = useCountryDetection;
18699
18473
  exports.useCountryMatching = useCountryMatching;
18700
- exports.usePhoneValidation = usePhoneValidation;
18474
+ exports.useCountrySelection = useCountrySelection;
18475
+ exports.usePhoneValidation = require_usePhoneValidation.usePhoneValidation;
18476
+ exports.useSyncedModel = useSyncedModel;
18701
18477
  exports.useTelInputValidation = useTelInputValidation;
18702
18478
  exports.useTypingPhase = useTypingPhase;
18703
18479