@datum-cloud/datum-ui 0.5.0 → 0.6.0-alpha.a9a8815

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 (154) hide show
  1. package/README.md +78 -40
  2. package/dist/adapter-context-rWveHhDd.mjs +25 -0
  3. package/dist/combobox/index.mjs +2 -0
  4. package/dist/combobox-cKTFK4uN.mjs +96 -0
  5. package/dist/components/features/combobox/combobox.d.ts +27 -0
  6. package/dist/components/features/combobox/combobox.d.ts.map +1 -0
  7. package/dist/components/features/combobox/index.d.ts +3 -0
  8. package/dist/components/features/combobox/index.d.ts.map +1 -0
  9. package/dist/components/features/combobox/types.d.ts +78 -0
  10. package/dist/components/features/combobox/types.d.ts.map +1 -0
  11. package/dist/components/features/date-time-picker/date-time-picker.d.ts +9 -0
  12. package/dist/components/features/date-time-picker/date-time-picker.d.ts.map +1 -0
  13. package/dist/components/features/date-time-picker/index.d.ts +3 -0
  14. package/dist/components/features/date-time-picker/index.d.ts.map +1 -0
  15. package/dist/components/features/date-time-picker/types.d.ts +53 -0
  16. package/dist/components/features/date-time-picker/types.d.ts.map +1 -0
  17. package/dist/components/features/date-time-picker/utils/format.d.ts +13 -0
  18. package/dist/components/features/date-time-picker/utils/format.d.ts.map +1 -0
  19. package/dist/components/features/date-time-picker/utils/index.d.ts +3 -0
  20. package/dist/components/features/date-time-picker/utils/index.d.ts.map +1 -0
  21. package/dist/components/features/date-time-picker/utils/timezone.d.ts +23 -0
  22. package/dist/components/features/date-time-picker/utils/timezone.d.ts.map +1 -0
  23. package/dist/components/features/form/adapter-context.d.ts +17 -0
  24. package/dist/components/features/form/adapter-context.d.ts.map +1 -0
  25. package/dist/components/features/form/adapter-types.d.ts +120 -0
  26. package/dist/components/features/form/adapter-types.d.ts.map +1 -0
  27. package/dist/components/features/form/adapters/conform/conform-adapter.d.ts +9 -0
  28. package/dist/components/features/form/adapters/conform/conform-adapter.d.ts.map +1 -0
  29. package/dist/components/features/form/adapters/conform/conform-provider.d.ts +22 -0
  30. package/dist/components/features/form/adapters/conform/conform-provider.d.ts.map +1 -0
  31. package/dist/components/features/form/adapters/conform/index.d.ts +3 -0
  32. package/dist/components/features/form/adapters/conform/index.d.ts.map +1 -0
  33. package/dist/components/features/form/adapters/rhf/index.d.ts +3 -0
  34. package/dist/components/features/form/adapters/rhf/index.d.ts.map +1 -0
  35. package/dist/components/features/form/adapters/rhf/rhf-adapter.d.ts +10 -0
  36. package/dist/components/features/form/adapters/rhf/rhf-adapter.d.ts.map +1 -0
  37. package/dist/components/features/form/adapters/rhf/rhf-provider.d.ts +22 -0
  38. package/dist/components/features/form/adapters/rhf/rhf-provider.d.ts.map +1 -0
  39. package/dist/components/features/form/components/form-autocomplete.d.ts.map +1 -1
  40. package/dist/components/features/form/components/form-autosearch.d.ts +25 -0
  41. package/dist/components/features/form/components/form-autosearch.d.ts.map +1 -0
  42. package/dist/components/features/form/components/form-checkbox.d.ts.map +1 -1
  43. package/dist/components/features/form/components/form-combobox.d.ts +76 -0
  44. package/dist/components/features/form/components/form-combobox.d.ts.map +1 -0
  45. package/dist/components/features/form/components/form-copy-box.d.ts.map +1 -1
  46. package/dist/components/features/form/components/form-custom.d.ts.map +1 -1
  47. package/dist/components/features/form/components/form-date-picker.d.ts +38 -0
  48. package/dist/components/features/form/components/form-date-picker.d.ts.map +1 -0
  49. package/dist/components/features/form/components/form-date-time-picker.d.ts +37 -0
  50. package/dist/components/features/form/components/form-date-time-picker.d.ts.map +1 -0
  51. package/dist/components/features/form/components/form-field-array.d.ts +5 -17
  52. package/dist/components/features/form/components/form-field-array.d.ts.map +1 -1
  53. package/dist/components/features/form/components/form-field.d.ts +7 -21
  54. package/dist/components/features/form/components/form-field.d.ts.map +1 -1
  55. package/dist/components/features/form/components/form-input-group.d.ts +4 -4
  56. package/dist/components/features/form/components/form-input-group.d.ts.map +1 -1
  57. package/dist/components/features/form/components/form-input.d.ts.map +1 -1
  58. package/dist/components/features/form/components/form-radio-group.d.ts.map +1 -1
  59. package/dist/components/features/form/components/form-root.d.ts +5 -25
  60. package/dist/components/features/form/components/form-root.d.ts.map +1 -1
  61. package/dist/components/features/form/components/form-select.d.ts.map +1 -1
  62. package/dist/components/features/form/components/form-switch.d.ts.map +1 -1
  63. package/dist/components/features/form/components/form-textarea.d.ts.map +1 -1
  64. package/dist/components/features/form/components/form-time-picker.d.ts +21 -0
  65. package/dist/components/features/form/components/form-time-picker.d.ts.map +1 -0
  66. package/dist/components/features/form/components/form-transfer.d.ts +37 -0
  67. package/dist/components/features/form/components/form-transfer.d.ts.map +1 -0
  68. package/dist/components/features/form/components/index.d.ts +6 -1
  69. package/dist/components/features/form/components/index.d.ts.map +1 -1
  70. package/dist/components/features/form/components/stepper/form-stepper.d.ts.map +1 -1
  71. package/dist/components/features/form/context/form-context.d.ts +2 -2
  72. package/dist/components/features/form/context/form-context.d.ts.map +1 -1
  73. package/dist/components/features/form/hooks/index.d.ts +1 -1
  74. package/dist/components/features/form/hooks/index.d.ts.map +1 -1
  75. package/dist/components/features/form/hooks/use-field.d.ts +12 -18
  76. package/dist/components/features/form/hooks/use-field.d.ts.map +1 -1
  77. package/dist/components/features/form/hooks/use-form-state.d.ts +36 -0
  78. package/dist/components/features/form/hooks/use-form-state.d.ts.map +1 -0
  79. package/dist/components/features/form/hooks/use-watch.d.ts +9 -20
  80. package/dist/components/features/form/hooks/use-watch.d.ts.map +1 -1
  81. package/dist/components/features/form/index.d.ts +69 -44
  82. package/dist/components/features/form/index.d.ts.map +1 -1
  83. package/dist/components/features/form/stepper/index.d.ts +17 -0
  84. package/dist/components/features/form/stepper/index.d.ts.map +1 -0
  85. package/dist/components/features/form/types/index.d.ts +68 -32
  86. package/dist/components/features/form/types/index.d.ts.map +1 -1
  87. package/dist/components/features/form/utils/get-field-constraints.d.ts +43 -0
  88. package/dist/components/features/form/utils/get-field-constraints.d.ts.map +1 -0
  89. package/dist/components/features/form/utils/get-schema-defaults.d.ts +24 -0
  90. package/dist/components/features/form/utils/get-schema-defaults.d.ts.map +1 -0
  91. package/dist/components/features/time-picker/index.d.ts +3 -0
  92. package/dist/components/features/time-picker/index.d.ts.map +1 -0
  93. package/dist/components/features/time-picker/time-picker.d.ts +22 -0
  94. package/dist/components/features/time-picker/time-picker.d.ts.map +1 -0
  95. package/dist/components/features/time-picker/types.d.ts +31 -0
  96. package/dist/components/features/time-picker/types.d.ts.map +1 -0
  97. package/dist/components/features/transfer/components/index.d.ts +9 -0
  98. package/dist/components/features/transfer/components/index.d.ts.map +1 -0
  99. package/dist/components/features/transfer/components/transfer-group.d.ts +7 -0
  100. package/dist/components/features/transfer/components/transfer-group.d.ts.map +1 -0
  101. package/dist/components/features/transfer/components/transfer-item.d.ts +10 -0
  102. package/dist/components/features/transfer/components/transfer-item.d.ts.map +1 -0
  103. package/dist/components/features/transfer/components/transfer-panel.d.ts +18 -0
  104. package/dist/components/features/transfer/components/transfer-panel.d.ts.map +1 -0
  105. package/dist/components/features/transfer/components/transfer-search.d.ts +9 -0
  106. package/dist/components/features/transfer/components/transfer-search.d.ts.map +1 -0
  107. package/dist/components/features/transfer/hooks/use-transfer-dnd.d.ts +26 -0
  108. package/dist/components/features/transfer/hooks/use-transfer-dnd.d.ts.map +1 -0
  109. package/dist/components/features/transfer/hooks/use-transfer-state.d.ts +20 -0
  110. package/dist/components/features/transfer/hooks/use-transfer-state.d.ts.map +1 -0
  111. package/dist/components/features/transfer/index.d.ts +3 -0
  112. package/dist/components/features/transfer/index.d.ts.map +1 -0
  113. package/dist/components/features/transfer/transfer.d.ts +6 -0
  114. package/dist/components/features/transfer/transfer.d.ts.map +1 -0
  115. package/dist/components/features/transfer/types.d.ts +69 -0
  116. package/dist/components/features/transfer/types.d.ts.map +1 -0
  117. package/dist/date-picker/index.mjs +1 -1
  118. package/dist/date-time-picker/index.mjs +2 -0
  119. package/dist/date-time-picker-Bx_n4nEJ.mjs +177 -0
  120. package/dist/form/adapters/conform/index.mjs +326 -0
  121. package/dist/form/adapters/rhf/index.mjs +275 -0
  122. package/dist/form/index.mjs +3 -2
  123. package/dist/form/stepper/index.mjs +542 -0
  124. package/dist/form-context-Ccxm-wqL.mjs +17 -0
  125. package/dist/form-zf5QOnAk.mjs +1611 -0
  126. package/dist/get-field-constraints-CxfZ753o.mjs +49 -0
  127. package/dist/grid/index.mjs +1 -1
  128. package/dist/hooks/index.mjs +2 -2
  129. package/dist/index.mjs +14 -13
  130. package/dist/input-number/index.mjs +1 -1
  131. package/dist/map/index.mjs +1 -1
  132. package/dist/{map-ClxB41Hg.mjs → map-CWIQ-eql.mjs} +1 -1
  133. package/dist/more-actions/index.mjs +1 -1
  134. package/dist/page-title/index.mjs +1 -1
  135. package/dist/stepper/index.mjs +1 -320
  136. package/dist/stepper-DvIOp0hh.mjs +321 -0
  137. package/dist/tag-input/index.mjs +1 -1
  138. package/dist/task-queue/index.mjs +1 -1
  139. package/dist/time-picker/index.mjs +2 -0
  140. package/dist/time-picker-BoF7pZZ2.mjs +43 -0
  141. package/dist/transfer/index.mjs +2 -0
  142. package/dist/transfer-C55XfEXy.mjs +243 -0
  143. package/package.json +58 -2
  144. package/dist/form-Co3fM4B7.mjs +0 -2114
  145. /package/dist/{col-q-J99UHe.mjs → col-1T0Q3SlH.mjs} +0 -0
  146. /package/dist/{hooks-Cb7YlxN4.mjs → hooks-D8r2M2U6.mjs} +0 -0
  147. /package/dist/{input-number-mDB-5M5C.mjs → input-number-a7uydAsw.mjs} +0 -0
  148. /package/dist/{map-leaflet-imports-CaMm_rdF.mjs → map-leaflet-imports-CRSKA79m.mjs} +0 -0
  149. /package/dist/{more-actions-CGagbIDT.mjs → more-actions-ILnEZq_E.mjs} +0 -0
  150. /package/dist/{page-title-R7QbfbWp.mjs → page-title-ChsnpBiH.mjs} +0 -0
  151. /package/dist/{tag-input-BVSwNcRd.mjs → tag-input-T9cUX9-G.mjs} +0 -0
  152. /package/dist/{task-queue-dropdown-DyM5R8KF.mjs → task-queue-dropdown-Wcbj-f0V.mjs} +0 -0
  153. /package/dist/{to-api-format-BnbRFYQI.mjs → to-api-format-Bh3c01gr.mjs} +0 -0
  154. /package/dist/{use-copy-to-clipboard-BGdTmkFV.mjs → use-copy-to-clipboard-uNeeVHC4.mjs} +0 -0
