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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/README.md +4 -0
  2. package/dist/autocomplete/index.mjs +1 -1
  3. package/dist/{autocomplete-V5-qslzS.mjs → autocomplete-CkYJueBL.mjs} +2 -2
  4. package/dist/autosearch/index.mjs +199 -0
  5. package/dist/{calendar-date-picker-DWK94_DC.mjs → calendar-date-picker-CDT-8Ha8.mjs} +2 -1
  6. package/dist/combobox/index.mjs +2 -0
  7. package/dist/combobox-B-C9lJeD.mjs +97 -0
  8. package/dist/components/features/autocomplete/autocomplete.d.ts +1 -1
  9. package/dist/components/features/autocomplete/autocomplete.d.ts.map +1 -1
  10. package/dist/components/features/autocomplete/autocomplete.types.d.ts +2 -0
  11. package/dist/components/features/autocomplete/autocomplete.types.d.ts.map +1 -1
  12. package/dist/components/features/autosearch/autosearch.d.ts +35 -0
  13. package/dist/components/features/autosearch/autosearch.d.ts.map +1 -0
  14. package/dist/components/features/autosearch/autosearch.types.d.ts +51 -0
  15. package/dist/components/features/autosearch/autosearch.types.d.ts.map +1 -0
  16. package/dist/components/features/autosearch/index.d.ts +3 -0
  17. package/dist/components/features/autosearch/index.d.ts.map +1 -0
  18. package/dist/components/features/calendar-date-picker/calendar-date-picker.d.ts +2 -1
  19. package/dist/components/features/calendar-date-picker/calendar-date-picker.d.ts.map +1 -1
  20. package/dist/components/features/combobox/combobox.d.ts +27 -0
  21. package/dist/components/features/combobox/combobox.d.ts.map +1 -0
  22. package/dist/components/features/combobox/index.d.ts +3 -0
  23. package/dist/components/features/combobox/index.d.ts.map +1 -0
  24. package/dist/components/features/combobox/types.d.ts +84 -0
  25. package/dist/components/features/combobox/types.d.ts.map +1 -0
  26. package/dist/components/features/date-time-picker/date-time-picker.d.ts +9 -0
  27. package/dist/components/features/date-time-picker/date-time-picker.d.ts.map +1 -0
  28. package/dist/components/features/date-time-picker/index.d.ts +3 -0
  29. package/dist/components/features/date-time-picker/index.d.ts.map +1 -0
  30. package/dist/components/features/date-time-picker/types.d.ts +59 -0
  31. package/dist/components/features/date-time-picker/types.d.ts.map +1 -0
  32. package/dist/components/features/date-time-picker/utils/format.d.ts +13 -0
  33. package/dist/components/features/date-time-picker/utils/format.d.ts.map +1 -0
  34. package/dist/components/features/date-time-picker/utils/index.d.ts +3 -0
  35. package/dist/components/features/date-time-picker/utils/index.d.ts.map +1 -0
  36. package/dist/components/features/date-time-picker/utils/timezone.d.ts +23 -0
  37. package/dist/components/features/date-time-picker/utils/timezone.d.ts.map +1 -0
  38. package/dist/components/features/form/adapter-types.d.ts +26 -0
  39. package/dist/components/features/form/adapter-types.d.ts.map +1 -1
  40. package/dist/components/features/form/adapters/conform/conform-adapter.d.ts.map +1 -1
  41. package/dist/components/features/form/adapters/rhf/rhf-adapter.d.ts.map +1 -1
  42. package/dist/components/features/form/components/form-autocomplete.d.ts.map +1 -1
  43. package/dist/components/features/form/components/form-autosearch.d.ts +37 -0
  44. package/dist/components/features/form/components/form-autosearch.d.ts.map +1 -0
  45. package/dist/components/features/form/components/form-checkbox.d.ts.map +1 -1
  46. package/dist/components/features/form/components/form-combobox.d.ts +80 -0
  47. package/dist/components/features/form/components/form-combobox.d.ts.map +1 -0
  48. package/dist/components/features/form/components/form-copy-box.d.ts +3 -0
  49. package/dist/components/features/form/components/form-copy-box.d.ts.map +1 -1
  50. package/dist/components/features/form/components/form-custom.d.ts.map +1 -1
  51. package/dist/components/features/form/components/form-date-picker.d.ts +40 -0
  52. package/dist/components/features/form/components/form-date-picker.d.ts.map +1 -0
  53. package/dist/components/features/form/components/form-date-time-picker.d.ts +39 -0
  54. package/dist/components/features/form/components/form-date-time-picker.d.ts.map +1 -0
  55. package/dist/components/features/form/components/form-dialog.d.ts.map +1 -1
  56. package/dist/components/features/form/components/form-field.d.ts.map +1 -1
  57. package/dist/components/features/form/components/form-radio-group.d.ts.map +1 -1
  58. package/dist/components/features/form/components/form-root.d.ts.map +1 -1
  59. package/dist/components/features/form/components/form-switch.d.ts.map +1 -1
  60. package/dist/components/features/form/components/form-time-picker.d.ts +21 -0
  61. package/dist/components/features/form/components/form-time-picker.d.ts.map +1 -0
  62. package/dist/components/features/form/components/form-transfer.d.ts +37 -0
  63. package/dist/components/features/form/components/form-transfer.d.ts.map +1 -0
  64. package/dist/components/features/form/components/index.d.ts +7 -1
  65. package/dist/components/features/form/components/index.d.ts.map +1 -1
  66. package/dist/components/features/form/components/stepper/form-stepper.d.ts +3 -1
  67. package/dist/components/features/form/components/stepper/form-stepper.d.ts.map +1 -1
  68. package/dist/components/features/form/hooks/index.d.ts +1 -1
  69. package/dist/components/features/form/hooks/index.d.ts.map +1 -1
  70. package/dist/components/features/form/hooks/use-display-touched.d.ts +20 -0
  71. package/dist/components/features/form/hooks/use-display-touched.d.ts.map +1 -0
  72. package/dist/components/features/form/hooks/use-field.d.ts +4 -0
  73. package/dist/components/features/form/hooks/use-field.d.ts.map +1 -1
  74. package/dist/components/features/form/hooks/use-form-state.d.ts +36 -0
  75. package/dist/components/features/form/hooks/use-form-state.d.ts.map +1 -0
  76. package/dist/components/features/form/index.d.ts +39 -21
  77. package/dist/components/features/form/index.d.ts.map +1 -1
  78. package/dist/components/features/form/stepper/index.d.ts +17 -0
  79. package/dist/components/features/form/stepper/index.d.ts.map +1 -0
  80. package/dist/components/features/form/types/index.d.ts +46 -0
  81. package/dist/components/features/form/types/index.d.ts.map +1 -1
  82. package/dist/components/features/form/utils/get-field-constraints.d.ts +23 -1
  83. package/dist/components/features/form/utils/get-field-constraints.d.ts.map +1 -1
  84. package/dist/components/features/form/utils/get-schema-defaults.d.ts +24 -0
  85. package/dist/components/features/form/utils/get-schema-defaults.d.ts.map +1 -0
  86. package/dist/components/features/form/utils/zod-helpers.d.ts +12 -0
  87. package/dist/components/features/form/utils/zod-helpers.d.ts.map +1 -0
  88. package/dist/components/features/time-picker/index.d.ts +3 -0
  89. package/dist/components/features/time-picker/index.d.ts.map +1 -0
  90. package/dist/components/features/time-picker/time-picker.d.ts +22 -0
  91. package/dist/components/features/time-picker/time-picker.d.ts.map +1 -0
  92. package/dist/components/features/time-picker/types.d.ts +31 -0
  93. package/dist/components/features/time-picker/types.d.ts.map +1 -0
  94. package/dist/components/features/transfer/components/index.d.ts +9 -0
  95. package/dist/components/features/transfer/components/index.d.ts.map +1 -0
  96. package/dist/components/features/transfer/components/transfer-group.d.ts +7 -0
  97. package/dist/components/features/transfer/components/transfer-group.d.ts.map +1 -0
  98. package/dist/components/features/transfer/components/transfer-item.d.ts +10 -0
  99. package/dist/components/features/transfer/components/transfer-item.d.ts.map +1 -0
  100. package/dist/components/features/transfer/components/transfer-panel.d.ts +18 -0
  101. package/dist/components/features/transfer/components/transfer-panel.d.ts.map +1 -0
  102. package/dist/components/features/transfer/components/transfer-search.d.ts +9 -0
  103. package/dist/components/features/transfer/components/transfer-search.d.ts.map +1 -0
  104. package/dist/components/features/transfer/hooks/use-transfer-dnd.d.ts +26 -0
  105. package/dist/components/features/transfer/hooks/use-transfer-dnd.d.ts.map +1 -0
  106. package/dist/components/features/transfer/hooks/use-transfer-state.d.ts +20 -0
  107. package/dist/components/features/transfer/hooks/use-transfer-state.d.ts.map +1 -0
  108. package/dist/components/features/transfer/index.d.ts +3 -0
  109. package/dist/components/features/transfer/index.d.ts.map +1 -0
  110. package/dist/components/features/transfer/transfer.d.ts +6 -0
  111. package/dist/components/features/transfer/transfer.d.ts.map +1 -0
  112. package/dist/components/features/transfer/types.d.ts +69 -0
  113. package/dist/components/features/transfer/types.d.ts.map +1 -0
  114. package/dist/data-table/index.mjs +1 -1
  115. package/dist/date-picker/index.mjs +2 -2
  116. package/dist/date-time-picker/index.mjs +2 -0
  117. package/dist/date-time-picker-BomrW07W.mjs +178 -0
  118. package/dist/form/adapters/conform/index.mjs +110 -13
  119. package/dist/form/adapters/rhf/index.mjs +116 -27
  120. package/dist/form/index.mjs +3 -3
  121. package/dist/form/stepper/index.mjs +519 -0
  122. package/dist/{form-BE1xBne4.mjs → form-B3rQ4CH9.mjs} +447 -605
  123. package/dist/form-context-Ccxm-wqL.mjs +17 -0
  124. package/dist/grid/index.mjs +1 -1
  125. package/dist/hooks/index.mjs +2 -2
  126. package/dist/index.mjs +16 -16
  127. package/dist/input-number/index.mjs +1 -1
  128. package/dist/map/index.mjs +1 -1
  129. package/dist/{map-Cw7u8r6E.mjs → map-CWIQ-eql.mjs} +1 -1
  130. package/dist/more-actions/index.mjs +1 -1
  131. package/dist/page-title/index.mjs +1 -1
  132. package/dist/stepper/index.mjs +1 -320
  133. package/dist/stepper-DvIOp0hh.mjs +321 -0
  134. package/dist/tag-input/index.mjs +1 -1
  135. package/dist/task-queue/index.mjs +1 -1
  136. package/dist/time-picker/index.mjs +2 -0
  137. package/dist/time-picker-BoF7pZZ2.mjs +43 -0
  138. package/dist/transfer/index.mjs +2 -0
  139. package/dist/transfer-46C-rFFW.mjs +264 -0
  140. package/dist/{get-field-constraints-BPMW8VvY.mjs → use-display-touched-I39aXEBD.mjs} +51 -16
  141. package/package.json +42 -1
  142. /package/dist/{adapter-context-B7L2ucTr.mjs → adapter-context-rWveHhDd.mjs} +0 -0
  143. /package/dist/{col-YBbQ5wlb.mjs → col-1T0Q3SlH.mjs} +0 -0
  144. /package/dist/{hooks-DYjN7lvC.mjs → hooks-D8r2M2U6.mjs} +0 -0
  145. /package/dist/{input-number-DEjXG2I6.mjs → input-number-a7uydAsw.mjs} +0 -0
  146. /package/dist/{map-leaflet-imports-D6nTEOIh.mjs → map-leaflet-imports-CRSKA79m.mjs} +0 -0
  147. /package/dist/{more-actions-BNQ2yfWZ.mjs → more-actions-ILnEZq_E.mjs} +0 -0
  148. /package/dist/{page-title-CNiRNZ7p.mjs → page-title-ChsnpBiH.mjs} +0 -0
  149. /package/dist/{tag-input-BKed-cul.mjs → tag-input-T9cUX9-G.mjs} +0 -0
  150. /package/dist/{task-queue-dropdown-Di_Wjspz.mjs → task-queue-dropdown-Wcbj-f0V.mjs} +0 -0
  151. /package/dist/{to-api-format-Cq4prffn.mjs → to-api-format-Bh3c01gr.mjs} +0 -0
  152. /package/dist/{use-copy-to-clipboard-BGdTmkFV.mjs → use-copy-to-clipboard-uNeeVHC4.mjs} +0 -0
