@datum-cloud/datum-ui 0.6.0-alpha.b817c77 → 0.6.0-alpha.b8a44ac

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 (126) hide show
  1. package/README.md +3 -0
  2. package/dist/combobox/index.mjs +2 -0
  3. package/dist/combobox-cKTFK4uN.mjs +96 -0
  4. package/dist/components/features/combobox/combobox.d.ts +27 -0
  5. package/dist/components/features/combobox/combobox.d.ts.map +1 -0
  6. package/dist/components/features/combobox/index.d.ts +3 -0
  7. package/dist/components/features/combobox/index.d.ts.map +1 -0
  8. package/dist/components/features/combobox/types.d.ts +78 -0
  9. package/dist/components/features/combobox/types.d.ts.map +1 -0
  10. package/dist/components/features/date-time-picker/date-time-picker.d.ts +9 -0
  11. package/dist/components/features/date-time-picker/date-time-picker.d.ts.map +1 -0
  12. package/dist/components/features/date-time-picker/index.d.ts +3 -0
  13. package/dist/components/features/date-time-picker/index.d.ts.map +1 -0
  14. package/dist/components/features/date-time-picker/types.d.ts +53 -0
  15. package/dist/components/features/date-time-picker/types.d.ts.map +1 -0
  16. package/dist/components/features/date-time-picker/utils/format.d.ts +13 -0
  17. package/dist/components/features/date-time-picker/utils/format.d.ts.map +1 -0
  18. package/dist/components/features/date-time-picker/utils/index.d.ts +3 -0
  19. package/dist/components/features/date-time-picker/utils/index.d.ts.map +1 -0
  20. package/dist/components/features/date-time-picker/utils/timezone.d.ts +23 -0
  21. package/dist/components/features/date-time-picker/utils/timezone.d.ts.map +1 -0
  22. package/dist/components/features/form/adapter-types.d.ts +20 -0
  23. package/dist/components/features/form/adapter-types.d.ts.map +1 -1
  24. package/dist/components/features/form/adapters/conform/conform-adapter.d.ts.map +1 -1
  25. package/dist/components/features/form/adapters/rhf/rhf-adapter.d.ts.map +1 -1
  26. package/dist/components/features/form/components/form-autosearch.d.ts +25 -0
  27. package/dist/components/features/form/components/form-autosearch.d.ts.map +1 -0
  28. package/dist/components/features/form/components/form-combobox.d.ts +76 -0
  29. package/dist/components/features/form/components/form-combobox.d.ts.map +1 -0
  30. package/dist/components/features/form/components/form-copy-box.d.ts +3 -0
  31. package/dist/components/features/form/components/form-copy-box.d.ts.map +1 -1
  32. package/dist/components/features/form/components/form-custom.d.ts.map +1 -1
  33. package/dist/components/features/form/components/form-date-picker.d.ts +38 -0
  34. package/dist/components/features/form/components/form-date-picker.d.ts.map +1 -0
  35. package/dist/components/features/form/components/form-date-time-picker.d.ts +37 -0
  36. package/dist/components/features/form/components/form-date-time-picker.d.ts.map +1 -0
  37. package/dist/components/features/form/components/form-dialog.d.ts.map +1 -1
  38. package/dist/components/features/form/components/form-radio-group.d.ts.map +1 -1
  39. package/dist/components/features/form/components/form-root.d.ts.map +1 -1
  40. package/dist/components/features/form/components/form-time-picker.d.ts +21 -0
  41. package/dist/components/features/form/components/form-time-picker.d.ts.map +1 -0
  42. package/dist/components/features/form/components/form-transfer.d.ts +37 -0
  43. package/dist/components/features/form/components/form-transfer.d.ts.map +1 -0
  44. package/dist/components/features/form/components/index.d.ts +7 -1
  45. package/dist/components/features/form/components/index.d.ts.map +1 -1
  46. package/dist/components/features/form/components/stepper/form-stepper.d.ts.map +1 -1
  47. package/dist/components/features/form/hooks/index.d.ts +1 -1
  48. package/dist/components/features/form/hooks/index.d.ts.map +1 -1
  49. package/dist/components/features/form/hooks/use-form-state.d.ts +36 -0
  50. package/dist/components/features/form/hooks/use-form-state.d.ts.map +1 -0
  51. package/dist/components/features/form/index.d.ts +39 -21
  52. package/dist/components/features/form/index.d.ts.map +1 -1
  53. package/dist/components/features/form/stepper/index.d.ts +17 -0
  54. package/dist/components/features/form/stepper/index.d.ts.map +1 -0
  55. package/dist/components/features/form/types/index.d.ts +36 -0
  56. package/dist/components/features/form/types/index.d.ts.map +1 -1
  57. package/dist/components/features/form/utils/get-field-constraints.d.ts +23 -1
  58. package/dist/components/features/form/utils/get-field-constraints.d.ts.map +1 -1
  59. package/dist/components/features/form/utils/get-schema-defaults.d.ts +24 -0
  60. package/dist/components/features/form/utils/get-schema-defaults.d.ts.map +1 -0
  61. package/dist/components/features/form/utils/zod-helpers.d.ts +12 -0
  62. package/dist/components/features/form/utils/zod-helpers.d.ts.map +1 -0
  63. package/dist/components/features/time-picker/index.d.ts +3 -0
  64. package/dist/components/features/time-picker/index.d.ts.map +1 -0
  65. package/dist/components/features/time-picker/time-picker.d.ts +22 -0
  66. package/dist/components/features/time-picker/time-picker.d.ts.map +1 -0
  67. package/dist/components/features/time-picker/types.d.ts +31 -0
  68. package/dist/components/features/time-picker/types.d.ts.map +1 -0
  69. package/dist/components/features/transfer/components/index.d.ts +9 -0
  70. package/dist/components/features/transfer/components/index.d.ts.map +1 -0
  71. package/dist/components/features/transfer/components/transfer-group.d.ts +7 -0
  72. package/dist/components/features/transfer/components/transfer-group.d.ts.map +1 -0
  73. package/dist/components/features/transfer/components/transfer-item.d.ts +10 -0
  74. package/dist/components/features/transfer/components/transfer-item.d.ts.map +1 -0
  75. package/dist/components/features/transfer/components/transfer-panel.d.ts +18 -0
  76. package/dist/components/features/transfer/components/transfer-panel.d.ts.map +1 -0
  77. package/dist/components/features/transfer/components/transfer-search.d.ts +9 -0
  78. package/dist/components/features/transfer/components/transfer-search.d.ts.map +1 -0
  79. package/dist/components/features/transfer/hooks/use-transfer-dnd.d.ts +26 -0
  80. package/dist/components/features/transfer/hooks/use-transfer-dnd.d.ts.map +1 -0
  81. package/dist/components/features/transfer/hooks/use-transfer-state.d.ts +20 -0
  82. package/dist/components/features/transfer/hooks/use-transfer-state.d.ts.map +1 -0
  83. package/dist/components/features/transfer/index.d.ts +3 -0
  84. package/dist/components/features/transfer/index.d.ts.map +1 -0
  85. package/dist/components/features/transfer/transfer.d.ts +6 -0
  86. package/dist/components/features/transfer/transfer.d.ts.map +1 -0
  87. package/dist/components/features/transfer/types.d.ts +69 -0
  88. package/dist/components/features/transfer/types.d.ts.map +1 -0
  89. package/dist/date-picker/index.mjs +1 -1
  90. package/dist/date-time-picker/index.mjs +2 -0
  91. package/dist/date-time-picker-Dy2jrJoN.mjs +175 -0
  92. package/dist/form/adapters/conform/index.mjs +102 -12
  93. package/dist/form/adapters/rhf/index.mjs +112 -26
  94. package/dist/form/index.mjs +3 -3
  95. package/dist/form/stepper/index.mjs +541 -0
  96. package/dist/form-context-Ccxm-wqL.mjs +17 -0
  97. package/dist/{form-BE1xBne4.mjs → form-mlNLKaB5.mjs} +350 -592
  98. package/dist/{get-field-constraints-BPMW8VvY.mjs → get-field-constraints-BicgDkfH.mjs} +19 -16
  99. package/dist/grid/index.mjs +1 -1
  100. package/dist/hooks/index.mjs +2 -2
  101. package/dist/index.mjs +14 -14
  102. package/dist/input-number/index.mjs +1 -1
  103. package/dist/map/index.mjs +1 -1
  104. package/dist/{map-Cw7u8r6E.mjs → map-CWIQ-eql.mjs} +1 -1
  105. package/dist/more-actions/index.mjs +1 -1
  106. package/dist/page-title/index.mjs +1 -1
  107. package/dist/stepper/index.mjs +1 -320
  108. package/dist/stepper-DvIOp0hh.mjs +321 -0
  109. package/dist/tag-input/index.mjs +1 -1
  110. package/dist/task-queue/index.mjs +1 -1
  111. package/dist/time-picker/index.mjs +2 -0
  112. package/dist/time-picker-BoF7pZZ2.mjs +43 -0
  113. package/dist/transfer/index.mjs +2 -0
  114. package/dist/transfer-B2n8pgEQ.mjs +260 -0
  115. package/package.json +37 -1
  116. /package/dist/{adapter-context-B7L2ucTr.mjs → adapter-context-rWveHhDd.mjs} +0 -0
  117. /package/dist/{col-YBbQ5wlb.mjs → col-1T0Q3SlH.mjs} +0 -0
  118. /package/dist/{hooks-DYjN7lvC.mjs → hooks-D8r2M2U6.mjs} +0 -0
  119. /package/dist/{input-number-DEjXG2I6.mjs → input-number-a7uydAsw.mjs} +0 -0
  120. /package/dist/{map-leaflet-imports-D6nTEOIh.mjs → map-leaflet-imports-CRSKA79m.mjs} +0 -0
  121. /package/dist/{more-actions-BNQ2yfWZ.mjs → more-actions-ILnEZq_E.mjs} +0 -0
  122. /package/dist/{page-title-CNiRNZ7p.mjs → page-title-ChsnpBiH.mjs} +0 -0
  123. /package/dist/{tag-input-BKed-cul.mjs → tag-input-T9cUX9-G.mjs} +0 -0
  124. /package/dist/{task-queue-dropdown-Di_Wjspz.mjs → task-queue-dropdown-Wcbj-f0V.mjs} +0 -0
  125. /package/dist/{to-api-format-Cq4prffn.mjs → to-api-format-Bh3c01gr.mjs} +0 -0
  126. /package/dist/{use-copy-to-clipboard-BGdTmkFV.mjs → use-copy-to-clipboard-uNeeVHC4.mjs} +0 -0
