@desource/phone-mask-vue 0.3.0 → 1.0.0

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