@@ -10,16 +10,21 @@ import { i as SelectItem, l as SelectTrigger, n as SelectContent, t as Select, u
10
10
  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
- import { t as Autocomplete } from "./autocomplete-V5-qslzS.mjs";
13
+ import { t as Autocomplete } from "./autocomplete-CkYJueBL.mjs";
14
+ import { t as CalendarDatePicker } from "./calendar-date-picker-CDT-8Ha8.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 { Autosearch } from "./autosearch/index.mjs";
17
+ import { n as useFormContext$1, t as FormProvider } from "./form-context-Ccxm-wqL.mjs";
18
+ import { t as Combobox } from "./combobox-B-C9lJeD.mjs";
19
+ import { t as useCopyToClipboard } from "./use-copy-to-clipboard-uNeeVHC4.mjs";
20
+ import { t as DateTimePicker } from "./date-time-picker-BomrW07W.mjs";
21
+ import { n as useAdapter } from "./adapter-context-rWveHhDd.mjs";
18
22
  import { InputWithAddons } from "./input-with-addons/index.mjs";
23
+ import { t as TimePicker } from "./time-picker-BoF7pZZ2.mjs";
24
+ import { t as Transfer } from "./transfer-46C-rFFW.mjs";
19
25
  import { CheckIcon, CircleHelp, CopyIcon } from "lucide-react";
20
26
  import * as React$1 from "react";
21
27
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
22
- import { z } from "zod";
23
28
  //#region src/components/features/form/context/field-context.tsx
24
29
  const FieldContext = React$1.createContext(null);
25
30
  function FieldProvider({ children, value }) {
@@ -90,7 +95,10 @@ function FormAutocomplete({ disabled, className, ...props }) {
90
95
  name: fieldState?.name,
91
96
  id,
92
97
  value,
93
- onValueChange: (val) => fieldState?.change(val),
98
+ onValueChange: (val) => {
99
+ fieldState?.change(val);
100
+ fieldState?.blur();
101
+ },
94
102
  disabled: isDisabled,
95
103
  triggerClassName: cn(hasErrors && "border-destructive", props.triggerClassName),
96
104
  className
@@ -98,19 +106,58 @@ function FormAutocomplete({ disabled, className, ...props }) {
98
106
  }
99
107
  FormAutocomplete.displayName = "Form.Autocomplete";
100
108
  //#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, {
109
+ //#region src/components/features/form/components/form-autosearch.tsx
110
+ /**
111
+ * Form.Autosearch - Search-first input with dropdown results
112
+ *
113
+ * Automatically wired to the parent Form.Field context.
114
+ * Shows a text input that triggers search and displays results in a popover.
115
+ * Different from Form.Autocomplete which shows all options upfront.
116
+ *
117
+ * @example Basic usage
118
+ * ```tsx
119
+ * <Form.Field name="userId" label="User" required>
120
+ * <Form.Autosearch
121
+ * options={users}
122
+ * onSearch={handleSearch}
123
+ * loading={isSearching}
124
+ * placeholder="Search users..."
125
+ * />
126
+ * </Form.Field>
127
+ * ```
128
+ *
129
+ * @example With debounce control
130
+ * ```tsx
131
+ * <Form.Field name="email" label="Email">
132
+ * <Form.Autosearch
133
+ * options={searchResults}
134
+ * onSearch={debouncedSearch}
135
+ * searchDebounceMs={500}
136
+ * placeholder="Type email to search..."
137
+ * />
138
+ * </Form.Field>
139
+ * ```
140
+ */
141
+ function FormAutosearch({ disabled, className, inputClassName, ...props }) {
142
+ const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
143
+ const isDisabled = disabled ?? fieldDisabled;
144
+ const hasErrors = errors && errors.length > 0;
145
+ const value = fieldState?.value != null ? String(fieldState.value) : "";
146
+ return /* @__PURE__ */ jsx(Autosearch, {
147
+ ...props,
148
+ name: fieldState?.name,
149
+ id,
105
150
  value,
106
- children
151
+ onValueChange: (val) => {
152
+ fieldState?.change(val);
153
+ fieldState?.blur();
154
+ },
155
+ disabled: isDisabled,
156
+ inputClassName: cn(hasErrors && "border-destructive", inputClassName),
157
+ className
107
158
  });
108
159
  }
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;
113
- }
160
+ FormAutosearch.displayName = "Form.Autosearch";
114
161
  //#endregion
115
162
  //#region src/components/features/form/components/form-button.tsx
116
163
  /**
@@ -168,7 +215,10 @@ function FormCheckbox({ label, disabled, className }) {
168
215
  children: [/* @__PURE__ */ jsx(Checkbox, {
169
216
  id,
170
217
  checked,
171
- onCheckedChange: (value) => fieldState?.change(Boolean(value)),
218
+ onCheckedChange: (value) => {
219
+ fieldState?.change(Boolean(value));
220
+ fieldState?.blur();
221
+ },
172
222
  disabled: isDisabled,
173
223
  "aria-invalid": hasErrors || void 0,
174
224
  "aria-describedby": hasErrors ? `${id}-error` : void 0
@@ -181,6 +231,36 @@ function FormCheckbox({ label, disabled, className }) {
181
231
  }
182
232
  FormCheckbox.displayName = "Form.Checkbox";
183
233
  //#endregion
234
+ //#region src/components/features/form/components/form-combobox.tsx
235
+ function FormCombobox({ options, placeholder, searchPlaceholder, emptyMessage, disabled, className, triggerClassName, contentClassName, searchable = true, showDropdownArrow = true, clearable = false, "data-testid": testId, modal }) {
236
+ const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
237
+ const isDisabled = disabled ?? fieldDisabled;
238
+ const hasErrors = errors && errors.length > 0;
239
+ const handleChange = React$1.useCallback((value) => {
240
+ fieldState?.change(value ?? "");
241
+ fieldState?.blur();
242
+ }, [fieldState]);
243
+ return /* @__PURE__ */ jsx(Combobox, {
244
+ id,
245
+ options,
246
+ value: fieldState?.value ?? "",
247
+ onChange: handleChange,
248
+ placeholder,
249
+ searchPlaceholder,
250
+ emptyMessage,
251
+ disabled: isDisabled,
252
+ searchable,
253
+ showDropdownArrow,
254
+ clearable,
255
+ modal,
256
+ className,
257
+ triggerClassName: cn(hasErrors && "border-destructive", triggerClassName),
258
+ contentClassName,
259
+ "data-testid": testId
260
+ });
261
+ }
262
+ FormCombobox.displayName = "Form.Combobox";
263
+ //#endregion
184
264
  //#region src/components/features/form/components/form-copy-box.tsx
185
265
  /**
186
266
  * Form.CopyBox - Read-only field with copy-to-clipboard functionality
@@ -246,6 +326,7 @@ function FormCopyBox({ variant = "default", className, contentClassName, buttonC
246
326
  })]
247
327
  });
248
328
  }
329
+ FormCopyBox.displayName = "Form.CopyBox";
249
330
  //#endregion
250
331
  //#region src/components/features/form/components/form-custom.tsx
251
332
  /**
@@ -270,17 +351,119 @@ function FormCopyBox({ variant = "default", className, contentClassName, buttonC
270
351
  * ```
271
352
  */
272
353
  function FormCustom({ children }) {
273
- const { form, fields, isSubmitting, submit, reset } = useFormContext$1();
354
+ const ctx = useFormContext$1();
274
355
  return /* @__PURE__ */ jsx(Fragment$1, { children: children({
275
- form,
276
- fields,
277
- isSubmitting,
278
- submit,
279
- reset
356
+ form: ctx.form,
357
+ fields: ctx.fields,
358
+ isSubmitting: ctx.isSubmitting,
359
+ isDirty: ctx.isDirty,
360
+ isValid: ctx.isValid,
361
+ isSubmitted: ctx.isSubmitted,
362
+ submitCount: ctx.submitCount,
363
+ dirtyFields: ctx.dirtyFields,
364
+ touchedFields: ctx.touchedFields,
365
+ submit: ctx.submit,
366
+ reset: ctx.reset
280
367
  }) });
281
368
  }
282
369
  FormCustom.displayName = "Form.Custom";
283
370
  //#endregion
371
+ //#region src/components/features/form/components/form-date-picker.tsx
372
+ function FormDatePicker({ placeholder, disabled, className, triggerClassName, numberOfMonths = 1, minDate: minDateProp, maxDate: maxDateProp, disableFuture, disablePast, modal }) {
373
+ const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
374
+ const isDisabled = disabled ?? fieldDisabled;
375
+ const hasErrors = errors && errors.length > 0;
376
+ const currentValue = React$1.useMemo(() => {
377
+ const val = fieldState?.value;
378
+ if (!val) return {
379
+ from: void 0,
380
+ to: void 0
381
+ };
382
+ if (val instanceof Date) return {
383
+ from: val,
384
+ to: val
385
+ };
386
+ if (typeof val === "string") {
387
+ const date = new Date(val);
388
+ return {
389
+ from: date,
390
+ to: date
391
+ };
392
+ }
393
+ if (typeof val === "object" && "from" in val) return val;
394
+ return {
395
+ from: void 0,
396
+ to: void 0
397
+ };
398
+ }, [fieldState?.value]);
399
+ const minDate = minDateProp;
400
+ const maxDate = maxDateProp;
401
+ return /* @__PURE__ */ jsx(CalendarDatePicker, {
402
+ id,
403
+ date: currentValue,
404
+ onDateSelect: React$1.useCallback((range) => {
405
+ if (!range) {
406
+ fieldState?.change(void 0);
407
+ fieldState?.blur();
408
+ return;
409
+ }
410
+ if (numberOfMonths === 1) fieldState?.change(range.from.toISOString());
411
+ else fieldState?.change({
412
+ from: range.from.toISOString(),
413
+ to: range.to?.toISOString()
414
+ });
415
+ fieldState?.blur();
416
+ }, [fieldState, numberOfMonths]),
417
+ numberOfMonths,
418
+ placeholder,
419
+ disabled: isDisabled,
420
+ minDate,
421
+ maxDate,
422
+ disableFuture,
423
+ disablePast,
424
+ variant: "outline",
425
+ modal,
426
+ className: cn(className),
427
+ triggerClassName: cn(triggerClassName),
428
+ "aria-invalid": hasErrors || void 0,
429
+ "aria-describedby": hasErrors ? `${id}-error` : void 0
430
+ });
431
+ }
432
+ FormDatePicker.displayName = "Form.DatePicker";
433
+ //#endregion
434
+ //#region src/components/features/form/components/form-date-time-picker.tsx
435
+ function FormDateTimePicker({ minDate: minDateProp, maxDate: maxDateProp, disabledDates, timezone, showTimezoneIndicator, placeholder, disabled, className, modal }) {
436
+ const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
437
+ const isDisabled = disabled ?? fieldDisabled;
438
+ const hasErrors = errors && errors.length > 0;
439
+ const currentValue = React$1.useMemo(() => {
440
+ const val = fieldState?.value;
441
+ if (!val) return void 0;
442
+ if (typeof val === "string") return val;
443
+ }, [fieldState?.value]);
444
+ const minDate = minDateProp;
445
+ const maxDate = maxDateProp;
446
+ return /* @__PURE__ */ jsx(DateTimePicker, {
447
+ value: currentValue,
448
+ onChange: React$1.useCallback((value) => {
449
+ fieldState?.change(value);
450
+ fieldState?.blur();
451
+ }, [fieldState]),
452
+ minDate,
453
+ maxDate,
454
+ disabledDates,
455
+ timezone,
456
+ showTimezoneIndicator,
457
+ placeholder,
458
+ disabled: isDisabled,
459
+ modal,
460
+ className: cn(className),
461
+ "aria-invalid": hasErrors || void 0,
462
+ "aria-describedby": hasErrors ? `${id}-error` : void 0
463
+ });
464
+ }
465
+ FormDateTimePicker.displayName = "Form.DateTimePicker";
466
+ //#endregion
284
467
  //#region src/components/features/form/components/form-description.tsx
285
468
  /**
286
469
  * Form.Description - Display field description/helper text
@@ -377,9 +560,6 @@ function FormDialog({ open, onOpenChange, defaultOpen, title, description, trigg
377
560
  try {
378
561
  await onSubmit?.(data);
379
562
  onSuccess?.(data);
380
- } catch (error) {
381
- console.error("Form submission error:", error);
382
- throw error;
383
563
  } finally {
384
564
  if (loading === void 0) setInternalIsSubmitting(false);
385
565
  }
@@ -526,10 +706,33 @@ function FieldLabel({ htmlFor, label, hasErrors, required, tooltip, className })
526
706
  */
527
707
  function FormField({ name, children, label, description, tooltip, required = false, disabled = false, className, labelClassName }) {
528
708
  const adapter = useAdapter();
529
- const { fields, isSubmitting, form } = useFormContext$1();
709
+ const { fields, isSubmitting, form, mode, displayTouchedFields, markFieldTouched } = useFormContext$1();
530
710
  const fieldState = adapter.useField(name);
531
- const errors = fieldState.errors;
711
+ const isDisplayTouched = displayTouchedFields.includes(name);
712
+ const rawErrors = fieldState.errors;
713
+ const errors = isDisplayTouched ? rawErrors : [];
532
714
  const hasErrors = errors.length > 0;
715
+ const hasFocusedRef = React$1.useRef(false);
716
+ React$1.useEffect(() => {
717
+ hasFocusedRef.current = false;
718
+ }, [name]);
719
+ const handleFocus = React$1.useCallback((e) => {
720
+ if (e.target.type !== "hidden") hasFocusedRef.current = true;
721
+ }, []);
722
+ const handleChange = React$1.useCallback(() => {
723
+ if (mode === "onChange" && hasFocusedRef.current) markFieldTouched(name);
724
+ }, [
725
+ mode,
726
+ name,
727
+ markFieldTouched
728
+ ]);
729
+ const handleBlur = React$1.useCallback(() => {
730
+ if ((mode === "onChange" || mode === "onBlur") && hasFocusedRef.current) markFieldTouched(name);
731
+ }, [
732
+ mode,
733
+ name,
734
+ markFieldTouched
735
+ ]);
533
736
  const fieldId = fieldState.id;
534
737
  const descriptionId = description ? `${fieldId}-description` : void 0;
535
738
  const errorId = hasErrors ? `${fieldId}-error` : void 0;
@@ -576,6 +779,9 @@ function FormField({ name, children, label, description, tooltip, required = fal
576
779
  value: contextValue,
577
780
  children: /* @__PURE__ */ jsxs("div", {
578
781
  className: cn("flex flex-col space-y-2", className),
782
+ onFocusCapture: handleFocus,
783
+ onChange: handleChange,
784
+ onBlur: handleBlur,
579
785
  children: [
580
786
  label && /* @__PURE__ */ jsx(FieldLabel, {
581
787
  htmlFor: fieldId,
@@ -675,6 +881,39 @@ function FormInput({ ref, type = "text", className, disabled, ...props }) {
675
881
  }
676
882
  FormInput.displayName = "Form.Input";
677
883
  //#endregion
884
+ //#region src/components/features/form/components/form-input-group.tsx
885
+ /**
886
+ * Form.InputGroup - Input with leading/trailing addons
887
+ *
888
+ * Automatically wired to the parent Form.Field context.
889
+ *
890
+ * @example
891
+ * ```tsx
892
+ * <Form.Field name="website" label="Website" required>
893
+ * <Form.InputGroup leading="https://" placeholder="example.com" />
894
+ * </Form.Field>
895
+ * ```
896
+ */
897
+ function FormInputGroup({ ref, className, disabled, ...props }) {
898
+ const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
899
+ const isDisabled = disabled ?? fieldDisabled;
900
+ const hasErrors = errors && errors.length > 0;
901
+ return /* @__PURE__ */ jsx(InputWithAddons, {
902
+ ...props,
903
+ ref,
904
+ id,
905
+ name: fieldState?.name,
906
+ value: fieldState?.value ?? "",
907
+ onChange: (e) => fieldState?.change(e.target.value),
908
+ onBlur: () => fieldState?.blur(),
909
+ className: cn("text-xs!", className),
910
+ disabled: isDisabled,
911
+ "aria-invalid": hasErrors || void 0,
912
+ "aria-describedby": hasErrors ? `${id}-error` : void 0
913
+ });
914
+ }
915
+ FormInputGroup.displayName = "Form.InputGroup";
916
+ //#endregion
678
917
  //#region src/components/features/form/components/form-radio-group.tsx
679
918
  /**
680
919
  * Form.RadioGroup - Radio button group component
@@ -698,7 +937,10 @@ function FormRadioGroup({ orientation = "vertical", disabled, className, childre
698
937
  const hasErrors = errors && errors.length > 0;
699
938
  return /* @__PURE__ */ jsx(RadioGroup, {
700
939
  value: fieldState?.value != null ? String(fieldState.value) : void 0,
701
- onValueChange: (val) => fieldState?.change(val),
940
+ onValueChange: (val) => {
941
+ fieldState?.change(val);
942
+ fieldState?.blur();
943
+ },
702
944
  disabled: isDisabled,
703
945
  "aria-invalid": hasErrors || void 0,
704
946
  "aria-describedby": hasErrors ? `${id}-error` : void 0,
@@ -716,7 +958,8 @@ FormRadioGroup.displayName = "Form.RadioGroup";
716
958
  * ```
717
959
  */
718
960
  function FormRadioItem({ value, label, description, disabled }) {
719
- const radioId = `radio-${value}`;
961
+ const { id: fieldId } = useFieldContext$1();
962
+ const radioId = `${fieldId}-radio-${value}`;
720
963
  return /* @__PURE__ */ jsxs("div", {
721
964
  className: "flex items-start space-x-2",
722
965
  children: [/* @__PURE__ */ jsx(RadioGroupItem, {
@@ -762,13 +1005,17 @@ FormRadioItem.displayName = "Form.RadioItem";
762
1005
  * </Form.Root>
763
1006
  * ```
764
1007
  */
765
- function FormRoot({ schema, children, onSubmit, action, method = "POST", formComponent: FormComp = "form", id, name, defaultValues, mode = "onBlur", isSubmitting: externalIsSubmitting, onError, onSuccess, telemetry, className }) {
1008
+ function FormRoot({ schema, children, onSubmit, action, method = "POST", formComponent: FormComp = "form", id, name, defaultValues, mode = "onChange", isSubmitting: externalIsSubmitting, onError, onSuccess, telemetry, className }) {
766
1009
  const adapter = useAdapter();
767
1010
  const [internalIsSubmitting, setInternalIsSubmitting] = React$1.useState(false);
768
1011
  const isSubmitting = externalIsSubmitting ?? internalIsSubmitting;
1012
+ const [isSubmitted, setIsSubmitted] = React$1.useState(false);
1013
+ const [submitCount, setSubmitCount] = React$1.useState(0);
769
1014
  const formRef = React$1.useRef(null);
770
1015
  const wrappedOnSubmit = React$1.useCallback(async (data) => {
771
1016
  setInternalIsSubmitting(true);
1017
+ setIsSubmitted(true);
1018
+ setSubmitCount((prev) => prev + 1);
772
1019
  try {
773
1020
  await onSubmit?.(data);
774
1021
  telemetry?.onSuccess?.({
@@ -807,25 +1054,49 @@ function FormRoot({ schema, children, onSubmit, action, method = "POST", formCom
807
1054
  onSubmit: onSubmit ? wrappedOnSubmit : void 0,
808
1055
  formRef
809
1056
  });
1057
+ const { formState } = instance;
810
1058
  const contextValue = React$1.useMemo(() => ({
811
1059
  form: instance,
812
1060
  fields: instance.fields,
813
1061
  isSubmitting,
1062
+ isDirty: formState.isDirty,
1063
+ isValid: formState.isValid,
1064
+ isSubmitted,
1065
+ submitCount,
1066
+ dirtyFields: formState.dirtyFields,
1067
+ touchedFields: formState.touchedFields,
1068
+ mode,
1069
+ displayTouchedFields: instance.touchedFields,
1070
+ markFieldTouched: instance.markFieldTouched,
1071
+ markAllFieldsTouched: instance.markAllFieldsTouched,
814
1072
  submit: () => formRef.current?.requestSubmit(),
815
1073
  reset: () => instance.reset(),
816
1074
  formId: instance.id
817
- }), [instance, isSubmitting]);
818
- const isRenderFunction = typeof children === "function";
819
- const renderProps = {
820
- form: instance,
821
- fields: instance.fields,
1075
+ }), [
1076
+ instance,
822
1077
  isSubmitting,
823
- submit: () => formRef.current?.requestSubmit(),
824
- reset: () => instance.reset()
825
- };
1078
+ formState,
1079
+ isSubmitted,
1080
+ submitCount,
1081
+ mode
1082
+ ]);
1083
+ const isRenderFunction = typeof children === "function";
826
1084
  const renderChildren = () => {
827
- if (isRenderFunction) return children(renderProps);
828
- return children;
1085
+ if (!isRenderFunction) return children;
1086
+ return children({
1087
+ form: instance,
1088
+ fields: instance.fields,
1089
+ isSubmitting,
1090
+ isDirty: formState.isDirty,
1091
+ isValid: formState.isValid,
1092
+ isSubmitted,
1093
+ submitCount,
1094
+ dirtyFields: formState.dirtyFields,
1095
+ touchedFields: formState.touchedFields,
1096
+ mode,
1097
+ submit: () => formRef.current?.requestSubmit(),
1098
+ reset: () => instance.reset()
1099
+ });
829
1100
  };
830
1101
  return /* @__PURE__ */ jsx(FormProvider, {
831
1102
  value: contextValue,
@@ -840,7 +1111,9 @@ function FormRoot({ schema, children, onSubmit, action, method = "POST", formCom
840
1111
  autoComplete: "off",
841
1112
  noValidate: true,
842
1113
  onSubmit: (e) => {
1114
+ const submitter = e.nativeEvent.submitter;
843
1115
  e.stopPropagation();
1116
+ if (submitter && !submitter.hidden) instance.markAllFieldsTouched();
844
1117
  telemetry?.onSubmit?.({
845
1118
  formName: name ?? "",
846
1119
  formId: id
@@ -955,7 +1228,10 @@ function FormSwitch({ label, disabled, className }) {
955
1228
  children: [/* @__PURE__ */ jsx(Switch, {
956
1229
  id,
957
1230
  checked,
958
- onCheckedChange: (value) => fieldState?.change(Boolean(value)),
1231
+ onCheckedChange: (value) => {
1232
+ fieldState?.change(Boolean(value));
1233
+ fieldState?.blur();
1234
+ },
959
1235
  disabled: isDisabled,
960
1236
  "aria-invalid": hasErrors || void 0,
961
1237
  "aria-describedby": hasErrors ? `${id}-error` : void 0
@@ -1002,6 +1278,75 @@ function FormTextarea({ ref, className, disabled, rows = 3, ...props }) {
1002
1278
  }
1003
1279
  FormTextarea.displayName = "Form.Textarea";
1004
1280
  //#endregion
1281
+ //#region src/components/features/form/components/form-time-picker.tsx
1282
+ function FormTimePicker({ min, max, step, placeholder, disabled, className }) {
1283
+ const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
1284
+ const isDisabled = disabled ?? fieldDisabled;
1285
+ const hasErrors = errors && errors.length > 0;
1286
+ return /* @__PURE__ */ jsx(TimePicker, {
1287
+ id,
1288
+ value: fieldState?.value ?? "",
1289
+ onChange: React$1.useCallback((value) => {
1290
+ fieldState?.change(value || void 0);
1291
+ fieldState?.blur();
1292
+ }, [fieldState]),
1293
+ min,
1294
+ max,
1295
+ step,
1296
+ placeholder,
1297
+ disabled: isDisabled,
1298
+ className: cn(className),
1299
+ "aria-invalid": hasErrors || void 0,
1300
+ "aria-describedby": hasErrors ? `${id}-error` : void 0
1301
+ });
1302
+ }
1303
+ FormTimePicker.displayName = "Form.TimePicker";
1304
+ //#endregion
1305
+ //#region src/components/features/form/components/form-transfer.tsx
1306
+ /**
1307
+ * Form.Transfer - Transfer list component for selecting multiple items
1308
+ *
1309
+ * Automatically wired to the parent Form.Field context.
1310
+ * Displays minItems/maxItems constraints when provided.
1311
+ *
1312
+ * @example
1313
+ * ```tsx
1314
+ * <Form.Field name="teams" label="Select Teams">
1315
+ * <Form.Transfer
1316
+ * items={teams}
1317
+ * itemKey="id"
1318
+ * itemLabel="name"
1319
+ * minItems={2}
1320
+ * maxItems={5}
1321
+ * />
1322
+ * </Form.Field>
1323
+ * ```
1324
+ */
1325
+ function FormTransfer({ disabled, minItems, maxItems, ...props }) {
1326
+ const { id, errors, disabled: fieldDisabled, fieldState } = useFieldContext$1();
1327
+ const isDisabled = disabled ?? fieldDisabled;
1328
+ const hasErrors = errors && errors.length > 0;
1329
+ const value = Array.isArray(fieldState?.value) ? fieldState.value : [];
1330
+ const handleChange = React$1.useCallback((newValue) => {
1331
+ fieldState?.change(newValue);
1332
+ fieldState?.blur();
1333
+ }, [fieldState]);
1334
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
1335
+ "aria-invalid": hasErrors || void 0,
1336
+ "aria-describedby": hasErrors ? `${id}-error` : void 0,
1337
+ children: /* @__PURE__ */ jsx(Transfer, {
1338
+ ...props,
1339
+ value,
1340
+ onChange: handleChange,
1341
+ disabled: isDisabled
1342
+ })
1343
+ }), (minItems != null || maxItems != null) && /* @__PURE__ */ jsx("p", {
1344
+ className: "text-ring text-xs text-wrap mt-2",
1345
+ children: minItems != null && maxItems != null ? `Select between ${minItems} and ${maxItems} items` : minItems != null ? `Select at least ${minItems} items` : `Select up to ${maxItems} items`
1346
+ })] });
1347
+ }
1348
+ FormTransfer.displayName = "Form.Transfer";
1349
+ //#endregion
1005
1350
  //#region src/components/features/form/hooks/use-watch.ts
1006
1351
  /**
1007
1352
  * Hook to watch a field's value reactively.
@@ -1078,525 +1423,15 @@ function FormWhen({ field, is, isNot, in: inArray, notIn, children }) {
1078
1423
  }
1079
1424
  FormWhen.displayName = "Form.When";
1080
1425
  //#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
1426
  //#region src/components/features/form/hooks/use-field.ts
1596
1427
  /**
1597
1428
  * Hook to access and control a specific field.
1598
1429
  * Delegates to the active adapter's useField implementation.
1599
1430
  *
1431
+ * Note: `meta.disabled` is always `false` because this hook operates
1432
+ * independently of Form.Field. If you need disabled state, read it
1433
+ * from your component props or from the Form.Field context via `useFieldContext()`.
1434
+ *
1600
1435
  * @example
1601
1436
  * ```tsx
1602
1437
  * function MyCustomInput({ name }: { name: string }) {
@@ -1682,48 +1517,36 @@ function useFormContext() {
1682
1517
  return useFormContext$1();
1683
1518
  }
1684
1519
  //#endregion
1685
- //#region src/components/features/form/hooks/use-stepper.ts
1520
+ //#region src/components/features/form/hooks/use-form-state.ts
1686
1521
  /**
1687
- * Hook to access the stepper context
1688
- * Must be used within a Form.Stepper component
1522
+ * Hook to access form-level state (dirty, valid, submitted, etc.)
1689
1523
  *
1690
1524
  * @example
1691
1525
  * ```tsx
1692
- * function StepContent() {
1693
- * const {
1694
- * current,
1695
- * currentIndex,
1696
- * steps,
1697
- * next,
1698
- * prev,
1699
- * goTo,
1700
- * isFirst,
1701
- * isLast,
1702
- * } = useStepper();
1526
+ * function SaveButton() {
1527
+ * const { isDirty, isSubmitting, isValid } = useFormState()
1703
1528
  *
1704
1529
  * 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
- * );
1530
+ * <button
1531
+ * type="submit"
1532
+ * disabled={!isDirty || isSubmitting || !isValid}
1533
+ * >
1534
+ * Save Changes
1535
+ * </button>
1536
+ * )
1711
1537
  * }
1712
1538
  * ```
1713
1539
  */
1714
- function useStepper() {
1715
- const context = useFormStepperContext();
1540
+ function useFormState() {
1541
+ const ctx = useFormContext$1();
1716
1542
  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
1543
+ isDirty: ctx.isDirty,
1544
+ isValid: ctx.isValid,
1545
+ isSubmitting: ctx.isSubmitting,
1546
+ isSubmitted: ctx.isSubmitted,
1547
+ submitCount: ctx.submitCount,
1548
+ dirtyFields: ctx.dirtyFields,
1549
+ touchedFields: ctx.touchedFields
1727
1550
  };
1728
1551
  }
1729
1552
  //#endregion
@@ -1774,19 +1597,34 @@ function useStepper() {
1774
1597
  * }
1775
1598
  * ```
1776
1599
  *
1777
- * @example Multi-Step Form
1600
+ * @example Multi-Step Form (separate import)
1778
1601
  * ```tsx
1602
+ * import { FormStepper, FormStep, StepperNavigation, StepperControls } from '@datum-cloud/datum-ui/form/stepper';
1603
+ *
1779
1604
  * const steps = [
1780
1605
  * { id: 'account', label: 'Account', schema: accountSchema },
1781
1606
  * { id: 'profile', label: 'Profile', schema: profileSchema },
1782
1607
  * ];
1783
1608
  *
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>
1609
+ * <FormStepper steps={steps} onComplete={handleComplete}>
1610
+ * <StepperNavigation />
1611
+ * <FormStep id="account">...</FormStep>
1612
+ * <FormStep id="profile">...</FormStep>
1613
+ * <StepperControls />
1614
+ * </FormStepper>
1615
+ * ```
1616
+ *
1617
+ * @example Form State
1618
+ * ```tsx
1619
+ * <Form.Root schema={schema} onSubmit={handleSubmit}>
1620
+ * {({ isDirty, isValid, isSubmitted, submitCount }) => (
1621
+ * <>
1622
+ * <Form.Field name="name"><Form.Input /></Form.Field>
1623
+ * <Form.Submit disabled={!isDirty || !isValid}>Save</Form.Submit>
1624
+ * {isSubmitted && <p>Submitted {submitCount} time(s)</p>}
1625
+ * </>
1626
+ * )}
1627
+ * </Form.Root>
1790
1628
  * ```
1791
1629
  *
1792
1630
  * @example Conditional Fields
@@ -1818,13 +1656,15 @@ function useStepper() {
1818
1656
  * - Form.When - Conditional rendering
1819
1657
  * - Form.FieldArray - Dynamic array of fields
1820
1658
  * - Form.Custom - Escape hatch for custom implementations
1821
- * - Form.Stepper, Form.Step, Form.StepperNavigation, Form.StepperControls
1822
1659
  * - Form.Dialog - Form rendered inside a Dialog
1823
1660
  * - Form.Submit, Form.Button, Form.Error, Form.Description
1824
1661
  *
1662
+ * Stepper (separate import):
1663
+ * - `@datum-cloud/datum-ui/form/stepper` provides FormStepper, FormStep, StepperNavigation, StepperControls, useStepper
1664
+ *
1825
1665
  * Hooks:
1826
- * - Form.useFormContext, Form.useFieldContext, Form.useField
1827
- * - Form.useWatch, Form.useWatchAll, Form.useStepper
1666
+ * - Form.useFormContext, Form.useFormState, Form.useFieldContext, Form.useField
1667
+ * - Form.useWatch, Form.useWatchAll
1828
1668
  */
1829
1669
  const Form = {
1830
1670
  Root: FormRoot,
@@ -1843,21 +1683,23 @@ const Form = {
1843
1683
  RadioItem: FormRadioItem,
1844
1684
  CopyBox: FormCopyBox,
1845
1685
  Autocomplete: FormAutocomplete,
1686
+ Autosearch: FormAutosearch,
1687
+ Combobox: FormCombobox,
1846
1688
  InputGroup: FormInputGroup,
1689
+ DatePicker: FormDatePicker,
1690
+ DateTimePicker: FormDateTimePicker,
1691
+ TimePicker: FormTimePicker,
1692
+ Transfer: FormTransfer,
1847
1693
  When: FormWhen,
1848
1694
  FieldArray: FormFieldArray,
1849
1695
  Custom: FormCustom,
1850
- Stepper: FormStepper,
1851
- Step: FormStep,
1852
- StepperNavigation,
1853
- StepperControls,
1854
1696
  Dialog: FormDialog,
1855
1697
  useFormContext,
1698
+ useFormState,
1856
1699
  useFieldContext,
1857
1700
  useField,
1858
1701
  useWatch,
1859
- useWatchAll,
1860
- useStepper
1702
+ useWatchAll
1861
1703
  };
1862
1704
  //#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 };
1705
+ 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 };