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