@rachelallyson/hero-hook-form 2.7.0 → 2.8.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.
@@ -1,4235 +0,0 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined") return require.apply(this, arguments);
5
- throw Error('Dynamic require of "' + x + '" is not supported');
6
- });
7
-
8
- // src/components/Form.tsx
9
- import React18 from "react";
10
- import { Button as Button3 } from "@heroui/react";
11
-
12
- // src/hooks/useFormHelper.ts
13
- import { useState } from "react";
14
- import { useForm } from "react-hook-form";
15
- function useFormHelper({
16
- defaultValues,
17
- methods,
18
- onError,
19
- onSubmit,
20
- onSuccess
21
- }) {
22
- const [submissionState, setSubmissionState] = useState({
23
- isSubmitted: false,
24
- isSubmitting: false,
25
- isSuccess: false
26
- });
27
- const form = methods ?? useForm({ defaultValues });
28
- const handleSubmit = async () => {
29
- setSubmissionState((prev) => ({
30
- ...prev,
31
- error: void 0,
32
- isSubmitting: true
33
- }));
34
- try {
35
- await form.handleSubmit(async (formData) => {
36
- await onSubmit(formData);
37
- })();
38
- setSubmissionState({
39
- isSubmitted: true,
40
- isSubmitting: false,
41
- isSuccess: true
42
- });
43
- onSuccess?.(form.getValues());
44
- } catch (error) {
45
- const errorMessage = error instanceof Error ? error.message : "An error occurred";
46
- setSubmissionState({
47
- error: errorMessage,
48
- isSubmitted: true,
49
- isSubmitting: false,
50
- isSuccess: false
51
- });
52
- onError?.({
53
- message: errorMessage
54
- });
55
- }
56
- };
57
- const resetForm = () => {
58
- form.reset();
59
- setSubmissionState({
60
- isSubmitted: false,
61
- isSubmitting: false,
62
- isSuccess: false
63
- });
64
- };
65
- return {
66
- error: submissionState.error,
67
- form,
68
- handleSubmit,
69
- isSubmitted: submissionState.isSubmitted,
70
- isSubmitting: submissionState.isSubmitting,
71
- isSuccess: submissionState.isSuccess,
72
- resetForm,
73
- submissionState
74
- };
75
- }
76
-
77
- // src/components/FormField.tsx
78
- import React17 from "react";
79
- import { get, useWatch as useWatch3 } from "react-hook-form";
80
-
81
- // src/fields/AutocompleteField.tsx
82
- import React from "react";
83
- import { Controller } from "react-hook-form";
84
-
85
- // src/ui/react.ts
86
- import {
87
- Autocomplete,
88
- AutocompleteItem,
89
- Button,
90
- Checkbox,
91
- DateInput,
92
- DatePicker,
93
- Input,
94
- Radio,
95
- RadioGroup,
96
- Select,
97
- SelectItem,
98
- Slider,
99
- Spinner,
100
- Switch,
101
- Textarea
102
- } from "@heroui/react";
103
-
104
- // src/fields/AutocompleteField.tsx
105
- function AutocompleteField(props) {
106
- const {
107
- autocompleteProps,
108
- children,
109
- className,
110
- control,
111
- description,
112
- isDisabled,
113
- items,
114
- label,
115
- name,
116
- placeholder,
117
- rules
118
- } = props;
119
- return /* @__PURE__ */ React.createElement(
120
- Controller,
121
- {
122
- control,
123
- name,
124
- render: ({ field: field2, fieldState }) => {
125
- const selectedKey = field2.value;
126
- const hasSelectedValue = selectedKey != null && selectedKey !== "";
127
- const allowsCustomValue = autocompleteProps?.allowsCustomValue ?? false;
128
- const shouldShowInputValue = allowsCustomValue || !hasSelectedValue;
129
- return /* @__PURE__ */ React.createElement("div", { className }, /* @__PURE__ */ React.createElement(
130
- Autocomplete,
131
- {
132
- ...autocompleteProps,
133
- description,
134
- errorMessage: fieldState.error?.message,
135
- isDisabled,
136
- isInvalid: Boolean(fieldState.error),
137
- label,
138
- placeholder,
139
- selectedKey: allowsCustomValue ? void 0 : hasSelectedValue ? String(selectedKey) : void 0,
140
- inputValue: shouldShowInputValue ? field2.value ?? "" : void 0,
141
- onSelectionChange: (key) => {
142
- const next = key ?? "";
143
- field2.onChange(next);
144
- },
145
- onInputChange: (value) => {
146
- if (allowsCustomValue) {
147
- field2.onChange(value);
148
- }
149
- },
150
- items
151
- },
152
- children ? children : (item) => /* @__PURE__ */ React.createElement(
153
- AutocompleteItem,
154
- {
155
- key: String(item.value),
156
- textValue: String(item.value),
157
- description: item.description,
158
- isDisabled: item.disabled
159
- },
160
- item.label
161
- )
162
- ));
163
- },
164
- rules
165
- }
166
- );
167
- }
168
-
169
- // src/fields/CheckboxField.tsx
170
- import React3 from "react";
171
- import { Controller as Controller2 } from "react-hook-form";
172
-
173
- // src/providers/ConfigProvider.tsx
174
- import React2, { createContext, useContext, useMemo } from "react";
175
- var DefaultsContext = createContext(null);
176
- function HeroHookFormProvider(props) {
177
- const value = useMemo(() => props.defaults ?? {}, [props.defaults]);
178
- return /* @__PURE__ */ React2.createElement(DefaultsContext.Provider, { value }, props.children);
179
- }
180
- function useHeroHookFormDefaults() {
181
- const cfg = useContext(DefaultsContext) ?? {};
182
- const common = cfg.common ?? {};
183
- const commonInput = {
184
- ...common.color !== void 0 ? { color: common.color } : {},
185
- ...common.size !== void 0 ? { size: common.size } : {},
186
- ...common.variant !== void 0 ? { variant: common.variant } : {},
187
- ...common.radius !== void 0 ? { radius: common.radius } : {},
188
- ...common.labelPlacement !== void 0 ? {
189
- labelPlacement: common.labelPlacement
190
- } : {}
191
- };
192
- const commonTextarea = {
193
- ...common.color !== void 0 ? { color: common.color } : {},
194
- ...common.size !== void 0 ? { size: common.size } : {},
195
- ...common.variant !== void 0 ? { variant: common.variant } : {},
196
- ...common.radius !== void 0 ? { radius: common.radius } : {},
197
- ...common.labelPlacement !== void 0 ? {
198
- labelPlacement: common.labelPlacement
199
- } : {}
200
- };
201
- const commonSelect = {
202
- ...common.color !== void 0 ? { color: common.color } : {},
203
- ...common.size !== void 0 ? { size: common.size } : {},
204
- ...common.variant !== void 0 ? { variant: common.variant } : {},
205
- ...common.radius !== void 0 ? { radius: common.radius } : {},
206
- ...common.labelPlacement !== void 0 ? {
207
- labelPlacement: common.labelPlacement
208
- } : {}
209
- };
210
- const commonCheckbox = {
211
- ...common.color !== void 0 ? {
212
- color: common.color
213
- } : {},
214
- ...common.size !== void 0 ? { size: common.size } : {}
215
- };
216
- const commonRadioGroup = {
217
- ...common.color !== void 0 ? {
218
- color: common.color
219
- } : {},
220
- ...common.size !== void 0 ? { size: common.size } : {}
221
- };
222
- const commonDateInput = {
223
- ...common.color !== void 0 ? {
224
- color: common.color
225
- } : {},
226
- ...common.size !== void 0 ? { size: common.size } : {},
227
- ...common.variant !== void 0 ? {
228
- variant: common.variant
229
- } : {},
230
- ...common.radius !== void 0 ? {
231
- radius: common.radius
232
- } : {}
233
- };
234
- const commonSlider = {
235
- ...common.color !== void 0 ? { color: common.color } : {},
236
- ...common.size !== void 0 ? { size: common.size } : {}
237
- };
238
- const commonSwitch = {
239
- ...common.color !== void 0 ? { color: common.color } : {},
240
- ...common.size !== void 0 ? { size: common.size } : {}
241
- };
242
- const commonButton = {
243
- ...common.color !== void 0 ? { color: common.color } : {},
244
- ...common.size !== void 0 ? { size: common.size } : {}
245
- };
246
- return {
247
- checkbox: { ...commonCheckbox, ...cfg.checkbox ?? {} },
248
- dateInput: { ...commonDateInput, ...cfg.dateInput ?? {} },
249
- input: { ...commonInput, ...cfg.input ?? {} },
250
- radioGroup: { ...commonRadioGroup, ...cfg.radioGroup ?? {} },
251
- select: { ...commonSelect, ...cfg.select ?? {} },
252
- slider: { ...commonSlider, ...cfg.slider ?? {} },
253
- submitButton: { ...commonButton, ...cfg.submitButton ?? {} },
254
- switch: { ...commonSwitch, ...cfg.switch ?? {} },
255
- textarea: { ...commonTextarea, ...cfg.textarea ?? {} }
256
- };
257
- }
258
-
259
- // src/fields/CheckboxField.tsx
260
- function CheckboxField(props) {
261
- const {
262
- checkboxProps,
263
- className,
264
- control,
265
- description,
266
- isDisabled,
267
- label,
268
- name,
269
- rules
270
- } = props;
271
- const defaults = useHeroHookFormDefaults();
272
- return /* @__PURE__ */ React3.createElement(
273
- Controller2,
274
- {
275
- control,
276
- name,
277
- render: ({ field: field2, fieldState }) => /* @__PURE__ */ React3.createElement("div", { className }, /* @__PURE__ */ React3.createElement(
278
- Checkbox,
279
- {
280
- ...defaults.checkbox,
281
- ...checkboxProps,
282
- isDisabled,
283
- isInvalid: Boolean(fieldState.error),
284
- isSelected: Boolean(field2.value),
285
- onBlur: field2.onBlur,
286
- onValueChange: (val) => field2.onChange(val)
287
- },
288
- label
289
- ), description ? /* @__PURE__ */ React3.createElement("p", { className: "text-small text-default-400" }, description) : null, fieldState.error?.message ? /* @__PURE__ */ React3.createElement("p", { className: "text-tiny text-danger mt-1" }, fieldState.error.message) : null),
290
- rules
291
- }
292
- );
293
- }
294
-
295
- // src/fields/ConditionalField.tsx
296
- import React4 from "react";
297
- import { useWatch, useFormContext } from "react-hook-form";
298
- function ConditionalField({
299
- className,
300
- config,
301
- control
302
- }) {
303
- const { condition, field: field2 } = config;
304
- const form = useFormContext();
305
- const formValues = useWatch({ control });
306
- const shouldShow = condition(formValues);
307
- if (!shouldShow) {
308
- return null;
309
- }
310
- return /* @__PURE__ */ React4.createElement("div", { className }, /* @__PURE__ */ React4.createElement(
311
- FormField,
312
- {
313
- config: field2,
314
- form,
315
- submissionState: {
316
- error: void 0,
317
- isSubmitted: false,
318
- isSubmitting: false,
319
- isSuccess: false
320
- }
321
- }
322
- ));
323
- }
324
-
325
- // src/fields/ContentField.tsx
326
- import React5 from "react";
327
- function ContentField({
328
- config,
329
- form,
330
- submissionState
331
- }) {
332
- if (config.render) {
333
- return /* @__PURE__ */ React5.createElement("div", { className: config.className }, config.render({
334
- errors: form.formState.errors,
335
- form,
336
- isSubmitting: submissionState.isSubmitting
337
- }));
338
- }
339
- return /* @__PURE__ */ React5.createElement("div", { className: config.className }, config.title && /* @__PURE__ */ React5.createElement("h3", { className: "text-lg font-semibold text-foreground mb-2" }, config.title), config.description && /* @__PURE__ */ React5.createElement("p", { className: "text-sm text-muted-foreground" }, config.description));
340
- }
341
-
342
- // src/fields/DateField.tsx
343
- import React6 from "react";
344
- import { Controller as Controller3 } from "react-hook-form";
345
- function CoercedDateInput(props) {
346
- const { dateProps, description, disabled, errorMessage, field: field2, label } = props;
347
- const defaults = useHeroHookFormDefaults();
348
- return /* @__PURE__ */ React6.createElement(
349
- DateInput,
350
- {
351
- ...defaults.dateInput,
352
- ...dateProps,
353
- description,
354
- errorMessage,
355
- isDisabled: disabled,
356
- isInvalid: Boolean(errorMessage),
357
- label,
358
- value: field2.value ?? null,
359
- onBlur: field2.onBlur,
360
- onChange: field2.onChange
361
- }
362
- );
363
- }
364
- function DateField(props) {
365
- const {
366
- className,
367
- control,
368
- dateProps,
369
- description,
370
- isDisabled,
371
- label,
372
- name,
373
- rules,
374
- transform
375
- } = props;
376
- return /* @__PURE__ */ React6.createElement(
377
- Controller3,
378
- {
379
- control,
380
- name,
381
- render: ({ field: field2, fieldState }) => /* @__PURE__ */ React6.createElement("div", { className }, /* @__PURE__ */ React6.createElement(
382
- CoercedDateInput,
383
- {
384
- dateProps,
385
- description,
386
- disabled: isDisabled,
387
- errorMessage: fieldState.error?.message,
388
- field: {
389
- ...field2,
390
- onChange: (value) => field2.onChange(transform ? transform(value) : value)
391
- },
392
- label
393
- }
394
- )),
395
- rules
396
- }
397
- );
398
- }
399
-
400
- // src/fields/DynamicSectionField.tsx
401
- import React7 from "react";
402
- import { useWatch as useWatch2, useFormContext as useFormContext2 } from "react-hook-form";
403
- function DynamicSectionField({
404
- className,
405
- config,
406
- control
407
- }) {
408
- const { condition, description, fields, title } = config;
409
- const form = useFormContext2();
410
- const formValues = useWatch2({ control });
411
- const shouldShow = condition(formValues);
412
- if (!shouldShow) {
413
- return null;
414
- }
415
- return /* @__PURE__ */ React7.createElement("div", { className }, (title || description) && /* @__PURE__ */ React7.createElement("div", { className: "mb-6" }, title && /* @__PURE__ */ React7.createElement("h3", { className: "text-lg font-semibold text-gray-900 mb-2" }, title), description && /* @__PURE__ */ React7.createElement("p", { className: "text-sm text-gray-600" }, description)), /* @__PURE__ */ React7.createElement("div", { className: "space-y-4" }, fields.map((fieldConfig, index) => /* @__PURE__ */ React7.createElement(
416
- FormField,
417
- {
418
- key: `${fieldConfig.name}-${index}`,
419
- config: fieldConfig,
420
- form,
421
- submissionState: {
422
- error: void 0,
423
- isSubmitted: false,
424
- isSubmitting: false,
425
- isSuccess: false
426
- }
427
- }
428
- ))));
429
- }
430
-
431
- // src/fields/FieldArrayField.tsx
432
- import React8 from "react";
433
- import { useFieldArray, useFormContext as useFormContext3 } from "react-hook-form";
434
- import { Button as Button2 } from "@heroui/react";
435
- function FieldArrayField({
436
- className,
437
- config
438
- }) {
439
- const {
440
- addButtonText = "Add Item",
441
- defaultItem,
442
- enableReordering = false,
443
- fields: fieldConfigs,
444
- max = 10,
445
- min = 0,
446
- name,
447
- removeButtonText = "Remove",
448
- renderAddButton,
449
- renderItem,
450
- reorderButtonText = { down: "\u2193", up: "\u2191" }
451
- } = config;
452
- const form = useFormContext3();
453
- if (!form || !form.control) {
454
- return null;
455
- }
456
- const { control } = form;
457
- const { append, fields, move, remove } = useFieldArray({
458
- control,
459
- name
460
- // FieldArray name
461
- });
462
- const canAdd = fields.length < max;
463
- const canRemove = fields.length > min;
464
- const handleAdd = () => {
465
- if (canAdd) {
466
- if (defaultItem) {
467
- append(defaultItem());
468
- } else {
469
- const defaultValues = fieldConfigs.reduce((acc, fieldConfig) => {
470
- const fieldName = fieldConfig.name;
471
- if (fieldConfig.type === "checkbox" || fieldConfig.type === "switch") {
472
- acc[fieldName] = false;
473
- } else if (fieldConfig.type === "slider") {
474
- acc[fieldName] = 0;
475
- } else {
476
- acc[fieldName] = "";
477
- }
478
- return acc;
479
- }, {});
480
- append(defaultValues);
481
- }
482
- }
483
- };
484
- const handleRemove = (index) => {
485
- if (canRemove) {
486
- remove(index);
487
- }
488
- };
489
- const handleMoveUp = (index) => {
490
- if (index > 0) {
491
- move(index, index - 1);
492
- }
493
- };
494
- const handleMoveDown = (index) => {
495
- if (index < fields.length - 1) {
496
- move(index, index + 1);
497
- }
498
- };
499
- const renderFieldArrayItems = () => {
500
- return fields.map((field2, index) => {
501
- const canMoveUp = enableReordering && index > 0;
502
- const canMoveDown = enableReordering && index < fields.length - 1;
503
- const itemCanRemove = canRemove;
504
- const fieldElements = fieldConfigs.map((fieldConfig) => {
505
- const fieldName = fieldConfig.name;
506
- const fullPath = `${name}.${index}.${fieldName}`;
507
- let processedConfig = { ...fieldConfig, name: fullPath };
508
- if ("dependsOn" in fieldConfig && fieldConfig.dependsOn && typeof fieldConfig.dependsOn === "string") {
509
- const dependsOnPath = fieldConfig.dependsOn;
510
- if (!dependsOnPath.startsWith(`${name}.`)) {
511
- processedConfig = {
512
- ...processedConfig,
513
- dependsOn: `${name}.${index}.${dependsOnPath}`,
514
- // Preserve dependsOnValue if it exists
515
- ..."dependsOnValue" in fieldConfig && {
516
- dependsOnValue: fieldConfig.dependsOnValue
517
- }
518
- };
519
- }
520
- }
521
- return /* @__PURE__ */ React8.createElement(
522
- FormField,
523
- {
524
- key: `${fieldConfig.name}-${index}`,
525
- config: processedConfig,
526
- form,
527
- submissionState: {
528
- error: void 0,
529
- isSubmitted: false,
530
- isSubmitting: false,
531
- isSuccess: false
532
- }
533
- }
534
- );
535
- });
536
- if (renderItem) {
537
- return /* @__PURE__ */ React8.createElement(React8.Fragment, { key: field2.id }, renderItem({
538
- canMoveDown,
539
- canMoveUp,
540
- canRemove: itemCanRemove,
541
- children: /* @__PURE__ */ React8.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, fieldElements),
542
- field: field2,
543
- fields,
544
- index,
545
- onMoveDown: () => handleMoveDown(index),
546
- onMoveUp: () => handleMoveUp(index),
547
- onRemove: () => handleRemove(index)
548
- }));
549
- }
550
- return /* @__PURE__ */ React8.createElement(
551
- "div",
552
- {
553
- key: field2.id,
554
- className: "border border-gray-200 rounded-lg p-4 space-y-4"
555
- },
556
- /* @__PURE__ */ React8.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React8.createElement("h4", { className: "text-sm font-medium text-gray-700" }, config.label, " #", index + 1), /* @__PURE__ */ React8.createElement("div", { className: "flex gap-2" }, enableReordering && /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(
557
- Button2,
558
- {
559
- size: "sm",
560
- variant: "light",
561
- isDisabled: !canMoveUp,
562
- onPress: () => handleMoveUp(index),
563
- "aria-label": `Move ${config.label} ${index + 1} up`
564
- },
565
- reorderButtonText.up
566
- ), /* @__PURE__ */ React8.createElement(
567
- Button2,
568
- {
569
- size: "sm",
570
- variant: "light",
571
- isDisabled: !canMoveDown,
572
- onPress: () => handleMoveDown(index),
573
- "aria-label": `Move ${config.label} ${index + 1} down`
574
- },
575
- reorderButtonText.down
576
- )), itemCanRemove && /* @__PURE__ */ React8.createElement(
577
- Button2,
578
- {
579
- size: "sm",
580
- variant: "light",
581
- color: "danger",
582
- startContent: "\u{1F5D1}\uFE0F",
583
- onPress: () => handleRemove(index),
584
- "aria-label": `${removeButtonText} ${config.label} ${index + 1}`
585
- },
586
- removeButtonText
587
- ))),
588
- /* @__PURE__ */ React8.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, fieldElements)
589
- );
590
- });
591
- };
592
- const renderAddButtonElement = () => {
593
- if (renderAddButton) {
594
- return renderAddButton({
595
- canAdd,
596
- onAdd: handleAdd
597
- });
598
- }
599
- if (!canAdd) {
600
- return null;
601
- }
602
- return /* @__PURE__ */ React8.createElement(
603
- Button2,
604
- {
605
- variant: "bordered",
606
- startContent: "\u2795",
607
- onPress: handleAdd,
608
- className: "w-full"
609
- },
610
- addButtonText
611
- );
612
- };
613
- return /* @__PURE__ */ React8.createElement("div", { className }, /* @__PURE__ */ React8.createElement("div", { className: "space-y-4" }, fields.length > 0 ? renderFieldArrayItems() : /* @__PURE__ */ React8.createElement("div", { className: "text-center py-8 text-gray-500" }, /* @__PURE__ */ React8.createElement("p", null, "No ", config.label?.toLowerCase(), " added yet."), renderAddButtonElement()), fields.length > 0 && renderAddButtonElement()));
614
- }
615
-
616
- // src/fields/FileField.tsx
617
- import React9 from "react";
618
- import { Controller as Controller4 } from "react-hook-form";
619
- function CoercedFileInput(props) {
620
- const {
621
- accept,
622
- description,
623
- disabled,
624
- errorMessage,
625
- field: field2,
626
- fileProps,
627
- label,
628
- multiple
629
- } = props;
630
- const defaults = useHeroHookFormDefaults();
631
- return /* @__PURE__ */ React9.createElement(
632
- Input,
633
- {
634
- ...defaults.input,
635
- ...fileProps,
636
- accept,
637
- description,
638
- errorMessage,
639
- isDisabled: disabled,
640
- isInvalid: Boolean(errorMessage),
641
- label,
642
- multiple,
643
- type: "file",
644
- value: field2.value ? "" : "",
645
- onBlur: field2.onBlur,
646
- onChange: (e) => {
647
- const target = e.target;
648
- field2.onChange(target.files);
649
- }
650
- }
651
- );
652
- }
653
- function FileField(props) {
654
- const {
655
- accept,
656
- className,
657
- control,
658
- description,
659
- fileProps,
660
- isDisabled,
661
- label,
662
- multiple = false,
663
- name,
664
- rules,
665
- transform
666
- } = props;
667
- return /* @__PURE__ */ React9.createElement(
668
- Controller4,
669
- {
670
- control,
671
- name,
672
- render: ({ field: field2, fieldState }) => /* @__PURE__ */ React9.createElement("div", { className }, /* @__PURE__ */ React9.createElement(
673
- CoercedFileInput,
674
- {
675
- accept,
676
- description,
677
- disabled: isDisabled,
678
- errorMessage: fieldState.error?.message,
679
- field: {
680
- ...field2,
681
- onChange: (value) => field2.onChange(transform ? transform(value) : value)
682
- },
683
- fileProps,
684
- label,
685
- multiple
686
- }
687
- )),
688
- rules
689
- }
690
- );
691
- }
692
-
693
- // src/fields/FontPickerField.tsx
694
- import React10 from "react";
695
- import { Controller as Controller5 } from "react-hook-form";
696
- var FontPickerComponent = null;
697
- var fontPickerLoaded = false;
698
- var fontPickerLoading = false;
699
- var loadingCallbacks = [];
700
- function FontPickerField(props) {
701
- const {
702
- className,
703
- control,
704
- description,
705
- fontPickerProps,
706
- isDisabled,
707
- label,
708
- name,
709
- rules
710
- } = props;
711
- const [fontPickerState, setFontPickerState] = React10.useState({
712
- component: FontPickerComponent,
713
- error: null,
714
- loading: false
715
- });
716
- React10.useEffect(() => {
717
- if (fontPickerLoaded && FontPickerComponent) {
718
- setFontPickerState({
719
- component: FontPickerComponent,
720
- error: null,
721
- loading: false
722
- });
723
- return;
724
- }
725
- if (fontPickerLoading) {
726
- setFontPickerState((prev) => ({ ...prev, loading: true }));
727
- const callback = () => {
728
- if (fontPickerLoaded && FontPickerComponent) {
729
- setFontPickerState({
730
- component: FontPickerComponent,
731
- error: null,
732
- loading: false
733
- });
734
- } else {
735
- setFontPickerState({
736
- component: null,
737
- error: "Font picker package not found",
738
- loading: false
739
- });
740
- }
741
- };
742
- loadingCallbacks.push(callback);
743
- return;
744
- }
745
- const loadFontPicker = async () => {
746
- fontPickerLoading = true;
747
- setFontPickerState((prev) => ({ ...prev, loading: true }));
748
- try {
749
- const fontPickerModule = await import("@rachelallyson/heroui-font-picker");
750
- FontPickerComponent = fontPickerModule.FontPicker || fontPickerModule.default;
751
- fontPickerLoaded = true;
752
- fontPickerLoading = false;
753
- setFontPickerState({
754
- component: FontPickerComponent,
755
- error: null,
756
- loading: false
757
- });
758
- loadingCallbacks.forEach((callback) => callback());
759
- loadingCallbacks.length = 0;
760
- } catch {
761
- fontPickerLoading = false;
762
- setFontPickerState({
763
- component: null,
764
- error: "Font picker package not found",
765
- loading: false
766
- });
767
- loadingCallbacks.forEach((callback) => callback());
768
- loadingCallbacks.length = 0;
769
- }
770
- };
771
- void loadFontPicker();
772
- }, []);
773
- if (fontPickerState.loading) {
774
- return /* @__PURE__ */ React10.createElement("div", { className }, /* @__PURE__ */ React10.createElement("div", { className: "space-y-2" }, label && /* @__PURE__ */ React10.createElement("label", { className: "block text-sm font-medium text-foreground" }, label), description && /* @__PURE__ */ React10.createElement("p", { className: "text-sm text-muted-foreground" }, description), /* @__PURE__ */ React10.createElement("div", { className: "p-4 border border-default-200 bg-default-50 rounded-medium" }, /* @__PURE__ */ React10.createElement("p", { className: "text-default-600 text-sm" }, "Loading font picker..."))));
775
- }
776
- if (!fontPickerState.component) {
777
- return /* @__PURE__ */ React10.createElement("div", { className }, /* @__PURE__ */ React10.createElement("div", { className: "space-y-2" }, label && /* @__PURE__ */ React10.createElement("label", { className: "block text-sm font-medium text-foreground" }, label), description && /* @__PURE__ */ React10.createElement("p", { className: "text-sm text-muted-foreground" }, description), /* @__PURE__ */ React10.createElement("div", { className: "p-4 border border-warning-200 bg-warning-50 rounded-medium" }, /* @__PURE__ */ React10.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."))));
778
- }
779
- return /* @__PURE__ */ React10.createElement(
780
- Controller5,
781
- {
782
- control,
783
- name,
784
- render: ({ field: field2, fieldState }) => /* @__PURE__ */ React10.createElement(
785
- fontPickerState.component,
786
- {
787
- label,
788
- description,
789
- value: field2.value ?? "",
790
- onSelectionChange: (value) => field2.onChange(value),
791
- errorMessage: fieldState.error?.message,
792
- isDisabled,
793
- className,
794
- ...fontPickerProps
795
- }
796
- ),
797
- rules
798
- }
799
- );
800
- }
801
-
802
- // src/fields/InputField.tsx
803
- import React11 from "react";
804
- import { Controller as Controller6 } from "react-hook-form";
805
- function CoercedInput(props) {
806
- const { description, disabled, errorMessage, field: field2, inputProps, label } = props;
807
- const defaults = useHeroHookFormDefaults();
808
- return /* @__PURE__ */ React11.createElement(
809
- Input,
810
- {
811
- ...defaults.input,
812
- ...inputProps,
813
- description,
814
- errorMessage,
815
- isDisabled: disabled,
816
- isInvalid: Boolean(errorMessage),
817
- label,
818
- value: field2.value ?? "",
819
- onBlur: field2.onBlur,
820
- onValueChange: field2.onChange
821
- }
822
- );
823
- }
824
- var InputField = React11.memo(
825
- (props) => {
826
- const {
827
- className,
828
- control,
829
- description,
830
- inputProps,
831
- isDisabled,
832
- label,
833
- name,
834
- rules,
835
- transform
836
- } = props;
837
- return /* @__PURE__ */ React11.createElement(
838
- Controller6,
839
- {
840
- control,
841
- name,
842
- render: ({ field: field2, fieldState }) => /* @__PURE__ */ React11.createElement("div", { className }, /* @__PURE__ */ React11.createElement(
843
- CoercedInput,
844
- {
845
- description,
846
- disabled: isDisabled,
847
- errorMessage: fieldState.error?.message,
848
- field: {
849
- ...field2,
850
- onChange: (value) => {
851
- if (inputProps?.type === "number") {
852
- const numValue = value === "" ? void 0 : Number(value);
853
- field2.onChange(
854
- transform ? transform(String(numValue)) : numValue
855
- );
856
- } else {
857
- field2.onChange(transform ? transform(value) : value);
858
- }
859
- }
860
- },
861
- inputProps,
862
- label
863
- }
864
- )),
865
- rules
866
- }
867
- );
868
- }
869
- );
870
-
871
- // src/fields/RadioGroupField.tsx
872
- import React12 from "react";
873
- import { Controller as Controller7 } from "react-hook-form";
874
- function RadioGroupField(props) {
875
- const {
876
- className,
877
- control,
878
- description,
879
- isDisabled,
880
- label,
881
- name,
882
- options,
883
- radioGroupProps,
884
- rules
885
- } = props;
886
- const defaults = useHeroHookFormDefaults();
887
- return /* @__PURE__ */ React12.createElement(
888
- Controller7,
889
- {
890
- control,
891
- name,
892
- render: ({ field: field2, fieldState }) => /* @__PURE__ */ React12.createElement("div", { className }, /* @__PURE__ */ React12.createElement(
893
- RadioGroup,
894
- {
895
- ...defaults.radioGroup,
896
- ...radioGroupProps,
897
- description,
898
- isDisabled,
899
- isInvalid: Boolean(fieldState.error),
900
- label,
901
- value: String(field2.value ?? ""),
902
- onBlur: field2.onBlur,
903
- onValueChange: (val) => field2.onChange(val)
904
- },
905
- options.map((opt) => /* @__PURE__ */ React12.createElement(
906
- Radio,
907
- {
908
- key: String(opt.value),
909
- isDisabled: opt.disabled,
910
- value: String(opt.value)
911
- },
912
- opt.label
913
- ))
914
- ), fieldState.error?.message ? /* @__PURE__ */ React12.createElement("p", { className: "text-tiny text-danger mt-1" }, fieldState.error.message) : null),
915
- rules
916
- }
917
- );
918
- }
919
-
920
- // src/fields/SelectField.tsx
921
- import React13 from "react";
922
- import { Controller as Controller8 } from "react-hook-form";
923
- function SelectField(props) {
924
- const {
925
- className,
926
- control,
927
- description,
928
- isDisabled,
929
- label,
930
- name,
931
- options,
932
- placeholder,
933
- rules,
934
- selectProps
935
- } = props;
936
- const defaults = useHeroHookFormDefaults();
937
- return /* @__PURE__ */ React13.createElement(
938
- Controller8,
939
- {
940
- control,
941
- name,
942
- render: ({ field: field2, fieldState }) => {
943
- const selectedKey = field2.value;
944
- return /* @__PURE__ */ React13.createElement("div", { className }, /* @__PURE__ */ React13.createElement(
945
- Select,
946
- {
947
- ...defaults.select,
948
- ...selectProps,
949
- description,
950
- errorMessage: fieldState.error?.message,
951
- isDisabled,
952
- isInvalid: Boolean(fieldState.error),
953
- label,
954
- placeholder,
955
- selectedKeys: selectedKey != null ? /* @__PURE__ */ new Set([String(selectedKey)]) : /* @__PURE__ */ new Set(),
956
- onSelectionChange: (keys) => {
957
- const keyArray = Array.from(keys);
958
- const next = keyArray[0] ?? "";
959
- field2.onChange(next);
960
- }
961
- },
962
- options.map((opt) => /* @__PURE__ */ React13.createElement(
963
- SelectItem,
964
- {
965
- key: String(opt.value),
966
- isDisabled: opt.disabled,
967
- textValue: String(opt.value)
968
- },
969
- opt.label
970
- ))
971
- ));
972
- },
973
- rules
974
- }
975
- );
976
- }
977
-
978
- // src/fields/SliderField.tsx
979
- import React14 from "react";
980
- import { Controller as Controller9 } from "react-hook-form";
981
- function CoercedSlider(props) {
982
- const { description, disabled, errorMessage, field: field2, label, sliderProps } = props;
983
- const defaults = useHeroHookFormDefaults();
984
- return /* @__PURE__ */ React14.createElement(
985
- Slider,
986
- {
987
- ...defaults.slider,
988
- ...sliderProps,
989
- description,
990
- errorMessage,
991
- isDisabled: disabled,
992
- isInvalid: Boolean(errorMessage),
993
- label,
994
- value: field2.value ?? 0,
995
- onBlur: field2.onBlur,
996
- onValueChange: field2.onChange
997
- }
998
- );
999
- }
1000
- function SliderField(props) {
1001
- const {
1002
- className,
1003
- control,
1004
- description,
1005
- isDisabled,
1006
- label,
1007
- name,
1008
- rules,
1009
- sliderProps,
1010
- transform
1011
- } = props;
1012
- return /* @__PURE__ */ React14.createElement(
1013
- Controller9,
1014
- {
1015
- control,
1016
- name,
1017
- render: ({ field: field2, fieldState }) => /* @__PURE__ */ React14.createElement("div", { className }, /* @__PURE__ */ React14.createElement(
1018
- CoercedSlider,
1019
- {
1020
- description,
1021
- disabled: isDisabled,
1022
- errorMessage: fieldState.error?.message,
1023
- field: {
1024
- ...field2,
1025
- onChange: (value) => field2.onChange(transform ? transform(value) : value)
1026
- },
1027
- label,
1028
- sliderProps
1029
- }
1030
- )),
1031
- rules
1032
- }
1033
- );
1034
- }
1035
-
1036
- // src/fields/SwitchField.tsx
1037
- import React15 from "react";
1038
- import { Controller as Controller10 } from "react-hook-form";
1039
- function SwitchField(props) {
1040
- const {
1041
- className,
1042
- control,
1043
- description,
1044
- isDisabled,
1045
- label,
1046
- name,
1047
- rules,
1048
- switchProps
1049
- } = props;
1050
- const defaults = useHeroHookFormDefaults();
1051
- return /* @__PURE__ */ React15.createElement(
1052
- Controller10,
1053
- {
1054
- control,
1055
- name,
1056
- render: ({ field: field2, fieldState }) => /* @__PURE__ */ React15.createElement("div", { className }, /* @__PURE__ */ React15.createElement(
1057
- Switch,
1058
- {
1059
- ...defaults.switch,
1060
- ...switchProps,
1061
- isDisabled,
1062
- isSelected: Boolean(field2.value),
1063
- onBlur: field2.onBlur,
1064
- onValueChange: (val) => field2.onChange(val)
1065
- },
1066
- label
1067
- ), description ? /* @__PURE__ */ React15.createElement("p", { className: "text-small text-default-400" }, description) : null, fieldState.error?.message ? /* @__PURE__ */ React15.createElement("p", { className: "text-tiny text-danger mt-1" }, fieldState.error.message) : null),
1068
- rules
1069
- }
1070
- );
1071
- }
1072
-
1073
- // src/fields/TextareaField.tsx
1074
- import React16 from "react";
1075
- import { Controller as Controller11 } from "react-hook-form";
1076
- function TextareaField(props) {
1077
- const {
1078
- className,
1079
- control,
1080
- description,
1081
- isDisabled,
1082
- label,
1083
- name,
1084
- rules,
1085
- textareaProps
1086
- } = props;
1087
- const defaults = useHeroHookFormDefaults();
1088
- return /* @__PURE__ */ React16.createElement(
1089
- Controller11,
1090
- {
1091
- control,
1092
- name,
1093
- render: ({ field: field2, fieldState }) => /* @__PURE__ */ React16.createElement("div", { className }, /* @__PURE__ */ React16.createElement(
1094
- Textarea,
1095
- {
1096
- ...defaults.textarea,
1097
- ...textareaProps,
1098
- description,
1099
- errorMessage: fieldState.error?.message,
1100
- isDisabled,
1101
- isInvalid: Boolean(fieldState.error),
1102
- label,
1103
- value: field2.value ?? "",
1104
- onBlur: field2.onBlur,
1105
- onValueChange: field2.onChange
1106
- }
1107
- )),
1108
- rules
1109
- }
1110
- );
1111
- }
1112
-
1113
- // src/components/FormField.tsx
1114
- var FormField = React17.memo(
1115
- ({
1116
- config,
1117
- form,
1118
- submissionState
1119
- }) => {
1120
- if (!form || !form.control) {
1121
- return null;
1122
- }
1123
- const { control } = form;
1124
- const watchedValues = useWatch3({ control });
1125
- if (config.type === "content") {
1126
- return /* @__PURE__ */ React17.createElement(
1127
- ContentField,
1128
- {
1129
- config,
1130
- form,
1131
- submissionState
1132
- }
1133
- );
1134
- }
1135
- if (config.condition && !config.condition(watchedValues)) {
1136
- return null;
1137
- }
1138
- if (config.dependsOn) {
1139
- const dependentValue = get(watchedValues, config.dependsOn);
1140
- if (config.dependsOnValue !== void 0 && dependentValue !== config.dependsOnValue) {
1141
- return null;
1142
- }
1143
- }
1144
- const baseProps = {
1145
- ariaDescribedBy: config.ariaDescribedBy,
1146
- ariaLabel: config.ariaLabel,
1147
- className: config.className,
1148
- description: config.description,
1149
- isDisabled: config.isDisabled ?? submissionState.isSubmitting,
1150
- label: config.label,
1151
- name: config.name,
1152
- rules: config.rules
1153
- };
1154
- switch (config.type) {
1155
- case "input":
1156
- return /* @__PURE__ */ React17.createElement(
1157
- InputField,
1158
- {
1159
- ...baseProps,
1160
- control,
1161
- defaultValue: config.defaultValue,
1162
- inputProps: config.inputProps
1163
- }
1164
- );
1165
- case "textarea":
1166
- return /* @__PURE__ */ React17.createElement(
1167
- TextareaField,
1168
- {
1169
- ...baseProps,
1170
- control,
1171
- defaultValue: config.defaultValue,
1172
- textareaProps: config.textareaProps
1173
- }
1174
- );
1175
- case "select":
1176
- return /* @__PURE__ */ React17.createElement(
1177
- SelectField,
1178
- {
1179
- ...baseProps,
1180
- control,
1181
- defaultValue: config.defaultValue,
1182
- options: (config.options ?? []).map((opt) => ({
1183
- label: opt.label,
1184
- value: String(opt.value)
1185
- })),
1186
- selectProps: config.selectProps
1187
- }
1188
- );
1189
- case "autocomplete":
1190
- return /* @__PURE__ */ React17.createElement(
1191
- AutocompleteField,
1192
- {
1193
- ...baseProps,
1194
- control,
1195
- defaultValue: config.defaultValue,
1196
- items: (config.options ?? []).map((opt) => ({
1197
- label: opt.label,
1198
- value: String(opt.value)
1199
- })),
1200
- autocompleteProps: config.autocompleteProps
1201
- }
1202
- );
1203
- case "checkbox":
1204
- return /* @__PURE__ */ React17.createElement(
1205
- CheckboxField,
1206
- {
1207
- ...baseProps,
1208
- checkboxProps: config.checkboxProps,
1209
- control,
1210
- defaultValue: config.defaultValue
1211
- }
1212
- );
1213
- case "radio":
1214
- return /* @__PURE__ */ React17.createElement(
1215
- RadioGroupField,
1216
- {
1217
- ...baseProps,
1218
- control,
1219
- defaultValue: config.defaultValue,
1220
- options: (config.radioOptions ?? []).map((opt) => ({
1221
- label: opt.label,
1222
- value: String(opt.value)
1223
- })),
1224
- radioGroupProps: config.radioProps
1225
- }
1226
- );
1227
- case "switch":
1228
- return /* @__PURE__ */ React17.createElement(
1229
- SwitchField,
1230
- {
1231
- ...baseProps,
1232
- control,
1233
- defaultValue: config.defaultValue,
1234
- switchProps: config.switchProps
1235
- }
1236
- );
1237
- case "slider":
1238
- return /* @__PURE__ */ React17.createElement(
1239
- SliderField,
1240
- {
1241
- ...baseProps,
1242
- control,
1243
- defaultValue: config.defaultValue,
1244
- sliderProps: config.sliderProps
1245
- }
1246
- );
1247
- case "date":
1248
- return /* @__PURE__ */ React17.createElement(
1249
- DateField,
1250
- {
1251
- ...baseProps,
1252
- control,
1253
- dateProps: config.dateProps,
1254
- defaultValue: config.defaultValue
1255
- }
1256
- );
1257
- case "file":
1258
- return /* @__PURE__ */ React17.createElement(
1259
- FileField,
1260
- {
1261
- ...baseProps,
1262
- accept: config.accept,
1263
- control,
1264
- defaultValue: config.defaultValue,
1265
- fileProps: config.fileProps,
1266
- multiple: config.multiple
1267
- }
1268
- );
1269
- case "fontPicker":
1270
- return /* @__PURE__ */ React17.createElement(
1271
- FontPickerField,
1272
- {
1273
- ...baseProps,
1274
- control,
1275
- defaultValue: config.defaultValue,
1276
- fontPickerProps: config.fontPickerProps
1277
- }
1278
- );
1279
- case "custom":
1280
- return config.render({
1281
- control,
1282
- errors: form.formState.errors,
1283
- form,
1284
- isSubmitting: submissionState.isSubmitting,
1285
- name: config.name
1286
- });
1287
- case "conditional":
1288
- return /* @__PURE__ */ React17.createElement(
1289
- ConditionalField,
1290
- {
1291
- config,
1292
- control,
1293
- className: config.className
1294
- }
1295
- );
1296
- case "fieldArray":
1297
- return /* @__PURE__ */ React17.createElement(
1298
- FieldArrayField,
1299
- {
1300
- config,
1301
- className: config.className
1302
- }
1303
- );
1304
- case "dynamicSection":
1305
- return /* @__PURE__ */ React17.createElement(
1306
- DynamicSectionField,
1307
- {
1308
- config,
1309
- control,
1310
- className: config.className
1311
- }
1312
- );
1313
- default: {
1314
- const fieldType = config.type;
1315
- console.warn(`Unknown field type: ${fieldType}`);
1316
- return null;
1317
- }
1318
- }
1319
- }
1320
- );
1321
-
1322
- // src/components/Form.tsx
1323
- function ConfigurableForm({
1324
- className,
1325
- columns = 1,
1326
- defaultValues,
1327
- fields,
1328
- layout = "vertical",
1329
- onError,
1330
- onSubmit,
1331
- onSuccess,
1332
- resetButtonText = "Reset",
1333
- showResetButton = false,
1334
- spacing = "4",
1335
- submitButtonProps = {},
1336
- submitButtonText = "Submit",
1337
- subtitle,
1338
- title
1339
- }) {
1340
- const {
1341
- error,
1342
- form,
1343
- handleSubmit,
1344
- isSubmitted,
1345
- isSubmitting,
1346
- isSuccess,
1347
- resetForm,
1348
- submissionState
1349
- } = useFormHelper({
1350
- defaultValues,
1351
- onError,
1352
- onSubmit,
1353
- onSuccess
1354
- });
1355
- const renderFields = () => {
1356
- if (layout === "grid") {
1357
- return /* @__PURE__ */ React18.createElement(
1358
- "div",
1359
- {
1360
- 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"}`
1361
- },
1362
- fields.map((field2) => /* @__PURE__ */ React18.createElement(
1363
- FormField,
1364
- {
1365
- key: field2.name,
1366
- config: field2,
1367
- form,
1368
- submissionState
1369
- }
1370
- ))
1371
- );
1372
- }
1373
- if (layout === "horizontal") {
1374
- return /* @__PURE__ */ React18.createElement("div", { className: `grid gap-${spacing} grid-cols-1 md:grid-cols-2` }, fields.map((field2) => /* @__PURE__ */ React18.createElement(
1375
- FormField,
1376
- {
1377
- key: field2.name,
1378
- config: field2,
1379
- form,
1380
- submissionState
1381
- }
1382
- )));
1383
- }
1384
- return /* @__PURE__ */ React18.createElement("div", { className: `space-y-${spacing}` }, fields.map((field2) => /* @__PURE__ */ React18.createElement(
1385
- FormField,
1386
- {
1387
- key: field2.name,
1388
- config: field2,
1389
- form,
1390
- submissionState
1391
- }
1392
- )));
1393
- };
1394
- const handleFormSubmit = (e) => {
1395
- e.preventDefault();
1396
- void handleSubmit();
1397
- };
1398
- return /* @__PURE__ */ React18.createElement("form", { className, role: "form", onSubmit: handleFormSubmit }, title && /* @__PURE__ */ React18.createElement("div", { className: "mb-6" }, /* @__PURE__ */ React18.createElement("h2", { className: "text-xl font-semibold text-foreground mb-2" }, title), subtitle && /* @__PURE__ */ React18.createElement("p", { className: "text-sm text-muted-foreground" }, subtitle)), isSubmitted && isSuccess && /* @__PURE__ */ React18.createElement(
1399
- "div",
1400
- {
1401
- className: "mb-6 p-4 bg-success-50 border border-success-200 rounded-lg",
1402
- "data-testid": "success-message"
1403
- },
1404
- /* @__PURE__ */ React18.createElement("p", { className: "text-success-800 font-medium" }, "Success!"),
1405
- /* @__PURE__ */ React18.createElement("p", { className: "text-success-700 text-sm mt-1" }, "Your request has been processed successfully.")
1406
- ), error && /* @__PURE__ */ React18.createElement(
1407
- "div",
1408
- {
1409
- className: "mb-6 p-4 bg-danger-50 border border-danger-200 rounded-lg",
1410
- "data-testid": "error-message"
1411
- },
1412
- /* @__PURE__ */ React18.createElement("p", { className: "text-danger-800 font-medium" }, "Error"),
1413
- /* @__PURE__ */ React18.createElement("p", { className: "text-danger-700 text-sm mt-1" }, error)
1414
- ), renderFields(), /* @__PURE__ */ React18.createElement("div", { className: "mt-6 flex gap-3 justify-end" }, /* @__PURE__ */ React18.createElement(
1415
- Button3,
1416
- {
1417
- color: "primary",
1418
- isDisabled: isSubmitting,
1419
- isLoading: isSubmitting,
1420
- type: "submit",
1421
- ...submitButtonProps
1422
- },
1423
- submitButtonText
1424
- ), showResetButton && /* @__PURE__ */ React18.createElement(
1425
- Button3,
1426
- {
1427
- isDisabled: isSubmitting,
1428
- type: "button",
1429
- variant: "bordered",
1430
- onPress: resetForm
1431
- },
1432
- resetButtonText
1433
- )));
1434
- }
1435
-
1436
- // src/components/ServerActionForm.tsx
1437
- import React19 from "react";
1438
- import { useActionState } from "react";
1439
- function ServerActionForm({
1440
- action,
1441
- className,
1442
- clientValidationSchema,
1443
- columns = 1,
1444
- defaultValues,
1445
- fields,
1446
- initialState,
1447
- layout = "vertical",
1448
- onError,
1449
- onSuccess,
1450
- resetButtonText = "Reset",
1451
- showResetButton = false,
1452
- spacing = "4",
1453
- submitButtonProps = {},
1454
- submitButtonText = "Submit",
1455
- subtitle,
1456
- title
1457
- }) {
1458
- const [state, formAction, pending] = useActionState(
1459
- action,
1460
- initialState ?? { errors: void 0, message: void 0, success: false }
1461
- );
1462
- const formRef = React19.useRef(null);
1463
- const [clientErrors, setClientErrors] = React19.useState({});
1464
- const lastSubmittedFormData = React19.useRef(null);
1465
- React19.useEffect(() => {
1466
- if (state && (state.errors || state.message && !state.success)) {
1467
- onError?.({
1468
- errors: state.errors,
1469
- message: state.message
1470
- });
1471
- }
1472
- }, [state, onError]);
1473
- React19.useEffect(() => {
1474
- if (state?.success && lastSubmittedFormData.current) {
1475
- onSuccess?.(lastSubmittedFormData.current);
1476
- }
1477
- }, [state?.success, onSuccess]);
1478
- const handleReset = () => {
1479
- formRef.current?.reset();
1480
- setClientErrors({});
1481
- };
1482
- const handleSubmit = async (e) => {
1483
- e.preventDefault();
1484
- if (clientValidationSchema) {
1485
- const formData2 = new FormData(e.currentTarget);
1486
- const values = {};
1487
- formData2.forEach((val, key) => {
1488
- if (val === "on") {
1489
- values[key] = true;
1490
- } else if (val === "") {
1491
- if (!values[key]) {
1492
- values[key] = false;
1493
- }
1494
- } else {
1495
- values[key] = val;
1496
- }
1497
- });
1498
- const result = clientValidationSchema.safeParse(values);
1499
- if (!result.success) {
1500
- const errors = {};
1501
- result.error.issues.forEach((issue) => {
1502
- const path = issue.path.join(".");
1503
- errors[path] = issue.message;
1504
- });
1505
- setClientErrors((prev) => ({ ...prev, ...errors }));
1506
- return;
1507
- }
1508
- setClientErrors({});
1509
- }
1510
- const formData = new FormData(e.currentTarget);
1511
- lastSubmittedFormData.current = formData;
1512
- formAction(formData);
1513
- };
1514
- const renderFields = () => {
1515
- if (layout === "grid") {
1516
- return /* @__PURE__ */ React19.createElement(
1517
- "div",
1518
- {
1519
- 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"}`
1520
- },
1521
- fields.map((field2) => /* @__PURE__ */ React19.createElement(
1522
- ServerActionField,
1523
- {
1524
- key: field2.name,
1525
- clientErrors,
1526
- defaultValues,
1527
- errors: state?.errors,
1528
- field: field2
1529
- }
1530
- ))
1531
- );
1532
- }
1533
- if (layout === "horizontal") {
1534
- return /* @__PURE__ */ React19.createElement("div", { className: `grid gap-${spacing} grid-cols-1 md:grid-cols-2` }, fields.map((field2) => /* @__PURE__ */ React19.createElement(
1535
- ServerActionField,
1536
- {
1537
- key: field2.name,
1538
- clientErrors,
1539
- defaultValues,
1540
- errors: state?.errors,
1541
- field: field2
1542
- }
1543
- )));
1544
- }
1545
- return /* @__PURE__ */ React19.createElement("div", { className: `space-y-${spacing}` }, fields.map((field2) => /* @__PURE__ */ React19.createElement(
1546
- ServerActionField,
1547
- {
1548
- key: field2.name,
1549
- clientErrors,
1550
- defaultValues,
1551
- errors: state?.errors,
1552
- field: field2
1553
- }
1554
- )));
1555
- };
1556
- return /* @__PURE__ */ React19.createElement(
1557
- "form",
1558
- {
1559
- ref: formRef,
1560
- className,
1561
- role: "form",
1562
- onSubmit: handleSubmit
1563
- },
1564
- title && /* @__PURE__ */ React19.createElement("div", { className: "mb-6" }, /* @__PURE__ */ React19.createElement("h2", { className: "text-xl font-semibold text-foreground mb-2" }, title), subtitle && /* @__PURE__ */ React19.createElement("p", { className: "text-sm text-muted-foreground" }, subtitle)),
1565
- state?.success && !pending && /* @__PURE__ */ React19.createElement(
1566
- "div",
1567
- {
1568
- className: "mb-6 p-4 bg-success-50 border border-success-200 rounded-lg",
1569
- "data-testid": "success-message"
1570
- },
1571
- /* @__PURE__ */ React19.createElement("p", { className: "text-success-800 font-medium" }, "Success!"),
1572
- state.message && /* @__PURE__ */ React19.createElement("p", { className: "text-success-700 text-sm mt-1" }, state.message)
1573
- ),
1574
- state?.message && !state.success && /* @__PURE__ */ React19.createElement(
1575
- "div",
1576
- {
1577
- className: "mb-6 p-4 bg-danger-50 border border-danger-200 rounded-lg",
1578
- "data-testid": "error-message"
1579
- },
1580
- /* @__PURE__ */ React19.createElement("p", { className: "text-danger-800 font-medium" }, "Error"),
1581
- /* @__PURE__ */ React19.createElement("p", { className: "text-danger-700 text-sm mt-1" }, state.message)
1582
- ),
1583
- renderFields(),
1584
- /* @__PURE__ */ React19.createElement("div", { className: "mt-6 flex gap-3 justify-end" }, /* @__PURE__ */ React19.createElement(
1585
- Button,
1586
- {
1587
- color: "primary",
1588
- isDisabled: pending,
1589
- isLoading: pending,
1590
- type: "submit",
1591
- ...submitButtonProps
1592
- },
1593
- submitButtonText
1594
- ), showResetButton && /* @__PURE__ */ React19.createElement(
1595
- Button,
1596
- {
1597
- isDisabled: pending,
1598
- type: "button",
1599
- variant: "bordered",
1600
- onPress: handleReset
1601
- },
1602
- resetButtonText
1603
- ))
1604
- );
1605
- }
1606
- function ServerActionField({
1607
- clientErrors,
1608
- defaultValues,
1609
- errors,
1610
- field: field2
1611
- }) {
1612
- if (field2.type === "content") {
1613
- const contentField2 = field2;
1614
- if (contentField2.render) {
1615
- return /* @__PURE__ */ React19.createElement("div", { className: contentField2.className }, contentField2.render({
1616
- errors: {},
1617
- form: null,
1618
- isSubmitting: false
1619
- }));
1620
- }
1621
- return /* @__PURE__ */ React19.createElement("div", { className: contentField2.className }, contentField2.title && /* @__PURE__ */ React19.createElement("h3", { className: "text-lg font-semibold text-foreground mb-2" }, contentField2.title), contentField2.description && /* @__PURE__ */ React19.createElement("p", { className: "text-sm text-muted-foreground" }, contentField2.description));
1622
- }
1623
- const fieldName = field2.name;
1624
- const fieldErrors = errors?.[fieldName];
1625
- const clientError = clientErrors?.[fieldName];
1626
- const errorMessage = clientError || (fieldErrors && fieldErrors.length > 0 ? fieldErrors[0] : void 0);
1627
- const getDefaultValue = () => {
1628
- const fromProps = defaultValues?.[fieldName];
1629
- const fromField = field2.defaultValue;
1630
- if (fromProps !== void 0 && fromProps !== null) {
1631
- return typeof fromProps === "string" ? fromProps : String(fromProps);
1632
- }
1633
- if (fromField !== void 0 && fromField !== null) {
1634
- return typeof fromField === "string" ? fromField : String(fromField);
1635
- }
1636
- return "";
1637
- };
1638
- const getDefaultChecked = () => {
1639
- const fromProps = defaultValues?.[fieldName];
1640
- const fromField = field2.defaultValue;
1641
- if (fromProps !== void 0 && fromProps !== null) {
1642
- return typeof fromProps === "boolean" ? fromProps : false;
1643
- }
1644
- if (fromField !== void 0 && fromField !== null) {
1645
- return typeof fromField === "boolean" ? fromField : false;
1646
- }
1647
- return false;
1648
- };
1649
- const [value, setValue] = React19.useState(getDefaultValue);
1650
- const [checked, setChecked] = React19.useState(getDefaultChecked);
1651
- React19.useEffect(() => {
1652
- const newDefaultValue = defaultValues?.[fieldName];
1653
- if (newDefaultValue !== void 0 && newDefaultValue !== null) {
1654
- if (field2.type === "checkbox") {
1655
- setChecked(
1656
- typeof newDefaultValue === "boolean" ? newDefaultValue : false
1657
- );
1658
- } else {
1659
- setValue(
1660
- typeof newDefaultValue === "string" ? newDefaultValue : String(newDefaultValue)
1661
- );
1662
- }
1663
- }
1664
- }, [defaultValues, fieldName, field2.type]);
1665
- React19.useEffect(() => {
1666
- const hiddenInput = document.querySelector(
1667
- `input[type="hidden"][name="${fieldName}"]`
1668
- );
1669
- if (hiddenInput) {
1670
- if (field2.type === "checkbox") {
1671
- hiddenInput.value = checked ? "on" : "";
1672
- } else {
1673
- hiddenInput.value = value;
1674
- }
1675
- }
1676
- }, [value, checked, fieldName, field2.type]);
1677
- switch (field2.type) {
1678
- case "input": {
1679
- const inputType = field2.inputProps?.type || "text";
1680
- return /* @__PURE__ */ React19.createElement(React19.Fragment, null, /* @__PURE__ */ React19.createElement("input", { type: "hidden", name: fieldName, value }), /* @__PURE__ */ React19.createElement(
1681
- Input,
1682
- {
1683
- ...field2.inputProps,
1684
- "data-field-name": fieldName,
1685
- type: inputType,
1686
- label: field2.label,
1687
- description: field2.description,
1688
- isDisabled: field2.isDisabled,
1689
- isInvalid: Boolean(errorMessage),
1690
- errorMessage,
1691
- value,
1692
- onValueChange: setValue
1693
- }
1694
- ));
1695
- }
1696
- case "textarea": {
1697
- return /* @__PURE__ */ React19.createElement(React19.Fragment, null, /* @__PURE__ */ React19.createElement("input", { type: "hidden", name: fieldName, value }), /* @__PURE__ */ React19.createElement(
1698
- Textarea,
1699
- {
1700
- ...field2.textareaProps,
1701
- "data-field-name": fieldName,
1702
- label: field2.label,
1703
- description: field2.description,
1704
- isDisabled: field2.isDisabled,
1705
- isInvalid: Boolean(errorMessage),
1706
- errorMessage,
1707
- value,
1708
- onValueChange: setValue
1709
- }
1710
- ));
1711
- }
1712
- case "checkbox": {
1713
- return /* @__PURE__ */ React19.createElement(React19.Fragment, null, /* @__PURE__ */ React19.createElement("input", { type: "hidden", name: fieldName, value: checked ? "on" : "" }), /* @__PURE__ */ React19.createElement(
1714
- Checkbox,
1715
- {
1716
- ...field2.checkboxProps,
1717
- "data-field-name": fieldName,
1718
- isDisabled: field2.isDisabled,
1719
- isSelected: checked,
1720
- onValueChange: setChecked,
1721
- isInvalid: Boolean(errorMessage),
1722
- errorMessage
1723
- },
1724
- field2.label
1725
- ));
1726
- }
1727
- case "select": {
1728
- const options = field2.options || [];
1729
- return /* @__PURE__ */ React19.createElement(React19.Fragment, null, /* @__PURE__ */ React19.createElement("input", { type: "hidden", name: fieldName, value }), /* @__PURE__ */ React19.createElement(
1730
- Select,
1731
- {
1732
- ...field2.selectProps,
1733
- "data-field-name": fieldName,
1734
- label: field2.label,
1735
- description: field2.description,
1736
- isDisabled: field2.isDisabled,
1737
- isInvalid: Boolean(errorMessage),
1738
- errorMessage,
1739
- selectedKeys: value ? [value] : [],
1740
- onSelectionChange: (keys) => {
1741
- const selectedValue = Array.from(keys)[0];
1742
- setValue(selectedValue || "");
1743
- }
1744
- },
1745
- options.map(
1746
- (option) => /* @__PURE__ */ React19.createElement(SelectItem, { key: String(option.value) }, option.label)
1747
- )
1748
- ));
1749
- }
1750
- default:
1751
- return /* @__PURE__ */ React19.createElement(React19.Fragment, null, /* @__PURE__ */ React19.createElement("input", { type: "hidden", name: fieldName, value }), /* @__PURE__ */ React19.createElement(
1752
- Input,
1753
- {
1754
- "data-field-name": fieldName,
1755
- label: field2.label,
1756
- description: field2.description,
1757
- isDisabled: field2.isDisabled,
1758
- isInvalid: Boolean(errorMessage),
1759
- errorMessage,
1760
- value,
1761
- onValueChange: setValue
1762
- }
1763
- ));
1764
- }
1765
- }
1766
-
1767
- // src/hooks/useHeroForm.ts
1768
- import { useFormContext as useFormContext4 } from "react-hook-form";
1769
- function useHeroForm() {
1770
- const form = useFormContext4();
1771
- const defaults = useHeroHookFormDefaults();
1772
- return {
1773
- // All React Hook Form methods and state
1774
- ...form,
1775
- // Hero Hook Form styling defaults
1776
- defaults
1777
- };
1778
- }
1779
-
1780
- // src/providers/FormProvider.tsx
1781
- import React20 from "react";
1782
- import { FormProvider as RHFProvider } from "react-hook-form";
1783
- function FormProvider(props) {
1784
- return /* @__PURE__ */ React20.createElement(RHFProvider, { ...props.methods }, /* @__PURE__ */ React20.createElement(
1785
- "form",
1786
- {
1787
- className: props.className,
1788
- id: props.id,
1789
- noValidate: props.noValidate,
1790
- onSubmit: (event) => void props.methods.handleSubmit(props.onSubmit)(event)
1791
- },
1792
- props.children
1793
- ));
1794
- }
1795
-
1796
- // src/submit/SubmitButton.tsx
1797
- import React21 from "react";
1798
- function SubmitButton(props) {
1799
- const ctx = useFormContext5();
1800
- const loading = props.isLoading ?? ctx.formState.isSubmitting;
1801
- const enhancedState = props.enhancedState;
1802
- const isDisabledFromProps = props.buttonProps?.isDisabled ?? false;
1803
- const isDisabled = Boolean(isDisabledFromProps) || Boolean(loading);
1804
- const defaults = useHeroHookFormDefaults();
1805
- const getButtonContent = () => {
1806
- if (enhancedState?.isSuccess) {
1807
- return /* @__PURE__ */ React21.createElement("span", { className: "inline-flex items-center gap-2" }, "\u2705", props.successText || "Success!");
1808
- }
1809
- if (loading) {
1810
- return /* @__PURE__ */ React21.createElement("span", { className: "inline-flex items-center gap-2" }, "\u23F3", props.loadingText || "Submitting...");
1811
- }
1812
- return props.children;
1813
- };
1814
- const getButtonColor = () => {
1815
- if (enhancedState?.isSuccess) {
1816
- return "success";
1817
- }
1818
- if (enhancedState?.isError) {
1819
- return "danger";
1820
- }
1821
- return props.buttonProps?.color || defaults.submitButton.color;
1822
- };
1823
- return /* @__PURE__ */ React21.createElement(
1824
- Button,
1825
- {
1826
- type: "submit",
1827
- ...defaults.submitButton,
1828
- ...props.buttonProps,
1829
- isDisabled,
1830
- color: getButtonColor()
1831
- },
1832
- getButtonContent()
1833
- );
1834
- }
1835
-
1836
- // src/utils/applyServerErrors.ts
1837
- function applyServerErrors(setError, serverError) {
1838
- if (!serverError.fieldErrors?.length) return;
1839
- for (const err of serverError.fieldErrors) {
1840
- setError(err.path, { message: err.message, type: err.type });
1841
- }
1842
- }
1843
-
1844
- // src/utils/testing.ts
1845
- function createFormTestUtils(form) {
1846
- return {
1847
- /**
1848
- * Get a field by name
1849
- */
1850
- getField: (name) => {
1851
- return {
1852
- error: form.formState.errors[name],
1853
- isDirty: !!form.formState.dirtyFields[name],
1854
- isTouched: !!form.formState.touchedFields[name],
1855
- value: form.getValues(name)
1856
- };
1857
- },
1858
- /**
1859
- * Get the current form state
1860
- */
1861
- getFormState: () => ({
1862
- errors: form.formState.errors,
1863
- isSubmitted: form.formState.isSubmitted,
1864
- isSubmitting: form.formState.isSubmitting,
1865
- isSuccess: form.formState.isSubmitSuccessful,
1866
- values: form.getValues()
1867
- }),
1868
- /**
1869
- * Reset the form
1870
- */
1871
- resetForm: () => {
1872
- form.reset();
1873
- },
1874
- /**
1875
- * Set a field value
1876
- */
1877
- setFieldValue: (name, value) => {
1878
- form.setValue(name, value, { shouldValidate: true });
1879
- },
1880
- /**
1881
- * Submit the form
1882
- */
1883
- submitForm: async () => {
1884
- const isValid = await form.trigger();
1885
- if (isValid) {
1886
- await form.handleSubmit(() => {
1887
- })();
1888
- }
1889
- },
1890
- /**
1891
- * Trigger validation for a field or all fields
1892
- */
1893
- triggerValidation: async (name) => {
1894
- if (name) {
1895
- return await form.trigger(name);
1896
- }
1897
- return await form.trigger();
1898
- }
1899
- };
1900
- }
1901
- function createMockFormData(overrides = {}) {
1902
- return {
1903
- agreeToTerms: true,
1904
- confirmPassword: "password123",
1905
- email: "test@example.com",
1906
- firstName: "John",
1907
- lastName: "Doe",
1908
- password: "password123",
1909
- phone: "123-456-7890",
1910
- ...overrides
1911
- };
1912
- }
1913
- function createMockFormErrors(overrides = {}) {
1914
- return {
1915
- email: { message: "Invalid email address", type: "pattern" },
1916
- password: { message: "Password is too short", type: "minLength" },
1917
- ...overrides
1918
- };
1919
- }
1920
- function waitForFormState(form, condition, timeout = 5e3) {
1921
- return new Promise((resolve, reject) => {
1922
- const startTime = Date.now();
1923
- const checkState = () => {
1924
- if (condition(form.formState)) {
1925
- resolve();
1926
- return;
1927
- }
1928
- if (Date.now() - startTime > timeout) {
1929
- reject(new Error("Timeout waiting for form state"));
1930
- return;
1931
- }
1932
- setTimeout(checkState, 100);
1933
- };
1934
- checkState();
1935
- });
1936
- }
1937
- function simulateFieldInput(form, name, value) {
1938
- form.setValue(name, value);
1939
- void form.trigger(name);
1940
- }
1941
- function simulateFormSubmission(form, onSubmit) {
1942
- return form.handleSubmit(onSubmit)();
1943
- }
1944
- function hasFormErrors(form) {
1945
- return Object.keys(form.formState.errors).length > 0;
1946
- }
1947
- function getFormErrors(form) {
1948
- return Object.values(form.formState.errors).map(
1949
- (error) => error.message
1950
- );
1951
- }
1952
- function hasFieldError(form, name) {
1953
- return !!form.formState.errors[name];
1954
- }
1955
- function getFieldError(form, name) {
1956
- const error = form.formState.errors[name];
1957
- return error?.message;
1958
- }
1959
-
1960
- // src/utils/validation.ts
1961
- import { z } from "zod";
1962
- var createMinLengthSchema = (min, fieldName) => z.string().min(min, `${fieldName} must be at least ${min} characters`);
1963
- var createMaxLengthSchema = (max, fieldName) => z.string().max(max, `${fieldName} must be no more than ${max} characters`);
1964
- var createEmailSchema = () => z.email("Please enter a valid email address");
1965
- var createRequiredSchema = (fieldName) => z.string().min(1, `${fieldName} is required`);
1966
- var createUrlSchema = () => z.string().url("Please enter a valid URL");
1967
- var createPhoneSchema = () => z.string().regex(/^[+]?[1-9][\d]{0,15}$/, "Please enter a valid phone number");
1968
- var createPasswordSchema = (minLength = 8) => z.string().min(minLength, `Password must be at least ${minLength} characters`).regex(
1969
- /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
1970
- "Password must contain at least one uppercase letter, one lowercase letter, and one number"
1971
- );
1972
- var createNumberRangeSchema = (min, max, fieldName) => z.number().min(min, `${fieldName} must be at least ${min}`).max(max, `${fieldName} must be no more than ${max}`);
1973
- var createDateSchema = (fieldName) => z.date({ message: `${fieldName} is required` });
1974
- var createFutureDateSchema = (fieldName) => z.date({ message: `${fieldName} is required` }).refine((date) => date > /* @__PURE__ */ new Date(), {
1975
- message: `${fieldName} must be in the future`
1976
- });
1977
- var createPastDateSchema = (fieldName) => z.date({ message: `${fieldName} is required` }).refine((date) => date < /* @__PURE__ */ new Date(), {
1978
- message: `${fieldName} must be in the past`
1979
- });
1980
- var createFileSchema = (maxSizeInMB = 5, allowedTypes = ["image/jpeg", "image/png", "image/gif"]) => z.instanceof(File).refine(
1981
- (file) => file.size <= maxSizeInMB * 1024 * 1024,
1982
- `File size must be less than ${maxSizeInMB}MB`
1983
- ).refine(
1984
- (file) => allowedTypes.includes(file.type),
1985
- `File type must be one of: ${allowedTypes.join(", ")}`
1986
- );
1987
- var createRequiredCheckboxSchema = (fieldName) => z.boolean().refine((val) => val === true, {
1988
- message: `You must agree to ${fieldName}`
1989
- });
1990
- var crossFieldValidation = {
1991
- /**
1992
- * Conditional required field validation
1993
- */
1994
- conditionalRequired: (field2, conditionField, conditionValue) => {
1995
- return z.object({
1996
- [conditionField]: z.any(),
1997
- [field2]: z.string()
1998
- }).refine(
1999
- (data) => {
2000
- if (data[conditionField] === conditionValue) {
2001
- return data[field2] && data[field2].trim().length > 0;
2002
- }
2003
- return true;
2004
- },
2005
- {
2006
- message: "This field is required",
2007
- path: [field2]
2008
- }
2009
- );
2010
- },
2011
- /**
2012
- * Date range validation
2013
- */
2014
- dateRange: (startField, endField) => {
2015
- return z.object({
2016
- [endField]: z.string(),
2017
- [startField]: z.string()
2018
- }).refine(
2019
- (data) => {
2020
- const startDate = new Date(data[startField]);
2021
- const endDate = new Date(data[endField]);
2022
- return startDate < endDate;
2023
- },
2024
- {
2025
- message: "End date must be after start date",
2026
- path: [endField]
2027
- }
2028
- );
2029
- },
2030
- /**
2031
- * Password confirmation validation
2032
- */
2033
- passwordConfirmation: (passwordField, confirmField) => {
2034
- return z.object({
2035
- [confirmField]: z.string(),
2036
- [passwordField]: z.string()
2037
- }).refine((data) => data[passwordField] === data[confirmField], {
2038
- message: "Passwords do not match",
2039
- path: [confirmField]
2040
- });
2041
- }
2042
- };
2043
- var commonValidations = {
2044
- confirmPassword: (passwordField, confirmField) => crossFieldValidation.passwordConfirmation(passwordField, confirmField),
2045
- date: (fieldName) => createDateSchema(fieldName),
2046
- email: createEmailSchema(),
2047
- file: (maxSizeInMB, allowedTypes) => createFileSchema(maxSizeInMB, allowedTypes),
2048
- futureDate: (fieldName) => createFutureDateSchema(fieldName),
2049
- maxLength: (max, fieldName) => createMaxLengthSchema(max, fieldName),
2050
- minLength: (min, fieldName) => createMinLengthSchema(min, fieldName),
2051
- numberRange: (min, max, fieldName) => createNumberRangeSchema(min, max, fieldName),
2052
- password: (minLength) => createPasswordSchema(minLength),
2053
- pastDate: (fieldName) => createPastDateSchema(fieldName),
2054
- phone: createPhoneSchema(),
2055
- required: (fieldName) => createRequiredSchema(fieldName),
2056
- requiredCheckbox: (fieldName) => createRequiredCheckboxSchema(fieldName),
2057
- url: createUrlSchema()
2058
- };
2059
-
2060
- // src/index.ts
2061
- import { useFormContext as useFormContext5 } from "react-hook-form";
2062
-
2063
- // src/components/ZodForm.tsx
2064
- import React23 from "react";
2065
- import { Button as Button5 } from "@heroui/react";
2066
- import {
2067
- FormProvider as FormProvider2
2068
- } from "react-hook-form";
2069
-
2070
- // src/zod-integration.ts
2071
- import { useForm as useForm2 } from "react-hook-form";
2072
- import { z as z2 } from "zod";
2073
- function createZodResolver(schema) {
2074
- return async (values) => {
2075
- try {
2076
- const result = await schema.parseAsync(values);
2077
- return {
2078
- errors: {},
2079
- values: result
2080
- };
2081
- } catch (error) {
2082
- if (error instanceof z2.ZodError) {
2083
- const errors = {};
2084
- error.issues.forEach((err) => {
2085
- const path = err.path.join(".");
2086
- errors[path] = { message: err.message };
2087
- });
2088
- return {
2089
- errors,
2090
- values: {}
2091
- };
2092
- }
2093
- throw error;
2094
- }
2095
- };
2096
- }
2097
- function useZodForm(config) {
2098
- if (!config.resolver && config.schema) {
2099
- config.resolver = createZodResolver(config.schema);
2100
- }
2101
- return useForm2(config);
2102
- }
2103
- function createZodFormConfig(schema, fields, defaultValues) {
2104
- return {
2105
- fields,
2106
- schema,
2107
- ...defaultValues && {
2108
- defaultValues
2109
- }
2110
- };
2111
- }
2112
-
2113
- // src/hooks/useEnhancedFormState.ts
2114
- import { useCallback, useEffect, useState as useState2 } from "react";
2115
- function useEnhancedFormState(form, options = {}) {
2116
- const {
2117
- autoReset = true,
2118
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
2119
- errorMessage: _errorMessage = "An error occurred. Please try again.",
2120
- onError,
2121
- onSuccess,
2122
- resetDelay = 3e3,
2123
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
2124
- successMessage: _successMessage = "Form submitted successfully!"
2125
- } = options;
2126
- const [status, setStatus] = useState2("idle");
2127
- const [error, setError] = useState2(void 0);
2128
- const [submittedData, setSubmittedData] = useState2(void 0);
2129
- const { formState, getValues: _getValues } = form;
2130
- const { dirtyFields, errors, isSubmitting, touchedFields } = formState;
2131
- useEffect(() => {
2132
- if (isSubmitting) {
2133
- setStatus("submitting");
2134
- }
2135
- }, [isSubmitting]);
2136
- useEffect(() => {
2137
- if (status === "success" && autoReset) {
2138
- const timer = setTimeout(() => {
2139
- setStatus("idle");
2140
- setSubmittedData(void 0);
2141
- setError(void 0);
2142
- }, resetDelay);
2143
- return () => clearTimeout(timer);
2144
- }
2145
- }, [status, autoReset, resetDelay]);
2146
- const handleSuccess = useCallback(
2147
- (data) => {
2148
- setStatus("success");
2149
- setSubmittedData(data);
2150
- setError(void 0);
2151
- onSuccess?.(data);
2152
- },
2153
- [onSuccess]
2154
- );
2155
- const handleError = useCallback(
2156
- (errorMessage) => {
2157
- setStatus("error");
2158
- setError(errorMessage);
2159
- setSubmittedData(void 0);
2160
- onError?.(errorMessage);
2161
- },
2162
- [onError]
2163
- );
2164
- const reset = useCallback(() => {
2165
- setStatus("idle");
2166
- setError(void 0);
2167
- setSubmittedData(void 0);
2168
- }, []);
2169
- return {
2170
- dirtyFields: new Set(Object.keys(dirtyFields)),
2171
- error,
2172
- errorCount: Object.keys(errors).length,
2173
- handleError,
2174
- handleSuccess,
2175
- hasErrors: Object.keys(errors).length > 0,
2176
- isError: status === "error",
2177
- isSubmitting,
2178
- isSuccess: status === "success",
2179
- reset,
2180
- status,
2181
- submittedData,
2182
- touchedFields: new Set(Object.keys(touchedFields))
2183
- };
2184
- }
2185
-
2186
- // src/components/FormStatus.tsx
2187
- import React22 from "react";
2188
- import { Button as Button4 } from "@heroui/react";
2189
- function FormStatus({
2190
- className = "",
2191
- onDismiss,
2192
- showDetails = false,
2193
- state
2194
- }) {
2195
- const { error, isError, isSubmitting, isSuccess, status, submittedData } = state;
2196
- if (status === "idle") {
2197
- return null;
2198
- }
2199
- if (isSubmitting) {
2200
- return /* @__PURE__ */ React22.createElement(
2201
- "div",
2202
- {
2203
- className: `flex items-center gap-3 p-4 bg-blue-50 border border-blue-200 rounded-lg ${className}`
2204
- },
2205
- /* @__PURE__ */ React22.createElement("span", { className: "text-blue-600" }, "\u23F3"),
2206
- /* @__PURE__ */ React22.createElement("div", null, /* @__PURE__ */ React22.createElement("p", { className: "text-sm font-medium text-blue-900" }, "Submitting form..."), showDetails && /* @__PURE__ */ React22.createElement("p", { className: "text-xs text-blue-700" }, "Please wait while we process your request."))
2207
- );
2208
- }
2209
- if (isSuccess) {
2210
- return /* @__PURE__ */ React22.createElement(
2211
- "div",
2212
- {
2213
- className: `flex items-center gap-3 p-4 bg-green-50 border border-green-200 rounded-lg ${className}`,
2214
- "data-testid": "success-message"
2215
- },
2216
- /* @__PURE__ */ React22.createElement("span", { className: "text-green-600" }, "\u2705"),
2217
- /* @__PURE__ */ React22.createElement("div", { className: "flex-1" }, /* @__PURE__ */ React22.createElement("p", { className: "text-sm font-medium text-green-900" }, "Form submitted successfully!"), showDetails && submittedData && /* @__PURE__ */ React22.createElement("p", { className: "text-xs text-green-700" }, "Your data has been saved. Thank you for your submission.")),
2218
- onDismiss && /* @__PURE__ */ React22.createElement(
2219
- Button4,
2220
- {
2221
- size: "sm",
2222
- variant: "light",
2223
- isIconOnly: true,
2224
- onPress: onDismiss,
2225
- "aria-label": "Dismiss success message"
2226
- },
2227
- "\u2715"
2228
- )
2229
- );
2230
- }
2231
- if (isError && error) {
2232
- return /* @__PURE__ */ React22.createElement(
2233
- "div",
2234
- {
2235
- className: `flex items-center gap-3 p-4 bg-red-50 border border-red-200 rounded-lg ${className}`,
2236
- "data-testid": "error-message"
2237
- },
2238
- /* @__PURE__ */ React22.createElement("span", { className: "text-red-600" }, "\u26A0\uFE0F"),
2239
- /* @__PURE__ */ React22.createElement("div", { className: "flex-1" }, /* @__PURE__ */ React22.createElement("p", { className: "text-sm font-medium text-red-900" }, "Error submitting form"), /* @__PURE__ */ React22.createElement("p", { className: "text-xs text-red-700" }, error)),
2240
- onDismiss && /* @__PURE__ */ React22.createElement(
2241
- Button4,
2242
- {
2243
- size: "sm",
2244
- variant: "light",
2245
- isIconOnly: true,
2246
- onPress: onDismiss,
2247
- "aria-label": "Dismiss error message"
2248
- },
2249
- "\u2715"
2250
- )
2251
- );
2252
- }
2253
- return null;
2254
- }
2255
- function FormToast({
2256
- duration = 5e3,
2257
- onDismiss,
2258
- position = "top-right",
2259
- state
2260
- }) {
2261
- const [isVisible, setIsVisible] = React22.useState(false);
2262
- React22.useEffect(() => {
2263
- if (state.isSuccess || state.isError) {
2264
- setIsVisible(true);
2265
- if (duration > 0) {
2266
- const timer = setTimeout(() => {
2267
- setIsVisible(false);
2268
- onDismiss?.();
2269
- }, duration);
2270
- return () => clearTimeout(timer);
2271
- }
2272
- }
2273
- }, [state.isSuccess, state.isError, duration, onDismiss]);
2274
- if (!isVisible) {
2275
- return null;
2276
- }
2277
- const positionClasses = {
2278
- "bottom-left": "bottom-4 left-4",
2279
- "bottom-right": "bottom-4 right-4",
2280
- "top-left": "top-4 left-4",
2281
- "top-right": "top-4 right-4"
2282
- };
2283
- return /* @__PURE__ */ React22.createElement("div", { className: `fixed z-50 ${positionClasses[position]}` }, /* @__PURE__ */ React22.createElement(FormStatus, { state, onDismiss }));
2284
- }
2285
-
2286
- // src/components/ZodForm.tsx
2287
- function ZodForm({
2288
- className,
2289
- columns = 1,
2290
- config,
2291
- layout = "vertical",
2292
- onError,
2293
- onSubmit,
2294
- onSuccess,
2295
- resetButtonText = "Reset",
2296
- showResetButton = false,
2297
- spacing = "4",
2298
- submitButtonProps = {},
2299
- submitButtonText = "Submit",
2300
- subtitle,
2301
- title
2302
- }) {
2303
- const form = useZodForm(config);
2304
- const enhancedState = useEnhancedFormState(form, {
2305
- autoReset: true,
2306
- onError: (error) => onError?.({ field: "form", message: error }),
2307
- onSuccess,
2308
- resetDelay: 3e3
2309
- });
2310
- const handleSubmit = async () => {
2311
- try {
2312
- await form.handleSubmit(
2313
- async (formData) => {
2314
- await onSubmit(formData);
2315
- enhancedState.handleSuccess(formData);
2316
- },
2317
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
2318
- (_errors) => {
2319
- enhancedState.handleError("Please fix the validation errors above");
2320
- }
2321
- )();
2322
- } catch (error) {
2323
- const errorMessage = error instanceof Error ? error.message : "An error occurred";
2324
- enhancedState.handleError(errorMessage);
2325
- }
2326
- };
2327
- const resetForm = () => {
2328
- form.reset();
2329
- enhancedState.reset();
2330
- };
2331
- const renderFields = () => {
2332
- if (layout === "grid") {
2333
- return /* @__PURE__ */ React23.createElement(
2334
- "div",
2335
- {
2336
- 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"}`
2337
- },
2338
- config.fields.map((field2) => /* @__PURE__ */ React23.createElement(
2339
- FormField,
2340
- {
2341
- key: field2.name,
2342
- config: field2,
2343
- form,
2344
- submissionState: {
2345
- error: enhancedState.error,
2346
- isSubmitted: enhancedState.status !== "idle",
2347
- isSubmitting: enhancedState.isSubmitting,
2348
- isSuccess: enhancedState.isSuccess
2349
- }
2350
- }
2351
- ))
2352
- );
2353
- }
2354
- if (layout === "horizontal") {
2355
- return /* @__PURE__ */ React23.createElement("div", { className: `grid gap-${spacing} grid-cols-1 md:grid-cols-2` }, config.fields.map((field2) => /* @__PURE__ */ React23.createElement(
2356
- FormField,
2357
- {
2358
- key: field2.name,
2359
- config: field2,
2360
- form,
2361
- submissionState: {
2362
- error: enhancedState.error,
2363
- isSubmitted: enhancedState.status !== "idle",
2364
- isSubmitting: enhancedState.isSubmitting,
2365
- isSuccess: enhancedState.isSuccess
2366
- }
2367
- }
2368
- )));
2369
- }
2370
- return /* @__PURE__ */ React23.createElement("div", { className: `space-y-${spacing}` }, config.fields.map((field2) => /* @__PURE__ */ React23.createElement(
2371
- FormField,
2372
- {
2373
- key: field2.name,
2374
- config: field2,
2375
- form,
2376
- submissionState: {
2377
- error: enhancedState.error,
2378
- isSubmitted: enhancedState.status !== "idle",
2379
- isSubmitting: enhancedState.isSubmitting,
2380
- isSuccess: enhancedState.isSuccess
2381
- }
2382
- }
2383
- )));
2384
- };
2385
- const handleFormSubmit = (e) => {
2386
- e.preventDefault();
2387
- void handleSubmit();
2388
- };
2389
- React23.useEffect(() => {
2390
- if (config.onError && Object.keys(form.formState.errors).length > 0) {
2391
- config.onError(form.formState.errors);
2392
- }
2393
- }, [form.formState.errors, config.onError]);
2394
- return /* @__PURE__ */ React23.createElement(FormProvider2, { ...form }, /* @__PURE__ */ React23.createElement("form", { className, role: "form", onSubmit: handleFormSubmit }, title && /* @__PURE__ */ React23.createElement("div", { className: "mb-6" }, /* @__PURE__ */ React23.createElement("h2", { className: "text-xl font-semibold text-foreground mb-2" }, title), subtitle && /* @__PURE__ */ React23.createElement("p", { className: "text-sm text-muted-foreground" }, subtitle)), /* @__PURE__ */ React23.createElement(
2395
- FormStatus,
2396
- {
2397
- state: enhancedState,
2398
- onDismiss: () => enhancedState.reset(),
2399
- showDetails: true
2400
- }
2401
- ), renderFields(), /* @__PURE__ */ React23.createElement("div", { className: "mt-6 flex gap-3 justify-end" }, /* @__PURE__ */ React23.createElement(
2402
- Button5,
2403
- {
2404
- color: "primary",
2405
- isDisabled: enhancedState.isSubmitting,
2406
- isLoading: enhancedState.isSubmitting,
2407
- type: "submit",
2408
- ...submitButtonProps
2409
- },
2410
- enhancedState.isSuccess ? "Success!" : submitButtonText
2411
- ), showResetButton && /* @__PURE__ */ React23.createElement(
2412
- Button5,
2413
- {
2414
- isDisabled: enhancedState.isSubmitting,
2415
- type: "button",
2416
- variant: "bordered",
2417
- onPress: resetForm
2418
- },
2419
- resetButtonText
2420
- ))));
2421
- }
2422
-
2423
- // src/components/SimpleForm.tsx
2424
- import React24 from "react";
2425
- function SimpleForm({
2426
- className,
2427
- defaultValues,
2428
- field: field2,
2429
- hideSubmitButton = false,
2430
- onError,
2431
- onSubmit,
2432
- onSuccess,
2433
- schema,
2434
- submitButton,
2435
- subtitle,
2436
- title
2437
- }) {
2438
- return /* @__PURE__ */ React24.createElement(
2439
- ZodForm,
2440
- {
2441
- className,
2442
- config: {
2443
- defaultValues,
2444
- fields: [field2],
2445
- schema
2446
- },
2447
- onError,
2448
- onSubmit,
2449
- onSuccess,
2450
- showResetButton: false,
2451
- submitButtonText: hideSubmitButton ? "" : "Submit",
2452
- subtitle,
2453
- title,
2454
- submitButtonProps: hideSubmitButton && submitButton ? {
2455
- style: { display: "none" }
2456
- } : {}
2457
- }
2458
- );
2459
- }
2460
-
2461
- // src/builders/BasicFormBuilder.ts
2462
- var BasicFormBuilder = class {
2463
- constructor() {
2464
- this.fields = [];
2465
- }
2466
- /**
2467
- * Add an input field
2468
- */
2469
- input(name, label, type = "text") {
2470
- this.fields.push({
2471
- inputProps: { type },
2472
- label,
2473
- name,
2474
- type: "input"
2475
- });
2476
- return this;
2477
- }
2478
- /**
2479
- * Add a textarea field
2480
- */
2481
- textarea(name, label, placeholder) {
2482
- this.fields.push({
2483
- label,
2484
- name,
2485
- textareaProps: { placeholder },
2486
- type: "textarea"
2487
- });
2488
- return this;
2489
- }
2490
- /**
2491
- * Add a select field
2492
- */
2493
- select(name, label, options) {
2494
- this.fields.push({
2495
- label,
2496
- name,
2497
- options,
2498
- type: "select"
2499
- });
2500
- return this;
2501
- }
2502
- /**
2503
- * Add an autocomplete field
2504
- */
2505
- autocomplete(name, label, items, placeholder) {
2506
- this.fields.push({
2507
- autocompleteProps: placeholder ? { placeholder } : void 0,
2508
- label,
2509
- name,
2510
- options: items,
2511
- type: "autocomplete"
2512
- });
2513
- return this;
2514
- }
2515
- /**
2516
- * Add a checkbox field
2517
- */
2518
- checkbox(name, label) {
2519
- this.fields.push({
2520
- label,
2521
- name,
2522
- type: "checkbox"
2523
- });
2524
- return this;
2525
- }
2526
- /**
2527
- * Add a content field for headers, questions, or custom content between fields
2528
- */
2529
- content(title, description, options) {
2530
- this.fields.push({
2531
- className: options?.className,
2532
- description: description || void 0,
2533
- name: options?.name,
2534
- render: options?.render,
2535
- title: title || void 0,
2536
- type: "content"
2537
- });
2538
- return this;
2539
- }
2540
- /**
2541
- * Add a switch field
2542
- */
2543
- switch(name, label, description) {
2544
- this.fields.push({
2545
- description,
2546
- label,
2547
- name,
2548
- type: "switch"
2549
- });
2550
- return this;
2551
- }
2552
- /**
2553
- * Build the final field configuration array
2554
- */
2555
- build() {
2556
- return this.fields;
2557
- }
2558
- };
2559
- function createBasicFormBuilder() {
2560
- return new BasicFormBuilder();
2561
- }
2562
- var FormFieldHelpers = {
2563
- /**
2564
- * Create an autocomplete field
2565
- *
2566
- * @example
2567
- * ```tsx
2568
- * // Simple autocomplete
2569
- * FormFieldHelpers.autocomplete("country", "Country", options)
2570
- *
2571
- * // With placeholder
2572
- * FormFieldHelpers.autocomplete("country", "Country", options, "Search countries")
2573
- *
2574
- * // With full customization
2575
- * FormFieldHelpers.autocomplete("country", "Country", options, "Search countries", {
2576
- * classNames: { base: "custom-autocomplete" },
2577
- * allowsCustomValue: true
2578
- * })
2579
- * ```
2580
- */
2581
- autocomplete: (name, label, items, placeholder, autocompleteProps) => ({
2582
- autocompleteProps: {
2583
- ...placeholder && { placeholder },
2584
- ...autocompleteProps
2585
- },
2586
- label,
2587
- name,
2588
- options: items,
2589
- type: "autocomplete"
2590
- }),
2591
- /**
2592
- * Create a checkbox field
2593
- *
2594
- * @example
2595
- * ```tsx
2596
- * // Simple checkbox
2597
- * FormFieldHelpers.checkbox("newsletter", "Subscribe to newsletter")
2598
- *
2599
- * // With full customization
2600
- * FormFieldHelpers.checkbox("newsletter", "Subscribe to newsletter", {
2601
- * classNames: { base: "custom-checkbox" },
2602
- * size: "lg"
2603
- * })
2604
- * ```
2605
- */
2606
- checkbox: (name, label, checkboxProps) => ({
2607
- checkboxProps,
2608
- label,
2609
- name,
2610
- type: "checkbox"
2611
- }),
2612
- /**
2613
- * Create a conditional field that shows/hides based on form data
2614
- *
2615
- * @example
2616
- * ```tsx
2617
- * FormFieldHelpers.conditional(
2618
- * "phone",
2619
- * (values) => values.hasPhone === true,
2620
- * FormFieldHelpers.input("phone", "Phone Number", "tel")
2621
- * )
2622
- * ```
2623
- *
2624
- * @example
2625
- * With explicit type in condition function (similar to content helper pattern):
2626
- * ```tsx
2627
- * FormFieldHelpers.conditional(
2628
- * "options",
2629
- * (formData: Partial<z.infer<typeof fieldSchema>>) =>
2630
- * formData.fieldType === 'DROPDOWN',
2631
- * FormFieldHelpers.textarea("options", "Dropdown Options", "One per line")
2632
- * )
2633
- * ```
2634
- */
2635
- conditional: (name, condition, field2) => {
2636
- return {
2637
- condition,
2638
- field: field2,
2639
- name,
2640
- type: "conditional"
2641
- };
2642
- },
2643
- /**
2644
- * Create a content field for headers, questions, or custom content between fields
2645
- *
2646
- * @example
2647
- * ```tsx
2648
- * // Simple header
2649
- * FormFieldHelpers.content("Personal Information", "Please provide your details")
2650
- *
2651
- * // Custom render
2652
- * FormFieldHelpers.content(null, null, {
2653
- * render: () => <div>Custom content</div>
2654
- * })
2655
- * ```
2656
- */
2657
- content: (title, description, options) => {
2658
- return {
2659
- className: options?.className,
2660
- description: description || void 0,
2661
- name: options?.name,
2662
- render: options?.render,
2663
- title: title || void 0,
2664
- type: "content"
2665
- };
2666
- },
2667
- /**
2668
- * Create a date field
2669
- *
2670
- * @example
2671
- * ```tsx
2672
- * // Simple date field
2673
- * FormFieldHelpers.date("birthDate", "Birth Date")
2674
- *
2675
- * // With full customization
2676
- * FormFieldHelpers.date("birthDate", "Birth Date", {
2677
- * label: "Select your birth date",
2678
- * granularity: "day",
2679
- * minValue: new CalendarDate(1900, 1, 1)
2680
- * })
2681
- * ```
2682
- */
2683
- date: (name, label, dateProps) => ({
2684
- dateProps,
2685
- label,
2686
- name,
2687
- type: "date"
2688
- }),
2689
- /**
2690
- * Create a file upload field
2691
- *
2692
- * @example
2693
- * ```tsx
2694
- * // Simple file field
2695
- * FormFieldHelpers.file("avatar", "Profile Picture")
2696
- *
2697
- * // With accept and multiple
2698
- * FormFieldHelpers.file("avatar", "Profile Picture", {
2699
- * accept: "image/*",
2700
- * multiple: true
2701
- * })
2702
- *
2703
- * // With full customization
2704
- * FormFieldHelpers.file("avatar", "Profile Picture", {
2705
- * accept: "image/*",
2706
- * multiple: false,
2707
- * fileProps: { className: "custom-file-input" }
2708
- * })
2709
- * ```
2710
- */
2711
- file: (name, label, options) => ({
2712
- accept: options?.accept,
2713
- fileProps: options?.fileProps,
2714
- label,
2715
- multiple: options?.multiple,
2716
- name,
2717
- type: "file"
2718
- }),
2719
- /**
2720
- * Create a font picker field
2721
- *
2722
- * @example
2723
- * ```tsx
2724
- * // Simple font picker
2725
- * FormFieldHelpers.fontPicker("font", "Choose Font")
2726
- *
2727
- * // With full customization
2728
- * FormFieldHelpers.fontPicker("font", "Choose Font", {
2729
- * showFontPreview: true,
2730
- * loadAllVariants: false,
2731
- * fontsLoadedTimeout: 5000
2732
- * })
2733
- * ```
2734
- */
2735
- fontPicker: (name, label, fontPickerProps) => ({
2736
- fontPickerProps,
2737
- label,
2738
- name,
2739
- type: "fontPicker"
2740
- }),
2741
- /**
2742
- * Create an input field
2743
- *
2744
- * @example
2745
- * ```tsx
2746
- * // Simple input
2747
- * FormFieldHelpers.input("name", "Name")
2748
- *
2749
- * // With type
2750
- * FormFieldHelpers.input("email", "Email", "email")
2751
- *
2752
- * // With full customization
2753
- * FormFieldHelpers.input("email", "Email", "email", {
2754
- * placeholder: "Enter your email",
2755
- * classNames: { input: "custom-input" },
2756
- * startContent: <MailIcon />,
2757
- * description: "We'll never share your email"
2758
- * })
2759
- * ```
2760
- */
2761
- input: (name, label, type, inputProps) => ({
2762
- inputProps: {
2763
- type: type || "text",
2764
- ...inputProps
2765
- },
2766
- label,
2767
- name,
2768
- type: "input"
2769
- }),
2770
- /**
2771
- * Create a radio group field
2772
- *
2773
- * @example
2774
- * ```tsx
2775
- * // Simple radio group
2776
- * FormFieldHelpers.radio("gender", "Gender", [
2777
- * { label: "Male", value: "male" },
2778
- * { label: "Female", value: "female" }
2779
- * ])
2780
- *
2781
- * // With full customization
2782
- * FormFieldHelpers.radio("gender", "Gender", options, {
2783
- * orientation: "horizontal",
2784
- * classNames: { base: "custom-radio" }
2785
- * })
2786
- * ```
2787
- */
2788
- radio: (name, label, options, radioProps) => ({
2789
- label,
2790
- name,
2791
- radioOptions: options,
2792
- radioProps,
2793
- type: "radio"
2794
- }),
2795
- /**
2796
- * Create a select field
2797
- *
2798
- * @example
2799
- * ```tsx
2800
- * // Simple select
2801
- * FormFieldHelpers.select("country", "Country", options)
2802
- *
2803
- * // With full customization
2804
- * FormFieldHelpers.select("country", "Country", options, {
2805
- * placeholder: "Select a country",
2806
- * classNames: { trigger: "custom-select" },
2807
- * selectionMode: "multiple"
2808
- * })
2809
- * ```
2810
- */
2811
- select: (name, label, options, selectProps) => ({
2812
- label,
2813
- name,
2814
- options,
2815
- selectProps,
2816
- type: "select"
2817
- }),
2818
- /**
2819
- * Create a slider field
2820
- *
2821
- * @example
2822
- * ```tsx
2823
- * // Simple slider
2824
- * FormFieldHelpers.slider("rating", "Rating")
2825
- *
2826
- * // With full customization
2827
- * FormFieldHelpers.slider("rating", "Rating", {
2828
- * minValue: 1,
2829
- * maxValue: 5,
2830
- * step: 1,
2831
- * showSteps: true,
2832
- * classNames: { base: "custom-slider" }
2833
- * })
2834
- * ```
2835
- */
2836
- slider: (name, label, sliderProps) => ({
2837
- label,
2838
- name,
2839
- sliderProps,
2840
- type: "slider"
2841
- }),
2842
- /**
2843
- * Create a switch field
2844
- *
2845
- * @example
2846
- * ```tsx
2847
- * // Simple switch
2848
- * FormFieldHelpers.switch("notifications", "Enable notifications")
2849
- *
2850
- * // With description
2851
- * FormFieldHelpers.switch("notifications", "Enable notifications", "Receive email notifications")
2852
- *
2853
- * // With full customization
2854
- * FormFieldHelpers.switch("notifications", "Enable notifications", "Receive email notifications", {
2855
- * classNames: { base: "custom-switch" },
2856
- * size: "lg",
2857
- * color: "primary"
2858
- * })
2859
- * ```
2860
- */
2861
- switch: (name, label, description, switchProps) => ({
2862
- description,
2863
- label,
2864
- name,
2865
- switchProps,
2866
- type: "switch"
2867
- }),
2868
- /**
2869
- * Create a textarea field
2870
- *
2871
- * @example
2872
- * ```tsx
2873
- * // Simple textarea
2874
- * FormFieldHelpers.textarea("message", "Message")
2875
- *
2876
- * // With placeholder
2877
- * FormFieldHelpers.textarea("message", "Message", "Enter your message")
2878
- *
2879
- * // With full customization
2880
- * FormFieldHelpers.textarea("message", "Message", "Enter your message", {
2881
- * classNames: { input: "custom-textarea" },
2882
- * minRows: 3,
2883
- * maxRows: 10
2884
- * })
2885
- * ```
2886
- */
2887
- textarea: (name, label, placeholder, textareaProps) => ({
2888
- label,
2889
- name,
2890
- textareaProps: {
2891
- ...placeholder && { placeholder },
2892
- ...textareaProps
2893
- },
2894
- type: "textarea"
2895
- })
2896
- };
2897
- var CommonFields = {
2898
- /**
2899
- * Address fields
2900
- */
2901
- address: () => [
2902
- FormFieldHelpers.input("street", "Street Address"),
2903
- FormFieldHelpers.input("city", "City"),
2904
- FormFieldHelpers.input("state", "State/Province"),
2905
- FormFieldHelpers.input("zipCode", "ZIP/Postal Code"),
2906
- FormFieldHelpers.select("country", "Country", [
2907
- { label: "Select a country", value: "" },
2908
- { label: "United States", value: "us" },
2909
- { label: "Canada", value: "ca" },
2910
- { label: "United Kingdom", value: "uk" },
2911
- { label: "Australia", value: "au" },
2912
- { label: "Germany", value: "de" },
2913
- { label: "France", value: "fr" }
2914
- ])
2915
- ],
2916
- /**
2917
- * Personal information fields
2918
- */
2919
- personal: () => [
2920
- FormFieldHelpers.input("firstName", "First Name"),
2921
- FormFieldHelpers.input("lastName", "Last Name"),
2922
- FormFieldHelpers.input("email", "Email", "email"),
2923
- FormFieldHelpers.input("phone", "Phone", "tel")
2924
- ],
2925
- /**
2926
- * Terms and conditions fields
2927
- */
2928
- terms: () => [
2929
- FormFieldHelpers.checkbox(
2930
- "terms",
2931
- "I agree to the terms and conditions"
2932
- ),
2933
- FormFieldHelpers.checkbox(
2934
- "privacy",
2935
- "I agree to the privacy policy"
2936
- ),
2937
- FormFieldHelpers.checkbox(
2938
- "newsletter",
2939
- "Subscribe to newsletter"
2940
- )
2941
- ]
2942
- };
2943
-
2944
- // src/builders/AdvancedFormBuilder.ts
2945
- function inputField(name, label, props) {
2946
- return {
2947
- label,
2948
- name,
2949
- type: "input",
2950
- ...props && {
2951
- inputProps: {
2952
- className: props.className,
2953
- description: props.description,
2954
- disabled: props.isDisabled,
2955
- placeholder: props.placeholder,
2956
- type: props.type || "text"
2957
- }
2958
- }
2959
- };
2960
- }
2961
- function textareaField(name, label, props) {
2962
- return {
2963
- label,
2964
- name,
2965
- type: "textarea",
2966
- ...props && {
2967
- textareaProps: {
2968
- className: props.className,
2969
- description: props.description,
2970
- disabled: props.isDisabled,
2971
- placeholder: props.placeholder,
2972
- rows: props.rows
2973
- }
2974
- }
2975
- };
2976
- }
2977
- function selectField(name, label, options) {
2978
- return {
2979
- label,
2980
- name,
2981
- options,
2982
- type: "select"
2983
- };
2984
- }
2985
- function checkboxField(name, label, props) {
2986
- return {
2987
- label,
2988
- name,
2989
- type: "checkbox",
2990
- ...props && {
2991
- checkboxProps: {
2992
- className: props.className,
2993
- disabled: props.isDisabled
2994
- }
2995
- }
2996
- };
2997
- }
2998
- function switchField(name, label, props) {
2999
- return {
3000
- description: props?.description,
3001
- isDisabled: props?.isDisabled,
3002
- label,
3003
- name,
3004
- type: "switch",
3005
- ...props?.className && {
3006
- switchProps: {
3007
- className: props.className
3008
- }
3009
- }
3010
- };
3011
- }
3012
- function radioField(name, label, options, props) {
3013
- return {
3014
- label,
3015
- name,
3016
- radioOptions: options,
3017
- type: "radio",
3018
- ...props && {
3019
- radioProps: {
3020
- className: props.className,
3021
- isDisabled: props.isDisabled,
3022
- orientation: props.orientation
3023
- }
3024
- }
3025
- };
3026
- }
3027
- function sliderField(name, label, props) {
3028
- return {
3029
- label,
3030
- name,
3031
- type: "slider",
3032
- ...props && {
3033
- sliderProps: {
3034
- className: props.className,
3035
- maxValue: props.max ?? 100,
3036
- minValue: props.min ?? 0,
3037
- step: props.step ?? 1
3038
- }
3039
- }
3040
- };
3041
- }
3042
- function dateField(name, label, props) {
3043
- return {
3044
- label,
3045
- name,
3046
- type: "date",
3047
- ...props && {
3048
- dateProps: {
3049
- className: props.className,
3050
- placeholder: props.placeholder
3051
- }
3052
- }
3053
- };
3054
- }
3055
- function fileField(name, label, props) {
3056
- return {
3057
- label,
3058
- name,
3059
- type: "file",
3060
- ...props && {
3061
- fileProps: {
3062
- accept: props.accept || "",
3063
- className: props.className || "",
3064
- disabled: props.isDisabled || false,
3065
- multiple: props.multiple || false
3066
- }
3067
- }
3068
- };
3069
- }
3070
- function fontPickerField(name, label, props) {
3071
- return {
3072
- className: props?.className,
3073
- description: props?.description,
3074
- fontPickerProps: props?.fontPickerProps,
3075
- label,
3076
- name,
3077
- type: "fontPicker"
3078
- };
3079
- }
3080
- function contentField(title, description, options) {
3081
- return {
3082
- className: options?.className,
3083
- description: description || void 0,
3084
- name: options?.name,
3085
- render: options?.render,
3086
- title: title || void 0,
3087
- type: "content"
3088
- };
3089
- }
3090
- function createField(type, name, label, optionsOrProps, props) {
3091
- switch (type) {
3092
- case "input":
3093
- return inputField(name, label, optionsOrProps);
3094
- case "textarea":
3095
- return textareaField(name, label, optionsOrProps);
3096
- case "select":
3097
- return selectField(name, label, optionsOrProps);
3098
- case "checkbox":
3099
- return checkboxField(name, label, optionsOrProps);
3100
- case "switch":
3101
- return switchField(name, label, optionsOrProps);
3102
- case "radio":
3103
- return radioField(name, label, optionsOrProps, props);
3104
- case "slider":
3105
- return sliderField(name, label, optionsOrProps);
3106
- case "date":
3107
- return dateField(name, label, optionsOrProps);
3108
- case "file":
3109
- return fileField(name, label, optionsOrProps);
3110
- case "fontPicker":
3111
- return fontPickerField(name, label, optionsOrProps);
3112
- case "content":
3113
- if (typeof optionsOrProps === "string" || optionsOrProps === null) {
3114
- return contentField(optionsOrProps, props);
3115
- }
3116
- if (typeof optionsOrProps === "object" && optionsOrProps !== null) {
3117
- return contentField(
3118
- optionsOrProps.title,
3119
- optionsOrProps.description,
3120
- optionsOrProps
3121
- );
3122
- }
3123
- return contentField(
3124
- name,
3125
- label,
3126
- optionsOrProps
3127
- );
3128
- default:
3129
- throw new Error(`Unknown field type: ${type}`);
3130
- }
3131
- }
3132
- var AdvancedFieldBuilder = class {
3133
- constructor() {
3134
- this.fields = [];
3135
- }
3136
- field(type, name, label, optionsOrProps, props) {
3137
- this.fields.push(createField(type, name, label, optionsOrProps, props));
3138
- return this;
3139
- }
3140
- /**
3141
- * Add a conditional field that shows/hides based on form data
3142
- */
3143
- conditionalField(name, condition, field2) {
3144
- this.fields.push({
3145
- condition,
3146
- field: field2,
3147
- name,
3148
- type: "conditional"
3149
- });
3150
- return this;
3151
- }
3152
- /**
3153
- * Add a field array for dynamic repeating field groups
3154
- */
3155
- fieldArray(name, label, fields, options) {
3156
- this.fields.push({
3157
- addButtonText: options?.addButtonText,
3158
- fields,
3159
- label,
3160
- max: options?.max,
3161
- min: options?.min,
3162
- name,
3163
- removeButtonText: options?.removeButtonText,
3164
- type: "fieldArray"
3165
- });
3166
- return this;
3167
- }
3168
- /**
3169
- * Add a dynamic section that shows/hides based on form data
3170
- */
3171
- dynamicSection(name, condition, fields, options) {
3172
- this.fields.push({
3173
- condition,
3174
- description: options?.description,
3175
- fields,
3176
- name,
3177
- title: options?.title,
3178
- type: "dynamicSection"
3179
- });
3180
- return this;
3181
- }
3182
- /**
3183
- * Build the final field configuration array
3184
- */
3185
- build() {
3186
- return this.fields;
3187
- }
3188
- };
3189
- var FieldArrayItemBuilder = class {
3190
- constructor() {
3191
- this.fields = [];
3192
- }
3193
- field(type, name, label, optionsOrProps, props) {
3194
- this.fields.push(createField(type, name, label, optionsOrProps, props));
3195
- return this;
3196
- }
3197
- /**
3198
- * Build the field array item configuration
3199
- */
3200
- build() {
3201
- return this.fields;
3202
- }
3203
- };
3204
- function createFieldArrayItemBuilder() {
3205
- return new FieldArrayItemBuilder();
3206
- }
3207
- var FieldArrayBuilder = class {
3208
- constructor(arrayName) {
3209
- this.arrayName = arrayName;
3210
- this.fields = [];
3211
- }
3212
- field(type, name, label, optionsOrProps, props) {
3213
- const fullPath = `${this.arrayName}.${name}`;
3214
- const fieldConfig = createField(
3215
- type,
3216
- fullPath,
3217
- label,
3218
- optionsOrProps,
3219
- props
3220
- );
3221
- this.fields.push(fieldConfig);
3222
- return this;
3223
- }
3224
- build() {
3225
- return this.fields;
3226
- }
3227
- };
3228
- function createFieldArrayBuilder(arrayName) {
3229
- return new FieldArrayBuilder(arrayName);
3230
- }
3231
- function createAdvancedBuilder() {
3232
- return new AdvancedFieldBuilder();
3233
- }
3234
-
3235
- // src/builders/TypeInferredBuilder.ts
3236
- import { z as z3 } from "zod";
3237
- var TypeInferredBuilder = class {
3238
- constructor() {
3239
- this.schemaFields = {};
3240
- this.formFields = [];
3241
- }
3242
- /**
3243
- * Add a text field
3244
- */
3245
- text(name, label, options) {
3246
- const { maxLength, minLength, pattern, ...fieldOptions } = options || {};
3247
- let zodType = z3.string();
3248
- if (minLength)
3249
- zodType = zodType.min(
3250
- minLength,
3251
- `${label} must be at least ${minLength} characters`
3252
- );
3253
- if (maxLength)
3254
- zodType = zodType.max(
3255
- maxLength,
3256
- `${label} must be no more than ${maxLength} characters`
3257
- );
3258
- if (pattern)
3259
- zodType = zodType.regex(
3260
- new RegExp(pattern),
3261
- `${label} format is invalid`
3262
- );
3263
- this.schemaFields[name] = zodType;
3264
- this.formFields.push({
3265
- inputProps: { type: "text", ...fieldOptions },
3266
- label,
3267
- name,
3268
- type: "input"
3269
- });
3270
- return this;
3271
- }
3272
- /**
3273
- * Add an email field
3274
- */
3275
- email(name, label, options) {
3276
- this.schemaFields[name] = z3.string().email(`Please enter a valid email address`);
3277
- this.formFields.push({
3278
- inputProps: { type: "email", ...options },
3279
- label,
3280
- name,
3281
- type: "input"
3282
- });
3283
- return this;
3284
- }
3285
- /**
3286
- * Add a number field
3287
- */
3288
- number(name, label, options) {
3289
- const { max, min, step, ...fieldOptions } = options || {};
3290
- let zodType = z3.number();
3291
- if (min !== void 0)
3292
- zodType = zodType.min(min, `${label} must be at least ${min}`);
3293
- if (max !== void 0)
3294
- zodType = zodType.max(max, `${label} must be no more than ${max}`);
3295
- this.schemaFields[name] = zodType;
3296
- this.formFields.push({
3297
- inputProps: { max, min, step, type: "number", ...fieldOptions },
3298
- label,
3299
- name,
3300
- type: "input"
3301
- });
3302
- return this;
3303
- }
3304
- /**
3305
- * Add a textarea field
3306
- */
3307
- textarea(name, label, options) {
3308
- const { minLength, ...fieldOptions } = options || {};
3309
- let zodType = z3.string();
3310
- if (minLength)
3311
- zodType = zodType.min(
3312
- minLength,
3313
- `${label} must be at least ${minLength} characters`
3314
- );
3315
- this.schemaFields[name] = zodType;
3316
- this.formFields.push({
3317
- label,
3318
- name,
3319
- textareaProps: fieldOptions,
3320
- type: "textarea"
3321
- });
3322
- return this;
3323
- }
3324
- /**
3325
- * Add a select field
3326
- */
3327
- select(name, label, options) {
3328
- this.schemaFields[name] = z3.string().min(1, `Please select a ${label.toLowerCase()}`);
3329
- this.formFields.push({
3330
- label,
3331
- name,
3332
- options,
3333
- type: "select"
3334
- });
3335
- return this;
3336
- }
3337
- /**
3338
- * Add a checkbox field
3339
- */
3340
- checkbox(name, label, options) {
3341
- const { required = false, ...fieldOptions } = options || {};
3342
- let zodType = z3.boolean();
3343
- if (required) {
3344
- zodType = zodType.refine(
3345
- (val) => val === true,
3346
- `You must agree to ${label.toLowerCase()}`
3347
- );
3348
- }
3349
- this.schemaFields[name] = zodType;
3350
- this.formFields.push({
3351
- checkboxProps: fieldOptions,
3352
- label,
3353
- name,
3354
- type: "checkbox"
3355
- });
3356
- return this;
3357
- }
3358
- /**
3359
- * Add a switch field
3360
- */
3361
- switch(name, label, options) {
3362
- this.schemaFields[name] = z3.boolean().optional();
3363
- this.formFields.push({
3364
- label,
3365
- name,
3366
- switchProps: options,
3367
- type: "switch"
3368
- });
3369
- return this;
3370
- }
3371
- /**
3372
- * Add a radio field
3373
- */
3374
- radio(name, label, options, fieldOptions) {
3375
- this.schemaFields[name] = z3.string().min(1, `Please select a ${label.toLowerCase()}`);
3376
- this.formFields.push({
3377
- label,
3378
- name,
3379
- radioOptions: options,
3380
- radioProps: fieldOptions,
3381
- type: "radio"
3382
- });
3383
- return this;
3384
- }
3385
- /**
3386
- * Add a slider field
3387
- */
3388
- slider(name, label, options) {
3389
- const { max = 100, min = 0, step = 1, ...fieldOptions } = options || {};
3390
- let zodType = z3.number();
3391
- if (min !== void 0)
3392
- zodType = zodType.min(min, `${label} must be at least ${min}`);
3393
- if (max !== void 0)
3394
- zodType = zodType.max(max, `${label} must be no more than ${max}`);
3395
- this.schemaFields[name] = zodType;
3396
- this.formFields.push({
3397
- label,
3398
- name,
3399
- sliderProps: {
3400
- maxValue: max,
3401
- minValue: min,
3402
- step,
3403
- ...fieldOptions
3404
- },
3405
- type: "slider"
3406
- });
3407
- return this;
3408
- }
3409
- /**
3410
- * Add a date field
3411
- */
3412
- date(name, label, options) {
3413
- this.schemaFields[name] = z3.string().min(1, `${label} is required`);
3414
- this.formFields.push({
3415
- dateProps: options,
3416
- label,
3417
- name,
3418
- type: "date"
3419
- });
3420
- return this;
3421
- }
3422
- /**
3423
- * Add a file field
3424
- */
3425
- file(name, label, options) {
3426
- this.schemaFields[name] = z3.any().optional();
3427
- this.formFields.push({
3428
- fileProps: options,
3429
- label,
3430
- name,
3431
- type: "file"
3432
- });
3433
- return this;
3434
- }
3435
- /**
3436
- * Build the final schema and fields
3437
- */
3438
- build() {
3439
- return {
3440
- fields: this.formFields,
3441
- schema: z3.object(this.schemaFields)
3442
- };
3443
- }
3444
- };
3445
- function createTypeInferredBuilder() {
3446
- return new TypeInferredBuilder();
3447
- }
3448
- function defineInferredForm(fieldDefinitions) {
3449
- const builder = createTypeInferredBuilder();
3450
- fieldDefinitions(builder);
3451
- return builder.build();
3452
- }
3453
- var field = {
3454
- checkbox: (name, label, options) => {
3455
- const builder = new TypeInferredBuilder();
3456
- return builder.checkbox(name, label, options);
3457
- },
3458
- date: (name, label, options) => {
3459
- const builder = new TypeInferredBuilder();
3460
- return builder.date(name, label, options);
3461
- },
3462
- email: (name, label, options) => {
3463
- const builder = new TypeInferredBuilder();
3464
- return builder.email(name, label, options);
3465
- },
3466
- file: (name, label, options) => {
3467
- const builder = new TypeInferredBuilder();
3468
- return builder.file(name, label, options);
3469
- },
3470
- number: (name, label, options) => {
3471
- const builder = new TypeInferredBuilder();
3472
- return builder.number(name, label, options);
3473
- },
3474
- radio: (name, label, options, fieldOptions) => {
3475
- const builder = new TypeInferredBuilder();
3476
- return builder.radio(name, label, options, fieldOptions);
3477
- },
3478
- select: (name, label, options) => {
3479
- const builder = new TypeInferredBuilder();
3480
- return builder.select(name, label, options);
3481
- },
3482
- slider: (name, label, options) => {
3483
- const builder = new TypeInferredBuilder();
3484
- return builder.slider(name, label, options);
3485
- },
3486
- switch: (name, label, options) => {
3487
- const builder = new TypeInferredBuilder();
3488
- return builder.switch(name, label, options);
3489
- },
3490
- text: (name, label, options) => {
3491
- const builder = new TypeInferredBuilder();
3492
- return builder.text(name, label, options);
3493
- },
3494
- textarea: (name, label, options) => {
3495
- const builder = new TypeInferredBuilder();
3496
- return builder.textarea(name, label, options);
3497
- }
3498
- };
3499
-
3500
- // src/builders/NestedPathBuilder.ts
3501
- var NestedPathBuilder = class {
3502
- constructor() {
3503
- this.fields = [];
3504
- }
3505
- /**
3506
- * Create a nested object path builder
3507
- * Usage: builder.nest("address").field("street", "Street Address")
3508
- */
3509
- nest(path) {
3510
- return new NestedObjectBuilder(this, path);
3511
- }
3512
- /**
3513
- * Create a section-based path builder
3514
- * Usage: builder.section("shipping").field("street", "Street Address")
3515
- */
3516
- section(path) {
3517
- return new SectionBuilder(this, path);
3518
- }
3519
- /**
3520
- * Add a field with single path
3521
- * Usage: builder.field("firstName", "First Name")
3522
- */
3523
- field(name, label, type = "input", props) {
3524
- this.fields.push({
3525
- label,
3526
- name,
3527
- type,
3528
- ...props
3529
- });
3530
- return this;
3531
- }
3532
- /**
3533
- * Add a field with path segments
3534
- * Usage: builder.fieldPath(["user", "profile", "name"], "Full Name")
3535
- */
3536
- fieldPath(path, label, type = "input", props) {
3537
- const name = path.join(".");
3538
- this.fields.push({
3539
- label,
3540
- name,
3541
- type,
3542
- ...props
3543
- });
3544
- return this;
3545
- }
3546
- /**
3547
- * Add a field with template literal path
3548
- * Usage: builder.field`user.profile.name`("Full Name")
3549
- */
3550
- fieldTemplate(path) {
3551
- const pathString = path[0];
3552
- return new FieldTemplateBuilder(this, pathString);
3553
- }
3554
- /**
3555
- * Return to the parent builder (no-op for root builder)
3556
- */
3557
- end() {
3558
- return this;
3559
- }
3560
- build() {
3561
- return this.fields;
3562
- }
3563
- };
3564
- var NestedObjectBuilder = class _NestedObjectBuilder {
3565
- constructor(parent, path) {
3566
- this.parent = parent;
3567
- this.path = path;
3568
- }
3569
- /**
3570
- * Add a field to the current nested path
3571
- */
3572
- field(fieldName, label, type = "input", props) {
3573
- const fullPath = `${this.path}.${fieldName}`;
3574
- this.parent.fields.push({
3575
- label,
3576
- name: fullPath,
3577
- type,
3578
- ...props
3579
- });
3580
- return this;
3581
- }
3582
- /**
3583
- * Nest deeper into the object
3584
- */
3585
- nest(subPath) {
3586
- return new _NestedObjectBuilder(
3587
- this.parent,
3588
- `${this.path}.${subPath}`
3589
- );
3590
- }
3591
- /**
3592
- * Return to the parent builder
3593
- */
3594
- end() {
3595
- return this.parent;
3596
- }
3597
- };
3598
- var SectionBuilder = class {
3599
- constructor(parent, path) {
3600
- this.parent = parent;
3601
- this.path = path;
3602
- }
3603
- /**
3604
- * Add a field to the current section
3605
- */
3606
- field(fieldName, label, type = "input", props) {
3607
- const fullPath = `${this.path}.${fieldName}`;
3608
- this.parent.fields.push({
3609
- label,
3610
- name: fullPath,
3611
- type,
3612
- ...props
3613
- });
3614
- return this;
3615
- }
3616
- /**
3617
- * Add multiple fields to the section
3618
- */
3619
- fields(fieldDefinitions) {
3620
- fieldDefinitions.forEach((field2) => {
3621
- this.field(field2.name, field2.label, field2.type, field2.props);
3622
- });
3623
- return this;
3624
- }
3625
- /**
3626
- * Nest deeper into the section
3627
- */
3628
- nest(subPath) {
3629
- return new NestedObjectBuilder(
3630
- this.parent,
3631
- `${this.path}.${subPath}`
3632
- );
3633
- }
3634
- /**
3635
- * Return to the parent builder
3636
- */
3637
- end() {
3638
- return this.parent;
3639
- }
3640
- };
3641
- var FieldTemplateBuilder = class {
3642
- constructor(parent, path) {
3643
- this.parent = parent;
3644
- this.path = path;
3645
- }
3646
- /**
3647
- * Complete the field definition
3648
- */
3649
- complete(label, type = "input", props) {
3650
- this.parent.fields.push({
3651
- label,
3652
- name: this.path,
3653
- type,
3654
- ...props
3655
- });
3656
- return this.parent;
3657
- }
3658
- };
3659
- function createNestedPathBuilder() {
3660
- return new NestedPathBuilder();
3661
- }
3662
-
3663
- // src/hooks/useDebouncedValidation.ts
3664
- import { useCallback as useCallback2, useEffect as useEffect2, useRef } from "react";
3665
- function useDebouncedValidation(form, options = {}) {
3666
- const { delay = 300, enabled = true, fields } = options;
3667
- const timeoutRef = useRef(
3668
- void 0
3669
- );
3670
- const lastValuesRef = useRef({});
3671
- const debouncedTrigger = useCallback2(() => {
3672
- if (!enabled) return;
3673
- if (timeoutRef.current) {
3674
- clearTimeout(timeoutRef.current);
3675
- timeoutRef.current = void 0;
3676
- }
3677
- timeoutRef.current = setTimeout(async () => {
3678
- const currentValues = form.getValues();
3679
- const lastValues = lastValuesRef.current;
3680
- const hasChanges = fields ? fields.some((field2) => currentValues[field2] !== lastValues[field2]) : Object.keys(currentValues).some(
3681
- (key) => currentValues[key] !== lastValues[key]
3682
- );
3683
- if (hasChanges) {
3684
- lastValuesRef.current = { ...currentValues };
3685
- if (fields && fields.length > 0) {
3686
- await form.trigger(fields);
3687
- } else {
3688
- await form.trigger();
3689
- }
3690
- }
3691
- }, delay);
3692
- }, [form, delay, fields, enabled]);
3693
- useEffect2(() => {
3694
- return () => {
3695
- if (timeoutRef.current) {
3696
- clearTimeout(timeoutRef.current);
3697
- timeoutRef.current = void 0;
3698
- }
3699
- };
3700
- }, []);
3701
- useEffect2(() => {
3702
- if (form.formState.isSubmitSuccessful) {
3703
- lastValuesRef.current = {};
3704
- }
3705
- }, [form.formState.isSubmitSuccessful]);
3706
- return {
3707
- debouncedTrigger,
3708
- isDebouncing: !!timeoutRef.current
3709
- };
3710
- }
3711
- function useDebouncedFieldValidation(form, fieldName, options = {}) {
3712
- const { delay = 300, enabled = true } = options;
3713
- const timeoutRef = useRef(
3714
- void 0
3715
- );
3716
- const debouncedFieldTrigger = useCallback2(() => {
3717
- if (!enabled) return;
3718
- if (timeoutRef.current) {
3719
- clearTimeout(timeoutRef.current);
3720
- }
3721
- timeoutRef.current = setTimeout(async () => {
3722
- await form.trigger(fieldName);
3723
- }, delay);
3724
- }, [form, fieldName, delay, enabled]);
3725
- useEffect2(() => {
3726
- return () => {
3727
- if (timeoutRef.current) {
3728
- clearTimeout(timeoutRef.current);
3729
- timeoutRef.current = void 0;
3730
- }
3731
- };
3732
- }, []);
3733
- return {
3734
- debouncedFieldTrigger,
3735
- isDebouncing: !!timeoutRef.current
3736
- };
3737
- }
3738
-
3739
- // src/hooks/useInferredForm.ts
3740
- import { useForm as useForm3 } from "react-hook-form";
3741
- var zodResolver;
3742
- try {
3743
- zodResolver = __require("@hookform/resolvers/zod").zodResolver;
3744
- } catch {
3745
- }
3746
- function useInferredForm(schema, fields, options = {}) {
3747
- const {
3748
- defaultValues,
3749
- delayError = 0,
3750
- mode = "onChange",
3751
- reValidateMode = "onChange",
3752
- shouldFocusError = true,
3753
- shouldUnregister = false
3754
- } = options;
3755
- return useForm3({
3756
- defaultValues,
3757
- delayError,
3758
- mode,
3759
- resolver: zodResolver ? zodResolver(schema) : void 0,
3760
- reValidateMode,
3761
- shouldFocusError,
3762
- shouldUnregister
3763
- });
3764
- }
3765
- function useTypeInferredForm(formConfig, options = {}) {
3766
- return useInferredForm(formConfig.schema, formConfig.fields, options);
3767
- }
3768
-
3769
- // src/utils/performance.ts
3770
- import { useCallback as useCallback3, useMemo as useMemo2, useRef as useRef2 } from "react";
3771
- function debounce(func, delay) {
3772
- let timeoutId;
3773
- return (...args) => {
3774
- clearTimeout(timeoutId);
3775
- timeoutId = setTimeout(() => func(...args), delay);
3776
- };
3777
- }
3778
- function throttle(func, limit) {
3779
- let inThrottle;
3780
- return (...args) => {
3781
- if (!inThrottle) {
3782
- func(...args);
3783
- inThrottle = true;
3784
- setTimeout(() => inThrottle = false, limit);
3785
- }
3786
- };
3787
- }
3788
- function useMemoizedCallback(callback, deps) {
3789
- const callbackRef = useRef2(callback);
3790
- callbackRef.current = callback;
3791
- return useCallback3(
3792
- ((...args) => callbackRef.current(...args)),
3793
- deps
3794
- );
3795
- }
3796
- function shallowEqual(prevProps, nextProps) {
3797
- const prevKeys = Object.keys(prevProps);
3798
- const nextKeys = Object.keys(nextProps);
3799
- if (prevKeys.length !== nextKeys.length) {
3800
- return false;
3801
- }
3802
- for (const key of prevKeys) {
3803
- if (prevProps[key] !== nextProps[key]) {
3804
- return false;
3805
- }
3806
- }
3807
- return true;
3808
- }
3809
- function deepEqual(prevProps, nextProps) {
3810
- if (prevProps === nextProps) {
3811
- return true;
3812
- }
3813
- if (typeof prevProps !== typeof nextProps) {
3814
- return false;
3815
- }
3816
- if (typeof prevProps !== "object" || prevProps === null || nextProps === null) {
3817
- return prevProps === nextProps;
3818
- }
3819
- const prevKeys = Object.keys(prevProps);
3820
- const nextKeys = Object.keys(nextProps);
3821
- if (prevKeys.length !== nextKeys.length) {
3822
- return false;
3823
- }
3824
- for (const key of prevKeys) {
3825
- if (!nextKeys.includes(key)) {
3826
- return false;
3827
- }
3828
- if (!deepEqual(prevProps[key], nextProps[key])) {
3829
- return false;
3830
- }
3831
- }
3832
- return true;
3833
- }
3834
- function usePerformanceMonitor(componentName, enabled = false) {
3835
- const renderCountRef = useRef2(0);
3836
- const lastRenderTimeRef = useRef2(Date.now());
3837
- if (enabled) {
3838
- renderCountRef.current += 1;
3839
- const now = Date.now();
3840
- const timeSinceLastRender = now - lastRenderTimeRef.current;
3841
- console.log(`[Performance] ${componentName}:`, {
3842
- renderCount: renderCountRef.current,
3843
- timeSinceLastRender: `${timeSinceLastRender}ms`
3844
- });
3845
- lastRenderTimeRef.current = now;
3846
- }
3847
- return {
3848
- renderCount: renderCountRef.current,
3849
- resetRenderCount: () => {
3850
- renderCountRef.current = 0;
3851
- }
3852
- };
3853
- }
3854
- function createOptimizedFieldHandler(onChange, options = {}) {
3855
- const { debounce: debounceMs, throttle: throttleMs } = options;
3856
- let handler = onChange;
3857
- if (throttleMs) {
3858
- handler = throttle(handler, throttleMs);
3859
- }
3860
- if (debounceMs) {
3861
- handler = debounce(handler, debounceMs);
3862
- }
3863
- return handler;
3864
- }
3865
- function useMemoizedFieldProps(props, deps) {
3866
- return useMemo2(() => props, deps);
3867
- }
3868
-
3869
- // src/utils/arraySync.ts
3870
- function syncArrays(options) {
3871
- const { current, existing, getId } = options;
3872
- const existingMap = /* @__PURE__ */ new Map();
3873
- const currentMap = /* @__PURE__ */ new Map();
3874
- existing.forEach((item) => {
3875
- const id = getId(item);
3876
- if (id !== void 0) {
3877
- existingMap.set(id, item);
3878
- }
3879
- });
3880
- current.forEach((item) => {
3881
- const id = getId(item);
3882
- if (id !== void 0) {
3883
- currentMap.set(id, item);
3884
- }
3885
- });
3886
- const toDelete = [];
3887
- existingMap.forEach((item, id) => {
3888
- if (!currentMap.has(id)) {
3889
- toDelete.push(item);
3890
- }
3891
- });
3892
- const toUpdate = [];
3893
- existingMap.forEach((existingItem, id) => {
3894
- const currentItem = currentMap.get(id);
3895
- if (currentItem) {
3896
- toUpdate.push({ current: currentItem, existing: existingItem });
3897
- }
3898
- });
3899
- const toCreate = [];
3900
- currentMap.forEach((item, id) => {
3901
- if (!existingMap.has(id)) {
3902
- toCreate.push(item);
3903
- }
3904
- });
3905
- return {
3906
- toCreate,
3907
- toDelete,
3908
- toUpdate
3909
- };
3910
- }
3911
-
3912
- // src/utils/createFieldArrayCustomConfig.tsx
3913
- import React25 from "react";
3914
- import { useFieldArray as useFieldArray2 } from "react-hook-form";
3915
- import { Button as Button6 } from "@heroui/react";
3916
- function createFieldArrayCustomConfig(options) {
3917
- const {
3918
- className,
3919
- defaultItem,
3920
- enableReordering = false,
3921
- label,
3922
- max = 10,
3923
- min = 0,
3924
- name,
3925
- renderAddButton,
3926
- renderItem
3927
- } = options;
3928
- return {
3929
- className,
3930
- label,
3931
- name,
3932
- // ArrayPath is compatible with Path for CustomFieldConfig
3933
- render: ({ control, errors, form }) => {
3934
- const { append, fields, move, remove } = useFieldArray2({
3935
- control,
3936
- name
3937
- });
3938
- const canAdd = fields.length < max;
3939
- const canRemove = fields.length > min;
3940
- const handleAdd = () => {
3941
- if (canAdd) {
3942
- if (defaultItem) {
3943
- append(defaultItem());
3944
- } else {
3945
- append({});
3946
- }
3947
- }
3948
- };
3949
- const handleRemove = (index) => {
3950
- if (canRemove) {
3951
- remove(index);
3952
- }
3953
- };
3954
- const handleMoveUp = (index) => {
3955
- if (enableReordering && index > 0) {
3956
- move(index, index - 1);
3957
- }
3958
- };
3959
- const handleMoveDown = (index) => {
3960
- if (enableReordering && index < fields.length - 1) {
3961
- move(index, index + 1);
3962
- }
3963
- };
3964
- return /* @__PURE__ */ React25.createElement("div", { className }, /* @__PURE__ */ React25.createElement("div", { className: "space-y-4" }, fields.map((field2, index) => {
3965
- const canMoveUp = enableReordering && index > 0;
3966
- const canMoveDown = enableReordering && index < fields.length - 1;
3967
- return /* @__PURE__ */ React25.createElement(React25.Fragment, { key: field2.id }, renderItem({
3968
- canMoveDown,
3969
- canMoveUp,
3970
- control,
3971
- errors,
3972
- field: field2,
3973
- fields,
3974
- form,
3975
- index,
3976
- onMoveDown: () => handleMoveDown(index),
3977
- onMoveUp: () => handleMoveUp(index),
3978
- onRemove: () => handleRemove(index)
3979
- }));
3980
- }), fields.length === 0 && renderAddButton ? /* @__PURE__ */ React25.createElement("div", { className: "text-center py-8 text-gray-500" }, /* @__PURE__ */ React25.createElement("p", null, "No ", label?.toLowerCase() || "items", " added yet."), renderAddButton({ canAdd, onAdd: handleAdd })) : null, fields.length > 0 && renderAddButton ? renderAddButton({ canAdd, onAdd: handleAdd }) : canAdd && /* @__PURE__ */ React25.createElement(
3981
- Button6,
3982
- {
3983
- variant: "bordered",
3984
- onPress: handleAdd,
3985
- className: "w-full"
3986
- },
3987
- "Add Item"
3988
- )));
3989
- },
3990
- type: "custom"
3991
- };
3992
- }
3993
-
3994
- // src/builders/validation-helpers.ts
3995
- import { z as z4 } from "zod";
3996
- var validationPatterns = {
3997
- // Credit card validation
3998
- creditCard: z4.string().regex(
3999
- // eslint-disable-next-line no-useless-escape
4000
- /^[0-9]{4}[\s\-]?[0-9]{4}[\s\-]?[0-9]{4}[\s\-]?[0-9]{4}$/,
4001
- "Please enter a valid credit card number"
4002
- ),
4003
- // Date validation (MM/DD/YYYY)
4004
- date: z4.string().regex(
4005
- /^(0[1-9]|1[0-2])\/(0[1-9]|[12][0-9]|3[01])\/\d{4}$/,
4006
- "Please enter a valid date (MM/DD/YYYY)"
4007
- ),
4008
- // Email validation
4009
- email: z4.string().email("Please enter a valid email address"),
4010
- // Password validation
4011
- 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(
4012
- /[^A-Za-z0-9]/,
4013
- "Password must contain at least one special character"
4014
- ),
4015
- // Phone number validation (international)
4016
- phoneInternational: z4.string().regex(/^\+?[\d\s\-\(\)]+$/, "Please enter a valid phone number"),
4017
- // Phone number validation (US format)
4018
- phoneUS: z4.string().regex(
4019
- /^\(\d{3}\) \d{3}-\d{4}$/,
4020
- "Please enter a valid phone number (XXX) XXX-XXXX"
4021
- ),
4022
- // SSN validation
4023
- ssn: z4.string().regex(/^\d{3}-\d{2}-\d{4}$/, "Please enter a valid SSN (XXX-XX-XXXX)"),
4024
- // Strong password validation
4025
- 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(
4026
- /[^A-Za-z0-9]/,
4027
- "Password must contain at least one special character"
4028
- ),
4029
- // Time validation (HH:MM AM/PM)
4030
- time: z4.string().regex(
4031
- /^(0[1-9]|1[0-2]):[0-5][0-9] (AM|PM)$/i,
4032
- "Please enter a valid time (HH:MM AM/PM)"
4033
- ),
4034
- // URL validation
4035
- url: z4.string().url("Please enter a valid URL"),
4036
- // ZIP code validation
4037
- zipCode: z4.string().regex(/^\d{5}(-\d{4})?$/, "Please enter a valid ZIP code")
4038
- };
4039
- var asyncValidation = {
4040
- /**
4041
- * Email availability check
4042
- */
4043
- emailAvailability: async (email) => {
4044
- return new Promise((resolve) => {
4045
- setTimeout(() => {
4046
- const takenEmails = ["test@example.com", "admin@example.com"];
4047
- resolve(!takenEmails.includes(email));
4048
- }, 1e3);
4049
- });
4050
- },
4051
- /**
4052
- * Username availability check
4053
- */
4054
- usernameAvailability: async (username) => {
4055
- return new Promise((resolve) => {
4056
- setTimeout(() => {
4057
- const takenUsernames = ["admin", "test", "user"];
4058
- resolve(!takenUsernames.includes(username.toLowerCase()));
4059
- }, 1e3);
4060
- });
4061
- }
4062
- };
4063
- var errorMessages = {
4064
- date: () => "Please enter a valid date",
4065
- email: () => "Please enter a valid email address",
4066
- max: (fieldName, max) => `${fieldName} must be no more than ${max}`,
4067
- maxLength: (fieldName, max) => `${fieldName} must be no more than ${max} characters`,
4068
- min: (fieldName, min) => `${fieldName} must be at least ${min}`,
4069
- minLength: (fieldName, min) => `${fieldName} must be at least ${min} characters`,
4070
- pattern: (fieldName) => `${fieldName} format is invalid`,
4071
- phone: () => "Please enter a valid phone number",
4072
- required: (fieldName) => `${fieldName} is required`,
4073
- time: () => "Please enter a valid time",
4074
- url: () => "Please enter a valid URL"
4075
- };
4076
- var serverValidation = {
4077
- /**
4078
- * Apply server errors to form
4079
- */
4080
- applyServerErrors: (errors, setError) => {
4081
- Object.entries(errors).forEach(([field2, messages]) => {
4082
- setError(field2, {
4083
- message: messages[0],
4084
- type: "server"
4085
- // Use first error message
4086
- });
4087
- });
4088
- },
4089
- /**
4090
- * Clear server errors
4091
- */
4092
- clearServerErrors: (fields, clearErrors) => {
4093
- fields.forEach((field2) => {
4094
- clearErrors(field2, "server");
4095
- });
4096
- }
4097
- };
4098
- var validationUtils = {
4099
- /**
4100
- * Debounced validation
4101
- */
4102
- debounceValidation: (fn, delay = 300) => {
4103
- let timeoutId;
4104
- return (...args) => {
4105
- clearTimeout(timeoutId);
4106
- timeoutId = setTimeout(() => fn(...args), delay);
4107
- };
4108
- },
4109
- /**
4110
- * Get field error message
4111
- */
4112
- getFieldError: (errors, field2) => {
4113
- return errors[field2];
4114
- },
4115
- /**
4116
- * Check if field has error
4117
- */
4118
- hasFieldError: (errors, field2) => {
4119
- return !!errors[field2];
4120
- },
4121
- /**
4122
- * Validate form data against schema
4123
- */
4124
- validateForm: async (data, schema) => {
4125
- try {
4126
- await schema.parseAsync(data);
4127
- return { errors: {}, success: true };
4128
- } catch (error) {
4129
- if (error instanceof z4.ZodError) {
4130
- const errors = {};
4131
- error.issues.forEach((err) => {
4132
- const path = err.path.join(".");
4133
- errors[path] = err.message;
4134
- });
4135
- return { errors, success: false };
4136
- }
4137
- throw error;
4138
- }
4139
- }
4140
- };
4141
- export {
4142
- AdvancedFieldBuilder,
4143
- AutocompleteField,
4144
- BasicFormBuilder,
4145
- CheckboxField,
4146
- CommonFields,
4147
- ConditionalField,
4148
- ConfigurableForm,
4149
- ContentField,
4150
- DateField,
4151
- DynamicSectionField,
4152
- FieldArrayBuilder,
4153
- FieldArrayField,
4154
- FieldArrayItemBuilder,
4155
- FileField,
4156
- FontPickerField,
4157
- FormField,
4158
- FormFieldHelpers,
4159
- FormProvider,
4160
- FormStatus,
4161
- FormToast,
4162
- HeroHookFormProvider,
4163
- InputField,
4164
- RadioGroupField,
4165
- SelectField,
4166
- ServerActionForm,
4167
- SimpleForm,
4168
- SliderField,
4169
- SubmitButton,
4170
- SwitchField,
4171
- TextareaField,
4172
- TypeInferredBuilder,
4173
- ZodForm,
4174
- applyServerErrors,
4175
- asyncValidation,
4176
- commonValidations,
4177
- createAdvancedBuilder,
4178
- createBasicFormBuilder,
4179
- createDateSchema,
4180
- createEmailSchema,
4181
- createField,
4182
- createFieldArrayBuilder,
4183
- createFieldArrayCustomConfig,
4184
- createFieldArrayItemBuilder,
4185
- createFileSchema,
4186
- createFormTestUtils,
4187
- createFutureDateSchema,
4188
- createMaxLengthSchema,
4189
- createMinLengthSchema,
4190
- createMockFormData,
4191
- createMockFormErrors,
4192
- createNestedPathBuilder,
4193
- createNumberRangeSchema,
4194
- createOptimizedFieldHandler,
4195
- createPasswordSchema,
4196
- createPastDateSchema,
4197
- createPhoneSchema,
4198
- createRequiredCheckboxSchema,
4199
- createRequiredSchema,
4200
- createTypeInferredBuilder,
4201
- createUrlSchema,
4202
- createZodFormConfig,
4203
- crossFieldValidation,
4204
- debounce,
4205
- deepEqual,
4206
- defineInferredForm,
4207
- errorMessages,
4208
- field,
4209
- getFieldError,
4210
- getFormErrors,
4211
- hasFieldError,
4212
- hasFormErrors,
4213
- serverValidation,
4214
- shallowEqual,
4215
- simulateFieldInput,
4216
- simulateFormSubmission,
4217
- syncArrays,
4218
- throttle,
4219
- useDebouncedFieldValidation,
4220
- useDebouncedValidation,
4221
- useEnhancedFormState,
4222
- useFormContext5 as useFormContext,
4223
- useFormHelper,
4224
- useHeroForm,
4225
- useHeroHookFormDefaults,
4226
- useInferredForm,
4227
- useMemoizedCallback,
4228
- useMemoizedFieldProps,
4229
- usePerformanceMonitor,
4230
- useTypeInferredForm,
4231
- useZodForm,
4232
- validationPatterns,
4233
- validationUtils,
4234
- waitForFormState
4235
- };