@keysdown/form-wrapper 0.0.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Keys Down
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,529 @@
1
+ <h1 align="center">
2
+ Form Wrapper (Beta)
3
+ </h1>
4
+
5
+ > 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.
6
+
7
+ > This package is currently in Beta and is being tested in live applications, feel free to help in the development and maturation of the package.
8
+
9
+ ## Installation
10
+
11
+ ```shell
12
+ npm install @keysdown/form-wrapper --save
13
+ ```
14
+
15
+ ## Basic example
16
+
17
+ Basic example using Vue.
18
+
19
+ ```vue
20
+
21
+ <template>
22
+ <form @submit.prevent="submit">
23
+ <input type="text" v-model="form.first_name"/>
24
+ <input type="text" v-model="form.last_name"/>
25
+ <input type="text" v-model="form.username"/>
26
+ <button type="submit" :disabled="form.awaiting"/>
27
+ </form>
28
+ </template>
29
+
30
+ <script setup lang="ts">
31
+ import axios from 'axios'
32
+ import {ref} from 'vue'
33
+ import Form from '@keysdown/form-wrapper'
34
+
35
+ const form = ref(Form({
36
+ first_name: null,
37
+ last_name: null,
38
+ username: null
39
+ }))
40
+
41
+ const submit = () => {
42
+ form.value.setAwaiting(true)
43
+
44
+ axios.post('some-api', form.value.values())
45
+ .then(({data}) => {
46
+ form.value.reset()
47
+
48
+ // ...
49
+ })
50
+ .catch(({errors}) => {
51
+ form.value.errors.fill(errors)
52
+
53
+ form.value.setAwaiting(false)
54
+ })
55
+ }
56
+ </script>
57
+ ```
58
+
59
+ ## Basic example with validation
60
+
61
+ Basic example with validation using Vue.
62
+
63
+ ```vue
64
+ <template>
65
+ <form @submit.prevent="submit">
66
+ <input type="text" v-model="form.first_name"/>
67
+ <input type="text" v-model="form.last_name"/>
68
+ <input type="text" v-model="form.username"/>
69
+ <button type="submit" :disabled="form.awaiting"/>
70
+ </form>
71
+ </template>
72
+
73
+ <script setup lang="ts">
74
+ import axios from 'axios'
75
+ import {ref} from 'vue'
76
+ import Form from '@keysdown/form-wrapper'
77
+
78
+ const form = ref(Form({
79
+ first_name: {
80
+ value: null,
81
+ rules: ['required'],
82
+ messages: {
83
+ required: 'The first name field is required.'
84
+ }
85
+ },
86
+ last_name: {
87
+ value: null,
88
+ rules: ['required'],
89
+ messages: {
90
+ required: 'The last name field is required.'
91
+ }
92
+ },
93
+ username: {
94
+ value: null,
95
+ rules: ['required', 'min:6'],
96
+ messages: {
97
+ required: 'The username field is required.',
98
+ min: 'The username field must have at least 6 characters'
99
+ }
100
+ }
101
+ }))
102
+
103
+ const submit = () => {
104
+ form.value.validate()
105
+ .then((form) => {
106
+ form.setAwaiting(true)
107
+
108
+ axios.post('some-api', form.values())
109
+ .then(({data}) => {
110
+ form.reset()
111
+
112
+ // ...
113
+ })
114
+ .catch(({errors}) => {
115
+ form.errors.fill(errors)
116
+
117
+ form.setAwaiting(false)
118
+ })
119
+ })
120
+ .catch((form) => {
121
+ console.log('error')
122
+ })
123
+ }
124
+ </script>
125
+ ```
126
+
127
+
128
+ ## Form methods
129
+
130
+ ### addField(field, value)
131
+
132
+ Method used to add a single field to the form.
133
+
134
+ ```js
135
+ form.addField('username', null)
136
+
137
+ form.addField('username', {
138
+ validation: {
139
+ rules: ['required'],
140
+ messages: {
141
+ required: 'The username field is required.'
142
+ }
143
+ }
144
+ })
145
+ ```
146
+
147
+ ### addFields(fields)
148
+
149
+ Method used to add multiple fields to the form.
150
+
151
+ ```js
152
+ form.addFields({
153
+ full_name: null,
154
+ username: null
155
+ })
156
+
157
+ form.addFields({
158
+ full_name: {
159
+ validation: {
160
+ rules: ['required'],
161
+ messages: {
162
+ required: 'The full name field is required.'
163
+ }
164
+ }
165
+ },
166
+ username: {
167
+ validation: {
168
+ rules: ['required'],
169
+ messages: {
170
+ required: 'The username field is required.'
171
+ }
172
+ }
173
+ }
174
+ })
175
+ ```
176
+
177
+ ### fill(data, updateOriginalValues = false)
178
+
179
+ Method used when you want to fill in several form fields at once.
180
+
181
+ ```js
182
+ const form = Form({
183
+ username: null
184
+ })
185
+
186
+ console.log(form.values()) // {username: null}
187
+
188
+ form.fill({
189
+ username: 'keysdown'
190
+ })
191
+
192
+ console.log(form.values()) // {username: 'keysdown'}
193
+ ```
194
+
195
+ ### removeField(field)
196
+
197
+ Method used when you want to remove a field from the form.
198
+
199
+ ```js
200
+ const form = Form({
201
+ username: null
202
+ })
203
+
204
+ console.log(form.values()) // {username: null}
205
+
206
+ form.removeField('username')
207
+
208
+ console.log(form.values()) // {}
209
+ ```
210
+
211
+ ### removeFields(fields)
212
+
213
+ Method used to remove multiple fields to the form.
214
+
215
+ ```js
216
+ const form = Form({
217
+ username: null
218
+ })
219
+
220
+ console.log(form.values()) // {username: null}
221
+
222
+ form.removeFields(['username'])
223
+
224
+ console.log(form.values()) // {}
225
+ ```
226
+
227
+ ### reset()
228
+
229
+ Method used to reset all form values to original state.
230
+
231
+ ```js
232
+ const form = Form({
233
+ username: null
234
+ })
235
+
236
+ form.username = 'keysdown'
237
+
238
+ console.log(form.values()) // {username: 'keysdown'}
239
+
240
+ form.reset()
241
+
242
+ console.log(form.values()) // {username: null}
243
+ ```
244
+
245
+ ### setAwaiting(awaiting = true)
246
+
247
+ Method used to change the form loading state.
248
+
249
+ ```js
250
+ const form = Form({
251
+ username: null
252
+ })
253
+
254
+ console.log(form.awaiting) // false
255
+
256
+ form.setAwaiting()
257
+
258
+ console.log(form.awaiting) // true
259
+
260
+ form.setAwaiting(false)
261
+
262
+ console.log(form.awaiting) // false
263
+ ```
264
+
265
+ ### validate(field = null)
266
+
267
+ Method used to validate the entire form or a specific field.
268
+
269
+ ```js
270
+ const form = Form({
271
+ username: {
272
+ value: null,
273
+ rules: ['required'],
274
+ messages: {
275
+ required: 'The username field is required.'
276
+ }
277
+ }
278
+ })
279
+
280
+ form.validate('username')
281
+ .then(() => {
282
+ //...
283
+ })
284
+ .catch(() => {
285
+ //...
286
+ })
287
+
288
+ form.validate()
289
+ .then((form) => {
290
+ form.setAwaiting(true)
291
+
292
+ // ...
293
+ })
294
+ .catch((form) => {
295
+ console.log('error')
296
+ })
297
+ ```
298
+
299
+ ### validateField(field)
300
+
301
+ Method used to validate a specific field.
302
+
303
+ ```js
304
+ const form = Form({
305
+ username: {
306
+ value: null,
307
+ rules: ['required'],
308
+ messages: {
309
+ required: 'The username field is required.'
310
+ }
311
+ }
312
+ })
313
+
314
+ form.validateField('username')
315
+ .then(() => {
316
+ //...
317
+ })
318
+ .catch(() => {
319
+ //...
320
+ })
321
+
322
+ // Same as:
323
+
324
+ form.validate('username')
325
+ .then(() => {
326
+ //...
327
+ })
328
+ .catch(() => {
329
+ //...
330
+ })
331
+ ```
332
+
333
+ ### validateForm()
334
+
335
+ Method used to validate the entire form.
336
+
337
+ ```js
338
+ const form = Form({
339
+ username: {
340
+ value: null,
341
+ rules: ['required'],
342
+ messages: {
343
+ required: 'The username field is required.'
344
+ }
345
+ }
346
+ })
347
+
348
+ form.validateForm()
349
+ .then((form) => {
350
+ //...
351
+ })
352
+ .catch((form) => {
353
+ //...
354
+ })
355
+
356
+ // Same as:
357
+
358
+ form.validate()
359
+ .then((form) => {
360
+ form.setAwaiting(true)
361
+
362
+ // ...
363
+ })
364
+ .catch((form) => {
365
+ console.log('error')
366
+ })
367
+ ```
368
+
369
+ ### values()
370
+
371
+ Method used to access all form values in json format.
372
+
373
+ ```js
374
+ const form = Form({
375
+ username: null
376
+ })
377
+
378
+ form.username = 'keysdown'
379
+
380
+ axios.post('some-api', form.values())
381
+ ```
382
+
383
+ ### valuesAsFormData()
384
+
385
+ Method used to access all form values as form data.
386
+
387
+ ```js
388
+ const form = Form({
389
+ username: null
390
+ })
391
+
392
+ form.username = 'keysdown'
393
+
394
+ axios.post('some-api', form.valuesAsFormData(), {
395
+ headers: {
396
+ 'Content-Type': 'multipart/form-data'
397
+ }
398
+ })
399
+ ```
400
+
401
+ ## Validation
402
+
403
+ There is a property in the form object dedicated to validations, you can access everything related to validations through the `validation` property:
404
+
405
+ ```js
406
+ form.validation.errors.all()
407
+
408
+ form.validation.messages.all()
409
+
410
+ form.validation.rules.all()
411
+ ```
412
+
413
+ You can also use the following shortcuts:
414
+
415
+ ```js
416
+ form.errors.all()
417
+
418
+ form.messages.all()
419
+
420
+ form.rules.all()
421
+ ```
422
+
423
+ ### Validation collections
424
+
425
+ Both errors, messages and rules work using collections, the methods for managing collections are as follows:
426
+
427
+ #### all()
428
+
429
+ Returns all items in the collection.
430
+
431
+ ```js
432
+ form.errors.all()
433
+ form.messages.all()
434
+ form.rules.all()
435
+ ```
436
+
437
+ #### first(key)
438
+
439
+ Returns the first item in the collection.
440
+
441
+ ```js
442
+ form.errors.first('username')
443
+ form.messages.first('username')
444
+ form.rules.first('username')
445
+ ```
446
+
447
+ #### any()
448
+
449
+ Returns true if the collection has any items and false if it is empty.
450
+
451
+ ```js
452
+ form.errors.any()
453
+ form.messages.any()
454
+ form.rules.any()
455
+ ```
456
+
457
+ #### fill(items)
458
+
459
+ Inserts values into a collection, replacing any previous values.
460
+
461
+ ```js
462
+ form.errors.fill({
463
+ username: [
464
+ 'The username field is required.'
465
+ ]
466
+ })
467
+
468
+ form.messages.fill({
469
+ username: {
470
+ required: 'The username field is required.'
471
+ }
472
+ })
473
+
474
+ form.rules.fill({
475
+ username: ['required']
476
+ })
477
+ ```
478
+
479
+ #### push(key, data)
480
+
481
+ Inserts a single value into an item in a collection.
482
+
483
+ ```js
484
+ form.errors.push('username', 'The username field is required.')
485
+
486
+ form.messages.push('username', {
487
+ required: 'The username field is required.'
488
+ })
489
+
490
+ form.rules.push('username', 'required')
491
+ ```
492
+
493
+ #### has(key)
494
+
495
+ Checking if the collection has an item with the key.
496
+
497
+ ```js
498
+ form.errors.has('username')
499
+ form.messages.has('username')
500
+ form.rules.has('username')
501
+ ```
502
+
503
+ #### unset(key)
504
+
505
+ Remove an item from the collection.
506
+
507
+ ```js
508
+ form.errors.unset('username')
509
+ form.messages.unset('username')
510
+ form.rules.unset('username')
511
+ ```
512
+
513
+ #### clear(key)
514
+
515
+ Clears the entire collection, making it empty.
516
+
517
+ ```js
518
+ form.errors.clear()
519
+ form.messages.clear()
520
+ form.rules.clear()
521
+ ```
522
+
523
+ ### Validation rules
524
+
525
+ There are some predefined validation rules that you can use in the form:
526
+
527
+ #### required
528
+
529
+ Used when a field is required
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@keysdown/form-wrapper",
3
+ "version": "0.0.2",
4
+ "description": "JavaScript wrapper for forms",
5
+ "type": "module",
6
+ "main": "./dist/form-wrapper.umd.js",
7
+ "module": "./dist/form-wrapper.es.js",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/form-wrapper.es.js",
11
+ "require": "./dist/form-wrapper.umd.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "dev": "vite",
16
+ "build": "tsc && vite build",
17
+ "preview": "vite preview"
18
+ },
19
+ "devDependencies": {
20
+ "typescript": "^5.5.3",
21
+ "vite": "^5.4.1"
22
+ }
23
+ }
@@ -0,0 +1,21 @@
1
+ import {Collection} from '../utils/collections'
2
+
3
+ export class Errors extends Collection<string[]> {
4
+ public push(
5
+ item: string,
6
+ data: any,
7
+ ): this {
8
+ const currentItem = this.get(item) || []
9
+
10
+ this.collection = {
11
+ ...this.collection,
12
+ [item]: [...currentItem, data]
13
+ }
14
+
15
+ return this
16
+ }
17
+
18
+ public get(key: string): string[] | null {
19
+ return super.get(key, [])
20
+ }
21
+ }
@@ -0,0 +1,197 @@
1
+ import {Field, FieldDeclaration, Fields, RawFields} from '../types/fields.ts'
2
+ import {generateFieldDeclaration} from '../utils/fields.ts'
3
+ import {Values} from '../types/values.ts'
4
+ import {Validation} from './Validation.ts'
5
+ import {Rule} from '../utils/validations.ts'
6
+ import {objectToFormData} from '../utils/helpers.ts'
7
+ import {ErrorMessage} from '../types/messages.ts'
8
+
9
+ export class Form {
10
+ [key: string]: any
11
+
12
+ public awaiting: boolean = false
13
+
14
+ public originalValues: Values = {}
15
+
16
+ public validation: Validation = new Validation()
17
+
18
+ public constructor(
19
+ fields: Fields | RawFields
20
+ ) {
21
+ this.addFields(fields)
22
+ }
23
+
24
+ public addField(
25
+ field: string,
26
+ value: Field
27
+ ): this {
28
+ if (typeof value === 'object' && 'value' in value) {
29
+ const fieldDeclaration: FieldDeclaration = generateFieldDeclaration(value)
30
+
31
+ this[field] = fieldDeclaration.value
32
+
33
+ this.originalValues[field] = fieldDeclaration.value
34
+
35
+ this.validation.messages.push(field, fieldDeclaration.validation.messages)
36
+
37
+ this.validation.rules.push(field, fieldDeclaration.validation.rules)
38
+ } else {
39
+ this[field] = value
40
+
41
+ this.originalValues[field] = value
42
+ }
43
+
44
+ return this
45
+ }
46
+
47
+ public addFields(
48
+ fields: Fields | RawFields
49
+ ): this {
50
+ Object.keys(fields).forEach((field: string) => {
51
+ this.addField(field, fields[field])
52
+ })
53
+
54
+ return this
55
+ }
56
+
57
+ public get errors() {
58
+ return this.validation.errors
59
+ }
60
+
61
+ public fill(
62
+ data: { [key: string]: any },
63
+ updateOriginalValues: boolean = false
64
+ ): this {
65
+ Object.keys(data).forEach((field: string) => {
66
+ let value = data[field]
67
+
68
+ if (updateOriginalValues) {
69
+ this.originalValues[field] = value
70
+ }
71
+
72
+ this[field] = value
73
+ })
74
+
75
+ return this
76
+ }
77
+
78
+ public get messages() {
79
+ return this.validation.messages
80
+ }
81
+
82
+ public removeField(
83
+ field: string
84
+ ): this {
85
+ delete this[field]
86
+
87
+ delete this.originalValues[field]
88
+
89
+ this.validation.messages.unset(field)
90
+
91
+ this.validation.rules.unset(field)
92
+
93
+ return this
94
+ }
95
+
96
+ public removeFields(
97
+ fields: string[]
98
+ ): this {
99
+ fields.forEach((field: string) => {
100
+ this.removeField(field)
101
+ })
102
+
103
+ return this
104
+ }
105
+
106
+ public reset(): this {
107
+ this.validation.errors.clear()
108
+
109
+ Object.keys(this.originalValues).forEach((field: string) => {
110
+ this[field] = this.originalValues[field]
111
+ })
112
+
113
+ return this
114
+ }
115
+
116
+ public get rules() {
117
+ return this.validation.rules
118
+ }
119
+
120
+ public setAwaiting(
121
+ awaiting: boolean = true
122
+ ): this {
123
+ this.awaiting = awaiting
124
+
125
+ return this
126
+ }
127
+
128
+ public validate(
129
+ field: string | null = null
130
+ ): Promise<this | void> {
131
+ return field ? this.validateField(field) : this.validateForm()
132
+ }
133
+
134
+ public validateField(
135
+ field: string
136
+ ): Promise<void> {
137
+ this.validation.errors.unset(field)
138
+
139
+ const rules = this.validation.rules.get(field)
140
+
141
+ if (rules && rules.length > 0) {
142
+ const validations = rules.map((
143
+ rule: string
144
+ ) => {
145
+ const ruleParts = rule.split(':')
146
+
147
+ const ruleName: string = ruleParts[0]
148
+ const ruleAttributes: string[] = ruleParts.length === 2 ? ruleParts[1].split(',') : []
149
+
150
+ if (ruleName in Rule) {
151
+ return Rule[ruleName](this[field], ruleAttributes)
152
+ .catch((error?: string) => {
153
+ const errorMessage: ErrorMessage | null = this.validation.messages.get(field)
154
+
155
+ if (errorMessage && ruleName in errorMessage) {
156
+ this.validation.errors.push(field, errorMessage[ruleName])
157
+ }
158
+
159
+ return Promise.reject(error)
160
+ })
161
+ }
162
+
163
+ return Promise.reject(new Error(`There is no validation rule called "${rule}"`))
164
+ }
165
+ )
166
+
167
+ return Promise.all(validations).then(() => {
168
+ })
169
+ }
170
+
171
+ return Promise.resolve()
172
+ }
173
+
174
+ public validateForm(): Promise<this> {
175
+ const validations = Object.keys(this.originalValues).map(
176
+ (field: string) => this.validateField(field)
177
+ )
178
+
179
+ return Promise.all(validations)
180
+ .then(() => Promise.resolve(this))
181
+ .catch(() => Promise.reject(this))
182
+ }
183
+
184
+ public values(): Values {
185
+ const values: Values = {}
186
+
187
+ Object.keys(this.originalValues).forEach((field: string): void => {
188
+ values[field] = this[field]
189
+ })
190
+
191
+ return values
192
+ }
193
+
194
+ public valuesAsFormData() {
195
+ return objectToFormData(this.values())
196
+ }
197
+ }
@@ -0,0 +1,5 @@
1
+ import { Collection } from '../utils/collections'
2
+ import {ErrorMessage} from '../types/messages.ts'
3
+
4
+ export class Messages extends Collection<ErrorMessage> {
5
+ }
@@ -0,0 +1,4 @@
1
+ import { Collection } from '../utils/collections'
2
+
3
+ export class Rules extends Collection<string[]> {
4
+ }
@@ -0,0 +1,11 @@
1
+ import {Messages} from './Messages.ts'
2
+ import {Rules} from './Rules.ts'
3
+ import {Errors} from './Errors.ts'
4
+
5
+ export class Validation {
6
+ public errors: Errors = new Errors()
7
+
8
+ public messages: Messages = new Messages()
9
+
10
+ public rules: Rules = new Rules()
11
+ }
package/src/main.ts ADDED
@@ -0,0 +1,9 @@
1
+ import {Form} from './core/Form.ts'
2
+
3
+ const createForm = (data: any) => new Form(data)
4
+
5
+ export {
6
+ createForm
7
+ }
8
+
9
+ export default Form
@@ -0,0 +1,3 @@
1
+ export interface Items<T> {
2
+ [key: string]: T
3
+ }
@@ -0,0 +1,19 @@
1
+ import { Validation } from './validations'
2
+
3
+ export interface Field {
4
+ validation?: object,
5
+ value: any
6
+ }
7
+
8
+ export interface FieldDeclaration {
9
+ validation: Validation,
10
+ value: any
11
+ }
12
+
13
+ export interface Fields {
14
+ [key: string]: Field
15
+ }
16
+
17
+ export interface RawFields {
18
+ [key: string]: any
19
+ }
@@ -0,0 +1,3 @@
1
+ export type ErrorMessage = {
2
+ [key: string]: string
3
+ }
@@ -0,0 +1,3 @@
1
+ export interface Rules {
2
+ [key: string]: (value: any, attributes: string[]) => Promise<any>
3
+ }
@@ -0,0 +1,4 @@
1
+ export interface Validation {
2
+ rules: string[],
3
+ messages: object
4
+ }
@@ -0,0 +1,3 @@
1
+ export interface Values {
2
+ [key: string]: any
3
+ }
@@ -0,0 +1,78 @@
1
+ import {Items} from '../types/collections'
2
+
3
+ export class Collection<T> {
4
+ public collection: Items<T> = {}
5
+
6
+ public all(): Items<T> {
7
+ return this.collection
8
+ }
9
+
10
+ public first(
11
+ key: string
12
+ ): T | null {
13
+ const data = this.get(key)
14
+
15
+ if (data) {
16
+ return Array.isArray(data) ? data[0] : data
17
+ }
18
+
19
+ return null
20
+ }
21
+
22
+ public any(): boolean {
23
+ return Object.keys(this.collection).length > 0
24
+ }
25
+
26
+ public fill(
27
+ items: Items<T>
28
+ ): this {
29
+ this.collection = items
30
+
31
+ return this
32
+ }
33
+
34
+ public push(
35
+ key: string,
36
+ data: any
37
+ ): this {
38
+ this.collection = {
39
+ ...this.collection,
40
+ [key]: data
41
+ }
42
+
43
+ return this
44
+ }
45
+
46
+ public has(
47
+ key: string
48
+ ): boolean {
49
+ return this.collection.hasOwnProperty(key)
50
+ }
51
+
52
+ public get(
53
+ item: string,
54
+ defaultValue: T | null = null
55
+ ): T | null {
56
+ if (!this.has(item)) {
57
+ return defaultValue
58
+ }
59
+
60
+ return this.collection[item]
61
+ }
62
+
63
+ public unset(
64
+ key: string
65
+ ): this {
66
+ if (this.has(key)) {
67
+ delete this.collection[key]
68
+ }
69
+
70
+ return this
71
+ }
72
+
73
+ public clear(): this {
74
+ this.collection = {}
75
+
76
+ return this
77
+ }
78
+ }
@@ -0,0 +1,23 @@
1
+ import {FieldDeclaration} from '../types/fields'
2
+
3
+ const generateValidationRules = (
4
+ rules: string | string[]
5
+ ): string[] => {
6
+ return typeof rules === 'string' ?
7
+ rules.split('|') :
8
+ rules
9
+ }
10
+
11
+ export const generateFieldDeclaration = (
12
+ value: any
13
+ ): FieldDeclaration => {
14
+ return {
15
+ validation: {
16
+ rules: value.validation?.rules ?
17
+ generateValidationRules(value.validation.rules) :
18
+ [],
19
+ messages: value.validation?.messages || {}
20
+ },
21
+ value: value.value || null
22
+ }
23
+ }
@@ -0,0 +1,31 @@
1
+ export const isObject = (value: any): boolean => {
2
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
3
+ }
4
+
5
+ export const objectToFormData = (
6
+ values: Record<string, any>,
7
+ context?: FormData,
8
+ namespace: string | null = null
9
+ ): FormData => {
10
+ const formData: FormData = context || new FormData()
11
+
12
+ Object.keys(values).forEach((key: string): void => {
13
+ const value = values[key]
14
+
15
+ key = namespace ? `${namespace}[${key}]` : key
16
+
17
+ if ([undefined, null].indexOf(value) > -1) {
18
+ return
19
+ }
20
+
21
+ if ((isObject(value) && !(value instanceof File)) || Array.isArray(value)) {
22
+ objectToFormData(value, formData, key)
23
+
24
+ return
25
+ }
26
+
27
+ formData.append(key, value)
28
+ })
29
+
30
+ return formData
31
+ }
@@ -0,0 +1,17 @@
1
+ import {Rules} from '../types/rules.ts'
2
+
3
+ export const required = (
4
+ value: string
5
+ ): Promise<any> => new Promise((resolve, reject) => {
6
+ if (value === undefined || value === null) {
7
+ reject()
8
+ }
9
+
10
+ let str = String(value).replace(/\s/g, "");
11
+
12
+ str.length > 0 ? resolve(value) : reject()
13
+ })
14
+
15
+ export const Rule: Rules = {
16
+ required
17
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "isolatedModules": true,
13
+ "moduleDetection": "force",
14
+ "noEmit": true,
15
+
16
+ /* Linting */
17
+ "strict": true,
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true,
20
+ "noFallthroughCasesInSwitch": true
21
+ },
22
+ "include": ["src"]
23
+ }
package/vite.config.js ADDED
@@ -0,0 +1,17 @@
1
+ import {resolve} from 'path'
2
+ import {defineConfig} from 'vite'
3
+
4
+ export default defineConfig({
5
+ build: {
6
+ lib: {
7
+ entry: resolve(__dirname, 'src/main.ts'),
8
+ name: 'FormWrapper',
9
+ fileName: (format) => `form-wrapper.${format}.js`
10
+ },
11
+ rollupOptions: {
12
+ output: {
13
+ exports: 'named'
14
+ },
15
+ },
16
+ },
17
+ })