@@ -11,15 +11,19 @@ import { t as Tooltip } from "./tooltip-Cruvl5F6.mjs";
11
11
  import { t as Switch } from "./switch-DQJQhPIQ.mjs";
12
12
  import { t as Textarea } from "./textarea-BwD-MmTV.mjs";
13
13
  import { t as Autocomplete } from "./autocomplete-V5-qslzS.mjs";
14
+ import { t as CalendarDatePicker } from "./calendar-date-picker-DWK94_DC.mjs";
14
15
  import { t as toast } from "./toast-BWnN5fax.mjs";
15
- import { t as useCopyToClipboard } from "./use-copy-to-clipboard-BGdTmkFV.mjs";
16
- import { n as useAdapter } from "./adapter-context-B7L2ucTr.mjs";
17
- import { defineStepper } from "./stepper/index.mjs";
16
+ import { n as useFormContext$1, t as FormProvider } from "./form-context-Ccxm-wqL.mjs";
17
+ import { t as Combobox } from "./combobox-cKTFK4uN.mjs";
18
+ import { t as useCopyToClipboard } from "./use-copy-to-clipboard-uNeeVHC4.mjs";
19
+ import { t as DateTimePicker } from "./date-time-picker-Dy2jrJoN.mjs";
20
+ import { n as useAdapter } from "./adapter-context-rWveHhDd.mjs";
18
21
  import { InputWithAddons } from "./input-with-addons/index.mjs";
22
+ import { t as TimePicker } from "./time-picker-BoF7pZZ2.mjs";
23
+ import { t as Transfer } from "./transfer-B2n8pgEQ.mjs";
19
24
  import { CheckIcon, CircleHelp, CopyIcon } from "lucide-react";
20
25
  import * as React$1 from "react";
21
26
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
22
- import { z } from "zod";
23
27
  //#region src/components/features/form/context/field-context.tsx
24
28
  const FieldContext = React$1.createContext(null);
