@desource/phone-mask-vue 0.2.3 → 1.0.0

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