@serhiibudianskyi/wui 0.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/LICENSE +21 -0
- package/README.md +2 -0
- package/dist/components/Field.d.ts +9 -0
- package/dist/components/Field.js +167 -0
- package/dist/components/FileField.d.ts +12 -0
- package/dist/components/FileField.js +338 -0
- package/dist/components/Form.d.ts +8 -0
- package/dist/components/Form.js +100 -0
- package/dist/src/components/Field.d.ts +9 -0
- package/dist/src/components/Field.js +156 -0
- package/dist/src/components/FileField.d.ts +12 -0
- package/dist/src/components/FileField.js +285 -0
- package/dist/src/components/Form.d.ts +8 -0
- package/dist/src/components/Form.js +70 -0
- package/dist/src/types/Field.d.ts +82 -0
- package/dist/src/types/Field.js +498 -0
- package/dist/src/types/Form.d.ts +29 -0
- package/dist/src/types/Form.js +102 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/Field.d.ts +82 -0
- package/dist/types/Field.js +442 -0
- package/dist/types/Form.d.ts +29 -0
- package/dist/types/Form.js +107 -0
- package/package.json +57 -0
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
// Field class representing a form field
|
|
3
|
+
export class Field {
|
|
4
|
+
// Constructor to initialize the field with its configuration and schema
|
|
5
|
+
constructor(config, schema) {
|
|
6
|
+
// Configuration for the field
|
|
7
|
+
this._config = {};
|
|
8
|
+
// Zod schema for the field
|
|
9
|
+
this.schema = z.any();
|
|
10
|
+
this._config = config;
|
|
11
|
+
this.schema = schema;
|
|
12
|
+
}
|
|
13
|
+
get type() {
|
|
14
|
+
return this._config.type;
|
|
15
|
+
}
|
|
16
|
+
get name() {
|
|
17
|
+
return this._config.name;
|
|
18
|
+
}
|
|
19
|
+
get label() {
|
|
20
|
+
return this._config.label;
|
|
21
|
+
}
|
|
22
|
+
get placeholder() {
|
|
23
|
+
return this._config.placeholder || '';
|
|
24
|
+
}
|
|
25
|
+
get isReadOnly() {
|
|
26
|
+
return this._config.isReadOnly || false;
|
|
27
|
+
}
|
|
28
|
+
get isDisabled() {
|
|
29
|
+
return this._config.isDisabled || false;
|
|
30
|
+
}
|
|
31
|
+
get isRequired() {
|
|
32
|
+
return this._config.isRequired || false;
|
|
33
|
+
}
|
|
34
|
+
get min() {
|
|
35
|
+
return this._config.min;
|
|
36
|
+
}
|
|
37
|
+
get max() {
|
|
38
|
+
return this._config.max;
|
|
39
|
+
}
|
|
40
|
+
get revalidates() {
|
|
41
|
+
return this._config.revalidates || [];
|
|
42
|
+
}
|
|
43
|
+
get step() {
|
|
44
|
+
return this._config.step;
|
|
45
|
+
}
|
|
46
|
+
get options() {
|
|
47
|
+
return this._config.options || [];
|
|
48
|
+
}
|
|
49
|
+
get isMultiple() {
|
|
50
|
+
return this._config.isMultiple || false;
|
|
51
|
+
}
|
|
52
|
+
get maxSize() {
|
|
53
|
+
return this._config.maxSize;
|
|
54
|
+
}
|
|
55
|
+
get accept() {
|
|
56
|
+
return this._config.accept;
|
|
57
|
+
}
|
|
58
|
+
get maxFiles() {
|
|
59
|
+
return this.isMultiple ? this._config.maxFiles || +Infinity : 1;
|
|
60
|
+
}
|
|
61
|
+
get loadOptions() {
|
|
62
|
+
return this._config.loadOptions;
|
|
63
|
+
}
|
|
64
|
+
get className() {
|
|
65
|
+
return this._config.className || '';
|
|
66
|
+
}
|
|
67
|
+
get attrs() {
|
|
68
|
+
return this._config.attrs || {};
|
|
69
|
+
}
|
|
70
|
+
// Normalize the input value based on the field's configuration
|
|
71
|
+
getNormalizedValue(value) {
|
|
72
|
+
return this._config.normalize ? this._config.normalize(value) : value;
|
|
73
|
+
}
|
|
74
|
+
// Run custom validation logic for the field
|
|
75
|
+
runValidation(values, ctx) {
|
|
76
|
+
if (this._config.validate) {
|
|
77
|
+
this._config.validate(values, ctx);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Get default value for the field based on its type
|
|
81
|
+
getDefaultValue() {
|
|
82
|
+
switch (this._config.type) {
|
|
83
|
+
case 'file':
|
|
84
|
+
return (this._config.isMultiple ? [] : null);
|
|
85
|
+
case 'number':
|
|
86
|
+
return (this._config.min ?? 0);
|
|
87
|
+
case 'checkbox':
|
|
88
|
+
return false;
|
|
89
|
+
case 'date':
|
|
90
|
+
case 'datetime-local':
|
|
91
|
+
return new Date();
|
|
92
|
+
case 'time':
|
|
93
|
+
return '00:00';
|
|
94
|
+
case 'select':
|
|
95
|
+
case 'async-select':
|
|
96
|
+
case 'creatable-select':
|
|
97
|
+
case 'async-creatable-select':
|
|
98
|
+
return (this._config.isMultiple ? [] : null);
|
|
99
|
+
case 'textarea':
|
|
100
|
+
case 'text':
|
|
101
|
+
case 'email':
|
|
102
|
+
case 'password':
|
|
103
|
+
case 'radio':
|
|
104
|
+
default:
|
|
105
|
+
return '';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export class FieldFactory {
|
|
110
|
+
// Create a Zod schema for string fields
|
|
111
|
+
static createStringSchema(config) {
|
|
112
|
+
let schema = z.string();
|
|
113
|
+
// Apply required validation if specified
|
|
114
|
+
if (config.isRequired) {
|
|
115
|
+
schema = schema.nonempty({ message: `${config.label} is required` });
|
|
116
|
+
}
|
|
117
|
+
return schema;
|
|
118
|
+
}
|
|
119
|
+
// Create a Zod schema for email fields
|
|
120
|
+
static createEmailSchema(config) {
|
|
121
|
+
let schema = this.createStringSchema(config).email('Invalid email address');
|
|
122
|
+
return schema;
|
|
123
|
+
}
|
|
124
|
+
// Create a text field with the given configuration
|
|
125
|
+
static text(name, label, config = {}) {
|
|
126
|
+
const fieldConfig = {
|
|
127
|
+
type: 'text',
|
|
128
|
+
name,
|
|
129
|
+
label,
|
|
130
|
+
...config
|
|
131
|
+
};
|
|
132
|
+
return new Field(fieldConfig, this.createStringSchema(fieldConfig));
|
|
133
|
+
}
|
|
134
|
+
// Create an email field with the given configuration
|
|
135
|
+
static email(config) {
|
|
136
|
+
const fieldConfig = {
|
|
137
|
+
type: 'email',
|
|
138
|
+
name: config.name || 'email',
|
|
139
|
+
label: config.label || 'Email',
|
|
140
|
+
...config
|
|
141
|
+
};
|
|
142
|
+
return new Field(fieldConfig, this.createEmailSchema(fieldConfig));
|
|
143
|
+
}
|
|
144
|
+
// Create a password field with the given configuration
|
|
145
|
+
static password(config) {
|
|
146
|
+
const fieldConfig = {
|
|
147
|
+
type: 'password',
|
|
148
|
+
name: config.name || 'password',
|
|
149
|
+
label: config.label || 'Password',
|
|
150
|
+
...config
|
|
151
|
+
};
|
|
152
|
+
return new Field(fieldConfig, this.createStringSchema(fieldConfig));
|
|
153
|
+
}
|
|
154
|
+
// Create a textarea field with the given configuration
|
|
155
|
+
static textarea(name, label, config = {}) {
|
|
156
|
+
const fieldConfig = {
|
|
157
|
+
type: 'textarea',
|
|
158
|
+
name,
|
|
159
|
+
label,
|
|
160
|
+
...config
|
|
161
|
+
};
|
|
162
|
+
return new Field(fieldConfig, this.createStringSchema(fieldConfig));
|
|
163
|
+
}
|
|
164
|
+
// Create a Zod schema for number fields
|
|
165
|
+
static createNumberSchema(config) {
|
|
166
|
+
// Preprocess to handle empty strings and convert to number
|
|
167
|
+
let schema = z.preprocess((val) => {
|
|
168
|
+
if (typeof val === 'string' && val.trim() === '' || isNaN(Number(val))) {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
return Number(val);
|
|
172
|
+
}, z.number().optional() // It should be optional initially!
|
|
173
|
+
);
|
|
174
|
+
// Apply required and range validations if specified
|
|
175
|
+
if (config.isRequired) {
|
|
176
|
+
schema = schema.refine((val) => val !== undefined, {
|
|
177
|
+
message: `${config.label} is required`
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
// Minmax value validation
|
|
181
|
+
schema = schema
|
|
182
|
+
.refine((val) => {
|
|
183
|
+
if (val === undefined)
|
|
184
|
+
return true;
|
|
185
|
+
if (typeof config.min === 'number') {
|
|
186
|
+
return val >= config.min;
|
|
187
|
+
}
|
|
188
|
+
return true;
|
|
189
|
+
}, {
|
|
190
|
+
message: `${config.label} must be at least ${config.min}`
|
|
191
|
+
})
|
|
192
|
+
.refine((val) => {
|
|
193
|
+
if (val === undefined)
|
|
194
|
+
return true;
|
|
195
|
+
if (typeof config.max === 'number') {
|
|
196
|
+
return val <= config.max;
|
|
197
|
+
}
|
|
198
|
+
return true;
|
|
199
|
+
}, {
|
|
200
|
+
message: `${config.label} must be at most ${config.max}`
|
|
201
|
+
});
|
|
202
|
+
return schema;
|
|
203
|
+
}
|
|
204
|
+
// Create a number field with the given configuration
|
|
205
|
+
static number(name, label, config = {}) {
|
|
206
|
+
const fieldConfig = {
|
|
207
|
+
type: 'number',
|
|
208
|
+
name,
|
|
209
|
+
label,
|
|
210
|
+
...config
|
|
211
|
+
};
|
|
212
|
+
return new Field(fieldConfig, this.createNumberSchema(fieldConfig));
|
|
213
|
+
}
|
|
214
|
+
// Create a Zod schema for checkbox fields
|
|
215
|
+
static createCheckboxSchema(config) {
|
|
216
|
+
let schema = z.boolean();
|
|
217
|
+
// Apply required validation if specified
|
|
218
|
+
if (config.isRequired) {
|
|
219
|
+
schema = schema.refine((val) => val === true, {
|
|
220
|
+
message: `${config.label} must be checked`
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
return schema;
|
|
224
|
+
}
|
|
225
|
+
// Create a checkbox field with the given configuration
|
|
226
|
+
static checkbox(name, label, config = {}) {
|
|
227
|
+
const fieldConfig = {
|
|
228
|
+
type: 'checkbox',
|
|
229
|
+
name,
|
|
230
|
+
label,
|
|
231
|
+
...config
|
|
232
|
+
};
|
|
233
|
+
return new Field(fieldConfig, this.createCheckboxSchema(fieldConfig));
|
|
234
|
+
}
|
|
235
|
+
// Create a Zod schema for date fields
|
|
236
|
+
static createDateSchema(config) {
|
|
237
|
+
let schema = z.preprocess((val) => {
|
|
238
|
+
if (typeof val === 'string' || val instanceof Date) {
|
|
239
|
+
const date = new Date(val);
|
|
240
|
+
return isNaN(date.getTime()) ? undefined : date;
|
|
241
|
+
}
|
|
242
|
+
return undefined;
|
|
243
|
+
}, z.date().optional());
|
|
244
|
+
// Apply required validation if specified
|
|
245
|
+
if (config.isRequired) {
|
|
246
|
+
schema = schema.refine((val) => val !== undefined, {
|
|
247
|
+
message: `${config.label} is required`
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
// Minmax date validation
|
|
251
|
+
schema = schema
|
|
252
|
+
.refine((val) => {
|
|
253
|
+
if (val === undefined)
|
|
254
|
+
return true;
|
|
255
|
+
if (typeof config.min === 'string') {
|
|
256
|
+
return val >= new Date(config.min);
|
|
257
|
+
}
|
|
258
|
+
return true;
|
|
259
|
+
}, {
|
|
260
|
+
message: `${config.label} must be on or after ${config.min ? new Date(config.min).toLocaleDateString() : ''}`
|
|
261
|
+
})
|
|
262
|
+
.refine((val) => {
|
|
263
|
+
if (val === undefined)
|
|
264
|
+
return true;
|
|
265
|
+
if (typeof config.max === 'string') {
|
|
266
|
+
return val <= new Date(config.max);
|
|
267
|
+
}
|
|
268
|
+
return true;
|
|
269
|
+
}, {
|
|
270
|
+
message: `${config.label} must be on or before ${config.max ? new Date(config.max).toLocaleDateString() : ''}`
|
|
271
|
+
});
|
|
272
|
+
return schema;
|
|
273
|
+
}
|
|
274
|
+
// Create a date field with the given configuration
|
|
275
|
+
static date(name, label, config = {}) {
|
|
276
|
+
const fieldConfig = {
|
|
277
|
+
type: 'date',
|
|
278
|
+
name,
|
|
279
|
+
label,
|
|
280
|
+
...config
|
|
281
|
+
};
|
|
282
|
+
return new Field(fieldConfig, this.createDateSchema(fieldConfig));
|
|
283
|
+
}
|
|
284
|
+
// Create a Zod schema for datetime-local fields
|
|
285
|
+
static createDateTimeLocalSchema(config) {
|
|
286
|
+
let schema = z.preprocess((val) => {
|
|
287
|
+
if (typeof val === 'string' || val instanceof Date) {
|
|
288
|
+
const date = new Date(val);
|
|
289
|
+
return isNaN(date.getTime()) ? undefined : date;
|
|
290
|
+
}
|
|
291
|
+
return undefined;
|
|
292
|
+
}, z.date().optional());
|
|
293
|
+
// Apply required validation if specified
|
|
294
|
+
if (config.isRequired) {
|
|
295
|
+
schema = schema.refine((val) => val !== undefined, {
|
|
296
|
+
message: `${config.label} is required`
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
// Minmax datetime validation
|
|
300
|
+
schema = schema
|
|
301
|
+
.refine((val) => {
|
|
302
|
+
if (val === undefined)
|
|
303
|
+
return true;
|
|
304
|
+
if (typeof config.min === 'string') {
|
|
305
|
+
return val >= new Date(config.min);
|
|
306
|
+
}
|
|
307
|
+
return true;
|
|
308
|
+
}, {
|
|
309
|
+
message: `${config.label} must be on or after ${config.min ? new Date(config.min).toLocaleString() : ''}`
|
|
310
|
+
})
|
|
311
|
+
.refine((val) => {
|
|
312
|
+
if (val === undefined)
|
|
313
|
+
return true;
|
|
314
|
+
if (typeof config.max === 'string') {
|
|
315
|
+
return val <= new Date(config.max);
|
|
316
|
+
}
|
|
317
|
+
return true;
|
|
318
|
+
}, {
|
|
319
|
+
message: `${config.label} must be on or before ${config.max ? new Date(config.max).toLocaleString() : ''}`
|
|
320
|
+
});
|
|
321
|
+
return schema;
|
|
322
|
+
}
|
|
323
|
+
// Create a datetime-local field with the given configuration
|
|
324
|
+
static datetimeLocal(name, label, config = {}) {
|
|
325
|
+
const fieldConfig = {
|
|
326
|
+
type: 'datetime-local',
|
|
327
|
+
name,
|
|
328
|
+
label,
|
|
329
|
+
...config
|
|
330
|
+
};
|
|
331
|
+
return new Field(fieldConfig, this.createDateTimeLocalSchema(fieldConfig));
|
|
332
|
+
}
|
|
333
|
+
// Create a Zod schema for time fields
|
|
334
|
+
static createTimeSchema(config) {
|
|
335
|
+
let schema = z.string().optional();
|
|
336
|
+
// Apply required validation if specified
|
|
337
|
+
if (config.isRequired) {
|
|
338
|
+
schema = schema.refine((val) => val !== undefined && val !== '', {
|
|
339
|
+
message: `${config.label} is required`
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
// Validate time format (HH:MM)
|
|
343
|
+
schema = schema.refine((val) => !val || /^([01]\d|2[0-3]):([0-5]\d)$/.test(val), {
|
|
344
|
+
message: `${config.label} must be a valid time in HH:MM format`
|
|
345
|
+
});
|
|
346
|
+
// Minmax time validation
|
|
347
|
+
schema = schema
|
|
348
|
+
.refine((val) => {
|
|
349
|
+
if (!val)
|
|
350
|
+
return true;
|
|
351
|
+
if (typeof config.min === 'string') {
|
|
352
|
+
return val >= config.min;
|
|
353
|
+
}
|
|
354
|
+
return true;
|
|
355
|
+
}, {
|
|
356
|
+
message: `${config.label} must be at or after ${config.min}`
|
|
357
|
+
})
|
|
358
|
+
.refine((val) => {
|
|
359
|
+
if (!val)
|
|
360
|
+
return true;
|
|
361
|
+
if (typeof config.max === 'string') {
|
|
362
|
+
return val <= config.max;
|
|
363
|
+
}
|
|
364
|
+
return true;
|
|
365
|
+
}, {
|
|
366
|
+
message: `${config.label} must be at or before ${config.max}`
|
|
367
|
+
});
|
|
368
|
+
return schema;
|
|
369
|
+
}
|
|
370
|
+
// Create a time field with the given configuration
|
|
371
|
+
static time(name, label, config = {}) {
|
|
372
|
+
const fieldConfig = {
|
|
373
|
+
type: 'time',
|
|
374
|
+
name,
|
|
375
|
+
label,
|
|
376
|
+
...config
|
|
377
|
+
};
|
|
378
|
+
return new Field(fieldConfig, this.createTimeSchema(fieldConfig));
|
|
379
|
+
}
|
|
380
|
+
// Create a radio field with the given configuration
|
|
381
|
+
static radio(name, label, options, config = {}) {
|
|
382
|
+
const fieldConfig = {
|
|
383
|
+
type: 'radio',
|
|
384
|
+
name,
|
|
385
|
+
label,
|
|
386
|
+
options,
|
|
387
|
+
...config
|
|
388
|
+
};
|
|
389
|
+
return new Field(fieldConfig, this.createStringSchema(fieldConfig));
|
|
390
|
+
}
|
|
391
|
+
// Create a Zod schema for select fields
|
|
392
|
+
static createSelectSchema(config) {
|
|
393
|
+
if (config.isMultiple) {
|
|
394
|
+
// Array schema for multi-select
|
|
395
|
+
let schema = z.array(z.object({
|
|
396
|
+
label: z.string(),
|
|
397
|
+
value: z.string()
|
|
398
|
+
}));
|
|
399
|
+
// Apply required validation
|
|
400
|
+
if (config.isRequired) {
|
|
401
|
+
schema = schema.nonempty({ message: `${config.label} is required` });
|
|
402
|
+
}
|
|
403
|
+
return schema.default([]);
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
// For single-select, return an Option object or null
|
|
407
|
+
let schema = z.object({
|
|
408
|
+
label: z.string(),
|
|
409
|
+
value: z.string()
|
|
410
|
+
}).nullable().default(null);
|
|
411
|
+
// Apply required validation
|
|
412
|
+
if (config.isRequired) {
|
|
413
|
+
schema = schema.refine((val) => val !== null, { message: `${config.label} is required` });
|
|
414
|
+
}
|
|
415
|
+
return schema;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// Create a select field with the given configuration
|
|
419
|
+
static select(name, label, options, config = {}) {
|
|
420
|
+
const fieldConfig = {
|
|
421
|
+
type: 'select',
|
|
422
|
+
name,
|
|
423
|
+
label,
|
|
424
|
+
options,
|
|
425
|
+
...config
|
|
426
|
+
};
|
|
427
|
+
return new Field(fieldConfig, this.createSelectSchema(fieldConfig));
|
|
428
|
+
}
|
|
429
|
+
// Create an async-select field with the given configuration
|
|
430
|
+
static asyncSelect(name, label, loadOptions, config = {}) {
|
|
431
|
+
const fieldConfig = {
|
|
432
|
+
type: 'async-select',
|
|
433
|
+
name,
|
|
434
|
+
label,
|
|
435
|
+
loadOptions,
|
|
436
|
+
...config
|
|
437
|
+
};
|
|
438
|
+
return new Field(fieldConfig, this.createSelectSchema(fieldConfig));
|
|
439
|
+
}
|
|
440
|
+
// Create a creatable-select field with the given configuration
|
|
441
|
+
static creatableSelect(name, label, options, config = {}) {
|
|
442
|
+
const fieldConfig = {
|
|
443
|
+
type: 'creatable-select',
|
|
444
|
+
name,
|
|
445
|
+
label,
|
|
446
|
+
options,
|
|
447
|
+
...config
|
|
448
|
+
};
|
|
449
|
+
return new Field(fieldConfig, this.createSelectSchema(fieldConfig));
|
|
450
|
+
}
|
|
451
|
+
// Create an async-creatable-select field with the given configuration
|
|
452
|
+
static asyncCreatableSelect(name, label, loadOptions, config = {}) {
|
|
453
|
+
const fieldConfig = {
|
|
454
|
+
type: 'async-creatable-select',
|
|
455
|
+
name,
|
|
456
|
+
label,
|
|
457
|
+
loadOptions,
|
|
458
|
+
...config
|
|
459
|
+
};
|
|
460
|
+
return new Field(fieldConfig, this.createSelectSchema(fieldConfig));
|
|
461
|
+
}
|
|
462
|
+
static createFileSchema(config) {
|
|
463
|
+
if (config.isMultiple) {
|
|
464
|
+
// Array schema for multiple files
|
|
465
|
+
let schema = z.array(z.string());
|
|
466
|
+
// Apply required validation
|
|
467
|
+
if (config.isRequired) {
|
|
468
|
+
schema = schema.nonempty({ message: `${config.label} is required` });
|
|
469
|
+
}
|
|
470
|
+
// Apply maxFiles validation
|
|
471
|
+
if (config.maxFiles) {
|
|
472
|
+
schema = schema.max(config.maxFiles, {
|
|
473
|
+
message: `${config.label} cannot have more than ${config.maxFiles} file${config.maxFiles > 1 ? 's' : ''}`
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
return schema.default([]);
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
// For single file, return a string (file ID) or null
|
|
480
|
+
let schema = z.string().nullable().default(null);
|
|
481
|
+
// Apply required validation
|
|
482
|
+
if (config.isRequired) {
|
|
483
|
+
schema = schema.refine((val) => val !== null, { message: `${config.label} is required` });
|
|
484
|
+
}
|
|
485
|
+
return schema;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
// Create a file field with the given configuration
|
|
489
|
+
static file(name, label, config = {}) {
|
|
490
|
+
const fieldConfig = {
|
|
491
|
+
type: 'file',
|
|
492
|
+
name,
|
|
493
|
+
label,
|
|
494
|
+
...config
|
|
495
|
+
};
|
|
496
|
+
return new Field(fieldConfig, this.createFileSchema(fieldConfig));
|
|
497
|
+
}
|
|
498
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Field } from './Field';
|
|
3
|
+
export interface Section {
|
|
4
|
+
title?: string;
|
|
5
|
+
fields: Record<string, Field<any>>;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class Form {
|
|
9
|
+
private _fields;
|
|
10
|
+
private _schema;
|
|
11
|
+
private _defaultValues;
|
|
12
|
+
private _sections;
|
|
13
|
+
constructor(sections: Section[]);
|
|
14
|
+
get sections(): Section[];
|
|
15
|
+
get schema(): z.ZodObject<any>;
|
|
16
|
+
get defaultValues(): Record<string, any>;
|
|
17
|
+
setDefaultValues(values: Record<string, any>): void;
|
|
18
|
+
private _buildSchema;
|
|
19
|
+
private _buildDefaultValues;
|
|
20
|
+
private _extractFieldsFromSections;
|
|
21
|
+
}
|
|
22
|
+
export declare class FormBuilder {
|
|
23
|
+
private _sections;
|
|
24
|
+
private _defaultValues;
|
|
25
|
+
section(section: Section): this;
|
|
26
|
+
sections(sections: Section[]): this;
|
|
27
|
+
defaultValues(values: Record<string, any>): this;
|
|
28
|
+
build(): Form;
|
|
29
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export class Form {
|
|
3
|
+
// Alternative constructor to initialize the form with sections
|
|
4
|
+
constructor(sections) {
|
|
5
|
+
// Form fields
|
|
6
|
+
this._fields = {};
|
|
7
|
+
// Zod schema for the form
|
|
8
|
+
this._schema = null;
|
|
9
|
+
// Default values for the form
|
|
10
|
+
this._defaultValues = {};
|
|
11
|
+
// Sections of the form
|
|
12
|
+
this._sections = [];
|
|
13
|
+
this._sections = sections;
|
|
14
|
+
this._fields = this._extractFieldsFromSections();
|
|
15
|
+
this._buildDefaultValues();
|
|
16
|
+
}
|
|
17
|
+
get sections() {
|
|
18
|
+
return this._sections;
|
|
19
|
+
}
|
|
20
|
+
get schema() {
|
|
21
|
+
// Build the schema if it hasn't been built yet
|
|
22
|
+
if (!this._schema) {
|
|
23
|
+
this._schema = this._buildSchema();
|
|
24
|
+
}
|
|
25
|
+
return this._schema;
|
|
26
|
+
}
|
|
27
|
+
// Get default values for the form
|
|
28
|
+
get defaultValues() {
|
|
29
|
+
return this._defaultValues;
|
|
30
|
+
}
|
|
31
|
+
// Set default values for the form
|
|
32
|
+
setDefaultValues(values) {
|
|
33
|
+
this._defaultValues = { ...this._defaultValues, ...values };
|
|
34
|
+
}
|
|
35
|
+
// Build the Zod schema for the form based on its fields
|
|
36
|
+
_buildSchema() {
|
|
37
|
+
const shape = {};
|
|
38
|
+
// Iterate over each field to build its schema
|
|
39
|
+
for (const [name, field] of Object.entries(this._fields)) {
|
|
40
|
+
let schema = field.schema;
|
|
41
|
+
// Wrap the schema with preprocessing to normalize the value
|
|
42
|
+
schema = z.preprocess(field.getNormalizedValue.bind(field), schema);
|
|
43
|
+
shape[name] = schema;
|
|
44
|
+
}
|
|
45
|
+
// Return the combined schema with custom refinements callback
|
|
46
|
+
return z.object(shape).superRefine((values, ctx) => {
|
|
47
|
+
for (const field of Object.values(this._fields)) {
|
|
48
|
+
field.runValidation(values, ctx);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
// Build default values for the form based on its fields
|
|
53
|
+
_buildDefaultValues() {
|
|
54
|
+
this._defaultValues = {};
|
|
55
|
+
for (const [key, field] of Object.entries(this._fields)) {
|
|
56
|
+
this._defaultValues[key] = field.getDefaultValue();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Extract fields from sections
|
|
60
|
+
_extractFieldsFromSections() {
|
|
61
|
+
const fields = {};
|
|
62
|
+
for (const section of this._sections) {
|
|
63
|
+
Object.assign(fields, section.fields);
|
|
64
|
+
}
|
|
65
|
+
return fields;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export class FormBuilder {
|
|
69
|
+
constructor() {
|
|
70
|
+
// Set of sections of fields for the form
|
|
71
|
+
this._sections = [];
|
|
72
|
+
// Default values for the form
|
|
73
|
+
this._defaultValues = {};
|
|
74
|
+
}
|
|
75
|
+
// Add a section to the form
|
|
76
|
+
section(section) {
|
|
77
|
+
this._sections.push(section);
|
|
78
|
+
// Extract default values from section fields
|
|
79
|
+
for (const [name, field] of Object.entries(section.fields)) {
|
|
80
|
+
this._defaultValues[name] = field.getDefaultValue();
|
|
81
|
+
}
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
// Add multiple sections to the form
|
|
85
|
+
sections(sections) {
|
|
86
|
+
for (const section of sections) {
|
|
87
|
+
this.section(section);
|
|
88
|
+
}
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
// Set default values for the form
|
|
92
|
+
defaultValues(values) {
|
|
93
|
+
this._defaultValues = { ...this._defaultValues, ...values };
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
// Build and return the form instance
|
|
97
|
+
build() {
|
|
98
|
+
const form = new Form(this._sections);
|
|
99
|
+
form.setDefaultValues(this._defaultValues);
|
|
100
|
+
return form;
|
|
101
|
+
}
|
|
102
|
+
}
|