25
29
  function FieldProvider({ children, value }) {
@@ -98,19 +102,27 @@ function FormAutocomplete({ disabled, className, ...props }) {
98
102
  }
99
103
  FormAutocomplete.displayName = "Form.Autocomplete";
100
104
  //#endregion
101
- //#region src/components/features/form/context/form-context.tsx
102
- const FormContext = React$1.createContext(null);
103
- function FormProvider({ children, value }) {
104
- return /* @__PURE__ */ jsx(FormContext, {
105
- value,
106
- children
107
- });
108
- }
109
- function useFormContext$1() {
110
- const context = React$1.use(FormContext);
111
- if (!context) throw new Error("useFormContext must be used within a Form.Root component");
112
- return context;
105
+ //#region src/components/features/form/components/form-autosearch.tsx
106
+ /**
107
+ * Form.Autosearch - Alias to Form.Autocomplete with search-first focus
108
+ *
109
+ * This is a convenience wrapper around Form.Autocomplete that emphasizes
110
+ * the search functionality. It's functionally identical to Form.Autocomplete.
111
+ *
112
+ * @example Basic usage
113
+ * ```tsx
114
+ * <Form.Field name="search" label="Search">
115
+ * <Form.Autosearch
116
+ * options={options}
117
+ * placeholder="Type to search..."
118
+ * />
119
+ * </Form.Field>
120
+ * ```
121
+ */
122
+ function FormAutosearch(props) {
123
+ return /* @__PURE__ */ jsx(FormAutocomplete, { ...props });
113
124
  }
125
+ FormAutosearch.displayName = "Form.Autosearch";
114
126
  //#endregion
115
127
  //#region src/components/features/form/components/form-button.tsx
116
128
  /**
@@ -181,6 +193,34 @@ function FormCheckbox({ label, disabled, className }) {
181
193
  }
182
194
  FormCheckbox.displayName = "Form.Checkbox";
183
195
  //#endregion
196
+ //#region src/components/features/form/components/form-combobox.tsx
197
+ function FormCombobox({ options, placeholder, searchPlaceholder, emptyMessage, disabled, className, triggerClassName, contentClassName, searchable = true, showDropdownArrow = true, clearable = false, "data-testid": testId }) {
198
+ const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
199
+ const isDisabled = disabled ?? fieldDisabled;
200
+ const hasErrors = errors && errors.length > 0;
201
+ const handleChange = React$1.useCallback((value) => {
202
+ fieldState?.change(value ?? "");
203
+ }, [fieldState]);
204
+ return /* @__PURE__ */ jsx(Combobox, {
205
+ id,
206
+ options,
207
+ value: fieldState?.value ?? "",
208
+ onChange: handleChange,
209
+ placeholder,
210
+ searchPlaceholder,
211
+ emptyMessage,
212
+ disabled: isDisabled,
213
+ searchable,
214
+ showDropdownArrow,
215
+ clearable,
216
+ className,
217
+ triggerClassName: cn(hasErrors && "border-destructive", triggerClassName),
218
+ contentClassName,
219
+ "data-testid": testId
220
+ });
221
+ }
222
+ FormCombobox.displayName = "Form.Combobox";
223
+ //#endregion
184
224
  //#region src/components/features/form/components/form-copy-box.tsx
185
225
  /**
186
226
  * Form.CopyBox - Read-only field with copy-to-clipboard functionality
@@ -246,6 +286,7 @@ function FormCopyBox({ variant = "default", className, contentClassName, buttonC
246
286
  })]
247
287
  });
248
288
  }
289
+ FormCopyBox.displayName = "Form.CopyBox";
249
290
  //#endregion
250
291
  //#region src/components/features/form/components/form-custom.tsx
251
292
  /**
@@ -270,17 +311,114 @@ function FormCopyBox({ variant = "default", className, contentClassName, buttonC
270
311
  * ```
271
312
  */
272
313
  function FormCustom({ children }) {
273
- const { form, fields, isSubmitting, submit, reset } = useFormContext$1();
314
+ const ctx = useFormContext$1();
274
315
  return /* @__PURE__ */ jsx(Fragment$1, { children: children({
275
- form,
276
- fields,
277
- isSubmitting,
278
- submit,
279
- reset
316
+ form: ctx.form,
317
+ fields: ctx.fields,
318
+ isSubmitting: ctx.isSubmitting,
319
+ isDirty: ctx.isDirty,
320
+ isValid: ctx.isValid,
321
+ isSubmitted: ctx.isSubmitted,
322
+ submitCount: ctx.submitCount,
323
+ dirtyFields: ctx.dirtyFields,
324
+ touchedFields: ctx.touchedFields,
325
+ submit: ctx.submit,
326
+ reset: ctx.reset
280
327
  }) });
281
328
  }
282
329
  FormCustom.displayName = "Form.Custom";
283
330
  //#endregion
331
+ //#region src/components/features/form/components/form-date-picker.tsx
332
+ function FormDatePicker({ placeholder, disabled, className, triggerClassName, numberOfMonths = 1, minDate: minDateProp, maxDate: maxDateProp, disableFuture, disablePast }) {
333
+ const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
334
+ const isDisabled = disabled ?? fieldDisabled;
335
+ const hasErrors = errors && errors.length > 0;
336
+ const currentValue = React$1.useMemo(() => {
337
+ const val = fieldState?.value;
338
+ if (!val) return {
339
+ from: void 0,
340
+ to: void 0
341
+ };
342
+ if (val instanceof Date) return {
343
+ from: val,
344
+ to: val
345
+ };
346
+ if (typeof val === "string") {
347
+ const date = new Date(val);
348
+ return {
349
+ from: date,
350
+ to: date
351
+ };
352
+ }
353
+ if (typeof val === "object" && "from" in val) return val;
354
+ return {
355
+ from: void 0,
356
+ to: void 0
357
+ };
358
+ }, [fieldState?.value]);
359
+ const minDate = minDateProp;
360
+ const maxDate = maxDateProp;
361
+ return /* @__PURE__ */ jsx(CalendarDatePicker, {
362
+ id,
363
+ date: currentValue,
364
+ onDateSelect: React$1.useCallback((range) => {
365
+ if (!range) {
366
+ fieldState?.change(void 0);
367
+ return;
368
+ }
369
+ if (numberOfMonths === 1) fieldState?.change(range.from.toISOString());
370
+ else fieldState?.change({
371
+ from: range.from.toISOString(),
372
+ to: range.to?.toISOString()
373
+ });
374
+ }, [fieldState, numberOfMonths]),
375
+ numberOfMonths,
376
+ placeholder,
377
+ disabled: isDisabled,
378
+ minDate,
379
+ maxDate,
380
+ disableFuture,
381
+ disablePast,
382
+ variant: "outline",
383
+ className: cn(className),
384
+ triggerClassName: cn(triggerClassName),
385
+ "aria-invalid": hasErrors || void 0,
386
+ "aria-describedby": hasErrors ? `${id}-error` : void 0
387
+ });
388
+ }
389
+ FormDatePicker.displayName = "Form.DatePicker";
390
+ //#endregion
391
+ //#region src/components/features/form/components/form-date-time-picker.tsx
392
+ function FormDateTimePicker({ minDate: minDateProp, maxDate: maxDateProp, disabledDates, timezone, showTimezoneIndicator, placeholder, disabled, className }) {
393
+ const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
394
+ const isDisabled = disabled ?? fieldDisabled;
395
+ const hasErrors = errors && errors.length > 0;
396
+ const currentValue = React$1.useMemo(() => {
397
+ const val = fieldState?.value;
398
+ if (!val) return void 0;
399
+ if (typeof val === "string") return val;
400
+ }, [fieldState?.value]);
401
+ const minDate = minDateProp;
402
+ const maxDate = maxDateProp;
403
+ return /* @__PURE__ */ jsx(DateTimePicker, {
404
+ value: currentValue,
405
+ onChange: React$1.useCallback((value) => {
406
+ fieldState?.change(value);
407
+ }, [fieldState]),
408
+ minDate,
409
+ maxDate,
410
+ disabledDates,
411
+ timezone,
412
+ showTimezoneIndicator,
413
+ placeholder,
414
+ disabled: isDisabled,
415
+ className: cn(className),
416
+ "aria-invalid": hasErrors || void 0,
417
+ "aria-describedby": hasErrors ? `${id}-error` : void 0
418
+ });
419
+ }
420
+ FormDateTimePicker.displayName = "Form.DateTimePicker";
421
+ //#endregion
284
422
  //#region src/components/features/form/components/form-description.tsx
285
423
  /**
286
424
  * Form.Description - Display field description/helper text
@@ -377,9 +515,6 @@ function FormDialog({ open, onOpenChange, defaultOpen, title, description, trigg
377
515
  try {
378
516
  await onSubmit?.(data);
379
517
  onSuccess?.(data);
380
- } catch (error) {
381
- console.error("Form submission error:", error);
382
- throw error;
383
518
  } finally {
384
519
  if (loading === void 0) setInternalIsSubmitting(false);
385
520
  }
@@ -675,6 +810,39 @@ function FormInput({ ref, type = "text", className, disabled, ...props }) {
675
810
  }
676
811
  FormInput.displayName = "Form.Input";
677
812
  //#endregion
813
+ //#region src/components/features/form/components/form-input-group.tsx
814
+ /**
815
+ * Form.InputGroup - Input with leading/trailing addons
816
+ *
817
+ * Automatically wired to the parent Form.Field context.
818
+ *
819
+ * @example
820
+ * ```tsx
821
+ * <Form.Field name="website" label="Website" required>
822
+ * <Form.InputGroup leading="https://" placeholder="example.com" />
823
+ * </Form.Field>
824
+ * ```
825
+ */
826
+ function FormInputGroup({ ref, className, disabled, ...props }) {
827
+ const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
828
+ const isDisabled = disabled ?? fieldDisabled;
829
+ const hasErrors = errors && errors.length > 0;
830
+ return /* @__PURE__ */ jsx(InputWithAddons, {
831
+ ...props,
832
+ ref,
833
+ id,
834
+ name: fieldState?.name,
835
+ value: fieldState?.value ?? "",
836
+ onChange: (e) => fieldState?.change(e.target.value),
837
+ onBlur: () => fieldState?.blur(),
838
+ className: cn("text-xs!", className),
839
+ disabled: isDisabled,
840
+ "aria-invalid": hasErrors || void 0,
841
+ "aria-describedby": hasErrors ? `${id}-error` : void 0
842
+ });
843
+ }
844
+ FormInputGroup.displayName = "Form.InputGroup";
845
+ //#endregion
678
846
  //#region src/components/features/form/components/form-radio-group.tsx
679
847
  /**
680
848
  * Form.RadioGroup - Radio button group component
@@ -716,7 +884,8 @@ FormRadioGroup.displayName = "Form.RadioGroup";
716
884
  * ```
717
885
  */
718
886
  function FormRadioItem({ value, label, description, disabled }) {
719
- const radioId = `radio-${value}`;
887
+ const { id: fieldId } = useFieldContext$1();
888
+ const radioId = `${fieldId}-radio-${value}`;
720
889
  return /* @__PURE__ */ jsxs("div", {
721
890
  className: "flex items-start space-x-2",
722
891
  children: [/* @__PURE__ */ jsx(RadioGroupItem, {
@@ -766,9 +935,13 @@ function FormRoot({ schema, children, onSubmit, action, method = "POST", formCom
766
935
  const adapter = useAdapter();
767
936
  const [internalIsSubmitting, setInternalIsSubmitting] = React$1.useState(false);
768
937
  const isSubmitting = externalIsSubmitting ?? internalIsSubmitting;
938
+ const [isSubmitted, setIsSubmitted] = React$1.useState(false);
939
+ const [submitCount, setSubmitCount] = React$1.useState(0);
769
940
  const formRef = React$1.useRef(null);
770
941
  const wrappedOnSubmit = React$1.useCallback(async (data) => {
771
942
  setInternalIsSubmitting(true);
943
+ setIsSubmitted(true);
944
+ setSubmitCount((prev) => prev + 1);
772
945
  try {
773
946
  await onSubmit?.(data);
774
947
  telemetry?.onSuccess?.({
@@ -807,22 +980,47 @@ function FormRoot({ schema, children, onSubmit, action, method = "POST", formCom
807
980
  onSubmit: onSubmit ? wrappedOnSubmit : void 0,
808
981
  formRef
809
982
  });
983
+ const { formState } = instance;
810
984
  const contextValue = React$1.useMemo(() => ({
811
985
  form: instance,
812
986
  fields: instance.fields,
813
987
  isSubmitting,
988
+ isDirty: formState.isDirty,
989
+ isValid: formState.isValid,
990
+ isSubmitted,
991
+ submitCount,
992
+ dirtyFields: formState.dirtyFields,
993
+ touchedFields: formState.touchedFields,
814
994
  submit: () => formRef.current?.requestSubmit(),
815
995
  reset: () => instance.reset(),
816
996
  formId: instance.id
817
- }), [instance, isSubmitting]);
997
+ }), [
998
+ instance,
999
+ isSubmitting,
1000
+ formState,
1001
+ isSubmitted,
1002
+ submitCount
1003
+ ]);
818
1004
  const isRenderFunction = typeof children === "function";
819
- const renderProps = {
1005
+ const renderProps = React$1.useMemo(() => ({
820
1006
  form: instance,
821
1007
  fields: instance.fields,
822
1008
  isSubmitting,
1009
+ isDirty: formState.isDirty,
1010
+ isValid: formState.isValid,
1011
+ isSubmitted,
1012
+ submitCount,
1013
+ dirtyFields: formState.dirtyFields,
1014
+ touchedFields: formState.touchedFields,
823
1015
  submit: () => formRef.current?.requestSubmit(),
824
1016
  reset: () => instance.reset()
825
- };
1017
+ }), [
1018
+ instance,
1019
+ isSubmitting,
1020
+ formState,
1021
+ isSubmitted,
1022
+ submitCount
1023
+ ]);
826
1024
  const renderChildren = () => {
827
1025
  if (isRenderFunction) return children(renderProps);
828
1026
  return children;
@@ -1002,6 +1200,73 @@ function FormTextarea({ ref, className, disabled, rows = 3, ...props }) {
1002
1200
  }
1003
1201
  FormTextarea.displayName = "Form.Textarea";
1004
1202
  //#endregion
1203
+ //#region src/components/features/form/components/form-time-picker.tsx
1204
+ function FormTimePicker({ min, max, step, placeholder, disabled, className }) {
1205
+ const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
1206
+ const isDisabled = disabled ?? fieldDisabled;
1207
+ const hasErrors = errors && errors.length > 0;
1208
+ return /* @__PURE__ */ jsx(TimePicker, {
1209
+ id,
1210
+ value: fieldState?.value ?? "",
1211
+ onChange: React$1.useCallback((value) => {
1212
+ fieldState?.change(value || void 0);
1213
+ }, [fieldState]),
1214
+ min,
1215
+ max,
1216
+ step,
1217
+ placeholder,
1218
+ disabled: isDisabled,
1219
+ className: cn(className),
1220
+ "aria-invalid": hasErrors || void 0,
1221
+ "aria-describedby": hasErrors ? `${id}-error` : void 0
1222
+ });
1223
+ }
1224
+ FormTimePicker.displayName = "Form.TimePicker";
1225
+ //#endregion
1226
+ //#region src/components/features/form/components/form-transfer.tsx
1227
+ /**
1228
+ * Form.Transfer - Transfer list component for selecting multiple items
1229
+ *
1230
+ * Automatically wired to the parent Form.Field context.
1231
+ * Displays minItems/maxItems constraints when provided.
1232
+ *
1233
+ * @example
1234
+ * ```tsx
1235
+ * <Form.Field name="teams" label="Select Teams">
1236
+ * <Form.Transfer
1237
+ * items={teams}
1238
+ * itemKey="id"
1239
+ * itemLabel="name"
1240
+ * minItems={2}
1241
+ * maxItems={5}
1242
+ * />
1243
+ * </Form.Field>
1244
+ * ```
1245
+ */
1246
+ function FormTransfer({ disabled, minItems, maxItems, ...props }) {
1247
+ const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
1248
+ const isDisabled = disabled ?? fieldDisabled;
1249
+ const hasErrors = errors && errors.length > 0;
1250
+ const value = Array.isArray(fieldState?.value) ? fieldState.value : [];
1251
+ const handleChange = React$1.useCallback((newValue) => {
1252
+ fieldState?.change(newValue);
1253
+ }, [fieldState]);
1254
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
1255
+ "aria-invalid": hasErrors || void 0,
1256
+ "aria-describedby": hasErrors ? `${id}-error` : void 0,
1257
+ children: /* @__PURE__ */ jsx(Transfer, {
1258
+ ...props,
1259
+ value,
1260
+ onChange: handleChange,
1261
+ disabled: isDisabled
1262
+ })
1263
+ }), (minItems != null || maxItems != null) && /* @__PURE__ */ jsx("p", {
1264
+ className: "text-sm text-muted-foreground mt-2",
1265
+ children: minItems != null && maxItems != null ? `Select between ${minItems} and ${maxItems} items` : minItems != null ? `Select at least ${minItems} items` : `Select up to ${maxItems} items`
1266
+ })] });
1267
+ }
1268
+ FormTransfer.displayName = "Form.Transfer";
1269
+ //#endregion
1005
1270
  //#region src/components/features/form/hooks/use-watch.ts
1006
1271
  /**
1007
1272
  * Hook to watch a field's value reactively.
@@ -1078,520 +1343,6 @@ function FormWhen({ field, is, isNot, in: inArray, notIn, children }) {
1078
1343
  }
1079
1344
  FormWhen.displayName = "Form.When";
1080
1345
  //#endregion
1081
- //#region src/components/features/form/components/stepper/form-stepper.tsx
1082
- const FormStepperContext = React$1.createContext(null);
1083
- function useFormStepperContext() {
1084
- const context = React$1.use(FormStepperContext);
1085
- if (!context) throw new Error("useFormStepperContext must be used within a Form.Stepper component");
1086
- return context;
1087
- }
1088
- /**
1089
- * Recursively unwrap ZodIntersection (from .and()) to extract the base ZodObject.
1090
- *
1091
- * Zod v4 schema types use `def.type` as a string discriminant:
1092
- * - "intersection" (from .and()): merge left + right base objects
1093
- * - "object": return directly
1094
- *
1095
- * Note: In Zod v4, .superRefine() and .refine() return `this` (no wrapper),
1096
- * so only ZodIntersection needs unwrapping.
1097
- */
1098
- function getBaseObject(schema) {
1099
- if (schema.def.type === "intersection") {
1100
- const intersectionDef = schema.def;
1101
- const left = getBaseObject(intersectionDef.left);
1102
- const right = getBaseObject(intersectionDef.right);
1103
- return left.merge(right);
1104
- }
1105
- if (schema.def.type !== "object") {
1106
- console.warn(`mergeSchemas: expected ZodObject or ZodIntersection but got "${schema.def.type}". Falling back to empty object.`);
1107
- return z.object({});
1108
- }
1109
- return schema;
1110
- }
1111
- /**
1112
- * Merge multiple zod schemas into one ZodObject for HTML constraint generation.
1113
- * Handles ZodIntersection (.and()) by unwrapping to base ZodObject shapes.
1114
- * Per-step validation still uses the original schemas with all refinements intact.
1115
- */
1116
- function mergeSchemas(steps) {
1117
- if (steps.length === 0) throw new Error("Form.Stepper requires at least one step");
1118
- return steps.reduce((acc, step, index) => {
1119
- const base = getBaseObject(step.schema);
1120
- if (index === 0) return base;
1121
- return acc.merge(base);
1122
- }, {});
1123
- }
1124
- /**
1125
- * Convert StepConfig[] to Stepperize step format
1126
- */
1127
- function toStepperizeSteps(steps) {
1128
- return steps.map((step) => ({
1129
- id: step.id,
1130
- label: step.label,
1131
- description: step.description
1132
- }));
1133
- }
1134
- /**
1135
- * Form.Stepper - Multi-step form container
1136
- *
1137
- * Uses Stepperize internally for step navigation and a single Conform form
1138
- * instance for all steps. Schemas are auto-merged for unified validation.
1139
- *
1140
- * @example
1141
- * ```tsx
1142
- * const steps = [
1143
- * { id: 'account', label: 'Account', schema: accountSchema },
1144
- * { id: 'profile', label: 'Profile', schema: profileSchema },
1145
- * ];
1146
- *
1147
- * <Form.Stepper steps={steps} onComplete={handleComplete}>
1148
- * <Form.StepperNavigation />
1149
- *
1150
- * <Form.Step id="account">
1151
- * <Form.Field name="email" label="Email" required>
1152
- * <Form.Input type="email" />
1153
- * </Form.Field>
1154
- * </Form.Step>
1155
- *
1156
- * <Form.Step id="profile">
1157
- * <Form.Field name="name" label="Full Name" required>
1158
- * <Form.Input />
1159
- * </Form.Field>
1160
- * </Form.Step>
1161
- *
1162
- * <Form.StepperControls />
1163
- * </Form.Stepper>
1164
- * ```
1165
- */
1166
- function FormStepper({ steps, children, onComplete, onStepChange, initialStep, className, defaultValues, id, formComponent }) {
1167
- const stepperDef = React$1.useMemo(() => {
1168
- return defineStepper(...toStepperizeSteps(steps));
1169
- }, [steps]);
1170
- const initialStepIndex = React$1.useMemo(() => {
1171
- if (!initialStep) return void 0;
1172
- const index = steps.findIndex((s) => s.id === initialStep);
1173
- return index >= 0 ? steps[index].id : void 0;
1174
- }, [initialStep, steps]);
1175
- const { Stepper } = stepperDef;
1176
- const providerProps = initialStepIndex ? { initialStep: initialStepIndex } : {};
1177
- return /* @__PURE__ */ jsx(Stepper.Provider, {
1178
- ...providerProps,
1179
- children: /* @__PURE__ */ jsx(FormStepperContent, {
1180
- steps,
1181
- stepperDef,
1182
- onComplete,
1183
- onStepChange,
1184
- className,
1185
- defaultValues,
1186
- id,
1187
- formComponent,
1188
- children
1189
- })
1190
- });
1191
- }
1192
- FormStepper.displayName = "Form.Stepper";
1193
- function FormStepperContent({ steps, stepperDef, children, onComplete, onStepChange, className, defaultValues, id, formComponent }) {
1194
- const { useStepper } = stepperDef;
1195
- const stepper = useStepper();
1196
- return /* @__PURE__ */ jsx(StepForm, {
1197
- steps,
1198
- stepper,
1199
- currentStepConfig: React$1.useMemo(() => steps.find((s) => s.id === stepper.state.current.data.id) ?? steps[0], [steps, stepper.state.current.data.id]),
1200
- combinedSchema: React$1.useMemo(() => mergeSchemas(steps), [steps]),
1201
- storedValues: React$1.useMemo(() => {
1202
- const allMetadata = steps.reduce((acc, step) => ({
1203
- ...acc,
1204
- ...stepper.metadata.get(step.id) || {}
1205
- }), {});
1206
- return {
1207
- ...defaultValues,
1208
- ...allMetadata
1209
- };
1210
- }, [
1211
- steps,
1212
- stepper,
1213
- defaultValues,
1214
- stepper.state.current.data.id
1215
- ]),
1216
- onComplete,
1217
- onStepChange,
1218
- className,
1219
- id,
1220
- formComponent,
1221
- children
1222
- }, stepper.state.current.data.id);
1223
- }
1224
- function StepForm({ steps, stepper, currentStepConfig, combinedSchema: _combinedSchema, storedValues, children, onComplete, onStepChange, className, id, formComponent: FormComp = "form" }) {
1225
- const adapter = useAdapter();
1226
- const [isSubmitting, setIsSubmitting] = React$1.useState(false);
1227
- const formRef = React$1.useRef(null);
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");
1251
- }
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
1266
- });
1267
- const next = React$1.useCallback(() => {
1268
- formRef.current?.requestSubmit();
1269
- }, []);
1270
- const prev = React$1.useCallback(() => {
1271
- const currentValues = instance.getValues();
1272
- if (Object.keys(currentValues).length > 0) stepper.metadata.set(stepper.state.current.data.id, currentValues);
1273
- const prevStepId = stepper.lookup.getPrev(stepper.state.current.data.id)?.id;
1274
- if (prevStepId) {
1275
- stepper.navigation.goTo(prevStepId);
1276
- onStepChange?.(prevStepId, "prev");
1277
- }
1278
- }, [
1279
- instance,
1280
- stepper,
1281
- onStepChange
1282
- ]);
1283
- const goTo = React$1.useCallback((stepId) => {
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);
1287
- stepper.navigation.goTo(stepId);
1288
- onStepChange?.(stepId, "prev");
1289
- }
1290
- }, [
1291
- instance,
1292
- stepper,
1293
- currentIndex,
1294
- onStepChange
1295
- ]);
1296
- const getStepData = React$1.useCallback((stepId) => stepper.metadata.get(stepId), [stepper]);
1297
- const getAllStepData = React$1.useCallback(() => {
1298
- return steps.reduce((acc, step) => ({
1299
- ...acc,
1300
- ...stepper.metadata.get(step.id) || {}
1301
- }), {});
1302
- }, [steps, stepper]);
1303
- const stepperContextValue = React$1.useMemo(() => ({
1304
- steps,
1305
- current: currentStepConfig,
1306
- currentIndex,
1307
- next,
1308
- prev,
1309
- goTo,
1310
- isFirst: stepper.state.isFirst,
1311
- isLast: stepper.state.isLast,
1312
- getStepData,
1313
- getAllStepData,
1314
- utils: { getIndex: (stepId) => stepper.lookup.getIndex(stepId) }
1315
- }), [
1316
- steps,
1317
- currentStepConfig,
1318
- currentIndex,
1319
- stepper,
1320
- next,
1321
- prev,
1322
- goTo,
1323
- getStepData,
1324
- getAllStepData
1325
- ]);
1326
- const contextValue = React$1.useMemo(() => ({
1327
- form: instance,
1328
- fields: instance.fields,
1329
- isSubmitting,
1330
- submit: () => formRef.current?.requestSubmit(),
1331
- reset: () => instance.reset(),
1332
- formId: instance.id
1333
- }), [instance, isSubmitting]);
1334
- const renderProps = {
1335
- steps,
1336
- current: currentStepConfig,
1337
- currentIndex,
1338
- next,
1339
- prev,
1340
- goTo,
1341
- isFirst: stepper.state.isFirst,
1342
- isLast: stepper.state.isLast,
1343
- getStepData,
1344
- getAllStepData
1345
- };
1346
- const resolvedChildren = typeof children === "function" ? children(renderProps) : children;
1347
- return /* @__PURE__ */ jsx(FormStepperContext, {
1348
- value: stepperContextValue,
1349
- children: /* @__PURE__ */ jsx(FormProvider, {
1350
- value: contextValue,
1351
- children: /* @__PURE__ */ jsx(adapter.FormProvider, {
1352
- instance,
1353
- children: /* @__PURE__ */ jsx(FormComp, {
1354
- ref: formRef,
1355
- ...instance.formProps,
1356
- className: cn("space-y-6", className),
1357
- autoComplete: "off",
1358
- noValidate: true,
1359
- onSubmit: (e) => {
1360
- e.stopPropagation();
1361
- const adapterSubmit = instance.formProps.onSubmit;
1362
- adapterSubmit?.(e);
1363
- },
1364
- children: resolvedChildren
1365
- })
1366
- })
1367
- })
1368
- });
1369
- }
1370
- //#endregion
1371
- //#region src/components/features/form/components/stepper/form-step.tsx
1372
- /**
1373
- * Form.Step - Individual step content container
1374
- *
1375
- * Only renders its children when the step is active.
1376
- * Works with the single-form architecture - fields remain registered
1377
- * even when unmounted, preserving their values.
1378
- *
1379
- * @example
1380
- * ```tsx
1381
- * <Form.Step id="account">
1382
- * <Form.Field name="email" label="Email" required>
1383
- * <Form.Input type="email" />
1384
- * </Form.Field>
1385
- * </Form.Step>
1386
- * ```
1387
- */
1388
- function FormStep({ id, children }) {
1389
- const { current } = useFormStepperContext();
1390
- if (current.id !== id) return null;
1391
- return /* @__PURE__ */ jsx(Fragment$1, { children });
1392
- }
1393
- FormStep.displayName = "Form.Step";
1394
- //#endregion
1395
- //#region src/components/features/form/components/stepper/stepper-controls.tsx
1396
- /**
1397
- * Form.StepperControls - Navigation buttons (Previous/Next/Submit)
1398
- *
1399
- * Provides Previous and Next/Submit buttons for navigating between steps.
1400
- * The Next button triggers form validation before advancing.
1401
- * The Previous button navigates back without validation.
1402
- *
1403
- * @example
1404
- * ```tsx
1405
- * <Form.StepperControls
1406
- * prevLabel={(isFirst) => isFirst ? 'Cancel' : 'Previous'}
1407
- * nextLabel={(isLast) => isLast ? 'Submit' : 'Next'}
1408
- * loadingText="Creating..."
1409
- * onCancel={() => setOpen(false)}
1410
- * />
1411
- * ```
1412
- *
1413
- * @example With external loading state
1414
- * ```tsx
1415
- * <Form.StepperControls
1416
- * loading={fetcher.state === 'submitting'}
1417
- * disabled={!isValid}
1418
- * loadingText="Saving..."
1419
- * />
1420
- * ```
1421
- */
1422
- function StepperControls({ prevLabel = "Previous", nextLabel = (isLast) => isLast ? "Submit" : "Next", loadingText = "Submitting...", showPrev = true, loading, disabled, onPrev, onCancel, className }) {
1423
- const { prev, isFirst, isLast } = useFormStepperContext();
1424
- const { isSubmitting: formIsSubmitting } = useFormContext$1();
1425
- const isLoading = loading ?? formIsSubmitting;
1426
- const isDisabled = disabled ?? false;
1427
- const getPrevLabel = () => {
1428
- if (typeof prevLabel === "function") return prevLabel(isFirst);
1429
- return prevLabel;
1430
- };
1431
- const getNextLabel = () => {
1432
- if (typeof nextLabel === "function") return nextLabel(isLast);
1433
- return nextLabel;
1434
- };
1435
- const handlePrev = () => {
1436
- if (isFirst && onCancel) onCancel();
1437
- else {
1438
- onPrev?.();
1439
- prev();
1440
- }
1441
- };
1442
- return /* @__PURE__ */ jsxs("div", {
1443
- className: cn("flex items-center justify-between gap-3", className),
1444
- children: [/* @__PURE__ */ jsx("div", { children: showPrev && /* @__PURE__ */ jsx(Button, {
1445
- htmlType: "button",
1446
- type: "quaternary",
1447
- theme: "outline",
1448
- size: "small",
1449
- onClick: handlePrev,
1450
- disabled: isLoading || isDisabled,
1451
- children: getPrevLabel()
1452
- }) }), /* @__PURE__ */ jsx(Button, {
1453
- htmlType: "submit",
1454
- type: "primary",
1455
- size: "small",
1456
- loading: isLoading,
1457
- disabled: isLoading || isDisabled,
1458
- children: isLoading && isLast ? loadingText : getNextLabel()
1459
- })]
1460
- });
1461
- }
1462
- StepperControls.displayName = "Form.StepperControls";
1463
- //#endregion
1464
- //#region src/components/features/form/components/stepper/stepper-navigation.tsx
1465
- /**
1466
- * Form.StepperNavigation - Step indicators/progress
1467
- *
1468
- * Displays visual step indicators showing current progress through the form.
1469
- * Supports horizontal and vertical variants with optional label orientation.
1470
- *
1471
- * @example
1472
- * ```tsx
1473
- * <Form.StepperNavigation variant="horizontal" labelOrientation="vertical" />
1474
- * ```
1475
- */
1476
- function StepperNavigation({ variant = "horizontal", labelOrientation = "vertical", className }) {
1477
- const { steps, currentIndex } = useFormStepperContext();
1478
- if (variant === "horizontal" && labelOrientation === "vertical") return /* @__PURE__ */ jsx("nav", {
1479
- "aria-label": "Form steps",
1480
- className: cn("flex flex-row items-start justify-between", className),
1481
- children: steps.map((step, index) => {
1482
- const isActive = index === currentIndex;
1483
- const isCompleted = index < currentIndex;
1484
- return /* @__PURE__ */ jsxs("div", {
1485
- className: "relative flex flex-1 flex-col items-center",
1486
- children: [
1487
- !(index === steps.length - 1) && /* @__PURE__ */ jsx("div", { className: "bg-stepper-line absolute top-4 right-[calc(-50%+20px)] left-[calc(50%+20px)] h-0.5" }),
1488
- /* @__PURE__ */ jsx("div", {
1489
- className: cn("relative z-10 flex h-8 w-8 items-center justify-center rounded-full border bg-transparent text-sm font-medium transition-colors", isActive && "border-primary bg-primary text-primary-foreground", isCompleted && "border-tertiary-foreground bg-tertiary-foreground text-tertiary", !isActive && !isCompleted && "border-stepper-label text-stepper-label"),
1490
- "aria-current": isActive ? "step" : void 0,
1491
- children: isCompleted ? /* @__PURE__ */ jsx(CheckIcon, { className: "text-tertiary h-4 w-4" }) : index + 1
1492
- }),
1493
- /* @__PURE__ */ jsxs("div", {
1494
- className: "mt-1",
1495
- children: [/* @__PURE__ */ jsx("span", {
1496
- className: cn("text-xs font-medium", isActive && "text-foreground", isCompleted && "text-stepper-label", !isActive && !isCompleted && "text-stepper-label"),
1497
- children: step.label
1498
- }), step.description && /* @__PURE__ */ jsx("p", {
1499
- className: "text-muted-foreground mt-0.5 text-xs",
1500
- children: step.description
1501
- })]
1502
- })
1503
- ]
1504
- }, step.id);
1505
- })
1506
- });
1507
- if (variant === "horizontal") return /* @__PURE__ */ jsx("nav", {
1508
- "aria-label": "Form steps",
1509
- className: cn("flex flex-row items-center", className),
1510
- children: steps.map((step, index) => {
1511
- const isActive = index === currentIndex;
1512
- const isCompleted = index < currentIndex;
1513
- const isLast = index === steps.length - 1;
1514
- return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [/* @__PURE__ */ jsxs("div", {
1515
- className: "flex items-center",
1516
- children: [/* @__PURE__ */ jsx("div", {
1517
- className: cn("flex h-8 w-8 items-center justify-center rounded-full border text-sm font-medium transition-colors", isActive && "border-primary bg-primary text-primary-foreground", isCompleted && "border-tertiary-foreground bg-tertiary-foreground text-tertiary", !isActive && !isCompleted && "border-stepper-label text-stepper-label"),
1518
- "aria-current": isActive ? "step" : void 0,
1519
- children: isCompleted ? /* @__PURE__ */ jsx(CheckIcon, { className: "text-tertiary size-4" }) : index + 1
1520
- }), /* @__PURE__ */ jsx("div", {
1521
- className: "ml-2",
1522
- children: /* @__PURE__ */ jsx("span", {
1523
- className: cn("text-sm font-medium", isActive && "text-foreground", isCompleted && "text-stepper-label", !isActive && !isCompleted && "text-stepper-label"),
1524
- children: step.label
1525
- })
1526
- })]
1527
- }), !isLast && /* @__PURE__ */ jsx("div", { className: "bg-stepper-line mx-4 h-0.5 min-w-8 flex-1" })] }, step.id);
1528
- })
1529
- });
1530
- return /* @__PURE__ */ jsx("nav", {
1531
- "aria-label": "Form steps",
1532
- className: cn("flex flex-col", className),
1533
- children: steps.map((step, index) => {
1534
- const isActive = index === currentIndex;
1535
- const isCompleted = index < currentIndex;
1536
- const isLast = index === steps.length - 1;
1537
- return /* @__PURE__ */ jsxs("div", {
1538
- className: "flex flex-row",
1539
- children: [/* @__PURE__ */ jsxs("div", {
1540
- className: "flex flex-col items-center",
1541
- children: [/* @__PURE__ */ jsx("div", {
1542
- className: cn("flex h-8 w-8 items-center justify-center rounded-full border text-sm font-medium transition-colors", isActive && "border-primary bg-primary text-primary-foreground", isCompleted && "border-tertiary-foreground bg-tertiary-foreground text-tertiary", !isActive && !isCompleted && "border-stepper-label text-stepper-label"),
1543
- "aria-current": isActive ? "step" : void 0,
1544
- children: isCompleted ? /* @__PURE__ */ jsx(CheckIcon, { className: "text-tertiary size-4" }) : index + 1
1545
- }), !isLast && /* @__PURE__ */ jsx("div", { className: "bg-stepper-line my-1 min-h-8 w-0.5 flex-1" })]
1546
- }), /* @__PURE__ */ jsxs("div", {
1547
- className: "ml-3 pb-8",
1548
- children: [/* @__PURE__ */ jsx("span", {
1549
- className: cn("text-sm font-medium", isActive && "text-foreground", isCompleted && "text-stepper-label", !isActive && !isCompleted && "text-stepper-label"),
1550
- children: step.label
1551
- }), step.description && /* @__PURE__ */ jsx("p", {
1552
- className: "text-muted-foreground mt-0.5 text-xs",
1553
- children: step.description
1554
- })]
1555
- })]
1556
- }, step.id);
1557
- })
1558
- });
1559
- }
1560
- StepperNavigation.displayName = "Form.StepperNavigation";
1561
- //#endregion
1562
- //#region src/components/features/form/components/form-input-group.tsx
1563
- /**
1564
- * Form.InputGroup - Input with leading/trailing addons
1565
- *
1566
- * Automatically wired to the parent Form.Field context.
1567
- *
1568
- * @example
1569
- * ```tsx
1570
- * <Form.Field name="website" label="Website" required>
1571
- * <Form.InputGroup leading="https://" placeholder="example.com" />
1572
- * </Form.Field>
1573
- * ```
1574
- */
1575
- function FormInputGroup({ ref, className, disabled, ...props }) {
1576
- const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
1577
- const isDisabled = disabled ?? fieldDisabled;
1578
- const hasErrors = errors && errors.length > 0;
1579
- return /* @__PURE__ */ jsx(InputWithAddons, {
1580
- ...props,
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),
1588
- disabled: isDisabled,
1589
- "aria-invalid": hasErrors || void 0,
1590
- "aria-describedby": hasErrors ? `${id}-error` : void 0
1591
- });
1592
- }
1593
- FormInputGroup.displayName = "Form.InputGroup";
1594
- //#endregion
1595
1346
  //#region src/components/features/form/hooks/use-field.ts
