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