@@ -1,3 +1,3 @@
1
1
  import { t as CalendarDatePicker } from "../calendar-date-picker-DWK94_DC.mjs";
2
- import { C as utcToLocalInputString, S as utcStringToZonedDate, _ as getBrowserTimezone, a as formatTimeRangeDisplay, b as getTimezoneOffset, c as getPresetByKey, d as TimezoneSelector, f as QuickRangesPanel, g as formatUtcForDisplay, h as formatTimezoneLabel, i as formatSingleTimeDisplay, l as getPresetByShortcut, m as createTimezoneOption, n as TimeRangePicker, o as DEFAULT_PRESETS, p as CustomRangePanel, r as formatDateForInput, s as getDefaultPreset, t as toApiTimeRange, u as getPresetRange, v as getDefaultTimezoneOptions, w as zonedDateToUtcString, x as localInputStringToUtc, y as getShortTimezoneDisplay } from "../to-api-format-BnbRFYQI.mjs";
2
+ import { C as utcToLocalInputString, S as utcStringToZonedDate, _ as getBrowserTimezone, a as formatTimeRangeDisplay, b as getTimezoneOffset, c as getPresetByKey, d as TimezoneSelector, f as QuickRangesPanel, g as formatUtcForDisplay, h as formatTimezoneLabel, i as formatSingleTimeDisplay, l as getPresetByShortcut, m as createTimezoneOption, n as TimeRangePicker, o as DEFAULT_PRESETS, p as CustomRangePanel, r as formatDateForInput, s as getDefaultPreset, t as toApiTimeRange, u as getPresetRange, v as getDefaultTimezoneOptions, w as zonedDateToUtcString, x as localInputStringToUtc, y as getShortTimezoneDisplay } from "../to-api-format-Bh3c01gr.mjs";
3
3
  export { CustomRangePanel as AbsoluteRangePanel, CustomRangePanel, CalendarDatePicker, DEFAULT_PRESETS, QuickRangesPanel, TimeRangePicker, TimezoneSelector, createTimezoneOption, formatDateForInput, formatSingleTimeDisplay, formatTimeRangeDisplay, formatTimezoneLabel, formatUtcForDisplay, getBrowserTimezone, getDefaultPreset, getDefaultTimezoneOptions, getPresetByKey, getPresetByShortcut, getPresetRange, getShortTimezoneDisplay, getTimezoneOffset, localInputStringToUtc, toApiTimeRange, utcStringToZonedDate, utcToLocalInputString, zonedDateToUtcString };