1596
1347
  /**
1597
1348
  * Hook to access and control a specific field.
@@ -1682,48 +1433,36 @@ function useFormContext() {
1682
1433
  return useFormContext$1();
1683
1434
  }
1684
1435
  //#endregion
1685
- //#region src/components/features/form/hooks/use-stepper.ts
1436
+ //#region src/components/features/form/hooks/use-form-state.ts
1686
1437
  /**
1687
- * Hook to access the stepper context
1688
- * Must be used within a Form.Stepper component
1438
+ * Hook to access form-level state (dirty, valid, submitted, etc.)
1689
1439
  *
1690
1440
  * @example
1691
1441
  * ```tsx
1692
- * function StepContent() {
1693
- * const {
1694
- * current,
1695
- * currentIndex,
1696
- * steps,
1697
- * next,
1698
- * prev,
1699
- * goTo,
1700
- * isFirst,
1701
- * isLast,
1702
- * } = useStepper();
1442
+ * function SaveButton() {
1443
+ * const { isDirty, isSubmitting, isValid } = useFormState()
1703
1444
  *
1704
1445
  * return (
1705
- * <div>
1706
- * <h2>Step {currentIndex + 1}: {current.label}</h2>
1707
- * <button onClick={prev} disabled={isFirst}>Previous</button>
1708
- * <button onClick={next} disabled={isLast}>Next</button>
1709
- * </div>
1710
- * );
1446
+ * <button
1447
+ * type="submit"
1448
+ * disabled={!isDirty || isSubmitting || !isValid}
1449
+ * >
1450
+ * Save Changes
1451
+ * </button>
1452
+ * )
1711
1453
  * }
1712
1454
  * ```
1713
1455
  */
