@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.
- package/CHANGELOG.md +34 -0
- package/README.md +153 -10
- package/dist/index.cjs +824 -885
- package/dist/index.js +824 -885
- package/dist/index.mjs +825 -886
- package/dist/phone-mask-vue.css +64 -64
- package/dist/types/components/PhoneInput.vue.d.ts +7 -7
- package/dist/types/components/PhoneInput.vue.d.ts.map +1 -1
- package/dist/types/composables/internal/useCopyAction.d.ts +14 -0
- package/dist/types/composables/internal/useCopyAction.d.ts.map +1 -0
- package/dist/types/composables/internal/useCountry.d.ts +22 -0
- package/dist/types/composables/internal/useCountry.d.ts.map +1 -0
- package/dist/types/composables/internal/useCountrySelector.d.ts +30 -0
- package/dist/types/composables/internal/useCountrySelector.d.ts.map +1 -0
- package/dist/types/composables/internal/useFormatter.d.ts +46 -0
- package/dist/types/composables/internal/useFormatter.d.ts.map +1 -0
- package/dist/types/composables/internal/useInputHandlers.d.ts +20 -0
- package/dist/types/composables/internal/useInputHandlers.d.ts.map +1 -0
- package/dist/types/composables/internal/useTheme.d.ts +10 -0
- package/dist/types/composables/internal/useTheme.d.ts.map +1 -0
- package/dist/types/composables/internal/useValidationHint.d.ts +6 -0
- package/dist/types/composables/internal/useValidationHint.d.ts.map +1 -0
- package/dist/types/composables/usePhoneMask.d.ts +8 -0
- package/dist/types/composables/usePhoneMask.d.ts.map +1 -0
- package/dist/types/composables/{useClipboard.d.ts → utility/useClipboard.d.ts} +1 -2
- package/dist/types/composables/utility/useClipboard.d.ts.map +1 -0
- package/dist/types/composables/utility/useTimer.d.ts +9 -0
- package/dist/types/composables/utility/useTimer.d.ts.map +1 -0
- package/dist/types/directives/vPhoneMask.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/types.d.ts +31 -9
- package/dist/types/types.d.ts.map +1 -1
- package/package.json +15 -9
- package/dist/types/composables/useClipboard.d.ts.map +0 -1
- package/dist/types/composables/useCountrySelector.d.ts +0 -21
- package/dist/types/composables/useCountrySelector.d.ts.map +0 -1
- package/dist/types/composables/useMask.d.ts +0 -20
- package/dist/types/composables/useMask.d.ts.map +0 -1
- package/dist/types/composables/usePhoneFormatter.d.ts +0 -16
- package/dist/types/composables/usePhoneFormatter.d.ts.map +0 -1
- package/dist/types/consts.d.ts +0 -4
- 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
|
|
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
|
|
26
|
+
let t2 = "", o2 = "";
|
|
16
27
|
if (Array.isArray(e2)) {
|
|
17
|
-
const
|
|
18
|
-
for (const
|
|
19
|
-
const [e3, s2] = divideMask(
|
|
20
|
-
|
|
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
|
-
|
|
33
|
+
o2 = n2;
|
|
23
34
|
} else {
|
|
24
|
-
const [
|
|
25
|
-
|
|
35
|
+
const [n2, s2] = divideMask(e2);
|
|
36
|
+
t2 = n2, o2 = s2;
|
|
26
37
|
}
|
|
27
|
-
return [
|
|
38
|
+
return [t2, o2];
|
|
28
39
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const [
|
|
32
|
-
return 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
|
-
|
|
35
|
-
const [
|
|
36
|
-
return { id: e2, code:
|
|
45
|
+
s$1.map(([e2, t2]) => {
|
|
46
|
+
const [o2, n2] = getCodeAndMask(t2);
|
|
47
|
+
return { id: e2, code: o2, mask: n2 };
|
|
37
48
|
});
|
|
38
|
-
|
|
39
|
-
const [s2,
|
|
40
|
-
return 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
|
-
|
|
43
|
-
const [
|
|
44
|
-
return { id: e2, code:
|
|
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 =
|
|
48
|
-
return
|
|
49
|
-
const [
|
|
50
|
-
return 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 =
|
|
54
|
-
return
|
|
55
|
-
const [s2,
|
|
56
|
-
return { id: e3, code: s2, mask:
|
|
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
|
-
},
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
69
|
-
|
|
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
|
|
72
|
-
const
|
|
73
|
-
return
|
|
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,
|
|
109
|
+
function pickMaskVariant(t2, e2) {
|
|
85
110
|
if (1 === t2.length) return t2[0];
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
88
|
-
const o2 =
|
|
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,
|
|
92
|
-
let
|
|
93
|
-
const
|
|
116
|
+
function formatDigitsWithMap(t2, e2) {
|
|
117
|
+
let r = "";
|
|
118
|
+
const n2 = [];
|
|
94
119
|
let o2 = 0;
|
|
95
|
-
const a =
|
|
96
|
-
for (let
|
|
97
|
-
const
|
|
98
|
-
if ("#" ===
|
|
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
|
-
|
|
125
|
+
r += e2[o2], n2.push(o2), o2++;
|
|
101
126
|
} else {
|
|
102
|
-
const
|
|
103
|
-
(
|
|
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:
|
|
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(
|
|
110
|
-
const
|
|
252
|
+
async function detectCountryFromGeoIP(t2 = o, r = e) {
|
|
253
|
+
const n2 = new AbortController(), c = setTimeout(() => n2.abort(), r);
|
|
111
254
|
try {
|
|
112
|
-
const
|
|
113
|
-
if (clearTimeout(
|
|
114
|
-
const
|
|
115
|
-
return (
|
|
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(
|
|
260
|
+
return clearTimeout(c), null;
|
|
118
261
|
}
|
|
119
262
|
}
|
|
120
|
-
async function detectByGeoIp(
|
|
263
|
+
async function detectByGeoIp() {
|
|
121
264
|
try {
|
|
122
265
|
const o3 = localStorage.getItem(p);
|
|
123
266
|
if (o3) {
|
|
124
|
-
const
|
|
125
|
-
if (
|
|
126
|
-
|
|
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 (
|
|
273
|
+
const o2 = await detectCountryFromGeoIP(), e2 = parseCountryCode(o2);
|
|
274
|
+
if (e2) {
|
|
132
275
|
try {
|
|
133
|
-
|
|
276
|
+
const t2 = JSON.stringify({ country_code: e2, ts: Date.now() });
|
|
277
|
+
localStorage.setItem(p, t2);
|
|
134
278
|
} catch {
|
|
135
279
|
}
|
|
136
|
-
return
|
|
280
|
+
return e2;
|
|
137
281
|
}
|
|
138
282
|
return null;
|
|
139
283
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
const
|
|
148
|
-
const
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
160
|
-
const
|
|
161
|
-
if (
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
195
|
-
|
|
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
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
390
|
+
const scheduleValidationHint = (delay) => {
|
|
391
|
+
showValidationHint.value = false;
|
|
392
|
+
validationTimer.set(() => {
|
|
393
|
+
showValidationHint.value = true;
|
|
394
|
+
}, delay);
|
|
201
395
|
};
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
|
208
|
-
|
|
209
|
-
if (item) selectCountry(item.id);
|
|
409
|
+
const handleBeforeInput = (e2) => {
|
|
410
|
+
processBeforeInput(e2);
|
|
210
411
|
};
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (
|
|
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
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
|
359
|
-
|
|
360
|
-
|
|
490
|
+
const handleSearchChange = (e2) => {
|
|
491
|
+
search.value = e2.target.value;
|
|
492
|
+
setFocusedIndex(0);
|
|
361
493
|
};
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
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
|
|
367
|
-
|
|
368
|
-
if (!
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
395
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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(
|
|
520
|
-
if (
|
|
521
|
-
|
|
556
|
+
});
|
|
557
|
+
vue.watch(dropdownOpen, (isOpen) => {
|
|
558
|
+
if (!isOpen) {
|
|
559
|
+
removeListeners();
|
|
560
|
+
return;
|
|
522
561
|
}
|
|
523
|
-
|
|
562
|
+
positionDropdown();
|
|
563
|
+
window.addEventListener("resize", positionDropdown);
|
|
564
|
+
window.addEventListener("scroll", positionDropdown, true);
|
|
565
|
+
window.addEventListener("click", onDocClick, true);
|
|
524
566
|
});
|
|
525
|
-
|
|
567
|
+
vue.onBeforeUnmount(removeListeners);
|
|
526
568
|
return {
|
|
527
569
|
// State
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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
|
-
|
|
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
|
-
|
|
567
|
-
copyTimer = setTimeout(() => {
|
|
599
|
+
copyTimer.set(() => {
|
|
568
600
|
copied.value = false;
|
|
569
|
-
|
|
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
|
-
|
|
580
|
-
|
|
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
|
-
|
|
585
|
-
const
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
const
|
|
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
|
|
596
|
-
const
|
|
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
|
|
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
|
|
613
|
-
const
|
|
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
|
|
622
|
-
const
|
|
623
|
-
const
|
|
624
|
-
const
|
|
625
|
-
const
|
|
626
|
-
const
|
|
627
|
-
const
|
|
628
|
-
const
|
|
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
|
|
666
|
-
|
|
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
|
|
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
|
-
|
|
675
|
-
|
|
785
|
+
dropdownStyle,
|
|
786
|
+
filteredCountries,
|
|
676
787
|
hasDropdown,
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
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
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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
|
|
747
|
-
const
|
|
748
|
-
|
|
749
|
-
|
|
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
|
|
784
|
-
|
|
785
|
-
|
|
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:
|
|
828
|
+
focus: focusInput,
|
|
874
829
|
blur: () => telRef.value?.blur(),
|
|
875
|
-
clear
|
|
876
|
-
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",
|
|
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(
|
|
899
|
-
"aria-expanded": vue.unref(
|
|
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:
|
|
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(
|
|
884
|
+
"aria-label": `${vue.unref(country).name} flag`
|
|
907
885
|
}, [
|
|
908
|
-
vue.renderSlot(_ctx.$slots, "flag", { country: vue.unref(
|
|
909
|
-
vue.createTextVNode(vue.toDisplayString(vue.unref(
|
|
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,
|
|
912
|
-
vue.createElementVNode("span",
|
|
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(
|
|
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[
|
|
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,
|
|
931
|
-
]),
|
|
932
|
-
vue.createElementVNode("div",
|
|
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":
|
|
948
|
-
onBeforeinput: _cache[
|
|
949
|
-
(...args) => vue.unref(
|
|
950
|
-
onInput
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
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
|
|
970
|
-
title: copyButtonTitle
|
|
971
|
-
onClick:
|
|
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",
|
|
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",
|
|
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,
|
|
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",
|
|
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,
|
|
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(
|
|
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
|
|
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
|
|
1006
|
+
style: vue.normalizeStyle(vue.unref(dropdownStyle))
|
|
1025
1007
|
}, [
|
|
1026
|
-
vue.createElementVNode("div",
|
|
1027
|
-
vue.
|
|
1008
|
+
vue.createElementVNode("div", _hoisted_12, [
|
|
1009
|
+
vue.createElementVNode("input", {
|
|
1028
1010
|
ref_key: "searchRef",
|
|
1029
1011
|
ref: searchRef,
|
|
1030
|
-
|
|
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
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
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(
|
|
1038
|
+
"is-selected": c.id === vue.unref(country).id
|
|
1069
1039
|
}
|
|
1070
1040
|
]),
|
|
1071
|
-
"aria-selected": c.id === vue.unref(
|
|
1041
|
+
"aria-selected": c.id === vue.unref(country).id,
|
|
1072
1042
|
title: c.name,
|
|
1073
|
-
onClick: ($event) =>
|
|
1074
|
-
onMouseenter: ($event) =>
|
|
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,
|
|
1085
|
-
vue.createElementVNode("span",
|
|
1086
|
-
vue.createElementVNode("span",
|
|
1087
|
-
], 42,
|
|
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",
|
|
1090
|
-
], 8,
|
|
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-
|
|
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
|
|
1189
|
-
if (!
|
|
1190
|
-
|
|
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(
|
|
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
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
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
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
if (
|
|
1287
|
-
|
|
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(
|
|
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 =
|
|
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
|
|
1374
|
-
|
|
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:
|
|
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;
|