@datum-cloud/datum-ui 0.5.0 → 0.6.0-alpha.b817c77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -40
- package/dist/adapter-context-B7L2ucTr.mjs +25 -0
- package/dist/components/features/form/adapter-context.d.ts +17 -0
- package/dist/components/features/form/adapter-context.d.ts.map +1 -0
- package/dist/components/features/form/adapter-types.d.ts +100 -0
- package/dist/components/features/form/adapter-types.d.ts.map +1 -0
- package/dist/components/features/form/adapters/conform/conform-adapter.d.ts +9 -0
- package/dist/components/features/form/adapters/conform/conform-adapter.d.ts.map +1 -0
- package/dist/components/features/form/adapters/conform/conform-provider.d.ts +22 -0
- package/dist/components/features/form/adapters/conform/conform-provider.d.ts.map +1 -0
- package/dist/components/features/form/adapters/conform/index.d.ts +3 -0
- package/dist/components/features/form/adapters/conform/index.d.ts.map +1 -0
- package/dist/components/features/form/adapters/rhf/index.d.ts +3 -0
- package/dist/components/features/form/adapters/rhf/index.d.ts.map +1 -0
- package/dist/components/features/form/adapters/rhf/rhf-adapter.d.ts +10 -0
- package/dist/components/features/form/adapters/rhf/rhf-adapter.d.ts.map +1 -0
- package/dist/components/features/form/adapters/rhf/rhf-provider.d.ts +22 -0
- package/dist/components/features/form/adapters/rhf/rhf-provider.d.ts.map +1 -0
- package/dist/components/features/form/components/form-autocomplete.d.ts.map +1 -1
- package/dist/components/features/form/components/form-checkbox.d.ts.map +1 -1
- package/dist/components/features/form/components/form-copy-box.d.ts.map +1 -1
- package/dist/components/features/form/components/form-field-array.d.ts +5 -17
- package/dist/components/features/form/components/form-field-array.d.ts.map +1 -1
- package/dist/components/features/form/components/form-field.d.ts +7 -21
- package/dist/components/features/form/components/form-field.d.ts.map +1 -1
- package/dist/components/features/form/components/form-input-group.d.ts +4 -4
- package/dist/components/features/form/components/form-input-group.d.ts.map +1 -1
- package/dist/components/features/form/components/form-input.d.ts.map +1 -1
- package/dist/components/features/form/components/form-radio-group.d.ts.map +1 -1
- package/dist/components/features/form/components/form-root.d.ts +5 -25
- package/dist/components/features/form/components/form-root.d.ts.map +1 -1
- package/dist/components/features/form/components/form-select.d.ts.map +1 -1
- package/dist/components/features/form/components/form-switch.d.ts.map +1 -1
- package/dist/components/features/form/components/form-textarea.d.ts.map +1 -1
- package/dist/components/features/form/components/stepper/form-stepper.d.ts.map +1 -1
- package/dist/components/features/form/context/form-context.d.ts +2 -2
- package/dist/components/features/form/context/form-context.d.ts.map +1 -1
- package/dist/components/features/form/hooks/use-field.d.ts +12 -18
- package/dist/components/features/form/hooks/use-field.d.ts.map +1 -1
- package/dist/components/features/form/hooks/use-watch.d.ts +9 -20
- package/dist/components/features/form/hooks/use-watch.d.ts.map +1 -1
- package/dist/components/features/form/index.d.ts +33 -27
- package/dist/components/features/form/index.d.ts.map +1 -1
- package/dist/components/features/form/types/index.d.ts +32 -32
- package/dist/components/features/form/types/index.d.ts.map +1 -1
- package/dist/components/features/form/utils/get-field-constraints.d.ts +11 -0
- package/dist/components/features/form/utils/get-field-constraints.d.ts.map +1 -0
- package/dist/date-picker/index.mjs +1 -1
- package/dist/form/adapters/conform/index.mjs +237 -0
- package/dist/form/adapters/rhf/index.mjs +181 -0
- package/dist/form/index.mjs +3 -2
- package/dist/{form-Co3fM4B7.mjs → form-BE1xBne4.mjs} +328 -579
- package/dist/get-field-constraints-BPMW8VvY.mjs +48 -0
- package/dist/grid/index.mjs +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/index.mjs +12 -11
- package/dist/input-number/index.mjs +1 -1
- package/dist/map/index.mjs +1 -1
- package/dist/{map-ClxB41Hg.mjs → map-Cw7u8r6E.mjs} +1 -1
- package/dist/more-actions/index.mjs +1 -1
- package/dist/page-title/index.mjs +1 -1
- package/dist/tag-input/index.mjs +1 -1
- package/dist/task-queue/index.mjs +1 -1
- package/package.json +22 -2
- /package/dist/{col-q-J99UHe.mjs → col-YBbQ5wlb.mjs} +0 -0
- /package/dist/{hooks-Cb7YlxN4.mjs → hooks-DYjN7lvC.mjs} +0 -0
- /package/dist/{input-number-mDB-5M5C.mjs → input-number-DEjXG2I6.mjs} +0 -0
- /package/dist/{map-leaflet-imports-CaMm_rdF.mjs → map-leaflet-imports-D6nTEOIh.mjs} +0 -0
- /package/dist/{more-actions-CGagbIDT.mjs → more-actions-BNQ2yfWZ.mjs} +0 -0
- /package/dist/{page-title-R7QbfbWp.mjs → page-title-CNiRNZ7p.mjs} +0 -0
- /package/dist/{tag-input-BVSwNcRd.mjs → tag-input-BKed-cul.mjs} +0 -0
- /package/dist/{task-queue-dropdown-DyM5R8KF.mjs → task-queue-dropdown-Di_Wjspz.mjs} +0 -0
- /package/dist/{to-api-format-BnbRFYQI.mjs → to-api-format-Cq4prffn.mjs} +0 -0
|
@@ -13,14 +13,13 @@ import { t as Textarea } from "./textarea-BwD-MmTV.mjs";
|
|
|
13
13
|
import { t as Autocomplete } from "./autocomplete-V5-qslzS.mjs";
|
|
14
14
|
import { t as toast } from "./toast-BWnN5fax.mjs";
|
|
15
15
|
import { t as useCopyToClipboard } from "./use-copy-to-clipboard-BGdTmkFV.mjs";
|
|
16
|
+
import { n as useAdapter } from "./adapter-context-B7L2ucTr.mjs";
|
|
16
17
|
import { defineStepper } from "./stepper/index.mjs";
|
|
17
18
|
import { InputWithAddons } from "./input-with-addons/index.mjs";
|
|
18
19
|
import { CheckIcon, CircleHelp, CopyIcon } from "lucide-react";
|
|
19
20
|
import * as React$1 from "react";
|
|
20
21
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
21
22
|
import { z } from "zod";
|
|
22
|
-
import { FormProvider, getFormProps, getInputProps, getTextareaProps, useForm, useFormMetadata, useInputControl } from "@conform-to/react";
|
|
23
|
-
import { getZodConstraint, parseWithZod } from "@conform-to/zod/v4";
|
|
24
23
|
//#region src/components/features/form/context/field-context.tsx
|
|
25
24
|
const FieldContext = React$1.createContext(null);
|
|
26
25
|
function FieldProvider({ children, value }) {
|
|
@@ -82,17 +81,16 @@ function useOptionalFieldContext() {
|
|
|
82
81
|
* ```
|
|
83
82
|
*/
|
|
84
83
|
function FormAutocomplete({ disabled, className, ...props }) {
|
|
85
|
-
const {
|
|
86
|
-
const control = useInputControl(fieldMeta);
|
|
84
|
+
const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
|
|
87
85
|
const isDisabled = disabled ?? fieldDisabled;
|
|
88
86
|
const hasErrors = errors && errors.length > 0;
|
|
89
|
-
const
|
|
87
|
+
const value = fieldState?.value != null ? String(fieldState.value) : "";
|
|
90
88
|
return /* @__PURE__ */ jsx(Autocomplete, {
|
|
91
89
|
...props,
|
|
92
|
-
name:
|
|
93
|
-
id
|
|
94
|
-
value
|
|
95
|
-
onValueChange:
|
|
90
|
+
name: fieldState?.name,
|
|
91
|
+
id,
|
|
92
|
+
value,
|
|
93
|
+
onValueChange: (val) => fieldState?.change(val),
|
|
96
94
|
disabled: isDisabled,
|
|
97
95
|
triggerClassName: cn(hasErrors && "border-destructive", props.triggerClassName),
|
|
98
96
|
className
|
|
@@ -102,7 +100,7 @@ FormAutocomplete.displayName = "Form.Autocomplete";
|
|
|
102
100
|
//#endregion
|
|
103
101
|
//#region src/components/features/form/context/form-context.tsx
|
|
104
102
|
const FormContext = React$1.createContext(null);
|
|
105
|
-
function FormProvider
|
|
103
|
+
function FormProvider({ children, value }) {
|
|
106
104
|
return /* @__PURE__ */ jsx(FormContext, {
|
|
107
105
|
value,
|
|
108
106
|
children
|
|
@@ -161,27 +159,21 @@ FormButton.displayName = "Form.Button";
|
|
|
161
159
|
* ```
|
|
162
160
|
*/
|
|
163
161
|
function FormCheckbox({ label, disabled, className }) {
|
|
164
|
-
const {
|
|
165
|
-
const control = useInputControl(fieldMeta);
|
|
162
|
+
const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
|
|
166
163
|
const isDisabled = disabled ?? fieldDisabled;
|
|
167
164
|
const hasErrors = errors && errors.length > 0;
|
|
168
|
-
const
|
|
169
|
-
const handleCheckedChange = (checked) => {
|
|
170
|
-
control.change(checked ? "on" : "");
|
|
171
|
-
};
|
|
172
|
-
const checkboxId = fieldMeta.id;
|
|
165
|
+
const checked = Boolean(fieldState?.value);
|
|
173
166
|
return /* @__PURE__ */ jsxs("div", {
|
|
174
167
|
className: cn("flex items-center space-x-2", className),
|
|
175
168
|
children: [/* @__PURE__ */ jsx(Checkbox, {
|
|
176
|
-
id
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
onCheckedChange: handleCheckedChange,
|
|
169
|
+
id,
|
|
170
|
+
checked,
|
|
171
|
+
onCheckedChange: (value) => fieldState?.change(Boolean(value)),
|
|
180
172
|
disabled: isDisabled,
|
|
181
173
|
"aria-invalid": hasErrors || void 0,
|
|
182
|
-
"aria-describedby": hasErrors ? `${
|
|
174
|
+
"aria-describedby": hasErrors ? `${id}-error` : void 0
|
|
183
175
|
}), label && /* @__PURE__ */ jsx(Label, {
|
|
184
|
-
htmlFor:
|
|
176
|
+
htmlFor: id,
|
|
185
177
|
className: cn("cursor-pointer text-sm font-normal", isDisabled && "cursor-not-allowed opacity-70"),
|
|
186
178
|
children: label
|
|
187
179
|
})]
|
|
@@ -218,20 +210,14 @@ FormCheckbox.displayName = "Form.Checkbox";
|
|
|
218
210
|
* ```
|
|
219
211
|
*/
|
|
220
212
|
function FormCopyBox({ variant = "default", className, contentClassName, buttonClassName, placeholder = "" }) {
|
|
221
|
-
const {
|
|
222
|
-
const
|
|
223
|
-
const
|
|
224
|
-
const
|
|
225
|
-
const value = control.value ?? placeholder;
|
|
213
|
+
const { fieldState } = useFieldContext$1();
|
|
214
|
+
const [copied, copy] = useCopyToClipboard();
|
|
215
|
+
const value = fieldState?.value != null ? String(fieldState.value) : "";
|
|
216
|
+
const displayValue = value || placeholder;
|
|
226
217
|
const copyToClipboard = () => {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
copy(stringValue).then(() => {
|
|
218
|
+
if (!value) return;
|
|
219
|
+
copy(value).then(() => {
|
|
230
220
|
toast.success("Copied to clipboard");
|
|
231
|
-
setCopied(true);
|
|
232
|
-
setTimeout(() => {
|
|
233
|
-
setCopied(false);
|
|
234
|
-
}, 2e3);
|
|
235
221
|
});
|
|
236
222
|
};
|
|
237
223
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -240,7 +226,7 @@ function FormCopyBox({ variant = "default", className, contentClassName, buttonC
|
|
|
240
226
|
className: cn("flex w-full items-center overflow-hidden px-3 py-2 text-xs opacity-50", contentClassName),
|
|
241
227
|
children: /* @__PURE__ */ jsx("span", {
|
|
242
228
|
className: "truncate",
|
|
243
|
-
children:
|
|
229
|
+
children: displayValue
|
|
244
230
|
})
|
|
245
231
|
}), /* @__PURE__ */ jsx("div", {
|
|
246
232
|
className: "flex items-center py-2 pr-3",
|
|
@@ -493,9 +479,6 @@ function FormError({ children, className }) {
|
|
|
493
479
|
FormError.displayName = "Form.Error";
|
|
494
480
|
//#endregion
|
|
495
481
|
//#region src/components/features/form/components/form-field.tsx
|
|
496
|
-
/**
|
|
497
|
-
* Internal FieldLabel component with hover-reveal tooltip
|
|
498
|
-
*/
|
|
499
482
|
function FieldLabel({ htmlFor, label, hasErrors, required, tooltip, className }) {
|
|
500
483
|
const [isTooltipVisible, setIsTooltipVisible] = React$1.useState(false);
|
|
501
484
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -522,93 +505,70 @@ function FieldLabel({ htmlFor, label, hasErrors, required, tooltip, className })
|
|
|
522
505
|
});
|
|
523
506
|
}
|
|
524
507
|
/**
|
|
525
|
-
* Form.Field - Field wrapper
|
|
526
|
-
*
|
|
527
|
-
* Provides field context to children with:
|
|
528
|
-
* - Automatic label rendering
|
|
529
|
-
* - Error display
|
|
530
|
-
* - Description text
|
|
531
|
-
* - Required indicator
|
|
532
|
-
* - Accessibility attributes
|
|
533
|
-
*
|
|
534
|
-
* Supports two patterns:
|
|
535
|
-
* 1. ReactNode children - for standard Form inputs
|
|
536
|
-
* 2. Render function - for custom components needing field access
|
|
508
|
+
* Form.Field - Field wrapper that provides label, errors, and description.
|
|
509
|
+
* Uses the active adapter to resolve field state by name.
|
|
537
510
|
*
|
|
538
511
|
* @example Standard usage
|
|
539
512
|
* ```tsx
|
|
540
|
-
* <Form.Field name="email" label="Email
|
|
513
|
+
* <Form.Field name="email" label="Email" required>
|
|
541
514
|
* <Form.Input type="email" />
|
|
542
515
|
* </Form.Field>
|
|
543
516
|
* ```
|
|
544
517
|
*
|
|
545
518
|
* @example Render function for custom components
|
|
546
519
|
* ```tsx
|
|
547
|
-
* <Form.Field name="role" label="Role"
|
|
548
|
-
* {({ control, meta
|
|
549
|
-
* <CustomSelect
|
|
550
|
-
* name={meta.name}
|
|
551
|
-
* value={control.value}
|
|
552
|
-
* onChange={control.change}
|
|
553
|
-
* />
|
|
520
|
+
* <Form.Field name="role" label="Role">
|
|
521
|
+
* {({ control, meta }) => (
|
|
522
|
+
* <CustomSelect value={control.value} onChange={control.change} />
|
|
554
523
|
* )}
|
|
555
524
|
* </Form.Field>
|
|
556
525
|
* ```
|
|
557
526
|
*/
|
|
558
527
|
function FormField({ name, children, label, description, tooltip, required = false, disabled = false, className, labelClassName }) {
|
|
559
|
-
const
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
if (!current) break;
|
|
566
|
-
if (/^\d+$/.test(part)) {
|
|
567
|
-
const fieldList = current.getFieldList?.();
|
|
568
|
-
if (fieldList) {
|
|
569
|
-
const item = fieldList[Number.parseInt(part, 10)];
|
|
570
|
-
if (i < parts.length - 1 && item?.getFieldset) current = item.getFieldset();
|
|
571
|
-
else current = item;
|
|
572
|
-
} else current = current[part];
|
|
573
|
-
} else if (current[part] !== void 0) current = current[part];
|
|
574
|
-
else if (typeof current.getFieldset === "function") current = current.getFieldset()[part];
|
|
575
|
-
else current = void 0;
|
|
576
|
-
}
|
|
577
|
-
return current;
|
|
578
|
-
}, [fields, name]);
|
|
579
|
-
const errors = fieldMeta?.errors;
|
|
580
|
-
const hasErrors = errors && errors.length > 0;
|
|
581
|
-
const fieldId = fieldMeta?.id ?? "";
|
|
528
|
+
const adapter = useAdapter();
|
|
529
|
+
const { fields, isSubmitting, form } = useFormContext$1();
|
|
530
|
+
const fieldState = adapter.useField(name);
|
|
531
|
+
const errors = fieldState.errors;
|
|
532
|
+
const hasErrors = errors.length > 0;
|
|
533
|
+
const fieldId = fieldState.id;
|
|
582
534
|
const descriptionId = description ? `${fieldId}-description` : void 0;
|
|
583
535
|
const errorId = hasErrors ? `${fieldId}-error` : void 0;
|
|
536
|
+
const fieldRequired = required || fieldState.required;
|
|
584
537
|
const contextValue = React$1.useMemo(() => ({
|
|
585
|
-
name
|
|
538
|
+
name,
|
|
586
539
|
id: fieldId,
|
|
587
540
|
errors,
|
|
588
|
-
required,
|
|
541
|
+
required: fieldRequired,
|
|
589
542
|
disabled,
|
|
590
|
-
|
|
543
|
+
fieldState
|
|
591
544
|
}), [
|
|
592
|
-
|
|
545
|
+
name,
|
|
593
546
|
fieldId,
|
|
594
547
|
errors,
|
|
595
|
-
|
|
596
|
-
disabled
|
|
548
|
+
fieldRequired,
|
|
549
|
+
disabled,
|
|
550
|
+
fieldState
|
|
597
551
|
]);
|
|
598
|
-
if (!fieldMeta) {
|
|
599
|
-
console.warn(`Form.Field: Field "${name}" not found in form schema`);
|
|
600
|
-
return null;
|
|
601
|
-
}
|
|
602
552
|
const isRenderFunction = typeof children === "function";
|
|
603
553
|
const renderContent = () => {
|
|
604
|
-
if (isRenderFunction) return
|
|
605
|
-
|
|
554
|
+
if (isRenderFunction) return children({
|
|
555
|
+
field: fieldState,
|
|
556
|
+
control: {
|
|
557
|
+
value: fieldState.value,
|
|
558
|
+
change: fieldState.change,
|
|
559
|
+
blur: fieldState.blur,
|
|
560
|
+
focus: fieldState.focus
|
|
561
|
+
},
|
|
562
|
+
meta: {
|
|
563
|
+
name,
|
|
564
|
+
id: fieldId,
|
|
565
|
+
errors,
|
|
566
|
+
required: fieldRequired,
|
|
567
|
+
disabled
|
|
568
|
+
},
|
|
606
569
|
fields,
|
|
607
570
|
form,
|
|
608
|
-
isSubmitting
|
|
609
|
-
required,
|
|
610
|
-
disabled,
|
|
611
|
-
children
|
|
571
|
+
isSubmitting
|
|
612
572
|
});
|
|
613
573
|
return children;
|
|
614
574
|
};
|
|
@@ -621,7 +581,7 @@ function FormField({ name, children, label, description, tooltip, required = fal
|
|
|
621
581
|
htmlFor: fieldId,
|
|
622
582
|
label,
|
|
623
583
|
hasErrors,
|
|
624
|
-
required,
|
|
584
|
+
required: fieldRequired,
|
|
625
585
|
tooltip,
|
|
626
586
|
className: labelClassName
|
|
627
587
|
}),
|
|
@@ -645,46 +605,11 @@ function FormField({ name, children, label, description, tooltip, required = fal
|
|
|
645
605
|
})
|
|
646
606
|
});
|
|
647
607
|
}
|
|
648
|
-
/**
|
|
649
|
-
* Internal component to handle render function pattern
|
|
650
|
-
* This is needed because hooks (useInputControl) must be called unconditionally
|
|
651
|
-
*/
|
|
652
|
-
function FormFieldRenderContent({ fieldMeta, fields, form, isSubmitting, required, disabled, children }) {
|
|
653
|
-
const control = useInputControl(fieldMeta);
|
|
654
|
-
const meta = React$1.useMemo(() => ({
|
|
655
|
-
name: fieldMeta.name,
|
|
656
|
-
id: fieldMeta.id,
|
|
657
|
-
errors: fieldMeta.errors,
|
|
658
|
-
required,
|
|
659
|
-
disabled
|
|
660
|
-
}), [
|
|
661
|
-
fieldMeta.name,
|
|
662
|
-
fieldMeta.id,
|
|
663
|
-
fieldMeta.errors,
|
|
664
|
-
required,
|
|
665
|
-
disabled
|
|
666
|
-
]);
|
|
667
|
-
return /* @__PURE__ */ jsx(Fragment$1, { children: children({
|
|
668
|
-
field: fieldMeta,
|
|
669
|
-
control: {
|
|
670
|
-
value: control.value,
|
|
671
|
-
change: control.change,
|
|
672
|
-
blur: control.blur,
|
|
673
|
-
focus: control.focus
|
|
674
|
-
},
|
|
675
|
-
meta,
|
|
676
|
-
fields,
|
|
677
|
-
form,
|
|
678
|
-
isSubmitting
|
|
679
|
-
}) });
|
|
680
|
-
}
|
|
681
608
|
FormField.displayName = "Form.Field";
|
|
682
609
|
//#endregion
|
|
683
610
|
//#region src/components/features/form/components/form-field-array.tsx
|
|
684
611
|
/**
|
|
685
|
-
* Form.FieldArray - Dynamic array of fields
|
|
686
|
-
*
|
|
687
|
-
* Provides helpers for managing arrays of form fields.
|
|
612
|
+
* Form.FieldArray - Dynamic array of fields with append/remove/move helpers.
|
|
688
613
|
*
|
|
689
614
|
* @example
|
|
690
615
|
* ```tsx
|
|
@@ -692,78 +617,26 @@ FormField.displayName = "Form.Field";
|
|
|
692
617
|
* {({ fields, append, remove }) => (
|
|
693
618
|
* <>
|
|
694
619
|
* {fields.map((field, index) => (
|
|
695
|
-
* <div key={field.key}
|
|
620
|
+
* <div key={field.key}>
|
|
696
621
|
* <Form.Field name={`members.${index}.email`} label="Email">
|
|
697
622
|
* <Form.Input type="email" />
|
|
698
623
|
* </Form.Field>
|
|
699
|
-
* <
|
|
700
|
-
* <Form.Select>
|
|
701
|
-
* <Form.SelectItem value="admin">Admin</Form.SelectItem>
|
|
702
|
-
* <Form.SelectItem value="user">User</Form.SelectItem>
|
|
703
|
-
* </Form.Select>
|
|
704
|
-
* </Form.Field>
|
|
705
|
-
* <button type="button" onClick={() => remove(index)}>
|
|
706
|
-
* Remove
|
|
707
|
-
* </button>
|
|
624
|
+
* <button onClick={() => remove(index)}>Remove</button>
|
|
708
625
|
* </div>
|
|
709
626
|
* ))}
|
|
710
|
-
* <button
|
|
711
|
-
* Add Member
|
|
712
|
-
* </button>
|
|
627
|
+
* <button onClick={() => append({})}>Add Member</button>
|
|
713
628
|
* </>
|
|
714
629
|
* )}
|
|
715
630
|
* </Form.FieldArray>
|
|
716
631
|
* ```
|
|
717
632
|
*/
|
|
718
633
|
function FormFieldArray({ name, children }) {
|
|
719
|
-
const
|
|
720
|
-
const form = useFormMetadata(formId);
|
|
721
|
-
const arrayField = React$1.useMemo(() => {
|
|
722
|
-
const parts = name.split(".");
|
|
723
|
-
let current = fields;
|
|
724
|
-
for (const part of parts) {
|
|
725
|
-
if (!current) break;
|
|
726
|
-
if (typeof current.getFieldset === "function") current = current.getFieldset()[part];
|
|
727
|
-
else current = current[part];
|
|
728
|
-
}
|
|
729
|
-
return current;
|
|
730
|
-
}, [fields, name]);
|
|
731
|
-
const arrayFieldName = arrayField?.name ?? "";
|
|
732
|
-
const append = React$1.useCallback((value = {}) => {
|
|
733
|
-
if (!arrayFieldName) return;
|
|
734
|
-
form.insert({
|
|
735
|
-
name: arrayFieldName,
|
|
736
|
-
defaultValue: value
|
|
737
|
-
});
|
|
738
|
-
}, [form, arrayFieldName]);
|
|
739
|
-
const remove = React$1.useCallback((index) => {
|
|
740
|
-
if (!arrayFieldName) return;
|
|
741
|
-
form.remove({
|
|
742
|
-
name: arrayFieldName,
|
|
743
|
-
index
|
|
744
|
-
});
|
|
745
|
-
}, [form, arrayFieldName]);
|
|
746
|
-
const move = React$1.useCallback((from, to) => {
|
|
747
|
-
if (!arrayFieldName) return;
|
|
748
|
-
form.reorder({
|
|
749
|
-
name: arrayFieldName,
|
|
750
|
-
from,
|
|
751
|
-
to
|
|
752
|
-
});
|
|
753
|
-
}, [form, arrayFieldName]);
|
|
754
|
-
if (!arrayField) {
|
|
755
|
-
console.warn(`Form.FieldArray: Field "${name}" not found in form schema`);
|
|
756
|
-
return null;
|
|
757
|
-
}
|
|
634
|
+
const fieldArray = useAdapter().useFieldArray(name);
|
|
758
635
|
return /* @__PURE__ */ jsx(Fragment$1, { children: children({
|
|
759
|
-
fields:
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
})),
|
|
764
|
-
append,
|
|
765
|
-
remove,
|
|
766
|
-
move
|
|
636
|
+
fields: fieldArray.items,
|
|
637
|
+
append: fieldArray.append,
|
|
638
|
+
remove: fieldArray.remove,
|
|
639
|
+
move: fieldArray.move
|
|
767
640
|
}) });
|
|
768
641
|
}
|
|
769
642
|
FormFieldArray.displayName = "Form.FieldArray";
|
|
@@ -782,19 +655,22 @@ FormFieldArray.displayName = "Form.FieldArray";
|
|
|
782
655
|
* ```
|
|
783
656
|
*/
|
|
784
657
|
function FormInput({ ref, type = "text", className, disabled, ...props }) {
|
|
785
|
-
const {
|
|
786
|
-
const inputProps = getInputProps(fieldMeta, { type });
|
|
658
|
+
const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
|
|
787
659
|
const isDisabled = disabled ?? fieldDisabled;
|
|
788
660
|
const hasErrors = errors && errors.length > 0;
|
|
789
661
|
return /* @__PURE__ */ jsx(Input, {
|
|
790
|
-
ref,
|
|
791
|
-
...inputProps,
|
|
792
662
|
...props,
|
|
663
|
+
ref,
|
|
664
|
+
id,
|
|
665
|
+
name: fieldState?.name,
|
|
793
666
|
type,
|
|
667
|
+
value: fieldState?.value ?? "",
|
|
668
|
+
onChange: (e) => fieldState?.change(e.target.value),
|
|
669
|
+
onBlur: () => fieldState?.blur(),
|
|
670
|
+
className: cn("!text-xs", className),
|
|
794
671
|
disabled: isDisabled,
|
|
795
672
|
"aria-invalid": hasErrors || void 0,
|
|
796
|
-
"aria-describedby": hasErrors ? `${
|
|
797
|
-
className: cn("!text-xs", className)
|
|
673
|
+
"aria-describedby": hasErrors ? `${id}-error` : void 0
|
|
798
674
|
});
|
|
799
675
|
}
|
|
800
676
|
FormInput.displayName = "Form.Input";
|
|
@@ -817,18 +693,15 @@ FormInput.displayName = "Form.Input";
|
|
|
817
693
|
* ```
|
|
818
694
|
*/
|
|
819
695
|
function FormRadioGroup({ orientation = "vertical", disabled, className, children }) {
|
|
820
|
-
const {
|
|
821
|
-
const control = useInputControl(fieldMeta);
|
|
696
|
+
const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
|
|
822
697
|
const isDisabled = disabled ?? fieldDisabled;
|
|
823
698
|
const hasErrors = errors && errors.length > 0;
|
|
824
|
-
const radioValue = Array.isArray(control.value) ? control.value[0] : control.value;
|
|
825
699
|
return /* @__PURE__ */ jsx(RadioGroup, {
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
onValueChange: control.change,
|
|
700
|
+
value: fieldState?.value != null ? String(fieldState.value) : void 0,
|
|
701
|
+
onValueChange: (val) => fieldState?.change(val),
|
|
829
702
|
disabled: isDisabled,
|
|
830
703
|
"aria-invalid": hasErrors || void 0,
|
|
831
|
-
"aria-describedby": hasErrors ? `${
|
|
704
|
+
"aria-describedby": hasErrors ? `${id}-error` : void 0,
|
|
832
705
|
className: cn(orientation === "horizontal" ? "flex flex-row space-x-4" : "flex flex-col space-y-2", className),
|
|
833
706
|
children
|
|
834
707
|
});
|
|
@@ -868,21 +741,12 @@ FormRadioItem.displayName = "Form.RadioItem";
|
|
|
868
741
|
//#endregion
|
|
869
742
|
//#region src/components/features/form/components/form-root.tsx
|
|
870
743
|
/**
|
|
871
|
-
* Form.Root -
|
|
872
|
-
*
|
|
873
|
-
* Provides form context to all children with built-in:
|
|
874
|
-
* - Zod schema validation
|
|
875
|
-
* - Conform integration
|
|
876
|
-
* - Optional telemetry callbacks
|
|
877
|
-
*
|
|
878
|
-
* Supports two patterns:
|
|
879
|
-
* 1. ReactNode children - for standard forms
|
|
880
|
-
* 2. Render function - for forms needing access to form state
|
|
744
|
+
* Form.Root - Root form container that integrates with the active adapter.
|
|
881
745
|
*
|
|
882
746
|
* @example Standard usage
|
|
883
747
|
* ```tsx
|
|
884
|
-
* <Form.Root schema={
|
|
885
|
-
* <Form.Field name="email" label="Email"
|
|
748
|
+
* <Form.Root schema={schema} onSubmit={handleSubmit}>
|
|
749
|
+
* <Form.Field name="email" label="Email">
|
|
886
750
|
* <Form.Input type="email" />
|
|
887
751
|
* </Form.Field>
|
|
888
752
|
* <Form.Submit>Save</Form.Submit>
|
|
@@ -891,146 +755,99 @@ FormRadioItem.displayName = "Form.RadioItem";
|
|
|
891
755
|
*
|
|
892
756
|
* @example Render function for form state access
|
|
893
757
|
* ```tsx
|
|
894
|
-
* <Form.Root schema={
|
|
758
|
+
* <Form.Root schema={schema}>
|
|
895
759
|
* {({ form, fields, isSubmitting }) => (
|
|
896
|
-
*
|
|
897
|
-
* <Form.Field name="email" label="Email" required>
|
|
898
|
-
* <Form.Input type="email" />
|
|
899
|
-
* </Form.Field>
|
|
900
|
-
* <Button
|
|
901
|
-
* disabled={isSubmitting}
|
|
902
|
-
* onClick={() => form.update({ value: { email: '' } })}
|
|
903
|
-
* >
|
|
904
|
-
* Cancel
|
|
905
|
-
* </Button>
|
|
906
|
-
* <Form.Submit>Save</Form.Submit>
|
|
907
|
-
* </>
|
|
760
|
+
* <Form.Field name="email"><Form.Input /></Form.Field>
|
|
908
761
|
* )}
|
|
909
762
|
* </Form.Root>
|
|
910
763
|
* ```
|
|
911
764
|
*/
|
|
912
765
|
function FormRoot({ schema, children, onSubmit, action, method = "POST", formComponent: FormComp = "form", id, name, defaultValues, mode = "onBlur", isSubmitting: externalIsSubmitting, onError, onSuccess, telemetry, className }) {
|
|
766
|
+
const adapter = useAdapter();
|
|
913
767
|
const [internalIsSubmitting, setInternalIsSubmitting] = React$1.useState(false);
|
|
914
768
|
const isSubmitting = externalIsSubmitting ?? internalIsSubmitting;
|
|
915
769
|
const formRef = React$1.useRef(null);
|
|
916
|
-
const
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
defaultValue: defaultValues,
|
|
923
|
-
onValidate({ formData }) {
|
|
924
|
-
return parseWithZod(formData, { schema });
|
|
925
|
-
},
|
|
926
|
-
async onSubmit(event, { submission }) {
|
|
927
|
-
const formName = name || id || "unnamed-form";
|
|
928
|
-
telemetry?.onSubmit?.({
|
|
929
|
-
formName,
|
|
770
|
+
const wrappedOnSubmit = React$1.useCallback(async (data) => {
|
|
771
|
+
setInternalIsSubmitting(true);
|
|
772
|
+
try {
|
|
773
|
+
await onSubmit?.(data);
|
|
774
|
+
telemetry?.onSuccess?.({
|
|
775
|
+
formName: name ?? "",
|
|
930
776
|
formId: id
|
|
931
777
|
});
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
telemetry?.onError?.({
|
|
948
|
-
formName,
|
|
949
|
-
formId: id,
|
|
950
|
-
error
|
|
951
|
-
});
|
|
952
|
-
telemetry?.captureError?.(error, {
|
|
953
|
-
message: `Form submission error: ${formName}`,
|
|
954
|
-
tags: {
|
|
955
|
-
"form.name": formName,
|
|
956
|
-
"form.id": id || "unknown"
|
|
957
|
-
}
|
|
958
|
-
});
|
|
959
|
-
onError?.(error);
|
|
960
|
-
} finally {
|
|
961
|
-
setInternalIsSubmitting(false);
|
|
962
|
-
}
|
|
963
|
-
} else if (submission?.status === "error") {
|
|
964
|
-
telemetry?.onValidationError?.({
|
|
965
|
-
formName,
|
|
966
|
-
formId: id,
|
|
967
|
-
fieldErrors: submission.error ?? {}
|
|
968
|
-
});
|
|
969
|
-
if (onError) {
|
|
970
|
-
const { ZodError } = await import("zod");
|
|
971
|
-
onError(new ZodError(Object.entries(submission.error ?? {}).flatMap(([path, messages]) => (messages ?? []).map((message) => ({
|
|
972
|
-
code: "custom",
|
|
973
|
-
path: path.split("."),
|
|
974
|
-
message
|
|
975
|
-
})))));
|
|
976
|
-
}
|
|
977
|
-
}
|
|
778
|
+
onSuccess?.(data);
|
|
779
|
+
} catch (error) {
|
|
780
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
781
|
+
telemetry?.onError?.({
|
|
782
|
+
formName: name ?? "",
|
|
783
|
+
formId: id,
|
|
784
|
+
error: err
|
|
785
|
+
});
|
|
786
|
+
telemetry?.captureError?.(err, {
|
|
787
|
+
formName: name ?? "",
|
|
788
|
+
formId: id
|
|
789
|
+
});
|
|
790
|
+
onError?.(error);
|
|
791
|
+
} finally {
|
|
792
|
+
setInternalIsSubmitting(false);
|
|
978
793
|
}
|
|
794
|
+
}, [
|
|
795
|
+
onSubmit,
|
|
796
|
+
onSuccess,
|
|
797
|
+
onError,
|
|
798
|
+
telemetry,
|
|
799
|
+
name,
|
|
800
|
+
id
|
|
801
|
+
]);
|
|
802
|
+
const instance = adapter.useCreateForm({
|
|
803
|
+
schema,
|
|
804
|
+
defaultValues,
|
|
805
|
+
mode,
|
|
806
|
+
id,
|
|
807
|
+
onSubmit: onSubmit ? wrappedOnSubmit : void 0,
|
|
808
|
+
formRef
|
|
979
809
|
});
|
|
980
|
-
const submit = React$1.useCallback(() => {
|
|
981
|
-
formRef.current?.requestSubmit();
|
|
982
|
-
}, []);
|
|
983
|
-
const reset = React$1.useCallback(() => {
|
|
984
|
-
form.reset();
|
|
985
|
-
}, [form]);
|
|
986
810
|
const contextValue = React$1.useMemo(() => ({
|
|
987
|
-
form,
|
|
988
|
-
fields,
|
|
989
|
-
isSubmitting,
|
|
990
|
-
submit,
|
|
991
|
-
reset,
|
|
992
|
-
formId: form.id
|
|
993
|
-
}), [
|
|
994
|
-
form,
|
|
995
|
-
fields,
|
|
811
|
+
form: instance,
|
|
812
|
+
fields: instance.fields,
|
|
996
813
|
isSubmitting,
|
|
997
|
-
submit,
|
|
998
|
-
reset
|
|
999
|
-
|
|
814
|
+
submit: () => formRef.current?.requestSubmit(),
|
|
815
|
+
reset: () => instance.reset(),
|
|
816
|
+
formId: instance.id
|
|
817
|
+
}), [instance, isSubmitting]);
|
|
1000
818
|
const isRenderFunction = typeof children === "function";
|
|
1001
|
-
const renderProps =
|
|
1002
|
-
form,
|
|
1003
|
-
fields,
|
|
1004
|
-
isSubmitting,
|
|
1005
|
-
submit,
|
|
1006
|
-
reset
|
|
1007
|
-
}), [
|
|
1008
|
-
form,
|
|
1009
|
-
fields,
|
|
819
|
+
const renderProps = {
|
|
820
|
+
form: instance,
|
|
821
|
+
fields: instance.fields,
|
|
1010
822
|
isSubmitting,
|
|
1011
|
-
submit,
|
|
1012
|
-
reset
|
|
1013
|
-
|
|
823
|
+
submit: () => formRef.current?.requestSubmit(),
|
|
824
|
+
reset: () => instance.reset()
|
|
825
|
+
};
|
|
1014
826
|
const renderChildren = () => {
|
|
1015
827
|
if (isRenderFunction) return children(renderProps);
|
|
1016
828
|
return children;
|
|
1017
829
|
};
|
|
1018
|
-
|
|
1019
|
-
return /* @__PURE__ */ jsx(FormProvider$1, {
|
|
830
|
+
return /* @__PURE__ */ jsx(FormProvider, {
|
|
1020
831
|
value: contextValue,
|
|
1021
|
-
children: /* @__PURE__ */ jsx(FormProvider, {
|
|
1022
|
-
|
|
832
|
+
children: /* @__PURE__ */ jsx(adapter.FormProvider, {
|
|
833
|
+
instance,
|
|
1023
834
|
children: /* @__PURE__ */ jsx(FormComp, {
|
|
1024
835
|
ref: formRef,
|
|
1025
|
-
...
|
|
1026
|
-
onSubmit: (e) => {
|
|
1027
|
-
e.stopPropagation();
|
|
1028
|
-
conformOnSubmit(e);
|
|
1029
|
-
},
|
|
836
|
+
...instance.formProps,
|
|
1030
837
|
method,
|
|
1031
838
|
action,
|
|
1032
839
|
className: cn("space-y-6", className),
|
|
1033
840
|
autoComplete: "off",
|
|
841
|
+
noValidate: true,
|
|
842
|
+
onSubmit: (e) => {
|
|
843
|
+
e.stopPropagation();
|
|
844
|
+
telemetry?.onSubmit?.({
|
|
845
|
+
formName: name ?? "",
|
|
846
|
+
formId: id
|
|
847
|
+
});
|
|
848
|
+
const adapterSubmit = instance.formProps.onSubmit;
|
|
849
|
+
adapterSubmit?.(e);
|
|
850
|
+
},
|
|
1034
851
|
children: renderChildren()
|
|
1035
852
|
})
|
|
1036
853
|
})
|
|
@@ -1056,21 +873,19 @@ FormRoot.displayName = "Form.Root";
|
|
|
1056
873
|
* ```
|
|
1057
874
|
*/
|
|
1058
875
|
function FormSelect({ placeholder, disabled, className, children }) {
|
|
1059
|
-
const {
|
|
1060
|
-
const control = useInputControl(fieldMeta);
|
|
876
|
+
const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
|
|
1061
877
|
const isDisabled = disabled ?? fieldDisabled;
|
|
1062
878
|
const hasErrors = errors && errors.length > 0;
|
|
1063
|
-
const selectValue = Array.isArray(control.value) ? control.value[0] : control.value;
|
|
1064
879
|
return /* @__PURE__ */ jsxs(Select, {
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
onValueChange: control.change,
|
|
880
|
+
value: fieldState?.value != null ? String(fieldState.value) : void 0,
|
|
881
|
+
onValueChange: (val) => fieldState?.change(val),
|
|
1068
882
|
disabled: isDisabled,
|
|
1069
883
|
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
1070
|
-
id
|
|
1071
|
-
"aria-invalid": hasErrors || void 0,
|
|
1072
|
-
"aria-describedby": hasErrors ? `${fieldMeta.id}-error` : void 0,
|
|
884
|
+
id,
|
|
1073
885
|
className: cn(className),
|
|
886
|
+
"aria-invalid": hasErrors || void 0,
|
|
887
|
+
"aria-describedby": hasErrors ? `${id}-error` : void 0,
|
|
888
|
+
onBlur: () => fieldState?.blur(),
|
|
1074
889
|
children: /* @__PURE__ */ jsx(SelectValue, { placeholder })
|
|
1075
890
|
}), /* @__PURE__ */ jsx(SelectContent, { children })]
|
|
1076
891
|
});
|
|
@@ -1131,27 +946,21 @@ FormSubmit.displayName = "Form.Submit";
|
|
|
1131
946
|
* ```
|
|
1132
947
|
*/
|
|
1133
948
|
function FormSwitch({ label, disabled, className }) {
|
|
1134
|
-
const {
|
|
1135
|
-
const control = useInputControl(fieldMeta);
|
|
949
|
+
const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
|
|
1136
950
|
const isDisabled = disabled ?? fieldDisabled;
|
|
1137
951
|
const hasErrors = errors && errors.length > 0;
|
|
1138
|
-
const
|
|
1139
|
-
const handleCheckedChange = (checked) => {
|
|
1140
|
-
control.change(checked ? "on" : "");
|
|
1141
|
-
};
|
|
1142
|
-
const switchId = fieldMeta.id;
|
|
952
|
+
const checked = Boolean(fieldState?.value);
|
|
1143
953
|
return /* @__PURE__ */ jsxs("div", {
|
|
1144
954
|
className: cn("flex items-center space-x-2", className),
|
|
1145
955
|
children: [/* @__PURE__ */ jsx(Switch, {
|
|
1146
|
-
id
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
onCheckedChange: handleCheckedChange,
|
|
956
|
+
id,
|
|
957
|
+
checked,
|
|
958
|
+
onCheckedChange: (value) => fieldState?.change(Boolean(value)),
|
|
1150
959
|
disabled: isDisabled,
|
|
1151
960
|
"aria-invalid": hasErrors || void 0,
|
|
1152
|
-
"aria-describedby": hasErrors ? `${
|
|
961
|
+
"aria-describedby": hasErrors ? `${id}-error` : void 0
|
|
1153
962
|
}), label && /* @__PURE__ */ jsx(Label, {
|
|
1154
|
-
htmlFor:
|
|
963
|
+
htmlFor: id,
|
|
1155
964
|
className: cn("cursor-pointer text-sm font-normal", isDisabled && "cursor-not-allowed opacity-70"),
|
|
1156
965
|
children: label
|
|
1157
966
|
})]
|
|
@@ -1173,99 +982,59 @@ FormSwitch.displayName = "Form.Switch";
|
|
|
1173
982
|
* ```
|
|
1174
983
|
*/
|
|
1175
984
|
function FormTextarea({ ref, className, disabled, rows = 3, ...props }) {
|
|
1176
|
-
const {
|
|
1177
|
-
const textareaProps = getTextareaProps(fieldMeta);
|
|
985
|
+
const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
|
|
1178
986
|
const isDisabled = disabled ?? fieldDisabled;
|
|
1179
987
|
const hasErrors = errors && errors.length > 0;
|
|
1180
988
|
return /* @__PURE__ */ jsx(Textarea, {
|
|
1181
|
-
ref,
|
|
1182
|
-
...textareaProps,
|
|
1183
989
|
...props,
|
|
990
|
+
ref,
|
|
991
|
+
id,
|
|
992
|
+
name: fieldState?.name,
|
|
993
|
+
value: fieldState?.value ?? "",
|
|
994
|
+
onChange: (e) => fieldState?.change(e.target.value),
|
|
995
|
+
onBlur: () => fieldState?.blur(),
|
|
1184
996
|
rows,
|
|
997
|
+
className: cn(className),
|
|
1185
998
|
disabled: isDisabled,
|
|
1186
999
|
"aria-invalid": hasErrors || void 0,
|
|
1187
|
-
"aria-describedby": hasErrors ? `${
|
|
1188
|
-
className: cn(className)
|
|
1000
|
+
"aria-describedby": hasErrors ? `${id}-error` : void 0
|
|
1189
1001
|
});
|
|
1190
1002
|
}
|
|
1191
1003
|
FormTextarea.displayName = "Form.Textarea";
|
|
1192
1004
|
//#endregion
|
|
1193
1005
|
//#region src/components/features/form/hooks/use-watch.ts
|
|
1194
1006
|
/**
|
|
1195
|
-
* Hook to watch a field's value
|
|
1196
|
-
*
|
|
1007
|
+
* Hook to watch a field's value reactively.
|
|
1008
|
+
* Delegates to the active adapter's useWatch implementation.
|
|
1197
1009
|
*
|
|
1198
1010
|
* @example
|
|
1199
1011
|
* ```tsx
|
|
1200
1012
|
* function ConditionalField() {
|
|
1201
|
-
* const contactMethod = useWatch('contactMethod')
|
|
1202
|
-
*
|
|
1013
|
+
* const contactMethod = useWatch('contactMethod')
|
|
1203
1014
|
* if (contactMethod === 'email') {
|
|
1204
|
-
* return <Form.Field name="email"><Form.Input type="email" /></Form.Field
|
|
1015
|
+
* return <Form.Field name="email"><Form.Input type="email" /></Form.Field>
|
|
1205
1016
|
* }
|
|
1206
|
-
*
|
|
1207
|
-
* if (contactMethod === 'phone') {
|
|
1208
|
-
* return <Form.Field name="phone"><Form.Input type="tel" /></Form.Field>;
|
|
1209
|
-
* }
|
|
1210
|
-
*
|
|
1211
|
-
* return null;
|
|
1017
|
+
* return null
|
|
1212
1018
|
* }
|
|
1213
1019
|
* ```
|
|
1214
1020
|
*/
|
|
1215
1021
|
function useWatch(name) {
|
|
1216
|
-
|
|
1217
|
-
return useInputControl(React$1.useMemo(() => {
|
|
1218
|
-
const parts = name.split(".");
|
|
1219
|
-
let current = fields;
|
|
1220
|
-
for (const part of parts) {
|
|
1221
|
-
if (!current) break;
|
|
1222
|
-
if (/^\d+$/.test(part)) {
|
|
1223
|
-
const fieldList = current.getFieldList?.();
|
|
1224
|
-
if (fieldList) current = fieldList[Number.parseInt(part, 10)]?.getFieldset?.();
|
|
1225
|
-
else current = current[part];
|
|
1226
|
-
} else if (typeof current.getFieldset === "function") current = current.getFieldset()[part];
|
|
1227
|
-
else current = current[part];
|
|
1228
|
-
}
|
|
1229
|
-
return current;
|
|
1230
|
-
}, [fields, name])).value;
|
|
1022
|
+
return useAdapter().useWatch(name);
|
|
1231
1023
|
}
|
|
1232
1024
|
/**
|
|
1233
|
-
* Hook to watch multiple fields at once
|
|
1025
|
+
* Hook to watch multiple fields at once.
|
|
1026
|
+
* Delegates to the active adapter's useWatchAll implementation.
|
|
1234
1027
|
*
|
|
1235
1028
|
* @example
|
|
1236
1029
|
* ```tsx
|
|
1237
1030
|
* function Summary() {
|
|
1238
|
-
* const values = useWatchAll(['firstName', 'lastName', 'email'])
|
|
1239
|
-
*
|
|
1240
|
-
* return (
|
|
1241
|
-
* <div>
|
|
1242
|
-
* Name: {values.firstName} {values.lastName}
|
|
1243
|
-
* Email: {values.email}
|
|
1244
|
-
* </div>
|
|
1245
|
-
* );
|
|
1031
|
+
* const values = useWatchAll(['firstName', 'lastName', 'email'])
|
|
1032
|
+
* return <div>Name: {values.firstName} {values.lastName}</div>
|
|
1246
1033
|
* }
|
|
1247
1034
|
* ```
|
|
1248
1035
|
*/
|
|
1249
1036
|
function useWatchAll(names) {
|
|
1250
|
-
|
|
1251
|
-
return React$1.useMemo(() => {
|
|
1252
|
-
const result = {};
|
|
1253
|
-
for (const name of names) {
|
|
1254
|
-
const parts = name.split(".");
|
|
1255
|
-
let current = fields;
|
|
1256
|
-
for (const part of parts) {
|
|
1257
|
-
if (!current) break;
|
|
1258
|
-
if (/^\d+$/.test(part)) {
|
|
1259
|
-
const fieldList = current.getFieldList?.();
|
|
1260
|
-
if (fieldList) current = fieldList[Number.parseInt(part, 10)]?.getFieldset?.();
|
|
1261
|
-
else current = current[part];
|
|
1262
|
-
} else if (typeof current.getFieldset === "function") current = current.getFieldset()[part];
|
|
1263
|
-
else current = current[part];
|
|
1264
|
-
}
|
|
1265
|
-
if (current) result[name] = current.value;
|
|
1266
|
-
}
|
|
1267
|
-
return result;
|
|
1268
|
-
}, [fields, names]);
|
|
1037
|
+
return useAdapter().useWatchAll(names);
|
|
1269
1038
|
}
|
|
1270
1039
|
//#endregion
|
|
1271
1040
|
//#region src/components/features/form/components/form-when.tsx
|
|
@@ -1452,71 +1221,78 @@ function FormStepperContent({ steps, stepperDef, children, onComplete, onStepCha
|
|
|
1452
1221
|
children
|
|
1453
1222
|
}, stepper.state.current.data.id);
|
|
1454
1223
|
}
|
|
1455
|
-
function StepForm({ steps, stepper, currentStepConfig, combinedSchema, storedValues, children, onComplete, onStepChange, className, id, formComponent: FormComp = "form" }) {
|
|
1224
|
+
function StepForm({ steps, stepper, currentStepConfig, combinedSchema: _combinedSchema, storedValues, children, onComplete, onStepChange, className, id, formComponent: FormComp = "form" }) {
|
|
1225
|
+
const adapter = useAdapter();
|
|
1456
1226
|
const [isSubmitting, setIsSubmitting] = React$1.useState(false);
|
|
1457
1227
|
const formRef = React$1.useRef(null);
|
|
1458
|
-
const
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
} catch (error) {
|
|
1482
|
-
console.error("Stepper form completion error:", error);
|
|
1483
|
-
} finally {
|
|
1484
|
-
setIsSubmitting(false);
|
|
1485
|
-
}
|
|
1486
|
-
} else {
|
|
1487
|
-
const nextStepId = stepper.lookup.getNext(stepper.state.current.data.id)?.id;
|
|
1488
|
-
if (nextStepId) {
|
|
1489
|
-
stepper.navigation.goTo(nextStepId);
|
|
1490
|
-
onStepChange?.(nextStepId, "next");
|
|
1491
|
-
}
|
|
1228
|
+
const currentIndex = stepper.lookup.getIndex(stepper.state.current.data.id);
|
|
1229
|
+
const handleStepSubmit = React$1.useCallback(async (data) => {
|
|
1230
|
+
stepper.metadata.set(stepper.state.current.data.id, data);
|
|
1231
|
+
if (stepper.state.isLast) {
|
|
1232
|
+
setIsSubmitting(true);
|
|
1233
|
+
try {
|
|
1234
|
+
await onComplete({
|
|
1235
|
+
...steps.reduce((acc, step) => ({
|
|
1236
|
+
...acc,
|
|
1237
|
+
...stepper.metadata.get(step.id) || {}
|
|
1238
|
+
}), {}),
|
|
1239
|
+
...data
|
|
1240
|
+
});
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
console.error("Stepper form completion error:", error);
|
|
1243
|
+
} finally {
|
|
1244
|
+
setIsSubmitting(false);
|
|
1245
|
+
}
|
|
1246
|
+
} else {
|
|
1247
|
+
const nextStepId = stepper.lookup.getNext(stepper.state.current.data.id)?.id;
|
|
1248
|
+
if (nextStepId) {
|
|
1249
|
+
stepper.navigation.goTo(nextStepId);
|
|
1250
|
+
onStepChange?.(nextStepId, "next");
|
|
1492
1251
|
}
|
|
1493
1252
|
}
|
|
1253
|
+
}, [
|
|
1254
|
+
stepper,
|
|
1255
|
+
steps,
|
|
1256
|
+
onComplete,
|
|
1257
|
+
onStepChange
|
|
1258
|
+
]);
|
|
1259
|
+
const instance = adapter.useCreateForm({
|
|
1260
|
+
schema: currentStepConfig.schema,
|
|
1261
|
+
defaultValues: storedValues,
|
|
1262
|
+
mode: "onSubmit",
|
|
1263
|
+
id: `${id ?? "stepper"}-${currentStepConfig.id}`,
|
|
1264
|
+
onSubmit: handleStepSubmit,
|
|
1265
|
+
formRef
|
|
1494
1266
|
});
|
|
1495
1267
|
const next = React$1.useCallback(() => {
|
|
1496
1268
|
formRef.current?.requestSubmit();
|
|
1497
1269
|
}, []);
|
|
1498
1270
|
const prev = React$1.useCallback(() => {
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
const currentData = {};
|
|
1502
|
-
formData.forEach((value, key) => {
|
|
1503
|
-
if (!key.startsWith("$")) currentData[key] = value;
|
|
1504
|
-
});
|
|
1505
|
-
if (Object.keys(currentData).length > 0) stepper.metadata.set(stepper.state.current.data.id, currentData);
|
|
1506
|
-
}
|
|
1271
|
+
const currentValues = instance.getValues();
|
|
1272
|
+
if (Object.keys(currentValues).length > 0) stepper.metadata.set(stepper.state.current.data.id, currentValues);
|
|
1507
1273
|
const prevStepId = stepper.lookup.getPrev(stepper.state.current.data.id)?.id;
|
|
1508
1274
|
if (prevStepId) {
|
|
1509
1275
|
stepper.navigation.goTo(prevStepId);
|
|
1510
1276
|
onStepChange?.(prevStepId, "prev");
|
|
1511
1277
|
}
|
|
1512
|
-
}, [
|
|
1278
|
+
}, [
|
|
1279
|
+
instance,
|
|
1280
|
+
stepper,
|
|
1281
|
+
onStepChange
|
|
1282
|
+
]);
|
|
1513
1283
|
const goTo = React$1.useCallback((stepId) => {
|
|
1514
|
-
const currentIndex = stepper.lookup.getIndex(stepper.state.current.data.id);
|
|
1515
1284
|
if (stepper.lookup.getIndex(stepId) < currentIndex) {
|
|
1285
|
+
const currentValues = instance.getValues();
|
|
1286
|
+
if (Object.keys(currentValues).length > 0) stepper.metadata.set(stepper.state.current.data.id, currentValues);
|
|
1516
1287
|
stepper.navigation.goTo(stepId);
|
|
1517
1288
|
onStepChange?.(stepId, "prev");
|
|
1518
1289
|
}
|
|
1519
|
-
}, [
|
|
1290
|
+
}, [
|
|
1291
|
+
instance,
|
|
1292
|
+
stepper,
|
|
1293
|
+
currentIndex,
|
|
1294
|
+
onStepChange
|
|
1295
|
+
]);
|
|
1520
1296
|
const getStepData = React$1.useCallback((stepId) => stepper.metadata.get(stepId), [stepper]);
|
|
1521
1297
|
const getAllStepData = React$1.useCallback(() => {
|
|
1522
1298
|
return steps.reduce((acc, step) => ({
|
|
@@ -1527,7 +1303,7 @@ function StepForm({ steps, stepper, currentStepConfig, combinedSchema, storedVal
|
|
|
1527
1303
|
const stepperContextValue = React$1.useMemo(() => ({
|
|
1528
1304
|
steps,
|
|
1529
1305
|
current: currentStepConfig,
|
|
1530
|
-
currentIndex
|
|
1306
|
+
currentIndex,
|
|
1531
1307
|
next,
|
|
1532
1308
|
prev,
|
|
1533
1309
|
goTo,
|
|
@@ -1535,10 +1311,11 @@ function StepForm({ steps, stepper, currentStepConfig, combinedSchema, storedVal
|
|
|
1535
1311
|
isLast: stepper.state.isLast,
|
|
1536
1312
|
getStepData,
|
|
1537
1313
|
getAllStepData,
|
|
1538
|
-
utils: { getIndex: (
|
|
1314
|
+
utils: { getIndex: (stepId) => stepper.lookup.getIndex(stepId) }
|
|
1539
1315
|
}), [
|
|
1540
1316
|
steps,
|
|
1541
1317
|
currentStepConfig,
|
|
1318
|
+
currentIndex,
|
|
1542
1319
|
stepper,
|
|
1543
1320
|
next,
|
|
1544
1321
|
prev,
|
|
@@ -1546,22 +1323,18 @@ function StepForm({ steps, stepper, currentStepConfig, combinedSchema, storedVal
|
|
|
1546
1323
|
getStepData,
|
|
1547
1324
|
getAllStepData
|
|
1548
1325
|
]);
|
|
1549
|
-
const
|
|
1550
|
-
form,
|
|
1551
|
-
fields,
|
|
1326
|
+
const contextValue = React$1.useMemo(() => ({
|
|
1327
|
+
form: instance,
|
|
1328
|
+
fields: instance.fields,
|
|
1552
1329
|
isSubmitting,
|
|
1553
1330
|
submit: () => formRef.current?.requestSubmit(),
|
|
1554
|
-
reset: () =>
|
|
1555
|
-
formId:
|
|
1556
|
-
}), [
|
|
1557
|
-
form,
|
|
1558
|
-
fields,
|
|
1559
|
-
isSubmitting
|
|
1560
|
-
]);
|
|
1331
|
+
reset: () => instance.reset(),
|
|
1332
|
+
formId: instance.id
|
|
1333
|
+
}), [instance, isSubmitting]);
|
|
1561
1334
|
const renderProps = {
|
|
1562
1335
|
steps,
|
|
1563
1336
|
current: currentStepConfig,
|
|
1564
|
-
currentIndex
|
|
1337
|
+
currentIndex,
|
|
1565
1338
|
next,
|
|
1566
1339
|
prev,
|
|
1567
1340
|
goTo,
|
|
@@ -1573,16 +1346,21 @@ function StepForm({ steps, stepper, currentStepConfig, combinedSchema, storedVal
|
|
|
1573
1346
|
const resolvedChildren = typeof children === "function" ? children(renderProps) : children;
|
|
1574
1347
|
return /* @__PURE__ */ jsx(FormStepperContext, {
|
|
1575
1348
|
value: stepperContextValue,
|
|
1576
|
-
children: /* @__PURE__ */ jsx(FormProvider
|
|
1577
|
-
value:
|
|
1578
|
-
children: /* @__PURE__ */ jsx(FormProvider, {
|
|
1579
|
-
|
|
1349
|
+
children: /* @__PURE__ */ jsx(FormProvider, {
|
|
1350
|
+
value: contextValue,
|
|
1351
|
+
children: /* @__PURE__ */ jsx(adapter.FormProvider, {
|
|
1352
|
+
instance,
|
|
1580
1353
|
children: /* @__PURE__ */ jsx(FormComp, {
|
|
1581
1354
|
ref: formRef,
|
|
1582
|
-
...
|
|
1583
|
-
method: "POST",
|
|
1355
|
+
...instance.formProps,
|
|
1584
1356
|
className: cn("space-y-6", className),
|
|
1585
1357
|
autoComplete: "off",
|
|
1358
|
+
noValidate: true,
|
|
1359
|
+
onSubmit: (e) => {
|
|
1360
|
+
e.stopPropagation();
|
|
1361
|
+
const adapterSubmit = instance.formProps.onSubmit;
|
|
1362
|
+
adapterSubmit?.(e);
|
|
1363
|
+
},
|
|
1586
1364
|
children: resolvedChildren
|
|
1587
1365
|
})
|
|
1588
1366
|
})
|
|
@@ -1783,110 +1561,77 @@ StepperNavigation.displayName = "Form.StepperNavigation";
|
|
|
1783
1561
|
//#endregion
|
|
1784
1562
|
//#region src/components/features/form/components/form-input-group.tsx
|
|
1785
1563
|
/**
|
|
1786
|
-
* Form.
|
|
1564
|
+
* Form.InputGroup - Input with leading/trailing addons
|
|
1787
1565
|
*
|
|
1788
1566
|
* Automatically wired to the parent Form.Field context.
|
|
1789
1567
|
*
|
|
1790
1568
|
* @example
|
|
1791
1569
|
* ```tsx
|
|
1792
|
-
* <Form.Field name="
|
|
1793
|
-
* <Form.
|
|
1570
|
+
* <Form.Field name="website" label="Website" required>
|
|
1571
|
+
* <Form.InputGroup leading="https://" placeholder="example.com" />
|
|
1794
1572
|
* </Form.Field>
|
|
1795
1573
|
* ```
|
|
1796
1574
|
*/
|
|
1797
|
-
function FormInputGroup({ ref,
|
|
1798
|
-
const {
|
|
1799
|
-
const inputProps = getInputProps(fieldMeta, { type });
|
|
1575
|
+
function FormInputGroup({ ref, className, disabled, ...props }) {
|
|
1576
|
+
const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
|
|
1800
1577
|
const isDisabled = disabled ?? fieldDisabled;
|
|
1801
1578
|
const hasErrors = errors && errors.length > 0;
|
|
1802
1579
|
return /* @__PURE__ */ jsx(InputWithAddons, {
|
|
1803
|
-
ref,
|
|
1804
|
-
...inputProps,
|
|
1805
1580
|
...props,
|
|
1806
|
-
|
|
1581
|
+
ref,
|
|
1582
|
+
id,
|
|
1583
|
+
name: fieldState?.name,
|
|
1584
|
+
value: fieldState?.value ?? "",
|
|
1585
|
+
onChange: (e) => fieldState?.change(e.target.value),
|
|
1586
|
+
onBlur: () => fieldState?.blur(),
|
|
1587
|
+
className: cn("text-xs!", className),
|
|
1807
1588
|
disabled: isDisabled,
|
|
1808
1589
|
"aria-invalid": hasErrors || void 0,
|
|
1809
|
-
"aria-describedby": hasErrors ? `${
|
|
1810
|
-
className: cn("text-xs!", className)
|
|
1590
|
+
"aria-describedby": hasErrors ? `${id}-error` : void 0
|
|
1811
1591
|
});
|
|
1812
1592
|
}
|
|
1813
1593
|
FormInputGroup.displayName = "Form.InputGroup";
|
|
1814
1594
|
//#endregion
|
|
1815
1595
|
//#region src/components/features/form/hooks/use-field.ts
|
|
1816
1596
|
/**
|
|
1817
|
-
* Hook to access and control a specific field
|
|
1818
|
-
*
|
|
1597
|
+
* Hook to access and control a specific field.
|
|
1598
|
+
* Delegates to the active adapter's useField implementation.
|
|
1819
1599
|
*
|
|
1820
1600
|
* @example
|
|
1821
1601
|
* ```tsx
|
|
1822
1602
|
* function MyCustomInput({ name }: { name: string }) {
|
|
1823
|
-
* const { field, control, meta, errors } = useField(name)
|
|
1824
|
-
*
|
|
1603
|
+
* const { field, control, meta, errors } = useField(name)
|
|
1825
1604
|
* return (
|
|
1826
|
-
* <
|
|
1827
|
-
*
|
|
1828
|
-
*
|
|
1829
|
-
*
|
|
1830
|
-
*
|
|
1831
|
-
*
|
|
1832
|
-
*
|
|
1833
|
-
*
|
|
1834
|
-
*
|
|
1835
|
-
* {errors?.map((error) => (
|
|
1836
|
-
* <span key={error} className="text-red-500">{error}</span>
|
|
1837
|
-
* ))}
|
|
1838
|
-
* </div>
|
|
1839
|
-
* );
|
|
1605
|
+
* <input
|
|
1606
|
+
* name={meta.name}
|
|
1607
|
+
* id={meta.id}
|
|
1608
|
+
* value={control.value ?? ''}
|
|
1609
|
+
* onChange={(e) => control.change(e.target.value)}
|
|
1610
|
+
* onBlur={control.blur}
|
|
1611
|
+
* aria-invalid={!!errors?.length}
|
|
1612
|
+
* />
|
|
1613
|
+
* )
|
|
1840
1614
|
* }
|
|
1841
1615
|
* ```
|
|
1842
1616
|
*/
|
|
1843
1617
|
function useField(name) {
|
|
1844
|
-
const
|
|
1845
|
-
const field = React$1.useMemo(() => {
|
|
1846
|
-
const parts = name.split(".");
|
|
1847
|
-
let current = fields;
|
|
1848
|
-
for (let i = 0; i < parts.length; i++) {
|
|
1849
|
-
const part = parts[i];
|
|
1850
|
-
if (!current) break;
|
|
1851
|
-
if (/^\d+$/.test(part)) {
|
|
1852
|
-
const fieldList = current.getFieldList?.();
|
|
1853
|
-
if (fieldList) {
|
|
1854
|
-
const item = fieldList[Number.parseInt(part, 10)];
|
|
1855
|
-
if (i < parts.length - 1 && item?.getFieldset) current = item.getFieldset();
|
|
1856
|
-
else current = item;
|
|
1857
|
-
} else current = current[part];
|
|
1858
|
-
} else if (current[part] !== void 0) current = current[part];
|
|
1859
|
-
else if (typeof current.getFieldset === "function") current = current.getFieldset()[part];
|
|
1860
|
-
else current = void 0;
|
|
1861
|
-
}
|
|
1862
|
-
return current;
|
|
1863
|
-
}, [fields, name]);
|
|
1864
|
-
if (!field) throw new Error(`Field "${name}" not found in form. Make sure the field name matches your schema.`);
|
|
1865
|
-
const control = useInputControl(field);
|
|
1866
|
-
const controlValue = Array.isArray(control.value) ? control.value[0] : control.value;
|
|
1867
|
-
const meta = React$1.useMemo(() => ({
|
|
1868
|
-
name: field.name,
|
|
1869
|
-
id: field.id,
|
|
1870
|
-
errors: field.errors,
|
|
1871
|
-
required: field.required ?? false,
|
|
1872
|
-
disabled: field.disabled ?? false
|
|
1873
|
-
}), [
|
|
1874
|
-
field.name,
|
|
1875
|
-
field.id,
|
|
1876
|
-
field.errors,
|
|
1877
|
-
field.required,
|
|
1878
|
-
field.disabled
|
|
1879
|
-
]);
|
|
1618
|
+
const fieldState = useAdapter().useField(name);
|
|
1880
1619
|
return {
|
|
1881
|
-
field,
|
|
1620
|
+
field: fieldState,
|
|
1882
1621
|
control: {
|
|
1883
|
-
value:
|
|
1884
|
-
change:
|
|
1885
|
-
blur:
|
|
1886
|
-
focus:
|
|
1622
|
+
value: fieldState.value,
|
|
1623
|
+
change: fieldState.change,
|
|
1624
|
+
blur: fieldState.blur,
|
|
1625
|
+
focus: fieldState.focus
|
|
1626
|
+
},
|
|
1627
|
+
meta: {
|
|
1628
|
+
name: fieldState.name,
|
|
1629
|
+
id: fieldState.id,
|
|
1630
|
+
errors: fieldState.errors,
|
|
1631
|
+
required: fieldState.required,
|
|
1632
|
+
disabled: false
|
|
1887
1633
|
},
|
|
1888
|
-
|
|
1889
|
-
errors: field.errors
|
|
1634
|
+
errors: fieldState.errors
|
|
1890
1635
|
};
|
|
1891
1636
|
}
|
|
1892
1637
|
//#endregion
|
|
@@ -1986,12 +1731,27 @@ function useStepper() {
|
|
|
1986
1731
|
/**
|
|
1987
1732
|
* Datum Form Library
|
|
1988
1733
|
*
|
|
1989
|
-
* A compound component pattern form library
|
|
1990
|
-
*
|
|
1734
|
+
* A compound component pattern form library with pluggable adapter support.
|
|
1735
|
+
* Choose between Conform.js or React Hook Form as your form backend,
|
|
1736
|
+
* with Zod for schema validation.
|
|
1737
|
+
*
|
|
1738
|
+
* ## Adapter Setup
|
|
1739
|
+
*
|
|
1740
|
+
* Wrap your app with an adapter provider:
|
|
1741
|
+
*
|
|
1742
|
+
* ```tsx
|
|
1743
|
+
* // Conform adapter
|
|
1744
|
+
* import { ConformAdapter } from '@datum-cloud/datum-ui/form/adapters/conform'
|
|
1745
|
+
* <ConformAdapter><App /></ConformAdapter>
|
|
1746
|
+
*
|
|
1747
|
+
* // React Hook Form adapter
|
|
1748
|
+
* import { RHFAdapter } from '@datum-cloud/datum-ui/form/adapters/rhf'
|
|
1749
|
+
* <RHFAdapter><App /></RHFAdapter>
|
|
1750
|
+
* ```
|
|
1991
1751
|
*
|
|
1992
1752
|
* @example Basic Usage
|
|
1993
1753
|
* ```tsx
|
|
1994
|
-
* import { Form } from '
|
|
1754
|
+
* import { Form } from '@datum-cloud/datum-ui/form';
|
|
1995
1755
|
* import { z } from 'zod';
|
|
1996
1756
|
*
|
|
1997
1757
|
* const userSchema = z.object({
|
|
@@ -2046,36 +1806,25 @@ function useStepper() {
|
|
|
2046
1806
|
/**
|
|
2047
1807
|
* Form compound component
|
|
2048
1808
|
*
|
|
2049
|
-
*
|
|
1809
|
+
* Requires an adapter provider at the application root:
|
|
1810
|
+
* - `<ConformAdapter>` from `@datum-cloud/datum-ui/form/adapters/conform`
|
|
1811
|
+
* - `<RHFAdapter>` from `@datum-cloud/datum-ui/form/adapters/rhf`
|
|
1812
|
+
*
|
|
1813
|
+
* Components:
|
|
2050
1814
|
* - Form.Root - Main form container
|
|
2051
1815
|
* - Form.Field - Field wrapper with label and error handling
|
|
2052
|
-
* - Form.Input
|
|
2053
|
-
* - Form.
|
|
2054
|
-
* - Form.Select - Dropdown select
|
|
2055
|
-
* - Form.SelectItem - Select option
|
|
2056
|
-
* - Form.Checkbox - Checkbox input
|
|
2057
|
-
* - Form.Switch - Toggle switch
|
|
2058
|
-
* - Form.RadioGroup - Radio button group
|
|
2059
|
-
* - Form.RadioItem - Radio button option
|
|
2060
|
-
* - Form.Submit - Submit button with loading state
|
|
2061
|
-
* - Form.Error - Error display
|
|
2062
|
-
* - Form.Description - Helper text
|
|
2063
|
-
* - Form.Autocomplete - Searchable select with virtualization
|
|
1816
|
+
* - Form.Input, Form.Textarea, Form.Select, Form.Checkbox, Form.Switch, Form.RadioGroup
|
|
1817
|
+
* - Form.Autocomplete, Form.CopyBox, Form.InputGroup
|
|
2064
1818
|
* - Form.When - Conditional rendering
|
|
2065
1819
|
* - Form.FieldArray - Dynamic array of fields
|
|
2066
1820
|
* - Form.Custom - Escape hatch for custom implementations
|
|
2067
|
-
* - Form.Stepper
|
|
2068
|
-
* - Form.
|
|
2069
|
-
* - Form.
|
|
2070
|
-
*
|
|
2071
|
-
*
|
|
2072
|
-
*
|
|
2073
|
-
* - Form.
|
|
2074
|
-
* - Form.useFieldContext - Access field context
|
|
2075
|
-
* - Form.useField - Access and control a specific field
|
|
2076
|
-
* - Form.useWatch - Watch a field's value
|
|
2077
|
-
* - Form.useWatchAll - Watch multiple fields
|
|
2078
|
-
* - Form.useStepper - Access stepper context
|
|
1821
|
+
* - Form.Stepper, Form.Step, Form.StepperNavigation, Form.StepperControls
|
|
1822
|
+
* - Form.Dialog - Form rendered inside a Dialog
|
|
1823
|
+
* - Form.Submit, Form.Button, Form.Error, Form.Description
|
|
1824
|
+
*
|
|
1825
|
+
* Hooks:
|
|
1826
|
+
* - Form.useFormContext, Form.useFieldContext, Form.useField
|
|
1827
|
+
* - Form.useWatch, Form.useWatchAll, Form.useStepper
|
|
2079
1828
|
*/
|
|
2080
1829
|
const Form = {
|
|
2081
1830
|
Root: FormRoot,
|