1714
- function useStepper() {
1715
- const context = useFormStepperContext();
1456
+ function useFormState() {
1457
+ const ctx = useFormContext$1();
1716
1458
  return {
1717
- steps: context.steps,
1718
- current: context.current,
1719
- currentIndex: context.currentIndex,
1720
- next: context.next,
1721
- prev: context.prev,
1722
- goTo: context.goTo,
1723
- isFirst: context.isFirst,
1724
- isLast: context.isLast,
1725
- getStepData: context.getStepData,
1726
- getAllStepData: context.getAllStepData
1459
+ isDirty: ctx.isDirty,
1460
+ isValid: ctx.isValid,
1461
+ isSubmitting: ctx.isSubmitting,
1462
+ isSubmitted: ctx.isSubmitted,
1463
+ submitCount: ctx.submitCount,
1464
+ dirtyFields: ctx.dirtyFields,
1465
+ touchedFields: ctx.touchedFields
1727
1466
  };
1728
1467
  }
1729
1468
  //#endregion
@@ -1774,19 +1513,34 @@ function useStepper() {
1774
1513
  * }
1775
1514
  * ```
1776
1515
  *
1777
- * @example Multi-Step Form
1516
+ * @example Multi-Step Form (separate import)
1778
1517
  * ```tsx
1518
+ * import { FormStepper, FormStep, StepperNavigation, StepperControls } from '@datum-cloud/datum-ui/form/stepper';
1519
+ *
1779
1520
  * const steps = [
1780
1521
  * { id: 'account', label: 'Account', schema: accountSchema },
1781
1522
  * { id: 'profile', label: 'Profile', schema: profileSchema },
1782
1523
  * ];
1783
1524
  *
1784
- * <Form.Stepper steps={steps} onComplete={handleComplete}>
1785
- * <Form.StepperNavigation />
1786
- * <Form.Step id="account">...</Form.Step>
1787
- * <Form.Step id="profile">...</Form.Step>
1788
- * <Form.StepperControls />
1789
- * </Form.Stepper>
1525
+ * <FormStepper steps={steps} onComplete={handleComplete}>
1526
+ * <StepperNavigation />
1527
+ * <FormStep id="account">...</FormStep>
1528
+ * <FormStep id="profile">...</FormStep>
1529
+ * <StepperControls />
1530
+ * </FormStepper>
1531
+ * ```
1532
+ *
1533
+ * @example Form State
1534
+ * ```tsx
1535
+ * <Form.Root schema={schema} onSubmit={handleSubmit}>
1536
+ * {({ isDirty, isValid, isSubmitted, submitCount }) => (
1537
+ * <>
1538
+ * <Form.Field name="name"><Form.Input /></Form.Field>
1539
+ * <Form.Submit disabled={!isDirty || !isValid}>Save</Form.Submit>
1540
+ * {isSubmitted && <p>Submitted {submitCount} time(s)</p>}
1541
+ * </>
1542
+ * )}
1543
+ * </Form.Root>
1790
1544
  * ```
1791
1545
  *
1792
1546
  * @example Conditional Fields
@@ -1818,13 +1572,15 @@ function useStepper() {
1818
1572
  * - Form.When - Conditional rendering
1819
1573
  * - Form.FieldArray - Dynamic array of fields
1820
1574
  * - Form.Custom - Escape hatch for custom implementations
1821
- * - Form.Stepper, Form.Step, Form.StepperNavigation, Form.StepperControls
1822
1575
  * - Form.Dialog - Form rendered inside a Dialog
1823
1576
  * - Form.Submit, Form.Button, Form.Error, Form.Description
1824
1577
  *
1578
+ * Stepper (separate import):
1579
+ * - `@datum-cloud/datum-ui/form/stepper` provides FormStepper, FormStep, StepperNavigation, StepperControls, useStepper
1580
+ *
1825
1581
  * Hooks:
1826
- * - Form.useFormContext, Form.useFieldContext, Form.useField
1827
- * - Form.useWatch, Form.useWatchAll, Form.useStepper
1582
+ * - Form.useFormContext, Form.useFormState, Form.useFieldContext, Form.useField
1583
+ * - Form.useWatch, Form.useWatchAll
1828
1584
  */
1829
1585
  const Form = {
1830
1586
  Root: FormRoot,
@@ -1843,21 +1599,23 @@ const Form = {
1843
1599
  RadioItem: FormRadioItem,
1844
1600
  CopyBox: FormCopyBox,
1845
1601
  Autocomplete: FormAutocomplete,
1602
+ Autosearch: FormAutosearch,
1603
+ Combobox: FormCombobox,
1846
1604
  InputGroup: FormInputGroup,
1605
+ DatePicker: FormDatePicker,
1606
+ DateTimePicker: FormDateTimePicker,
1607
+ TimePicker: FormTimePicker,
1608
+ Transfer: FormTransfer,
1847
1609
  When: FormWhen,
1848
1610
  FieldArray: FormFieldArray,
1849
1611
  Custom: FormCustom,
1850
- Stepper: FormStepper,
1851
- Step: FormStep,
1852
- StepperNavigation,
1853
- StepperControls,
1854
1612
  Dialog: FormDialog,
1855
1613
  useFormContext,
1614
+ useFormState,
1856
1615
  useFieldContext,
1857
1616
  useField,
1858
1617
  useWatch,
1859
- useWatchAll,
1860
- useStepper
1618
+ useWatchAll
1861
1619
  };
1862
1620
  //#endregion
1863
- export { FormButton as A, FormField as C, FormCustom as D, FormDescription as E, FormCopyBox as O, FormFieldArray as S, FormDialog as T, FormSelectItem as _, useField as a, FormRadioItem as b, FormStep as c, useWatch as d, useWatchAll as f, FormSelect as g, FormSubmit as h, useFieldContext as i, FormAutocomplete as j, FormCheckbox as k, FormStepper as l, FormSwitch as m, useStepper as n, StepperNavigation as o, FormTextarea as p, useFormContext as r, StepperControls as s, Form as t, FormWhen as u, FormRoot as v, FormError as w, FormInput as x, FormRadioGroup as y };
1621
+ export { FormCheckbox as A, FormDialog as C, FormCustom as D, FormDatePicker as E, FormAutosearch as M, FormAutocomplete as N, FormCopyBox as O, FormError as S, FormDateTimePicker as T, FormRadioGroup as _, useField as a, FormFieldArray as b, useWatchAll as c, FormTextarea as d, FormSwitch as f, FormRoot as g, FormSelectItem as h, useFieldContext as i, FormButton as j, FormCombobox as k, FormTransfer as l, FormSelect as m, useFormState as n, FormWhen as o, FormSubmit as p, useFormContext as r, useWatch as s, Form as t, FormTimePicker as u, FormRadioItem as v, FormDescription as w, FormField as x, FormInput as y };