@desource/phone-mask-vue 0.3.0 → 1.1.0

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