@rachelallyson/hero-hook-form 1.2.0 → 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.
- package/CHANGELOG.md +80 -0
- package/dist/cypress/index.d.ts +141 -0
- package/dist/cypress/index.js +897 -0
- package/dist/index.d.ts +730 -5
- package/dist/index.js +1832 -220
- package/dist/react/index.d.ts +730 -5
- package/dist/react/index.js +1832 -220
- package/package.json +10 -3
- package/README.md +0 -412
package/dist/index.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
1
8
|
// src/components/Form.tsx
|
|
2
|
-
import
|
|
3
|
-
import { Button as
|
|
9
|
+
import React16 from "react";
|
|
10
|
+
import { Button as Button3 } from "@heroui/react";
|
|
4
11
|
|
|
5
12
|
// src/hooks/useFormHelper.ts
|
|
6
13
|
import { useState } from "react";
|
|
@@ -68,8 +75,8 @@ function useFormHelper({
|
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
// src/components/FormField.tsx
|
|
71
|
-
import
|
|
72
|
-
import { useWatch } from "react-hook-form";
|
|
78
|
+
import React15 from "react";
|
|
79
|
+
import { useWatch as useWatch3 } from "react-hook-form";
|
|
73
80
|
|
|
74
81
|
// src/fields/CheckboxField.tsx
|
|
75
82
|
import React2 from "react";
|
|
@@ -191,16 +198,16 @@ function CheckboxField(props) {
|
|
|
191
198
|
{
|
|
192
199
|
control,
|
|
193
200
|
name,
|
|
194
|
-
render: ({ field, fieldState }) => /* @__PURE__ */ React2.createElement("div", { className }, /* @__PURE__ */ React2.createElement(
|
|
201
|
+
render: ({ field: field2, fieldState }) => /* @__PURE__ */ React2.createElement("div", { className }, /* @__PURE__ */ React2.createElement(
|
|
195
202
|
Checkbox,
|
|
196
203
|
{
|
|
197
204
|
...defaults.checkbox,
|
|
198
205
|
...checkboxProps,
|
|
199
206
|
isDisabled,
|
|
200
207
|
isInvalid: Boolean(fieldState.error),
|
|
201
|
-
isSelected: Boolean(
|
|
202
|
-
onBlur:
|
|
203
|
-
onValueChange: (val) =>
|
|
208
|
+
isSelected: Boolean(field2.value),
|
|
209
|
+
onBlur: field2.onBlur,
|
|
210
|
+
onValueChange: (val) => field2.onChange(val)
|
|
204
211
|
},
|
|
205
212
|
label
|
|
206
213
|
), description ? /* @__PURE__ */ React2.createElement("p", { className: "text-small text-default-400" }, description) : null, fieldState.error?.message ? /* @__PURE__ */ React2.createElement("p", { className: "text-tiny text-danger mt-1" }, fieldState.error.message) : null),
|
|
@@ -209,13 +216,43 @@ function CheckboxField(props) {
|
|
|
209
216
|
);
|
|
210
217
|
}
|
|
211
218
|
|
|
212
|
-
// src/fields/
|
|
219
|
+
// src/fields/ConditionalField.tsx
|
|
213
220
|
import React3 from "react";
|
|
221
|
+
import { useWatch, useFormContext } from "react-hook-form";
|
|
222
|
+
function ConditionalField({
|
|
223
|
+
config,
|
|
224
|
+
control,
|
|
225
|
+
className
|
|
226
|
+
}) {
|
|
227
|
+
const { condition, field: field2, name } = config;
|
|
228
|
+
const form = useFormContext();
|
|
229
|
+
const formValues = useWatch({ control });
|
|
230
|
+
const shouldShow = condition(formValues);
|
|
231
|
+
if (!shouldShow) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
return /* @__PURE__ */ React3.createElement("div", { className }, /* @__PURE__ */ React3.createElement(
|
|
235
|
+
FormField,
|
|
236
|
+
{
|
|
237
|
+
config: field2,
|
|
238
|
+
form,
|
|
239
|
+
submissionState: {
|
|
240
|
+
isSubmitting: false,
|
|
241
|
+
isSubmitted: false,
|
|
242
|
+
isSuccess: false,
|
|
243
|
+
error: void 0
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/fields/DateField.tsx
|
|
250
|
+
import React4 from "react";
|
|
214
251
|
import { Controller as Controller2 } from "react-hook-form";
|
|
215
252
|
function CoercedDateInput(props) {
|
|
216
|
-
const { dateProps, description, disabled, errorMessage, field, label } = props;
|
|
253
|
+
const { dateProps, description, disabled, errorMessage, field: field2, label } = props;
|
|
217
254
|
const defaults = useHeroHookFormDefaults();
|
|
218
|
-
return /* @__PURE__ */
|
|
255
|
+
return /* @__PURE__ */ React4.createElement(
|
|
219
256
|
DateInput,
|
|
220
257
|
{
|
|
221
258
|
...defaults.dateInput,
|
|
@@ -225,9 +262,9 @@ function CoercedDateInput(props) {
|
|
|
225
262
|
isDisabled: disabled,
|
|
226
263
|
isInvalid: Boolean(errorMessage),
|
|
227
264
|
label,
|
|
228
|
-
value:
|
|
229
|
-
onBlur:
|
|
230
|
-
onChange:
|
|
265
|
+
value: field2.value ?? null,
|
|
266
|
+
onBlur: field2.onBlur,
|
|
267
|
+
onChange: field2.onChange
|
|
231
268
|
}
|
|
232
269
|
);
|
|
233
270
|
}
|
|
@@ -243,12 +280,12 @@ function DateField(props) {
|
|
|
243
280
|
rules,
|
|
244
281
|
transform
|
|
245
282
|
} = props;
|
|
246
|
-
return /* @__PURE__ */
|
|
283
|
+
return /* @__PURE__ */ React4.createElement(
|
|
247
284
|
Controller2,
|
|
248
285
|
{
|
|
249
286
|
control,
|
|
250
287
|
name,
|
|
251
|
-
render: ({ field, fieldState }) => /* @__PURE__ */
|
|
288
|
+
render: ({ field: field2, fieldState }) => /* @__PURE__ */ React4.createElement("div", { className }, /* @__PURE__ */ React4.createElement(
|
|
252
289
|
CoercedDateInput,
|
|
253
290
|
{
|
|
254
291
|
dateProps,
|
|
@@ -256,8 +293,8 @@ function DateField(props) {
|
|
|
256
293
|
disabled: isDisabled,
|
|
257
294
|
errorMessage: fieldState.error?.message,
|
|
258
295
|
field: {
|
|
259
|
-
...
|
|
260
|
-
onChange: (value) =>
|
|
296
|
+
...field2,
|
|
297
|
+
onChange: (value) => field2.onChange(transform ? transform(value) : value)
|
|
261
298
|
},
|
|
262
299
|
label
|
|
263
300
|
}
|
|
@@ -267,8 +304,136 @@ function DateField(props) {
|
|
|
267
304
|
);
|
|
268
305
|
}
|
|
269
306
|
|
|
307
|
+
// src/fields/DynamicSectionField.tsx
|
|
308
|
+
import React5 from "react";
|
|
309
|
+
import { useWatch as useWatch2, useFormContext as useFormContext2 } from "react-hook-form";
|
|
310
|
+
function DynamicSectionField({
|
|
311
|
+
config,
|
|
312
|
+
control,
|
|
313
|
+
className
|
|
314
|
+
}) {
|
|
315
|
+
const { condition, fields, title, description } = config;
|
|
316
|
+
const form = useFormContext2();
|
|
317
|
+
const formValues = useWatch2({ control });
|
|
318
|
+
const shouldShow = condition(formValues);
|
|
319
|
+
if (!shouldShow) {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
return /* @__PURE__ */ React5.createElement("div", { className }, (title || description) && /* @__PURE__ */ React5.createElement("div", { className: "mb-6" }, title && /* @__PURE__ */ React5.createElement("h3", { className: "text-lg font-semibold text-gray-900 mb-2" }, title), description && /* @__PURE__ */ React5.createElement("p", { className: "text-sm text-gray-600" }, description)), /* @__PURE__ */ React5.createElement("div", { className: "space-y-4" }, fields.map((fieldConfig, index) => /* @__PURE__ */ React5.createElement(
|
|
323
|
+
FormField,
|
|
324
|
+
{
|
|
325
|
+
key: `${fieldConfig.name}-${index}`,
|
|
326
|
+
config: fieldConfig,
|
|
327
|
+
form,
|
|
328
|
+
submissionState: {
|
|
329
|
+
isSubmitting: false,
|
|
330
|
+
isSubmitted: false,
|
|
331
|
+
isSuccess: false,
|
|
332
|
+
error: void 0
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
))));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/fields/FieldArrayField.tsx
|
|
339
|
+
import React6 from "react";
|
|
340
|
+
import { useFieldArray, useFormContext as useFormContext3 } from "react-hook-form";
|
|
341
|
+
import { Button as Button2 } from "@heroui/react";
|
|
342
|
+
function FieldArrayField({
|
|
343
|
+
config,
|
|
344
|
+
className
|
|
345
|
+
}) {
|
|
346
|
+
const {
|
|
347
|
+
name,
|
|
348
|
+
fields: fieldConfigs,
|
|
349
|
+
min = 0,
|
|
350
|
+
max = 10,
|
|
351
|
+
addButtonText = "Add Item",
|
|
352
|
+
removeButtonText = "Remove"
|
|
353
|
+
} = config;
|
|
354
|
+
const form = useFormContext3();
|
|
355
|
+
if (!form || !form.control) {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
const { control } = form;
|
|
359
|
+
const { fields, append, remove } = useFieldArray({
|
|
360
|
+
control,
|
|
361
|
+
name
|
|
362
|
+
// FieldArray name
|
|
363
|
+
});
|
|
364
|
+
const canAdd = fields.length < max;
|
|
365
|
+
const canRemove = fields.length > min;
|
|
366
|
+
const handleAdd = () => {
|
|
367
|
+
if (canAdd) {
|
|
368
|
+
const defaultValues = fieldConfigs.reduce((acc, fieldConfig) => {
|
|
369
|
+
const fieldName = fieldConfig.name;
|
|
370
|
+
if (fieldConfig.type === "checkbox" || fieldConfig.type === "switch") {
|
|
371
|
+
acc[fieldName] = false;
|
|
372
|
+
} else if (fieldConfig.type === "slider") {
|
|
373
|
+
acc[fieldName] = 0;
|
|
374
|
+
} else {
|
|
375
|
+
acc[fieldName] = "";
|
|
376
|
+
}
|
|
377
|
+
return acc;
|
|
378
|
+
}, {});
|
|
379
|
+
append(defaultValues);
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
const handleRemove = (index) => {
|
|
383
|
+
if (canRemove) {
|
|
384
|
+
remove(index);
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
return /* @__PURE__ */ React6.createElement("div", { className }, /* @__PURE__ */ React6.createElement("div", { className: "space-y-4" }, fields.map((field2, index) => /* @__PURE__ */ React6.createElement("div", { key: field2.id, className: "border border-gray-200 rounded-lg p-4 space-y-4" }, /* @__PURE__ */ React6.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React6.createElement("h4", { className: "text-sm font-medium text-gray-700" }, config.label, " #", index + 1), canRemove && /* @__PURE__ */ React6.createElement(
|
|
388
|
+
Button2,
|
|
389
|
+
{
|
|
390
|
+
size: "sm",
|
|
391
|
+
variant: "light",
|
|
392
|
+
color: "danger",
|
|
393
|
+
startContent: "\u{1F5D1}\uFE0F",
|
|
394
|
+
onPress: () => handleRemove(index),
|
|
395
|
+
"aria-label": `${removeButtonText} ${config.label} ${index + 1}`
|
|
396
|
+
},
|
|
397
|
+
removeButtonText
|
|
398
|
+
)), /* @__PURE__ */ React6.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, fieldConfigs.map((fieldConfig) => /* @__PURE__ */ React6.createElement(
|
|
399
|
+
FormField,
|
|
400
|
+
{
|
|
401
|
+
key: `${fieldConfig.name}-${index}`,
|
|
402
|
+
config: {
|
|
403
|
+
...fieldConfig,
|
|
404
|
+
name: `${name}.${index}.${fieldConfig.name}`
|
|
405
|
+
},
|
|
406
|
+
form,
|
|
407
|
+
submissionState: {
|
|
408
|
+
isSubmitting: false,
|
|
409
|
+
isSubmitted: false,
|
|
410
|
+
isSuccess: false,
|
|
411
|
+
error: void 0
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
))))), canAdd && /* @__PURE__ */ React6.createElement(
|
|
415
|
+
Button2,
|
|
416
|
+
{
|
|
417
|
+
variant: "bordered",
|
|
418
|
+
startContent: "\u2795",
|
|
419
|
+
onPress: handleAdd,
|
|
420
|
+
className: "w-full"
|
|
421
|
+
},
|
|
422
|
+
addButtonText
|
|
423
|
+
), fields.length === 0 && /* @__PURE__ */ React6.createElement("div", { className: "text-center py-8 text-gray-500" }, /* @__PURE__ */ React6.createElement("p", null, "No ", config.label?.toLowerCase(), " added yet."), /* @__PURE__ */ React6.createElement(
|
|
424
|
+
Button2,
|
|
425
|
+
{
|
|
426
|
+
variant: "bordered",
|
|
427
|
+
startContent: "\u2795",
|
|
428
|
+
onPress: handleAdd,
|
|
429
|
+
className: "mt-2"
|
|
430
|
+
},
|
|
431
|
+
addButtonText
|
|
432
|
+
))));
|
|
433
|
+
}
|
|
434
|
+
|
|
270
435
|
// src/fields/FileField.tsx
|
|
271
|
-
import
|
|
436
|
+
import React7 from "react";
|
|
272
437
|
import { Controller as Controller3 } from "react-hook-form";
|
|
273
438
|
function CoercedFileInput(props) {
|
|
274
439
|
const {
|
|
@@ -276,13 +441,13 @@ function CoercedFileInput(props) {
|
|
|
276
441
|
description,
|
|
277
442
|
disabled,
|
|
278
443
|
errorMessage,
|
|
279
|
-
field,
|
|
444
|
+
field: field2,
|
|
280
445
|
fileProps,
|
|
281
446
|
label,
|
|
282
447
|
multiple
|
|
283
448
|
} = props;
|
|
284
449
|
const defaults = useHeroHookFormDefaults();
|
|
285
|
-
return /* @__PURE__ */
|
|
450
|
+
return /* @__PURE__ */ React7.createElement(
|
|
286
451
|
Input,
|
|
287
452
|
{
|
|
288
453
|
...defaults.input,
|
|
@@ -295,11 +460,11 @@ function CoercedFileInput(props) {
|
|
|
295
460
|
label,
|
|
296
461
|
multiple,
|
|
297
462
|
type: "file",
|
|
298
|
-
value:
|
|
299
|
-
onBlur:
|
|
463
|
+
value: field2.value ? "" : "",
|
|
464
|
+
onBlur: field2.onBlur,
|
|
300
465
|
onChange: (e) => {
|
|
301
466
|
const target = e.target;
|
|
302
|
-
|
|
467
|
+
field2.onChange(target.files);
|
|
303
468
|
}
|
|
304
469
|
}
|
|
305
470
|
);
|
|
@@ -318,12 +483,12 @@ function FileField(props) {
|
|
|
318
483
|
rules,
|
|
319
484
|
transform
|
|
320
485
|
} = props;
|
|
321
|
-
return /* @__PURE__ */
|
|
486
|
+
return /* @__PURE__ */ React7.createElement(
|
|
322
487
|
Controller3,
|
|
323
488
|
{
|
|
324
489
|
control,
|
|
325
490
|
name,
|
|
326
|
-
render: ({ field, fieldState }) => /* @__PURE__ */
|
|
491
|
+
render: ({ field: field2, fieldState }) => /* @__PURE__ */ React7.createElement("div", { className }, /* @__PURE__ */ React7.createElement(
|
|
327
492
|
CoercedFileInput,
|
|
328
493
|
{
|
|
329
494
|
accept,
|
|
@@ -331,8 +496,8 @@ function FileField(props) {
|
|
|
331
496
|
disabled: isDisabled,
|
|
332
497
|
errorMessage: fieldState.error?.message,
|
|
333
498
|
field: {
|
|
334
|
-
...
|
|
335
|
-
onChange: (value) =>
|
|
499
|
+
...field2,
|
|
500
|
+
onChange: (value) => field2.onChange(transform ? transform(value) : value)
|
|
336
501
|
},
|
|
337
502
|
fileProps,
|
|
338
503
|
label,
|
|
@@ -345,7 +510,7 @@ function FileField(props) {
|
|
|
345
510
|
}
|
|
346
511
|
|
|
347
512
|
// src/fields/FontPickerField.tsx
|
|
348
|
-
import
|
|
513
|
+
import React8 from "react";
|
|
349
514
|
import { Controller as Controller4 } from "react-hook-form";
|
|
350
515
|
var FontPickerComponent = null;
|
|
351
516
|
var fontPickerLoaded = false;
|
|
@@ -362,12 +527,12 @@ function FontPickerField(props) {
|
|
|
362
527
|
name,
|
|
363
528
|
rules
|
|
364
529
|
} = props;
|
|
365
|
-
const [fontPickerState, setFontPickerState] =
|
|
530
|
+
const [fontPickerState, setFontPickerState] = React8.useState({
|
|
366
531
|
component: FontPickerComponent,
|
|
367
532
|
loading: false,
|
|
368
533
|
error: null
|
|
369
534
|
});
|
|
370
|
-
|
|
535
|
+
React8.useEffect(() => {
|
|
371
536
|
if (fontPickerLoaded && FontPickerComponent) {
|
|
372
537
|
setFontPickerState({
|
|
373
538
|
component: FontPickerComponent,
|
|
@@ -425,23 +590,23 @@ function FontPickerField(props) {
|
|
|
425
590
|
void loadFontPicker();
|
|
426
591
|
}, []);
|
|
427
592
|
if (fontPickerState.loading) {
|
|
428
|
-
return /* @__PURE__ */
|
|
593
|
+
return /* @__PURE__ */ React8.createElement("div", { className }, /* @__PURE__ */ React8.createElement("div", { className: "space-y-2" }, label && /* @__PURE__ */ React8.createElement("label", { className: "block text-sm font-medium text-foreground" }, label), description && /* @__PURE__ */ React8.createElement("p", { className: "text-sm text-muted-foreground" }, description), /* @__PURE__ */ React8.createElement("div", { className: "p-4 border border-default-200 bg-default-50 rounded-medium" }, /* @__PURE__ */ React8.createElement("p", { className: "text-default-600 text-sm" }, "Loading font picker..."))));
|
|
429
594
|
}
|
|
430
595
|
if (!fontPickerState.component) {
|
|
431
|
-
return /* @__PURE__ */
|
|
596
|
+
return /* @__PURE__ */ React8.createElement("div", { className }, /* @__PURE__ */ React8.createElement("div", { className: "space-y-2" }, label && /* @__PURE__ */ React8.createElement("label", { className: "block text-sm font-medium text-foreground" }, label), description && /* @__PURE__ */ React8.createElement("p", { className: "text-sm text-muted-foreground" }, description), /* @__PURE__ */ React8.createElement("div", { className: "p-4 border border-warning-200 bg-warning-50 rounded-medium" }, /* @__PURE__ */ React8.createElement("p", { className: "text-warning-800 text-sm" }, "Font picker requires the @rachelallyson/heroui-font-picker package. Please install it as a peer dependency for advanced font selection features."))));
|
|
432
597
|
}
|
|
433
|
-
return /* @__PURE__ */
|
|
598
|
+
return /* @__PURE__ */ React8.createElement(
|
|
434
599
|
Controller4,
|
|
435
600
|
{
|
|
436
601
|
control,
|
|
437
602
|
name,
|
|
438
|
-
render: ({ field, fieldState }) => /* @__PURE__ */
|
|
603
|
+
render: ({ field: field2, fieldState }) => /* @__PURE__ */ React8.createElement(
|
|
439
604
|
fontPickerState.component,
|
|
440
605
|
{
|
|
441
606
|
label,
|
|
442
607
|
description,
|
|
443
|
-
value:
|
|
444
|
-
onSelectionChange: (value) =>
|
|
608
|
+
value: field2.value ?? "",
|
|
609
|
+
onSelectionChange: (value) => field2.onChange(value),
|
|
445
610
|
errorMessage: fieldState.error?.message,
|
|
446
611
|
isDisabled,
|
|
447
612
|
className,
|
|
@@ -454,12 +619,12 @@ function FontPickerField(props) {
|
|
|
454
619
|
}
|
|
455
620
|
|
|
456
621
|
// src/fields/InputField.tsx
|
|
457
|
-
import
|
|
622
|
+
import React9 from "react";
|
|
458
623
|
import { Controller as Controller5 } from "react-hook-form";
|
|
459
624
|
function CoercedInput(props) {
|
|
460
|
-
const { description, disabled, errorMessage, field, inputProps, label } = props;
|
|
625
|
+
const { description, disabled, errorMessage, field: field2, inputProps, label } = props;
|
|
461
626
|
const defaults = useHeroHookFormDefaults();
|
|
462
|
-
return /* @__PURE__ */
|
|
627
|
+
return /* @__PURE__ */ React9.createElement(
|
|
463
628
|
Input,
|
|
464
629
|
{
|
|
465
630
|
...defaults.input,
|
|
@@ -469,13 +634,13 @@ function CoercedInput(props) {
|
|
|
469
634
|
isDisabled: disabled,
|
|
470
635
|
isInvalid: Boolean(errorMessage),
|
|
471
636
|
label,
|
|
472
|
-
value:
|
|
473
|
-
onBlur:
|
|
474
|
-
onValueChange:
|
|
637
|
+
value: field2.value ?? "",
|
|
638
|
+
onBlur: field2.onBlur,
|
|
639
|
+
onValueChange: field2.onChange
|
|
475
640
|
}
|
|
476
641
|
);
|
|
477
642
|
}
|
|
478
|
-
|
|
643
|
+
var InputField = React9.memo((props) => {
|
|
479
644
|
const {
|
|
480
645
|
className,
|
|
481
646
|
control,
|
|
@@ -487,27 +652,27 @@ function InputField(props) {
|
|
|
487
652
|
rules,
|
|
488
653
|
transform
|
|
489
654
|
} = props;
|
|
490
|
-
return /* @__PURE__ */
|
|
655
|
+
return /* @__PURE__ */ React9.createElement(
|
|
491
656
|
Controller5,
|
|
492
657
|
{
|
|
493
658
|
control,
|
|
494
659
|
name,
|
|
495
|
-
render: ({ field, fieldState }) => /* @__PURE__ */
|
|
660
|
+
render: ({ field: field2, fieldState }) => /* @__PURE__ */ React9.createElement("div", { className }, /* @__PURE__ */ React9.createElement(
|
|
496
661
|
CoercedInput,
|
|
497
662
|
{
|
|
498
663
|
description,
|
|
499
664
|
disabled: isDisabled,
|
|
500
665
|
errorMessage: fieldState.error?.message,
|
|
501
666
|
field: {
|
|
502
|
-
...
|
|
667
|
+
...field2,
|
|
503
668
|
onChange: (value) => {
|
|
504
669
|
if (inputProps?.type === "number") {
|
|
505
670
|
const numValue = value === "" ? void 0 : Number(value);
|
|
506
|
-
|
|
671
|
+
field2.onChange(
|
|
507
672
|
transform ? transform(String(numValue)) : numValue
|
|
508
673
|
);
|
|
509
674
|
} else {
|
|
510
|
-
|
|
675
|
+
field2.onChange(transform ? transform(value) : value);
|
|
511
676
|
}
|
|
512
677
|
}
|
|
513
678
|
},
|
|
@@ -518,10 +683,10 @@ function InputField(props) {
|
|
|
518
683
|
rules
|
|
519
684
|
}
|
|
520
685
|
);
|
|
521
|
-
}
|
|
686
|
+
});
|
|
522
687
|
|
|
523
688
|
// src/fields/RadioGroupField.tsx
|
|
524
|
-
import
|
|
689
|
+
import React10 from "react";
|
|
525
690
|
import { Controller as Controller6 } from "react-hook-form";
|
|
526
691
|
function RadioGroupField(props) {
|
|
527
692
|
const {
|
|
@@ -536,12 +701,12 @@ function RadioGroupField(props) {
|
|
|
536
701
|
rules
|
|
537
702
|
} = props;
|
|
538
703
|
const defaults = useHeroHookFormDefaults();
|
|
539
|
-
return /* @__PURE__ */
|
|
704
|
+
return /* @__PURE__ */ React10.createElement(
|
|
540
705
|
Controller6,
|
|
541
706
|
{
|
|
542
707
|
control,
|
|
543
708
|
name,
|
|
544
|
-
render: ({ field, fieldState }) => /* @__PURE__ */
|
|
709
|
+
render: ({ field: field2, fieldState }) => /* @__PURE__ */ React10.createElement("div", { className }, /* @__PURE__ */ React10.createElement(
|
|
545
710
|
RadioGroup,
|
|
546
711
|
{
|
|
547
712
|
...defaults.radioGroup,
|
|
@@ -550,11 +715,11 @@ function RadioGroupField(props) {
|
|
|
550
715
|
isDisabled,
|
|
551
716
|
isInvalid: Boolean(fieldState.error),
|
|
552
717
|
label,
|
|
553
|
-
value: String(
|
|
554
|
-
onBlur:
|
|
555
|
-
onValueChange: (val) =>
|
|
718
|
+
value: String(field2.value ?? ""),
|
|
719
|
+
onBlur: field2.onBlur,
|
|
720
|
+
onValueChange: (val) => field2.onChange(val)
|
|
556
721
|
},
|
|
557
|
-
options.map((opt) => /* @__PURE__ */
|
|
722
|
+
options.map((opt) => /* @__PURE__ */ React10.createElement(
|
|
558
723
|
Radio,
|
|
559
724
|
{
|
|
560
725
|
key: String(opt.value),
|
|
@@ -563,14 +728,14 @@ function RadioGroupField(props) {
|
|
|
563
728
|
},
|
|
564
729
|
opt.label
|
|
565
730
|
))
|
|
566
|
-
), fieldState.error?.message ? /* @__PURE__ */
|
|
731
|
+
), fieldState.error?.message ? /* @__PURE__ */ React10.createElement("p", { className: "text-tiny text-danger mt-1" }, fieldState.error.message) : null),
|
|
567
732
|
rules
|
|
568
733
|
}
|
|
569
734
|
);
|
|
570
735
|
}
|
|
571
736
|
|
|
572
737
|
// src/fields/SelectField.tsx
|
|
573
|
-
import
|
|
738
|
+
import React11 from "react";
|
|
574
739
|
import { Controller as Controller7 } from "react-hook-form";
|
|
575
740
|
function SelectField(props) {
|
|
576
741
|
const {
|
|
@@ -586,14 +751,14 @@ function SelectField(props) {
|
|
|
586
751
|
selectProps
|
|
587
752
|
} = props;
|
|
588
753
|
const defaults = useHeroHookFormDefaults();
|
|
589
|
-
return /* @__PURE__ */
|
|
754
|
+
return /* @__PURE__ */ React11.createElement(
|
|
590
755
|
Controller7,
|
|
591
756
|
{
|
|
592
757
|
control,
|
|
593
758
|
name,
|
|
594
|
-
render: ({ field, fieldState }) => {
|
|
595
|
-
const selectedKey =
|
|
596
|
-
return /* @__PURE__ */
|
|
759
|
+
render: ({ field: field2, fieldState }) => {
|
|
760
|
+
const selectedKey = field2.value;
|
|
761
|
+
return /* @__PURE__ */ React11.createElement("div", { className }, /* @__PURE__ */ React11.createElement(
|
|
597
762
|
Select,
|
|
598
763
|
{
|
|
599
764
|
...defaults.select,
|
|
@@ -608,10 +773,10 @@ function SelectField(props) {
|
|
|
608
773
|
onSelectionChange: (keys) => {
|
|
609
774
|
const keyArray = Array.from(keys);
|
|
610
775
|
const next = keyArray[0] ?? "";
|
|
611
|
-
|
|
776
|
+
field2.onChange(next);
|
|
612
777
|
}
|
|
613
778
|
},
|
|
614
|
-
options.map((opt) => /* @__PURE__ */
|
|
779
|
+
options.map((opt) => /* @__PURE__ */ React11.createElement(
|
|
615
780
|
SelectItem,
|
|
616
781
|
{
|
|
617
782
|
key: String(opt.value),
|
|
@@ -628,12 +793,12 @@ function SelectField(props) {
|
|
|
628
793
|
}
|
|
629
794
|
|
|
630
795
|
// src/fields/SliderField.tsx
|
|
631
|
-
import
|
|
796
|
+
import React12 from "react";
|
|
632
797
|
import { Controller as Controller8 } from "react-hook-form";
|
|
633
798
|
function CoercedSlider(props) {
|
|
634
|
-
const { description, disabled, errorMessage, field, label, sliderProps } = props;
|
|
799
|
+
const { description, disabled, errorMessage, field: field2, label, sliderProps } = props;
|
|
635
800
|
const defaults = useHeroHookFormDefaults();
|
|
636
|
-
return /* @__PURE__ */
|
|
801
|
+
return /* @__PURE__ */ React12.createElement(
|
|
637
802
|
Slider,
|
|
638
803
|
{
|
|
639
804
|
...defaults.slider,
|
|
@@ -643,9 +808,9 @@ function CoercedSlider(props) {
|
|
|
643
808
|
isDisabled: disabled,
|
|
644
809
|
isInvalid: Boolean(errorMessage),
|
|
645
810
|
label,
|
|
646
|
-
value:
|
|
647
|
-
onBlur:
|
|
648
|
-
onValueChange:
|
|
811
|
+
value: field2.value ?? 0,
|
|
812
|
+
onBlur: field2.onBlur,
|
|
813
|
+
onValueChange: field2.onChange
|
|
649
814
|
}
|
|
650
815
|
);
|
|
651
816
|
}
|
|
@@ -661,20 +826,20 @@ function SliderField(props) {
|
|
|
661
826
|
sliderProps,
|
|
662
827
|
transform
|
|
663
828
|
} = props;
|
|
664
|
-
return /* @__PURE__ */
|
|
829
|
+
return /* @__PURE__ */ React12.createElement(
|
|
665
830
|
Controller8,
|
|
666
831
|
{
|
|
667
832
|
control,
|
|
668
833
|
name,
|
|
669
|
-
render: ({ field, fieldState }) => /* @__PURE__ */
|
|
834
|
+
render: ({ field: field2, fieldState }) => /* @__PURE__ */ React12.createElement("div", { className }, /* @__PURE__ */ React12.createElement(
|
|
670
835
|
CoercedSlider,
|
|
671
836
|
{
|
|
672
837
|
description,
|
|
673
838
|
disabled: isDisabled,
|
|
674
839
|
errorMessage: fieldState.error?.message,
|
|
675
840
|
field: {
|
|
676
|
-
...
|
|
677
|
-
onChange: (value) =>
|
|
841
|
+
...field2,
|
|
842
|
+
onChange: (value) => field2.onChange(transform ? transform(value) : value)
|
|
678
843
|
},
|
|
679
844
|
label,
|
|
680
845
|
sliderProps
|
|
@@ -686,7 +851,7 @@ function SliderField(props) {
|
|
|
686
851
|
}
|
|
687
852
|
|
|
688
853
|
// src/fields/SwitchField.tsx
|
|
689
|
-
import
|
|
854
|
+
import React13 from "react";
|
|
690
855
|
import { Controller as Controller9 } from "react-hook-form";
|
|
691
856
|
function SwitchField(props) {
|
|
692
857
|
const {
|
|
@@ -700,30 +865,30 @@ function SwitchField(props) {
|
|
|
700
865
|
switchProps
|
|
701
866
|
} = props;
|
|
702
867
|
const defaults = useHeroHookFormDefaults();
|
|
703
|
-
return /* @__PURE__ */
|
|
868
|
+
return /* @__PURE__ */ React13.createElement(
|
|
704
869
|
Controller9,
|
|
705
870
|
{
|
|
706
871
|
control,
|
|
707
872
|
name,
|
|
708
|
-
render: ({ field, fieldState }) => /* @__PURE__ */
|
|
873
|
+
render: ({ field: field2, fieldState }) => /* @__PURE__ */ React13.createElement("div", { className }, /* @__PURE__ */ React13.createElement(
|
|
709
874
|
Switch,
|
|
710
875
|
{
|
|
711
876
|
...defaults.switch,
|
|
712
877
|
...switchProps,
|
|
713
878
|
isDisabled,
|
|
714
|
-
isSelected: Boolean(
|
|
715
|
-
onBlur:
|
|
716
|
-
onValueChange: (val) =>
|
|
879
|
+
isSelected: Boolean(field2.value),
|
|
880
|
+
onBlur: field2.onBlur,
|
|
881
|
+
onValueChange: (val) => field2.onChange(val)
|
|
717
882
|
},
|
|
718
883
|
label
|
|
719
|
-
), description ? /* @__PURE__ */
|
|
884
|
+
), description ? /* @__PURE__ */ React13.createElement("p", { className: "text-small text-default-400" }, description) : null, fieldState.error?.message ? /* @__PURE__ */ React13.createElement("p", { className: "text-tiny text-danger mt-1" }, fieldState.error.message) : null),
|
|
720
885
|
rules
|
|
721
886
|
}
|
|
722
887
|
);
|
|
723
888
|
}
|
|
724
889
|
|
|
725
890
|
// src/fields/TextareaField.tsx
|
|
726
|
-
import
|
|
891
|
+
import React14 from "react";
|
|
727
892
|
import { Controller as Controller10 } from "react-hook-form";
|
|
728
893
|
function TextareaField(props) {
|
|
729
894
|
const {
|
|
@@ -737,12 +902,12 @@ function TextareaField(props) {
|
|
|
737
902
|
textareaProps
|
|
738
903
|
} = props;
|
|
739
904
|
const defaults = useHeroHookFormDefaults();
|
|
740
|
-
return /* @__PURE__ */
|
|
905
|
+
return /* @__PURE__ */ React14.createElement(
|
|
741
906
|
Controller10,
|
|
742
907
|
{
|
|
743
908
|
control,
|
|
744
909
|
name,
|
|
745
|
-
render: ({ field, fieldState }) => /* @__PURE__ */
|
|
910
|
+
render: ({ field: field2, fieldState }) => /* @__PURE__ */ React14.createElement("div", { className }, /* @__PURE__ */ React14.createElement(
|
|
746
911
|
Textarea,
|
|
747
912
|
{
|
|
748
913
|
...defaults.textarea,
|
|
@@ -752,9 +917,9 @@ function TextareaField(props) {
|
|
|
752
917
|
isDisabled,
|
|
753
918
|
isInvalid: Boolean(fieldState.error),
|
|
754
919
|
label,
|
|
755
|
-
value:
|
|
756
|
-
onBlur:
|
|
757
|
-
onValueChange:
|
|
920
|
+
value: field2.value ?? "",
|
|
921
|
+
onBlur: field2.onBlur,
|
|
922
|
+
onValueChange: field2.onChange
|
|
758
923
|
}
|
|
759
924
|
)),
|
|
760
925
|
rules
|
|
@@ -763,13 +928,16 @@ function TextareaField(props) {
|
|
|
763
928
|
}
|
|
764
929
|
|
|
765
930
|
// src/components/FormField.tsx
|
|
766
|
-
|
|
931
|
+
var FormField = React15.memo(({
|
|
767
932
|
config,
|
|
768
933
|
form,
|
|
769
934
|
submissionState
|
|
770
|
-
}) {
|
|
935
|
+
}) => {
|
|
936
|
+
if (!form || !form.control) {
|
|
937
|
+
return null;
|
|
938
|
+
}
|
|
771
939
|
const { control } = form;
|
|
772
|
-
const watchedValues =
|
|
940
|
+
const watchedValues = useWatch3({ control });
|
|
773
941
|
if (config.condition && !config.condition(watchedValues)) {
|
|
774
942
|
return null;
|
|
775
943
|
}
|
|
@@ -791,7 +959,7 @@ function FormField({
|
|
|
791
959
|
};
|
|
792
960
|
switch (config.type) {
|
|
793
961
|
case "input":
|
|
794
|
-
return /* @__PURE__ */
|
|
962
|
+
return /* @__PURE__ */ React15.createElement(
|
|
795
963
|
InputField,
|
|
796
964
|
{
|
|
797
965
|
...baseProps,
|
|
@@ -801,7 +969,7 @@ function FormField({
|
|
|
801
969
|
}
|
|
802
970
|
);
|
|
803
971
|
case "textarea":
|
|
804
|
-
return /* @__PURE__ */
|
|
972
|
+
return /* @__PURE__ */ React15.createElement(
|
|
805
973
|
TextareaField,
|
|
806
974
|
{
|
|
807
975
|
...baseProps,
|
|
@@ -811,7 +979,7 @@ function FormField({
|
|
|
811
979
|
}
|
|
812
980
|
);
|
|
813
981
|
case "select":
|
|
814
|
-
return /* @__PURE__ */
|
|
982
|
+
return /* @__PURE__ */ React15.createElement(
|
|
815
983
|
SelectField,
|
|
816
984
|
{
|
|
817
985
|
...baseProps,
|
|
@@ -825,7 +993,7 @@ function FormField({
|
|
|
825
993
|
}
|
|
826
994
|
);
|
|
827
995
|
case "checkbox":
|
|
828
|
-
return /* @__PURE__ */
|
|
996
|
+
return /* @__PURE__ */ React15.createElement(
|
|
829
997
|
CheckboxField,
|
|
830
998
|
{
|
|
831
999
|
...baseProps,
|
|
@@ -835,7 +1003,7 @@ function FormField({
|
|
|
835
1003
|
}
|
|
836
1004
|
);
|
|
837
1005
|
case "radio":
|
|
838
|
-
return /* @__PURE__ */
|
|
1006
|
+
return /* @__PURE__ */ React15.createElement(
|
|
839
1007
|
RadioGroupField,
|
|
840
1008
|
{
|
|
841
1009
|
...baseProps,
|
|
@@ -849,7 +1017,7 @@ function FormField({
|
|
|
849
1017
|
}
|
|
850
1018
|
);
|
|
851
1019
|
case "switch":
|
|
852
|
-
return /* @__PURE__ */
|
|
1020
|
+
return /* @__PURE__ */ React15.createElement(
|
|
853
1021
|
SwitchField,
|
|
854
1022
|
{
|
|
855
1023
|
...baseProps,
|
|
@@ -859,7 +1027,7 @@ function FormField({
|
|
|
859
1027
|
}
|
|
860
1028
|
);
|
|
861
1029
|
case "slider":
|
|
862
|
-
return /* @__PURE__ */
|
|
1030
|
+
return /* @__PURE__ */ React15.createElement(
|
|
863
1031
|
SliderField,
|
|
864
1032
|
{
|
|
865
1033
|
...baseProps,
|
|
@@ -869,7 +1037,7 @@ function FormField({
|
|
|
869
1037
|
}
|
|
870
1038
|
);
|
|
871
1039
|
case "date":
|
|
872
|
-
return /* @__PURE__ */
|
|
1040
|
+
return /* @__PURE__ */ React15.createElement(
|
|
873
1041
|
DateField,
|
|
874
1042
|
{
|
|
875
1043
|
...baseProps,
|
|
@@ -879,7 +1047,7 @@ function FormField({
|
|
|
879
1047
|
}
|
|
880
1048
|
);
|
|
881
1049
|
case "file":
|
|
882
|
-
return /* @__PURE__ */
|
|
1050
|
+
return /* @__PURE__ */ React15.createElement(
|
|
883
1051
|
FileField,
|
|
884
1052
|
{
|
|
885
1053
|
...baseProps,
|
|
@@ -891,7 +1059,7 @@ function FormField({
|
|
|
891
1059
|
}
|
|
892
1060
|
);
|
|
893
1061
|
case "fontPicker":
|
|
894
|
-
return /* @__PURE__ */
|
|
1062
|
+
return /* @__PURE__ */ React15.createElement(
|
|
895
1063
|
FontPickerField,
|
|
896
1064
|
{
|
|
897
1065
|
...baseProps,
|
|
@@ -908,13 +1076,39 @@ function FormField({
|
|
|
908
1076
|
isSubmitting: submissionState.isSubmitting,
|
|
909
1077
|
name: config.name
|
|
910
1078
|
});
|
|
1079
|
+
case "conditional":
|
|
1080
|
+
return /* @__PURE__ */ React15.createElement(
|
|
1081
|
+
ConditionalField,
|
|
1082
|
+
{
|
|
1083
|
+
config,
|
|
1084
|
+
control,
|
|
1085
|
+
className: config.className
|
|
1086
|
+
}
|
|
1087
|
+
);
|
|
1088
|
+
case "fieldArray":
|
|
1089
|
+
return /* @__PURE__ */ React15.createElement(
|
|
1090
|
+
FieldArrayField,
|
|
1091
|
+
{
|
|
1092
|
+
config,
|
|
1093
|
+
className: config.className
|
|
1094
|
+
}
|
|
1095
|
+
);
|
|
1096
|
+
case "dynamicSection":
|
|
1097
|
+
return /* @__PURE__ */ React15.createElement(
|
|
1098
|
+
DynamicSectionField,
|
|
1099
|
+
{
|
|
1100
|
+
config,
|
|
1101
|
+
control,
|
|
1102
|
+
className: config.className
|
|
1103
|
+
}
|
|
1104
|
+
);
|
|
911
1105
|
default: {
|
|
912
1106
|
const fieldType = config.type;
|
|
913
1107
|
console.warn(`Unknown field type: ${fieldType}`);
|
|
914
1108
|
return null;
|
|
915
1109
|
}
|
|
916
1110
|
}
|
|
917
|
-
}
|
|
1111
|
+
});
|
|
918
1112
|
|
|
919
1113
|
// src/components/Form.tsx
|
|
920
1114
|
function ConfigurableForm({
|
|
@@ -951,16 +1145,16 @@ function ConfigurableForm({
|
|
|
951
1145
|
});
|
|
952
1146
|
const renderFields = () => {
|
|
953
1147
|
if (layout === "grid") {
|
|
954
|
-
return /* @__PURE__ */
|
|
1148
|
+
return /* @__PURE__ */ React16.createElement(
|
|
955
1149
|
"div",
|
|
956
1150
|
{
|
|
957
1151
|
className: `grid gap-${spacing} ${columns === 1 ? "grid-cols-1" : columns === 2 ? "grid-cols-1 md:grid-cols-2" : "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"}`
|
|
958
1152
|
},
|
|
959
|
-
fields.map((
|
|
1153
|
+
fields.map((field2) => /* @__PURE__ */ React16.createElement(
|
|
960
1154
|
FormField,
|
|
961
1155
|
{
|
|
962
|
-
key:
|
|
963
|
-
config:
|
|
1156
|
+
key: field2.name,
|
|
1157
|
+
config: field2,
|
|
964
1158
|
form,
|
|
965
1159
|
submissionState
|
|
966
1160
|
}
|
|
@@ -968,21 +1162,21 @@ function ConfigurableForm({
|
|
|
968
1162
|
);
|
|
969
1163
|
}
|
|
970
1164
|
if (layout === "horizontal") {
|
|
971
|
-
return /* @__PURE__ */
|
|
1165
|
+
return /* @__PURE__ */ React16.createElement("div", { className: `grid gap-${spacing} grid-cols-1 md:grid-cols-2` }, fields.map((field2) => /* @__PURE__ */ React16.createElement(
|
|
972
1166
|
FormField,
|
|
973
1167
|
{
|
|
974
|
-
key:
|
|
975
|
-
config:
|
|
1168
|
+
key: field2.name,
|
|
1169
|
+
config: field2,
|
|
976
1170
|
form,
|
|
977
1171
|
submissionState
|
|
978
1172
|
}
|
|
979
1173
|
)));
|
|
980
1174
|
}
|
|
981
|
-
return /* @__PURE__ */
|
|
1175
|
+
return /* @__PURE__ */ React16.createElement("div", { className: `space-y-${spacing}` }, fields.map((field2) => /* @__PURE__ */ React16.createElement(
|
|
982
1176
|
FormField,
|
|
983
1177
|
{
|
|
984
|
-
key:
|
|
985
|
-
config:
|
|
1178
|
+
key: field2.name,
|
|
1179
|
+
config: field2,
|
|
986
1180
|
form,
|
|
987
1181
|
submissionState
|
|
988
1182
|
}
|
|
@@ -992,24 +1186,24 @@ function ConfigurableForm({
|
|
|
992
1186
|
e.preventDefault();
|
|
993
1187
|
void handleSubmit();
|
|
994
1188
|
};
|
|
995
|
-
return /* @__PURE__ */
|
|
1189
|
+
return /* @__PURE__ */ React16.createElement("form", { className, role: "form", onSubmit: handleFormSubmit }, title && /* @__PURE__ */ React16.createElement("div", { className: "mb-6" }, /* @__PURE__ */ React16.createElement("h2", { className: "text-xl font-semibold text-foreground mb-2" }, title), subtitle && /* @__PURE__ */ React16.createElement("p", { className: "text-sm text-muted-foreground" }, subtitle)), isSubmitted && isSuccess && /* @__PURE__ */ React16.createElement(
|
|
996
1190
|
"div",
|
|
997
1191
|
{
|
|
998
1192
|
className: "mb-6 p-4 bg-success-50 border border-success-200 rounded-lg",
|
|
999
1193
|
"data-testid": "success-message"
|
|
1000
1194
|
},
|
|
1001
|
-
/* @__PURE__ */
|
|
1002
|
-
/* @__PURE__ */
|
|
1003
|
-
), error && /* @__PURE__ */
|
|
1195
|
+
/* @__PURE__ */ React16.createElement("p", { className: "text-success-800 font-medium" }, "Success!"),
|
|
1196
|
+
/* @__PURE__ */ React16.createElement("p", { className: "text-success-700 text-sm mt-1" }, "Your request has been processed successfully.")
|
|
1197
|
+
), error && /* @__PURE__ */ React16.createElement(
|
|
1004
1198
|
"div",
|
|
1005
1199
|
{
|
|
1006
1200
|
className: "mb-6 p-4 bg-danger-50 border border-danger-200 rounded-lg",
|
|
1007
1201
|
"data-testid": "error-message"
|
|
1008
1202
|
},
|
|
1009
|
-
/* @__PURE__ */
|
|
1010
|
-
/* @__PURE__ */
|
|
1011
|
-
), renderFields(), /* @__PURE__ */
|
|
1012
|
-
|
|
1203
|
+
/* @__PURE__ */ React16.createElement("p", { className: "text-danger-800 font-medium" }, "Error"),
|
|
1204
|
+
/* @__PURE__ */ React16.createElement("p", { className: "text-danger-700 text-sm mt-1" }, error)
|
|
1205
|
+
), renderFields(), /* @__PURE__ */ React16.createElement("div", { className: "mt-6 flex gap-3 justify-end" }, /* @__PURE__ */ React16.createElement(
|
|
1206
|
+
Button3,
|
|
1013
1207
|
{
|
|
1014
1208
|
color: "primary",
|
|
1015
1209
|
isDisabled: isSubmitting,
|
|
@@ -1018,8 +1212,8 @@ function ConfigurableForm({
|
|
|
1018
1212
|
...submitButtonProps
|
|
1019
1213
|
},
|
|
1020
1214
|
submitButtonText
|
|
1021
|
-
), showResetButton && /* @__PURE__ */
|
|
1022
|
-
|
|
1215
|
+
), showResetButton && /* @__PURE__ */ React16.createElement(
|
|
1216
|
+
Button3,
|
|
1023
1217
|
{
|
|
1024
1218
|
isDisabled: isSubmitting,
|
|
1025
1219
|
type: "button",
|
|
@@ -1031,9 +1225,9 @@ function ConfigurableForm({
|
|
|
1031
1225
|
}
|
|
1032
1226
|
|
|
1033
1227
|
// src/hooks/useHeroForm.ts
|
|
1034
|
-
import { useFormContext } from "react-hook-form";
|
|
1228
|
+
import { useFormContext as useFormContext4 } from "react-hook-form";
|
|
1035
1229
|
function useHeroForm() {
|
|
1036
|
-
const form =
|
|
1230
|
+
const form = useFormContext4();
|
|
1037
1231
|
const defaults = useHeroHookFormDefaults();
|
|
1038
1232
|
return {
|
|
1039
1233
|
// All React Hook Form methods and state
|
|
@@ -1044,10 +1238,10 @@ function useHeroForm() {
|
|
|
1044
1238
|
}
|
|
1045
1239
|
|
|
1046
1240
|
// src/providers/FormProvider.tsx
|
|
1047
|
-
import
|
|
1241
|
+
import React17 from "react";
|
|
1048
1242
|
import { FormProvider as RHFProvider } from "react-hook-form";
|
|
1049
1243
|
function FormProvider(props) {
|
|
1050
|
-
return /* @__PURE__ */
|
|
1244
|
+
return /* @__PURE__ */ React17.createElement(RHFProvider, { ...props.methods }, /* @__PURE__ */ React17.createElement(
|
|
1051
1245
|
"form",
|
|
1052
1246
|
{
|
|
1053
1247
|
className: props.className,
|
|
@@ -1060,22 +1254,42 @@ function FormProvider(props) {
|
|
|
1060
1254
|
}
|
|
1061
1255
|
|
|
1062
1256
|
// src/submit/SubmitButton.tsx
|
|
1063
|
-
import
|
|
1257
|
+
import React18 from "react";
|
|
1064
1258
|
function SubmitButton(props) {
|
|
1065
|
-
const ctx =
|
|
1259
|
+
const ctx = useFormContext5();
|
|
1066
1260
|
const loading = props.isLoading ?? ctx.formState.isSubmitting;
|
|
1261
|
+
const enhancedState = props.enhancedState;
|
|
1067
1262
|
const isDisabledFromProps = props.buttonProps?.isDisabled ?? false;
|
|
1068
1263
|
const isDisabled = Boolean(isDisabledFromProps) || Boolean(loading);
|
|
1069
1264
|
const defaults = useHeroHookFormDefaults();
|
|
1070
|
-
|
|
1265
|
+
const getButtonContent = () => {
|
|
1266
|
+
if (enhancedState?.isSuccess) {
|
|
1267
|
+
return /* @__PURE__ */ React18.createElement("span", { className: "inline-flex items-center gap-2" }, "\u2705", props.successText || "Success!");
|
|
1268
|
+
}
|
|
1269
|
+
if (loading) {
|
|
1270
|
+
return /* @__PURE__ */ React18.createElement("span", { className: "inline-flex items-center gap-2" }, "\u23F3", props.loadingText || "Submitting...");
|
|
1271
|
+
}
|
|
1272
|
+
return props.children;
|
|
1273
|
+
};
|
|
1274
|
+
const getButtonColor = () => {
|
|
1275
|
+
if (enhancedState?.isSuccess) {
|
|
1276
|
+
return "success";
|
|
1277
|
+
}
|
|
1278
|
+
if (enhancedState?.isError) {
|
|
1279
|
+
return "danger";
|
|
1280
|
+
}
|
|
1281
|
+
return props.buttonProps?.color || defaults.submitButton.color;
|
|
1282
|
+
};
|
|
1283
|
+
return /* @__PURE__ */ React18.createElement(
|
|
1071
1284
|
Button,
|
|
1072
1285
|
{
|
|
1073
1286
|
type: "submit",
|
|
1074
1287
|
...defaults.submitButton,
|
|
1075
1288
|
...props.buttonProps,
|
|
1076
|
-
isDisabled
|
|
1289
|
+
isDisabled,
|
|
1290
|
+
color: getButtonColor()
|
|
1077
1291
|
},
|
|
1078
|
-
|
|
1292
|
+
getButtonContent()
|
|
1079
1293
|
);
|
|
1080
1294
|
}
|
|
1081
1295
|
|
|
@@ -1266,13 +1480,69 @@ var commonValidations = {
|
|
|
1266
1480
|
requiredCheckbox: (fieldName) => createRequiredCheckboxSchema(fieldName),
|
|
1267
1481
|
url: createUrlSchema()
|
|
1268
1482
|
};
|
|
1483
|
+
var crossFieldValidation = {
|
|
1484
|
+
/**
|
|
1485
|
+
* Password confirmation validation
|
|
1486
|
+
*/
|
|
1487
|
+
passwordConfirmation: (passwordField, confirmField) => {
|
|
1488
|
+
return z.object({
|
|
1489
|
+
[passwordField]: z.string(),
|
|
1490
|
+
[confirmField]: z.string()
|
|
1491
|
+
}).refine(
|
|
1492
|
+
(data) => data[passwordField] === data[confirmField],
|
|
1493
|
+
{
|
|
1494
|
+
message: "Passwords do not match",
|
|
1495
|
+
path: [confirmField]
|
|
1496
|
+
}
|
|
1497
|
+
);
|
|
1498
|
+
},
|
|
1499
|
+
/**
|
|
1500
|
+
* Date range validation
|
|
1501
|
+
*/
|
|
1502
|
+
dateRange: (startField, endField) => {
|
|
1503
|
+
return z.object({
|
|
1504
|
+
[startField]: z.string(),
|
|
1505
|
+
[endField]: z.string()
|
|
1506
|
+
}).refine(
|
|
1507
|
+
(data) => {
|
|
1508
|
+
const startDate = new Date(data[startField]);
|
|
1509
|
+
const endDate = new Date(data[endField]);
|
|
1510
|
+
return startDate < endDate;
|
|
1511
|
+
},
|
|
1512
|
+
{
|
|
1513
|
+
message: "End date must be after start date",
|
|
1514
|
+
path: [endField]
|
|
1515
|
+
}
|
|
1516
|
+
);
|
|
1517
|
+
},
|
|
1518
|
+
/**
|
|
1519
|
+
* Conditional required field validation
|
|
1520
|
+
*/
|
|
1521
|
+
conditionalRequired: (field2, conditionField, conditionValue) => {
|
|
1522
|
+
return z.object({
|
|
1523
|
+
[field2]: z.string(),
|
|
1524
|
+
[conditionField]: z.any()
|
|
1525
|
+
}).refine(
|
|
1526
|
+
(data) => {
|
|
1527
|
+
if (data[conditionField] === conditionValue) {
|
|
1528
|
+
return data[field2] && data[field2].trim().length > 0;
|
|
1529
|
+
}
|
|
1530
|
+
return true;
|
|
1531
|
+
},
|
|
1532
|
+
{
|
|
1533
|
+
message: "This field is required",
|
|
1534
|
+
path: [field2]
|
|
1535
|
+
}
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
};
|
|
1269
1539
|
|
|
1270
1540
|
// src/index.ts
|
|
1271
|
-
import { useFormContext as
|
|
1541
|
+
import { useFormContext as useFormContext5 } from "react-hook-form";
|
|
1272
1542
|
|
|
1273
1543
|
// src/components/ZodForm.tsx
|
|
1274
|
-
import
|
|
1275
|
-
import { Button as
|
|
1544
|
+
import React20 from "react";
|
|
1545
|
+
import { Button as Button5 } from "@heroui/react";
|
|
1276
1546
|
|
|
1277
1547
|
// src/zod-integration.ts
|
|
1278
1548
|
import { useForm as useForm2 } from "react-hook-form";
|
|
@@ -1315,6 +1585,146 @@ function createZodFormConfig(schema, fields, defaultValues) {
|
|
|
1315
1585
|
};
|
|
1316
1586
|
}
|
|
1317
1587
|
|
|
1588
|
+
// src/hooks/useEnhancedFormState.ts
|
|
1589
|
+
import { useCallback, useEffect, useState as useState2 } from "react";
|
|
1590
|
+
function useEnhancedFormState(form, options = {}) {
|
|
1591
|
+
const {
|
|
1592
|
+
onSuccess,
|
|
1593
|
+
onError,
|
|
1594
|
+
successMessage = "Form submitted successfully!",
|
|
1595
|
+
errorMessage = "An error occurred. Please try again.",
|
|
1596
|
+
autoReset = true,
|
|
1597
|
+
resetDelay = 3e3
|
|
1598
|
+
} = options;
|
|
1599
|
+
const [status, setStatus] = useState2("idle");
|
|
1600
|
+
const [error, setError] = useState2(void 0);
|
|
1601
|
+
const [submittedData, setSubmittedData] = useState2(void 0);
|
|
1602
|
+
const { formState, getValues } = form;
|
|
1603
|
+
const { errors, touchedFields, dirtyFields, isSubmitting } = formState;
|
|
1604
|
+
useEffect(() => {
|
|
1605
|
+
if (isSubmitting) {
|
|
1606
|
+
setStatus("submitting");
|
|
1607
|
+
}
|
|
1608
|
+
}, [isSubmitting]);
|
|
1609
|
+
useEffect(() => {
|
|
1610
|
+
if (status === "success" && autoReset) {
|
|
1611
|
+
const timer = setTimeout(() => {
|
|
1612
|
+
setStatus("idle");
|
|
1613
|
+
setSubmittedData(void 0);
|
|
1614
|
+
setError(void 0);
|
|
1615
|
+
}, resetDelay);
|
|
1616
|
+
return () => clearTimeout(timer);
|
|
1617
|
+
}
|
|
1618
|
+
}, [status, autoReset, resetDelay]);
|
|
1619
|
+
const handleSuccess = useCallback((data) => {
|
|
1620
|
+
setStatus("success");
|
|
1621
|
+
setSubmittedData(data);
|
|
1622
|
+
setError(void 0);
|
|
1623
|
+
onSuccess?.(data);
|
|
1624
|
+
}, [onSuccess]);
|
|
1625
|
+
const handleError = useCallback((errorMessage2) => {
|
|
1626
|
+
setStatus("error");
|
|
1627
|
+
setError(errorMessage2);
|
|
1628
|
+
setSubmittedData(void 0);
|
|
1629
|
+
onError?.(errorMessage2);
|
|
1630
|
+
}, [onError]);
|
|
1631
|
+
const reset = useCallback(() => {
|
|
1632
|
+
setStatus("idle");
|
|
1633
|
+
setError(void 0);
|
|
1634
|
+
setSubmittedData(void 0);
|
|
1635
|
+
}, []);
|
|
1636
|
+
return {
|
|
1637
|
+
status,
|
|
1638
|
+
isSubmitting,
|
|
1639
|
+
isSuccess: status === "success",
|
|
1640
|
+
isError: status === "error",
|
|
1641
|
+
error,
|
|
1642
|
+
submittedData,
|
|
1643
|
+
touchedFields: new Set(Object.keys(touchedFields)),
|
|
1644
|
+
dirtyFields: new Set(Object.keys(dirtyFields)),
|
|
1645
|
+
hasErrors: Object.keys(errors).length > 0,
|
|
1646
|
+
errorCount: Object.keys(errors).length,
|
|
1647
|
+
handleSuccess,
|
|
1648
|
+
handleError,
|
|
1649
|
+
reset
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
// src/components/FormStatus.tsx
|
|
1654
|
+
import React19 from "react";
|
|
1655
|
+
import { Button as Button4 } from "@heroui/react";
|
|
1656
|
+
function FormStatus({
|
|
1657
|
+
state,
|
|
1658
|
+
onDismiss,
|
|
1659
|
+
className = "",
|
|
1660
|
+
showDetails = false
|
|
1661
|
+
}) {
|
|
1662
|
+
const { status, isSubmitting, isSuccess, isError, error, submittedData } = state;
|
|
1663
|
+
if (status === "idle") {
|
|
1664
|
+
return null;
|
|
1665
|
+
}
|
|
1666
|
+
if (isSubmitting) {
|
|
1667
|
+
return /* @__PURE__ */ React19.createElement("div", { className: `flex items-center gap-3 p-4 bg-blue-50 border border-blue-200 rounded-lg ${className}` }, /* @__PURE__ */ React19.createElement("span", { className: "text-blue-600" }, "\u23F3"), /* @__PURE__ */ React19.createElement("div", null, /* @__PURE__ */ React19.createElement("p", { className: "text-sm font-medium text-blue-900" }, "Submitting form..."), showDetails && /* @__PURE__ */ React19.createElement("p", { className: "text-xs text-blue-700" }, "Please wait while we process your request.")));
|
|
1668
|
+
}
|
|
1669
|
+
if (isSuccess) {
|
|
1670
|
+
return /* @__PURE__ */ React19.createElement("div", { className: `flex items-center gap-3 p-4 bg-green-50 border border-green-200 rounded-lg ${className}`, "data-testid": "success-message" }, /* @__PURE__ */ React19.createElement("span", { className: "text-green-600" }, "\u2705"), /* @__PURE__ */ React19.createElement("div", { className: "flex-1" }, /* @__PURE__ */ React19.createElement("p", { className: "text-sm font-medium text-green-900" }, "Form submitted successfully!"), showDetails && submittedData && /* @__PURE__ */ React19.createElement("p", { className: "text-xs text-green-700" }, "Your data has been saved. Thank you for your submission.")), onDismiss && /* @__PURE__ */ React19.createElement(
|
|
1671
|
+
Button4,
|
|
1672
|
+
{
|
|
1673
|
+
size: "sm",
|
|
1674
|
+
variant: "light",
|
|
1675
|
+
isIconOnly: true,
|
|
1676
|
+
onPress: onDismiss,
|
|
1677
|
+
"aria-label": "Dismiss success message"
|
|
1678
|
+
},
|
|
1679
|
+
"\u2715"
|
|
1680
|
+
));
|
|
1681
|
+
}
|
|
1682
|
+
if (isError && error) {
|
|
1683
|
+
return /* @__PURE__ */ React19.createElement("div", { className: `flex items-center gap-3 p-4 bg-red-50 border border-red-200 rounded-lg ${className}`, "data-testid": "error-message" }, /* @__PURE__ */ React19.createElement("span", { className: "text-red-600" }, "\u26A0\uFE0F"), /* @__PURE__ */ React19.createElement("div", { className: "flex-1" }, /* @__PURE__ */ React19.createElement("p", { className: "text-sm font-medium text-red-900" }, "Error submitting form"), /* @__PURE__ */ React19.createElement("p", { className: "text-xs text-red-700" }, error)), onDismiss && /* @__PURE__ */ React19.createElement(
|
|
1684
|
+
Button4,
|
|
1685
|
+
{
|
|
1686
|
+
size: "sm",
|
|
1687
|
+
variant: "light",
|
|
1688
|
+
isIconOnly: true,
|
|
1689
|
+
onPress: onDismiss,
|
|
1690
|
+
"aria-label": "Dismiss error message"
|
|
1691
|
+
},
|
|
1692
|
+
"\u2715"
|
|
1693
|
+
));
|
|
1694
|
+
}
|
|
1695
|
+
return null;
|
|
1696
|
+
}
|
|
1697
|
+
function FormToast({
|
|
1698
|
+
state,
|
|
1699
|
+
onDismiss,
|
|
1700
|
+
position = "top-right",
|
|
1701
|
+
duration = 5e3
|
|
1702
|
+
}) {
|
|
1703
|
+
const [isVisible, setIsVisible] = React19.useState(false);
|
|
1704
|
+
React19.useEffect(() => {
|
|
1705
|
+
if (state.isSuccess || state.isError) {
|
|
1706
|
+
setIsVisible(true);
|
|
1707
|
+
if (duration > 0) {
|
|
1708
|
+
const timer = setTimeout(() => {
|
|
1709
|
+
setIsVisible(false);
|
|
1710
|
+
onDismiss?.();
|
|
1711
|
+
}, duration);
|
|
1712
|
+
return () => clearTimeout(timer);
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
}, [state.isSuccess, state.isError, duration, onDismiss]);
|
|
1716
|
+
if (!isVisible) {
|
|
1717
|
+
return null;
|
|
1718
|
+
}
|
|
1719
|
+
const positionClasses = {
|
|
1720
|
+
"top-right": "top-4 right-4",
|
|
1721
|
+
"top-left": "top-4 left-4",
|
|
1722
|
+
"bottom-right": "bottom-4 right-4",
|
|
1723
|
+
"bottom-left": "bottom-4 left-4"
|
|
1724
|
+
};
|
|
1725
|
+
return /* @__PURE__ */ React19.createElement("div", { className: `fixed z-50 ${positionClasses[position]}` }, /* @__PURE__ */ React19.createElement(FormStatus, { state, onDismiss }));
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1318
1728
|
// src/components/ZodForm.tsx
|
|
1319
1729
|
function ZodForm({
|
|
1320
1730
|
className,
|
|
@@ -1335,97 +1745,83 @@ function ZodForm({
|
|
|
1335
1745
|
title
|
|
1336
1746
|
}) {
|
|
1337
1747
|
const form = useZodForm(config);
|
|
1338
|
-
const
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1748
|
+
const enhancedState = useEnhancedFormState(form, {
|
|
1749
|
+
onSuccess,
|
|
1750
|
+
onError: (error) => onError?.({ message: error, field: "form" }),
|
|
1751
|
+
autoReset: true,
|
|
1752
|
+
resetDelay: 3e3
|
|
1343
1753
|
});
|
|
1344
1754
|
const handleSubmit = async () => {
|
|
1345
|
-
setSubmissionState((prev) => ({
|
|
1346
|
-
...prev,
|
|
1347
|
-
error: void 0,
|
|
1348
|
-
isSubmitting: true
|
|
1349
|
-
}));
|
|
1350
|
-
const isValid = await form.trigger();
|
|
1351
|
-
if (!isValid) {
|
|
1352
|
-
setSubmissionState({
|
|
1353
|
-
error: "Please fix the validation errors above",
|
|
1354
|
-
isSubmitted: true,
|
|
1355
|
-
isSubmitting: false,
|
|
1356
|
-
isSuccess: false
|
|
1357
|
-
});
|
|
1358
|
-
return;
|
|
1359
|
-
}
|
|
1360
1755
|
try {
|
|
1361
|
-
await form.handleSubmit(
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
onSuccess?.(form.getValues());
|
|
1756
|
+
await form.handleSubmit(
|
|
1757
|
+
async (formData) => {
|
|
1758
|
+
await onSubmit(formData);
|
|
1759
|
+
enhancedState.handleSuccess(formData);
|
|
1760
|
+
},
|
|
1761
|
+
(errors) => {
|
|
1762
|
+
enhancedState.handleError("Please fix the validation errors above");
|
|
1763
|
+
}
|
|
1764
|
+
)();
|
|
1371
1765
|
} catch (error) {
|
|
1372
1766
|
const errorMessage = error instanceof Error ? error.message : "An error occurred";
|
|
1373
|
-
|
|
1374
|
-
error: errorMessage,
|
|
1375
|
-
isSubmitted: true,
|
|
1376
|
-
isSubmitting: false,
|
|
1377
|
-
isSuccess: false
|
|
1378
|
-
});
|
|
1379
|
-
onError?.({
|
|
1380
|
-
message: errorMessage
|
|
1381
|
-
});
|
|
1767
|
+
enhancedState.handleError(errorMessage);
|
|
1382
1768
|
}
|
|
1383
1769
|
};
|
|
1384
1770
|
const resetForm = () => {
|
|
1385
1771
|
form.reset();
|
|
1386
|
-
|
|
1387
|
-
error: void 0,
|
|
1388
|
-
isSubmitted: false,
|
|
1389
|
-
isSubmitting: false,
|
|
1390
|
-
isSuccess: false
|
|
1391
|
-
});
|
|
1772
|
+
enhancedState.reset();
|
|
1392
1773
|
};
|
|
1393
1774
|
const renderFields = () => {
|
|
1394
1775
|
if (layout === "grid") {
|
|
1395
|
-
return /* @__PURE__ */
|
|
1776
|
+
return /* @__PURE__ */ React20.createElement(
|
|
1396
1777
|
"div",
|
|
1397
1778
|
{
|
|
1398
1779
|
className: `grid gap-${spacing} ${columns === 1 ? "grid-cols-1" : columns === 2 ? "grid-cols-1 md:grid-cols-2" : "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"}`
|
|
1399
1780
|
},
|
|
1400
|
-
config.fields.map((
|
|
1781
|
+
config.fields.map((field2) => /* @__PURE__ */ React20.createElement(
|
|
1401
1782
|
FormField,
|
|
1402
1783
|
{
|
|
1403
|
-
key:
|
|
1404
|
-
config:
|
|
1784
|
+
key: field2.name,
|
|
1785
|
+
config: field2,
|
|
1405
1786
|
form,
|
|
1406
|
-
submissionState
|
|
1787
|
+
submissionState: {
|
|
1788
|
+
isSubmitting: enhancedState.isSubmitting,
|
|
1789
|
+
isSubmitted: enhancedState.status !== "idle",
|
|
1790
|
+
isSuccess: enhancedState.isSuccess,
|
|
1791
|
+
error: enhancedState.error
|
|
1792
|
+
}
|
|
1407
1793
|
}
|
|
1408
1794
|
))
|
|
1409
1795
|
);
|
|
1410
1796
|
}
|
|
1411
1797
|
if (layout === "horizontal") {
|
|
1412
|
-
return /* @__PURE__ */
|
|
1798
|
+
return /* @__PURE__ */ React20.createElement("div", { className: `grid gap-${spacing} grid-cols-1 md:grid-cols-2` }, config.fields.map((field2) => /* @__PURE__ */ React20.createElement(
|
|
1413
1799
|
FormField,
|
|
1414
1800
|
{
|
|
1415
|
-
key:
|
|
1416
|
-
config:
|
|
1801
|
+
key: field2.name,
|
|
1802
|
+
config: field2,
|
|
1417
1803
|
form,
|
|
1418
|
-
submissionState
|
|
1804
|
+
submissionState: {
|
|
1805
|
+
isSubmitting: enhancedState.isSubmitting,
|
|
1806
|
+
isSubmitted: enhancedState.status !== "idle",
|
|
1807
|
+
isSuccess: enhancedState.isSuccess,
|
|
1808
|
+
error: enhancedState.error
|
|
1809
|
+
}
|
|
1419
1810
|
}
|
|
1420
1811
|
)));
|
|
1421
1812
|
}
|
|
1422
|
-
return /* @__PURE__ */
|
|
1813
|
+
return /* @__PURE__ */ React20.createElement("div", { className: `space-y-${spacing}` }, config.fields.map((field2) => /* @__PURE__ */ React20.createElement(
|
|
1423
1814
|
FormField,
|
|
1424
1815
|
{
|
|
1425
|
-
key:
|
|
1426
|
-
config:
|
|
1816
|
+
key: field2.name,
|
|
1817
|
+
config: field2,
|
|
1427
1818
|
form,
|
|
1428
|
-
submissionState
|
|
1819
|
+
submissionState: {
|
|
1820
|
+
isSubmitting: enhancedState.isSubmitting,
|
|
1821
|
+
isSubmitted: enhancedState.status !== "idle",
|
|
1822
|
+
isSuccess: enhancedState.isSuccess,
|
|
1823
|
+
error: enhancedState.error
|
|
1824
|
+
}
|
|
1429
1825
|
}
|
|
1430
1826
|
)));
|
|
1431
1827
|
};
|
|
@@ -1433,7 +1829,7 @@ function ZodForm({
|
|
|
1433
1829
|
e.preventDefault();
|
|
1434
1830
|
void handleSubmit();
|
|
1435
1831
|
};
|
|
1436
|
-
|
|
1832
|
+
React20.useEffect(() => {
|
|
1437
1833
|
if (config.onError && Object.keys(form.formState.errors).length > 0) {
|
|
1438
1834
|
config.onError(form.formState.errors);
|
|
1439
1835
|
}
|
|
@@ -1442,42 +1838,33 @@ function ZodForm({
|
|
|
1442
1838
|
return render({
|
|
1443
1839
|
errors: form.formState.errors,
|
|
1444
1840
|
form,
|
|
1445
|
-
isSubmitted:
|
|
1446
|
-
isSubmitting:
|
|
1447
|
-
isSuccess:
|
|
1841
|
+
isSubmitted: enhancedState.status !== "idle",
|
|
1842
|
+
isSubmitting: enhancedState.isSubmitting,
|
|
1843
|
+
isSuccess: enhancedState.isSuccess,
|
|
1448
1844
|
values: form.getValues()
|
|
1449
1845
|
});
|
|
1450
1846
|
}
|
|
1451
|
-
return /* @__PURE__ */
|
|
1452
|
-
|
|
1453
|
-
{
|
|
1454
|
-
className: "mb-6 p-4 bg-success-50 border border-success-200 rounded-lg",
|
|
1455
|
-
"data-testid": "success-message"
|
|
1456
|
-
},
|
|
1457
|
-
/* @__PURE__ */ React16.createElement("p", { className: "text-success-800 font-medium" }, "Success!"),
|
|
1458
|
-
/* @__PURE__ */ React16.createElement("p", { className: "text-success-700 text-sm mt-1" }, "Your request has been processed successfully.")
|
|
1459
|
-
), submissionState.error && errorDisplay !== "none" && /* @__PURE__ */ React16.createElement(
|
|
1460
|
-
"div",
|
|
1847
|
+
return /* @__PURE__ */ React20.createElement("form", { className, role: "form", onSubmit: handleFormSubmit }, title && /* @__PURE__ */ React20.createElement("div", { className: "mb-6" }, /* @__PURE__ */ React20.createElement("h2", { className: "text-xl font-semibold text-foreground mb-2" }, title), subtitle && /* @__PURE__ */ React20.createElement("p", { className: "text-sm text-muted-foreground" }, subtitle)), /* @__PURE__ */ React20.createElement(
|
|
1848
|
+
FormStatus,
|
|
1461
1849
|
{
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
Button3,
|
|
1850
|
+
state: enhancedState,
|
|
1851
|
+
onDismiss: () => enhancedState.reset(),
|
|
1852
|
+
showDetails: true
|
|
1853
|
+
}
|
|
1854
|
+
), renderFields(), /* @__PURE__ */ React20.createElement("div", { className: "mt-6 flex gap-3 justify-end" }, /* @__PURE__ */ React20.createElement(
|
|
1855
|
+
Button5,
|
|
1469
1856
|
{
|
|
1470
1857
|
color: "primary",
|
|
1471
|
-
isDisabled:
|
|
1472
|
-
isLoading:
|
|
1858
|
+
isDisabled: enhancedState.isSubmitting,
|
|
1859
|
+
isLoading: enhancedState.isSubmitting,
|
|
1473
1860
|
type: "submit",
|
|
1474
1861
|
...submitButtonProps
|
|
1475
1862
|
},
|
|
1476
|
-
submitButtonText
|
|
1477
|
-
), showResetButton && /* @__PURE__ */
|
|
1478
|
-
|
|
1863
|
+
enhancedState.isSuccess ? "Success!" : submitButtonText
|
|
1864
|
+
), showResetButton && /* @__PURE__ */ React20.createElement(
|
|
1865
|
+
Button5,
|
|
1479
1866
|
{
|
|
1480
|
-
isDisabled:
|
|
1867
|
+
isDisabled: enhancedState.isSubmitting,
|
|
1481
1868
|
type: "button",
|
|
1482
1869
|
variant: "bordered",
|
|
1483
1870
|
onPress: resetForm
|
|
@@ -1485,14 +1872,1209 @@ function ZodForm({
|
|
|
1485
1872
|
resetButtonText
|
|
1486
1873
|
)));
|
|
1487
1874
|
}
|
|
1875
|
+
|
|
1876
|
+
// src/builders/BasicFormBuilder.ts
|
|
1877
|
+
var BasicFormBuilder = class {
|
|
1878
|
+
constructor() {
|
|
1879
|
+
this.fields = [];
|
|
1880
|
+
}
|
|
1881
|
+
/**
|
|
1882
|
+
* Add an input field
|
|
1883
|
+
*/
|
|
1884
|
+
input(name, label, type = "text") {
|
|
1885
|
+
this.fields.push({
|
|
1886
|
+
name,
|
|
1887
|
+
label,
|
|
1888
|
+
type: "input",
|
|
1889
|
+
inputProps: { type }
|
|
1890
|
+
});
|
|
1891
|
+
return this;
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1894
|
+
* Add a textarea field
|
|
1895
|
+
*/
|
|
1896
|
+
textarea(name, label, placeholder) {
|
|
1897
|
+
this.fields.push({
|
|
1898
|
+
name,
|
|
1899
|
+
label,
|
|
1900
|
+
type: "textarea",
|
|
1901
|
+
textareaProps: { placeholder }
|
|
1902
|
+
});
|
|
1903
|
+
return this;
|
|
1904
|
+
}
|
|
1905
|
+
/**
|
|
1906
|
+
* Add a select field
|
|
1907
|
+
*/
|
|
1908
|
+
select(name, label, options) {
|
|
1909
|
+
this.fields.push({
|
|
1910
|
+
name,
|
|
1911
|
+
label,
|
|
1912
|
+
type: "select",
|
|
1913
|
+
options
|
|
1914
|
+
});
|
|
1915
|
+
return this;
|
|
1916
|
+
}
|
|
1917
|
+
/**
|
|
1918
|
+
* Add a checkbox field
|
|
1919
|
+
*/
|
|
1920
|
+
checkbox(name, label) {
|
|
1921
|
+
this.fields.push({
|
|
1922
|
+
name,
|
|
1923
|
+
label,
|
|
1924
|
+
type: "checkbox"
|
|
1925
|
+
});
|
|
1926
|
+
return this;
|
|
1927
|
+
}
|
|
1928
|
+
/**
|
|
1929
|
+
* Add a switch field
|
|
1930
|
+
*/
|
|
1931
|
+
switch(name, label) {
|
|
1932
|
+
this.fields.push({
|
|
1933
|
+
name,
|
|
1934
|
+
label,
|
|
1935
|
+
type: "switch"
|
|
1936
|
+
});
|
|
1937
|
+
return this;
|
|
1938
|
+
}
|
|
1939
|
+
/**
|
|
1940
|
+
* Build the final field configuration array
|
|
1941
|
+
*/
|
|
1942
|
+
build() {
|
|
1943
|
+
return this.fields;
|
|
1944
|
+
}
|
|
1945
|
+
};
|
|
1946
|
+
function createBasicFormBuilder() {
|
|
1947
|
+
return new BasicFormBuilder();
|
|
1948
|
+
}
|
|
1949
|
+
var FormFieldHelpers = {
|
|
1950
|
+
/**
|
|
1951
|
+
* Create an input field
|
|
1952
|
+
*/
|
|
1953
|
+
input: (name, label, type = "text") => ({
|
|
1954
|
+
name,
|
|
1955
|
+
label,
|
|
1956
|
+
type: "input",
|
|
1957
|
+
inputProps: { type }
|
|
1958
|
+
}),
|
|
1959
|
+
/**
|
|
1960
|
+
* Create a textarea field
|
|
1961
|
+
*/
|
|
1962
|
+
textarea: (name, label, placeholder) => ({
|
|
1963
|
+
name,
|
|
1964
|
+
label,
|
|
1965
|
+
type: "textarea",
|
|
1966
|
+
textareaProps: { placeholder }
|
|
1967
|
+
}),
|
|
1968
|
+
/**
|
|
1969
|
+
* Create a select field
|
|
1970
|
+
*/
|
|
1971
|
+
select: (name, label, options) => ({
|
|
1972
|
+
name,
|
|
1973
|
+
label,
|
|
1974
|
+
type: "select",
|
|
1975
|
+
options
|
|
1976
|
+
}),
|
|
1977
|
+
/**
|
|
1978
|
+
* Create a checkbox field
|
|
1979
|
+
*/
|
|
1980
|
+
checkbox: (name, label) => ({
|
|
1981
|
+
name,
|
|
1982
|
+
label,
|
|
1983
|
+
type: "checkbox"
|
|
1984
|
+
}),
|
|
1985
|
+
/**
|
|
1986
|
+
* Create a switch field
|
|
1987
|
+
*/
|
|
1988
|
+
switch: (name, label) => ({
|
|
1989
|
+
name,
|
|
1990
|
+
label,
|
|
1991
|
+
type: "switch"
|
|
1992
|
+
})
|
|
1993
|
+
};
|
|
1994
|
+
var CommonFields = {
|
|
1995
|
+
/**
|
|
1996
|
+
* Personal information fields
|
|
1997
|
+
*/
|
|
1998
|
+
personal: () => [
|
|
1999
|
+
FormFieldHelpers.input("firstName", "First Name"),
|
|
2000
|
+
FormFieldHelpers.input("lastName", "Last Name"),
|
|
2001
|
+
FormFieldHelpers.input("email", "Email", "email"),
|
|
2002
|
+
FormFieldHelpers.input("phone", "Phone", "tel")
|
|
2003
|
+
],
|
|
2004
|
+
/**
|
|
2005
|
+
* Address fields
|
|
2006
|
+
*/
|
|
2007
|
+
address: () => [
|
|
2008
|
+
FormFieldHelpers.input("street", "Street Address"),
|
|
2009
|
+
FormFieldHelpers.input("city", "City"),
|
|
2010
|
+
FormFieldHelpers.input("state", "State/Province"),
|
|
2011
|
+
FormFieldHelpers.input("zipCode", "ZIP/Postal Code"),
|
|
2012
|
+
FormFieldHelpers.select(
|
|
2013
|
+
"country",
|
|
2014
|
+
"Country",
|
|
2015
|
+
[
|
|
2016
|
+
{ label: "Select a country", value: "" },
|
|
2017
|
+
{ label: "United States", value: "us" },
|
|
2018
|
+
{ label: "Canada", value: "ca" },
|
|
2019
|
+
{ label: "United Kingdom", value: "uk" },
|
|
2020
|
+
{ label: "Australia", value: "au" },
|
|
2021
|
+
{ label: "Germany", value: "de" },
|
|
2022
|
+
{ label: "France", value: "fr" }
|
|
2023
|
+
]
|
|
2024
|
+
)
|
|
2025
|
+
],
|
|
2026
|
+
/**
|
|
2027
|
+
* Terms and conditions fields
|
|
2028
|
+
*/
|
|
2029
|
+
terms: () => [
|
|
2030
|
+
FormFieldHelpers.checkbox("terms", "I agree to the terms and conditions"),
|
|
2031
|
+
FormFieldHelpers.checkbox("privacy", "I agree to the privacy policy"),
|
|
2032
|
+
FormFieldHelpers.checkbox("newsletter", "Subscribe to newsletter")
|
|
2033
|
+
]
|
|
2034
|
+
};
|
|
2035
|
+
|
|
2036
|
+
// src/builders/AdvancedFormBuilder.ts
|
|
2037
|
+
function inputField(name, label, props) {
|
|
2038
|
+
return {
|
|
2039
|
+
name,
|
|
2040
|
+
label,
|
|
2041
|
+
type: "input",
|
|
2042
|
+
...props && {
|
|
2043
|
+
inputProps: {
|
|
2044
|
+
type: props.type || "text",
|
|
2045
|
+
placeholder: props.placeholder,
|
|
2046
|
+
description: props.description,
|
|
2047
|
+
disabled: props.isDisabled,
|
|
2048
|
+
className: props.className
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
function textareaField(name, label, props) {
|
|
2054
|
+
return {
|
|
2055
|
+
name,
|
|
2056
|
+
label,
|
|
2057
|
+
type: "textarea",
|
|
2058
|
+
...props && {
|
|
2059
|
+
textareaProps: {
|
|
2060
|
+
placeholder: props.placeholder,
|
|
2061
|
+
description: props.description,
|
|
2062
|
+
disabled: props.isDisabled,
|
|
2063
|
+
className: props.className,
|
|
2064
|
+
rows: props.rows
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
};
|
|
2068
|
+
}
|
|
2069
|
+
function selectField(name, label, options, props) {
|
|
2070
|
+
return {
|
|
2071
|
+
name,
|
|
2072
|
+
label,
|
|
2073
|
+
type: "select",
|
|
2074
|
+
options
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
function checkboxField(name, label, props) {
|
|
2078
|
+
return {
|
|
2079
|
+
name,
|
|
2080
|
+
label,
|
|
2081
|
+
type: "checkbox",
|
|
2082
|
+
...props && {
|
|
2083
|
+
checkboxProps: {
|
|
2084
|
+
disabled: props.isDisabled,
|
|
2085
|
+
className: props.className
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2090
|
+
function switchField(name, label, props) {
|
|
2091
|
+
return {
|
|
2092
|
+
name,
|
|
2093
|
+
label,
|
|
2094
|
+
type: "switch",
|
|
2095
|
+
...props && {
|
|
2096
|
+
switchProps: {
|
|
2097
|
+
disabled: props.isDisabled,
|
|
2098
|
+
className: props.className
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
2103
|
+
function radioField(name, label, options, props) {
|
|
2104
|
+
return {
|
|
2105
|
+
name,
|
|
2106
|
+
label,
|
|
2107
|
+
type: "radio",
|
|
2108
|
+
radioOptions: options,
|
|
2109
|
+
...props && {
|
|
2110
|
+
radioProps: {
|
|
2111
|
+
isDisabled: props.isDisabled,
|
|
2112
|
+
className: props.className,
|
|
2113
|
+
orientation: props.orientation
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
function sliderField(name, label, props) {
|
|
2119
|
+
return {
|
|
2120
|
+
name,
|
|
2121
|
+
label,
|
|
2122
|
+
type: "slider",
|
|
2123
|
+
...props && {
|
|
2124
|
+
sliderProps: {
|
|
2125
|
+
min: props.min || 0,
|
|
2126
|
+
max: props.max || 100,
|
|
2127
|
+
step: props.step || 1,
|
|
2128
|
+
disabled: props.isDisabled || false,
|
|
2129
|
+
className: props.className || ""
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
function dateField(name, label, props) {
|
|
2135
|
+
return {
|
|
2136
|
+
name,
|
|
2137
|
+
label,
|
|
2138
|
+
type: "date",
|
|
2139
|
+
...props && {
|
|
2140
|
+
dateProps: {
|
|
2141
|
+
placeholder: props.placeholder || "",
|
|
2142
|
+
disabled: props.isDisabled || false,
|
|
2143
|
+
className: props.className || ""
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
function fileField(name, label, props) {
|
|
2149
|
+
return {
|
|
2150
|
+
name,
|
|
2151
|
+
label,
|
|
2152
|
+
type: "file",
|
|
2153
|
+
...props && {
|
|
2154
|
+
fileProps: {
|
|
2155
|
+
accept: props.accept || "",
|
|
2156
|
+
multiple: props.multiple || false,
|
|
2157
|
+
disabled: props.isDisabled || false,
|
|
2158
|
+
className: props.className || ""
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
};
|
|
2162
|
+
}
|
|
2163
|
+
function fontPickerField(name, label, props) {
|
|
2164
|
+
return {
|
|
2165
|
+
name,
|
|
2166
|
+
label,
|
|
2167
|
+
type: "fontPicker",
|
|
2168
|
+
...props && {
|
|
2169
|
+
fontPickerProps: {
|
|
2170
|
+
disabled: props.isDisabled || false,
|
|
2171
|
+
className: props.className || ""
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
};
|
|
2175
|
+
}
|
|
2176
|
+
function createField(type, name, label, optionsOrProps, props) {
|
|
2177
|
+
switch (type) {
|
|
2178
|
+
case "input":
|
|
2179
|
+
return inputField(name, label, optionsOrProps);
|
|
2180
|
+
case "textarea":
|
|
2181
|
+
return textareaField(name, label, optionsOrProps);
|
|
2182
|
+
case "select":
|
|
2183
|
+
return selectField(name, label, optionsOrProps, props);
|
|
2184
|
+
case "checkbox":
|
|
2185
|
+
return checkboxField(name, label, optionsOrProps);
|
|
2186
|
+
case "switch":
|
|
2187
|
+
return switchField(name, label, optionsOrProps);
|
|
2188
|
+
case "radio":
|
|
2189
|
+
return radioField(name, label, optionsOrProps, props);
|
|
2190
|
+
case "slider":
|
|
2191
|
+
return sliderField(name, label, optionsOrProps);
|
|
2192
|
+
case "date":
|
|
2193
|
+
return dateField(name, label, optionsOrProps);
|
|
2194
|
+
case "file":
|
|
2195
|
+
return fileField(name, label, optionsOrProps);
|
|
2196
|
+
case "fontPicker":
|
|
2197
|
+
return fontPickerField(name, label, optionsOrProps);
|
|
2198
|
+
default:
|
|
2199
|
+
throw new Error(`Unknown field type: ${type}`);
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
var AdvancedFieldBuilder = class {
|
|
2203
|
+
constructor() {
|
|
2204
|
+
this.fields = [];
|
|
2205
|
+
}
|
|
2206
|
+
field(type, name, label, optionsOrProps, props) {
|
|
2207
|
+
this.fields.push(createField(type, name, label, optionsOrProps, props));
|
|
2208
|
+
return this;
|
|
2209
|
+
}
|
|
2210
|
+
/**
|
|
2211
|
+
* Add a conditional field that shows/hides based on form data
|
|
2212
|
+
*/
|
|
2213
|
+
conditionalField(name, condition, field2) {
|
|
2214
|
+
this.fields.push({
|
|
2215
|
+
name,
|
|
2216
|
+
type: "conditional",
|
|
2217
|
+
condition,
|
|
2218
|
+
field: field2
|
|
2219
|
+
});
|
|
2220
|
+
return this;
|
|
2221
|
+
}
|
|
2222
|
+
/**
|
|
2223
|
+
* Add a field array for dynamic repeating field groups
|
|
2224
|
+
*/
|
|
2225
|
+
fieldArray(name, label, fields, options) {
|
|
2226
|
+
this.fields.push({
|
|
2227
|
+
name,
|
|
2228
|
+
label,
|
|
2229
|
+
type: "fieldArray",
|
|
2230
|
+
fields,
|
|
2231
|
+
min: options?.min,
|
|
2232
|
+
max: options?.max,
|
|
2233
|
+
addButtonText: options?.addButtonText,
|
|
2234
|
+
removeButtonText: options?.removeButtonText
|
|
2235
|
+
});
|
|
2236
|
+
return this;
|
|
2237
|
+
}
|
|
2238
|
+
/**
|
|
2239
|
+
* Add a dynamic section that shows/hides based on form data
|
|
2240
|
+
*/
|
|
2241
|
+
dynamicSection(name, condition, fields, options) {
|
|
2242
|
+
this.fields.push({
|
|
2243
|
+
name,
|
|
2244
|
+
type: "dynamicSection",
|
|
2245
|
+
condition,
|
|
2246
|
+
fields,
|
|
2247
|
+
title: options?.title,
|
|
2248
|
+
description: options?.description
|
|
2249
|
+
});
|
|
2250
|
+
return this;
|
|
2251
|
+
}
|
|
2252
|
+
/**
|
|
2253
|
+
* Build the final field configuration array
|
|
2254
|
+
*/
|
|
2255
|
+
build() {
|
|
2256
|
+
return this.fields;
|
|
2257
|
+
}
|
|
2258
|
+
};
|
|
2259
|
+
var FieldArrayItemBuilder = class {
|
|
2260
|
+
constructor() {
|
|
2261
|
+
this.fields = [];
|
|
2262
|
+
}
|
|
2263
|
+
field(type, name, label, optionsOrProps, props) {
|
|
2264
|
+
this.fields.push(createField(type, name, label, optionsOrProps, props));
|
|
2265
|
+
return this;
|
|
2266
|
+
}
|
|
2267
|
+
/**
|
|
2268
|
+
* Build the field array item configuration
|
|
2269
|
+
*/
|
|
2270
|
+
build() {
|
|
2271
|
+
return this.fields;
|
|
2272
|
+
}
|
|
2273
|
+
};
|
|
2274
|
+
function createFieldArrayItemBuilder() {
|
|
2275
|
+
return new FieldArrayItemBuilder();
|
|
2276
|
+
}
|
|
2277
|
+
var FieldArrayBuilder = class {
|
|
2278
|
+
constructor(arrayName) {
|
|
2279
|
+
this.arrayName = arrayName;
|
|
2280
|
+
this.fields = [];
|
|
2281
|
+
}
|
|
2282
|
+
field(type, name, label, optionsOrProps, props) {
|
|
2283
|
+
const fullPath = `${this.arrayName}.${name}`;
|
|
2284
|
+
const fieldConfig = createField(type, fullPath, label, optionsOrProps, props);
|
|
2285
|
+
this.fields.push(fieldConfig);
|
|
2286
|
+
return this;
|
|
2287
|
+
}
|
|
2288
|
+
build() {
|
|
2289
|
+
return this.fields;
|
|
2290
|
+
}
|
|
2291
|
+
};
|
|
2292
|
+
function createFieldArrayBuilder(arrayName) {
|
|
2293
|
+
return new FieldArrayBuilder(arrayName);
|
|
2294
|
+
}
|
|
2295
|
+
function createAdvancedBuilder() {
|
|
2296
|
+
return new AdvancedFieldBuilder();
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
// src/builders/TypeInferredBuilder.ts
|
|
2300
|
+
import { z as z3 } from "zod";
|
|
2301
|
+
var TypeInferredBuilder = class {
|
|
2302
|
+
constructor() {
|
|
2303
|
+
this.schemaFields = {};
|
|
2304
|
+
this.formFields = [];
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2307
|
+
* Add a text field
|
|
2308
|
+
*/
|
|
2309
|
+
text(name, label, options) {
|
|
2310
|
+
const { minLength, maxLength, pattern, ...fieldOptions } = options || {};
|
|
2311
|
+
let zodType = z3.string();
|
|
2312
|
+
if (minLength) zodType = zodType.min(minLength, `${label} must be at least ${minLength} characters`);
|
|
2313
|
+
if (maxLength) zodType = zodType.max(maxLength, `${label} must be no more than ${maxLength} characters`);
|
|
2314
|
+
if (pattern) zodType = zodType.regex(new RegExp(pattern), `${label} format is invalid`);
|
|
2315
|
+
this.schemaFields[name] = zodType;
|
|
2316
|
+
this.formFields.push({
|
|
2317
|
+
name,
|
|
2318
|
+
label,
|
|
2319
|
+
type: "input",
|
|
2320
|
+
inputProps: { type: "text", ...fieldOptions }
|
|
2321
|
+
});
|
|
2322
|
+
return this;
|
|
2323
|
+
}
|
|
2324
|
+
/**
|
|
2325
|
+
* Add an email field
|
|
2326
|
+
*/
|
|
2327
|
+
email(name, label, options) {
|
|
2328
|
+
this.schemaFields[name] = z3.string().email(`Please enter a valid email address`);
|
|
2329
|
+
this.formFields.push({
|
|
2330
|
+
name,
|
|
2331
|
+
label,
|
|
2332
|
+
type: "input",
|
|
2333
|
+
inputProps: { type: "email", ...options }
|
|
2334
|
+
});
|
|
2335
|
+
return this;
|
|
2336
|
+
}
|
|
2337
|
+
/**
|
|
2338
|
+
* Add a number field
|
|
2339
|
+
*/
|
|
2340
|
+
number(name, label, options) {
|
|
2341
|
+
const { min, max, step, ...fieldOptions } = options || {};
|
|
2342
|
+
let zodType = z3.number();
|
|
2343
|
+
if (min !== void 0) zodType = zodType.min(min, `${label} must be at least ${min}`);
|
|
2344
|
+
if (max !== void 0) zodType = zodType.max(max, `${label} must be no more than ${max}`);
|
|
2345
|
+
this.schemaFields[name] = zodType;
|
|
2346
|
+
this.formFields.push({
|
|
2347
|
+
name,
|
|
2348
|
+
label,
|
|
2349
|
+
type: "input",
|
|
2350
|
+
inputProps: { type: "number", min, max, step, ...fieldOptions }
|
|
2351
|
+
});
|
|
2352
|
+
return this;
|
|
2353
|
+
}
|
|
2354
|
+
/**
|
|
2355
|
+
* Add a textarea field
|
|
2356
|
+
*/
|
|
2357
|
+
textarea(name, label, options) {
|
|
2358
|
+
const { minLength, ...fieldOptions } = options || {};
|
|
2359
|
+
let zodType = z3.string();
|
|
2360
|
+
if (minLength) zodType = zodType.min(minLength, `${label} must be at least ${minLength} characters`);
|
|
2361
|
+
this.schemaFields[name] = zodType;
|
|
2362
|
+
this.formFields.push({
|
|
2363
|
+
name,
|
|
2364
|
+
label,
|
|
2365
|
+
type: "textarea",
|
|
2366
|
+
textareaProps: fieldOptions
|
|
2367
|
+
});
|
|
2368
|
+
return this;
|
|
2369
|
+
}
|
|
2370
|
+
/**
|
|
2371
|
+
* Add a select field
|
|
2372
|
+
*/
|
|
2373
|
+
select(name, label, options, fieldOptions) {
|
|
2374
|
+
this.schemaFields[name] = z3.string().min(1, `Please select a ${label.toLowerCase()}`);
|
|
2375
|
+
this.formFields.push({
|
|
2376
|
+
name,
|
|
2377
|
+
label,
|
|
2378
|
+
type: "select",
|
|
2379
|
+
options
|
|
2380
|
+
});
|
|
2381
|
+
return this;
|
|
2382
|
+
}
|
|
2383
|
+
/**
|
|
2384
|
+
* Add a checkbox field
|
|
2385
|
+
*/
|
|
2386
|
+
checkbox(name, label, options) {
|
|
2387
|
+
const { required = false, ...fieldOptions } = options || {};
|
|
2388
|
+
let zodType = z3.boolean();
|
|
2389
|
+
if (required) {
|
|
2390
|
+
zodType = zodType.refine((val) => val === true, `You must agree to ${label.toLowerCase()}`);
|
|
2391
|
+
}
|
|
2392
|
+
this.schemaFields[name] = zodType;
|
|
2393
|
+
this.formFields.push({
|
|
2394
|
+
name,
|
|
2395
|
+
label,
|
|
2396
|
+
type: "checkbox",
|
|
2397
|
+
checkboxProps: fieldOptions
|
|
2398
|
+
});
|
|
2399
|
+
return this;
|
|
2400
|
+
}
|
|
2401
|
+
/**
|
|
2402
|
+
* Add a switch field
|
|
2403
|
+
*/
|
|
2404
|
+
switch(name, label, options) {
|
|
2405
|
+
this.schemaFields[name] = z3.boolean().optional();
|
|
2406
|
+
this.formFields.push({
|
|
2407
|
+
name,
|
|
2408
|
+
label,
|
|
2409
|
+
type: "switch",
|
|
2410
|
+
switchProps: options
|
|
2411
|
+
});
|
|
2412
|
+
return this;
|
|
2413
|
+
}
|
|
2414
|
+
/**
|
|
2415
|
+
* Add a radio field
|
|
2416
|
+
*/
|
|
2417
|
+
radio(name, label, options, fieldOptions) {
|
|
2418
|
+
this.schemaFields[name] = z3.string().min(1, `Please select a ${label.toLowerCase()}`);
|
|
2419
|
+
this.formFields.push({
|
|
2420
|
+
name,
|
|
2421
|
+
label,
|
|
2422
|
+
type: "radio",
|
|
2423
|
+
radioOptions: options,
|
|
2424
|
+
radioProps: fieldOptions
|
|
2425
|
+
});
|
|
2426
|
+
return this;
|
|
2427
|
+
}
|
|
2428
|
+
/**
|
|
2429
|
+
* Add a slider field
|
|
2430
|
+
*/
|
|
2431
|
+
slider(name, label, options) {
|
|
2432
|
+
const { min = 0, max = 100, step = 1, ...fieldOptions } = options || {};
|
|
2433
|
+
let zodType = z3.number();
|
|
2434
|
+
if (min !== void 0) zodType = zodType.min(min, `${label} must be at least ${min}`);
|
|
2435
|
+
if (max !== void 0) zodType = zodType.max(max, `${label} must be no more than ${max}`);
|
|
2436
|
+
this.schemaFields[name] = zodType;
|
|
2437
|
+
this.formFields.push({
|
|
2438
|
+
name,
|
|
2439
|
+
label,
|
|
2440
|
+
type: "slider",
|
|
2441
|
+
sliderProps: { min, max, step, ...fieldOptions }
|
|
2442
|
+
});
|
|
2443
|
+
return this;
|
|
2444
|
+
}
|
|
2445
|
+
/**
|
|
2446
|
+
* Add a date field
|
|
2447
|
+
*/
|
|
2448
|
+
date(name, label, options) {
|
|
2449
|
+
this.schemaFields[name] = z3.string().min(1, `${label} is required`);
|
|
2450
|
+
this.formFields.push({
|
|
2451
|
+
name,
|
|
2452
|
+
label,
|
|
2453
|
+
type: "date",
|
|
2454
|
+
dateProps: options
|
|
2455
|
+
});
|
|
2456
|
+
return this;
|
|
2457
|
+
}
|
|
2458
|
+
/**
|
|
2459
|
+
* Add a file field
|
|
2460
|
+
*/
|
|
2461
|
+
file(name, label, options) {
|
|
2462
|
+
this.schemaFields[name] = z3.any().optional();
|
|
2463
|
+
this.formFields.push({
|
|
2464
|
+
name,
|
|
2465
|
+
label,
|
|
2466
|
+
type: "file",
|
|
2467
|
+
fileProps: options
|
|
2468
|
+
});
|
|
2469
|
+
return this;
|
|
2470
|
+
}
|
|
2471
|
+
/**
|
|
2472
|
+
* Build the final schema and fields
|
|
2473
|
+
*/
|
|
2474
|
+
build() {
|
|
2475
|
+
return {
|
|
2476
|
+
schema: z3.object(this.schemaFields),
|
|
2477
|
+
fields: this.formFields
|
|
2478
|
+
};
|
|
2479
|
+
}
|
|
2480
|
+
};
|
|
2481
|
+
function createTypeInferredBuilder() {
|
|
2482
|
+
return new TypeInferredBuilder();
|
|
2483
|
+
}
|
|
2484
|
+
function defineInferredForm(fieldDefinitions) {
|
|
2485
|
+
const builder = createTypeInferredBuilder();
|
|
2486
|
+
fieldDefinitions(builder);
|
|
2487
|
+
return builder.build();
|
|
2488
|
+
}
|
|
2489
|
+
var field = {
|
|
2490
|
+
text: (name, label, options) => {
|
|
2491
|
+
const builder = new TypeInferredBuilder();
|
|
2492
|
+
return builder.text(name, label, options);
|
|
2493
|
+
},
|
|
2494
|
+
email: (name, label, options) => {
|
|
2495
|
+
const builder = new TypeInferredBuilder();
|
|
2496
|
+
return builder.email(name, label, options);
|
|
2497
|
+
},
|
|
2498
|
+
number: (name, label, options) => {
|
|
2499
|
+
const builder = new TypeInferredBuilder();
|
|
2500
|
+
return builder.number(name, label, options);
|
|
2501
|
+
},
|
|
2502
|
+
textarea: (name, label, options) => {
|
|
2503
|
+
const builder = new TypeInferredBuilder();
|
|
2504
|
+
return builder.textarea(name, label, options);
|
|
2505
|
+
},
|
|
2506
|
+
select: (name, label, options, fieldOptions) => {
|
|
2507
|
+
const builder = new TypeInferredBuilder();
|
|
2508
|
+
return builder.select(name, label, options, fieldOptions);
|
|
2509
|
+
},
|
|
2510
|
+
checkbox: (name, label, options) => {
|
|
2511
|
+
const builder = new TypeInferredBuilder();
|
|
2512
|
+
return builder.checkbox(name, label, options);
|
|
2513
|
+
},
|
|
2514
|
+
switch: (name, label, options) => {
|
|
2515
|
+
const builder = new TypeInferredBuilder();
|
|
2516
|
+
return builder.switch(name, label, options);
|
|
2517
|
+
},
|
|
2518
|
+
radio: (name, label, options, fieldOptions) => {
|
|
2519
|
+
const builder = new TypeInferredBuilder();
|
|
2520
|
+
return builder.radio(name, label, options, fieldOptions);
|
|
2521
|
+
},
|
|
2522
|
+
slider: (name, label, options) => {
|
|
2523
|
+
const builder = new TypeInferredBuilder();
|
|
2524
|
+
return builder.slider(name, label, options);
|
|
2525
|
+
},
|
|
2526
|
+
date: (name, label, options) => {
|
|
2527
|
+
const builder = new TypeInferredBuilder();
|
|
2528
|
+
return builder.date(name, label, options);
|
|
2529
|
+
},
|
|
2530
|
+
file: (name, label, options) => {
|
|
2531
|
+
const builder = new TypeInferredBuilder();
|
|
2532
|
+
return builder.file(name, label, options);
|
|
2533
|
+
}
|
|
2534
|
+
};
|
|
2535
|
+
|
|
2536
|
+
// src/builders/NestedPathBuilder.ts
|
|
2537
|
+
var NestedPathBuilder = class {
|
|
2538
|
+
constructor() {
|
|
2539
|
+
this.fields = [];
|
|
2540
|
+
}
|
|
2541
|
+
/**
|
|
2542
|
+
* Create a nested object path builder
|
|
2543
|
+
* Usage: builder.nest("address").field("street", "Street Address")
|
|
2544
|
+
*/
|
|
2545
|
+
nest(path) {
|
|
2546
|
+
return new NestedObjectBuilder(this, path);
|
|
2547
|
+
}
|
|
2548
|
+
/**
|
|
2549
|
+
* Create a section-based path builder
|
|
2550
|
+
* Usage: builder.section("shipping").field("street", "Street Address")
|
|
2551
|
+
*/
|
|
2552
|
+
section(path) {
|
|
2553
|
+
return new SectionBuilder(this, path);
|
|
2554
|
+
}
|
|
2555
|
+
/**
|
|
2556
|
+
* Add a field with single path
|
|
2557
|
+
* Usage: builder.field("firstName", "First Name")
|
|
2558
|
+
*/
|
|
2559
|
+
field(name, label, type = "input", props) {
|
|
2560
|
+
this.fields.push({
|
|
2561
|
+
name,
|
|
2562
|
+
label,
|
|
2563
|
+
type,
|
|
2564
|
+
...props
|
|
2565
|
+
});
|
|
2566
|
+
return this;
|
|
2567
|
+
}
|
|
2568
|
+
/**
|
|
2569
|
+
* Add a field with path segments
|
|
2570
|
+
* Usage: builder.fieldPath(["user", "profile", "name"], "Full Name")
|
|
2571
|
+
*/
|
|
2572
|
+
fieldPath(path, label, type = "input", props) {
|
|
2573
|
+
const name = path.join(".");
|
|
2574
|
+
this.fields.push({
|
|
2575
|
+
name,
|
|
2576
|
+
label,
|
|
2577
|
+
type,
|
|
2578
|
+
...props
|
|
2579
|
+
});
|
|
2580
|
+
return this;
|
|
2581
|
+
}
|
|
2582
|
+
/**
|
|
2583
|
+
* Add a field with template literal path
|
|
2584
|
+
* Usage: builder.field`user.profile.name`("Full Name")
|
|
2585
|
+
*/
|
|
2586
|
+
fieldTemplate(path, ...args) {
|
|
2587
|
+
const pathString = path[0];
|
|
2588
|
+
return new FieldTemplateBuilder(this, pathString);
|
|
2589
|
+
}
|
|
2590
|
+
/**
|
|
2591
|
+
* Return to the parent builder (no-op for root builder)
|
|
2592
|
+
*/
|
|
2593
|
+
end() {
|
|
2594
|
+
return this;
|
|
2595
|
+
}
|
|
2596
|
+
build() {
|
|
2597
|
+
return this.fields;
|
|
2598
|
+
}
|
|
2599
|
+
};
|
|
2600
|
+
var NestedObjectBuilder = class _NestedObjectBuilder {
|
|
2601
|
+
constructor(parent, path) {
|
|
2602
|
+
this.parent = parent;
|
|
2603
|
+
this.path = path;
|
|
2604
|
+
}
|
|
2605
|
+
/**
|
|
2606
|
+
* Add a field to the current nested path
|
|
2607
|
+
*/
|
|
2608
|
+
field(fieldName, label, type = "input", props) {
|
|
2609
|
+
const fullPath = `${this.path}.${fieldName}`;
|
|
2610
|
+
this.parent.fields.push({
|
|
2611
|
+
name: fullPath,
|
|
2612
|
+
label,
|
|
2613
|
+
type,
|
|
2614
|
+
...props
|
|
2615
|
+
});
|
|
2616
|
+
return this;
|
|
2617
|
+
}
|
|
2618
|
+
/**
|
|
2619
|
+
* Nest deeper into the object
|
|
2620
|
+
*/
|
|
2621
|
+
nest(subPath) {
|
|
2622
|
+
return new _NestedObjectBuilder(this.parent, `${this.path}.${subPath}`);
|
|
2623
|
+
}
|
|
2624
|
+
/**
|
|
2625
|
+
* Return to the parent builder
|
|
2626
|
+
*/
|
|
2627
|
+
end() {
|
|
2628
|
+
return this.parent;
|
|
2629
|
+
}
|
|
2630
|
+
};
|
|
2631
|
+
var SectionBuilder = class {
|
|
2632
|
+
constructor(parent, path) {
|
|
2633
|
+
this.parent = parent;
|
|
2634
|
+
this.path = path;
|
|
2635
|
+
}
|
|
2636
|
+
/**
|
|
2637
|
+
* Add a field to the current section
|
|
2638
|
+
*/
|
|
2639
|
+
field(fieldName, label, type = "input", props) {
|
|
2640
|
+
const fullPath = `${this.path}.${fieldName}`;
|
|
2641
|
+
this.parent.fields.push({
|
|
2642
|
+
name: fullPath,
|
|
2643
|
+
label,
|
|
2644
|
+
type,
|
|
2645
|
+
...props
|
|
2646
|
+
});
|
|
2647
|
+
return this;
|
|
2648
|
+
}
|
|
2649
|
+
/**
|
|
2650
|
+
* Add multiple fields to the section
|
|
2651
|
+
*/
|
|
2652
|
+
fields(fieldDefinitions) {
|
|
2653
|
+
fieldDefinitions.forEach((field2) => {
|
|
2654
|
+
this.field(field2.name, field2.label, field2.type, field2.props);
|
|
2655
|
+
});
|
|
2656
|
+
return this;
|
|
2657
|
+
}
|
|
2658
|
+
/**
|
|
2659
|
+
* Nest deeper into the section
|
|
2660
|
+
*/
|
|
2661
|
+
nest(subPath) {
|
|
2662
|
+
return new NestedObjectBuilder(this.parent, `${this.path}.${subPath}`);
|
|
2663
|
+
}
|
|
2664
|
+
/**
|
|
2665
|
+
* Return to the parent builder
|
|
2666
|
+
*/
|
|
2667
|
+
end() {
|
|
2668
|
+
return this.parent;
|
|
2669
|
+
}
|
|
2670
|
+
};
|
|
2671
|
+
var FieldTemplateBuilder = class {
|
|
2672
|
+
constructor(parent, path) {
|
|
2673
|
+
this.parent = parent;
|
|
2674
|
+
this.path = path;
|
|
2675
|
+
}
|
|
2676
|
+
/**
|
|
2677
|
+
* Complete the field definition
|
|
2678
|
+
*/
|
|
2679
|
+
complete(label, type = "input", props) {
|
|
2680
|
+
this.parent.fields.push({
|
|
2681
|
+
name: this.path,
|
|
2682
|
+
label,
|
|
2683
|
+
type,
|
|
2684
|
+
...props
|
|
2685
|
+
});
|
|
2686
|
+
return this.parent;
|
|
2687
|
+
}
|
|
2688
|
+
};
|
|
2689
|
+
function createNestedPathBuilder() {
|
|
2690
|
+
return new NestedPathBuilder();
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
// src/hooks/useDebouncedValidation.ts
|
|
2694
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef } from "react";
|
|
2695
|
+
function useDebouncedValidation(form, options = {}) {
|
|
2696
|
+
const { delay = 300, fields, enabled = true } = options;
|
|
2697
|
+
const timeoutRef = useRef(void 0);
|
|
2698
|
+
const lastValuesRef = useRef({});
|
|
2699
|
+
const debouncedTrigger = useCallback2(() => {
|
|
2700
|
+
if (!enabled) return;
|
|
2701
|
+
if (timeoutRef.current) {
|
|
2702
|
+
clearTimeout(timeoutRef.current);
|
|
2703
|
+
timeoutRef.current = void 0;
|
|
2704
|
+
}
|
|
2705
|
+
timeoutRef.current = setTimeout(async () => {
|
|
2706
|
+
const currentValues = form.getValues();
|
|
2707
|
+
const lastValues = lastValuesRef.current;
|
|
2708
|
+
const hasChanges = fields ? fields.some((field2) => currentValues[field2] !== lastValues[field2]) : Object.keys(currentValues).some((key) => currentValues[key] !== lastValues[key]);
|
|
2709
|
+
if (hasChanges) {
|
|
2710
|
+
lastValuesRef.current = { ...currentValues };
|
|
2711
|
+
if (fields && fields.length > 0) {
|
|
2712
|
+
await form.trigger(fields);
|
|
2713
|
+
} else {
|
|
2714
|
+
await form.trigger();
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
}, delay);
|
|
2718
|
+
}, [form, delay, fields, enabled]);
|
|
2719
|
+
useEffect2(() => {
|
|
2720
|
+
return () => {
|
|
2721
|
+
if (timeoutRef.current) {
|
|
2722
|
+
clearTimeout(timeoutRef.current);
|
|
2723
|
+
timeoutRef.current = void 0;
|
|
2724
|
+
}
|
|
2725
|
+
};
|
|
2726
|
+
}, []);
|
|
2727
|
+
useEffect2(() => {
|
|
2728
|
+
if (form.formState.isSubmitSuccessful) {
|
|
2729
|
+
lastValuesRef.current = {};
|
|
2730
|
+
}
|
|
2731
|
+
}, [form.formState.isSubmitSuccessful]);
|
|
2732
|
+
return {
|
|
2733
|
+
debouncedTrigger,
|
|
2734
|
+
isDebouncing: !!timeoutRef.current
|
|
2735
|
+
};
|
|
2736
|
+
}
|
|
2737
|
+
function useDebouncedFieldValidation(form, fieldName, options = {}) {
|
|
2738
|
+
const { delay = 300, enabled = true } = options;
|
|
2739
|
+
const timeoutRef = useRef(void 0);
|
|
2740
|
+
const debouncedFieldTrigger = useCallback2(() => {
|
|
2741
|
+
if (!enabled) return;
|
|
2742
|
+
if (timeoutRef.current) {
|
|
2743
|
+
clearTimeout(timeoutRef.current);
|
|
2744
|
+
}
|
|
2745
|
+
timeoutRef.current = setTimeout(async () => {
|
|
2746
|
+
await form.trigger(fieldName);
|
|
2747
|
+
}, delay);
|
|
2748
|
+
}, [form, fieldName, delay, enabled]);
|
|
2749
|
+
useEffect2(() => {
|
|
2750
|
+
return () => {
|
|
2751
|
+
if (timeoutRef.current) {
|
|
2752
|
+
clearTimeout(timeoutRef.current);
|
|
2753
|
+
timeoutRef.current = void 0;
|
|
2754
|
+
}
|
|
2755
|
+
};
|
|
2756
|
+
}, []);
|
|
2757
|
+
return {
|
|
2758
|
+
debouncedFieldTrigger,
|
|
2759
|
+
isDebouncing: !!timeoutRef.current
|
|
2760
|
+
};
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
// src/hooks/useInferredForm.ts
|
|
2764
|
+
import { useForm as useForm3 } from "react-hook-form";
|
|
2765
|
+
var zodResolver;
|
|
2766
|
+
try {
|
|
2767
|
+
zodResolver = __require("@hookform/resolvers/zod").zodResolver;
|
|
2768
|
+
} catch {
|
|
2769
|
+
}
|
|
2770
|
+
function useInferredForm(schema, fields, options = {}) {
|
|
2771
|
+
const {
|
|
2772
|
+
defaultValues,
|
|
2773
|
+
mode = "onChange",
|
|
2774
|
+
reValidateMode = "onChange",
|
|
2775
|
+
shouldFocusError = true,
|
|
2776
|
+
shouldUnregister = false,
|
|
2777
|
+
delayError = 0
|
|
2778
|
+
} = options;
|
|
2779
|
+
return useForm3({
|
|
2780
|
+
resolver: zodResolver ? zodResolver(schema) : void 0,
|
|
2781
|
+
defaultValues,
|
|
2782
|
+
mode,
|
|
2783
|
+
reValidateMode,
|
|
2784
|
+
shouldFocusError,
|
|
2785
|
+
shouldUnregister,
|
|
2786
|
+
delayError
|
|
2787
|
+
});
|
|
2788
|
+
}
|
|
2789
|
+
function useTypeInferredForm(formConfig, options = {}) {
|
|
2790
|
+
return useInferredForm(formConfig.schema, formConfig.fields, options);
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
// src/utils/performance.ts
|
|
2794
|
+
import { useCallback as useCallback3, useMemo as useMemo2, useRef as useRef2 } from "react";
|
|
2795
|
+
function debounce(func, delay) {
|
|
2796
|
+
let timeoutId;
|
|
2797
|
+
return (...args) => {
|
|
2798
|
+
clearTimeout(timeoutId);
|
|
2799
|
+
timeoutId = setTimeout(() => func(...args), delay);
|
|
2800
|
+
};
|
|
2801
|
+
}
|
|
2802
|
+
function throttle(func, limit) {
|
|
2803
|
+
let inThrottle;
|
|
2804
|
+
return (...args) => {
|
|
2805
|
+
if (!inThrottle) {
|
|
2806
|
+
func(...args);
|
|
2807
|
+
inThrottle = true;
|
|
2808
|
+
setTimeout(() => inThrottle = false, limit);
|
|
2809
|
+
}
|
|
2810
|
+
};
|
|
2811
|
+
}
|
|
2812
|
+
function useMemoizedCallback(callback, deps) {
|
|
2813
|
+
const callbackRef = useRef2(callback);
|
|
2814
|
+
callbackRef.current = callback;
|
|
2815
|
+
return useCallback3(
|
|
2816
|
+
((...args) => callbackRef.current(...args)),
|
|
2817
|
+
deps
|
|
2818
|
+
);
|
|
2819
|
+
}
|
|
2820
|
+
function shallowEqual(prevProps, nextProps) {
|
|
2821
|
+
const prevKeys = Object.keys(prevProps);
|
|
2822
|
+
const nextKeys = Object.keys(nextProps);
|
|
2823
|
+
if (prevKeys.length !== nextKeys.length) {
|
|
2824
|
+
return false;
|
|
2825
|
+
}
|
|
2826
|
+
for (const key of prevKeys) {
|
|
2827
|
+
if (prevProps[key] !== nextProps[key]) {
|
|
2828
|
+
return false;
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
return true;
|
|
2832
|
+
}
|
|
2833
|
+
function deepEqual(prevProps, nextProps) {
|
|
2834
|
+
if (prevProps === nextProps) {
|
|
2835
|
+
return true;
|
|
2836
|
+
}
|
|
2837
|
+
if (typeof prevProps !== typeof nextProps) {
|
|
2838
|
+
return false;
|
|
2839
|
+
}
|
|
2840
|
+
if (typeof prevProps !== "object" || prevProps === null || nextProps === null) {
|
|
2841
|
+
return prevProps === nextProps;
|
|
2842
|
+
}
|
|
2843
|
+
const prevKeys = Object.keys(prevProps);
|
|
2844
|
+
const nextKeys = Object.keys(nextProps);
|
|
2845
|
+
if (prevKeys.length !== nextKeys.length) {
|
|
2846
|
+
return false;
|
|
2847
|
+
}
|
|
2848
|
+
for (const key of prevKeys) {
|
|
2849
|
+
if (!nextKeys.includes(key)) {
|
|
2850
|
+
return false;
|
|
2851
|
+
}
|
|
2852
|
+
if (!deepEqual(prevProps[key], nextProps[key])) {
|
|
2853
|
+
return false;
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
return true;
|
|
2857
|
+
}
|
|
2858
|
+
function usePerformanceMonitor(componentName, enabled = process.env.NODE_ENV === "development") {
|
|
2859
|
+
const renderCountRef = useRef2(0);
|
|
2860
|
+
const lastRenderTimeRef = useRef2(Date.now());
|
|
2861
|
+
if (enabled) {
|
|
2862
|
+
renderCountRef.current += 1;
|
|
2863
|
+
const now = Date.now();
|
|
2864
|
+
const timeSinceLastRender = now - lastRenderTimeRef.current;
|
|
2865
|
+
console.log(`[Performance] ${componentName}:`, {
|
|
2866
|
+
renderCount: renderCountRef.current,
|
|
2867
|
+
timeSinceLastRender: `${timeSinceLastRender}ms`
|
|
2868
|
+
});
|
|
2869
|
+
lastRenderTimeRef.current = now;
|
|
2870
|
+
}
|
|
2871
|
+
return {
|
|
2872
|
+
renderCount: renderCountRef.current,
|
|
2873
|
+
resetRenderCount: () => {
|
|
2874
|
+
renderCountRef.current = 0;
|
|
2875
|
+
}
|
|
2876
|
+
};
|
|
2877
|
+
}
|
|
2878
|
+
function createOptimizedFieldHandler(onChange, options = {}) {
|
|
2879
|
+
const { debounce: debounceMs, throttle: throttleMs, validate = false } = options;
|
|
2880
|
+
let handler = onChange;
|
|
2881
|
+
if (throttleMs) {
|
|
2882
|
+
handler = throttle(handler, throttleMs);
|
|
2883
|
+
}
|
|
2884
|
+
if (debounceMs) {
|
|
2885
|
+
handler = debounce(handler, debounceMs);
|
|
2886
|
+
}
|
|
2887
|
+
return handler;
|
|
2888
|
+
}
|
|
2889
|
+
function useMemoizedFieldProps(props, deps) {
|
|
2890
|
+
return useMemo2(() => props, deps);
|
|
2891
|
+
}
|
|
2892
|
+
function useBatchedFieldUpdates(form, fields) {
|
|
2893
|
+
const batchRef = useRef2({});
|
|
2894
|
+
const timeoutRef = useRef2(void 0);
|
|
2895
|
+
const batchUpdate = useCallback3((fieldName, value) => {
|
|
2896
|
+
batchRef.current[fieldName] = value;
|
|
2897
|
+
if (timeoutRef.current) {
|
|
2898
|
+
clearTimeout(timeoutRef.current);
|
|
2899
|
+
}
|
|
2900
|
+
timeoutRef.current = setTimeout(() => {
|
|
2901
|
+
Object.entries(batchRef.current).forEach(([key, val]) => {
|
|
2902
|
+
form.setValue(key, val);
|
|
2903
|
+
});
|
|
2904
|
+
batchRef.current = {};
|
|
2905
|
+
}, 16);
|
|
2906
|
+
}, [form]);
|
|
2907
|
+
return { batchUpdate };
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
// src/builders/validation-helpers.ts
|
|
2911
|
+
import { z as z4 } from "zod";
|
|
2912
|
+
var validationPatterns = {
|
|
2913
|
+
// Email validation
|
|
2914
|
+
email: z4.string().email("Please enter a valid email address"),
|
|
2915
|
+
// Phone number validation (US format)
|
|
2916
|
+
phoneUS: z4.string().regex(
|
|
2917
|
+
/^\(\d{3}\) \d{3}-\d{4}$/,
|
|
2918
|
+
"Please enter a valid phone number (XXX) XXX-XXXX"
|
|
2919
|
+
),
|
|
2920
|
+
// Phone number validation (international)
|
|
2921
|
+
phoneInternational: z4.string().regex(
|
|
2922
|
+
/^\+?[\d\s\-\(\)]+$/,
|
|
2923
|
+
"Please enter a valid phone number"
|
|
2924
|
+
),
|
|
2925
|
+
// URL validation
|
|
2926
|
+
url: z4.string().url("Please enter a valid URL"),
|
|
2927
|
+
// Password validation
|
|
2928
|
+
password: z4.string().min(8, "Password must be at least 8 characters").regex(/[A-Z]/, "Password must contain at least one uppercase letter").regex(/[a-z]/, "Password must contain at least one lowercase letter").regex(/[0-9]/, "Password must contain at least one number").regex(/[^A-Za-z0-9]/, "Password must contain at least one special character"),
|
|
2929
|
+
// Strong password validation
|
|
2930
|
+
strongPassword: z4.string().min(12, "Password must be at least 12 characters").regex(/[A-Z]/, "Password must contain at least one uppercase letter").regex(/[a-z]/, "Password must contain at least one lowercase letter").regex(/[0-9]/, "Password must contain at least one number").regex(/[^A-Za-z0-9]/, "Password must contain at least one special character"),
|
|
2931
|
+
// Credit card validation
|
|
2932
|
+
creditCard: z4.string().regex(
|
|
2933
|
+
/^[0-9]{4}[\s\-]?[0-9]{4}[\s\-]?[0-9]{4}[\s\-]?[0-9]{4}$/,
|
|
2934
|
+
"Please enter a valid credit card number"
|
|
2935
|
+
),
|
|
2936
|
+
// SSN validation
|
|
2937
|
+
ssn: z4.string().regex(
|
|
2938
|
+
/^\d{3}-\d{2}-\d{4}$/,
|
|
2939
|
+
"Please enter a valid SSN (XXX-XX-XXXX)"
|
|
2940
|
+
),
|
|
2941
|
+
// ZIP code validation
|
|
2942
|
+
zipCode: z4.string().regex(
|
|
2943
|
+
/^\d{5}(-\d{4})?$/,
|
|
2944
|
+
"Please enter a valid ZIP code"
|
|
2945
|
+
),
|
|
2946
|
+
// Date validation (MM/DD/YYYY)
|
|
2947
|
+
date: z4.string().regex(
|
|
2948
|
+
/^(0[1-9]|1[0-2])\/(0[1-9]|[12][0-9]|3[01])\/\d{4}$/,
|
|
2949
|
+
"Please enter a valid date (MM/DD/YYYY)"
|
|
2950
|
+
),
|
|
2951
|
+
// Time validation (HH:MM AM/PM)
|
|
2952
|
+
time: z4.string().regex(
|
|
2953
|
+
/^(0[1-9]|1[0-2]):[0-5][0-9] (AM|PM)$/i,
|
|
2954
|
+
"Please enter a valid time (HH:MM AM/PM)"
|
|
2955
|
+
)
|
|
2956
|
+
};
|
|
2957
|
+
var asyncValidation = {
|
|
2958
|
+
/**
|
|
2959
|
+
* Email availability check
|
|
2960
|
+
*/
|
|
2961
|
+
emailAvailability: async (email) => {
|
|
2962
|
+
return new Promise((resolve) => {
|
|
2963
|
+
setTimeout(() => {
|
|
2964
|
+
const takenEmails = ["test@example.com", "admin@example.com"];
|
|
2965
|
+
resolve(!takenEmails.includes(email));
|
|
2966
|
+
}, 1e3);
|
|
2967
|
+
});
|
|
2968
|
+
},
|
|
2969
|
+
/**
|
|
2970
|
+
* Username availability check
|
|
2971
|
+
*/
|
|
2972
|
+
usernameAvailability: async (username) => {
|
|
2973
|
+
return new Promise((resolve) => {
|
|
2974
|
+
setTimeout(() => {
|
|
2975
|
+
const takenUsernames = ["admin", "test", "user"];
|
|
2976
|
+
resolve(!takenUsernames.includes(username.toLowerCase()));
|
|
2977
|
+
}, 1e3);
|
|
2978
|
+
});
|
|
2979
|
+
}
|
|
2980
|
+
};
|
|
2981
|
+
var errorMessages = {
|
|
2982
|
+
required: (fieldName) => `${fieldName} is required`,
|
|
2983
|
+
minLength: (fieldName, min) => `${fieldName} must be at least ${min} characters`,
|
|
2984
|
+
maxLength: (fieldName, max) => `${fieldName} must be no more than ${max} characters`,
|
|
2985
|
+
min: (fieldName, min) => `${fieldName} must be at least ${min}`,
|
|
2986
|
+
max: (fieldName, max) => `${fieldName} must be no more than ${max}`,
|
|
2987
|
+
pattern: (fieldName) => `${fieldName} format is invalid`,
|
|
2988
|
+
email: () => "Please enter a valid email address",
|
|
2989
|
+
url: () => "Please enter a valid URL",
|
|
2990
|
+
phone: () => "Please enter a valid phone number",
|
|
2991
|
+
date: () => "Please enter a valid date",
|
|
2992
|
+
time: () => "Please enter a valid time"
|
|
2993
|
+
};
|
|
2994
|
+
var serverValidation = {
|
|
2995
|
+
/**
|
|
2996
|
+
* Apply server errors to form
|
|
2997
|
+
*/
|
|
2998
|
+
applyServerErrors: (errors, setError) => {
|
|
2999
|
+
Object.entries(errors).forEach(([field2, messages]) => {
|
|
3000
|
+
setError(field2, {
|
|
3001
|
+
type: "server",
|
|
3002
|
+
message: messages[0]
|
|
3003
|
+
// Use first error message
|
|
3004
|
+
});
|
|
3005
|
+
});
|
|
3006
|
+
},
|
|
3007
|
+
/**
|
|
3008
|
+
* Clear server errors
|
|
3009
|
+
*/
|
|
3010
|
+
clearServerErrors: (fields, clearErrors) => {
|
|
3011
|
+
fields.forEach((field2) => {
|
|
3012
|
+
clearErrors(field2, "server");
|
|
3013
|
+
});
|
|
3014
|
+
}
|
|
3015
|
+
};
|
|
3016
|
+
var validationUtils = {
|
|
3017
|
+
/**
|
|
3018
|
+
* Debounced validation
|
|
3019
|
+
*/
|
|
3020
|
+
debounceValidation: (fn, delay = 300) => {
|
|
3021
|
+
let timeoutId;
|
|
3022
|
+
return (...args) => {
|
|
3023
|
+
clearTimeout(timeoutId);
|
|
3024
|
+
timeoutId = setTimeout(() => fn(...args), delay);
|
|
3025
|
+
};
|
|
3026
|
+
},
|
|
3027
|
+
/**
|
|
3028
|
+
* Validate form data against schema
|
|
3029
|
+
*/
|
|
3030
|
+
validateForm: async (data, schema) => {
|
|
3031
|
+
try {
|
|
3032
|
+
await schema.parseAsync(data);
|
|
3033
|
+
return { success: true, errors: {} };
|
|
3034
|
+
} catch (error) {
|
|
3035
|
+
if (error instanceof z4.ZodError) {
|
|
3036
|
+
const errors = {};
|
|
3037
|
+
error.issues.forEach((err) => {
|
|
3038
|
+
const path = err.path.join(".");
|
|
3039
|
+
errors[path] = err.message;
|
|
3040
|
+
});
|
|
3041
|
+
return { success: false, errors };
|
|
3042
|
+
}
|
|
3043
|
+
throw error;
|
|
3044
|
+
}
|
|
3045
|
+
},
|
|
3046
|
+
/**
|
|
3047
|
+
* Get field error message
|
|
3048
|
+
*/
|
|
3049
|
+
getFieldError: (errors, field2) => {
|
|
3050
|
+
return errors[field2];
|
|
3051
|
+
},
|
|
3052
|
+
/**
|
|
3053
|
+
* Check if field has error
|
|
3054
|
+
*/
|
|
3055
|
+
hasFieldError: (errors, field2) => {
|
|
3056
|
+
return !!errors[field2];
|
|
3057
|
+
}
|
|
3058
|
+
};
|
|
1488
3059
|
export {
|
|
3060
|
+
AdvancedFieldBuilder,
|
|
3061
|
+
BasicFormBuilder,
|
|
1489
3062
|
CheckboxField,
|
|
3063
|
+
CommonFields,
|
|
3064
|
+
ConditionalField,
|
|
1490
3065
|
ConfigurableForm,
|
|
1491
3066
|
DateField,
|
|
3067
|
+
DynamicSectionField,
|
|
3068
|
+
FieldArrayBuilder,
|
|
3069
|
+
FieldArrayField,
|
|
3070
|
+
FieldArrayItemBuilder,
|
|
1492
3071
|
FileField,
|
|
1493
3072
|
FontPickerField,
|
|
1494
3073
|
FormField,
|
|
3074
|
+
FormFieldHelpers,
|
|
1495
3075
|
FormProvider,
|
|
3076
|
+
FormStatus,
|
|
3077
|
+
FormToast,
|
|
1496
3078
|
HeroHookFormProvider,
|
|
1497
3079
|
InputField,
|
|
1498
3080
|
RadioGroupField,
|
|
@@ -1501,13 +3083,20 @@ export {
|
|
|
1501
3083
|
SubmitButton,
|
|
1502
3084
|
SwitchField,
|
|
1503
3085
|
TextareaField,
|
|
3086
|
+
TypeInferredBuilder,
|
|
1504
3087
|
ZodForm,
|
|
1505
3088
|
applyServerErrors,
|
|
3089
|
+
asyncValidation,
|
|
1506
3090
|
commonValidations,
|
|
3091
|
+
createAdvancedBuilder,
|
|
3092
|
+
createBasicFormBuilder,
|
|
1507
3093
|
createConditionalSchema,
|
|
1508
3094
|
createConfirmPasswordSchema,
|
|
1509
3095
|
createDateSchema,
|
|
1510
3096
|
createEmailSchema,
|
|
3097
|
+
createField,
|
|
3098
|
+
createFieldArrayBuilder,
|
|
3099
|
+
createFieldArrayItemBuilder,
|
|
1511
3100
|
createFileSchema,
|
|
1512
3101
|
createFormTestUtils,
|
|
1513
3102
|
createFutureDateSchema,
|
|
@@ -1515,24 +3104,47 @@ export {
|
|
|
1515
3104
|
createMinLengthSchema,
|
|
1516
3105
|
createMockFormData,
|
|
1517
3106
|
createMockFormErrors,
|
|
3107
|
+
createNestedPathBuilder,
|
|
1518
3108
|
createNumberRangeSchema,
|
|
3109
|
+
createOptimizedFieldHandler,
|
|
1519
3110
|
createPasswordSchema,
|
|
1520
3111
|
createPastDateSchema,
|
|
1521
3112
|
createPhoneSchema,
|
|
1522
3113
|
createRequiredCheckboxSchema,
|
|
1523
3114
|
createRequiredSchema,
|
|
3115
|
+
createTypeInferredBuilder,
|
|
1524
3116
|
createUrlSchema,
|
|
1525
3117
|
createZodFormConfig,
|
|
3118
|
+
crossFieldValidation,
|
|
3119
|
+
debounce,
|
|
3120
|
+
deepEqual,
|
|
3121
|
+
defineInferredForm,
|
|
3122
|
+
errorMessages,
|
|
3123
|
+
field,
|
|
1526
3124
|
getFieldError,
|
|
1527
3125
|
getFormErrors,
|
|
1528
3126
|
hasFieldError,
|
|
1529
3127
|
hasFormErrors,
|
|
3128
|
+
serverValidation,
|
|
3129
|
+
shallowEqual,
|
|
1530
3130
|
simulateFieldInput,
|
|
1531
3131
|
simulateFormSubmission,
|
|
1532
|
-
|
|
3132
|
+
throttle,
|
|
3133
|
+
useBatchedFieldUpdates,
|
|
3134
|
+
useDebouncedFieldValidation,
|
|
3135
|
+
useDebouncedValidation,
|
|
3136
|
+
useEnhancedFormState,
|
|
3137
|
+
useFormContext5 as useFormContext,
|
|
1533
3138
|
useFormHelper,
|
|
1534
3139
|
useHeroForm,
|
|
1535
3140
|
useHeroHookFormDefaults,
|
|
3141
|
+
useInferredForm,
|
|
3142
|
+
useMemoizedCallback,
|
|
3143
|
+
useMemoizedFieldProps,
|
|
3144
|
+
usePerformanceMonitor,
|
|
3145
|
+
useTypeInferredForm,
|
|
1536
3146
|
useZodForm,
|
|
3147
|
+
validationPatterns,
|
|
3148
|
+
validationUtils,
|
|
1537
3149
|
waitForFormState
|
|
1538
3150
|
};
|