@@ -0,0 +1,2 @@
1
+ import { t as DateTimePicker } from "../date-time-picker-Bx_n4nEJ.mjs";
2
+ export { DateTimePicker };
@@ -0,0 +1,177 @@
1
+ import { t as cn } from "./cn-D2KYQ917.mjs";
2
+ import { t as Button } from "./button-D3RrsMfQ.mjs";
3
+ import { t as Calendar$1 } from "./calendar-DEkCw7I1.mjs";
4
+ import { i as PopoverTrigger, r as PopoverContent, t as Popover } from "./popover-FJAcbYoH.mjs";
5
+ import { CalendarIcon } from "lucide-react";
6
+ import * as React$1 from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ import { format } from "date-fns";
9
+ import { formatInTimeZone, fromZonedTime, toZonedTime } from "date-fns-tz";
10
+ //#region src/components/features/date-time-picker/utils/format.ts
11
+ /**
12
+ * Format date for display
13
+ */
14
+ function formatDate(date) {
15
+ if (!date) return "";
16
+ return format(date, "PP");
17
+ }
18
+ //#endregion
19
+ //#region src/components/features/date-time-picker/utils/timezone.ts
20
+ /**
21
+ * Get the browser's timezone
22
+ */
23
+ function getBrowserTimezone() {
24
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
25
+ }
26
+ /**
27
+ * Convert local date + time to UTC ISO string
28
+ * @param date - Date object (date part)
29
+ * @param time - Time string in HH:mm format
30
+ * @param timezone - IANA timezone (e.g., "America/New_York")
31
+ * @returns UTC ISO string
32
+ */
33
+ function localDateTimeToUtc(date, time, timezone) {
34
+ try {
35
+ const [hours, minutes] = time.split(":").map(Number);
36
+ return fromZonedTime(`${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")} ${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`, timezone).toISOString();
37
+ } catch {
38
+ console.warn(`Invalid timezone "${timezone}", falling back to UTC`);
39
+ const [hours = 0, minutes = 0] = time.split(":").map(Number);
40
+ const combined = new Date(date);
41
+ combined.setUTCHours(hours, minutes, 0, 0);
42
+ return combined.toISOString();
43
+ }
44
+ }
45
+ /**
46
+ * Convert UTC ISO string to local date + time
47
+ * @param utcString - UTC ISO string
48
+ * @param timezone - IANA timezone
49
+ * @returns Object with date and time (HH:mm)
50
+ */
51
+ function utcToLocalDateTime(utcString, timezone) {
52
+ try {
53
+ const utcDate = new Date(utcString);
54
+ const time = formatInTimeZone(utcDate, timezone, "HH:mm");
55
+ return {
56
+ date: toZonedTime(utcDate, timezone),
57
+ time
58
+ };
59
+ } catch {
60
+ console.warn(`Invalid timezone "${timezone}", falling back to UTC`);
61
+ const utcDate = new Date(utcString);
62
+ const hours = utcDate.getUTCHours();
63
+ const minutes = utcDate.getUTCMinutes();
64
+ return {
65
+ date: utcDate,
66
+ time: `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`
67
+ };
68
+ }
69
+ }
70
+ //#endregion
71
+ //#region src/components/features/date-time-picker/date-time-picker.tsx
72
+ const DEFAULT_TIMEZONE = getBrowserTimezone();
73
+ function DateTimePicker({ ref, value, onChange, minDate, maxDate, disabledDates, timezone = DEFAULT_TIMEZONE, showTimezoneIndicator = false, placeholder = "Select date and time", disabled = false, className }) {
74
+ const initialState = React$1.useMemo(() => {
75
+ if (!value) return {
76
+ date: void 0,
77
+ time: ""
78
+ };
79
+ return utcToLocalDateTime(value, timezone);
80
+ }, [value, timezone]);
81
+ const [state, setState] = React$1.useState(initialState);
82
+ const prevValue = React$1.useRef(value);
83
+ const prevTimezone = React$1.useRef(timezone);
84
+ React$1.useEffect(() => {
85
+ if (value !== prevValue.current || timezone !== prevTimezone.current) {
86
+ prevValue.current = value;
87
+ prevTimezone.current = timezone;
88
+ setState(() => value ? utcToLocalDateTime(value, timezone) : {
89
+ date: void 0,
90
+ time: ""
91
+ });
92
+ }
93
+ }, [value, timezone]);
94
+ const handleDateChange = React$1.useCallback((newDate) => {
95
+ setState((prev) => ({
96
+ ...prev,
97
+ date: newDate
98
+ }));
99
+ }, []);
100
+ const handleTimeChange = React$1.useCallback((e) => {
101
+ const newTime = e.target.value;
102
+ if (newTime && !/^\d{2}:\d{2}$/.test(newTime)) return;
103
+ setState((prev) => ({
104
+ ...prev,
105
+ time: newTime
106
+ }));
107
+ }, []);
108
+ React$1.useEffect(() => {
109
+ if (state.date && state.time) {
110
+ const utcValue = localDateTimeToUtc(state.date, state.time, timezone);
111
+ onChange?.(utcValue);
112
+ }
113
+ }, [
114
+ state.date,
115
+ state.time,
116
+ timezone,
117
+ onChange
118
+ ]);
119
+ const formatDisplayValue = () => {
120
+ if (!state.date) return placeholder;
121
+ if (state.time) return `${formatDate(state.date)} ${state.time}`;
122
+ return formatDate(state.date);
123
+ };
124
+ return /* @__PURE__ */ jsx("div", {
125
+ ref,
126
+ className,
127
+ children: /* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsx(PopoverTrigger, {
128
+ asChild: true,
129
+ children: /* @__PURE__ */ jsxs(Button, {
130
+ variant: "outline",
131
+ className: cn("w-full justify-start text-left font-normal", !state.date && "text-muted-foreground"),
132
+ disabled,
133
+ children: [
134
+ /* @__PURE__ */ jsx(CalendarIcon, { className: "mr-2 h-4 w-4" }),
135
+ /* @__PURE__ */ jsx("span", {
136
+ className: "flex-1",
137
+ children: formatDisplayValue()
138
+ }),
139
+ showTimezoneIndicator && state.date && state.time && /* @__PURE__ */ jsx("span", {
140
+ className: "text-muted-foreground ml-2 text-xs",
141
+ children: timezone
142
+ })
143
+ ]
144
+ })
145
+ }), /* @__PURE__ */ jsx(PopoverContent, {
146
+ className: "w-auto p-0",
147
+ align: "start",
148
+ children: /* @__PURE__ */ jsxs("div", {
149
+ className: "p-3",
150
+ children: [/* @__PURE__ */ jsx(Calendar$1, {
151
+ mode: "single",
152
+ selected: state.date,
153
+ onSelect: handleDateChange,
154
+ disabled: disabledDates,
155
+ fromDate: minDate,
156
+ toDate: maxDate
157
+ }), /* @__PURE__ */ jsxs("div", {
158
+ className: "mt-3",
159
+ children: [/* @__PURE__ */ jsx("label", {
160
+ className: "mb-1 block text-sm font-medium",
161
+ children: "Time"
162
+ }), /* @__PURE__ */ jsx("input", {
163
+ type: "time",
164
+ "aria-label": "Select time",
165
+ value: state.time,
166
+ onChange: handleTimeChange,
167
+ disabled: disabled || !state.date,
168
+ className: cn("flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1", "text-sm shadow-sm transition-colors", "placeholder:text-muted-foreground", "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", "disabled:cursor-not-allowed disabled:opacity-50")
169
+ })]
170
+ })]
171
+ })
172
+ })] })
173
+ });
174
+ }
175
+ DateTimePicker.displayName = "DateTimePicker";
176
+ //#endregion
177
+ export { DateTimePicker as t };
@@ -0,0 +1,326 @@
1
+ import { t as FormAdapterProvider } from "../../../adapter-context-rWveHhDd.mjs";
2
+ import { t as getFieldConstraints } from "../../../get-field-constraints-CxfZ753o.mjs";
3
+ import * as React$1 from "react";
4
+ import { jsx } from "react/jsx-runtime";
5
+ import { FormProvider, getFormProps, getInputProps, useForm, useFormMetadata, useInputControl } from "@conform-to/react";
6
+ import { isDirty, useFormData } from "@conform-to/react/future";
7
+ import { getZodConstraint, parseWithZod } from "@conform-to/zod/v4";
8
+ //#region src/components/features/form/adapters/conform/conform-adapter.tsx
9
+ /**
10
+ * Shared context for the touched fields set.
11
+ * Created in useConformCreateForm and consumed by useConformField.
12
+ */
13
+ const TouchedFieldsContext = React$1.createContext({ current: /* @__PURE__ */ new Set() });
14
+ /**
15
+ * Resolve a Conform field metadata object by dot-notation path.
16
+ *
17
+ * Handles:
18
+ * - Top-level fields: `"email"` -> `fields.email`
19
+ * - Nested objects: `"address.city"` -> `fields.address.getFieldset().city`
20
+ * - Array items: `"items.0.name"` -> `fields.items.getFieldList()[0].getFieldset().name`
21
+ */
22
+ function resolveConformField(fields, name) {
23
+ const parts = name.split(".");
24
+ let current = fields;
25
+ for (let i = 0; i < parts.length; i++) {
26
+ const part = parts[i];
27
+ if (!current) break;
28
+ if (/^\d+$/.test(part)) {
29
+ const fieldList = current.getFieldList?.();
30
+ if (fieldList) {
31
+ const item = fieldList[Number.parseInt(part, 10)];
32
+ if (i < parts.length - 1 && item?.getFieldset) current = item.getFieldset();
33
+ else current = item;
34
+ } else current = current[part];
35
+ } else if (current[part] !== void 0) current = current[part];
36
+ else if (typeof current.getFieldset === "function") current = current.getFieldset()[part];
37
+ else current = void 0;
38
+ }
39
+ return current;
40
+ }
41
+ function convertFromString(value) {
42
+ if (value === void 0) return void 0;
43
+ if (Array.isArray(value)) return value[0];
44
+ if (value === "on") return true;
45
+ if (value.startsWith("[") && value.endsWith("]")) try {
46
+ return JSON.parse(value);
47
+ } catch {
48
+ return value;
49
+ }
50
+ return value;
51
+ }
52
+ function convertToString(value) {
53
+ if (typeof value === "boolean") return value ? "on" : "";
54
+ if (value === null || value === void 0) return "";
55
+ if (Array.isArray(value)) return JSON.stringify(value);
56
+ return String(value);
57
+ }
58
+ /** Create a Conform form instance and normalize it to the adapter interface. */
59
+ function useConformCreateForm(props) {
60
+ const { schema, defaultValues, mode, id, onSubmit, formRef } = props;
61
+ const [form, fields] = useForm({
62
+ id,
63
+ defaultValue: defaultValues,
64
+ constraint: getZodConstraint(schema),
65
+ shouldValidate: {
66
+ onBlur: "onBlur",
67
+ onChange: "onInput",
68
+ onSubmit: "onSubmit"
69
+ }[mode],
70
+ shouldRevalidate: mode !== "onSubmit" ? "onInput" : void 0,
71
+ onValidate: ({ formData }) => parseWithZod(formData, { schema }),
72
+ onSubmit: onSubmit ? (event, context) => {
73
+ event.preventDefault();
74
+ const submission = context.submission;
75
+ if (submission?.status === "success") onSubmit(submission.value);
76
+ } : void 0
77
+ });
78
+ const constraints = React$1.useMemo(() => getFieldConstraints(schema), [schema]);
79
+ const formIsDirty = useFormData(formRef, (formData) => isDirty(formData, { defaultValue: defaultValues }) ?? false) ?? false;
80
+ const dirtyFields = useFormData(formRef, (formData) => {
81
+ const result = {};
82
+ const defaults = defaultValues ?? {};
83
+ for (const key of Object.keys(defaults)) {
84
+ const current = formData.get(key);
85
+ const defaultVal = defaults[key];
86
+ result[key] = current !== (defaultVal === true ? "on" : defaultVal === false || defaultVal === null || defaultVal === void 0 ? "" : String(defaultVal));
87
+ }
88
+ for (const key of formData.keys()) if (!(key in result) && !key.startsWith("$")) {
89
+ const current = formData.get(key);
90
+ result[key] = current !== "" && current !== null;
91
+ }
92
+ const dirty = {};
93
+ for (const [key, value] of Object.entries(result)) if (value) dirty[key] = true;
94
+ return dirty;
95
+ }) ?? {};
96
+ const isValid = useFormData(formRef, (formData) => {
97
+ return parseWithZod(formData, { schema })?.status === "success";
98
+ }) ?? false;
99
+ const touchedFieldsRef = React$1.useRef(/* @__PURE__ */ new Set());
100
+ const [touchedFields, setTouchedFields] = React$1.useState({});
101
+ React$1.useEffect(() => {
102
+ const formEl = formRef?.current;
103
+ if (!formEl) return;
104
+ const handleFocusOut = (event) => {
105
+ const target = event.target;
106
+ if (target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement) {
107
+ const name = target.name;
108
+ if (name && !touchedFieldsRef.current.has(name)) {
109
+ touchedFieldsRef.current.add(name);
110
+ setTouchedFields((prev) => ({
111
+ ...prev,
112
+ [name]: true
113
+ }));
114
+ }
115
+ }
116
+ };
117
+ formEl.addEventListener("focusout", handleFocusOut);
118
+ return () => formEl.removeEventListener("focusout", handleFocusOut);
119
+ }, [formRef]);
120
+ const formState = React$1.useMemo(() => ({
121
+ isDirty: formIsDirty,
122
+ isValid,
123
+ isSubmitted: false,
124
+ submitCount: 0,
125
+ dirtyFields,
126
+ touchedFields
127
+ }), [
128
+ formIsDirty,
129
+ isValid,
130
+ dirtyFields,
131
+ touchedFields
132
+ ]);
133
+ const normalizedFields = React$1.useMemo(() => {
134
+ const result = {};
135
+ for (const [key, fieldMeta] of Object.entries(fields)) result[key] = {
136
+ id: fieldMeta.id ?? `${id ?? form.id}-${key}`,
137
+ errors: fieldMeta.errors ?? [],
138
+ required: constraints[key]?.required ?? false,
139
+ isDirty: dirtyFields[key] ?? false,
140
+ isTouched: touchedFields[key] ?? false
141
+ };
142
+ return result;
143
+ }, [
144
+ fields,
145
+ id,
146
+ form.id,
147
+ constraints,
148
+ dirtyFields,
149
+ touchedFields
150
+ ]);
151
+ const conformFormProps = React$1.useMemo(() => getFormProps(form), [form]);
152
+ return React$1.useMemo(() => ({
153
+ id: form.id,
154
+ fields: normalizedFields,
155
+ formProps: conformFormProps,
156
+ formState,
157
+ submit: () => formRef?.current?.requestSubmit(),
158
+ reset: () => form.reset(),
159
+ getValues: () => {
160
+ if (!formRef?.current) return {};
161
+ const formData = new FormData(formRef.current);
162
+ const values = {};
163
+ for (const [key, value] of formData.entries()) values[key] = value;
164
+ return values;
165
+ },
166
+ raw: {
167
+ form,
168
+ fields,
169
+ touchedFieldsRef
170
+ }
171
+ }), [
172
+ form,
173
+ fields,
174
+ normalizedFields,
175
+ conformFormProps,
176
+ formState,
177
+ formRef
178
+ ]);
179
+ }
180
+ /** Resolve a field by dot-notation path and return its normalized state. */
181
+ function useConformField(name) {
182
+ const fieldMeta = resolveConformField(useFormMetadata().getFieldset(), name);
183
+ const touchedFieldsRef = React$1.use(TouchedFieldsContext);
184
+ const control = useInputControl(fieldMeta ?? {
185
+ name,
186
+ key: void 0,
187
+ id: name
188
+ });
189
+ if (!fieldMeta) throw new Error(`[Conform Adapter] Field "${name}" not found. Make sure the field name matches your schema.`);
190
+ const currentValue = convertFromString(control.value);
191
+ const defaultValue = convertFromString(fieldMeta.initialValue);
192
+ const fieldIsDirty = currentValue !== (defaultValue === void 0 ? "" : defaultValue);
193
+ const fieldIsTouched = touchedFieldsRef.current.has(name);
194
+ return React$1.useMemo(() => ({
195
+ name: fieldMeta.name,
196
+ id: fieldMeta.id,
197
+ errors: fieldMeta.errors ?? [],
198
+ required: fieldMeta.required ?? false,
199
+ isDirty: fieldIsDirty,
200
+ isTouched: fieldIsTouched,
201
+ value: currentValue,
202
+ change: (value) => control.change(convertToString(value)),
203
+ blur: () => control.blur(),
204
+ focus: () => control.focus(),
205
+ inputProps: getInputProps(fieldMeta, { type: "text" })
206
+ }), [
207
+ fieldMeta,
208
+ control,
209
+ currentValue,
210
+ fieldIsDirty,
211
+ fieldIsTouched
212
+ ]);
213
+ }
214
+ /** Watch a single field's value reactively. */
215
+ function useConformWatch(name) {
216
+ const fieldMeta = resolveConformField(useFormMetadata().getFieldset(), name);
217
+ if (!fieldMeta) return void 0;
218
+ return convertFromString(fieldMeta.value);
219
+ }
220
+ /** Watch multiple fields' values reactively. */
221
+ function useConformWatchAll(names) {
222
+ const allFields = useFormMetadata().getFieldset();
223
+ return React$1.useMemo(() => {
224
+ const result = {};
225
+ for (const name of names) {
226
+ const fieldMeta = resolveConformField(allFields, name);
227
+ if (fieldMeta) result[name] = convertFromString(fieldMeta.value);
228
+ }
229
+ return result;
230
+ }, [allFields, names]);
231
+ }
232
+ /** Get field array helpers for a given array field name. */
233
+ function useConformFieldArray(name) {
234
+ const formMeta = useFormMetadata();
235
+ const arrayField = resolveConformField(formMeta.getFieldset(), name);
236
+ const items = React$1.useMemo(() => {
237
+ if (!arrayField?.getFieldList) return [];
238
+ return arrayField.getFieldList().map((item, index) => ({
239
+ id: item.id ?? `${name}-${index}`,
240
+ key: item.key ?? `${name}-${index}`,
241
+ name: `${name}[${index}]`
242
+ }));
243
+ }, [arrayField, name]);
244
+ const arrayFieldName = arrayField?.name;
245
+ return {
246
+ items,
247
+ append: React$1.useCallback((defaultValue) => {
248
+ if (!arrayFieldName) return;
249
+ formMeta.insert({
250
+ name: arrayFieldName,
251
+ defaultValue
252
+ });
253
+ }, [formMeta, arrayFieldName]),
254
+ remove: React$1.useCallback((index) => {
255
+ if (!arrayFieldName) return;
256
+ formMeta.remove({
257
+ name: arrayFieldName,
258
+ index
259
+ });
260
+ }, [formMeta, arrayFieldName]),
261
+ move: React$1.useCallback((from, to) => {
262
+ if (!arrayFieldName) return;
263
+ formMeta.reorder({
264
+ name: arrayFieldName,
265
+ from,
266
+ to
267
+ });
268
+ }, [formMeta, arrayFieldName])
269
+ };
270
+ }
271
+ /**
272
+ * Wraps children in Conform's FormProvider so that useFormMetadata() and
273
+ * useInputControl() work inside field components.
274
+ * Also provides the touched fields ref via context for useConformField.
275
+ */
276
+ function ConformFormProviderWrapper({ instance, children }) {
277
+ const { form, touchedFieldsRef } = instance.raw;
278
+ return /* @__PURE__ */ jsx(FormProvider, {
279
+ context: form.context,
280
+ children: /* @__PURE__ */ jsx(TouchedFieldsContext, {
281
+ value: touchedFieldsRef,
282
+ children
283
+ })
284
+ });
285
+ }
286
+ /**
287
+ * Conform.js adapter implementing the `FormAdapter` interface.
288
+ *
289
+ * Maps Conform's `useForm` / `useInputControl` / `useFormMetadata` APIs
290
+ * to the normalized form adapter contract.
291
+ */
292
+ const conformAdapter = {
293
+ name: "Conform",
294
+ useCreateForm: useConformCreateForm,
295
+ useField: useConformField,
296
+ useWatch: useConformWatch,
297
+ useWatchAll: useConformWatchAll,
298
+ useFieldArray: useConformFieldArray,
299
+ FormProvider: ConformFormProviderWrapper
300
+ };
301
+ //#endregion
302
+ //#region src/components/features/form/adapters/conform/conform-provider.tsx
303
+ /**
304
+ * Wrap your application with this provider to use Conform.js as the form backend.
305
+ *
306
+ * @example
307
+ * ```tsx
308
+ * import { ConformAdapter } from '@datum-cloud/datum-ui/form/adapters/conform'
309
+ *
310
+ * function App() {
311
+ * return (
312
+ * <ConformAdapter>
313
+ * <MyApp />
314
+ * </ConformAdapter>
315
+ * )
316
+ * }
317
+ * ```
318
+ */
319
+ function ConformAdapter({ children }) {
320
+ return /* @__PURE__ */ jsx(FormAdapterProvider, {
321
+ adapter: conformAdapter,
322
+ children
323
+ });
324
+ }
325
+ //#endregion
326
+ export { ConformAdapter, conformAdapter };