@overgaming/valiform 0.1.13 → 0.2.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/README.md CHANGED
@@ -0,0 +1,296 @@
1
+ # Valiform
2
+
3
+ Lightweight, headless form validation library for Vue 3. Framework-agnostic validation rules, composable field state, and full localization support — with zero UI assumptions.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @overgaming/valiform
9
+ ```
10
+
11
+ ## Setup
12
+
13
+ ### Vue
14
+
15
+ ```ts
16
+ import { createApp } from 'vue';
17
+ import { FormsPlugin } from '@overgaming/valiform';
18
+ import { es } from '@overgaming/valiform/locales';
19
+
20
+ const app = createApp(App);
21
+
22
+ app.use(FormsPlugin, {
23
+ locale: 'es',
24
+ locales: { es }
25
+ });
26
+ ```
27
+
28
+ ### Nuxt
29
+
30
+ ```ts
31
+ // nuxt.config.ts
32
+ export default defineNuxtConfig({
33
+ modules: ['@overgaming/valiform/nuxt'],
34
+ valiform: {
35
+ locale: 'es',
36
+ locales: { es }
37
+ }
38
+ });
39
+ ```
40
+
41
+ The Nuxt module auto-imports `<Form>`, `<Field>`, `useLocale`, `useFormContext` and `useFieldContext`.
42
+
43
+ ---
44
+
45
+ ## Usage
46
+
47
+ ### With custom components (recommended)
48
+
49
+ Build a custom input component using `useFieldContext` and `defineModel`:
50
+
51
+ ```vue
52
+ <!-- MyInput.vue -->
53
+ <template>
54
+ <input
55
+ class="my-input"
56
+ :class="{ 'my-input--error': error }"
57
+ v-model.trim="inputValue"
58
+ v-bind="inputProps"
59
+ :type
60
+ />
61
+ </template>
62
+
63
+ <script setup lang="ts">
64
+ import { useFieldContext } from '@overgaming/valiform';
65
+
66
+ defineProps({
67
+ type: { type: String, default: 'text' }
68
+ });
69
+
70
+ const model = defineModel({ type: String, default: '' });
71
+ const { inputValue, inputProps, error } = useFieldContext({ inputValue: model });
72
+ </script>
73
+ ```
74
+
75
+ Then use it inside a `<Field>`:
76
+
77
+ ```vue
78
+ <template>
79
+ <Form v-model="formData" @submit="handleSubmit" v-slot="{ isValid }">
80
+ <Field
81
+ name="name"
82
+ validation="required|min:3"
83
+ v-slot="{ labelProps, errorProps, error, isTouched }"
84
+ >
85
+ <label v-bind="labelProps">Name</label>
86
+ <MyInput />
87
+ <span v-if="error && isTouched" v-bind="errorProps">{{ error }}</span>
88
+ </Field>
89
+
90
+ <button type="submit" :disabled="!isValid">Submit</button>
91
+ </Form>
92
+ </template>
93
+ ```
94
+
95
+ ### With native inputs
96
+
97
+ For native HTML `<input>` elements, use `setValue` and `inputValue` from the slot:
98
+
99
+ ```vue
100
+ <template>
101
+ <Form v-model="formData" @submit="handleSubmit" v-slot="{ isValid }">
102
+ <Field
103
+ name="name"
104
+ validation="required|min:3"
105
+ v-slot="{ inputProps, inputValue, setValue, error, isTouched }"
106
+ >
107
+ <label :for="inputProps.id">Name</label>
108
+ <input
109
+ v-bind="inputProps"
110
+ :value="inputValue"
111
+ @input="setValue($event.target.value)"
112
+ type="text"
113
+ />
114
+ <span v-if="error && isTouched">{{ error }}</span>
115
+ </Field>
116
+
117
+ <button type="submit" :disabled="!isValid">Submit</button>
118
+ </Form>
119
+ </template>
120
+ ```
121
+
122
+ ---
123
+
124
+ ## Components
125
+
126
+ ### `<Form>`
127
+
128
+ Wraps your form and manages the global form state.
129
+
130
+ | Prop | Type | Description |
131
+ | ------------ | ------------------------- | -------------------------------- |
132
+ | `modelValue` | `Record<string, unknown>` | Form values (use with `v-model`) |
133
+
134
+ | Event | Payload | Description |
135
+ | -------- | -------------------------------- | ---------------------------- |
136
+ | `submit` | `(values, { setErrors, reset })` | Emitted on valid form submit |
137
+
138
+ | Slot prop | Type | Description |
139
+ | ---------- | ------------ | ----------------------------------- |
140
+ | `isValid` | `boolean` | Whether all fields are valid |
141
+ | `errors` | `string[]` | Array of all form-level errors |
142
+ | `reset` | `() => void` | Resets all fields to initial values |
143
+ | `validate` | `() => void` | Triggers validation on all fields |
144
+
145
+ ### `<Field>`
146
+
147
+ Manages a single field's state and validation.
148
+
149
+ | Prop | Type | Description |
150
+ | -------------------- | ------------------------- | --------------------------------------- |
151
+ | `name` | `string` | Field name (used as key in form values) |
152
+ | `modelValue` | `unknown` | Field value when used outside `<Form>` |
153
+ | `validation` | `string \| Function` | Validation rules |
154
+ | `validationMessages` | `Record<string, unknown>` | Custom error messages for this field |
155
+ | `error` | `string \| string[]` | External error (e.g. from API) |
156
+ | `id` | `string` | Override the auto-generated input id |
157
+
158
+ | Slot prop | Type | Description |
159
+ | ------------ | -------------------------- | --------------------------------------------------------------------------------------------------------- |
160
+ | `inputProps` | `object` | Props for custom Vue components (`modelValue`, `onUpdate:modelValue`, `onBlur`, `id`, `name`, aria attrs) |
161
+ | `inputValue` | `unknown` | Current field value (writable) |
162
+ | `labelProps` | `{ for: string }` | Props for `<label>` |
163
+ | `errorProps` | `{ id, role, aria-live }` | Props for the error element |
164
+ | `helpProps` | `{ id: string }` | Props for a help text element |
165
+ | `error` | `string \| null` | First error message |
166
+ | `errors` | `string[]` | All error messages |
167
+ | `isValid` | `boolean` | Whether the field is valid |
168
+ | `isTouched` | `boolean` | Whether the field has been blurred |
169
+ | `isDirty` | `boolean` | Whether the value has changed |
170
+ | `setValue` | `(value: unknown) => void` | Updates the field value (use with native inputs) |
171
+ | `validate` | `() => void` | Triggers validation manually |
172
+ | `reset` | `() => void` | Resets to initial value |
173
+
174
+ ---
175
+
176
+ ## Validation rules
177
+
178
+ Rules are specified as a pipe-separated string: `"required|min:3|max:100"`.
179
+
180
+ | Rule | Arguments | Description |
181
+ | ------------------- | -------------------- | ------------------------------------------ |
182
+ | `required` | — | Value must not be empty |
183
+ | `min` | `min` | Minimum string length |
184
+ | `max` | `max` | Maximum string length |
185
+ | `email` | — | Valid email address |
186
+ | `url` | — | Valid URL |
187
+ | `number` | — | Must be a number |
188
+ | `alpha` | — | Letters only |
189
+ | `alphanumeric` | — | Letters and numbers only |
190
+ | `alphaSpaces` | — | Letters and spaces only |
191
+ | `lowercase` | — | Lowercase only |
192
+ | `uppercase` | — | Uppercase only |
193
+ | `between` | `min,max` | Number between two values |
194
+ | `length` | `exact` or `min,max` | String length |
195
+ | `matches` | `value1,value2,...` | Must match one of the values |
196
+ | `is` | `value` | Strict equality |
197
+ | `not` | `value` | Must not equal value |
198
+ | `accepted` | — | Must be truthy (checkboxes) |
199
+ | `confirm` | `fieldName` | Must match another field's value |
200
+ | `startsWith` | `prefix` | Must start with prefix |
201
+ | `endsWith` | `suffix` | Must end with suffix |
202
+ | `containsAlpha` | — | Must contain at least one letter |
203
+ | `containsNumeric` | — | Must contain at least one number |
204
+ | `containsUppercase` | — | Must contain at least one uppercase letter |
205
+ | `containsLowercase` | — | Must contain at least one lowercase letter |
206
+ | `containsSymbol` | — | Must contain at least one symbol |
207
+ | `dateFormat` | `format` | Valid date format |
208
+ | `dateAfter` | `date` | Date must be after |
209
+ | `dateBefore` | `date` | Date must be before |
210
+ | `dateBetween` | `start,end` | Date between two dates |
211
+
212
+ ### Custom rules
213
+
214
+ ```ts
215
+ app.use(FormsPlugin, {
216
+ rules: {
217
+ myRule: (value, { args, messages }) => {
218
+ if (value !== args[0]) return messages.myRule ?? 'Invalid value';
219
+ return true;
220
+ }
221
+ }
222
+ });
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Localization
228
+
229
+ Built-in locales: `en` (default), `es`.
230
+
231
+ ```ts
232
+ import { en, es } from '@overgaming/valiform';
233
+
234
+ app.use(FormsPlugin, {
235
+ locale: 'es',
236
+ locales: { en, es }
237
+ });
238
+ ```
239
+
240
+ ### Switch locale at runtime
241
+
242
+ ```vue
243
+ <script setup>
244
+ import { useLocale } from '@overgaming/valiform';
245
+
246
+ const { locale, setLocale } = useLocale();
247
+ </script>
248
+ ```
249
+
250
+ ### Custom locale
251
+
252
+ ```ts
253
+ app.use(FormsPlugin, {
254
+ locales: {
255
+ en: {
256
+ required: 'This field is required',
257
+ email: 'Enter a valid email',
258
+ min: ({ args }) => `Minimum ${args[0]} characters`
259
+ }
260
+ }
261
+ });
262
+ ```
263
+
264
+ ---
265
+
266
+ ## Composables
267
+
268
+ ### `useFormContext()`
269
+
270
+ Access the parent `<Form>` state from any descendant component.
271
+
272
+ ```ts
273
+ const { values, isValid, errors, reset, validate, setErrors } = useFormContext();
274
+ ```
275
+
276
+ ### `useFieldContext(options?)`
277
+
278
+ Access the parent `<Field>` state from any descendant component. Optionally pass `{ inputValue }` to sync a local ref (e.g. from `defineModel`) bidirectionally with the field value.
279
+
280
+ ```ts
281
+ // Basic usage
282
+ const { inputValue, inputProps, error, isValid, isTouched, isDirty } = useFieldContext();
283
+
284
+ // With defineModel sync
285
+ const model = defineModel({ type: String, default: '' });
286
+ const { inputValue, inputProps, error } = useFieldContext({ inputValue: model });
287
+ ```
288
+
289
+ ### `useLocale()`
290
+
291
+ Access and change the active locale.
292
+
293
+ ```ts
294
+ const { locale, setLocale } = useLocale();
295
+ setLocale('es');
296
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("vue"),C=Symbol("locale-context"),T=(e=null)=>{const t=o.inject(C,e);if(!t&&t!==null)throw new Error("Context with localeContextKey not found");return t},q={required:"This field is required",email:"Please enter a valid email address",min:({value:e})=>`Must be at least ${e}`,max:({value:e})=>`Must be at most ${e}`,matches:"Format is not valid",number:"Must be a valid number",accepted:"Must be accepted",alpha:"Must contain only alphabetical characters",alphanumeric:"Must contain only letters and numbers",alphaSpaces:"Must contain only letters and spaces",between:({min:e,max:t})=>`Must be between ${e} and ${t}`,confirm:({fieldName:e})=>`Must match ${e}`,containsAlpha:"Must contain at least one letter",containsAlphanumeric:"Must contain at least one letter or number",containsAlphaSpaces:"Must contain at least one letter or space",containsLowercase:"Must contain at least one lowercase letter",containsNumeric:"Must contain at least one number",containsSymbol:"Must contain at least one symbol",containsUppercase:"Must contain at least one uppercase letter",dateAfter:({date:e})=>e?`Must be after ${e}`:"Must be after today",dateAfterOrEqual:({date:e})=>e?`Must be after or equal to ${e}`:"Must be after or equal to today",dateBefore:({date:e})=>e?`Must be before ${e}`:"Must be before today",dateBeforeOrEqual:({date:e})=>e?`Must be before or equal to ${e}`:"Must be before or equal to today",dateBetween:({startDate:e,endDate:t})=>`Must be between ${e} and ${t}`,dateFormat:({format:e})=>`Must match the format ${e}`,endsWith:({suffix:e})=>Array.isArray(e)?`Must end with one of: ${e.map(t=>`"${t}"`).join(", ")}`:`Must end with "${e}"`,is:({allowedValues:e})=>`Must be one of: ${Array.isArray(e)?e.join(", "):e}`,length:({value:e,min:t,max:n})=>t!==void 0&&n!==void 0?`Must be between ${t} and ${n} characters`:e!==void 0?`Must be exactly ${e} characters`:"Invalid length",lowercase:"Must contain only lowercase characters",not:({disallowedValues:e})=>`Must not be one of: ${Array.isArray(e)?e.join(", "):e}`,startsWith:({prefix:e})=>Array.isArray(e)?`Must start with one of: ${e.join(", ")}`:`Must start with "${e}"`,symbol:"Must contain only symbols",uppercase:"Must contain only uppercase characters",url:"Must be a valid URL"},E=o.shallowRef(new Map),z=(e,t)=>{E.value.set(e,t)},V=e=>{Object.entries(e).forEach(([t,n])=>{z(t,n)})},I=e=>E.value.get(e);function K(e){const t=e.accepted;return typeof t=="function"?t({}):t??"Must be accepted"}function U(e){return e?["yes","on","1","true"].includes(String(e).toLowerCase()):!1}function k(e,t={}){const{messages:n={}}=t;return U(e)?!0:K(n)}function Z(e){const t=e.alpha;return typeof t=="function"?t({}):t??"Must contain only alphabetical characters"}function Y(e){return e?/^[a-zA-Z]+$/.test(String(e)):!0}function G(e,t={}){const{messages:n={}}=t;return Y(e)?!0:Z(n)}function H(e){const t=e.alphanumeric;return typeof t=="function"?t({}):t??"Must contain only letters and numbers"}function J(e){return e?/^[a-zA-Z0-9]+$/.test(String(e)):!0}function Q(e,t={}){const{messages:n={}}=t;return J(e)?!0:H(n)}function X(e){const t=e.alphaSpaces;return typeof t=="function"?t({}):t??"Must contain only letters and spaces"}function ee(e){return e?/^[a-zA-Z\s]+$/.test(String(e)):!0}function te(e,t={}){const{messages:n={}}=t;return ee(e)?!0:X(n)}function ne(e,t,n){const r=e.between;return typeof r=="function"?r({min:t,max:n}):r??`Must be between ${t} and ${n}`}function re(e,t,n){if(!e&&e!==0)return!0;const r=Number(e);if(isNaN(r))return!1;const s=Number(t),a=Number(n);return isNaN(s)||isNaN(a)?!1:r>=s&&r<=a}function se(e,t={}){const{args:n=[],messages:r={}}=t,[s,a]=n;return re(e,s,a)?!0:ne(r,s,a)}function oe(e,t){const n=e.confirm;return typeof n=="function"?n({fieldName:t}):n??`Must match ${t}`}function ae(e,t,n){var r;return e?n?e===((r=n[t])==null?void 0:r.value):!1:!0}function ue(e,t={}){const{args:n=[],messages:r={},fields:s={}}=t,[a]=n;return ae(e,a,s)?!0:oe(r,a)}function ie(e){const t=e.containsAlpha;return typeof t=="function"?t({}):t??"Must contain at least one letter"}function ce(e){return e?/[a-zA-Z]/.test(String(e)):!0}function le(e,t={}){const{messages:n={}}=t;return ce(e)?!0:ie(n)}function fe(e){const t=e.containsAlphanumeric;return typeof t=="function"?t({}):t??"Must contain at least one letter or number"}function me(e){return e?/[a-zA-Z0-9]/.test(String(e)):!0}function de(e,t={}){const{messages:n={}}=t;return me(e)?!0:fe(n)}function ge(e){const t=e.containsAlphaSpaces;return typeof t=="function"?t({}):t??"Must contain at least one letter or space"}function pe(e){return e?/[a-zA-Z\s]/.test(String(e)):!0}function be(e,t={}){const{messages:n={}}=t;return pe(e)?!0:ge(n)}function ye(e){const t=e.containsLowercase;return typeof t=="function"?t({}):t??"Must contain at least one lowercase letter"}function $e(e){return e?/[a-z]/.test(String(e)):!0}function he(e,t={}){const{messages:n={}}=t;return $e(e)?!0:ye(n)}function ve(e){const t=e.containsNumeric;return typeof t=="function"?t({}):t??"Must contain at least one number"}function Me(e){return e?/\d/.test(String(e)):!0}function we(e,t={}){const{messages:n={}}=t;return Me(e)?!0:ve(n)}function Ne(e){const t=e.containsSymbol;return typeof t=="function"?t({}):t??"Must contain at least one symbol"}function Ae(e){return e?/[^\w\s]/.test(String(e)):!0}function De(e,t={}){const{messages:n={}}=t;return Ae(e)?!0:Ne(n)}function Se(e){const t=e.containsUppercase;return typeof t=="function"?t({}):t??"Must contain at least one uppercase letter"}function Fe(e){return e?/[A-Z]/.test(String(e)):!0}function xe(e,t={}){const{messages:n={}}=t;return Fe(e)?!0:Se(n)}function Ve(e,t){const n=e.dateAfter;return typeof n=="function"?n({date:t}):n??(t?`Must be after ${t}`:"Must be after today")}function je(e,t){if(!e)return!0;const n=new Date(String(e)),r=t?new Date(t):new Date;return!isNaN(n.getTime())&&!isNaN(r.getTime())&&n>r}function Pe(e,t={}){const{args:n=[],messages:r={}}=t,[s]=n;return je(e,s)?!0:Ve(r,s)}function Ce(e,t){const n=e.dateAfterOrEqual;return typeof n=="function"?n({date:t}):n??(t?`Must be after or equal to ${t}`:"Must be after or equal to today")}function Te(e,t){if(!e)return!0;const n=new Date(String(e)),r=t?new Date(t):new Date;return!isNaN(n.getTime())&&!isNaN(r.getTime())&&n>=r}function qe(e,t={}){const{args:n=[],messages:r={}}=t,[s]=n;return Te(e,s)?!0:Ce(r,s)}function Ee(e,t){const n=e.dateBefore;return typeof n=="function"?n({date:t}):n??(t?`Must be before ${t}`:"Must be before today")}function Be(e,t){if(!e)return!0;const n=new Date(String(e)),r=t?new Date(t):new Date;return!isNaN(n.getTime())&&!isNaN(r.getTime())&&n<r}function Oe(e,t={}){const{args:n=[],messages:r={}}=t,[s]=n;return Be(e,s)?!0:Ee(r,s)}function Re(e,t){const n=e.dateBeforeOrEqual;return typeof n=="function"?n({date:t}):n??(t?`Must be before or equal to ${t}`:"Must be before or equal to today")}function Le(e,t){if(!e)return!0;const n=new Date(String(e)),r=t?new Date(t):new Date;return!isNaN(n.getTime())&&!isNaN(r.getTime())&&n<=r}function We(e,t={}){const{args:n=[],messages:r={}}=t,[s]=n;return Le(e,s)?!0:Re(r,s)}function _e(e,t,n){const r=e.dateBetween;return typeof r=="function"?r({startDate:t,endDate:n}):r??`Must be between ${t} and ${n}`}function ze(e,t,n){if(!e)return!0;const r=new Date(String(e)),s=new Date(t),a=new Date(n);return!isNaN(r.getTime())&&!isNaN(s.getTime())&&!isNaN(a.getTime())&&r>=s&&r<=a}function Ie(e,t={}){const{args:n=[],messages:r={}}=t,[s,a]=n;return ze(e,s,a)?!0:_e(r,s,a)}const Ke=e=>{const t=e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&"),n={YYYY:"\\d{4}",YY:"\\d{2}",MM:"(0[1-9]|1[012])",M:"([1-9]|1[012])",DD:"([012][0-9]|3[01])",D:"([012]?[0-9]|3[01])"};let r=t;return Object.keys(n).forEach(s=>{r=r.replace(new RegExp(s,"g"),n[s])}),new RegExp(`^${r}$`)};function Ue(e,t){const n=e.dateFormat;return typeof n=="function"?n({format:t}):n??`Must match the format ${t}`}function ke(e,t){return e?t&&typeof t=="string"?Ke(t).test(String(e)):!isNaN(Date.parse(String(e))):!0}function Ze(e,t={}){const{args:n=[],messages:r={}}=t,[s]=n;return ke(e,s)?!0:Ue(r,s)}function Ye(e){const t=e.email;return typeof t=="function"?t({}):t??"Please enter a valid email address"}function Ge(e){return e?/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(e)):!0}function He(e,t={}){const{messages:n={}}=t;return Ge(e)?!0:Ye(n)}function Je(e,t){const n=e.endsWith;return typeof n=="function"?n({suffix:t.length===1?t[0]:t}):n??`Must end with: ${t.join(", ")}`}function Qe(e,...t){if(!e)return!0;const n=String(e);return t.some(r=>n.endsWith(r))}function Xe(e,t={}){const{args:n=[],messages:r={}}=t;return Qe(e,...n)?!0:Je(r,n)}function et(e,t){const n=e.is;return typeof n=="function"?n({allowedValues:t}):n??`Must be one of: ${t.join(", ")}`}function tt(e,...t){return!e&&e!==0&&e!==!1?!0:t.includes(String(e))}function nt(e,t={}){const{args:n=[],messages:r={}}=t;return tt(e,...n)?!0:et(r,n)}function rt(e,t,n){const r=e.length;return n!==void 0?typeof r=="function"?r({min:t,max:n}):r??`Must be between ${t} and ${n} characters`:typeof r=="function"?r({value:t}):r??`Must be exactly ${t} characters`}function st(e,t,n){if(!e&&e!==0)return!0;let r=0;if(typeof e=="string")r=e.length;else if(Array.isArray(e))r=e.length;else if(typeof e=="object"&&e!==null)r=Object.keys(e).length;else return!1;if(n!==void 0){const a=Number(t),u=Number(n);return!isNaN(a)&&!isNaN(u)&&r>=a&&r<=u}const s=Number(t);return!isNaN(s)&&r===s}function ot(e,t={}){const{args:n=[],messages:r={}}=t,[s,a]=n;return st(e,s,a)?!0:rt(r,s,a)}function at(e){const t=e.lowercase;return typeof t=="function"?t({}):t??"Must contain only lowercase characters"}function ut(e){return e?/^[a-z]+$/.test(String(e)):!0}function it(e,t={}){const{messages:n={}}=t;return ut(e)?!0:at(n)}function ct(e,t,n){const r=t.matches;return typeof r=="function"?r({value:e,pattern:n}):r??"Format is not valid"}function lt(e,t){if(!e)return!0;if(!t)return!1;try{if(t.startsWith("/")&&t.includes("/",1)){const n=t.lastIndexOf("/"),r=t.slice(1,n),s=t.slice(n+1);return new RegExp(r,s).test(String(e))}return String(e)===t}catch{return!1}}function ft(e,t={}){const{args:n=[],messages:r={}}=t,[s]=n;return lt(e,s)?!0:ct(e,r,s)}function mt(e,t){const n=e.max;return typeof n=="function"?n({value:t}):n??`Must be at most ${t}`}function dt(e,t){if(!e&&e!==0)return!0;const n=Number(t);if(isNaN(n))return!1;if(typeof e=="number")return e<=n;if(typeof e=="string"){const r=Number(e);return isNaN(r)?e.length<=n:r<=n}return Array.isArray(e)?e.length<=n:!1}function gt(e,t={}){const{args:n=[],messages:r={}}=t,[s]=n;return dt(e,s)?!0:mt(r,s)}function pt(e,t){const n=e.min;return typeof n=="function"?n({value:t}):n??`Must be at least ${t}`}function bt(e,t){if(!e&&e!==0)return!0;const n=Number(t);if(isNaN(n))return!1;if(typeof e=="number")return e>=n;if(typeof e=="string"){const r=Number(e);return isNaN(r)?e.length>=n:r>=n}return Array.isArray(e)?e.length>=n:!1}function yt(e,t={}){const{args:n=[],messages:r={}}=t,[s]=n;return bt(e,s)?!0:pt(r,s)}function $t(e,t){const n=e.not;return typeof n=="function"?n({disallowedValues:t}):n??`Must not be one of: ${t.join(", ")}`}function ht(e,...t){return!e&&e!==0&&e!==!1?!0:!t.includes(String(e))}function vt(e,t={}){const{args:n=[],messages:r={}}=t;return ht(e,...n)?!0:$t(r,n)}function Mt(e){const t=e.number;return typeof t=="function"?t({}):t??"Must be a valid number"}function wt(e){if(e==null||e==="")return!0;if(Number.isNaN(e))return!1;const t=Number(e);return!isNaN(t)&&isFinite(t)}function Nt(e,t={}){const{messages:n={}}=t;return wt(e)?!0:Mt(n)}function At(e){const t=e.required;return typeof t=="function"?t({}):t??"This field is required"}function Dt(e){return e==null||e===""?!1:typeof e=="string"?e.trim().length>0:Array.isArray(e)?e.length>0:typeof e=="object"?Object.keys(e).length>0:e===0?!0:!!e}function St(e,t={}){const{messages:n={}}=t;return Dt(e)?!0:At(n)}function Ft(e,t){const n=e.startsWith;return typeof n=="function"?n({prefix:t.length===1?t[0]:t}):n??`Must start with: ${t.join(", ")}`}function xt(e,...t){if(!e)return!0;const n=String(e);return t.some(r=>n.startsWith(r))}function Vt(e,t={}){const{args:n=[],messages:r={}}=t;return xt(e,...n)?!0:Ft(r,n)}function jt(e){const t=e.symbol;return typeof t=="function"?t({}):t??"Must contain only symbols"}function Pt(e){return e?/^[^\w\s]+$/.test(String(e)):!0}function Ct(e,t={}){const{messages:n={}}=t;return Pt(e)?!0:jt(n)}function Tt(e){const t=e.uppercase;return typeof t=="function"?t({}):t??"Must contain only uppercase characters"}function qt(e){return e?/^[A-Z]+$/.test(String(e)):!0}function Et(e,t={}){const{messages:n={}}=t;return qt(e)?!0:Tt(n)}function Bt(e){const t=e.url;return typeof t=="function"?t({}):t??"Must be a valid URL"}function Ot(e){if(!e)return!0;try{const t=new URL(String(e));return["http:","https:","ftp:","ftps:"].includes(t.protocol)}catch{return!1}}function Rt(e,t={}){const{messages:n={}}=t;return Ot(e)?!0:Bt(n)}const Lt=Object.freeze(Object.defineProperty({__proto__:null,accepted:k,alpha:G,alphaSpaces:te,alphanumeric:Q,between:se,confirm:ue,containsAlpha:le,containsAlphaSpaces:be,containsAlphanumeric:de,containsLowercase:he,containsNumeric:we,containsSymbol:De,containsUppercase:xe,dateAfter:Pe,dateAfterOrEqual:qe,dateBefore:Oe,dateBeforeOrEqual:We,dateBetween:Ie,dateFormat:Ze,email:He,endsWith:Xe,is:nt,length:ot,lowercase:it,matches:ft,max:gt,min:yt,not:vt,number:Nt,required:St,startsWith:Vt,symbol:Ct,uppercase:Et,url:Rt},Symbol.toStringTag,{value:"Module"})),Wt={install(e,t={}){V(Lt),t.rules&&V(t.rules);const n=o.isRef(t.locale)?t.locale:o.ref(t.locale??"en"),r={en:q};t.locales&&Object.keys(t.locales).forEach(a=>{r[a]={...r[a]??{},...t.locales[a]}});const s=o.shallowRef(r);e.provide(C,{locale:o.readonly(n),locales:o.readonly(s),setLocale:a=>{s.value[a]?n.value=a:console.warn(`Locale "${a}" not available.`)}})}},B=Symbol("form-state-context"),_t=e=>{o.provide(B,e)},zt=(e=null)=>{const t=o.inject(B,e);if(!t&&t!==null)throw new Error("Context with formStateContextKey not found");return t},O=Symbol("form-context"),It=e=>{o.provide(O,e)},Kt=(e=null)=>{const t=o.inject(O,e);if(!t&&t!==null)throw new Error("Context with formContextKey not found");return t};function Ut(e,t){if(!(!e||!t))return t.split(".").reduce((n,r)=>n==null?void 0:n[r],e)}function kt(e,t,n){if(!e||!t)return e;const r=t.split("."),s=r.pop(),a=r.reduce((u,g,c)=>{if(u[g]===null||u[g]===void 0){const y=r[c+1]||s;u[g]=/^\d+$/.test(y)?[]:{}}return u[g]},e);return a[s]=n,e}const Zt=o.defineComponent({__name:"Form",props:{modelValue:{default:()=>({})},modelModifiers:{}},emits:o.mergeModels(["submit"],["update:modelValue"]),setup(e,{emit:t}){const n=t,r=o.useModel(e,"modelValue");o.isReactive(r.value)||(r.value=o.reactive(r.value??{}));const s=o.ref({}),a=o.ref([]),u=o.computed(()=>r.value),g=o.computed(()=>a.value[0]??null),c=o.computed(()=>Object.keys(s.value).every(i=>s.value[i].isValid)),y=()=>{Object.keys(s.value).forEach(i=>{var b,p;(p=(b=s.value[i]).validate)==null||p.call(b)})},f=()=>{y(),c.value&&(a.value=[],n("submit",r.value,{setErrors:m,reset:l}))},m=(...i)=>{var b;if(i.length===1){const p=i[0];if(typeof p=="object"&&!Array.isArray(p)&&p!==null){Object.keys(p).forEach($=>{m($,p[$])});return}a.value=Array.isArray(p)?p:[p]}else if(i.length===2){const[p,$]=i;(b=s.value[p])==null||b.setErrors($)}},l=()=>{a.value=[],Object.keys(s.value).forEach(i=>{s.value[i].reset()})};return _t({getFields(){return s.value},getField(i){return s.value[i]},addField(i,b){s.value[i]=b},removeField(i){delete s.value[i]},setFieldValue(i,b){kt(r.value,i,b)},getFieldValue(i){return Ut(r.value,i)}}),It({values:u,isValid:c,error:g,errors:a,fields:s,reset:l,setErrors:m,validate:y}),(i,b)=>(o.openBlock(),o.createElementBlock("form",{onSubmit:o.withModifiers(f,["prevent"])},[o.renderSlot(i.$slots,"default",o.normalizeProps(o.guardReactiveProps({isValid:c.value,error:g.value,errors:a.value,values:u.value,fields:s.value,reset:l,setErrors:m,validate:y})))],32))}});function R(e){if(!e||typeof e!="string")return{};const t=e.split("|").filter(Boolean),n={};for(const r of t){const[s,...a]=r.split(":"),u=s.trim();if(a.length===0)n[u]=!0;else{const c=a.join(":").split(",").map(y=>y.trim());n[u]=c.length===1?c[0]:c}}return n}const Yt=(e,t,n={},r={})=>{if(!t)return!0;const s=R(t),a=[];for(const[u,g]of Object.entries(s)){const c=I(u);if(c){const f={args:g===!0?[]:Array.isArray(g)?g:[g],messages:n,fields:r},m=c(e,f);m!==!0&&a.push(m??"Invalid value")}else console.warn(`[Forms] Validation rule "${u}" not found.`)}return a.length===0?!0:a},Gt=(e,t)=>{const n=t(e);return n===!0?!0:typeof n=="string"?[n]:Array.isArray(n)?n:!!n?!0:["Invalid value"]};typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const j=()=>{};function Ht(e,t){function n(...r){return new Promise((s,a)=>{Promise.resolve(e(()=>t.apply(this,r),{fn:t,thisArg:this,args:r})).then(s).catch(a)})}return n}const Jt=e=>e();function Qt(e,t={}){let n,r,s=j;const a=c=>{clearTimeout(c),s(),s=j};let u;return c=>{const y=o.toValue(e),f=o.toValue(t.maxWait);return n&&a(n),y<=0||f!==void 0&&f<=0?(r&&(a(r),r=void 0),Promise.resolve(c())):new Promise((m,l)=>{s=t.rejectOnCancel?l:m,u=c,f&&!r&&(r=setTimeout(()=>{n&&a(n),r=void 0,m(u())},f)),n=setTimeout(()=>{r&&a(r),r=void 0,m(c())},y)})}}function Xt(e,t,n={}){const{eventFilter:r=Jt,...s}=n;return o.watch(e,Ht(r,t),s)}function P(e,t,n={}){const{debounce:r=0,maxWait:s=void 0,...a}=n;return Xt(e,t,{...a,eventFilter:Qt(r,{maxWait:s})})}function en(e,t={}){const n=o.ref(!0),r=o.ref(!1),s=o.ref([]),a=o.ref(null),u=o.computed(()=>o.toValue(t.rules)??null),g=o.computed(()=>o.toValue(t.messages)??{}),c=o.computed(()=>o.toValue(t.fields)??{}),y=o.computed(()=>{if(!u.value||typeof u.value!="string")return null;const i=R(u.value).confirm;if(!i||i===!0)return null;const b=c.value[i];return(b==null?void 0:b.value)??null}),f=()=>{let l;return typeof u.value=="function"?l=Gt(o.toValue(e),u.value):l=Yt(o.toValue(e),u.value,g.value,c.value),l===!0?(n.value=!0,s.value=[],!0):Array.isArray(l)?(n.value=!1,s.value=l,!1):l},m=()=>{a.value&&clearTimeout(a.value),r.value=!0,n.value=!0,s.value=[],a.value=setTimeout(()=>{r.value=!1,a.value=null},1e3)};return P(()=>o.toValue(e),()=>{r.value||f()},{debounce:300}),P(()=>o.toValue(y),()=>{r.value||o.toValue(e)&&f()},{debounce:300}),{validate:f,reset:m,isValid:o.readonly(n),errors:o.readonly(s)}}const L=Symbol("field-context"),tn=e=>{o.provide(L,e)},nn=(e=null)=>{const t=o.inject(L,e);if(!t&&t!==null)throw new Error("Context with fieldContextKey not found");return t},rn=o.defineComponent({__name:"Field",props:o.mergeModels({id:{},name:{},validation:{type:[String,Function,null]},validationMessages:{},error:{}},{modelValue:{},modelModifiers:{}}),emits:["update:modelValue"],setup(e){const t=e,n=o.useModel(e,"modelValue"),r=o.useId(),s=zt(),a=T(),u=o.computed({get:()=>s?s.getFieldValue(t.name):n.value,set:d=>{s?s.setFieldValue(t.name,d):n.value=d}}),g=o.computed(()=>t.validation??null),c=o.computed(()=>({...a?a.locales.value[a.locale.value]:{},...t.validationMessages??{}})),y=o.computed({get:()=>s?s.getFields():{},set:()=>{}}),f=en(u,{rules:g,messages:c,fields:y}),m=o.ref(!1),l=o.ref(!1),i=o.ref([]),b=o.computed(()=>u.value??null),p=o.computed(()=>f.isValid.value&&i.value.length===0),$=o.computed(()=>[...f.errors.value,...i.value]),h=o.computed(()=>$.value[0]??null),M=o.toValue(u),v=t.id??r,w=`${v}-error`,N=`${v}-help`,W=d=>{i.value=Array.isArray(d)?d:[d]},_=d=>{u.value=d,!l.value&&d!==M&&(l.value=!0)},A=()=>{u.value=M,m.value=!1,l.value=!1,i.value=[],f.reset()};o.watch(()=>t.error,d=>{d?i.value=Array.isArray(d)?d:[d]:i.value=[]},{immediate:!0});const D=o.computed(()=>({id:v,name:t.name,modelValue:u.value,"aria-invalid":!!h.value,"aria-describedby":h.value?`${w} ${N}`:N,"aria-errormessage":h.value?w:void 0,"onUpdate:modelValue":d=>{u.value=d,!l.value&&d!==M&&(l.value=!0)},onBlur:()=>{m.value||(m.value=!0,f.validate())}})),S={for:v},F={id:w,role:"alert","aria-live":"polite"},x={id:N};return o.onMounted(()=>{s&&s.addField(t.name,{value:b,isValid:p,isTouched:m,isDirty:l,error:h,errors:$,setErrors:W,reset:A,validate:f.validate})}),o.onUnmounted(()=>{s&&s.removeField(t.name)}),tn({inputValue:u,inputProps:D,labelProps:S,helpProps:x,errorProps:F,isValid:p,isTouched:m,isDirty:l,error:h,errors:$,validate:f.validate,reset:A}),(d,an)=>(o.openBlock(),o.createElementBlock("div",null,[o.renderSlot(d.$slots,"default",o.normalizeProps(o.guardReactiveProps({inputValue:u.value,inputProps:D.value,labelProps:S,helpProps:x,errorProps:F,isValid:p.value,isTouched:m.value,isDirty:l.value,error:h.value,errors:$.value,setValue:_,validate:o.unref(f).validate,reset:A})))]))}});function sn(){const e=T();if(!e)throw new Error("[Valiform] useLocale() must be used inside a component with FormsPlugin installed.");return{locale:e.locale,locales:e.locales,setLocale:e.setLocale}}const on={required:"Este campo es obligatorio",email:"Por favor ingresa un email válido",min:({value:e})=>`Debe ser al menos ${e}`,max:({value:e})=>`Debe ser como máximo ${e}`,matches:"El formato no es válido",number:"Debe ser un número válido",accepted:"Debe ser aceptado",alpha:"Debe contener solo caracteres alfabéticos",alphanumeric:"Debe contener solo letras y números",alphaSpaces:"Debe contener solo letras y espacios",between:({min:e,max:t})=>`Debe estar entre ${e} y ${t}`,confirm:({fieldName:e})=>`Debe coincidir con ${e}`,containsAlpha:"Debe contener al menos una letra",containsAlphanumeric:"Debe contener al menos una letra o número",containsAlphaSpaces:"Debe contener al menos una letra o espacio",containsLowercase:"Debe contener al menos una letra minúscula",containsNumeric:"Debe contener al menos un número",containsSymbol:"Debe contener al menos un símbolo",containsUppercase:"Debe contener al menos una letra mayúscula",dateAfter:({date:e})=>e?`Debe ser posterior a ${e}`:"Debe ser posterior a hoy",dateAfterOrEqual:({date:e})=>e?`Debe ser posterior o igual a ${e}`:"Debe ser posterior o igual a hoy",dateBefore:({date:e})=>e?`Debe ser anterior a ${e}`:"Debe ser anterior a hoy",dateBeforeOrEqual:({date:e})=>e?`Debe ser anterior o igual a ${e}`:"Debe ser anterior o igual a hoy",dateBetween:({startDate:e,endDate:t})=>`Debe estar entre ${e} y ${t}`,dateFormat:({format:e})=>`Debe coincidir con el formato ${e}`,endsWith:({suffix:e})=>Array.isArray(e)?`Debe terminar con uno de: ${e.map(t=>`"${t}"`).join(", ")}`:`Debe terminar con "${e}"`,is:({allowedValues:e})=>`Debe ser uno de: ${Array.isArray(e)?e.join(", "):e}`,length:({value:e,min:t,max:n})=>t!==void 0&&n!==void 0?`Debe tener entre ${t} y ${n} caracteres`:e!==void 0?`Debe tener exactamente ${e} caracteres`:"Longitud inválida",lowercase:"Debe contener solo caracteres en minúscula",not:({disallowedValues:e})=>`No debe ser uno de: ${Array.isArray(e)?e.join(", "):e}`,startsWith:({prefix:e})=>Array.isArray(e)?`Debe comenzar con uno de: ${e.join(", ")}`:`Debe comenzar con "${e}"`,symbol:"Debe contener solo símbolos",uppercase:"Debe contener solo caracteres en mayúscula",url:"Debe ser una URL válida"};exports.Field=rn;exports.Form=Zt;exports.FormsPlugin=Wt;exports.en=q;exports.es=on;exports.useFieldContext=nn;exports.useFormContext=Kt;exports.useLocale=sn;