@desource/phone-mask-vue 0.2.3 → 1.0.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/LICENSE +1 -1
  3. package/README.md +154 -11
  4. package/dist/index.cjs +858 -961
  5. package/dist/index.js +858 -961
  6. package/dist/index.mjs +859 -962
  7. package/dist/phone-mask-vue.css +64 -64
  8. package/dist/types/components/PhoneInput.vue.d.ts +7 -7
  9. package/dist/types/components/PhoneInput.vue.d.ts.map +1 -1
  10. package/dist/types/composables/internal/useCopyAction.d.ts +14 -0
  11. package/dist/types/composables/internal/useCopyAction.d.ts.map +1 -0
  12. package/dist/types/composables/internal/useCountry.d.ts +22 -0
  13. package/dist/types/composables/internal/useCountry.d.ts.map +1 -0
  14. package/dist/types/composables/internal/useCountrySelector.d.ts +30 -0
  15. package/dist/types/composables/internal/useCountrySelector.d.ts.map +1 -0
  16. package/dist/types/composables/internal/useFormatter.d.ts +46 -0
  17. package/dist/types/composables/internal/useFormatter.d.ts.map +1 -0
  18. package/dist/types/composables/internal/useInputHandlers.d.ts +20 -0
  19. package/dist/types/composables/internal/useInputHandlers.d.ts.map +1 -0
  20. package/dist/types/composables/internal/useTheme.d.ts +10 -0
  21. package/dist/types/composables/internal/useTheme.d.ts.map +1 -0
  22. package/dist/types/composables/internal/useValidationHint.d.ts +6 -0
  23. package/dist/types/composables/internal/useValidationHint.d.ts.map +1 -0
  24. package/dist/types/composables/usePhoneMask.d.ts +8 -0
  25. package/dist/types/composables/usePhoneMask.d.ts.map +1 -0
  26. package/dist/types/composables/{useClipboard.d.ts → utility/useClipboard.d.ts} +1 -2
  27. package/dist/types/composables/utility/useClipboard.d.ts.map +1 -0
  28. package/dist/types/composables/utility/useTimer.d.ts +9 -0
  29. package/dist/types/composables/utility/useTimer.d.ts.map +1 -0
  30. package/dist/types/directives/vPhoneMask.d.ts.map +1 -1
  31. package/dist/types/index.d.ts +4 -2
  32. package/dist/types/index.d.ts.map +1 -1
  33. package/dist/types/types.d.ts +31 -13
  34. package/dist/types/types.d.ts.map +1 -1
  35. package/package.json +18 -12
  36. package/dist/types/composables/useClipboard.d.ts.map +0 -1
  37. package/dist/types/composables/useCountrySelector.d.ts +0 -21
  38. package/dist/types/composables/useCountrySelector.d.ts.map +0 -1
  39. package/dist/types/composables/useMask.d.ts +0 -20
  40. package/dist/types/composables/useMask.d.ts.map +0 -1
  41. package/dist/types/composables/usePhoneFormatter.d.ts +0 -16
  42. package/dist/types/composables/usePhoneFormatter.d.ts.map +0 -1
  43. package/dist/types/consts.d.ts +0 -8
  44. package/dist/types/consts.d.ts.map +0 -1
package/dist/index.cjs CHANGED
@@ -2,66 +2,101 @@
2
2
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
3
  const vue = require("vue");
4
4
  const M = { AC: "+247 #####", AD: ["+376 ### ###", "+376 #### ####"], AE: ["+971 # ### ####", "+971 ## ### ####", "+971 ### ######", "+971 ### # #####"], AF: "+93 ## ### ####", AG: "+1 ###-###-####", AI: "+1 ###-###-####", AL: ["+355 ## ### ###", "+355 ## ### ####", "+355 ### ####", "+355 ### ###", "+355 ### #####"], AM: ["+374 ## ######", "+374 ### ## ###"], AO: "+244 ### ### ###", AR: ["+54 ## ####-####", "+54 # ## ####-####", "+54 ###-###-####"], AS: "+1 ###-###-####", AT: ["+43 # #########", "+43 ### ######"], AU: ["+61 # #### ####", "+61 ### ### ###", "+61 #### ### ###", "+61 ## ### ##"], AW: "+297 ### ####", AX: ["+358 ## #######", "+358 ### ######"], AZ: ["+994 ## ### ## ##", "+994 ### ## ## ##"], BA: ["+387 ## ###-###", "+387 ## ### ###"], BB: "+1 ###-###-####", BD: ["+880 #-#######", "+880 ####-######", "+880 ###-#######"], BE: ["+32 ## ## ## ##", "+32 ### ## ## ##", "+32 ### ## ###"], BF: "+226 ## ## ## ##", BG: ["+359 # ### ###", "+359 ## ### ###", "+359 ### ## ###"], BH: "+973 #### ####", BI: "+257 ## ## ## ##", BJ: ["+229 ## ## ## ## ##", "+229 ## ## ## ##"], BL: "+590 ### ## ## ##", BM: "+1 ###-###-####", BN: "+673 ### ####", BO: ["+591 # #######", "+591 ########", "+591 ### ## ####"], BQ: "+599 ### ####", BR: ["+55 ## ####-####", "+55 ## #####-####", "+55 ### ## ####", "+55 ####-####"], BS: "+1 ###-###-####", BT: ["+975 # ### ###", "+975 ## ## ## ##"], BW: ["+267 ### ####", "+267 ## ### ###", "+267 #### ### ###", "+267 ## #####"], BY: ["+375 ### ##-##-##", "+375 ## ###-##-##", "+375 ### ### ####"], BZ: ["+501 ###-####", "+501 #-###-####-###"], CA: "+1 ###-###-####", CC: ["+61 # #### ####", "+61 ### ### ###", "+61 #### ### ###"], CD: ["+243 ## #####", "+243 ### ### ###"], CF: "+236 ## ## ## ##", CG: ["+242 ## ### ####", "+242 # #### ####"], CH: ["+41 ## ### ## ##", "+41 ### ### ###"], CI: ["+225 ## ## # #####", "+225 ## ## ## ####"], CK: "+682 ## ###", CL: ["+56 ### ### ###", "+56 # #### ####", "+56 ### ### ####", "+56 ## ### ####"], CM: ["+237 # ## ## ## ##", "+237 ## ## ## ##"], CN: ["+86 ## #### ####", "+86 ### #### ####", "+86 ### ### ####", "+86 ########"], CO: ["+57 ### #######", "+57 # ### #######"], CR: ["+506 #### ####", "+506 ###-###-####"], CU: ["+53 # #######", "+53 ### #######"], CV: "+238 ### ## ##", CW: ["+599 # ### ####", "+599 ### ####"], CX: ["+61 # #### ####", "+61 ### ### ###", "+61 #### ### ###"], CY: "+357 ## ######", CZ: "+420 ### ### ###", DE: ["+49 ## ######", "+49 #### #######", "+49 ### ##########", "+49 ### # ######", "+49 ### # ####", "+49 ### #### ####", "+49 ########"], DJ: "+253 ## ## ## ##", DK: "+45 ## ## ## ##", DM: "+1 ###-###-####", DO: "+1 ###-###-####", DZ: ["+213 ## ## ## ##", "+213 ### ## ## ##", "+213 ## ### ## ##"], EC: ["+593 #-###-####", "+593 ## ### ####", "+593 #### ### ####"], EE: ["+372 ### ####", "+372 #### ####", "+372 ## ## ####"], EG: ["+20 # ########", "+20 ## ########", "+20 ### ### ####"], EH: ["+212 # ## ## ## ##", "+212 ##-#######"], ER: "+291 # ### ###", ES: ["+34 ### ## ## ##", "+34 ### ### ###"], ET: "+251 ## ### ####", FI: ["+358 ## #######", "+358 ### ######"], FJ: ["+679 ### ####", "+679 #### ### ####"], FK: "+500 #####", FM: "+691 ### ####", FO: "+298 ######", FR: ["+33 # ## ## ## ##", "+33 ### ## ## ##"], GA: "+241 ## ## ## ##", GB: ["+44 ### ### ####", "+44 #### ######", "+44 ## #### ####"], GD: "+1 ###-###-####", GE: ["+995 ## ### ## ##", "+995 ### ## ## ##", "+995 ### ### ###"], GF: "+594 ### ## ## ##", GG: ["+44 #### ######", "+44 ### ### ####", "+44 ## #### ####"], GH: ["+233 ## ### ####", "+233 ### #####"], GI: ["+350 ### #####", "+350 ########"], GL: "+299 ## ## ##", GM: "+220 ### ####", GN: ["+224 ## ## ## ##", "+224 ### ## ## ##"], GP: "+590 ### ## ## ##", GQ: ["+240 ### ### ###", "+240 ### ######"], GR: ["+30 ## #### ####", "+30 ### ### ####"], GT: ["+502 #### ####", "+502 #### ### ####"], GU: "+1 ###-###-####", GW: ["+245 ### ### ###", "+245 ### ####"], GY: "+592 ### ####", HK: ["+852 #### ####", "+852 ### ### ###", "+852 ### ## ### ###"], HN: ["+504 ####-####", "+504 ###########"], HR: ["+385 # #### ###", "+385 ## ### ####", "+385 ### ### ###", "+385 ## ## ###", "+385 ## ### ###"], HT: "+509 ## ## ####", HU: ["+36 # ### ####", "+36 ## ### ####", "+36 ## ### ###"], ID: ["+62 ## #######", "+62 ###-###-###", "+62 ### #######", "+62 ### # ### ###", "+62 ### ### ####"], IE: ["+353 ## #####", "+353 ## ### ####", "+353 #### ### ###", "+353 ### ### ###"], IL: ["+972 #-###-####", "+972 ##-###-####", "+972 #-###-###-###"], IM: ["+44 #### ######", "+44 ### ### ####", "+44 ## #### ####"], IN: ["+91 ##### #####", "+91 #### ## ####", "+91 #### ### ### ###", "+91 #### ### ####"], IO: "+246 ### ####", IQ: ["+964 # ### ####", "+964 ### ### ####"], IR: ["+98 ## #### ####", "+98 ### ### ####"], IS: "+354 ### ####", IT: ["+39 ## #### ####", "+39 ### ### ####", "+39 ### ### ###"], JE: ["+44 #### ######", "+44 ### ### ####", "+44 ## #### ####"], JM: "+1 ###-###-####", JO: ["+962 # ### ####", "+962 # #### ####", "+962 ### #####", "+962 ## #######"], JP: ["+81 #-####-####", "+81 ##-####-####", "+81 ###-###-###", "+81 ##-###-####"], KE: ["+254 ## #######", "+254 ### ######", "+254 ### ### ###"], KG: ["+996 ### ### ###", "+996 ### ### # ##"], KH: ["+855 ## ### ###", "+855 #### ### ###"], KI: ["+686 #####", "+686 ########"], KM: "+269 ### ## ##", KN: "+1 ###-###-####", KP: ["+850 # ### ####", "+850 ### ### ####"], KR: ["+82 #-###-####", "+82 ##-####-####", "+82 ##-###-####"], KW: ["+965 #### ####", "+965 ### #####", "+965 #### ###"], KY: "+1 ###-###-####", KZ: ["+7 ##### # ## ##", "+7 ### ### ####", "+7 ### ###-##-##"], LA: ["+856 ## ### ###", "+856 ## ## ### ###"], LB: ["+961 # ### ###", "+961 ## ### ###"], LC: "+1 ###-###-####", LI: ["+423 ### ## ##", "+423 ### ### ###"], LK: ["+94 ### ### ###", "+94 ## ### ####"], LR: ["+231 ## ### ###", "+231 ## ### ####"], LS: "+266 #### ####", LT: ["+370 ### #####", "+370 ### ## ###"], LU: ["+352 ## ## ## ##", "+352 ### ### ###", "+352 ### ## ###"], LV: "+371 ## ### ###", LY: "+218 ##-#######", MA: ["+212 # ## ## ## ##", "+212 ##-#######"], MC: ["+377 ## ## ## ##", "+377 # ## ## ## ##"], MD: ["+373 ## ### ###", "+373 ### ## ###", "+373 ### #####"], ME: "+382 ## ### ###", MF: "+590 ### ## ## ##", MG: "+261 ## ## ### ##", MH: "+692 ###-####", MK: ["+389 # ### ####", "+389 ## ### ###", "+389 ### # ## ##"], ML: "+223 ## ## ## ##", MM: ["+95 # ### ###", "+95 # ### ####", "+95 ### ### ####"], MN: "+976 #### ####", MO: ["+853 #### ####", "+853 #### ###"], MP: "+1 ###-###-####", MQ: "+596 ### ## ## ##", MR: "+222 ## ## ## ##", MS: "+1 ###-###-####", MT: "+356 #### ####", MU: ["+230 #### ####", "+230 ### ####"], MV: ["+960 ###-####", "+960 ### ### ####"], MW: ["+265 # ### ###", "+265 ### ## ## ##"], MX: "+52 ### ### ####", MY: ["+60 #-#### ####", "+60 ##-### ####", "+60 #-###-##-####", "+60 ###-### ####"], MZ: ["+258 ## ### ###", "+258 ## ### ####", "+258 ### ### ###"], NA: ["+264 ## ### ###", "+264 ## ### ####", "+264 ### ### ###"], NC: "+687 ##.##.##", NE: ["+227 ## ## ## ##", "+227 ## ### ###"], NF: ["+672 ## ####", "+672 # #####"], NG: ["+234 #### ## ####", "+234 ### ### ####", "+234 ### #### ####"], NI: "+505 #### ####", NL: ["+31 ## ### ####", "+31 # ########", "+31 ### ####", "+31 ## #######"], NO: ["+47 ## ## ## ##", "+47 ### ## ###"], NP: ["+977 #-#######", "+977 ###-#######", "+977 ###########"], NR: "+674 ### ####", NU: ["+683 ####", "+683 ### ####"], NZ: ["+64 # ### ####", "+64 ## ### ####", "+64 ### ### ###"], OM: ["+968 ## ######", "+968 #### ####", "+968 ### #####"], PA: ["+507 ###-####", "+507 ####-####"], PE: ["+51 # #######", "+51 ### ### ###", "+51 ### #####"], PF: ["+689 ## ## ## ##", "+689 ### ## ## ##"], PG: ["+675 ### ####", "+675 #### ####"], PH: ["+63 # #### ####", "+63 ### ### ####", "+63 #### # ### ####"], PK: ["+92 ## ########", "+92 ### #######", "+92 ### ### ##", "+92 #### #####"], PL: ["+48 ## ### ## ##", "+48 ### ### ###"], PM: ["+508 ## ## ##", "+508 ### ## ## ##"], PR: "+1 ###-###-####", PS: ["+970 # ### ####", "+970 ### ### ###", "+970 #### ### ###"], PT: ["+351 ## ### ####", "+351 ### ### ###"], PW: "+680 ### ####", PY: ["+595 ## ### ####", "+595 ### ######", "+595 #### ### ####"], QA: ["+974 #### ####", "+974 ### ####"], RE: "+262 ### ## ## ##", RO: ["+40 ## ### ####", "+40 ### ### ###"], RS: ["+381 ## ######", "+381 ## #######", "+381 ### #####"], RU: "+7 ### ###-##-##", RW: "+250 ### ### ###", SA: ["+966 ## ### ####", "+966 ### ### ####", "+966 #### #####"], SB: ["+677 #####", "+677 ## #####"], SC: ["+248 # ### ###", "+248 #######"], SD: "+249 ## ### ####", SE: ["+46 # ## ## ##", "+46 ## ### ## ##", "+46 ## ## ## ##", "+46 ### ## ## ###"], SG: ["+65 #### ####", "+65 #### ### ####"], SH: "+290 #####", SI: ["+386 # ### ## ##", "+386 ## ### ###", "+386 ## ######", "+386 ### #####"], SJ: ["+47 ## ## ## ##", "+47 ### ## ###"], SK: ["+421 #/### ### ##", "+421 ### ### ###", "+421 #######"], SL: "+232 ## ######", SM: ["+378 #### ######", "+378 ## ## ## ##"], SN: ["+221 ## ### ## ##", "+221 ### ## ## ##"], SO: ["+252 # ######", "+252 # #######"], SR: ["+597 ###-###", "+597 ###-####", "+597 ##-##-##"], SS: "+211 ### ### ###", ST: "+239 ### ####", SV: ["+503 #### ####", "+503 ### ####"], SX: "+1 ###-###-####", SY: ["+963 ## ### ####", "+963 ### ### ###"], SZ: ["+268 #### ####", "+268 ##### ####"], TA: "+290 ####", TC: "+1 ###-###-####", TD: "+235 ## ## ## ##", TG: "+228 ## ## ## ##", TH: ["+66 # ### ####", "+66 ## ### ####", "+66 #### ### ###"], TJ: ["+992 ### ## ####", "+992 ## ### ####"], TK: "+690 ####", TL: ["+670 ### ####", "+670 #### ####"], TM: ["+993 ## ##-##-##", "+993 ## ######"], TN: "+216 ## ### ###", TO: ["+676 ##-###", "+676 ### ####", "+676 #### ###"], TR: ["+90 ### ### ## ##", "+90 ### ### ####"], TT: "+1 ###-###-####", TV: ["+688 ## ###", "+688 ## ####"], TW: ["+886 # #### ####", "+886 ### ### ###", "+886 ## ### ####", "+886 ## #### ####"], TZ: ["+255 ## ### ####", "+255 ### ### ###", "+255 ### ## ####"], UA: ["+380 #### #####", "+380 ## ### ####", "+380 ### ### ###"], UG: ["+256 ## #######", "+256 ### ######"], US: "+1 ###-###-####", UY: ["+598 #### ####", "+598 ## ### ###", "+598 ### ####"], UZ: "+998 ## ### ## ##", VA: ["+39 ## #### ####", "+39 ### ### ####", "+39 ### ### ###"], VC: "+1 ###-###-####", VE: "+58 ###-#######", VG: "+1 ###-###-####", VI: "+1 ###-###-####", VN: ["+84 ### #### ###", "+84 ### ### ###", "+84 #### ######", "+84 ## ### ## ##"], VU: ["+678 #####", "+678 ### ####"], WF: ["+681 ## ## ##", "+681 ### ## ## ##"], WS: ["+685 #####", "+685 ## #####", "+685 ### ###"], XK: ["+383 ## ### ###", "+383 ### #####"], YE: ["+967 # ### ###", "+967 ### ### ###"], YT: "+262 ### ## ## ##", ZA: ["+27 ## ### ####", "+27 ### ### ###"], ZM: ["+260 ### ### ###", "+260 ## #######", "+260 #########"], ZW: ["+263 ## #####", "+263 ## ### ####", "+263 ### ####", "+263 #### ######"] };
