@keysdown/form-wrapper 1.0.5 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +299 -31
  2. package/dist/form-wrapper.cjs +1 -1
  3. package/dist/form-wrapper.js +108 -28
  4. package/dist/form-wrapper.umd.cjs +1 -1
  5. package/dist/plugins/formValidation.cjs +1 -0
  6. package/dist/plugins/formValidation.d.ts +4 -0
  7. package/dist/plugins/formValidation.js +7 -0
  8. package/dist/plugins/locales.cjs +1 -0
  9. package/dist/plugins/locales.d.ts +2 -0
  10. package/dist/plugins/locales.js +115 -0
  11. package/dist/plugins/rules.cjs +1 -0
  12. package/dist/plugins/rules.d.ts +2 -0
  13. package/dist/plugins/rules.js +2 -0
  14. package/dist/rules-BIt-pifQ.cjs +1 -0
  15. package/dist/rules-DZmnRoUy.js +196 -0
  16. package/dist/src/core/Errors.d.ts +1 -1
  17. package/dist/src/core/Form.d.ts +10 -0
  18. package/dist/src/plugins/formValidation.d.ts +3 -0
  19. package/dist/src/plugins/locales/en.d.ts +3 -0
  20. package/dist/src/plugins/locales/es.d.ts +3 -0
  21. package/dist/src/plugins/locales/index.d.ts +3 -0
  22. package/dist/src/plugins/locales/pt.d.ts +3 -0
  23. package/dist/src/plugins/rules/alpha.d.ts +2 -0
  24. package/dist/src/plugins/rules/alphaNumeric.d.ts +2 -0
  25. package/dist/src/plugins/rules/array.d.ts +2 -0
  26. package/dist/src/plugins/rules/between.d.ts +2 -0
  27. package/dist/src/plugins/rules/boolean.d.ts +2 -0
  28. package/dist/src/plugins/rules/confirmed.d.ts +2 -0
  29. package/dist/src/plugins/rules/date.d.ts +2 -0
  30. package/dist/src/plugins/rules/different.d.ts +2 -0
  31. package/dist/src/plugins/rules/digits.d.ts +2 -0
  32. package/dist/src/plugins/rules/digitsBetween.d.ts +2 -0
  33. package/dist/src/plugins/rules/email.d.ts +2 -0
  34. package/dist/src/plugins/rules/endsWith.d.ts +2 -0
  35. package/dist/src/plugins/rules/greaterThan.d.ts +2 -0
  36. package/dist/src/plugins/rules/greaterThanOrEqual.d.ts +2 -0
  37. package/dist/src/plugins/rules/in.d.ts +2 -0
  38. package/dist/src/plugins/rules/index.d.ts +35 -0
  39. package/dist/src/plugins/rules/integer.d.ts +2 -0
  40. package/dist/src/plugins/rules/ip.d.ts +2 -0
  41. package/dist/src/plugins/rules/json.d.ts +2 -0
  42. package/dist/src/plugins/rules/lessThan.d.ts +2 -0
  43. package/dist/src/plugins/rules/lessThanOrEqual.d.ts +2 -0
  44. package/dist/src/plugins/rules/max.d.ts +2 -0
  45. package/dist/src/plugins/rules/min.d.ts +2 -0
  46. package/dist/src/plugins/rules/notIn.d.ts +2 -0
  47. package/dist/src/plugins/rules/nullable.d.ts +2 -0
  48. package/dist/src/plugins/rules/numeric.d.ts +2 -0
  49. package/dist/src/plugins/rules/regex.d.ts +2 -0
  50. package/dist/src/plugins/rules/required.d.ts +2 -0
  51. package/dist/src/plugins/rules/same.d.ts +2 -0
  52. package/dist/src/plugins/rules/size.d.ts +2 -0
  53. package/dist/src/plugins/rules/startsWith.d.ts +2 -0
  54. package/dist/src/plugins/rules/string.d.ts +2 -0
  55. package/dist/src/plugins/rules/url.d.ts +2 -0
  56. package/dist/src/plugins/rules/uuid.d.ts +2 -0
  57. package/dist/src/types/fields.d.ts +1 -0
  58. package/dist/src/types/locale.d.ts +7 -0
  59. package/dist/src/types/plugin.d.ts +2 -0
  60. package/dist/src/types/rules.d.ts +7 -1
  61. package/dist/src/types/validations.d.ts +3 -2
  62. package/dist/src/utils/rule.d.ts +2 -0
  63. package/package.json +17 -2
  64. package/dist/form-wrapper.iife.js +0 -1
  65. 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 package that allows you to easily manage forms, with Form Wrapper it is possible to perform validations with error messages, in addition to managing the state of the forms.
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.4 kB
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 {createForm} from '@keysdown/form-wrapper'
311
+ import FormWrapper from '@keysdown/form-wrapper'
312
+ import formValidation from '@keysdown/form-wrapper/plugins/formValidation'
107
313
 
