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