@keysdown/form-wrapper 1.0.5 → 2.0.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 +335 -31
- package/dist/form-wrapper.cjs +1 -1
- package/dist/form-wrapper.js +114 -28
- package/dist/form-wrapper.umd.cjs +1 -1
- package/dist/plugins/formValidation.cjs +1 -0
- package/dist/plugins/formValidation.d.ts +4 -0
- package/dist/plugins/formValidation.js +7 -0
- package/dist/plugins/locales.cjs +1 -0
- package/dist/plugins/locales.d.ts +2 -0
- package/dist/plugins/locales.js +115 -0
- package/dist/plugins/rules.cjs +1 -0
- package/dist/plugins/rules.d.ts +2 -0
- package/dist/plugins/rules.js +2 -0
- package/dist/rules-BIt-pifQ.cjs +1 -0
- package/dist/rules-DZmnRoUy.js +196 -0
- package/dist/src/core/Errors.d.ts +1 -1
- package/dist/src/core/Form.d.ts +11 -0
- package/dist/src/plugins/formValidation.d.ts +3 -0
- package/dist/src/plugins/locales/en.d.ts +3 -0
- package/dist/src/plugins/locales/es.d.ts +3 -0
- package/dist/src/plugins/locales/index.d.ts +3 -0
- package/dist/src/plugins/locales/pt.d.ts +3 -0
- package/dist/src/plugins/rules/alpha.d.ts +2 -0
- package/dist/src/plugins/rules/alphaNumeric.d.ts +2 -0
- package/dist/src/plugins/rules/array.d.ts +2 -0
- package/dist/src/plugins/rules/between.d.ts +2 -0
- package/dist/src/plugins/rules/boolean.d.ts +2 -0
- package/dist/src/plugins/rules/confirmed.d.ts +2 -0
- package/dist/src/plugins/rules/date.d.ts +2 -0
- package/dist/src/plugins/rules/different.d.ts +2 -0
- package/dist/src/plugins/rules/digits.d.ts +2 -0
- package/dist/src/plugins/rules/digitsBetween.d.ts +2 -0
- package/dist/src/plugins/rules/email.d.ts +2 -0
- package/dist/src/plugins/rules/endsWith.d.ts +2 -0
- package/dist/src/plugins/rules/greaterThan.d.ts +2 -0
- package/dist/src/plugins/rules/greaterThanOrEqual.d.ts +2 -0
- package/dist/src/plugins/rules/in.d.ts +2 -0
- package/dist/src/plugins/rules/index.d.ts +35 -0
- package/dist/src/plugins/rules/integer.d.ts +2 -0
- package/dist/src/plugins/rules/ip.d.ts +2 -0
- package/dist/src/plugins/rules/json.d.ts +2 -0
- package/dist/src/plugins/rules/lessThan.d.ts +2 -0
- package/dist/src/plugins/rules/lessThanOrEqual.d.ts +2 -0
- package/dist/src/plugins/rules/max.d.ts +2 -0
- package/dist/src/plugins/rules/min.d.ts +2 -0
- package/dist/src/plugins/rules/notIn.d.ts +2 -0
- package/dist/src/plugins/rules/nullable.d.ts +2 -0
- package/dist/src/plugins/rules/numeric.d.ts +2 -0
- package/dist/src/plugins/rules/regex.d.ts +2 -0
- package/dist/src/plugins/rules/required.d.ts +2 -0
- package/dist/src/plugins/rules/same.d.ts +2 -0
- package/dist/src/plugins/rules/size.d.ts +2 -0
- package/dist/src/plugins/rules/startsWith.d.ts +2 -0
- package/dist/src/plugins/rules/string.d.ts +2 -0
- package/dist/src/plugins/rules/url.d.ts +2 -0
- package/dist/src/plugins/rules/uuid.d.ts +2 -0
- package/dist/src/types/fields.d.ts +1 -0
- package/dist/src/types/locale.d.ts +7 -0
- package/dist/src/types/plugin.d.ts +2 -0
- package/dist/src/types/rules.d.ts +7 -1
- package/dist/src/types/validations.d.ts +3 -2
- package/dist/src/utils/rule.d.ts +2 -0
- package/package.json +17 -2
- package/dist/form-wrapper.iife.js +0 -1
- package/dist/src/utils/validations.d.ts +0 -3
package/README.md
CHANGED
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
<img src="https://img.shields.io/github/license/keysdown/form-wrapper.svg" alt="MIT"/>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
|
-
> A
|
|
12
|
+
> A zero-dependency, framework-agnostic form state management library with a plugin-based validation system. Manage form fields, validate with customizable error messages, and extend with built-in validation rules, only importing what you need.
|
|
13
13
|
|
|
14
14
|
<p align="center">
|
|
15
|
-
<strong>Bundle size (minified + gzip):</strong> ~1.
|
|
15
|
+
<strong>Bundle size (minified + gzip):</strong> ~1.5 kB core / ~3 kB with all 33 rules
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
18
|
## Installation
|
|
@@ -43,6 +43,211 @@ const form = ref(new FormWrapper({
|
|
|
43
43
|
}))
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
## Validation Plugins
|
|
47
|
+
|
|
48
|
+
Validation rules are loaded via a plugin system, no rules are bundled in core. This keeps the base library minimal and lets you import only what you need.
|
|
49
|
+
|
|
50
|
+
### Loading all rules
|
|
51
|
+
|
|
52
|
+
Use the `formValidation` plugin to register all 33 validation rules at once:
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
import FormWrapper from '@keysdown/form-wrapper'
|
|
56
|
+
import formValidation from '@keysdown/form-wrapper/plugins/formValidation'
|
|
57
|
+
|
|
58
|
+
FormWrapper.extend(formValidation)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Default error messages with locales
|
|
62
|
+
|
|
63
|
+
Validation rules have built-in default error messages. By default, messages are in **English**. You can change the language by loading a locale:
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
import FormWrapper from '@keysdown/form-wrapper'
|
|
67
|
+
import formValidation from '@keysdown/form-wrapper/plugins/formValidation'
|
|
68
|
+
import pt from '@keysdown/form-wrapper/plugins/locales/pt'
|
|
69
|
+
|
|
70
|
+
FormWrapper.extend(formValidation)
|
|
71
|
+
FormWrapper.locale(pt) // switch to Portuguese
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Available locales: `en` (English, default), `pt` (Portuguese), `es` (Spanish).
|
|
75
|
+
|
|
76
|
+
Default messages support **interpolation**, placeholders like `:field`, `:min`, `:max`, `:size`, `:digits`, `:other`, `:values` are replaced with actual values:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
The first name field is required.
|
|
80
|
+
The password must be at least 6.
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Custom field display names with `attribute`
|
|
84
|
+
|
|
85
|
+
By default, the `:field` placeholder uses the field name with underscores replaced by spaces (e.g., `first_name` → "first name"). You can customize this with the `attribute` property:
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
const form = new FormWrapper({
|
|
89
|
+
email: {
|
|
90
|
+
value: null,
|
|
91
|
+
validation: {
|
|
92
|
+
rules: ['required', 'email']
|
|
93
|
+
},
|
|
94
|
+
attribute: 'contact email'
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// Default error message would show:
|
|
99
|
+
// "The contact email field is required."
|
|
100
|
+
// instead of "The email field is required."
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
const form = new FormWrapper({
|
|
105
|
+
password: {
|
|
106
|
+
value: null,
|
|
107
|
+
validation: {
|
|
108
|
+
rules: ['required', 'min:8']
|
|
109
|
+
},
|
|
110
|
+
attribute: 'senha'
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// With pt locale: "O campo senha deve ter no mínimo 8."
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
User-provided messages always **override** default messages:
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
const form = new FormWrapper({
|
|
121
|
+
email: {
|
|
122
|
+
value: null,
|
|
123
|
+
validation: {
|
|
124
|
+
rules: ['required', 'email'],
|
|
125
|
+
messages: {
|
|
126
|
+
required: 'Email is required.' // overrides the default message
|
|
127
|
+
// email uses the default locale message
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Tree-shaking individual rules
|
|
135
|
+
|
|
136
|
+
Import only the rules you need and use them directly in the `rules` array for tree-shaking:
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
import FormWrapper from '@keysdown/form-wrapper'
|
|
140
|
+
import { required, email } from '@keysdown/form-wrapper/plugins/rules'
|
|
141
|
+
|
|
142
|
+
const form = new FormWrapper({
|
|
143
|
+
email: {
|
|
144
|
+
value: null,
|
|
145
|
+
validation: {
|
|
146
|
+
rules: [required, email],
|
|
147
|
+
messages: {
|
|
148
|
+
required: 'The email field is required.',
|
|
149
|
+
email: 'The email must be a valid address.'
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
You can also mix function rules with string rules (useful for parameterized rules like `min:6`):
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
import { required, email } from '@keysdown/form-wrapper/plugins/rules'
|
|
160
|
+
|
|
161
|
+
rules: [required, email, 'min:6']
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Custom rules
|
|
165
|
+
|
|
166
|
+
Add inline validation rules directly in the `rules` array. A custom rule receives a destructured object `{value, fail, form, field}`:
|
|
167
|
+
|
|
168
|
+
```js
|
|
169
|
+
const form = createForm({
|
|
170
|
+
email: {
|
|
171
|
+
value: null,
|
|
172
|
+
validation: {
|
|
173
|
+
rules: [({value, fail}) => {
|
|
174
|
+
if (!value) fail('The email field is required.')
|
|
175
|
+
}]
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Access other form fields via the `form` parameter:
|
|
182
|
+
|
|
183
|
+
```js
|
|
184
|
+
const form = createForm({
|
|
185
|
+
password: { value: null, validation: {rules: []} },
|
|
186
|
+
password_confirmation: {
|
|
187
|
+
value: null,
|
|
188
|
+
validation: {
|
|
189
|
+
rules: [({value, fail, form}) => {
|
|
190
|
+
if (value !== form.password) fail('Passwords do not match.')
|
|
191
|
+
}]
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Custom rules support **async** validation (e.g., API calls):
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
const form = createForm({
|
|
201
|
+
email: {
|
|
202
|
+
value: null,
|
|
203
|
+
validation: {
|
|
204
|
+
rules: [async ({value, fail}) => {
|
|
205
|
+
const response = await fetch(`/api/check-email?email=${encodeURIComponent(value)}`)
|
|
206
|
+
const { available } = await response.json()
|
|
207
|
+
if (!available) fail('This email is already taken.')
|
|
208
|
+
}]
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
You can mix custom rules with built-in rules:
|
|
215
|
+
|
|
216
|
+
```js
|
|
217
|
+
import { required, email } from '@keysdown/form-wrapper/plugins/rules'
|
|
218
|
+
|
|
219
|
+
const form = createForm({
|
|
220
|
+
email: {
|
|
221
|
+
value: null,
|
|
222
|
+
validation: {
|
|
223
|
+
rules: [
|
|
224
|
+
required,
|
|
225
|
+
email,
|
|
226
|
+
async ({value, fail}) => {
|
|
227
|
+
const response = await fetch(`/api/check-email?email=${encodeURIComponent(value)}`)
|
|
228
|
+
const { available } = await response.json()
|
|
229
|
+
if (!available) fail('This email is already taken.')
|
|
230
|
+
}
|
|
231
|
+
],
|
|
232
|
+
messages: {
|
|
233
|
+
required: 'The email field is required.',
|
|
234
|
+
email: 'The email must be a valid address.'
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Static methods on FormWrapper
|
|
242
|
+
|
|
243
|
+
| Method | Description |
|
|
244
|
+
|---|---|
|
|
245
|
+
| `FormWrapper.extend(plugin)` | Register a plugin (e.g., `formValidation`) |
|
|
246
|
+
| `FormWrapper.addRule(name, handler)` | Register a single validation rule |
|
|
247
|
+
| `FormWrapper.locale(locale)` | Set the default error messages locale |
|
|
248
|
+
| `FormWrapper.rules` | Static rule registry |
|
|
249
|
+
| `FormWrapper.defaultMessages` | Static default messages registry |
|
|
250
|
+
|
|
46
251
|
## Basic example
|
|
47
252
|
|
|
48
253
|
Basic example using Vue.
|
|
@@ -103,29 +308,38 @@ Basic example with validation using Vue.
|
|
|
103
308
|
<script setup lang="ts">
|
|
104
309
|
import axios from 'axios'
|
|
105
310
|
import {ref} from 'vue'
|
|
106
|
-
import
|
|
311
|
+
import FormWrapper from '@keysdown/form-wrapper'
|
|
312
|
+
import formValidation from '@keysdown/form-wrapper/plugins/formValidation'
|
|
107
313
|
|
|
108
|
-
|
|
314
|
+
FormWrapper.extend(formValidation)
|
|
315
|
+
|
|
316
|
+
const form = ref(new FormWrapper({
|
|
109
317
|
first_name: {
|
|
110
318
|
value: null,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
319
|
+
validation: {
|
|
320
|
+
rules: ['required'],
|
|
321
|
+
messages: {
|
|
322
|
+
required: 'The first name field is required.'
|
|
323
|
+
}
|
|
114
324
|
}
|
|
115
325
|
},
|
|
116
326
|
last_name: {
|
|
117
327
|
value: null,
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
328
|
+
validation: {
|
|
329
|
+
rules: ['required'],
|
|
330
|
+
messages: {
|
|
331
|
+
required: 'The last name field is required.'
|
|
332
|
+
}
|
|
121
333
|
}
|
|
122
334
|
},
|
|
123
335
|
username: {
|
|
124
336
|
value: null,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
337
|
+
validation: {
|
|
338
|
+
rules: ['required', 'min:6'],
|
|
339
|
+
messages: {
|
|
340
|
+
required: 'The username field is required.',
|
|
341
|
+
min: 'The username field must have at least 6 characters'
|
|
342
|
+
}
|
|
129
343
|
}
|
|
130
344
|
}
|
|
131
345
|
}))
|
|
@@ -154,7 +368,6 @@ Basic example with validation using Vue.
|
|
|
154
368
|
</script>
|
|
155
369
|
```
|
|
156
370
|
|
|
157
|
-
|
|
158
371
|
## Form methods
|
|
159
372
|
|
|
160
373
|
### addField(field, value)
|
|
@@ -165,6 +378,7 @@ Method used to add a single field to the form.
|
|
|
165
378
|
form.addField('username', null)
|
|
166
379
|
|
|
167
380
|
form.addField('username', {
|
|
381
|
+
value: null,
|
|
168
382
|
validation: {
|
|
169
383
|
rules: ['required'],
|
|
170
384
|
messages: {
|
|
@@ -186,6 +400,7 @@ form.addFields({
|
|
|
186
400
|
|
|
187
401
|
form.addFields({
|
|
188
402
|
full_name: {
|
|
403
|
+
value: null,
|
|
189
404
|
validation: {
|
|
190
405
|
rules: ['required'],
|
|
191
406
|
messages: {
|
|
@@ -194,6 +409,7 @@ form.addFields({
|
|
|
194
409
|
}
|
|
195
410
|
},
|
|
196
411
|
username: {
|
|
412
|
+
value: null,
|
|
197
413
|
validation: {
|
|
198
414
|
rules: ['required'],
|
|
199
415
|
messages: {
|
|
@@ -338,9 +554,11 @@ Method used to validate the entire form or a specific field.
|
|
|
338
554
|
const form = createForm({
|
|
339
555
|
username: {
|
|
340
556
|
value: null,
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
557
|
+
validation: {
|
|
558
|
+
rules: ['required'],
|
|
559
|
+
messages: {
|
|
560
|
+
required: 'The username field is required.'
|
|
561
|
+
}
|
|
344
562
|
}
|
|
345
563
|
}
|
|
346
564
|
})
|
|
@@ -372,9 +590,11 @@ Method used to validate a specific field.
|
|
|
372
590
|
const form = createForm({
|
|
373
591
|
username: {
|
|
374
592
|
value: null,
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
593
|
+
validation: {
|
|
594
|
+
rules: ['required'],
|
|
595
|
+
messages: {
|
|
596
|
+
required: 'The username field is required.'
|
|
597
|
+
}
|
|
378
598
|
}
|
|
379
599
|
}
|
|
380
600
|
})
|
|
@@ -406,9 +626,11 @@ Method used to validate the entire form.
|
|
|
406
626
|
const form = createForm({
|
|
407
627
|
username: {
|
|
408
628
|
value: null,
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
629
|
+
validation: {
|
|
630
|
+
rules: ['required'],
|
|
631
|
+
messages: {
|
|
632
|
+
required: 'The username field is required.'
|
|
633
|
+
}
|
|
412
634
|
}
|
|
413
635
|
}
|
|
414
636
|
})
|
|
@@ -463,6 +685,42 @@ form.first_name = 'keysdown'
|
|
|
463
685
|
axios.post('some-api', form.values(['first_name']))
|
|
464
686
|
```
|
|
465
687
|
|
|
688
|
+
### filledValues()
|
|
689
|
+
|
|
690
|
+
Method used to access form values, excluding fields with `null` or `undefined` values.
|
|
691
|
+
|
|
692
|
+
```js
|
|
693
|
+
const form = createForm({
|
|
694
|
+
username: null,
|
|
695
|
+
email: null,
|
|
696
|
+
role: 'admin'
|
|
697
|
+
})
|
|
698
|
+
|
|
699
|
+
form.username = 'keysdown'
|
|
700
|
+
|
|
701
|
+
axios.post('some-api', form.filledValues())
|
|
702
|
+
// sends { username: 'keysdown', role: 'admin' }
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
Note that `0`, `false`, and `''` (empty string) are considered filled values and will be included.
|
|
706
|
+
|
|
707
|
+
#### Filtering values
|
|
708
|
+
|
|
709
|
+
It is also possible to filter which fields are considered.
|
|
710
|
+
|
|
711
|
+
```js
|
|
712
|
+
const form = createForm({
|
|
713
|
+
first_name: null,
|
|
714
|
+
last_name: null,
|
|
715
|
+
role: 'admin'
|
|
716
|
+
})
|
|
717
|
+
|
|
718
|
+
form.first_name = 'keysdown'
|
|
719
|
+
|
|
720
|
+
axios.post('some-api', form.filledValues(['first_name', 'last_name']))
|
|
721
|
+
// sends { first_name: 'keysdown' }
|
|
722
|
+
```
|
|
723
|
+
|
|
466
724
|
### valuesAsFormData()
|
|
467
725
|
|
|
468
726
|
Method used to access all form values as form data.
|
|
@@ -542,6 +800,16 @@ form.messages.first('username')
|
|
|
542
800
|
form.rules.first('username')
|
|
543
801
|
```
|
|
544
802
|
|
|
803
|
+
#### get(key)
|
|
804
|
+
|
|
805
|
+
Returns the value for the given key, or `null` if not found.
|
|
806
|
+
|
|
807
|
+
```js
|
|
808
|
+
form.errors.get('username')
|
|
809
|
+
form.messages.get('username')
|
|
810
|
+
form.rules.get('username')
|
|
811
|
+
```
|
|
812
|
+
|
|
545
813
|
#### any()
|
|
546
814
|
|
|
547
815
|
Returns true if the collection has any items and false if it is empty.
|
|
@@ -585,7 +853,7 @@ form.messages.push('username', {
|
|
|
585
853
|
required: 'The username field is required.'
|
|
586
854
|
})
|
|
587
855
|
|
|
588
|
-
form.rules.push('username', 'required')
|
|
856
|
+
form.rules.push('username', ['required'])
|
|
589
857
|
```
|
|
590
858
|
|
|
591
859
|
#### has(key)
|
|
@@ -609,7 +877,7 @@ form.messages.unset('username')
|
|
|
609
877
|
form.rules.unset('username')
|
|
610
878
|
```
|
|
611
879
|
|
|
612
|
-
#### clear(
|
|
880
|
+
#### clear()
|
|
613
881
|
|
|
614
882
|
Clears the entire collection, making it empty.
|
|
615
883
|
|
|
@@ -621,8 +889,44 @@ form.rules.clear()
|
|
|
621
889
|
|
|
622
890
|
### Validation rules
|
|
623
891
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
892
|
+
The `formValidation` plugin provides 33 validation rules. Rules are loaded via plugins, see [Validation Plugins](#validation-plugins) for setup instructions.
|
|
893
|
+
|
|
894
|
+
| Rule | Parameters | Description |
|
|
895
|
+
|---|---|---|
|
|
896
|
+
| `required` | — | Field must be present and not empty |
|
|
897
|
+
| `email` | — | Must be a valid email address |
|
|
898
|
+
| `url` | — | Must be a valid URL |
|
|
899
|
+
| `min` | `min:6` | Minimum length (string) or value (number) or count (array) |
|
|
900
|
+
| `max` | `max:255` | Maximum length/value/count |
|
|
901
|
+
| `between` | `between:1,10` | Between min and max |
|
|
902
|
+
| `size` | `size:10` | Exact length/value/count |
|
|
903
|
+
| `alpha` | — | Only alphabetic characters |
|
|
904
|
+
| `alphaNumeric` | — | Only letters and numbers |
|
|
905
|
+
| `string` | — | Must be a string |
|
|
906
|
+
| `integer` | — | Must be an integer |
|
|
907
|
+
| `numeric` | — | Must be numeric |
|
|
908
|
+
| `array` | — | Must be an array |
|
|
909
|
+
| `boolean` | — | Must be boolean (true, false, 0, 1, '0', '1') |
|
|
910
|
+
| `date` | — | Must be a valid date |
|
|
911
|
+
| `same` | `same:field` | Must match another field |
|
|
912
|
+
| `different` | `different:field` | Must differ from another field |
|
|
913
|
+
| `confirmed` | `confirmed:field` | Must match field_confirmation |
|
|
914
|
+
| `in` | `in:admin,user` | Value must be in list |
|
|
915
|
+
| `notIn` | `notIn:admin,user` | Value must not be in list |
|
|
916
|
+
| `regex` | `regex:/pattern/` | Must match regex pattern |
|
|
917
|
+
| `startsWith` | `startsWith:foo,bar` | Must start with one of the values |
|
|
918
|
+
| `endsWith` | `endsWith:foo,bar` | Must end with one of the values |
|
|
919
|
+
| `digits` | `digits:4` | Must have exactly N digits |
|
|
920
|
+
| `digitsBetween` | `digitsBetween:3,6` | Must have between min and max digits |
|
|
921
|
+
| `ip` | — | Must be a valid IP (v4 or v6) |
|
|
922
|
+
| `json` | — | Must be a valid JSON string |
|
|
923
|
+
| `uuid` | — | Must be a valid UUID |
|
|
924
|
+
| `lessThan` | `lessThan:field` | Numeric value less than another field |
|
|
925
|
+
| `greaterThan` | `greaterThan:field` | Numeric value greater than another field |
|
|
926
|
+
| `lessThanOrEqual` | `lessThanOrEqual:field` | Less than or equal to another field |
|
|
927
|
+
| `greaterThanOrEqual` | `greaterThanOrEqual:field` | Greater than or equal to another field |
|
|
928
|
+
| `nullable` | — | Allows null/empty (always passes) |
|
|
929
|
+
|
|
930
|
+
## License
|
|
931
|
+
|
|
932
|
+
[MIT](LICENSE)
|
package/dist/form-wrapper.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});var e=e=>typeof e==`string
|
|
1
|
+
Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});var e=e=>{if(typeof e==`string`){let t=[],n=e;for(;n.length>0;){let e=n.indexOf(`regex:`);if(e!==-1){let r=n.substring(0,e);r&&t.push(...r.split(`|`).filter(Boolean));let i=n.substring(e),a=i.indexOf(`:/`);if(a!==-1){let e=a+1,r=i.lastIndexOf(`/`);if(r>e){let e=i.substring(r+1),a=e.match(/^[gimsuy]*/)[0],o=i.substring(0,r+1+a.length);t.push(o),n=e.substring(a.length),n.startsWith(`|`)&&(n=n.substring(1));continue}}t.push(i);break}else{t.push(...n.split(`|`).filter(Boolean));break}}return t}return e},t=t=>({validation:{rules:t.validation?.rules?e(t.validation.rules):[],messages:t.validation?.messages??{}},value:t.value??null}),n=class{collection={};all(){return this.collection}first(e){let t=this.get(e);if(t!==null){let e=Array.isArray(t)?t[0]:t;if(e!=null)return String(e)}return null}any(){return Object.keys(this.collection).length>0}fill(e){return this.collection=e,this}push(e,t){return this.collection={...this.collection,[e]:t},this}has(e){return Array.isArray(e)?e.some(e=>this.collection.hasOwnProperty(e)):this.collection.hasOwnProperty(e)}get(e,t=null){return this.has(e)?this.collection[e]:t}unset(e){return this.has(e)&&delete this.collection[e],this}clear(){return this.collection={},this}},r=class extends n{},i=class extends n{},a=class extends n{push(e,t){let n=this.get(e)||[];return this.collection={...this.collection,[e]:[...n,t]},this}get(e){return super.get(e,[])}},o=class{errors=new a;messages=new r;rules=new i},s=e=>typeof e==`object`&&!!e&&!Array.isArray(e),c=(e,t,n=null)=>{let r=t||new FormData;return Object.keys(e).forEach(t=>{let i=e[t];if(t=n?`${n}[${t}]`:t,!([void 0,null].indexOf(i)>-1)){if(s(i)&&!(i instanceof File)||Array.isArray(i)){c(i,r,t);return}r.append(t,i)}}),r},l=class e{static rules={};static defaultMessages={};static interpolateMessage(e,t,n,r){let i=r[t]||t.replace(/_/g,` `);return e.replace(/:field/g,i).replace(/:min/g,n[0]||``).replace(/:max/g,n[1]||``).replace(/:size/g,n[0]||``).replace(/:digits/g,n[0]||``).replace(/:other/g,n[0]?r[n[0]]||n[0].replace(/_/g,` `):``).replace(/:values/g,n.join(`, `))}awaiting=!1;originalValues={};fieldAttributes={};validation=new o;constructor(e){this.addFields(e)}static extend(t){return t(e,e.rules),e}static addRule(t,n){return e.rules[t]=Object.assign(n,{ruleName:t}),e}static locale(t){return e.defaultMessages=t.messages,e}addField(e,n){if(typeof n==`object`&&n&&Object.prototype.toString.call(n)===`[object Object]`&&`value`in n){let r=t(n);this[e]=r.value,this.originalValues[e]=r.value,this.validation.messages.push(e,r.validation.messages),this.validation.rules.push(e,r.validation.rules),n.attribute&&(this.fieldAttributes[e]=n.attribute)}else this[e]=n,this.originalValues[e]=n;return this}addFields(e){return Object.keys(e).forEach(t=>{this.addField(t,e[t])}),this}get errors(){return this.validation.errors}fill(e,t=!1){return Object.keys(e).forEach(n=>{let r=e[n];t&&(this.originalValues[n]=r),this[n]=r,n in this.originalValues||(this.originalValues[n]=r)}),this}get messages(){return this.validation.messages}removeField(e){return delete this[e],delete this.originalValues[e],delete this.fieldAttributes[e],this.validation.errors.unset(e),this.validation.messages.unset(e),this.validation.rules.unset(e),this}removeFields(e){return e.forEach(e=>{this.removeField(e)}),this}reset(){return this.validation.errors.clear(),Object.keys(this.originalValues).forEach(e=>{this[e]=this.originalValues[e]}),this}wasChanged(e){return Array.isArray(e)?e.some(e=>this[e]!==this.originalValues[e]):this[e]!==this.originalValues[e]}filled(e){return Array.isArray(e)?e.every(e=>this[e]!==null&&this[e]!==void 0&&this[e]!==``):this[e]!==null&&this[e]!==void 0&&this[e]!==``}get rules(){return this.validation.rules}setAwaiting(e=!0){return this.awaiting=e,this}validate(e=null){return e?this.validateField(e):this.validateForm()}validateField(t){this.validation.errors.unset(t);let n=this.validation.rules.get(t);if(n&&n.length>0){let r=this.constructor.rules;return(async()=>{for(let i of n){let n=i;if(typeof n==`function`){if(`ruleName`in n){let r=n.ruleName;if(r===`nullable`&&(this[t]===null||this[t]===void 0||this[t]===``))return;try{await n(this[t],[],this,t)}catch(n){let i=this.validation.messages.get(t);throw r&&i&&r in i?this.validation.errors.push(t,i[r]):r in e.defaultMessages&&this.validation.errors.push(t,e.interpolateMessage(e.defaultMessages[r],t,[],this.fieldAttributes)),n}}else await new Promise((e,r)=>{let i=!1,a=e=>{i=!0,e&&this.validation.errors.push(t,e)};try{let o=n({value:this[t],fail:a,form:this,field:t});o instanceof Promise?o.then(()=>{i?r():e()}).catch(()=>{r()}):i?r():e()}catch{r()}});continue}let a=n.indexOf(`:`),o=a===-1?n:n.substring(0,a);if(o===`nullable`&&(this[t]===null||this[t]===void 0||this[t]===``))return;let s=a===-1?[]:n.substring(a+1).split(`,`);if(o in r)try{await r[o](this[t],s,this,t)}catch(n){let r=this.validation.messages.get(t);throw r&&o in r?this.validation.errors.push(t,r[o]):o in e.defaultMessages&&this.validation.errors.push(t,e.interpolateMessage(e.defaultMessages[o],t,s,this.fieldAttributes)),n}else throw Error(`There is no validation rule called "${o}"`)}})()}return Promise.resolve()}validateForm(){let e=Object.keys(this.originalValues).map(e=>this.validateField(e));return Promise.all(e).then(()=>Promise.resolve(this)).catch(()=>Promise.reject(this))}values(e){let t={};return Object.keys(this.originalValues).forEach(n=>{(!e||e.includes(n))&&(t[n]=this[n])}),t}filledValues(e){let t={};return Object.keys(this.originalValues).forEach(n=>{(!e||e.includes(n))&&this[n]!=null&&(t[n]=this[n])}),t}valuesAsFormData(e){return c(this.values(e))}},u=e=>new l(e),d=l;exports.createForm=u,exports.default=d;
|