@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.
Files changed (73) hide show
  1. package/README.md +75 -40
  2. package/dist/adapter-context-B7L2ucTr.mjs +25 -0
  3. package/dist/components/features/form/adapter-context.d.ts +17 -0
  4. package/dist/components/features/form/adapter-context.d.ts.map +1 -0
  5. package/dist/components/features/form/adapter-types.d.ts +100 -0
  6. package/dist/components/features/form/adapter-types.d.ts.map +1 -0
  7. package/dist/components/features/form/adapters/conform/conform-adapter.d.ts +9 -0
  8. package/dist/components/features/form/adapters/conform/conform-adapter.d.ts.map +1 -0
  9. package/dist/components/features/form/adapters/conform/conform-provider.d.ts +22 -0
  10. package/dist/components/features/form/adapters/conform/conform-provider.d.ts.map +1 -0
  11. package/dist/components/features/form/adapters/conform/index.d.ts +3 -0
  12. package/dist/components/features/form/adapters/conform/index.d.ts.map +1 -0
  13. package/dist/components/features/form/adapters/rhf/index.d.ts +3 -0
  14. package/dist/components/features/form/adapters/rhf/index.d.ts.map +1 -0
  15. package/dist/components/features/form/adapters/rhf/rhf-adapter.d.ts +10 -0
  16. package/dist/components/features/form/adapters/rhf/rhf-adapter.d.ts.map +1 -0
  17. package/dist/components/features/form/adapters/rhf/rhf-provider.d.ts +22 -0
  18. package/dist/components/features/form/adapters/rhf/rhf-provider.d.ts.map +1 -0
  19. package/dist/components/features/form/components/form-autocomplete.d.ts.map +1 -1
  20. package/dist/components/features/form/components/form-checkbox.d.ts.map +1 -1
  21. package/dist/components/features/form/components/form-copy-box.d.ts.map +1 -1
  22. package/dist/components/features/form/components/form-field-array.d.ts +5 -17
  23. package/dist/components/features/form/components/form-field-array.d.ts.map +1 -1
  24. package/dist/components/features/form/components/form-field.d.ts +7 -21
  25. package/dist/components/features/form/components/form-field.d.ts.map +1 -1
  26. package/dist/components/features/form/components/form-input-group.d.ts +4 -4
  27. package/dist/components/features/form/components/form-input-group.d.ts.map +1 -1
  28. package/dist/components/features/form/components/form-input.d.ts.map +1 -1
  29. package/dist/components/features/form/components/form-radio-group.d.ts.map +1 -1
  30. package/dist/components/features/form/components/form-root.d.ts +5 -25
  31. package/dist/components/features/form/components/form-root.d.ts.map +1 -1
  32. package/dist/components/features/form/components/form-select.d.ts.map +1 -1
  33. package/dist/components/features/form/components/form-switch.d.ts.map +1 -1
  34. package/dist/components/features/form/components/form-textarea.d.ts.map +1 -1
  35. package/dist/components/features/form/components/stepper/form-stepper.d.ts.map +1 -1
  36. package/dist/components/features/form/context/form-context.d.ts +2 -2
  37. package/dist/components/features/form/context/form-context.d.ts.map +1 -1
  38. package/dist/components/features/form/hooks/use-field.d.ts +12 -18
  39. package/dist/components/features/form/hooks/use-field.d.ts.map +1 -1
  40. package/dist/components/features/form/hooks/use-watch.d.ts +9 -20
  41. package/dist/components/features/form/hooks/use-watch.d.ts.map +1 -1
  42. package/dist/components/features/form/index.d.ts +33 -27
  43. package/dist/components/features/form/index.d.ts.map +1 -1
  44. package/dist/components/features/form/types/index.d.ts +32 -32
  45. package/dist/components/features/form/types/index.d.ts.map +1 -1
  46. package/dist/components/features/form/utils/get-field-constraints.d.ts +11 -0
  47. package/dist/components/features/form/utils/get-field-constraints.d.ts.map +1 -0
  48. package/dist/date-picker/index.mjs +1 -1
  49. package/dist/form/adapters/conform/index.mjs +237 -0
  50. package/dist/form/adapters/rhf/index.mjs +181 -0
  51. package/dist/form/index.mjs +3 -2
  52. package/dist/{form-Co3fM4B7.mjs → form-BE1xBne4.mjs} +328 -579
  53. package/dist/get-field-constraints-BPMW8VvY.mjs +48 -0
  54. package/dist/grid/index.mjs +1 -1
  55. package/dist/hooks/index.mjs +1 -1
  56. package/dist/index.mjs +12 -11
  57. package/dist/input-number/index.mjs +1 -1
  58. package/dist/map/index.mjs +1 -1
  59. package/dist/{map-ClxB41Hg.mjs → map-Cw7u8r6E.mjs} +1 -1
  60. package/dist/more-actions/index.mjs +1 -1
  61. package/dist/page-title/index.mjs +1 -1
  62. package/dist/tag-input/index.mjs +1 -1
  63. package/dist/task-queue/index.mjs +1 -1
  64. package/package.json +22 -2
  65. /package/dist/{col-q-J99UHe.mjs → col-YBbQ5wlb.mjs} +0 -0
  66. /package/dist/{hooks-Cb7YlxN4.mjs → hooks-DYjN7lvC.mjs} +0 -0
  67. /package/dist/{input-number-mDB-5M5C.mjs → input-number-DEjXG2I6.mjs} +0 -0
  68. /package/dist/{map-leaflet-imports-CaMm_rdF.mjs → map-leaflet-imports-D6nTEOIh.mjs} +0 -0
  69. /package/dist/{more-actions-CGagbIDT.mjs → more-actions-BNQ2yfWZ.mjs} +0 -0
  70. /package/dist/{page-title-R7QbfbWp.mjs → page-title-CNiRNZ7p.mjs} +0 -0
  71. /package/dist/{tag-input-BVSwNcRd.mjs → tag-input-BKed-cul.mjs} +0 -0
  72. /package/dist/{task-queue-dropdown-DyM5R8KF.mjs → task-queue-dropdown-Di_Wjspz.mjs} +0 -0
  73. /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 { fieldMeta, disabled: fieldDisabled, errors } = useFieldContext$1();
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 selectValue = Array.isArray(control.value) ? control.value[0] : control.value;
87
+ const value = fieldState?.value != null ? String(fieldState.value) : "";
90
88
  return /* @__PURE__ */ jsx(Autocomplete, {
91
89
  ...props,
92
- name: fieldMeta.name,
93
- id: fieldMeta.id,
94
- value: selectValue ?? "",
95
- onValueChange: control.change,
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$1({ children, value }) {
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 { fieldMeta, disabled: fieldDisabled, errors } = useFieldContext$1();
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 isChecked = control.value === "on" || control.value === "true";
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: checkboxId,
177
- name: fieldMeta.name,
178
- checked: isChecked,
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 ? `${fieldMeta.id}-error` : void 0
174
+ "aria-describedby": hasErrors ? `${id}-error` : void 0
183
175
  }), label && /* @__PURE__ */ jsx(Label, {
184
- htmlFor: checkboxId,
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 { fieldMeta } = useFieldContext$1();
222
- const control = useInputControl(fieldMeta);
223
- const [_, copy] = useCopyToClipboard();
224
- const [copied, setCopied] = React$1.useState(false);
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
- const stringValue = String(value);
228
- if (!stringValue) return;
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: String(value)
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 component
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 Address" required>
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" required>
548
- * {({ control, meta, fields }) => (
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 { fields, form, isSubmitting } = useFormContext$1();
560
- const fieldMeta = React$1.useMemo(() => {
561
- const parts = name.split(".");
562
- let current = fields;
563
- for (let i = 0; i < parts.length; i++) {
564
- const part = parts[i];
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: fieldMeta?.name ?? "",
538
+ name,
586
539
  id: fieldId,
587
540
  errors,
588
- required,
541
+ required: fieldRequired,
589
542
  disabled,
590
- fieldMeta
543
+ fieldState
591
544
  }), [
592
- fieldMeta,
545
+ name,
593
546
  fieldId,
594
547
  errors,
595
- required,
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 /* @__PURE__ */ jsx(FormFieldRenderContent, {
605
- fieldMeta,
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} className="flex gap-2">
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
- * <Form.Field name={`members.${index}.role`} label="Role">
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 type="button" onClick={() => append({ email: '', role: 'user' })}>
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 { fields, formId } = useFormContext$1();
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: (arrayField.getFieldList?.() ?? []).map((field, index) => ({
760
- id: field.id,
761
- key: field.key,
762
- name: `${name}.${index}`
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 { fieldMeta, disabled: fieldDisabled, errors } = useFieldContext$1();
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 ? `${fieldMeta.id}-error` : void 0,
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 { fieldMeta, disabled: fieldDisabled, errors } = useFieldContext$1();
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
- name: fieldMeta.name,
827
- value: radioValue ?? "",
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 ? `${fieldMeta.id}-error` : void 0,
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 - The root form component
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={userSchema} onSubmit={handleSubmit}>
885
- * <Form.Field name="email" label="Email" required>
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={userSchema} onSubmit={handleSubmit}>
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 shouldValidate = mode === "onChange" ? "onInput" : mode;
917
- const [form, fields] = useForm({
918
- id,
919
- constraint: getZodConstraint(schema),
920
- shouldValidate,
921
- shouldRevalidate: mode === "onSubmit" ? "onSubmit" : "onInput",
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
- if (!onSubmit) {
933
- setInternalIsSubmitting(true);
934
- return;
935
- }
936
- event.preventDefault();
937
- if (submission?.status === "success") {
938
- setInternalIsSubmitting(true);
939
- try {
940
- await onSubmit(submission.value);
941
- onSuccess?.(submission.value);
942
- telemetry?.onSuccess?.({
943
- formName,
944
- formId: id
945
- });
946
- } catch (error) {
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 = React$1.useMemo(() => ({
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
- const { onSubmit: conformOnSubmit, ...conformFormProps } = getFormProps(form);
1019
- return /* @__PURE__ */ jsx(FormProvider$1, {
830
+ return /* @__PURE__ */ jsx(FormProvider, {
1020
831
  value: contextValue,
1021
- children: /* @__PURE__ */ jsx(FormProvider, {
1022
- context: form.context,
832
+ children: /* @__PURE__ */ jsx(adapter.FormProvider, {
833
+ instance,
1023
834
  children: /* @__PURE__ */ jsx(FormComp, {
1024
835
  ref: formRef,
1025
- ...conformFormProps,
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 { fieldMeta, disabled: fieldDisabled, errors } = useFieldContext$1();
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
- name: fieldMeta.name,
1066
- value: selectValue ?? "",
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: fieldMeta.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 { fieldMeta, disabled: fieldDisabled, errors } = useFieldContext$1();
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 isChecked = control.value === "on" || control.value === "true";
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: switchId,
1147
- name: fieldMeta.name,
1148
- checked: isChecked,
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 ? `${fieldMeta.id}-error` : void 0
961
+ "aria-describedby": hasErrors ? `${id}-error` : void 0
1153
962
  }), label && /* @__PURE__ */ jsx(Label, {
1154
- htmlFor: switchId,
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 { fieldMeta, disabled: fieldDisabled, errors } = useFieldContext$1();
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 ? `${fieldMeta.id}-error` : void 0,
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
- * Triggers re-render when the watched field value changes
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
- const { fields } = useFormContext$1();
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
- const { fields } = useFormContext$1();
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 [form, fields] = useForm({
1459
- id: id ?? "stepper-form",
1460
- constraint: getZodConstraint(combinedSchema),
1461
- shouldValidate: "onBlur",
1462
- shouldRevalidate: "onInput",
1463
- defaultValue: storedValues,
1464
- onValidate({ formData }) {
1465
- return parseWithZod(formData, { schema: currentStepConfig.schema });
1466
- },
1467
- async onSubmit(event, { submission }) {
1468
- event.preventDefault();
1469
- if (submission?.status !== "success") return;
1470
- if (submission.value) stepper.metadata.set(stepper.state.current.data.id, submission.value);
1471
- if (stepper.state.isLast) {
1472
- setIsSubmitting(true);
1473
- try {
1474
- await onComplete({
1475
- ...steps.reduce((acc, step) => ({
1476
- ...acc,
1477
- ...stepper.metadata.get(step.id) || {}
1478
- }), {}),
1479
- ...submission.value
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
- if (formRef.current) {
1500
- const formData = new FormData(formRef.current);
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
- }, [stepper, onStepChange]);
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
- }, [stepper, onStepChange]);
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: stepper.lookup.getIndex(stepper.state.current.data.id),
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: (id) => stepper.lookup.getIndex(id) }
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 formContextValue = React$1.useMemo(() => ({
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: () => form.reset(),
1555
- formId: form.id
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: stepper.lookup.getIndex(stepper.state.current.data.id),
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$1, {
1577
- value: formContextValue,
1578
- children: /* @__PURE__ */ jsx(FormProvider, {
1579
- context: form.context,
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
- ...getFormProps(form),
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.Input - Text input component
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="email" label="Email" required>
1793
- * <Form.Input type="email" placeholder="john@example.com" />
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, type = "text", className, disabled, ...props }) {
1798
- const { fieldMeta, disabled: fieldDisabled, errors } = useFieldContext$1();
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
- type,
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 ? `${fieldMeta.id}-error` : void 0,
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
- * Provides field metadata, control methods, and errors
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
- * <div>
1827
- * <input
1828
- * name={meta.name}
1829
- * id={meta.id}
1830
- * value={control.value ?? ''}
1831
- * onChange={(e) => control.change(e.target.value)}
1832
- * onBlur={control.blur}
1833
- * aria-invalid={!!errors?.length}
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 { fields } = useFormContext$1();
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: controlValue,
1884
- change: control.change,
1885
- blur: control.blur,
1886
- focus: control.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
- meta,
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 built on top of Conform.js and Zod
1990
- * for easy form creation with built-in validation, error handling, and accessibility features.
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
- * Contains all form-related components as properties:
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 - Text input
2053
- * - Form.Textarea - Multi-line text input
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 - Multi-step form container
2068
- * - Form.Step - Individual step content
2069
- * - Form.StepperNavigation - Step progress indicators
2070
- * - Form.StepperControls - Previous/Next/Submit buttons
2071
- *
2072
- * Hooks available:
2073
- * - Form.useFormContext - Access form context
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,