108
- const form = ref(createForm({
314
+ FormWrapper.extend(formValidation)
315
+
316
+ const form = ref(new FormWrapper({
109
317
  first_name: {
110
318
  value: null,
111
- rules: ['required'],
112
- messages: {
113
- required: 'The first name field is required.'
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
- rules: ['required'],
119
- messages: {
120
- required: 'The last name field is required.'
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
- rules: ['required', 'min:6'],
126
- messages: {
127
- required: 'The username field is required.',
128
- min: 'The username field must have at least 6 characters'
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
- rules: ['required'],
342
- messages: {
343
- required: 'The username field is required.'
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
- rules: ['required'],
376
- messages: {
377
- required: 'The username field is required.'
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
- rules: ['required'],
410
- messages: {
411
- required: 'The username field is required.'
629
+ validation: {
630
+ rules: ['required'],
631
+ messages: {
632
+ required: 'The username field is required.'
633
+ }
412
634
  }
413
635
  }
414
636
  })
@@ -542,6 +764,16 @@ form.messages.first('username')
542
764
  form.rules.first('username')
543
765
  ```
544
766
 
767
+ #### get(key)
768
+
769
+ Returns the value for the given key, or `null` if not found.
770
+
771
+ ```js
772
+ form.errors.get('username')
773
+ form.messages.get('username')
774
+ form.rules.get('username')
775
+ ```
776
+
545
777
  #### any()
546
778
 
547
779
  Returns true if the collection has any items and false if it is empty.
@@ -585,7 +817,7 @@ form.messages.push('username', {
585
817
  required: 'The username field is required.'
586
818
  })
587
819
 
588
- form.rules.push('username', 'required')
820
+ form.rules.push('username', ['required'])
589
821
  ```
590
822
 
591
823
  #### has(key)
@@ -609,7 +841,7 @@ form.messages.unset('username')
609
841
  form.rules.unset('username')
610
842
  ```
611
843
 
612
- #### clear(key)
844
+ #### clear()
613
845
 
614
846
  Clears the entire collection, making it empty.
615
847
 
@@ -621,8 +853,44 @@ form.rules.clear()
621
853
 
622
854
  ### Validation rules
623
855
 
624
- There are some predefined validation rules that you can use in the form:
625
-
626
- #### required
627
-
628
- Used when a field is required
856
+ The `formValidation` plugin provides 33 validation rules. Rules are loaded via plugins, see [Validation Plugins](#validation-plugins) for setup instructions.
857
+
858
+ | Rule | Parameters | Description |
859
+ |---|---|---|
860
+ | `required` | | Field must be present and not empty |
861
+ | `email` | — | Must be a valid email address |
862
+ | `url` | — | Must be a valid URL |
863
+ | `min` | `min:6` | Minimum length (string) or value (number) or count (array) |
864
+ | `max` | `max:255` | Maximum length/value/count |
865
+ | `between` | `between:1,10` | Between min and max |
866
+ | `size` | `size:10` | Exact length/value/count |
867
+ | `alpha` | — | Only alphabetic characters |
868
+ | `alphaNumeric` | — | Only letters and numbers |
869
+ | `string` | — | Must be a string |
870
+ | `integer` | — | Must be an integer |
871
+ | `numeric` | — | Must be numeric |
872
+ | `array` | — | Must be an array |
873
+ | `boolean` | — | Must be boolean (true, false, 0, 1, '0', '1') |
874
+ | `date` | — | Must be a valid date |
875
+ | `same` | `same:field` | Must match another field |
876
+ | `different` | `different:field` | Must differ from another field |
877
+ | `confirmed` | `confirmed:field` | Must match field_confirmation |
878
+ | `in` | `in:admin,user` | Value must be in list |
879
+ | `notIn` | `notIn:admin,user` | Value must not be in list |
880
+ | `regex` | `regex:/pattern/` | Must match regex pattern |
881
+ | `startsWith` | `startsWith:foo,bar` | Must start with one of the values |
882
+ | `endsWith` | `endsWith:foo,bar` | Must end with one of the values |
883
+ | `digits` | `digits:4` | Must have exactly N digits |
884
+ | `digitsBetween` | `digitsBetween:3,6` | Must have between min and max digits |
885
+ | `ip` | — | Must be a valid IP (v4 or v6) |
886
+ | `json` | — | Must be a valid JSON string |
887
+ | `uuid` | — | Must be a valid UUID |
888
+ | `lessThan` | `lessThan:field` | Numeric value less than another field |
889
+ | `greaterThan` | `greaterThan:field` | Numeric value greater than another field |
890
+ | `lessThanOrEqual` | `lessThanOrEqual:field` | Less than or equal to another field |
891
+ | `greaterThanOrEqual` | `greaterThanOrEqual:field` | Greater than or equal to another field |
892
+ | `nullable` | — | Allows null/empty (always passes) |
893
+
894
+ ## License
895
+
896
+ [MIT](LICENSE)
@@ -1 +1 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});var e=e=>typeof e==`string`?e.split(`|`):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){let e=Array.isArray(t)?t[0]:t;if(e)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={required:e=>new Promise((t,n)=>{if(e==null)return n();String(e).replace(/\s/g,``).length>0?t(e):n()})},c=e=>typeof e==`object`&&!!e&&!Array.isArray(e),l=(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(c(i)&&!(i instanceof File)||Array.isArray(i)){l(i,r,t);return}r.append(t,i)}}),r},u=class{awaiting=!1;originalValues={};validation=new o;constructor(e){this.addFields(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)}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}),this}get messages(){return this.validation.messages}removeField(e){return delete this[e],delete this.originalValues[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(e){this.validation.errors.unset(e);let t=this.validation.rules.get(e);if(t&&t.length>0){let n=t.map(t=>{let n=t.split(`:`),r=n[0],i=n.length===2?n[1].split(`,`):[];return r in s?s[r](this[e],i).catch(t=>{let n=this.validation.messages.get(e);return n&&r in n&&this.validation.errors.push(e,n[r]),Promise.reject(t)}):Promise.reject(Error(`There is no validation rule called "${r}"`))});return Promise.all(n).then(()=>{})}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}valuesAsFormData(e){return l(this.values(e))}},d=e=>new u(e),f=u;exports.createForm=d,exports.default=f;
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}valuesAsFormData(e){return c(this.values(e))}},u=e=>new l(e),d=l;exports.createForm=u,exports.default=d;
@@ -1,5 +1,32 @@
1
1
  //#region src/utils/fields.ts
2
- var e = (e) => typeof e == "string" ? e.split("|") : e, t = (t) => ({
2
+ var e = (e) => {
3
+ if (typeof e == "string") {
4
+ let t = [], n = e;
5
+ for (; n.length > 0;) {
6
+ let e = n.indexOf("regex:");
7
+ if (e !== -1) {
8
+ let r = n.substring(0, e);
9
+ r && t.push(...r.split("|").filter(Boolean));
10
+ let i = n.substring(e), a = i.indexOf(":/");
11
+ if (a !== -1) {
12
+ let e = a + 1, r = i.lastIndexOf("/");
13
+ if (r > e) {
14
+ let e = i.substring(r + 1), a = e.match(/^[gimsuy]*/)[0], o = i.substring(0, r + 1 + a.length);
15
+ t.push(o), n = e.substring(a.length), n.startsWith("|") && (n = n.substring(1));
16
+ continue;
17
+ }
18
+ }
19
+ t.push(i);
20
+ break;
21
+ } else {
22
+ t.push(...n.split("|").filter(Boolean));
23
+ break;
24
+ }
25
+ }
26
+ return t;
27
+ }
28
+ return e;
29
+ }, t = (t) => ({
3
30
  validation: {
4
31
  rules: t.validation?.rules ? e(t.validation.rules) : [],
5
32
  messages: t.validation?.messages ?? {}
@@ -12,9 +39,9 @@ var e = (e) => typeof e == "string" ? e.split("|") : e, t = (t) => ({
12
39
  }
13
40
  first(e) {
14
41
  let t = this.get(e);
15
- if (t) {
42
+ if (t !== null) {
16
43
  let e = Array.isArray(t) ? t[0] : t;
17
- if (e) return String(e);
44
+ if (e != null) return String(e);
18
45
  }
19
46
  return null;
20
47
  }
@@ -57,32 +84,45 @@ var e = (e) => typeof e == "string" ? e.split("|") : e, t = (t) => ({
57
84
  errors = new a();
58
85
  messages = new r();
59
86
  rules = new i();
60
- }, s = { required: (e) => new Promise((t, n) => {
61
- if (e == null) return n();
62
- String(e).replace(/\s/g, "").length > 0 ? t(e) : n();
63
- }) }, c = (e) => typeof e == "object" && !!e && !Array.isArray(e), l = (e, t, n = null) => {
87
+ }, s = (e) => typeof e == "object" && !!e && !Array.isArray(e), c = (e, t, n = null) => {
64
88
  let r = t || new FormData();
65
89
  return Object.keys(e).forEach((t) => {
66
90
  let i = e[t];
67
91
  if (t = n ? `${n}[${t}]` : t, !([void 0, null].indexOf(i) > -1)) {
68
- if (c(i) && !(i instanceof File) || Array.isArray(i)) {
69
- l(i, r, t);
92
+ if (s(i) && !(i instanceof File) || Array.isArray(i)) {
93
+ c(i, r, t);
70
94
  return;
71
95
  }
72
96
  r.append(t, i);
73
97
  }
74
98
  }), r;
75
- }, u = class {
99
+ }, l = class e {
100
+ static rules = {};
101
+ static defaultMessages = {};
102
+ static interpolateMessage(e, t, n, r) {
103
+ let i = r[t] || t.replace(/_/g, " ");
104
+ 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(", "));
105
+ }
76
106
  awaiting = !1;
77
107
  originalValues = {};
108
+ fieldAttributes = {};
78
109
  validation = new o();
79
110
  constructor(e) {
80
111
  this.addFields(e);
81
112
  }
113
+ static extend(t) {
114
+ return t(e, e.rules), e;
115
+ }
116
+ static addRule(t, n) {
117
+ return e.rules[t] = Object.assign(n, { ruleName: t }), e;
118
+ }
119
+ static locale(t) {
120
+ return e.defaultMessages = t.messages, e;
121
+ }
82
122
  addField(e, n) {
83
123
  if (typeof n == "object" && n && Object.prototype.toString.call(n) === "[object Object]" && "value" in n) {
84
124
  let r = t(n);
85
- 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);
125
+ 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);
86
126
  } else this[e] = n, this.originalValues[e] = n;
87
127
  return this;
88
128
  }
@@ -97,14 +137,14 @@ var e = (e) => typeof e == "string" ? e.split("|") : e, t = (t) => ({
97
137
  fill(e, t = !1) {
98
138
  return Object.keys(e).forEach((n) => {
99
139
  let r = e[n];
100
- t && (this.originalValues[n] = r), this[n] = r;
140
+ t && (this.originalValues[n] = r), this[n] = r, n in this.originalValues || (this.originalValues[n] = r);
101
141
  }), this;
102
142
  }
103
143
  get messages() {
104
144
  return this.validation.messages;
105
145
  }
106
146
  removeField(e) {
107
- return delete this[e], delete this.originalValues[e], this.validation.errors.unset(e), this.validation.messages.unset(e), this.validation.rules.unset(e), this;
147
+ 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;
108
148
  }
109
149
  removeFields(e) {
110
150
  return e.forEach((e) => {
@@ -131,18 +171,58 @@ var e = (e) => typeof e == "string" ? e.split("|") : e, t = (t) => ({
131
171
  validate(e = null) {
132
172
  return e ? this.validateField(e) : this.validateForm();
133
173
  }
134
- validateField(e) {
135
- this.validation.errors.unset(e);
136
- let t = this.validation.rules.get(e);
137
- if (t && t.length > 0) {
138
- let n = t.map((t) => {
139
- let n = t.split(":"), r = n[0], i = n.length === 2 ? n[1].split(",") : [];
140
- return r in s ? s[r](this[e], i).catch((t) => {
141
- let n = this.validation.messages.get(e);
142
- return n && r in n && this.validation.errors.push(e, n[r]), Promise.reject(t);
143
- }) : Promise.reject(/* @__PURE__ */ Error(`There is no validation rule called "${r}"`));
144
- });
145
- return Promise.all(n).then(() => {});
174
+ validateField(t) {
175
+ this.validation.errors.unset(t);
176
+ let n = this.validation.rules.get(t);
177
+ if (n && n.length > 0) {
178
+ let r = this.constructor.rules;
179
+ return (async () => {
180
+ for (let i of n) {
181
+ let n = i;
182
+ if (typeof n == "function") {
183
+ if ("ruleName" in n) {
184
+ let r = n.ruleName;
185
+ if (r === "nullable" && (this[t] === null || this[t] === void 0 || this[t] === "")) return;
186
+ try {
187
+ await n(this[t], [], this, t);
188
+ } catch (n) {
189
+ let i = this.validation.messages.get(t);
190
+ 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;
191
+ }
192
+ } else await new Promise((e, r) => {
193
+ let i = !1, a = (e) => {
194
+ i = !0, e && this.validation.errors.push(t, e);
195
+ };
196
+ try {
197
+ let o = n({
198
+ value: this[t],
199
+ fail: a,
200
+ form: this,
201
+ field: t
202
+ });
203
+ o instanceof Promise ? o.then(() => {
204
+ i ? r() : e();
205
+ }).catch(() => {
206
+ r();
207
+ }) : i ? r() : e();
208
+ } catch {
209
+ r();
210
+ }
211
+ });
212
+ continue;
213
+ }
214
+ let a = n.indexOf(":"), o = a === -1 ? n : n.substring(0, a);
215
+ if (o === "nullable" && (this[t] === null || this[t] === void 0 || this[t] === "")) return;
216
+ let s = a === -1 ? [] : n.substring(a + 1).split(",");
217
+ if (o in r) try {
218
+ await r[o](this[t], s, this, t);
219
+ } catch (n) {
220
+ let r = this.validation.messages.get(t);
221
+ 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;
222
+ }
223
+ else throw Error(`There is no validation rule called "${o}"`);
224
+ }
225
+ })();
146
226
  }
147
227
  return Promise.resolve();
148
228
  }
@@ -157,8 +237,8 @@ var e = (e) => typeof e == "string" ? e.split("|") : e, t = (t) => ({
157
237
  }), t;
158
238
  }
159
239
  valuesAsFormData(e) {
160
- return l(this.values(e));
240
+ return c(this.values(e));
161
241
  }
162
- }, d = (e) => new u(e), f = u;
242
+ }, u = (e) => new l(e), d = l;
163
243
  //#endregion
164
- export { d as createForm, f as default };
244
+ export { u as createForm, d as default };