5
- const t$1 = /^[a-z]{2}$/i, countryCodeEmoji = (o) => {
6
- if (!t$1.test(o)) {
7
- const t2 = typeof o;
8
- throw new TypeError(`cc argument must be an ISO 3166-1 alpha-2 string, but got '${"string" === t2 ? o : t2}' instead.`);
5
+ const t$1 = /^[a-z]{2}$/i, countryCodeEmoji = (o2) => {
6
+ if (!t$1.test(o2)) {
7
+ const t2 = typeof o2;
8
+ throw new TypeError(`cc argument must be an ISO 3166-1 alpha-2 string, but got '${"string" === t2 ? o2 : t2}' instead.`);
9
9
  }
10
- const e = [...o.toUpperCase()].map((t2) => (t2.codePointAt(0) ?? 0) + 127397);
11
- return String.fromCodePoint(...e);
10
+ const e2 = [...o2.toUpperCase()].map((t2) => (t2.codePointAt(0) ?? 0) + 127397);
11
+ return String.fromCodePoint(...e2);
12
12
  };
13
- const t = Object.entries(M), divideMask = (e) => e.split(/ (.*)/s);
14
- function getCodeAndMask(e) {
15
- let n = "", t2 = "";
16
- if (Array.isArray(e)) {
17
- const o = [];
18
- for (const t3 of e) {
19
- const [e2, s] = divideMask(t3);
20
- n || (n = e2), o.push(s);
13
+ const o$1 = "en", n = /* @__PURE__ */ new Map(), getDisplayNames = (e2) => {
14
+ const t2 = e2.toLowerCase(), s2 = n.get(t2);
15
+ if (s2) return s2;
16
+ const r = new Intl.DisplayNames([e2], { type: "region" });
17
+ if (n.size >= 10) {
18
+ for (const e3 of n.keys()) if (e3 !== o$1) {
19
+ n.delete(e3);
20
+ break;
21
21
  }
22
- t2 = o;
22
+ }
23
+ return n.set(t2, r), r;
24
+ }, s$1 = Object.entries(M), divideMask = (e2) => e2.split(/ (.*)/s);
25
+ function getCodeAndMask(e2) {
26
+ let t2 = "", o2 = "";
27
+ if (Array.isArray(e2)) {
28
+ const n2 = [];
29
+ for (const o3 of e2) {
30
+ const [e3, s2] = divideMask(o3);
31
+ t2 || (t2 = e3), n2.push(s2);
32
+ }
33
+ o2 = n2;
23
34
  } else {
24
- const [o, s] = divideMask(e);
25
- n = o, t2 = s;
35
+ const [n2, s2] = divideMask(e2);
36
+ t2 = n2, o2 = s2;
26
37
  }
27
- return [n, t2];
38
+ return [t2, o2];
28
39
  }
29
- t.map(([e, n]) => ({ id: e, mask: n }));
30
- t.reduce((e, [n, t2]) => {
31
- const [o, s] = getCodeAndMask(t2);
32
- return e[n] = { code: o, mask: s }, e;
40
+ s$1.map(([e2, t2]) => ({ id: e2, mask: t2 }));
41
+ s$1.reduce((e2, [t2, o2]) => {
42
+ const [n2, s2] = getCodeAndMask(o2);
43
+ return e2[t2] = { code: n2, mask: s2 }, e2;
33
44
  }, {});
34
- t.map(([e, n]) => {
35
- const [t2, o] = getCodeAndMask(n);
36
- return { id: e, code: t2, mask: o };
45
+ s$1.map(([e2, t2]) => {
46
+ const [o2, n2] = getCodeAndMask(t2);
47
+ return { id: e2, code: o2, mask: n2 };
37
48
  });
38
- t.reduce((e, [t2, o]) => {
39
- const [s, a] = getCodeAndMask(o);
40
- return e[t2] = { code: s, mask: a, flag: countryCodeEmoji(t2) }, e;
49
+ s$1.reduce((e2, [o2, n2]) => {
50
+ const [s2, r] = getCodeAndMask(n2);
51
+ return e2[o2] = { code: s2, mask: r, flag: countryCodeEmoji(o2) }, e2;
41
52
  }, {});
42
- t.map(([e, t2]) => {
43
- const [o, s] = getCodeAndMask(t2);
44
- return { id: e, code: o, mask: s, flag: countryCodeEmoji(e) };
53
+ s$1.map(([e2, o2]) => {
54
+ const [n2, s2] = getCodeAndMask(o2);
55
+ return { id: e2, code: n2, mask: s2, flag: countryCodeEmoji(e2) };
45
56
  });
46
- const MasksFullMap = (e) => {
47
- const o = new Intl.DisplayNames([e], { type: "region" });
48
- return t.reduce((e2, [t2, s]) => {
49
- const [a, r] = getCodeAndMask(s), d = o.of(t2) ?? "";
50
- return e2[t2] = { code: a, mask: r, name: d, flag: countryCodeEmoji(t2) }, e2;
57
+ const MasksFullMap = (e2) => {
58
+ const o2 = getDisplayNames(e2);
59
+ return s$1.reduce((e3, [n2, s2]) => {
60
+ const [r, a] = getCodeAndMask(s2), d = o2.of(n2) ?? "";
61
+ return e3[n2] = { code: r, mask: a, name: d, flag: countryCodeEmoji(n2) }, e3;
51
62
  }, {});
52
- }, MasksFull = (e) => {
53
- const o = new Intl.DisplayNames([e], { type: "region" });
54
- return t.map(([e2, t2]) => {
55
- const [s, a] = getCodeAndMask(t2);
56
- return { id: e2, code: s, mask: a, name: o.of(e2) ?? "", flag: countryCodeEmoji(e2) };
63
+ }, MasksFull = (e2) => {
64
+ const o2 = getDisplayNames(e2);
65
+ return s$1.map(([e3, n2]) => {
66
+ const [s2, r] = getCodeAndMask(n2);
67
+ return { id: e3, code: s2, mask: r, name: o2.of(e3) ?? "", flag: countryCodeEmoji(e3) };
57
68
  });
58
- }, m = t.reduce((e, [t2, o]) => {
59
- const [s, a] = getCodeAndMask(o), r = new Intl.DisplayNames(["en"], { type: "region" });
60
- return e[t2] = { code: s, mask: a, name: r.of(t2) ?? "", flag: countryCodeEmoji(t2) }, e;
61
- }, {}), i = t.map(([e, t2]) => {
62
- const [o, s] = getCodeAndMask(t2);
63
- return { id: e, code: o, mask: s, name: new Intl.DisplayNames(["en"], { type: "region" }).of(e) ?? "", flag: countryCodeEmoji(e) };
64
- }), g = countryCodeEmoji;
69
+ }, f = MasksFullMap(o$1);
70
+ MasksFull(o$1);
71
+ const k = countryCodeEmoji;
72
+ function getNavigatorLang() {
73
+ return "undefined" != typeof navigator && navigator.language || "en";
74
+ }
75
+ function detectCountryFromLocale() {
76
+ try {
77
+ const t2 = getNavigatorLang();
78
+ try {
79
+ const e3 = new Intl.Locale(t2);
80
+ if (e3.region) return e3.region.toUpperCase();
81
+ } catch {
82
+ }
83
+ const e2 = t2.split(/[-_]/);
84
+ if (e2.length > 1) return e2[1]?.toUpperCase() || null;
85
+ } catch {
86
+ }
87
+ return null;
88
+ }
89
+ function hasCountry(t2) {
90
+ const r = f;
91
+ return t2.toUpperCase() in r;
92
+ }
93
+ function getCountry(e2, r) {
94
+ const n2 = MasksFullMap(r), o2 = e2.toUpperCase();
95
+ return o2 in n2 ? { id: o2, ...n2[o2] } : { id: "US", ...n2.US };
96
+ }
97
+ function parseCountryCode(t2, e2) {
98
+ return t2 && hasCountry(t2) ? t2.toUpperCase() : e2 || "";
99
+ }
65
100
  function toArray(t2) {
66
101
  return Array.isArray(t2) ? t2 : [t2];
67
102
  }
@@ -71,492 +106,488 @@ function countPlaceholders(t2) {
71
106
  function removeCountryCodePrefix(t2) {
72
107
  return t2.replace(/^\+\d+\s?/, "");
73
108
  }
74
- function pickMaskVariant(t2, n) {
109
+ function pickMaskVariant(t2, e2) {
75
110
  if (1 === t2.length) return t2[0];
76
- const r = t2.map((t3) => ({ mask: t3, count: countPlaceholders(t3) })), e = r.filter((t3) => t3.count >= n).sort((t3, n2) => t3.count - n2.count);
77
- if (e.length > 0) return e[0].mask;
78
- const o = r.sort((t3, n2) => n2.count - t3.count)[0];
79
- return o ? o.mask : t2[0];
111
+ const r = t2.map((t3) => ({ mask: t3, count: countPlaceholders(t3) })), n2 = r.filter((t3) => t3.count >= e2).sort((t3, e3) => t3.count - e3.count);
112
+ if (n2.length > 0) return n2[0].mask;
113
+ const o2 = r.sort((t3, e3) => e3.count - t3.count)[0];
114
+ return o2 ? o2.mask : t2[0];
80
115
  }
81
- function formatDigitsWithMap(t2, n) {
116
+ function formatDigitsWithMap(t2, e2) {
82
117
  let r = "";
83
- const e = [];
84
- let o = 0;
85
- const c = n.length, a = t2.length;
86
- for (let i2 = 0; i2 < a; i2++) {
87
- const a2 = t2[i2];
88
- if ("#" === a2) {
89
- if (!(o < c)) break;
90
- r += n[o], e.push(o), o++;
118
+ const n2 = [];
119
+ let o2 = 0;
120
+ const a = e2.length, s2 = t2.length;
121
+ for (let c = 0; c < s2; c++) {
122
+ const s3 = t2[c];
123
+ if ("#" === s3) {
124
+ if (!(o2 < a)) break;
125
+ r += e2[o2], n2.push(o2), o2++;
91
126
  } else {
92
- const n2 = -1 !== t2.indexOf("#", i2 + 1) && o < c;
93
- (r.length > 0 || n2) && (r += a2, e.push(-1));
127
+ const e3 = -1 !== t2.indexOf("#", c + 1) && o2 < a;
128
+ (r.length > 0 || e3) && (r += s3, n2.push(-1));
94
129
  }
95
130
  }
96
- return { display: r, map: e };
131
+ return { display: r, map: n2 };
97
132
  }
98
- const CACHE_KEY = "@desource/phone-mask:geo";
99
- const CACHE_EXPIRY_MS = 24 * 60 * 6e4;
100
- const GEO_IP_TIMEOUT = 1500;
101
- const GEO_IP_URL = "https://ipapi.co/json/";
102
- const Delimiters = [" ", "-", "(", ")"];
103
- const NavigationKeys = ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End", "Tab"];
104
- const InvalidPattern = /[^\d\s\-()]/;
105
- const emptyCountry = { id: "", code: "", mask: "", flag: "", name: "" };
106
- function useCountrySelector(usedLocale) {
107
- const isEnLocale = vue.computed(() => usedLocale.value.toLowerCase().startsWith("en"));
108
- const countries = vue.computed(() => isEnLocale.value ? i : MasksFull(usedLocale.value));
109
- const countriesMap = vue.computed(() => isEnLocale.value ? m : MasksFullMap(usedLocale.value));
110
- const selectedId = vue.ref(countries.value[0]?.id || "");
111
- const dropdownOpened = vue.ref(false);
112
- const hasDropdown = vue.ref(true);
113
- const search = vue.ref("");
114
- const focusedIndex = vue.ref(0);
115
- const selected = vue.computed(() => {
116
- const id = selectedId.value;
117
- const found = countriesMap.value[id];
118
- return found ? { id, ...found } : countries.value[0] || emptyCountry;
119
- });
120
- const hasCountry = (id) => {
121
- const _id = id.toUpperCase();
122
- return !!countriesMap.value[_id];
123
- };
124
- const filteredCountries = vue.computed(() => {
125
- const q = search.value.trim().toUpperCase();
126
- if (!q) return countries.value;
127
- const qCodeDigits = q.replace(/\D/g, "");
128
- const isNumericSearch = qCodeDigits.length > 0;
129
- return countries.value.map((c) => {
130
- const nameUpper = c.name.toUpperCase();
131
- const idUpper = c.id.toUpperCase();
132
- const codeDigits = c.code.replace(/\D/g, "");
133
- let score = 0;
134
- if (nameUpper.startsWith(q)) score = 1e3;
135
- else if (nameUpper.includes(q)) score = 500;
136
- if (c.code.startsWith(q)) score += 100;
137
- else if (c.code.includes(q)) score += 50;
138
- if (idUpper === q) score += 200;
139
- else if (idUpper.startsWith(q)) score += 150;
140
- if (isNumericSearch && codeDigits.startsWith(qCodeDigits)) score += 80;
141
- else if (isNumericSearch && codeDigits.includes(qCodeDigits)) score += 40;
142
- return { country: c, score };
143
- }).filter(({ score }) => score > 0).sort((a, b) => {
144
- if (b.score !== a.score) return b.score - a.score;
145
- return a.country.name.localeCompare(b.country.name);
146
- }).map(({ country }) => country);
147
- });
148
- const selectCountry = (id) => {
149
- selectedId.value = id;
150
- closeDropdown();
151
- };
152
- const toggleDropdown = async (searchRef) => {
153
- dropdownOpened.value = !dropdownOpened.value;
154
- if (!dropdownOpened.value) return;
155
- await vue.nextTick();
156
- searchRef.value?.focus({ preventScroll: true });
157
- focusedIndex.value = 0;
158
- };
159
- const closeDropdown = () => {
160
- dropdownOpened.value = false;
161
- };
162
- const focusNextOption = (scrollFn) => {
163
- if (filteredCountries.value.length === 0) return;
164
- focusedIndex.value = Math.min(filteredCountries.value.length - 1, focusedIndex.value + 1);
165
- scrollFn?.();
166
- };
167
- const focusPrevOption = (scrollFn) => {
168
- if (filteredCountries.value.length === 0) return;
169
- focusedIndex.value = Math.max(0, focusedIndex.value - 1);
170
- scrollFn?.();
171
- };
172
- const chooseFocusedOption = () => {
173
- const item = filteredCountries.value[focusedIndex.value];
174
- if (item) selectCountry(item.id);
175
- };
176
- const detectFromLocale = () => {
177
- try {
178
- const lang = navigator.language || "";
179
- try {
180
- const loc = new Intl.Locale(lang);
181
- if (loc.region && hasCountry(loc.region)) return loc.region.toUpperCase();
182
- } catch {
133
+ function filterCountries(t2, e2) {
134
+ const r = e2.trim().toUpperCase();
135
+ if (!r) return t2;
136
+ const n2 = r.replace(/\D/g, ""), o2 = n2.length > 0;
137
+ return t2.map((t3) => {
138
+ const e3 = t3.name.toUpperCase(), a = t3.id.toUpperCase(), s2 = t3.code.toUpperCase(), c = t3.code.replace(/\D/g, "");
139
+ let i2 = 0;
140
+ return e3.startsWith(r) ? i2 = 1e3 : e3.includes(r) && (i2 = 500), s2.startsWith(r) ? i2 += 100 : s2.includes(r) && (i2 += 50), a === r ? i2 += 200 : a.startsWith(r) && (i2 += 150), o2 && c.startsWith(n2) ? i2 += 80 : o2 && c.includes(n2) && (i2 += 40), { country: t3, score: i2 };
141
+ }).filter((t3) => t3.score > 0).sort((t3, e3) => e3.score !== t3.score ? e3.score - t3.score : t3.country.name.localeCompare(e3.country.name)).map((t3) => t3.country);
142
+ }
143
+ const t = [" ", "-", "(", ")"], e$1 = ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End", "Tab"], i = /[^\d\s\-()]/;
144
+ function extractDigits(t2, e2) {
145
+ const i2 = t2.replace(/\D/g, "");
146
+ return e2 ? i2.slice(0, e2) : i2;
147
+ }
148
+ function getSelection(t2) {
149
+ return t2 ? [t2.selectionStart ?? 0, t2.selectionEnd ?? 0] : [0, 0];
150
+ }
151
+ function setCaret(t2, e2) {
152
+ if (t2) try {
153
+ t2.setSelectionRange(e2, e2);
154
+ } catch {
155
+ }
156
+ }
157
+ function processBeforeInput(t2) {
158
+ if (!t2.target) return;
159
+ const e2 = t2.target, n2 = t2.data;
160
+ "insertText" === t2.inputType && n2 && (i.test(n2) || " " === n2 && e2.value.endsWith(" ")) && t2.preventDefault();
161
+ }
162
+ function processInput(t2, e2) {
163
+ if (!t2.target) return;
164
+ const i2 = t2.target, { formatter: n2 } = e2, r = n2.getMaxDigits(), s2 = extractDigits(i2.value, r);
165
+ return { newDigits: s2, caretDigitIndex: s2.length };
166
+ }
167
+ function processKeydown(i2, n2) {
168
+ if (!i2.target) return;
169
+ const r = i2.target, { digits: s2, formatter: c } = n2;
170
+ if (i2.ctrlKey || i2.metaKey || i2.altKey || e$1.includes(i2.key)) return;
171
+ const [g, a] = getSelection(r);
172
+ if ("Backspace" !== i2.key) if ("Delete" !== i2.key) /^[0-9]$/.test(i2.key) ? s2.length >= c.getMaxDigits() && i2.preventDefault() : 1 === i2.key.length && i2.preventDefault();
173
+ else {
174
+ if (i2.preventDefault(), g !== a) {
175
+ const t2 = c.getDigitRange(s2, g, a);
176
+ if (t2) {
177
+ const [e2, i3] = t2;
178
+ return { newDigits: s2.slice(0, e2) + s2.slice(i3), caretDigitIndex: e2 };
183
179
  }
184
- const parts = lang.split(/[-_]/);
185
- if (parts.length > 1 && hasCountry(parts[1])) return parts[1].toUpperCase();
186
- } catch {
187
180
  }
188
- return null;
189
- };
190
- const detectByGeoIp = async () => {
191
- try {
192
- const cached = localStorage.getItem(CACHE_KEY);
193
- if (cached) {
194
- const parsed = JSON.parse(cached);
195
- const isExpired = Date.now() - parsed.ts > CACHE_EXPIRY_MS;
196
- if (!isExpired && parsed.country_code && hasCountry(parsed.country_code)) {
197
- return parsed.country_code.toUpperCase();
198
- }
199
- if (isExpired) {
200
- localStorage.removeItem(CACHE_KEY);
201
- }
181
+ if (g < r.value.length) {
182
+ const t2 = c.getDigitRange(s2, g, r.value.length);
183
+ if (t2) {
184
+ const [e2] = t2;
185
+ return { newDigits: s2.slice(0, e2) + s2.slice(e2 + 1), caretDigitIndex: e2 };
202
186
  }
203
- } catch {
204
187
  }
205
- const controller = new AbortController();
206
- const timeoutId = setTimeout(() => controller.abort(), GEO_IP_TIMEOUT);
207
- try {
208
- const res = await fetch(GEO_IP_URL, {
209
- signal: controller.signal,
210
- headers: { Accept: "application/json" }
211
- });
212
- if (!res.ok) {
213
- return null;
188
+ }
189
+ else {
190
+ if (i2.preventDefault(), g !== a) {
191
+ const t2 = c.getDigitRange(s2, g, a);
192
+ if (t2) {
193
+ const [e2, i3] = t2;
194
+ return { newDigits: s2.slice(0, e2) + s2.slice(i3), caretDigitIndex: e2 };
214
195
  }
215
- const json = await res.json();
216
- const code = (json.country || json.country_code || json.countryCode || json.country_code2 || "").toString().toUpperCase();
217
- if (code && hasCountry(code)) {
218
- try {
219
- localStorage.setItem(CACHE_KEY, JSON.stringify({ country_code: code, ts: Date.now() }));
220
- } catch {
196
+ }
197
+ if (g > 0) {
198
+ const e2 = r.value;
199
+ let i3 = g - 1;
200
+ for (; i3 >= 0 && t.includes(e2[i3]); ) i3--;
201
+ if (i3 >= 0) {
202
+ const t2 = c.getDigitRange(s2, i3, i3 + 1);
203
+ if (t2) {
204
+ const [e3] = t2;
205
+ return { newDigits: s2.slice(0, e3) + s2.slice(e3 + 1), caretDigitIndex: e3 };
221
206
  }
222
- return code;
223
207
  }
224
- } catch {
225
- } finally {
226
- clearTimeout(timeoutId);
227
208
  }
228
- return null;
229
- };
230
- const selectInitialCountry = (id, emitFn) => {
231
- const previousId = selectedId.value;
232
- selectedId.value = id;
233
- if (previousId !== selectedId.value && emitFn) vue.nextTick(emitFn);
234
- };
235
- const initCountry = async (predefined, detect, emitFn) => {
236
- hasDropdown.value = !predefined && countries.value.length > 1;
237
- if (predefined && hasCountry(predefined)) {
238
- selectInitialCountry(predefined.toUpperCase(), emitFn);
239
- return;
240
- }
241
- if (!detect) return;
242
- const geo = await detectByGeoIp();
243
- if (geo) {
244
- selectInitialCountry(geo, emitFn);
245
- return;
246
- }
247
- const loc = detectFromLocale();
248
- if (loc) {
249
- selectInitialCountry(loc, emitFn);
250
- return;
209
+ }
210
+ }
211
+ function processPaste(t2, e2) {
212
+ if (!t2.target) return;
213
+ t2.preventDefault();
214
+ const i2 = t2.target, { digits: n2, formatter: r } = e2, s2 = t2.clipboardData?.getData("text") || "", c = r.getMaxDigits(), g = extractDigits(s2, c);
215
+ if (0 === g.length) return;
216
+ const [a, o2] = getSelection(i2);
217
+ if (a !== o2) {
218
+ const t3 = r.getDigitRange(n2, a, o2);
219
+ if (t3) {
220
+ const [e3, i3] = t3;
221
+ return { newDigits: extractDigits(n2.slice(0, e3) + g + n2.slice(i3), c), caretDigitIndex: e3 + g.length };
251
222
  }
252
- };
253
- return {
254
- countries,
255
- selectedId,
256
- selected,
257
- hasCountry,
258
- // Dropdown
259
- hasDropdown,
260
- dropdownOpened,
261
- search,
262
- focusedIndex,
263
- filteredCountries,
264
- selectCountry,
265
- toggleDropdown,
266
- closeDropdown,
267
- focusNextOption,
268
- focusPrevOption,
269
- chooseFocusedOption,
270
- // Country Detection
271
- initCountry
272
- };
223
+ }
224
+ const l = r.getDigitRange(n2, 0, a), f2 = l ? l[1] : 0;
225
+ return { newDigits: extractDigits(n2.slice(0, f2) + g + n2.slice(f2), c), caretDigitIndex: f2 + g.length };
273
226
  }
274
- function createPhoneFormatter(country) {
275
- const variants = toArray(country.mask);
276
- const variantsDigits = variants.map((m2) => countPlaceholders(removeCountryCodePrefix(m2)));
277
- const maxDigits = Math.max(...variantsDigits);
278
- const getMask = (digitLength) => {
279
- const mask = pickMaskVariant(variants, digitLength);
280
- return removeCountryCodePrefix(mask);
227
+ function createPhoneFormatter(o2) {
228
+ const a = toArray(o2.mask), i2 = a.map((n2) => countPlaceholders(removeCountryCodePrefix(n2))), g = Math.max(...i2), getMask = (t2) => {
229
+ const n2 = pickMaskVariant(a, t2);
230
+ return removeCountryCodePrefix(n2);
281
231
  };
282
- return {
283
- formatDisplay: (digits) => {
284
- const template = getMask(digits.length);
285
- return formatDigitsWithMap(template, digits).display;
286
- },
287
- getMaxDigits: () => maxDigits,
288
- getPlaceholder: () => {
289
- const template = getMask(0);
290
- return template;
291
- },
292
- getCaretPosition: (digitIndex) => {
293
- const template = getMask(digitIndex);
294
- const { display, map } = formatDigitsWithMap(template, "0".repeat(digitIndex));
295
- for (let i2 = 0; i2 < map.length; i2++) {
296
- if (map[i2] === digitIndex) return i2;
297
- }
298
- if (digitIndex >= map.length) return display.length;
299
- for (let i2 = 0; i2 < map.length; i2++) {
300
- if (map[i2] > digitIndex) return i2;
301
- }
302
- return display.length;
303
- },
304
- getDigitRange: (digits, selStart, selEnd) => {
305
- const template = getMask(digits.length);
306
- const { map } = formatDigitsWithMap(template, digits);
307
- let min = Infinity;
308
- let max = -Infinity;
309
- for (let i2 = selStart; i2 < selEnd && i2 < map.length; i2++) {
310
- const digitIdx = map[i2];
311
- if (digitIdx !== void 0 && digitIdx >= 0) {
312
- min = Math.min(min, digitIdx);
313
- max = Math.max(max, digitIdx);
314
- }
315
- }
316
- return min === Infinity ? null : [min, max + 1];
317
- },
318
- isComplete: (digits) => {
319
- return variantsDigits.includes(digits.length);
232
+ return { formatDisplay: (t2) => {
233
+ const e2 = getMask(t2.length);
234
+ return formatDigitsWithMap(e2, t2).display;
235
+ }, getMaxDigits: () => g, getPlaceholder: () => getMask(0), getCaretPosition: (t2) => {
236
+ const e2 = getMask(t2), { display: r, map: l } = formatDigitsWithMap(e2, "0".repeat(t2));
237
+ for (let e3 = 0; e3 < l.length; e3++) if (l[e3] === t2) return e3;
238
+ if (t2 >= l.length) return r.length;
239
+ for (let e3 = 0; e3 < l.length; e3++) if (l[e3] > t2) return e3;
240
+ return r.length;
241
+ }, getDigitRange: (t2, e2, r) => {
242
+ const l = getMask(t2.length), { map: o3 } = formatDigitsWithMap(l, t2);
243
+ let a2 = 1 / 0, i3 = -1 / 0;
244
+ for (let t3 = e2; t3 < r && t3 < o3.length; t3++) {
245
+ const e3 = o3[t3];
246
+ void 0 !== e3 && e3 >= 0 && (a2 = Math.min(a2, e3), i3 = Math.max(i3, e3));
320
247
  }
321
- };
248
+ return a2 === 1 / 0 ? null : [a2, i3 + 1];
249
+ }, isComplete: (t2) => i2.includes(t2.length) };
322
250
  }
323
- function setCaret(el, position) {
324
- if (!el) return;
251
+ const o = "https://ipapi.co/json/", e = 1500, p = "@desource/phone-mask:geo", s = 864e5;
252
+ async function detectCountryFromGeoIP(t2 = o, r = e) {
253
+ const n2 = new AbortController(), c = setTimeout(() => n2.abort(), r);
325
254
  try {
326
- el.setSelectionRange(position, position);
255
+ const o2 = await fetch(t2, { signal: n2.signal, headers: { Accept: "application/json" } });
256
+ if (clearTimeout(c), !o2.ok) return null;
257
+ const e2 = await o2.json();
258
+ return (e2.country || e2.country_code || e2.countryCode || e2.country_code2 || "").toString().toUpperCase() || null;
327
259
  } catch {
260
+ return clearTimeout(c), null;
328
261
  }
329
262
  }
330
- function extractDigits(value, maxLength) {
331
- const digits = value.replace(/\D/g, "");
332
- return maxLength ? digits.slice(0, maxLength) : digits;
263
+ async function detectByGeoIp() {
264
+ try {
265
+ const o3 = localStorage.getItem(p);
266
+ if (o3) {
267
+ const e3 = JSON.parse(o3), c = Date.now() - e3.ts > s, a = parseCountryCode(e3.country_code);
268
+ if (a && !c) return a;
269
+ localStorage.removeItem(p);
270
+ }
271
+ } catch {
272
+ }
273
+ const o2 = await detectCountryFromGeoIP(), e2 = parseCountryCode(o2);
274
+ if (e2) {
275
+ try {
276
+ const t2 = JSON.stringify({ country_code: e2, ts: Date.now() });
277
+ localStorage.setItem(p, t2);
278
+ } catch {
279
+ }
280
+ return e2;
281
+ }
282
+ return null;
333
283
  }
334
- function getSelection(el) {
335
- if (!el) return [0, 0];
336
- return [el.selectionStart ?? 0, el.selectionEnd ?? 0];
284
+ function useCountry({
285
+ country: countryOption,
286
+ locale: localeOption,
287
+ detect,
288
+ onCountryChange
289
+ } = {}) {
290
+ const locale = vue.computed(() => vue.toValue(localeOption) || getNavigatorLang());
291
+ const countryCode = vue.ref(parseCountryCode(vue.toValue(countryOption), "US"));
292
+ const country = vue.computed(() => getCountry(countryCode.value, locale.value));
293
+ const setCountry = (code) => {
294
+ const parsed = parseCountryCode(code);
295
+ if (parsed) {
296
+ countryCode.value = parsed;
297
+ return true;
298
+ }
299
+ return false;
300
+ };
301
+ const detectCountry = async () => {
302
+ const geoCountry = await detectByGeoIp();
303
+ if (setCountry(geoCountry)) return;
304
+ const localeCountry = detectCountryFromLocale();
305
+ setCountry(localeCountry);
306
+ };
307
+ vue.watchEffect(() => {
308
+ const newCountry = vue.toValue(countryOption);
309
+ if (newCountry && newCountry !== countryCode.value) {
310
+ setCountry(newCountry);
311
+ }
312
+ });
313
+ vue.watchEffect(() => {
314
+ if (vue.toValue(detect) && !vue.toValue(countryOption)) {
315
+ detectCountry();
316
+ }
317
+ });
318
+ vue.watchEffect(() => {
319
+ onCountryChange?.(country.value);
320
+ });
321
+ return { country, setCountry, locale };
337
322
  }
338
- function useMask(selected, telRef) {
339
- const digits = vue.ref("");
340
- const displayValue = vue.ref("");
341
- const validationTimer = vue.ref(null);
342
- const showValidationHint = vue.ref(false);
343
- const formatter = vue.computed(() => createPhoneFormatter(selected.value));
323
+ function useFormatter({
324
+ country,
325
+ value,
326
+ onChange,
327
+ onPhoneChange,
328
+ onValidationChange
329
+ }) {
330
+ const formatter = vue.computed(() => createPhoneFormatter(vue.toValue(country)));
331
+ const maxDigits = vue.computed(() => formatter.value.getMaxDigits());
332
+ const digits = vue.computed(() => extractDigits(vue.toValue(value), maxDigits.value));
344
333
  const displayPlaceholder = vue.computed(() => formatter.value.getPlaceholder());
334
+ const displayValue = vue.computed(() => formatter.value.formatDisplay(digits.value));
335
+ const full = vue.computed(() => digits.value ? `${vue.toValue(country).code}${digits.value}` : "");
336
+ const fullFormatted = vue.computed(() => displayValue.value ? `${vue.toValue(country).code} ${displayValue.value}` : "");
345
337
  const isComplete = vue.computed(() => formatter.value.isComplete(digits.value));
346
338
  const isEmpty = vue.computed(() => digits.value.length === 0);
347
- const maxDigits = vue.computed(() => formatter.value.getMaxDigits());
348
- const shouldShowWarn = vue.computed(() => showValidationHint.value && !isEmpty.value && !isComplete.value);
349
- const fullFormatted = vue.computed(() => {
350
- if (!displayValue.value) return "";
351
- return `${selected.value.code} ${displayValue.value}`;
339
+ const shouldShowWarn = vue.computed(() => !isEmpty.value && !isComplete.value);
340
+ const phoneData = vue.computed(() => ({
341
+ full: full.value,
342
+ fullFormatted: fullFormatted.value,
343
+ digits: digits.value
344
+ }));
345
+ vue.watchEffect(() => {
346
+ if (vue.toValue(value) !== digits.value) {
347
+ onChange(digits.value);
348
+ }
352
349
  });
353
- const full = vue.computed(() => {
354
- if (!digits.value) return "";
355
- return `${selected.value.code}${digits.value}`;
350
+ vue.watchEffect(() => {
351
+ onPhoneChange?.(phoneData.value);
356
352
  });
357
- const updateDisplay2 = () => {
358
- displayValue.value = formatter.value.formatDisplay(digits.value);
353
+ vue.watchEffect(() => {
354
+ onValidationChange?.(isComplete.value);
355
+ });
356
+ return {
357
+ digits,
358
+ formatter,
359
+ displayPlaceholder,
360
+ displayValue,
361
+ full,
362
+ fullFormatted,
363
+ isComplete,
364
+ isEmpty,
365
+ shouldShowWarn
359
366
  };
360
- const setCaretToDigitPosition = (digitIndex) => {
361
- const pos = formatter.value.getCaretPosition(digitIndex);
362
- setCaret(telRef.value, pos);
367
+ }
368
+ function useTimer() {
369
+ let timerRef = null;
370
+ const clear = () => {
371
+ if (timerRef) {
372
+ clearTimeout(timerRef);
373
+ timerRef = null;
374
+ }
363
375
  };
364
- const removeDigitsRange = (startIdx, endIdx) => {
365
- if (startIdx >= endIdx) return;
366
- digits.value = digits.value.slice(0, startIdx) + digits.value.slice(endIdx);
376
+ const set = (callback, delay) => {
377
+ clear();
378
+ timerRef = setTimeout(callback, delay);
367
379
  };
368
- const handleBeforeInput = (e) => {
369
- const el = e.target;
370
- if (!el) return;
371
- const data = e.data;
372
- if (e.inputType !== "insertText" || !data) return;
373
- if (InvalidPattern.test(data) || data === " " && el.value.endsWith(" ")) {
374
- e.preventDefault();
375
- }
380
+ vue.onUnmounted(clear);
381
+ return { set, clear };
382
+ }
383
+ function useValidationHint() {
384
+ const showValidationHint = vue.ref(false);
385
+ const validationTimer = useTimer();
386
+ const clearValidationHint = (hideHint = true) => {
387
+ if (hideHint) showValidationHint.value = false;
388
+ validationTimer.clear();
376
389
  };
377
- const handleInput = (e) => {
378
- const el = e.target;
379
- if (!el) return;
380
- const newDigits = extractDigits(el.value, maxDigits.value);
390
+ const scheduleValidationHint = (delay) => {
381
391
  showValidationHint.value = false;
382
- if (validationTimer.value) {
383
- clearTimeout(validationTimer.value);
384
- }
385
- digits.value = newDigits;
386
- updateDisplay2();
387
- if (newDigits.length > 0) {
388
- validationTimer.value = setTimeout(() => {
389
- showValidationHint.value = true;
390
- }, 500);
391
- }
392
+ validationTimer.set(() => {
393
+ showValidationHint.value = true;
394
+ }, delay);
395
+ };
396
+ return { showValidationHint, clearValidationHint, scheduleValidationHint };
397
+ }
398
+ const HINT_DELAY_INPUT = 500;
399
+ const HINT_DELAY_ACTION = 300;
400
+ function useInputHandlers(options) {
401
+ const { formatter, digits, inactive, onChange, scheduleValidationHint } = options;
402
+ const scheduleCaretUpdate = (el, digitIndex) => {
392
403
  vue.nextTick(() => {
393
- setCaretToDigitPosition(digits.value.length);
404
+ if (!el) return;
405
+ const pos = vue.toValue(formatter).getCaretPosition(digitIndex);
406
+ setCaret(el, pos);
394
407
  });
395
408
  };
396
- const handleKeydownInternal = (e) => {
397
- const el = telRef.value ?? e.target;
398
- if (!el) return;
399
- if (e.ctrlKey || e.metaKey || e.altKey || NavigationKeys.includes(e.key)) return;
400
- const [selStart, selEnd] = getSelection(el);
401
- if (e.key === "Backspace") {
402
- e.preventDefault();
403
- if (selStart !== selEnd) {
404
- const range = formatter.value.getDigitRange(digits.value, selStart, selEnd);
405
- if (range) {
406
- const [start, end] = range;
407
- removeDigitsRange(start, end);
408
- updateDisplay2();
409
- vue.nextTick(() => setCaretToDigitPosition(start));
410
- }
411
- return;
412
- }
413
- if (selStart > 0) {
414
- const displayStr = displayValue.value;
415
- let prevPos = selStart - 1;
416
- while (prevPos >= 0 && Delimiters.includes(displayStr[prevPos])) {
417
- prevPos--;
418
- }
419
- if (prevPos >= 0) {
420
- const range = formatter.value.getDigitRange(digits.value, prevPos, prevPos + 1);
421
- if (range) {
422
- const [start] = range;
423
- removeDigitsRange(start, start + 1);
424
- updateDisplay2();
425
- vue.nextTick(() => setCaretToDigitPosition(start));
426
- }
427
- }
428
- }
429
- return;
430
- }
431
- if (e.key === "Delete") {
432
- e.preventDefault();
433
- if (selStart !== selEnd) {
434
- const range = formatter.value.getDigitRange(digits.value, selStart, selEnd);
435
- if (range) {
436
- const [start, end] = range;
437
- removeDigitsRange(start, end);
438
- updateDisplay2();
439
- vue.nextTick(() => setCaretToDigitPosition(start));
440
- }
441
- return;
442
- }
443
- if (selStart < displayValue.value.length) {
444
- const range = formatter.value.getDigitRange(digits.value, selStart, selStart + 1);
445
- if (range) {
446
- const [start] = range;
447
- removeDigitsRange(start, start + 1);
448
- updateDisplay2();
449
- vue.nextTick(() => setCaretToDigitPosition(start));
450
- }
451
- }
452
- return;
453
- }
454
- if (/^[0-9]$/.test(e.key)) {
455
- if (digits.value.length >= maxDigits.value) {
456
- e.preventDefault();
457
- }
458
- return;
459
- }
460
- if (e.key.length === 1) {
461
- e.preventDefault();
462
- }
409
+ const handleBeforeInput = (e2) => {
410
+ processBeforeInput(e2);
463
411
  };
464
- const handleKeydown = (e) => {
465
- showValidationHint.value = false;
466
- if (validationTimer.value) {
467
- clearTimeout(validationTimer.value);
412
+ const handleInput = (e2) => {
413
+ if (vue.toValue(inactive)) return;
414
+ const result = processInput(e2, { formatter: vue.toValue(formatter) });
415
+ if (!result) return;
416
+ onChange?.(result.newDigits);
417
+ scheduleCaretUpdate(e2.target, result.caretDigitIndex);
418
+ scheduleValidationHint?.(HINT_DELAY_INPUT);
419
+ };
420
+ const handleKeydown = (e2) => {
421
+ if (vue.toValue(inactive)) return;
422
+ const result = processKeydown(e2, { digits: vue.toValue(digits), formatter: vue.toValue(formatter) });
423
+ if (!result) return;
424
+ onChange?.(result.newDigits);
425
+ scheduleCaretUpdate(e2.target, result.caretDigitIndex);
426
+ scheduleValidationHint?.(HINT_DELAY_ACTION);
427
+ };
428
+ const handlePaste = (e2) => {
429
+ if (vue.toValue(inactive)) return;
430
+ const result = processPaste(e2, { digits: vue.toValue(digits), formatter: vue.toValue(formatter) });
431
+ if (!result) return;
432
+ onChange?.(result.newDigits);
433
+ scheduleCaretUpdate(e2.target, result.caretDigitIndex);
434
+ scheduleValidationHint?.(HINT_DELAY_ACTION);
435
+ };
436
+ return {
437
+ handleBeforeInput,
438
+ handleInput,
439
+ handleKeydown,
440
+ handlePaste
441
+ };
442
+ }
443
+ function useCountrySelector({
444
+ rootRef,
445
+ dropdownRef,
446
+ searchRef,
447
+ selectorRef,
448
+ locale,
449
+ countryOption,
450
+ inactive,
451
+ onSelectCountry,
452
+ onAfterSelect
453
+ }) {
454
+ const search = vue.ref("");
455
+ const dropdownOpen = vue.ref(false);
456
+ const dropdownStyle = vue.shallowRef({});
457
+ const focusedIndex = vue.ref(0);
458
+ const countries = vue.computed(() => MasksFull(vue.toValue(locale)));
459
+ const filteredCountries = vue.computed(() => filterCountries(countries.value, search.value));
460
+ const hasDropdown = vue.computed(() => !vue.toValue(countryOption) && countries.value.length > 1);
461
+ const setFocusedIndex = (index2) => {
462
+ focusedIndex.value = index2;
463
+ };
464
+ const focusSearch = () => {
465
+ vue.nextTick(() => searchRef.value?.focus({ preventScroll: true }));
466
+ };
467
+ const closeDropdown = () => {
468
+ dropdownOpen.value = false;
469
+ };
470
+ const openDropdown = () => {
471
+ dropdownOpen.value = true;
472
+ setFocusedIndex(0);
473
+ focusSearch();
474
+ };
475
+ const toggleDropdown = () => {
476
+ if (vue.toValue(inactive) || !hasDropdown.value) return;
477
+ if (dropdownOpen.value) {
478
+ closeDropdown();
479
+ } else {
480
+ openDropdown();
468
481
  }
469
- handleKeydownInternal(e);
470
- if (validationTimer.value) clearTimeout(validationTimer.value);
471
- validationTimer.value = setTimeout(() => {
472
- if (!isComplete.value && !isEmpty.value) showValidationHint.value = true;
473
- }, 300);
474
482
  };
475
- const handlePaste = (e) => {
476
- e.preventDefault();
477
- const text = e.clipboardData?.getData("text") || "";
478
- const pastedDigits = extractDigits(text, maxDigits.value);
479
- if (pastedDigits.length === 0) return;
480
- const el = telRef.value;
481
- if (!el) return;
482
- const [selStart, selEnd] = getSelection(el);
483
- if (selStart !== selEnd) {
484
- const range2 = formatter.value.getDigitRange(digits.value, selStart, selEnd);
485
- if (range2) {
486
- const [start, end] = range2;
487
- const left2 = digits.value.slice(0, start);
488
- const right2 = digits.value.slice(end);
489
- digits.value = extractDigits(left2 + pastedDigits + right2, maxDigits.value);
490
- updateDisplay2();
491
- vue.nextTick(() => setCaretToDigitPosition(start + pastedDigits.length));
483
+ const selectCountry = (code) => {
484
+ onSelectCountry(code);
485
+ closeDropdown();
486
+ search.value = "";
487
+ setFocusedIndex(0);
488
+ onAfterSelect?.();
489
+ };
490
+ const handleSearchChange = (e2) => {
491
+ search.value = e2.target.value;
492
+ setFocusedIndex(0);
493
+ };
494
+ const onDocClick = (ev) => {
495
+ const target = ev.target;
496
+ const dropdownEl = dropdownRef.value;
497
+ const selectorEl = selectorRef.value;
498
+ if (!target) return;
499
+ if (dropdownEl?.contains(target)) return;
500
+ if (selectorEl?.contains(target)) return;
501
+ closeDropdown();
502
+ };
503
+ const positionDropdown = (e2) => {
504
+ if (e2?.type === "scroll" && e2.target && dropdownRef.value?.contains(e2.target)) return;
505
+ if (!rootRef.value) return;
506
+ const rect = rootRef.value.getBoundingClientRect();
507
+ dropdownStyle.value = {
508
+ top: `${rect.bottom + window.scrollY + 8}px`,
509
+ left: `${rect.left + window.scrollX}px`,
510
+ width: `${rect.width}px`
511
+ };
512
+ };
513
+ const scrollFocusedIntoView = () => {
514
+ vue.nextTick(() => {
515
+ const list = dropdownRef.value?.lastElementChild;
516
+ const option = list?.children[focusedIndex.value];
517
+ if (!list || !option) return;
518
+ const listRect = list.getBoundingClientRect();
519
+ const optionRect = option.getBoundingClientRect();
520
+ let scrollAmount = 0;
521
+ if (optionRect.top < listRect.top) {
522
+ scrollAmount = list.scrollTop - (listRect.top - optionRect.top);
523
+ } else if (optionRect.bottom > listRect.bottom) {
524
+ scrollAmount = list.scrollTop + (optionRect.bottom - listRect.bottom);
525
+ } else {
492
526
  return;
493
527
  }
494
- }
495
- const range = formatter.value.getDigitRange(digits.value, selStart, selStart);
496
- const insertIndex = range ? range[0] : digits.value.length;
497
- const left = digits.value.slice(0, insertIndex);
498
- const right = digits.value.slice(insertIndex);
499
- digits.value = extractDigits(left + pastedDigits + right, maxDigits.value);
500
- updateDisplay2();
501
- if (validationTimer.value) clearTimeout(validationTimer.value);
502
- validationTimer.value = setTimeout(() => {
503
- if (!isComplete.value && !isEmpty.value) showValidationHint.value = true;
504
- }, 300);
505
- vue.nextTick(() => setCaretToDigitPosition(insertIndex + pastedDigits.length));
528
+ list.scrollTo({ top: scrollAmount, behavior: "smooth" });
529
+ });
506
530
  };
507
- const handleFocus = () => {
508
- if (validationTimer.value) {
509
- clearTimeout(validationTimer.value);
531
+ const handleSearchKeydown = (e2) => {
532
+ if (e2.key === "ArrowDown") {
533
+ e2.preventDefault();
534
+ setFocusedIndex(Math.min(focusedIndex.value + 1, filteredCountries.value.length - 1));
535
+ scrollFocusedIntoView();
536
+ } else if (e2.key === "ArrowUp") {
537
+ e2.preventDefault();
538
+ setFocusedIndex(Math.max(focusedIndex.value - 1, 0));
539
+ scrollFocusedIntoView();
540
+ } else if (e2.key === "Enter" && filteredCountries.value[focusedIndex.value]) {
541
+ e2.preventDefault();
542
+ selectCountry(filteredCountries.value[focusedIndex.value].id);
543
+ } else if (e2.key === "Escape") {
544
+ closeDropdown();
510
545
  }
511
546
  };
512
- const clear = () => {
513
- digits.value = "";
514
- displayValue.value = "";
515
- showValidationHint.value = false;
516
- if (validationTimer.value) {
517
- clearTimeout(validationTimer.value);
518
- validationTimer.value = null;
519
- }
547
+ const removeListeners = () => {
548
+ window.removeEventListener("resize", positionDropdown);
549
+ window.removeEventListener("scroll", positionDropdown, true);
550
+ window.removeEventListener("click", onDocClick, true);
520
551
  };
521
- vue.watch(selected, () => {
522
- if (digits.value.length > maxDigits.value) {
523
- digits.value = digits.value.slice(0, maxDigits.value);
552
+ vue.watch(hasDropdown, (dropdownExists) => {
553
+ if (!dropdownExists && dropdownOpen.value) {
554
+ closeDropdown();
555
+ }
556
+ });
557
+ vue.watch(dropdownOpen, (isOpen) => {
558
+ if (!isOpen) {
559
+ removeListeners();
560
+ return;
524
561
  }
525
- updateDisplay2();
562
+ positionDropdown();
563
+ window.addEventListener("resize", positionDropdown);
564
+ window.addEventListener("scroll", positionDropdown, true);
565
+ window.addEventListener("click", onDocClick, true);
526
566
  });
527
- updateDisplay2();
567
+ vue.onBeforeUnmount(removeListeners);
528
568
  return {
529
569
  // State
530
- digits,
531
- displayValue,
532
- // Computed
533
- displayPlaceholder,
534
- isComplete,
535
- isEmpty,
536
- shouldShowWarn,
537
- fullFormatted,
538
- full,
539
- // Handlers
540
- handleBeforeInput,
541
- handleInput,
542
- handleKeydown,
543
- handlePaste,
544
- handleFocus,
545
- // Methods
546
- updateDisplayFromDigits: updateDisplay2,
547
- clear
570
+ dropdownOpen,
571
+ search,
572
+ focusedIndex,
573
+ dropdownStyle,
574
+ // Derived
575
+ filteredCountries,
576
+ hasDropdown,
577
+ // Actions
578
+ openDropdown,
579
+ closeDropdown,
580
+ toggleDropdown,
581
+ selectCountry,
582
+ setFocusedIndex,
583
+ handleSearchChange,
584
+ handleSearchKeydown
548
585
  };
549
586
  }
550
- function useClipboard() {
587
+ function useClipboard(delay = 1800) {
551
588
  const copied = vue.ref(false);
552
589
  const isCopying = vue.ref(false);
553
- let copyTimer = null;
554
- const clearTimer = () => {
555
- if (copyTimer) {
556
- clearTimeout(copyTimer);
557
- copyTimer = null;
558
- }
559
- };
590
+ const copyTimer = useTimer();
560
591
  const copy = async (text) => {
561
592
  if (isCopying.value) return false;
562
593
  const trimmedText = text.trim();
@@ -565,11 +596,9 @@ function useClipboard() {
565
596
  try {
566
597
  await navigator.clipboard.writeText(trimmedText);
567
598
  copied.value = true;
568
- clearTimer();
569
- copyTimer = setTimeout(() => {
599
+ copyTimer.set(() => {
570
600
  copied.value = false;
571
- copyTimer = null;
572
- }, 1800);
601
+ }, delay);
573
602
  return true;
574
603
  } catch (err) {
575
604
  console.warn("Copy failed", err);
@@ -578,24 +607,71 @@ function useClipboard() {
578
607
  isCopying.value = false;
579
608
  }
580
609
  };
581
- const onUnmount = () => {
582
- clearTimer();
610
+ return { copied, isCopying, copy };
611
+ }
612
+ const DELAY = 1800;
613
+ function useCopyAction({ liveRef, fullFormatted, onCopy }) {
614
+ const liveTimer = useTimer();
615
+ const { copied, copy } = useClipboard(DELAY);
616
+ const copyAriaLabel = vue.computed(() => copied.value ? "Copied" : `Copy ${fullFormatted.value}`);
617
+ const copyButtonTitle = vue.computed(() => copied.value ? "Copied" : "Copy phone number");
618
+ const announceToScreenReader = (message) => {
619
+ if (!liveRef?.value) return;
620
+ liveRef.value.textContent = message;
621
+ liveTimer.set(() => {
622
+ if (liveRef.value) liveRef.value.textContent = "";
623
+ }, DELAY);
624
+ };
625
+ const onCopyClick = async () => {
626
+ const valueToCopy = fullFormatted.value.trim();
627
+ const success = await copy(valueToCopy);
628
+ if (success) {
629
+ onCopy?.(valueToCopy);
630
+ announceToScreenReader("Phone number copied to clipboard");
631
+ }
632
+ };
633
+ return {
634
+ copied,
635
+ copyAriaLabel,
636
+ copyButtonTitle,
637
+ onCopyClick
638
+ };
639
+ }
640
+ function useTheme({ theme }) {
641
+ const systemDark = vue.ref(false);
642
+ const themeClass = vue.computed(() => {
643
+ return vue.toValue(theme) !== "auto" ? `theme-${vue.toValue(theme)}` : systemDark.value ? "theme-dark" : "theme-light";
644
+ });
645
+ let mq = null;
646
+ const handler = (e2) => {
647
+ systemDark.value = e2.matches;
648
+ };
649
+ vue.onBeforeMount(() => {
650
+ if (typeof window === "undefined") return;
651
+ mq = window.matchMedia?.("(prefers-color-scheme: dark)") ?? null;
652
+ if (!mq) return;
653
+ systemDark.value = mq.matches;
654
+ mq.addEventListener("change", handler);
655
+ });
656
+ vue.onBeforeUnmount(() => {
657
+ mq?.removeEventListener("change", handler);
658
+ });
659
+ return {
660
+ themeClass
583
661
  };
584
- return { copied, isCopying, copy, onUnmount };
585
662
  }
586
- const _hoisted_1 = { class: "pi-selector" };
587
- const _hoisted_2 = ["disabled", "tabindex", "aria-label", "aria-expanded", "aria-haspopup"];
588
- const _hoisted_3 = ["aria-label"];
589
- const _hoisted_4 = { class: "pi-code" };
590
- const _hoisted_5 = { class: "pi-input-wrap" };
591
- const _hoisted_6 = ["placeholder", "value", "disabled", "readonly", "aria-invalid"];
592
- const _hoisted_7 = {
663
+ const _hoisted_1 = ["disabled", "tabindex", "aria-label", "aria-expanded", "aria-haspopup"];
664
+ const _hoisted_2 = ["aria-label"];
665
+ const _hoisted_3 = { class: "pi-code" };
666
+ const _hoisted_4 = { class: "pi-input-wrap" };
667
+ const _hoisted_5 = ["placeholder", "value", "disabled", "readonly", "aria-invalid"];
668
+ const _hoisted_6 = {
593
669
  class: "pi-actions",
594
670
  role: "toolbar",
595
671
  "aria-label": "Phone input actions"
596
672
  };
597
- const _hoisted_8 = ["aria-label", "title"];
598
- const _hoisted_9 = {
673
+ const _hoisted_7 = ["aria-label", "title"];
674
+ const _hoisted_8 = {
599
675
  key: 1,
600
676
  width: "16",
601
677
  height: "16",
@@ -603,7 +679,7 @@ const _hoisted_9 = {
603
679
  fill: "none",
604
680
  "aria-hidden": "true"
605
681
  };
606
- const _hoisted_10 = {
682
+ const _hoisted_9 = {
607
683
  key: 2,
608
684
  width: "16",
609
685
  height: "16",
@@ -611,8 +687,8 @@ const _hoisted_10 = {
611
687
  fill: "none",
612
688
  "aria-hidden": "true"
613
689
  };
614
- const _hoisted_11 = ["aria-label", "title"];
615
- const _hoisted_12 = {
690
+ const _hoisted_10 = ["aria-label", "title"];
691
+ const _hoisted_11 = {
616
692
  key: 0,
617
693
  width: "11",
618
694
  height: "11",
@@ -620,14 +696,14 @@ const _hoisted_12 = {
620
696
  fill: "none",
621
697
  "aria-hidden": "true"
622
698
  };
623
- const _hoisted_13 = { class: "pi-search-wrap" };
624
- const _hoisted_14 = ["placeholder"];
625
- const _hoisted_15 = ["aria-activedescendant"];
626
- const _hoisted_16 = ["id", "aria-selected", "title", "onClick", "onMouseenter"];
627
- const _hoisted_17 = ["aria-label"];
628
- const _hoisted_18 = { class: "pi-opt-name" };
629
- const _hoisted_19 = { class: "pi-opt-code" };
630
- const _hoisted_20 = {
699
+ const _hoisted_12 = { class: "pi-search-wrap" };
700
+ const _hoisted_13 = ["value", "placeholder"];
701
+ const _hoisted_14 = ["aria-activedescendant"];
702
+ const _hoisted_15 = ["id", "aria-selected", "title", "onClick", "onMouseenter"];
703
+ const _hoisted_16 = ["aria-label"];
704
+ const _hoisted_17 = { class: "pi-opt-name" };
705
+ const _hoisted_18 = { class: "pi-opt-code" };
706
+ const _hoisted_19 = {
631
707
  key: 0,
632
708
  class: "pi-empty"
633
709
  };
@@ -650,242 +726,133 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
650
726
  dropdownClass: {},
651
727
  disableDefaultStyles: { type: Boolean, default: false }
652
728
  }, {
653
- "modelValue": {},
729
+ "modelValue": { default: "" },
654
730
  "modelModifiers": {}
655
731
  }),
656
732
  emits: /* @__PURE__ */ vue.mergeModels(["change", "country-change", "validation-change", "focus", "blur", "copy", "clear"], ["update:modelValue"]),
657
733
  setup(__props, { expose: __expose, emit: __emit }) {
658
734
  const props = __props;
659
735
  const slots = vue.useSlots();
660
- const model = vue.useModel(__props, "modelValue");
661
736
  const emit = __emit;
737
+ const model = vue.useModel(__props, "modelValue");
738
+ const onChange = (v) => {
739
+ model.value = v;
740
+ };
741
+ const { country, setCountry, locale } = useCountry({
742
+ country: () => props.country,
743
+ locale: () => props.locale,
744
+ detect: () => props.detect,
745
+ onCountryChange: (c) => emit("country-change", c)
746
+ });
747
+ const {
748
+ digits,
749
+ formatter,
750
+ displayPlaceholder,
751
+ displayValue,
752
+ full,
753
+ fullFormatted,
754
+ isComplete,
755
+ isEmpty,
756
+ shouldShowWarn
757
+ } = useFormatter({
758
+ country,
759
+ value: model,
760
+ onChange,
761
+ onPhoneChange: (data) => emit("change", data),
762
+ onValidationChange: (complete) => emit("validation-change", complete)
763
+ });
764
+ const { showValidationHint, clearValidationHint, scheduleValidationHint } = useValidationHint();
662
765
  const rootRef = vue.useTemplateRef("rootRef");
663
766
  const telRef = vue.useTemplateRef("telRef");
664
- const searchRef = vue.useTemplateRef("searchRef");
665
767
  const liveRef = vue.useTemplateRef("liveRef");
666
768
  const dropdownRef = vue.useTemplateRef("dropdownRef");
667
- const usedLocale = vue.computed(() => {
668
- if (props.locale) return props.locale;
669
- if (typeof navigator !== "undefined") {
670
- return navigator.language || "en";
671
- }
672
- return "en";
769
+ const searchRef = vue.useTemplateRef("searchRef");
770
+ const selectorRef = vue.useTemplateRef("selectorRef");
771
+ const inactive = vue.computed(() => props.disabled || props.readonly);
772
+ const incomplete = vue.computed(() => showValidationHint.value && shouldShowWarn.value);
773
+ const showCopyButton = vue.computed(() => props.showCopy && !isEmpty.value && !props.disabled);
774
+ const showClearButton = vue.computed(() => props.showClear && !isEmpty.value && !inactive.value);
775
+ const { copied, copyAriaLabel, copyButtonTitle, onCopyClick } = useCopyAction({
776
+ liveRef,
777
+ fullFormatted,
778
+ onCopy: (v) => emit("copy", v)
673
779
  });
674
- const dropdownStyle = vue.shallowRef({});
675
- const countrySelector = useCountrySelector(usedLocale);
780
+ const focusInput = () => vue.nextTick(() => telRef.value?.focus());
676
781
  const {
782
+ dropdownOpen,
677
783
  search,
678
- filteredCountries,
679
784
  focusedIndex,
680
- selected,
681
- dropdownOpened,
785
+ dropdownStyle,
786
+ filteredCountries,
682
787
  hasDropdown,
683
- focusNextOption,
684
- focusPrevOption,
685
- chooseFocusedOption,
686
- closeDropdown
687
- } = countrySelector;
688
- const mask = useMask(selected, telRef);
689
- const { digits, displayValue, displayPlaceholder, isComplete, isEmpty, shouldShowWarn, full, fullFormatted } = mask;
690
- const { copied, copy, onUnmount: onClipboardUnmount } = useClipboard();
691
- const inactive = vue.computed(() => props.disabled || props.readonly);
692
- const showCopyButton = vue.computed(() => props.showCopy && !isEmpty.value && !props.disabled);
693
- const showClearButton = vue.computed(() => props.showClear && !isEmpty.value && !inactive.value);
694
- const copyAriaLabel = vue.computed(() => copied.value ? "Copied" : `Copy ${selected.value.code} ${displayValue.value}`);
695
- const copyButtonTitle = vue.computed(() => copied.value ? "Copied" : "Copy phone number");
696
- const copyMessage = vue.computed(() => copied.value ? "Phone number copied to clipboard" : "");
697
- const sizeClass = vue.computed(() => `size-${props.size}`);
698
- const themeClass = vue.computed(() => {
699
- if (props.theme !== "auto") return `theme-${props.theme}`;
700
- if (typeof window !== "undefined" && window.matchMedia?.("(prefers-color-scheme: dark)").matches) {
701
- return "theme-dark";
702
- }
703
- return "theme-light";
788
+ closeDropdown,
789
+ toggleDropdown,
790
+ selectCountry,
791
+ setFocusedIndex,
792
+ handleSearchChange,
793
+ handleSearchKeydown
794
+ } = useCountrySelector({
795
+ rootRef,
796
+ dropdownRef,
797
+ searchRef,
798
+ selectorRef,
799
+ locale,
800
+ countryOption: () => props.country,
801
+ inactive,
802
+ onSelectCountry: setCountry,
803
+ onAfterSelect: focusInput
804
+ });
805
+ const { handleBeforeInput, handleInput, handleKeydown, handlePaste } = useInputHandlers({
806
+ formatter,
807
+ digits,
808
+ inactive,
809
+ onChange,
810
+ scheduleValidationHint
811
+ });
812
+ const handleFocus = (e2) => {
813
+ clearValidationHint(false);
814
+ closeDropdown();
815
+ emit("focus", e2);
816
+ };
817
+ const handleBlur = (e2) => emit("blur", e2);
818
+ const clear = () => {
819
+ onChange("");
820
+ clearValidationHint();
821
+ emit("clear");
822
+ };
823
+ const onClearClick = () => {
824
+ clear();
825
+ focusInput();
826
+ };
827
+ __expose({
828
+ focus: focusInput,
829
+ blur: () => telRef.value?.blur(),
830
+ clear,
831
+ selectCountry,
832
+ getFullNumber: () => full.value,
833
+ getFullFormattedNumber: () => fullFormatted.value,
834
+ getDigits: () => digits.value,
835
+ isValid: () => isComplete.value,
836
+ isComplete: () => isComplete.value
837
+ });
838
+ const { themeClass } = useTheme({
839
+ theme: () => props.theme
704
840
  });
705
841
  const rootClasses = vue.computed(() => [
706
842
  "phone-input",
707
- sizeClass.value,
843
+ `size-${props.size}`,
708
844
  themeClass.value,
709
845
  {
710
846
  "is-disabled": props.disabled,
711
847
  "is-readonly": props.readonly,
712
848
  "is-unstyled": props.disableDefaultStyles,
713
- "is-incomplete": props.withValidity && shouldShowWarn.value,
849
+ "is-incomplete": props.withValidity && incomplete.value,
714
850
  "is-complete": props.withValidity && isComplete.value
715
851
  }
716
852
  ]);
717
853
  const rootStyles = vue.computed(() => ({
718
854
  "--pi-actions-count": +showCopyButton.value + +showClearButton.value + (slots["actions-before"] ? 1 : 0)
719
855
  }));
720
- const emitModelUpdate = () => {
721
- if (model.value === digits.value) return;
722
- model.value = digits.value;
723
- emit("change", {
724
- full: full.value,
725
- fullFormatted: fullFormatted.value,
726
- digits: digits.value
727
- });
728
- };
729
- const onInput = async (e) => {
730
- if (inactive.value) return;
731
- mask.handleInput(e);
732
- await vue.nextTick();
733
- emitModelUpdate();
734
- };
735
- const onKeydown = async (e) => {
736
- if (inactive.value) return;
737
- mask.handleKeydown(e);
738
- await vue.nextTick();
739
- emitModelUpdate();
740
- };
741
- const onPaste = async (e) => {
742
- if (inactive.value) return;
743
- mask.handlePaste(e);
744
- await vue.nextTick();
745
- emitModelUpdate();
746
- };
747
- const onFocus = (e) => {
748
- mask.handleFocus();
749
- dropdownOpened.value = false;
750
- emit("focus", e);
751
- };
752
- const onBlur = (e) => emit("blur", e);
753
- const onSelectCountry = async (countryId) => {
754
- countrySelector.selectCountry(countryId);
755
- emit("country-change", selected.value);
756
- await vue.nextTick();
757
- telRef.value?.focus();
758
- };
759
- const onCopyClick = async () => {
760
- const valueToCopy = fullFormatted.value;
761
- const success = await copy(valueToCopy);
762
- if (success) {
763
- emit("copy", valueToCopy);
764
- }
765
- };
766
- const onClearClick = async () => {
767
- mask.clear();
768
- model.value = "";
769
- emit("change", {
770
- full: "",
771
- fullFormatted: "",
772
- digits: ""
773
- });
774
- emit("clear");
775
- await vue.nextTick();
776
- telRef.value?.focus();
777
- };
778
- const positionDropdown = (e) => {
779
- if (e?.type === "scroll" && e.target && dropdownRef.value?.contains(e.target)) return;
780
- const root = rootRef.value;
781
- if (!root) return;
782
- const rect = root.getBoundingClientRect();
783
- dropdownStyle.value = {
784
- top: `${rect.bottom + window.scrollY + 8}px`,
785
- left: `${rect.left + window.scrollX}px`,
786
- width: `${rect.width}px`
787
- };
788
- };
789
- const removeDropdownListeners = () => {
790
- window.removeEventListener("scroll", positionDropdown, true);
791
- window.removeEventListener("click", onDocClick, true);
792
- window.removeEventListener("resize", positionDropdown);
793
- };
794
- const toggleDropdown = async () => {
795
- if (inactive.value || !hasDropdown.value) return;
796
- await countrySelector.toggleDropdown(searchRef);
797
- if (dropdownOpened.value) {
798
- positionDropdown();
799
- window.addEventListener("scroll", positionDropdown, true);
800
- window.addEventListener("click", onDocClick, true);
801
- window.addEventListener("resize", positionDropdown);
802
- } else {
803
- removeDropdownListeners();
804
- }
805
- };
806
- const scrollFocusedIntoView = async () => {
807
- await vue.nextTick();
808
- const list = dropdownRef.value?.lastElementChild;
809
- if (!list) return;
810
- const option = list.children[focusedIndex.value];
811
- if (!option) return;
812
- const listRect = list.getBoundingClientRect();
813
- const optionRect = option.getBoundingClientRect();
814
- let scrollAmount = 0;
815
- if (optionRect.top < listRect.top) {
816
- scrollAmount = list.scrollTop - (listRect.top - optionRect.top);
817
- } else if (optionRect.bottom > listRect.bottom) {
818
- scrollAmount = list.scrollTop + (optionRect.bottom - listRect.bottom);
819
- } else {
820
- return;
821
- }
822
- list.scrollTo({ top: scrollAmount, behavior: "smooth" });
823
- };
824
- const onDocClick = (ev) => {
825
- const dropdown = dropdownRef.value;
826
- const selector = rootRef.value?.firstChild;
827
- if (!(dropdown || selector)) return;
828
- const target = ev.target;
829
- if (!target || dropdown?.contains(target) || selector?.contains(target)) return;
830
- dropdownOpened.value = false;
831
- };
832
- vue.watch(
833
- model,
834
- (newValue) => {
835
- if (!newValue) {
836
- if (!isEmpty.value) mask.clear();
837
- return;
838
- }
839
- const currentDigits = digits.value;
840
- if (newValue !== currentDigits) {
841
- const incomingDigits = newValue.replace(/\D/g, "");
842
- if (incomingDigits !== currentDigits) {
843
- digits.value = incomingDigits;
844
- mask.updateDisplayFromDigits();
845
- }
846
- }
847
- },
848
- { immediate: true }
849
- );
850
- vue.watch(
851
- [() => props.country, () => props.detect],
852
- async ([country, detect]) => {
853
- await vue.nextTick();
854
- await countrySelector.initCountry(country, detect, () => emit("country-change", selected.value));
855
- },
856
- { immediate: true }
857
- );
858
- vue.watch(
859
- copyMessage,
860
- (val) => {
861
- if (liveRef.value && val) {
862
- liveRef.value.textContent = val;
863
- }
864
- },
865
- { flush: "post" }
866
- );
867
- vue.watch(
868
- isComplete,
869
- (valid) => {
870
- emit("validation-change", valid);
871
- },
872
- { flush: "post" }
873
- );
874
- vue.onBeforeUnmount(() => {
875
- removeDropdownListeners();
876
- onClipboardUnmount();
877
- });
878
- __expose({
879
- focus: () => telRef.value?.focus(),
880
- blur: () => telRef.value?.blur(),
881
- clear: mask.clear,
882
- selectCountry: countrySelector.selectCountry,
883
- getFullNumber: () => full.value,
884
- getFullFormattedNumber: () => fullFormatted.value,
885
- getDigits: () => digits.value,
886
- isValid: () => isComplete.value,
887
- isComplete: () => isComplete.value
888
- });
889
856
  return (_ctx, _cache) => {
890
857
  return vue.openBlock(), vue.createElementBlock("div", {
891
858
  ref_key: "rootRef",
@@ -895,36 +862,41 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
895
862
  class: vue.normalizeClass(rootClasses.value),
896
863
  style: vue.normalizeStyle(rootStyles.value)
897
864
  }, [
898
- vue.createElementVNode("div", _hoisted_1, [
865
+ vue.createElementVNode("div", {
866
+ ref_key: "selectorRef",
867
+ ref: selectorRef,
868
+ class: "pi-selector"
869
+ }, [
899
870
  vue.createElementVNode("button", {
900
871
  type: "button",
901
872
  class: vue.normalizeClass(["pi-selector-btn", { "no-dropdown": !vue.unref(hasDropdown) || __props.readonly }]),
902
873
  disabled: __props.disabled,
903
874
  tabindex: inactive.value || !vue.unref(hasDropdown) ? -1 : void 0,
904
- "aria-label": `Selected country: ${vue.unref(selected).name}`,
905
- "aria-expanded": vue.unref(dropdownOpened),
875
+ "aria-label": `Selected country: ${vue.unref(country).name}`,
876
+ "aria-expanded": vue.unref(dropdownOpen),
906
877
  "aria-haspopup": vue.unref(hasDropdown) ? "listbox" : void 0,
907
- onClick: toggleDropdown
878
+ onClick: _cache[0] || (_cache[0] = //@ts-ignore
879
+ (...args) => vue.unref(toggleDropdown) && vue.unref(toggleDropdown)(...args))
908
880
  }, [
909
881
  vue.createElementVNode("span", {
910
882
  class: "pi-flag",
911
883
  role: "img",
912
- "aria-label": `${vue.unref(selected).name} flag`
884
+ "aria-label": `${vue.unref(country).name} flag`
913
885
  }, [
914
- vue.renderSlot(_ctx.$slots, "flag", { country: vue.unref(selected) }, () => [
915
- vue.createTextVNode(vue.toDisplayString(vue.unref(selected).flag), 1)
886
+ vue.renderSlot(_ctx.$slots, "flag", { country: vue.unref(country) }, () => [
887
+ vue.createTextVNode(vue.toDisplayString(vue.unref(country).flag), 1)
916
888
  ], true)
917
- ], 8, _hoisted_3),
918
- vue.createElementVNode("span", _hoisted_4, vue.toDisplayString(vue.unref(selected).code), 1),
889
+ ], 8, _hoisted_2),
890
+ vue.createElementVNode("span", _hoisted_3, vue.toDisplayString(vue.unref(country).code), 1),
919
891
  !inactive.value && vue.unref(hasDropdown) ? (vue.openBlock(), vue.createElementBlock("svg", {
920
892
  key: 0,
921
- class: vue.normalizeClass(["pi-chevron", { "is-open": vue.unref(dropdownOpened) }]),
893
+ class: vue.normalizeClass(["pi-chevron", { "is-open": vue.unref(dropdownOpen) }]),
922
894
  width: "12",
923
895
  height: "12",
924
896
  viewBox: "0 0 12 12",
925
897
  fill: "none",
926
898
  "aria-hidden": "true"
927
- }, [..._cache[6] || (_cache[6] = [
899
+ }, [..._cache[8] || (_cache[8] = [
928
900
  vue.createElementVNode("path", {
929
901
  d: "M2.5 4.5L6 8L9.5 4.5",
930
902
  stroke: "currentColor",
@@ -933,9 +905,9 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
933
905
  "stroke-linejoin": "round"
934
906
  }, null, -1)
935
907
  ])], 2)) : vue.createCommentVNode("", true)
936
- ], 10, _hoisted_2)
937
- ]),
938
- vue.createElementVNode("div", _hoisted_5, [
908
+ ], 10, _hoisted_1)
909
+ ], 512),
910
+ vue.createElementVNode("div", _hoisted_4, [
939
911
  vue.createElementVNode("input", {
940
912
  ref_key: "telRef",
941
913
  ref: telRef,
@@ -950,16 +922,19 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
950
922
  value: vue.unref(displayValue),
951
923
  disabled: __props.disabled,
952
924
  readonly: __props.readonly,
953
- "aria-invalid": vue.unref(shouldShowWarn),
954
- onBeforeinput: _cache[0] || (_cache[0] = //@ts-ignore
955
- (...args) => vue.unref(mask).handleBeforeInput && vue.unref(mask).handleBeforeInput(...args)),
956
- onInput,
957
- onKeydown,
958
- onPaste,
959
- onFocus,
960
- onBlur
961
- }, null, 40, _hoisted_6),
962
- vue.createElementVNode("div", _hoisted_7, [
925
+ "aria-invalid": incomplete.value,
926
+ onBeforeinput: _cache[1] || (_cache[1] = //@ts-ignore
927
+ (...args) => vue.unref(handleBeforeInput) && vue.unref(handleBeforeInput)(...args)),
928
+ onInput: _cache[2] || (_cache[2] = //@ts-ignore
929
+ (...args) => vue.unref(handleInput) && vue.unref(handleInput)(...args)),
930
+ onKeydown: _cache[3] || (_cache[3] = //@ts-ignore
931
+ (...args) => vue.unref(handleKeydown) && vue.unref(handleKeydown)(...args)),
932
+ onPaste: _cache[4] || (_cache[4] = //@ts-ignore
933
+ (...args) => vue.unref(handlePaste) && vue.unref(handlePaste)(...args)),
934
+ onFocus: handleFocus,
935
+ onBlur: handleBlur
936
+ }, null, 40, _hoisted_5),
937
+ vue.createElementVNode("div", _hoisted_6, [
963
938
  vue.createVNode(vue.Transition, { name: "fade-scale" }, {
964
939
  default: vue.withCtx(() => [
965
940
  vue.renderSlot(_ctx.$slots, "actions-before", {}, void 0, true)
@@ -971,26 +946,27 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
971
946
  showCopyButton.value ? (vue.openBlock(), vue.createElementBlock("button", {
972
947
  key: 0,
973
948
  type: "button",
974
- class: vue.normalizeClass(["pi-btn", { "is-copied": vue.unref(copied) }]),
975
- "aria-label": copyAriaLabel.value,
976
- title: copyButtonTitle.value,
977
- onClick: onCopyClick
949
+ class: vue.normalizeClass(["pi-btn", "pi-btn-copy", { "is-copied": vue.unref(copied) }]),
950
+ "aria-label": vue.unref(copyAriaLabel),
951
+ title: vue.unref(copyButtonTitle),
952
+ onClick: _cache[5] || (_cache[5] = //@ts-ignore
953
+ (...args) => vue.unref(onCopyClick) && vue.unref(onCopyClick)(...args))
978
954
  }, [
979
955
  slots["copy-svg"] ? vue.renderSlot(_ctx.$slots, "copy-svg", {
980
956
  key: 0,
981
957
  copied: vue.unref(copied)
982
- }, void 0, true) : !vue.unref(copied) ? (vue.openBlock(), vue.createElementBlock("svg", _hoisted_9, [..._cache[7] || (_cache[7] = [
958
+ }, void 0, true) : !vue.unref(copied) ? (vue.openBlock(), vue.createElementBlock("svg", _hoisted_8, [..._cache[9] || (_cache[9] = [
983
959
  vue.createElementVNode("path", {
984
960
  d: "M13.5 5.5V13.5H5.5V5.5H13.5ZM13.5 4H5.5C4.67 4 4 4.67 4 5.5V13.5C4 14.33 4.67 15 5.5 15H13.5C14.33 15 15 14.33 15 13.5V5.5C15 4.67 14.33 4 13.5 4ZM10.5 1H2.5V11H4V2.5H10.5V1Z",
985
961
  fill: "currentColor"
986
962
  }, null, -1)
987
- ])])) : (vue.openBlock(), vue.createElementBlock("svg", _hoisted_10, [..._cache[8] || (_cache[8] = [
963
+ ])])) : (vue.openBlock(), vue.createElementBlock("svg", _hoisted_9, [..._cache[10] || (_cache[10] = [
988
964
  vue.createElementVNode("path", {
989
965
  d: "M6.5 11.5L3 8L4.06 6.94L6.5 9.38L11.94 3.94L13 5L6.5 11.5Z",
990
966
  fill: "currentColor"
991
967
  }, null, -1)
992
968
  ])]))
993
- ], 10, _hoisted_8)) : vue.createCommentVNode("", true)
969
+ ], 10, _hoisted_7)) : vue.createCommentVNode("", true)
994
970
  ]),
995
971
  _: 3
996
972
  }),
@@ -999,18 +975,18 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
999
975
  showClearButton.value ? (vue.openBlock(), vue.createElementBlock("button", {
1000
976
  key: 0,
1001
977
  type: "button",
1002
- class: "pi-btn",
978
+ class: "pi-btn pi-btn-clear",
1003
979
  "aria-label": __props.clearButtonLabel,
1004
980
  title: __props.clearButtonLabel,
1005
981
  onClick: onClearClick
1006
982
  }, [
1007
- !slots["clear-svg"] ? (vue.openBlock(), vue.createElementBlock("svg", _hoisted_12, [..._cache[9] || (_cache[9] = [
983
+ !slots["clear-svg"] ? (vue.openBlock(), vue.createElementBlock("svg", _hoisted_11, [..._cache[11] || (_cache[11] = [
1008
984
  vue.createElementVNode("path", {
1009
985
  d: "M14 1.41L12.59 0L7 5.59L1.41 0L0 1.41L5.59 7L0 12.59L1.41 14L7 8.41L12.59 14L14 12.59L8.41 7L14 1.41Z",
1010
986
  fill: "currentColor"
1011
987
  }, null, -1)
1012
988
  ])])) : vue.renderSlot(_ctx.$slots, "clear-svg", { key: 1 }, void 0, true)
1013
- ], 8, _hoisted_11)) : vue.createCommentVNode("", true)
989
+ ], 8, _hoisted_10)) : vue.createCommentVNode("", true)
1014
990
  ]),
1015
991
  _: 3
1016
992
  })
@@ -1019,42 +995,30 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
1019
995
  (vue.openBlock(), vue.createBlock(vue.Teleport, { to: "body" }, [
1020
996
  vue.createVNode(vue.Transition, { name: "dropdown" }, {
1021
997
  default: vue.withCtx(() => [
1022
- vue.unref(dropdownOpened) ? (vue.openBlock(), vue.createElementBlock("div", {
998
+ vue.unref(dropdownOpen) ? (vue.openBlock(), vue.createElementBlock("div", {
1023
999
  key: 0,
1024
1000
  ref_key: "dropdownRef",
1025
1001
  ref: dropdownRef,
1026
- class: vue.normalizeClass(["phone-dropdown", [__props.dropdownClass, themeClass.value]]),
1002
+ class: vue.normalizeClass(["phone-dropdown", [__props.dropdownClass, vue.unref(themeClass)]]),
1027
1003
  role: "dialog",
1028
1004
  "aria-modal": "false",
1029
1005
  "aria-label": "Select country",
1030
- style: vue.normalizeStyle(dropdownStyle.value)
1006
+ style: vue.normalizeStyle(vue.unref(dropdownStyle))
1031
1007
  }, [
1032
- vue.createElementVNode("div", _hoisted_13, [
1033
- vue.withDirectives(vue.createElementVNode("input", {
1008
+ vue.createElementVNode("div", _hoisted_12, [
1009
+ vue.createElementVNode("input", {
1034
1010
  ref_key: "searchRef",
1035
1011
  ref: searchRef,
1036
- "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => vue.isRef(search) ? search.value = $event : null),
1012
+ value: vue.unref(search),
1037
1013
  type: "search",
1038
1014
  class: "pi-search",
1039
1015
  "aria-label": "Search countries",
1040
1016
  placeholder: __props.searchPlaceholder,
1041
- onKeydown: [
1042
- _cache[2] || (_cache[2] = vue.withKeys(vue.withModifiers(($event) => vue.unref(focusNextOption)(scrollFocusedIntoView), ["prevent"]), ["down"])),
1043
- _cache[3] || (_cache[3] = vue.withKeys(vue.withModifiers(($event) => vue.unref(focusPrevOption)(scrollFocusedIntoView), ["prevent"]), ["up"])),
1044
- _cache[4] || (_cache[4] = vue.withKeys(vue.withModifiers(
1045
- //@ts-ignore
1046
- (...args) => vue.unref(chooseFocusedOption) && vue.unref(chooseFocusedOption)(...args),
1047
- ["prevent"]
1048
- ), ["enter"])),
1049
- _cache[5] || (_cache[5] = vue.withKeys(
1050
- //@ts-ignore
1051
- (...args) => vue.unref(closeDropdown) && vue.unref(closeDropdown)(...args),
1052
- ["escape"]
1053
- ))
1054
- ]
1055
- }, null, 40, _hoisted_14), [
1056
- [vue.vModelText, vue.unref(search)]
1057
- ])
1017
+ onKeydown: _cache[6] || (_cache[6] = //@ts-ignore
1018
+ (...args) => vue.unref(handleSearchKeydown) && vue.unref(handleSearchKeydown)(...args)),
1019
+ onInput: _cache[7] || (_cache[7] = //@ts-ignore
1020
+ (...args) => vue.unref(handleSearchChange) && vue.unref(handleSearchChange)(...args))
1021
+ }, null, 40, _hoisted_13)
1058
1022
  ]),
1059
1023
  vue.createElementVNode("ul", {
1060
1024
  class: "pi-options",
@@ -1071,13 +1035,13 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
1071
1035
  "pi-option",
1072
1036
  {
1073
1037
  "is-focused": idx === vue.unref(focusedIndex),
1074
- "is-selected": c.id === vue.unref(selected).id
1038
+ "is-selected": c.id === vue.unref(country).id
1075
1039
  }
1076
1040
  ]),
1077
- "aria-selected": c.id === vue.unref(selected).id,
1041
+ "aria-selected": c.id === vue.unref(country).id,
1078
1042
  title: c.name,
1079
- onClick: ($event) => onSelectCountry(c.id),
1080
- onMouseenter: ($event) => focusedIndex.value = idx
1043
+ onClick: ($event) => vue.unref(selectCountry)(c.id),
1044
+ onMouseenter: ($event) => vue.unref(setFocusedIndex)(idx)
1081
1045
  }, [
1082
1046
  vue.createElementVNode("span", {
1083
1047
  class: "pi-flag",
@@ -1087,13 +1051,13 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
1087
1051
  vue.renderSlot(_ctx.$slots, "flag", { country: c }, () => [
1088
1052
  vue.createTextVNode(vue.toDisplayString(c.flag), 1)
1089
1053
  ], true)
1090
- ], 8, _hoisted_17),
1091
- vue.createElementVNode("span", _hoisted_18, vue.toDisplayString(c.name), 1),
1092
- vue.createElementVNode("span", _hoisted_19, vue.toDisplayString(c.code), 1)
1093
- ], 42, _hoisted_16);
1054
+ ], 8, _hoisted_16),
1055
+ vue.createElementVNode("span", _hoisted_17, vue.toDisplayString(c.name), 1),
1056
+ vue.createElementVNode("span", _hoisted_18, vue.toDisplayString(c.code), 1)
1057
+ ], 42, _hoisted_15);
1094
1058
  }), 128)),
1095
- vue.unref(filteredCountries).length === 0 ? (vue.openBlock(), vue.createElementBlock("li", _hoisted_20, vue.toDisplayString(__props.noResultsText), 1)) : vue.createCommentVNode("", true)
1096
- ], 8, _hoisted_15)
1059
+ vue.unref(filteredCountries).length === 0 ? (vue.openBlock(), vue.createElementBlock("li", _hoisted_19, vue.toDisplayString(__props.noResultsText), 1)) : vue.createCommentVNode("", true)
1060
+ ], 8, _hoisted_14)
1097
1061
  ], 6)) : vue.createCommentVNode("", true)
1098
1062
  ]),
1099
1063
  _: 3
@@ -1118,56 +1082,7 @@ const _export_sfc = (sfc, props) => {
1118
1082
  }
1119
1083
  return target;
1120
1084
  };
1121
- const PhoneInput = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-95467f81"]]);
1122
- function getNavigatorLang() {
1123
- if (typeof navigator !== "undefined") {
1124
- return navigator.language || "";
1125
- }
1126
- return "";
1127
- }
1128
- function getCountry(countryCode, locale) {
1129
- const isEn = locale.toLowerCase().startsWith("en");
1130
- const countriesMap = isEn ? m : MasksFullMap(locale);
1131
- const id = countryCode.toUpperCase();
1132
- const found = countriesMap[id];
1133
- return found ? { id, ...found } : null;
1134
- }
1135
- function getDefaultCountry(locale) {
1136
- const isEn = locale.toLowerCase().startsWith("en");
1137
- const countries = isEn ? m : MasksFullMap(locale);
1138
- return { id: "US", ...countries.US };
1139
- }
1140
- async function detectCountryFromGeoIP() {
1141
- try {
1142
- const controller = new AbortController();
1143
- const timeoutId = setTimeout(() => controller.abort(), GEO_IP_TIMEOUT);
1144
- const res = await fetch(GEO_IP_URL, {
1145
- signal: controller.signal,
1146
- headers: { Accept: "application/json" }
1147
- });
1148
- clearTimeout(timeoutId);
1149
- if (!res.ok) return null;
1150
- const json = await res.json();
1151
- const code = (json.country || json.country_code || json.countryCode || json.country_code2 || "").toString().toUpperCase();
1152
- return code || null;
1153
- } catch {
1154
- return null;
1155
- }
1156
- }
1157
- function detectCountryFromLocale() {
1158
- try {
1159
- const lang = getNavigatorLang();
1160
- try {
1161
- const loc = new Intl.Locale(lang);
1162
- if (loc.region) return loc.region.toUpperCase();
1163
- } catch {
1164
- }
1165
- const parts = lang.split(/[-_]/);
1166
- if (parts.length > 1) return parts[1]?.toUpperCase() || null;
1167
- } catch {
1168
- }
1169
- return null;
1170
- }
1085
+ const PhoneInput = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-33134720"]]);
1171
1086
  async function initState(binding) {
1172
1087
  const value = binding.value;
1173
1088
  let options = {};
@@ -1176,24 +1091,24 @@ async function initState(binding) {
1176
1091
  } else if (typeof value === "object" && value !== null) {
1177
1092
  options = value;
1178
1093
  }
1179
- const locale = options.locale || getNavigatorLang() || "en";
1180
- let country = null;
1094
+ const locale = options.locale || getNavigatorLang();
1095
+ let country;
1181
1096
  if (options.country) {
1182
1097
  country = getCountry(options.country, locale);
1183
1098
  } else if (options.detect) {
1184
1099
  const geoCountry = await detectCountryFromGeoIP();
1185
1100
  if (geoCountry) {
1186
1101
  country = getCountry(geoCountry, locale);
1187
- }
1188
- if (!country) {
1102
+ } else {
1189
1103
  const localeCountry = detectCountryFromLocale();
1190
1104
  if (localeCountry) {
1191
1105
  country = getCountry(localeCountry, locale);
1106
+ } else {
1107
+ country = getCountry("US", locale);
1192
1108
  }
1193
1109
  }
1194
- }
1195
- if (!country) {
1196
- country = getDefaultCountry(locale);
1110
+ } else {
1111
+ country = getCountry("US", locale);
1197
1112
  }
1198
1113
  return {
1199
1114
  country,
@@ -1215,147 +1130,50 @@ function updateDisplay(el, state) {
1215
1130
  });
1216
1131
  }
1217
1132
  }
1218
- function createBeforeInputHandler(el) {
1219
- return (e) => {
1220
- const data = e.data;
1221
- if (e.inputType !== "insertText" || !data) return;
1222
- if (InvalidPattern.test(data) || data === " " && el.value.endsWith(" ")) {
1223
- e.preventDefault();
1224
- }
1225
- };
1226
- }
1227
1133
  function createInputHandler(el, state) {
1228
- return (e) => {
1229
- const target = e.target;
1230
- if (!target) return;
1231
- const raw = target.value || "";
1232
- const maxDigits = state.formatter.getMaxDigits();
1233
- state.digits = extractDigits(raw, maxDigits);
1134
+ return (e2) => {
1135
+ const result = processInput(e2, { formatter: state.formatter });
1136
+ if (!result) return;
1137
+ state.digits = result.newDigits;
1234
1138
  updateDisplay(el, state);
1235
1139
  vue.nextTick(() => {
1236
- const pos = state.formatter.getCaretPosition(state.digits.length);
1140
+ const pos = state.formatter.getCaretPosition(result.caretDigitIndex);
1237
1141
  setCaret(el, pos);
1238
1142
  });
1239
1143
  };
1240
1144
  }
1241
1145
  function createKeydownHandler(el, state) {
1242
- return (e) => {
1243
- if (e.ctrlKey || e.metaKey || e.altKey || NavigationKeys.includes(e.key)) return;
1244
- const [selStart, selEnd] = getSelection(el);
1245
- if (e.key === "Backspace") {
1246
- e.preventDefault();
1247
- if (selStart !== selEnd) {
1248
- const range = state.formatter.getDigitRange(state.digits, selStart, selEnd);
1249
- if (range) {
1250
- const [start, end] = range;
1251
- state.digits = state.digits.slice(0, start) + state.digits.slice(end);
1252
- updateDisplay(el, state);
1253
- vue.nextTick(() => {
1254
- const pos = state.formatter.getCaretPosition(start);
1255
- setCaret(el, pos);
1256
- });
1257
- }
1258
- return;
1259
- }
1260
- if (selStart > 0) {
1261
- const displayStr = el.value;
1262
- let prevPos = selStart - 1;
1263
- while (prevPos >= 0 && Delimiters.includes(displayStr[prevPos])) {
1264
- prevPos--;
1265
- }
1266
- if (prevPos >= 0) {
1267
- const range = state.formatter.getDigitRange(state.digits, prevPos, prevPos + 1);
1268
- if (range) {
1269
- const [start] = range;
1270
- state.digits = state.digits.slice(0, start) + state.digits.slice(start + 1);
1271
- updateDisplay(el, state);
1272
- vue.nextTick(() => {
1273
- const pos = state.formatter.getCaretPosition(start);
1274
- setCaret(el, pos);
1275
- });
1276
- }
1277
- }
1278
- }
1279
- return;
1280
- }
1281
- if (e.key === "Delete") {
1282
- e.preventDefault();
1283
- if (selStart !== selEnd) {
1284
- const range = state.formatter.getDigitRange(state.digits, selStart, selEnd);
1285
- if (range) {
1286
- const [start, end] = range;
1287
- state.digits = state.digits.slice(0, start) + state.digits.slice(end);
1288
- updateDisplay(el, state);
1289
- vue.nextTick(() => {
1290
- const pos = state.formatter.getCaretPosition(start);
1291
- setCaret(el, pos);
1292
- });
1293
- }
1294
- return;
1295
- }
1296
- if (selStart < el.value.length) {
1297
- const range = state.formatter.getDigitRange(state.digits, selStart, selStart + 1);
1298
- if (range) {
1299
- const [start] = range;
1300
- state.digits = state.digits.slice(0, start) + state.digits.slice(start + 1);
1301
- updateDisplay(el, state);
1302
- vue.nextTick(() => {
1303
- const pos = state.formatter.getCaretPosition(start);
1304
- setCaret(el, pos);
1305
- });
1306
- }
1307
- }
1308
- return;
1309
- }
1310
- if (/^[0-9]$/.test(e.key)) {
1311
- if (state.digits.length >= state.formatter.getMaxDigits()) {
1312
- e.preventDefault();
1313
- }
1314
- return;
1315
- }
1316
- if (e.key.length === 1) {
1317
- e.preventDefault();
1318
- }
1146
+ return (e2) => {
1147
+ const result = processKeydown(e2, {
1148
+ digits: state.digits,
1149
+ formatter: state.formatter
1150
+ });
1151
+ if (!result) return;
1152
+ state.digits = result.newDigits;
1153
+ updateDisplay(el, state);
1154
+ vue.nextTick(() => {
1155
+ const pos = state.formatter.getCaretPosition(result.caretDigitIndex);
1156
+ setCaret(el, pos);
1157
+ });
1319
1158
  };
1320
1159
  }
1321
1160
  function createPasteHandler(el, state) {
1322
- return (e) => {
1323
- e.preventDefault();
1324
- const text = e.clipboardData?.getData("text") || "";
1325
- const maxDigits = state.formatter.getMaxDigits();
1326
- const pastedDigits = extractDigits(text, maxDigits);
1327
- if (pastedDigits.length === 0) return;
1328
- const [selStart, selEnd] = getSelection(el);
1329
- if (selStart !== selEnd) {
1330
- const range2 = state.formatter.getDigitRange(state.digits, selStart, selEnd);
1331
- if (range2) {
1332
- const [start, end] = range2;
1333
- const left2 = state.digits.slice(0, start);
1334
- const right2 = state.digits.slice(end);
1335
- state.digits = extractDigits(left2 + pastedDigits + right2, maxDigits);
1336
- updateDisplay(el, state);
1337
- vue.nextTick(() => {
1338
- const pos = state.formatter.getCaretPosition(start + pastedDigits.length);
1339
- setCaret(el, pos);
1340
- });
1341
- return;
1342
- }
1343
- }
1344
- const range = state.formatter.getDigitRange(state.digits, selStart, selStart);
1345
- const insertIndex = range ? range[0] : state.digits.length;
1346
- const left = state.digits.slice(0, insertIndex);
1347
- const right = state.digits.slice(insertIndex);
1348
- state.digits = extractDigits(left + pastedDigits + right, maxDigits);
1161
+ return (e2) => {
1162
+ const result = processPaste(e2, {
1163
+ digits: state.digits,
1164
+ formatter: state.formatter
1165
+ });
1166
+ if (!result) return;
1167
+ state.digits = result.newDigits;
1349
1168
  updateDisplay(el, state);
1350
1169
  vue.nextTick(() => {
1351
- const pos = state.formatter.getCaretPosition(insertIndex + pastedDigits.length);
1170
+ const pos = state.formatter.getCaretPosition(result.caretDigitIndex);
1352
1171
  setCaret(el, pos);
1353
1172
  });
1354
1173
  };
1355
1174
  }
1356
1175
  async function updateCountry(el, state, newCountryCode) {
1357
1176
  const newCountry = getCountry(newCountryCode, state.locale);
1358
- if (!newCountry) return;
1359
1177
  state.country = newCountry;
1360
1178
  state.formatter = createPhoneFormatter(newCountry);
1361
1179
  el.placeholder = state.formatter.getPlaceholder();
@@ -1381,7 +1199,7 @@ const vPhoneMask = {
1381
1199
  state.inputHandler = createInputHandler(el, state);
1382
1200
  state.keydownHandler = createKeydownHandler(el, state);
1383
1201
  state.pasteHandler = createPasteHandler(el, state);
1384
- state.beforeInputHandler = createBeforeInputHandler(el);
1202
+ state.beforeInputHandler = processBeforeInput;
1385
1203
  el.addEventListener("beforeinput", state.beforeInputHandler);
1386
1204
  el.addEventListener("input", state.inputHandler);
1387
1205
  el.addEventListener("keydown", state.keydownHandler);
@@ -1412,8 +1230,10 @@ const vPhoneMask = {
1412
1230
  if (newCountry && newCountry !== oldCountry) {
1413
1231
  await updateCountry(el, state, newCountry);
1414
1232
  }
1415
- const newDigits = extractDigits(el.value);
1416
- if (newDigits !== state.digits) {
1233
+ const maxDigits = state.formatter.getMaxDigits();
1234
+ const newDigits = extractDigits(el.value, maxDigits);
1235
+ const normalizedDisplay = state.formatter.formatDisplay(newDigits);
1236
+ if (newDigits !== state.digits || el.value !== normalizedDisplay) {
1417
1237
  state.digits = newDigits;
1418
1238
  updateDisplay(el, state);
1419
1239
  }
@@ -1428,6 +1248,82 @@ const vPhoneMask = {
1428
1248
  delete el.__phoneMaskState;
1429
1249
  }
1430
1250
  };
1251
+ function usePhoneMask(options) {
1252
+ const inputRef = vue.shallowRef(null);
1253
+ const { country, setCountry } = useCountry({
1254
+ country: options.country,
1255
+ locale: options.locale,
1256
+ detect: options.detect,
1257
+ onCountryChange: options.onCountryChange
1258
+ });
1259
+ const {
1260
+ digits,
1261
+ formatter,
1262
+ displayPlaceholder,
1263
+ displayValue,
1264
+ full,
1265
+ fullFormatted,
1266
+ isComplete,
1267
+ isEmpty,
1268
+ shouldShowWarn
1269
+ } = useFormatter({
1270
+ country,
1271
+ value: options.value,
1272
+ onChange: options.onChange,
1273
+ onPhoneChange: options.onPhoneChange
1274
+ });
1275
+ const { handleBeforeInput, handleInput, handleKeydown, handlePaste } = useInputHandlers({
1276
+ formatter,
1277
+ digits,
1278
+ onChange: options.onChange
1279
+ });
1280
+ vue.onMounted(() => {
1281
+ const el = inputRef.value;
1282
+ if (!el) return;
1283
+ el.setAttribute("type", "tel");
1284
+ el.setAttribute("inputmode", "tel");
1285
+ });
1286
+ vue.watchEffect(
1287
+ () => {
1288
+ const el = inputRef.value;
1289
+ if (!el) return;
1290
+ el.value = displayValue.value;
1291
+ el.setAttribute("placeholder", displayPlaceholder.value);
1292
+ },
1293
+ { flush: "post" }
1294
+ );
1295
+ vue.onMounted(() => {
1296
+ const el = inputRef.value;
1297
+ if (!el) return;
1298
+ el.addEventListener("beforeinput", handleBeforeInput);
1299
+ el.addEventListener("input", handleInput);
1300
+ el.addEventListener("keydown", handleKeydown);
1301
+ el.addEventListener("paste", handlePaste);
1302
+ });
1303
+ vue.onUnmounted(() => {
1304
+ const el = inputRef.value;
1305
+ if (!el) return;
1306
+ el.removeEventListener("beforeinput", handleBeforeInput);
1307
+ el.removeEventListener("input", handleInput);
1308
+ el.removeEventListener("keydown", handleKeydown);
1309
+ el.removeEventListener("paste", handlePaste);
1310
+ });
1311
+ const clear = () => {
1312
+ options.onChange("");
1313
+ };
1314
+ return {
1315
+ inputRef,
1316
+ digits,
1317
+ full,
1318
+ fullFormatted,
1319
+ isComplete,
1320
+ isEmpty,
1321
+ shouldShowWarn,
1322
+ country,
1323
+ setCountry,
1324
+ clear
1325
+ };
1326
+ }
1431
1327
  function install(app) {
1432
1328
  app.component("PhoneInput", PhoneInput);
1433
1329
  app.directive("phone-mask", vPhoneMask);
@@ -1436,7 +1332,7 @@ const index = {
1436
1332
  install
1437
1333
  };
1438
1334
  const PMaskHelpers = {
1439
- getFlagEmoji: g,
1335
+ getFlagEmoji: k,
1440
1336
  countPlaceholders,
1441
1337
  formatDigitsWithMap,
1442
1338
  pickMaskVariant,
@@ -1447,5 +1343,6 @@ exports.PMaskHelpers = PMaskHelpers;
1447
1343
  exports.PhoneInput = PhoneInput;
1448
1344
  exports.default = index;
1449
1345
  exports.install = install;
1346
+ exports.usePhoneMask = usePhoneMask;
1450
1347
  exports.vPhoneMask = vPhoneMask;
1451
1348
  exports.vPhoneMaskSetCountry = updateCountry;