@desource/phone-mask-vue 0.3.0 → 1.0.0

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