@alikhalilll/a-tel-input 1.0.2 → 1.1.1
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/.media/README.md +3 -0
- package/.media/hero.png +0 -0
- package/README.md +597 -72
- package/dist/_chunks/types.d.ts +661 -0
- package/dist/_chunks/types.js +52 -0
- package/dist/_chunks/types.js.map +1 -0
- package/dist/_chunks/usePhoneValidation.js +539 -0
- package/dist/_chunks/usePhoneValidation.js.map +1 -0
- package/dist/index.cjs +471 -695
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -587
- package/dist/index.d.ts +122 -587
- package/dist/index.js +454 -658
- package/dist/index.js.map +1 -1
- package/dist/styles.css +20 -5
- package/dist/vee-validate/index.cjs +113 -0
- package/dist/vee-validate/index.cjs.map +1 -0
- package/dist/vee-validate/index.d.cts +86 -0
- package/dist/vee-validate/index.d.ts +86 -0
- package/dist/vee-validate/index.js +112 -0
- package/dist/vee-validate/index.js.map +1 -0
- package/dist/zod/index.cjs +211 -0
- package/dist/zod/index.cjs.map +1 -0
- package/dist/zod/index.d.cts +65 -0
- package/dist/zod/index.d.ts +65 -0
- package/dist/zod/index.js +208 -0
- package/dist/zod/index.js.map +1 -0
- package/package.json +34 -3
- package/src/components/ACountrySelect.vue +17 -3
- package/src/components/ATelInput.vue +206 -66
- package/src/composables/useCountryDetection.ts +28 -11
- package/src/composables/useCountryMatching.ts +160 -20
- package/src/composables/useCountrySelection.ts +71 -0
- package/src/composables/usePhoneValidation.ts +81 -18
- package/src/composables/useSyncedModel.ts +80 -0
- package/src/composables/useTelInputValidation.ts +50 -11
- package/src/index.ts +2 -0
- package/src/types.ts +80 -0
- package/src/vee-validate/index.ts +2 -0
- package/src/vee-validate/useTelField.ts +202 -0
- package/src/zod/index.ts +259 -0
- package/web-types.json +44 -1
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_usePhoneValidation = require("../_chunks/usePhoneValidation.js");
|
|
3
|
+
const require_types = require("../_chunks/types.js");
|
|
4
|
+
let libphonenumber_js = require("libphonenumber-js");
|
|
5
|
+
let zod = require("zod");
|
|
6
|
+
//#region src/zod/index.ts
|
|
7
|
+
/**
|
|
8
|
+
* Zod adapter for `@alikhalilll/a-tel-input`.
|
|
9
|
+
*
|
|
10
|
+
* Wraps the same `usePhoneValidation()` engine the component uses, so a Zod schema
|
|
11
|
+
* never disagrees with the visual validation state in the field.
|
|
12
|
+
*
|
|
13
|
+
* Three usage shapes, all backed by libphonenumber-js:
|
|
14
|
+
*
|
|
15
|
+
* 1. **E.164 string** (default) — validate a full international number:
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { zPhone } from '@alikhalilll/a-tel-input/zod';
|
|
19
|
+
* const schema = z.object({ phone: zPhone() });
|
|
20
|
+
* schema.parse({ phone: '+201234567890' });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* 2. **National number for a fixed country** — when you already know the country (e.g.,
|
|
24
|
+
* a Saudi-only form) and only need to validate the digits the user typed:
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* const schema = z.object({ phone: zPhone({ country: 'SA' }) });
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* 3. **Combined object** — matches ATelInput's two v-models out of the box:
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { zPhoneObject } from '@alikhalilll/a-tel-input/zod';
|
|
34
|
+
* const schema = z.object({
|
|
35
|
+
* contact: zPhoneObject(), // -> { phone: string, country: number | null }
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* All three honour `allowedDialCodes` and produce a `PhoneValidationReason` localised
|
|
40
|
+
* through the same `DEFAULT_ERROR_MESSAGES` map as the component, so error wording is
|
|
41
|
+
* consistent across UI + schema.
|
|
42
|
+
*
|
|
43
|
+
* `zod` is an **optional peer dependency** — install it yourself in your app.
|
|
44
|
+
*/
|
|
45
|
+
function messageFor(reason, overrides) {
|
|
46
|
+
return overrides?.[reason] ?? require_types.DEFAULT_ERROR_MESSAGES[reason];
|
|
47
|
+
}
|
|
48
|
+
function failsAllowList(dialDigits, allowed) {
|
|
49
|
+
if (!allowed || allowed.length === 0) return false;
|
|
50
|
+
return !allowed.includes(dialDigits);
|
|
51
|
+
}
|
|
52
|
+
/** Run the same validate() the component uses, but resolve country from an E.164 string. */
|
|
53
|
+
function validateE164(value, v, locale) {
|
|
54
|
+
const trimmed = String(value ?? "").trim();
|
|
55
|
+
if (!trimmed) return {
|
|
56
|
+
ok: false,
|
|
57
|
+
reason: "invalid_phone",
|
|
58
|
+
country: null,
|
|
59
|
+
phone: {
|
|
60
|
+
raw: value,
|
|
61
|
+
digits: ""
|
|
62
|
+
},
|
|
63
|
+
full_phone: null,
|
|
64
|
+
required: null
|
|
65
|
+
};
|
|
66
|
+
const parsed = (0, libphonenumber_js.parsePhoneNumberFromString)(trimmed.startsWith("+") ? trimmed : `+${trimmed}`);
|
|
67
|
+
if (!parsed || !parsed.country) return {
|
|
68
|
+
ok: false,
|
|
69
|
+
reason: "parse_failed",
|
|
70
|
+
country: null,
|
|
71
|
+
phone: {
|
|
72
|
+
raw: value,
|
|
73
|
+
digits: ""
|
|
74
|
+
},
|
|
75
|
+
full_phone: null,
|
|
76
|
+
required: null
|
|
77
|
+
};
|
|
78
|
+
return v.validate({
|
|
79
|
+
country: { iso2: parsed.country },
|
|
80
|
+
phone: parsed.nationalNumber,
|
|
81
|
+
locale
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Zod schema for a phone string.
|
|
86
|
+
*
|
|
87
|
+
* @see {@link ZPhoneOptions} for behaviour modes.
|
|
88
|
+
*/
|
|
89
|
+
function zPhone(options = {}) {
|
|
90
|
+
let _v = null;
|
|
91
|
+
const getV = () => {
|
|
92
|
+
if (!_v) {
|
|
93
|
+
_v = require_usePhoneValidation.usePhoneValidation();
|
|
94
|
+
_v.getCountries();
|
|
95
|
+
}
|
|
96
|
+
return _v;
|
|
97
|
+
};
|
|
98
|
+
return zod.z.string().superRefine((value, ctx) => {
|
|
99
|
+
const v = getV();
|
|
100
|
+
if (!value || !String(value).trim()) {
|
|
101
|
+
if (options.requiredMessage) ctx.addIssue({
|
|
102
|
+
code: zod.z.ZodIssueCode.custom,
|
|
103
|
+
message: options.requiredMessage
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const result = options.country ? v.validate({
|
|
108
|
+
country: { iso2: options.country },
|
|
109
|
+
phone: value,
|
|
110
|
+
locale: options.locale
|
|
111
|
+
}) : validateE164(value, v, options.locale);
|
|
112
|
+
if (!result.ok) {
|
|
113
|
+
const reason = result.reason ?? "invalid_phone";
|
|
114
|
+
ctx.addIssue({
|
|
115
|
+
code: zod.z.ZodIssueCode.custom,
|
|
116
|
+
message: messageFor(reason, options.messages),
|
|
117
|
+
params: {
|
|
118
|
+
reason,
|
|
119
|
+
validation: result
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (failsAllowList(result.country?.dial_code?.replace(/^\+/, "") ?? "", options.allowedDialCodes)) ctx.addIssue({
|
|
125
|
+
code: zod.z.ZodIssueCode.custom,
|
|
126
|
+
message: messageFor("country_not_supported", options.messages),
|
|
127
|
+
params: {
|
|
128
|
+
reason: "country_not_supported",
|
|
129
|
+
validation: result
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Zod schema for the `{ phone, country }` object shape matching `ATelInput`'s two
|
|
136
|
+
* v-models. `phone` is the digits-only national number; `country` is the dial-digit
|
|
137
|
+
* **number** (e.g. `20` for Egypt). NANP (`+1`) maps to `'US'` for validation purposes,
|
|
138
|
+
* which is correct for `isValidPhoneNumber` (all NANP countries share the same rule set).
|
|
139
|
+
*/
|
|
140
|
+
function zPhoneObject(options = {}) {
|
|
141
|
+
let _v = null;
|
|
142
|
+
const getV = () => {
|
|
143
|
+
if (!_v) {
|
|
144
|
+
_v = require_usePhoneValidation.usePhoneValidation();
|
|
145
|
+
_v.getCountries();
|
|
146
|
+
}
|
|
147
|
+
return _v;
|
|
148
|
+
};
|
|
149
|
+
return zod.z.object({
|
|
150
|
+
phone: zod.z.string(),
|
|
151
|
+
country: zod.z.number().nullable()
|
|
152
|
+
}).superRefine((input, ctx) => {
|
|
153
|
+
const v = getV();
|
|
154
|
+
const phone = (input.phone ?? "").trim();
|
|
155
|
+
const country = input.country;
|
|
156
|
+
if (!phone) {
|
|
157
|
+
if (options.requiredMessage) ctx.addIssue({
|
|
158
|
+
code: zod.z.ZodIssueCode.custom,
|
|
159
|
+
path: ["phone"],
|
|
160
|
+
message: options.requiredMessage
|
|
161
|
+
});
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (country == null) {
|
|
165
|
+
ctx.addIssue({
|
|
166
|
+
code: zod.z.ZodIssueCode.custom,
|
|
167
|
+
path: ["country"],
|
|
168
|
+
message: messageFor("missing_country", options.messages)
|
|
169
|
+
});
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const iso2 = v.getCountriesByDial(String(country))[0]?.value ?? options.country;
|
|
173
|
+
if (!iso2) {
|
|
174
|
+
ctx.addIssue({
|
|
175
|
+
code: zod.z.ZodIssueCode.custom,
|
|
176
|
+
path: ["country"],
|
|
177
|
+
message: messageFor("country_not_supported", options.messages)
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const result = v.validate({
|
|
182
|
+
country: { iso2 },
|
|
183
|
+
phone,
|
|
184
|
+
locale: options.locale
|
|
185
|
+
});
|
|
186
|
+
if (!result.ok) {
|
|
187
|
+
const reason = result.reason ?? "invalid_phone";
|
|
188
|
+
ctx.addIssue({
|
|
189
|
+
code: zod.z.ZodIssueCode.custom,
|
|
190
|
+
path: ["phone"],
|
|
191
|
+
message: messageFor(reason, options.messages),
|
|
192
|
+
params: {
|
|
193
|
+
reason,
|
|
194
|
+
validation: result
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (failsAllowList(String(country), options.allowedDialCodes)) ctx.addIssue({
|
|
200
|
+
code: zod.z.ZodIssueCode.custom,
|
|
201
|
+
path: ["country"],
|
|
202
|
+
message: messageFor("country_not_supported", options.messages)
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
//#endregion
|
|
207
|
+
exports.DEFAULT_ERROR_MESSAGES = require_types.DEFAULT_ERROR_MESSAGES;
|
|
208
|
+
exports.zPhone = zPhone;
|
|
209
|
+
exports.zPhoneObject = zPhoneObject;
|
|
210
|
+
|
|
211
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["DEFAULT_ERROR_MESSAGES","usePhoneValidation","z"],"sources":["../../src/zod/index.ts"],"sourcesContent":["/**\n * Zod adapter for `@alikhalilll/a-tel-input`.\n *\n * Wraps the same `usePhoneValidation()` engine the component uses, so a Zod schema\n * never disagrees with the visual validation state in the field.\n *\n * Three usage shapes, all backed by libphonenumber-js:\n *\n * 1. **E.164 string** (default) — validate a full international number:\n *\n * ```ts\n * import { zPhone } from '@alikhalilll/a-tel-input/zod';\n * const schema = z.object({ phone: zPhone() });\n * schema.parse({ phone: '+201234567890' });\n * ```\n *\n * 2. **National number for a fixed country** — when you already know the country (e.g.,\n * a Saudi-only form) and only need to validate the digits the user typed:\n *\n * ```ts\n * const schema = z.object({ phone: zPhone({ country: 'SA' }) });\n * ```\n *\n * 3. **Combined object** — matches ATelInput's two v-models out of the box:\n *\n * ```ts\n * import { zPhoneObject } from '@alikhalilll/a-tel-input/zod';\n * const schema = z.object({\n * contact: zPhoneObject(), // -> { phone: string, country: number | null }\n * });\n * ```\n *\n * All three honour `allowedDialCodes` and produce a `PhoneValidationReason` localised\n * through the same `DEFAULT_ERROR_MESSAGES` map as the component, so error wording is\n * consistent across UI + schema.\n *\n * `zod` is an **optional peer dependency** — install it yourself in your app.\n */\n\nimport { z } from 'zod';\nimport { parsePhoneNumberFromString } from 'libphonenumber-js';\nimport {\n usePhoneValidation,\n type PhoneValidationReason,\n type PhoneValidationResult,\n} from '../composables/usePhoneValidation';\nimport { DEFAULT_ERROR_MESSAGES } from '../types';\n\nexport interface ZPhoneOptions {\n /**\n * ISO 3166-1 alpha-2 country code (`'EG'`, `'SA'`, …). When set, the input is treated\n * as a national number for that country. Leave undefined to validate a full E.164\n * string (the country is inferred from the leading `+` dial code).\n */\n country?: string;\n /**\n * Whitelist of allowed dial-digit codes (no `+`), e.g. `['20', '966']`. Numbers from\n * countries outside this list fail with `country_not_supported`. Matches the\n * `allowedDialCodes` prop on `ATelInput`.\n */\n allowedDialCodes?: string[];\n /**\n * BCP-47 locale, forwarded to the validator. Doesn't change the validity outcome —\n * only affects locale-dependent fields on the underlying validation result.\n */\n locale?: string;\n /**\n * Override the error messages used when the Zod issue is raised. Keyed by validation\n * reason. Falls back to the same English defaults the component uses.\n */\n messages?: Partial<Record<PhoneValidationReason, string>>;\n /**\n * Custom message for the \"empty input\" case. By default the schema accepts an empty\n * string — wrap with `z.string().min(1)` upstream if you want \"required\" semantics,\n * or pass a non-empty `requiredMessage` to enforce it here.\n */\n requiredMessage?: string;\n}\n\nfunction messageFor(reason: PhoneValidationReason, overrides?: ZPhoneOptions['messages']) {\n return overrides?.[reason] ?? DEFAULT_ERROR_MESSAGES[reason];\n}\n\nfunction failsAllowList(dialDigits: string, allowed?: string[]) {\n if (!allowed || allowed.length === 0) return false;\n return !allowed.includes(dialDigits);\n}\n\n/** Run the same validate() the component uses, but resolve country from an E.164 string. */\nfunction validateE164(\n value: string,\n v: ReturnType<typeof usePhoneValidation>,\n locale?: string\n): PhoneValidationResult {\n const trimmed = String(value ?? '').trim();\n if (!trimmed) {\n return {\n ok: false,\n reason: 'invalid_phone',\n country: null,\n phone: { raw: value, digits: '' },\n full_phone: null,\n required: null,\n };\n }\n // libphonenumber wants a leading `+` to skip the country-hint requirement.\n const e164 = trimmed.startsWith('+') ? trimmed : `+${trimmed}`;\n const parsed = parsePhoneNumberFromString(e164);\n if (!parsed || !parsed.country) {\n return {\n ok: false,\n reason: 'parse_failed',\n country: null,\n phone: { raw: value, digits: '' },\n full_phone: null,\n required: null,\n };\n }\n return v.validate({\n country: { iso2: parsed.country },\n phone: parsed.nationalNumber,\n locale,\n });\n}\n\n/**\n * Zod schema for a phone string.\n *\n * @see {@link ZPhoneOptions} for behaviour modes.\n */\nexport function zPhone(options: ZPhoneOptions = {}): z.ZodType<string, z.ZodTypeDef, string> {\n // Lazy: only construct the validator on first parse, so apps that bundle this don't\n // pay the libphonenumber metadata load until they actually validate.\n let _v: ReturnType<typeof usePhoneValidation> | null = null;\n const getV = () => {\n if (!_v) {\n _v = usePhoneValidation();\n void _v.getCountries();\n }\n return _v;\n };\n\n return z.string().superRefine((value, ctx) => {\n const v = getV();\n const isEmpty = !value || !String(value).trim();\n\n if (isEmpty) {\n if (options.requiredMessage) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: options.requiredMessage });\n }\n return;\n }\n\n const result: PhoneValidationResult = options.country\n ? v.validate({ country: { iso2: options.country }, phone: value, locale: options.locale })\n : validateE164(value, v, options.locale);\n\n if (!result.ok) {\n const reason = result.reason ?? 'invalid_phone';\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: messageFor(reason, options.messages),\n params: { reason, validation: result },\n });\n return;\n }\n\n const dialDigits = result.country?.dial_code?.replace(/^\\+/, '') ?? '';\n if (failsAllowList(dialDigits, options.allowedDialCodes)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: messageFor('country_not_supported', options.messages),\n params: { reason: 'country_not_supported' as PhoneValidationReason, validation: result },\n });\n }\n });\n}\n\n/**\n * Zod schema for the `{ phone, country }` object shape matching `ATelInput`'s two\n * v-models. `phone` is the digits-only national number; `country` is the dial-digit\n * **number** (e.g. `20` for Egypt). NANP (`+1`) maps to `'US'` for validation purposes,\n * which is correct for `isValidPhoneNumber` (all NANP countries share the same rule set).\n */\nexport function zPhoneObject(options: ZPhoneOptions = {}) {\n let _v: ReturnType<typeof usePhoneValidation> | null = null;\n const getV = () => {\n if (!_v) {\n _v = usePhoneValidation();\n void _v.getCountries();\n }\n return _v;\n };\n\n return z\n .object({\n phone: z.string(),\n country: z.number().nullable(),\n })\n .superRefine((input, ctx) => {\n const v = getV();\n const phone = (input.phone ?? '').trim();\n const country = input.country;\n\n if (!phone) {\n if (options.requiredMessage) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['phone'],\n message: options.requiredMessage,\n });\n }\n return;\n }\n\n if (country == null) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['country'],\n message: messageFor('missing_country', options.messages),\n });\n return;\n }\n\n const matches = v.getCountriesByDial(String(country));\n const iso2 = matches[0]?.value ?? options.country;\n if (!iso2) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['country'],\n message: messageFor('country_not_supported', options.messages),\n });\n return;\n }\n\n const result = v.validate({ country: { iso2 }, phone, locale: options.locale });\n if (!result.ok) {\n const reason = result.reason ?? 'invalid_phone';\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['phone'],\n message: messageFor(reason, options.messages),\n params: { reason, validation: result },\n });\n return;\n }\n\n if (failsAllowList(String(country), options.allowedDialCodes)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['country'],\n message: messageFor('country_not_supported', options.messages),\n });\n }\n });\n}\n\nexport { DEFAULT_ERROR_MESSAGES };\nexport type { PhoneValidationReason, PhoneValidationResult };\n"],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { O as PhoneValidationReason, f as DEFAULT_ERROR_MESSAGES, k as PhoneValidationResult } from "../_chunks/types.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
//#region src/zod/index.d.ts
|
|
5
|
+
interface ZPhoneOptions {
|
|
6
|
+
/**
|
|
7
|
+
* ISO 3166-1 alpha-2 country code (`'EG'`, `'SA'`, …). When set, the input is treated
|
|
8
|
+
* as a national number for that country. Leave undefined to validate a full E.164
|
|
9
|
+
* string (the country is inferred from the leading `+` dial code).
|
|
10
|
+
*/
|
|
11
|
+
country?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Whitelist of allowed dial-digit codes (no `+`), e.g. `['20', '966']`. Numbers from
|
|
14
|
+
* countries outside this list fail with `country_not_supported`. Matches the
|
|
15
|
+
* `allowedDialCodes` prop on `ATelInput`.
|
|
16
|
+
*/
|
|
17
|
+
allowedDialCodes?: string[];
|
|
18
|
+
/**
|
|
19
|
+
* BCP-47 locale, forwarded to the validator. Doesn't change the validity outcome —
|
|
20
|
+
* only affects locale-dependent fields on the underlying validation result.
|
|
21
|
+
*/
|
|
22
|
+
locale?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Override the error messages used when the Zod issue is raised. Keyed by validation
|
|
25
|
+
* reason. Falls back to the same English defaults the component uses.
|
|
26
|
+
*/
|
|
27
|
+
messages?: Partial<Record<PhoneValidationReason, string>>;
|
|
28
|
+
/**
|
|
29
|
+
* Custom message for the "empty input" case. By default the schema accepts an empty
|
|
30
|
+
* string — wrap with `z.string().min(1)` upstream if you want "required" semantics,
|
|
31
|
+
* or pass a non-empty `requiredMessage` to enforce it here.
|
|
32
|
+
*/
|
|
33
|
+
requiredMessage?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Zod schema for a phone string.
|
|
37
|
+
*
|
|
38
|
+
* @see {@link ZPhoneOptions} for behaviour modes.
|
|
39
|
+
*/
|
|
40
|
+
declare function zPhone(options?: ZPhoneOptions): z.ZodType<string, z.ZodTypeDef, string>;
|
|
41
|
+
/**
|
|
42
|
+
* Zod schema for the `{ phone, country }` object shape matching `ATelInput`'s two
|
|
43
|
+
* v-models. `phone` is the digits-only national number; `country` is the dial-digit
|
|
44
|
+
* **number** (e.g. `20` for Egypt). NANP (`+1`) maps to `'US'` for validation purposes,
|
|
45
|
+
* which is correct for `isValidPhoneNumber` (all NANP countries share the same rule set).
|
|
46
|
+
*/
|
|
47
|
+
declare function zPhoneObject(options?: ZPhoneOptions): z.ZodEffects<z.ZodObject<{
|
|
48
|
+
phone: z.ZodString;
|
|
49
|
+
country: z.ZodNullable<z.ZodNumber>;
|
|
50
|
+
}, "strip", z.ZodTypeAny, {
|
|
51
|
+
phone: string;
|
|
52
|
+
country: number | null;
|
|
53
|
+
}, {
|
|
54
|
+
phone: string;
|
|
55
|
+
country: number | null;
|
|
56
|
+
}>, {
|
|
57
|
+
phone: string;
|
|
58
|
+
country: number | null;
|
|
59
|
+
}, {
|
|
60
|
+
phone: string;
|
|
61
|
+
country: number | null;
|
|
62
|
+
}>;
|
|
63
|
+
//#endregion
|
|
64
|
+
export { DEFAULT_ERROR_MESSAGES, type PhoneValidationReason, type PhoneValidationResult, ZPhoneOptions, zPhone, zPhoneObject };
|
|
65
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { O as PhoneValidationReason, f as DEFAULT_ERROR_MESSAGES, k as PhoneValidationResult } from "../_chunks/types.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
//#region src/zod/index.d.ts
|
|
5
|
+
interface ZPhoneOptions {
|
|
6
|
+
/**
|
|
7
|
+
* ISO 3166-1 alpha-2 country code (`'EG'`, `'SA'`, …). When set, the input is treated
|
|
8
|
+
* as a national number for that country. Leave undefined to validate a full E.164
|
|
9
|
+
* string (the country is inferred from the leading `+` dial code).
|
|
10
|
+
*/
|
|
11
|
+
country?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Whitelist of allowed dial-digit codes (no `+`), e.g. `['20', '966']`. Numbers from
|
|
14
|
+
* countries outside this list fail with `country_not_supported`. Matches the
|
|
15
|
+
* `allowedDialCodes` prop on `ATelInput`.
|
|
16
|
+
*/
|
|
17
|
+
allowedDialCodes?: string[];
|
|
18
|
+
/**
|
|
19
|
+
* BCP-47 locale, forwarded to the validator. Doesn't change the validity outcome —
|
|
20
|
+
* only affects locale-dependent fields on the underlying validation result.
|
|
21
|
+
*/
|
|
22
|
+
locale?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Override the error messages used when the Zod issue is raised. Keyed by validation
|
|
25
|
+
* reason. Falls back to the same English defaults the component uses.
|
|
26
|
+
*/
|
|
27
|
+
messages?: Partial<Record<PhoneValidationReason, string>>;
|
|
28
|
+
/**
|
|
29
|
+
* Custom message for the "empty input" case. By default the schema accepts an empty
|
|
30
|
+
* string — wrap with `z.string().min(1)` upstream if you want "required" semantics,
|
|
31
|
+
* or pass a non-empty `requiredMessage` to enforce it here.
|
|
32
|
+
*/
|
|
33
|
+
requiredMessage?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Zod schema for a phone string.
|
|
37
|
+
*
|
|
38
|
+
* @see {@link ZPhoneOptions} for behaviour modes.
|
|
39
|
+
*/
|
|
40
|
+
declare function zPhone(options?: ZPhoneOptions): z.ZodType<string, z.ZodTypeDef, string>;
|
|
41
|
+
/**
|
|
42
|
+
* Zod schema for the `{ phone, country }` object shape matching `ATelInput`'s two
|
|
43
|
+
* v-models. `phone` is the digits-only national number; `country` is the dial-digit
|
|
44
|
+
* **number** (e.g. `20` for Egypt). NANP (`+1`) maps to `'US'` for validation purposes,
|
|
45
|
+
* which is correct for `isValidPhoneNumber` (all NANP countries share the same rule set).
|
|
46
|
+
*/
|
|
47
|
+
declare function zPhoneObject(options?: ZPhoneOptions): z.ZodEffects<z.ZodObject<{
|
|
48
|
+
phone: z.ZodString;
|
|
49
|
+
country: z.ZodNullable<z.ZodNumber>;
|
|
50
|
+
}, "strip", z.ZodTypeAny, {
|
|
51
|
+
phone: string;
|
|
52
|
+
country: number | null;
|
|
53
|
+
}, {
|
|
54
|
+
phone: string;
|
|
55
|
+
country: number | null;
|
|
56
|
+
}>, {
|
|
57
|
+
phone: string;
|
|
58
|
+
country: number | null;
|
|
59
|
+
}, {
|
|
60
|
+
phone: string;
|
|
61
|
+
country: number | null;
|
|
62
|
+
}>;
|
|
63
|
+
//#endregion
|
|
64
|
+
export { DEFAULT_ERROR_MESSAGES, type PhoneValidationReason, type PhoneValidationResult, ZPhoneOptions, zPhone, zPhoneObject };
|
|
65
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { n as usePhoneValidation } from "../_chunks/usePhoneValidation.js";
|
|
2
|
+
import { t as DEFAULT_ERROR_MESSAGES } from "../_chunks/types.js";
|
|
3
|
+
import { parsePhoneNumberFromString } from "libphonenumber-js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
//#region src/zod/index.ts
|
|
6
|
+
/**
|
|
7
|
+
* Zod adapter for `@alikhalilll/a-tel-input`.
|
|
8
|
+
*
|
|
9
|
+
* Wraps the same `usePhoneValidation()` engine the component uses, so a Zod schema
|
|
10
|
+
* never disagrees with the visual validation state in the field.
|
|
11
|
+
*
|
|
12
|
+
* Three usage shapes, all backed by libphonenumber-js:
|
|
13
|
+
*
|
|
14
|
+
* 1. **E.164 string** (default) — validate a full international number:
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { zPhone } from '@alikhalilll/a-tel-input/zod';
|
|
18
|
+
* const schema = z.object({ phone: zPhone() });
|
|
19
|
+
* schema.parse({ phone: '+201234567890' });
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* 2. **National number for a fixed country** — when you already know the country (e.g.,
|
|
23
|
+
* a Saudi-only form) and only need to validate the digits the user typed:
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* const schema = z.object({ phone: zPhone({ country: 'SA' }) });
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* 3. **Combined object** — matches ATelInput's two v-models out of the box:
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { zPhoneObject } from '@alikhalilll/a-tel-input/zod';
|
|
33
|
+
* const schema = z.object({
|
|
34
|
+
* contact: zPhoneObject(), // -> { phone: string, country: number | null }
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* All three honour `allowedDialCodes` and produce a `PhoneValidationReason` localised
|
|
39
|
+
* through the same `DEFAULT_ERROR_MESSAGES` map as the component, so error wording is
|
|
40
|
+
* consistent across UI + schema.
|
|
41
|
+
*
|
|
42
|
+
* `zod` is an **optional peer dependency** — install it yourself in your app.
|
|
43
|
+
*/
|
|
44
|
+
function messageFor(reason, overrides) {
|
|
45
|
+
return overrides?.[reason] ?? DEFAULT_ERROR_MESSAGES[reason];
|
|
46
|
+
}
|
|
47
|
+
function failsAllowList(dialDigits, allowed) {
|
|
48
|
+
if (!allowed || allowed.length === 0) return false;
|
|
49
|
+
return !allowed.includes(dialDigits);
|
|
50
|
+
}
|
|
51
|
+
/** Run the same validate() the component uses, but resolve country from an E.164 string. */
|
|
52
|
+
function validateE164(value, v, locale) {
|
|
53
|
+
const trimmed = String(value ?? "").trim();
|
|
54
|
+
if (!trimmed) return {
|
|
55
|
+
ok: false,
|
|
56
|
+
reason: "invalid_phone",
|
|
57
|
+
country: null,
|
|
58
|
+
phone: {
|
|
59
|
+
raw: value,
|
|
60
|
+
digits: ""
|
|
61
|
+
},
|
|
62
|
+
full_phone: null,
|
|
63
|
+
required: null
|
|
64
|
+
};
|
|
65
|
+
const parsed = parsePhoneNumberFromString(trimmed.startsWith("+") ? trimmed : `+${trimmed}`);
|
|
66
|
+
if (!parsed || !parsed.country) return {
|
|
67
|
+
ok: false,
|
|
68
|
+
reason: "parse_failed",
|
|
69
|
+
country: null,
|
|
70
|
+
phone: {
|
|
71
|
+
raw: value,
|
|
72
|
+
digits: ""
|
|
73
|
+
},
|
|
74
|
+
full_phone: null,
|
|
75
|
+
required: null
|
|
76
|
+
};
|
|
77
|
+
return v.validate({
|
|
78
|
+
country: { iso2: parsed.country },
|
|
79
|
+
phone: parsed.nationalNumber,
|
|
80
|
+
locale
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Zod schema for a phone string.
|
|
85
|
+
*
|
|
86
|
+
* @see {@link ZPhoneOptions} for behaviour modes.
|
|
87
|
+
*/
|
|
88
|
+
function zPhone(options = {}) {
|
|
89
|
+
let _v = null;
|
|
90
|
+
const getV = () => {
|
|
91
|
+
if (!_v) {
|
|
92
|
+
_v = usePhoneValidation();
|
|
93
|
+
_v.getCountries();
|
|
94
|
+
}
|
|
95
|
+
return _v;
|
|
96
|
+
};
|
|
97
|
+
return z.string().superRefine((value, ctx) => {
|
|
98
|
+
const v = getV();
|
|
99
|
+
if (!value || !String(value).trim()) {
|
|
100
|
+
if (options.requiredMessage) ctx.addIssue({
|
|
101
|
+
code: z.ZodIssueCode.custom,
|
|
102
|
+
message: options.requiredMessage
|
|
103
|
+
});
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const result = options.country ? v.validate({
|
|
107
|
+
country: { iso2: options.country },
|
|
108
|
+
phone: value,
|
|
109
|
+
locale: options.locale
|
|
110
|
+
}) : validateE164(value, v, options.locale);
|
|
111
|
+
if (!result.ok) {
|
|
112
|
+
const reason = result.reason ?? "invalid_phone";
|
|
113
|
+
ctx.addIssue({
|
|
114
|
+
code: z.ZodIssueCode.custom,
|
|
115
|
+
message: messageFor(reason, options.messages),
|
|
116
|
+
params: {
|
|
117
|
+
reason,
|
|
118
|
+
validation: result
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (failsAllowList(result.country?.dial_code?.replace(/^\+/, "") ?? "", options.allowedDialCodes)) ctx.addIssue({
|
|
124
|
+
code: z.ZodIssueCode.custom,
|
|
125
|
+
message: messageFor("country_not_supported", options.messages),
|
|
126
|
+
params: {
|
|
127
|
+
reason: "country_not_supported",
|
|
128
|
+
validation: result
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Zod schema for the `{ phone, country }` object shape matching `ATelInput`'s two
|
|
135
|
+
* v-models. `phone` is the digits-only national number; `country` is the dial-digit
|
|
136
|
+
* **number** (e.g. `20` for Egypt). NANP (`+1`) maps to `'US'` for validation purposes,
|
|
137
|
+
* which is correct for `isValidPhoneNumber` (all NANP countries share the same rule set).
|
|
138
|
+
*/
|
|
139
|
+
function zPhoneObject(options = {}) {
|
|
140
|
+
let _v = null;
|
|
141
|
+
const getV = () => {
|
|
142
|
+
if (!_v) {
|
|
143
|
+
_v = usePhoneValidation();
|
|
144
|
+
_v.getCountries();
|
|
145
|
+
}
|
|
146
|
+
return _v;
|
|
147
|
+
};
|
|
148
|
+
return z.object({
|
|
149
|
+
phone: z.string(),
|
|
150
|
+
country: z.number().nullable()
|
|
151
|
+
}).superRefine((input, ctx) => {
|
|
152
|
+
const v = getV();
|
|
153
|
+
const phone = (input.phone ?? "").trim();
|
|
154
|
+
const country = input.country;
|
|
155
|
+
if (!phone) {
|
|
156
|
+
if (options.requiredMessage) ctx.addIssue({
|
|
157
|
+
code: z.ZodIssueCode.custom,
|
|
158
|
+
path: ["phone"],
|
|
159
|
+
message: options.requiredMessage
|
|
160
|
+
});
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (country == null) {
|
|
164
|
+
ctx.addIssue({
|
|
165
|
+
code: z.ZodIssueCode.custom,
|
|
166
|
+
path: ["country"],
|
|
167
|
+
message: messageFor("missing_country", options.messages)
|
|
168
|
+
});
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const iso2 = v.getCountriesByDial(String(country))[0]?.value ?? options.country;
|
|
172
|
+
if (!iso2) {
|
|
173
|
+
ctx.addIssue({
|
|
174
|
+
code: z.ZodIssueCode.custom,
|
|
175
|
+
path: ["country"],
|
|
176
|
+
message: messageFor("country_not_supported", options.messages)
|
|
177
|
+
});
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const result = v.validate({
|
|
181
|
+
country: { iso2 },
|
|
182
|
+
phone,
|
|
183
|
+
locale: options.locale
|
|
184
|
+
});
|
|
185
|
+
if (!result.ok) {
|
|
186
|
+
const reason = result.reason ?? "invalid_phone";
|
|
187
|
+
ctx.addIssue({
|
|
188
|
+
code: z.ZodIssueCode.custom,
|
|
189
|
+
path: ["phone"],
|
|
190
|
+
message: messageFor(reason, options.messages),
|
|
191
|
+
params: {
|
|
192
|
+
reason,
|
|
193
|
+
validation: result
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (failsAllowList(String(country), options.allowedDialCodes)) ctx.addIssue({
|
|
199
|
+
code: z.ZodIssueCode.custom,
|
|
200
|
+
path: ["country"],
|
|
201
|
+
message: messageFor("country_not_supported", options.messages)
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
//#endregion
|
|
206
|
+
export { DEFAULT_ERROR_MESSAGES, zPhone, zPhoneObject };
|
|
207
|
+
|
|
208
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/zod/index.ts"],"sourcesContent":["/**\n * Zod adapter for `@alikhalilll/a-tel-input`.\n *\n * Wraps the same `usePhoneValidation()` engine the component uses, so a Zod schema\n * never disagrees with the visual validation state in the field.\n *\n * Three usage shapes, all backed by libphonenumber-js:\n *\n * 1. **E.164 string** (default) — validate a full international number:\n *\n * ```ts\n * import { zPhone } from '@alikhalilll/a-tel-input/zod';\n * const schema = z.object({ phone: zPhone() });\n * schema.parse({ phone: '+201234567890' });\n * ```\n *\n * 2. **National number for a fixed country** — when you already know the country (e.g.,\n * a Saudi-only form) and only need to validate the digits the user typed:\n *\n * ```ts\n * const schema = z.object({ phone: zPhone({ country: 'SA' }) });\n * ```\n *\n * 3. **Combined object** — matches ATelInput's two v-models out of the box:\n *\n * ```ts\n * import { zPhoneObject } from '@alikhalilll/a-tel-input/zod';\n * const schema = z.object({\n * contact: zPhoneObject(), // -> { phone: string, country: number | null }\n * });\n * ```\n *\n * All three honour `allowedDialCodes` and produce a `PhoneValidationReason` localised\n * through the same `DEFAULT_ERROR_MESSAGES` map as the component, so error wording is\n * consistent across UI + schema.\n *\n * `zod` is an **optional peer dependency** — install it yourself in your app.\n */\n\nimport { z } from 'zod';\nimport { parsePhoneNumberFromString } from 'libphonenumber-js';\nimport {\n usePhoneValidation,\n type PhoneValidationReason,\n type PhoneValidationResult,\n} from '../composables/usePhoneValidation';\nimport { DEFAULT_ERROR_MESSAGES } from '../types';\n\nexport interface ZPhoneOptions {\n /**\n * ISO 3166-1 alpha-2 country code (`'EG'`, `'SA'`, …). When set, the input is treated\n * as a national number for that country. Leave undefined to validate a full E.164\n * string (the country is inferred from the leading `+` dial code).\n */\n country?: string;\n /**\n * Whitelist of allowed dial-digit codes (no `+`), e.g. `['20', '966']`. Numbers from\n * countries outside this list fail with `country_not_supported`. Matches the\n * `allowedDialCodes` prop on `ATelInput`.\n */\n allowedDialCodes?: string[];\n /**\n * BCP-47 locale, forwarded to the validator. Doesn't change the validity outcome —\n * only affects locale-dependent fields on the underlying validation result.\n */\n locale?: string;\n /**\n * Override the error messages used when the Zod issue is raised. Keyed by validation\n * reason. Falls back to the same English defaults the component uses.\n */\n messages?: Partial<Record<PhoneValidationReason, string>>;\n /**\n * Custom message for the \"empty input\" case. By default the schema accepts an empty\n * string — wrap with `z.string().min(1)` upstream if you want \"required\" semantics,\n * or pass a non-empty `requiredMessage` to enforce it here.\n */\n requiredMessage?: string;\n}\n\nfunction messageFor(reason: PhoneValidationReason, overrides?: ZPhoneOptions['messages']) {\n return overrides?.[reason] ?? DEFAULT_ERROR_MESSAGES[reason];\n}\n\nfunction failsAllowList(dialDigits: string, allowed?: string[]) {\n if (!allowed || allowed.length === 0) return false;\n return !allowed.includes(dialDigits);\n}\n\n/** Run the same validate() the component uses, but resolve country from an E.164 string. */\nfunction validateE164(\n value: string,\n v: ReturnType<typeof usePhoneValidation>,\n locale?: string\n): PhoneValidationResult {\n const trimmed = String(value ?? '').trim();\n if (!trimmed) {\n return {\n ok: false,\n reason: 'invalid_phone',\n country: null,\n phone: { raw: value, digits: '' },\n full_phone: null,\n required: null,\n };\n }\n // libphonenumber wants a leading `+` to skip the country-hint requirement.\n const e164 = trimmed.startsWith('+') ? trimmed : `+${trimmed}`;\n const parsed = parsePhoneNumberFromString(e164);\n if (!parsed || !parsed.country) {\n return {\n ok: false,\n reason: 'parse_failed',\n country: null,\n phone: { raw: value, digits: '' },\n full_phone: null,\n required: null,\n };\n }\n return v.validate({\n country: { iso2: parsed.country },\n phone: parsed.nationalNumber,\n locale,\n });\n}\n\n/**\n * Zod schema for a phone string.\n *\n * @see {@link ZPhoneOptions} for behaviour modes.\n */\nexport function zPhone(options: ZPhoneOptions = {}): z.ZodType<string, z.ZodTypeDef, string> {\n // Lazy: only construct the validator on first parse, so apps that bundle this don't\n // pay the libphonenumber metadata load until they actually validate.\n let _v: ReturnType<typeof usePhoneValidation> | null = null;\n const getV = () => {\n if (!_v) {\n _v = usePhoneValidation();\n void _v.getCountries();\n }\n return _v;\n };\n\n return z.string().superRefine((value, ctx) => {\n const v = getV();\n const isEmpty = !value || !String(value).trim();\n\n if (isEmpty) {\n if (options.requiredMessage) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: options.requiredMessage });\n }\n return;\n }\n\n const result: PhoneValidationResult = options.country\n ? v.validate({ country: { iso2: options.country }, phone: value, locale: options.locale })\n : validateE164(value, v, options.locale);\n\n if (!result.ok) {\n const reason = result.reason ?? 'invalid_phone';\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: messageFor(reason, options.messages),\n params: { reason, validation: result },\n });\n return;\n }\n\n const dialDigits = result.country?.dial_code?.replace(/^\\+/, '') ?? '';\n if (failsAllowList(dialDigits, options.allowedDialCodes)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: messageFor('country_not_supported', options.messages),\n params: { reason: 'country_not_supported' as PhoneValidationReason, validation: result },\n });\n }\n });\n}\n\n/**\n * Zod schema for the `{ phone, country }` object shape matching `ATelInput`'s two\n * v-models. `phone` is the digits-only national number; `country` is the dial-digit\n * **number** (e.g. `20` for Egypt). NANP (`+1`) maps to `'US'` for validation purposes,\n * which is correct for `isValidPhoneNumber` (all NANP countries share the same rule set).\n */\nexport function zPhoneObject(options: ZPhoneOptions = {}) {\n let _v: ReturnType<typeof usePhoneValidation> | null = null;\n const getV = () => {\n if (!_v) {\n _v = usePhoneValidation();\n void _v.getCountries();\n }\n return _v;\n };\n\n return z\n .object({\n phone: z.string(),\n country: z.number().nullable(),\n })\n .superRefine((input, ctx) => {\n const v = getV();\n const phone = (input.phone ?? '').trim();\n const country = input.country;\n\n if (!phone) {\n if (options.requiredMessage) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['phone'],\n message: options.requiredMessage,\n });\n }\n return;\n }\n\n if (country == null) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['country'],\n message: messageFor('missing_country', options.messages),\n });\n return;\n }\n\n const matches = v.getCountriesByDial(String(country));\n const iso2 = matches[0]?.value ?? options.country;\n if (!iso2) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['country'],\n message: messageFor('country_not_supported', options.messages),\n });\n return;\n }\n\n const result = v.validate({ country: { iso2 }, phone, locale: options.locale });\n if (!result.ok) {\n const reason = result.reason ?? 'invalid_phone';\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['phone'],\n message: messageFor(reason, options.messages),\n params: { reason, validation: result },\n });\n return;\n }\n\n if (failsAllowList(String(country), options.allowedDialCodes)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['country'],\n message: messageFor('country_not_supported', options.messages),\n });\n }\n });\n}\n\nexport { DEFAULT_ERROR_MESSAGES };\nexport type { PhoneValidationReason, PhoneValidationResult };\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alikhalilll/a-tel-input",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Headless, shadcn-vue style Vue 3 international telephone input with country detection, validation, and a responsive country picker. Part of the @alikhalilll/a-* toolkit.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "alikhalilll",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"url": "git+https://github.com/alikhalilll/ali-nuxt-toolkit.git",
|
|
10
10
|
"directory": "packages/ui-components/ATelInput"
|
|
11
11
|
},
|
|
12
|
-
"homepage": "https://alikhalilll.github.io/ali-nuxt-toolkit/ui",
|
|
12
|
+
"homepage": "https://alikhalilll.github.io/ali-nuxt-toolkit/ui/tel-input/#install",
|
|
13
13
|
"bugs": {
|
|
14
14
|
"url": "https://github.com/alikhalilll/ali-nuxt-toolkit/issues"
|
|
15
15
|
},
|
|
@@ -61,6 +61,26 @@
|
|
|
61
61
|
"default": "./dist/resolver/index.cjs"
|
|
62
62
|
}
|
|
63
63
|
},
|
|
64
|
+
"./vee-validate": {
|
|
65
|
+
"import": {
|
|
66
|
+
"types": "./dist/vee-validate/index.d.ts",
|
|
67
|
+
"default": "./dist/vee-validate/index.js"
|
|
68
|
+
},
|
|
69
|
+
"require": {
|
|
70
|
+
"types": "./dist/vee-validate/index.d.cts",
|
|
71
|
+
"default": "./dist/vee-validate/index.cjs"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"./zod": {
|
|
75
|
+
"import": {
|
|
76
|
+
"types": "./dist/zod/index.d.ts",
|
|
77
|
+
"default": "./dist/zod/index.js"
|
|
78
|
+
},
|
|
79
|
+
"require": {
|
|
80
|
+
"types": "./dist/zod/index.d.cts",
|
|
81
|
+
"default": "./dist/zod/index.cjs"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
64
84
|
"./styles.css": "./dist/styles.css",
|
|
65
85
|
"./package.json": "./package.json"
|
|
66
86
|
},
|
|
@@ -74,6 +94,7 @@
|
|
|
74
94
|
"files": [
|
|
75
95
|
"dist",
|
|
76
96
|
"src",
|
|
97
|
+
".media",
|
|
77
98
|
"web-types.json",
|
|
78
99
|
"README.md",
|
|
79
100
|
"LICENSE"
|
|
@@ -85,7 +106,9 @@
|
|
|
85
106
|
"@nuxt/kit": "^3.0.0 || ^4.0.0",
|
|
86
107
|
"@vueuse/core": "^14.0.0",
|
|
87
108
|
"unplugin-vue-components": "^28.0.0 || ^29.0.0 || ^30.0.0 || ^31.0.0 || ^32.0.0",
|
|
88
|
-
"
|
|
109
|
+
"vee-validate": "^4.13.0",
|
|
110
|
+
"vue": "^3.5.0",
|
|
111
|
+
"zod": "^3.23.0 || ^4.0.0"
|
|
89
112
|
},
|
|
90
113
|
"peerDependenciesMeta": {
|
|
91
114
|
"@nuxt/kit": {
|
|
@@ -93,6 +116,12 @@
|
|
|
93
116
|
},
|
|
94
117
|
"unplugin-vue-components": {
|
|
95
118
|
"optional": true
|
|
119
|
+
},
|
|
120
|
+
"vee-validate": {
|
|
121
|
+
"optional": true
|
|
122
|
+
},
|
|
123
|
+
"zod": {
|
|
124
|
+
"optional": true
|
|
96
125
|
}
|
|
97
126
|
},
|
|
98
127
|
"dependencies": {
|
|
@@ -114,6 +143,8 @@
|
|
|
114
143
|
"unplugin-vue-components": "^32.1.0",
|
|
115
144
|
"vue": "^3.5.0",
|
|
116
145
|
"vue-tsc": "^3.2.4",
|
|
146
|
+
"vee-validate": "^4.13.0",
|
|
147
|
+
"zod": "^3.23.8",
|
|
117
148
|
"@alikhalilll/a-ui-base": "1.0.0",
|
|
118
149
|
"@alikhalilll/a-responsive-popover": "1.0.1"
|
|
119
150
|
},
|