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