@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.
- package/README.md +78 -40
- package/dist/adapter-context-rWveHhDd.mjs +25 -0
- package/dist/combobox/index.mjs +2 -0
- package/dist/combobox-cKTFK4uN.mjs +96 -0
- package/dist/components/features/combobox/combobox.d.ts +27 -0
- package/dist/components/features/combobox/combobox.d.ts.map +1 -0
- package/dist/components/features/combobox/index.d.ts +3 -0
- package/dist/components/features/combobox/index.d.ts.map +1 -0
- package/dist/components/features/combobox/types.d.ts +78 -0
- package/dist/components/features/combobox/types.d.ts.map +1 -0
- package/dist/components/features/date-time-picker/date-time-picker.d.ts +9 -0
- package/dist/components/features/date-time-picker/date-time-picker.d.ts.map +1 -0
- package/dist/components/features/date-time-picker/index.d.ts +3 -0
- package/dist/components/features/date-time-picker/index.d.ts.map +1 -0
- package/dist/components/features/date-time-picker/types.d.ts +53 -0
- package/dist/components/features/date-time-picker/types.d.ts.map +1 -0
- package/dist/components/features/date-time-picker/utils/format.d.ts +13 -0
- package/dist/components/features/date-time-picker/utils/format.d.ts.map +1 -0
- package/dist/components/features/date-time-picker/utils/index.d.ts +3 -0
- package/dist/components/features/date-time-picker/utils/index.d.ts.map +1 -0
- package/dist/components/features/date-time-picker/utils/timezone.d.ts +23 -0
- package/dist/components/features/date-time-picker/utils/timezone.d.ts.map +1 -0
- package/dist/components/features/form/adapter-context.d.ts +17 -0
- package/dist/components/features/form/adapter-context.d.ts.map +1 -0
- package/dist/components/features/form/adapter-types.d.ts +120 -0
- package/dist/components/features/form/adapter-types.d.ts.map +1 -0
- package/dist/components/features/form/adapters/conform/conform-adapter.d.ts +9 -0
- package/dist/components/features/form/adapters/conform/conform-adapter.d.ts.map +1 -0
- package/dist/components/features/form/adapters/conform/conform-provider.d.ts +22 -0
- package/dist/components/features/form/adapters/conform/conform-provider.d.ts.map +1 -0
- package/dist/components/features/form/adapters/conform/index.d.ts +3 -0
- package/dist/components/features/form/adapters/conform/index.d.ts.map +1 -0
- package/dist/components/features/form/adapters/rhf/index.d.ts +3 -0
- package/dist/components/features/form/adapters/rhf/index.d.ts.map +1 -0
- package/dist/components/features/form/adapters/rhf/rhf-adapter.d.ts +10 -0
- package/dist/components/features/form/adapters/rhf/rhf-adapter.d.ts.map +1 -0
- package/dist/components/features/form/adapters/rhf/rhf-provider.d.ts +22 -0
- package/dist/components/features/form/adapters/rhf/rhf-provider.d.ts.map +1 -0
- package/dist/components/features/form/components/form-autocomplete.d.ts.map +1 -1
- package/dist/components/features/form/components/form-autosearch.d.ts +25 -0
- package/dist/components/features/form/components/form-autosearch.d.ts.map +1 -0
- package/dist/components/features/form/components/form-checkbox.d.ts.map +1 -1
- package/dist/components/features/form/components/form-combobox.d.ts +76 -0
- package/dist/components/features/form/components/form-combobox.d.ts.map +1 -0
- package/dist/components/features/form/components/form-copy-box.d.ts.map +1 -1
- package/dist/components/features/form/components/form-custom.d.ts.map +1 -1
- package/dist/components/features/form/components/form-date-picker.d.ts +38 -0
- package/dist/components/features/form/components/form-date-picker.d.ts.map +1 -0
- package/dist/components/features/form/components/form-date-time-picker.d.ts +37 -0
- package/dist/components/features/form/components/form-date-time-picker.d.ts.map +1 -0
- package/dist/components/features/form/components/form-field-array.d.ts +5 -17
- package/dist/components/features/form/components/form-field-array.d.ts.map +1 -1
- package/dist/components/features/form/components/form-field.d.ts +7 -21
- package/dist/components/features/form/components/form-field.d.ts.map +1 -1
- package/dist/components/features/form/components/form-input-group.d.ts +4 -4
- package/dist/components/features/form/components/form-input-group.d.ts.map +1 -1
- package/dist/components/features/form/components/form-input.d.ts.map +1 -1
- package/dist/components/features/form/components/form-radio-group.d.ts.map +1 -1
- package/dist/components/features/form/components/form-root.d.ts +5 -25
- package/dist/components/features/form/components/form-root.d.ts.map +1 -1
- package/dist/components/features/form/components/form-select.d.ts.map +1 -1
- package/dist/components/features/form/components/form-switch.d.ts.map +1 -1
- package/dist/components/features/form/components/form-textarea.d.ts.map +1 -1
- package/dist/components/features/form/components/form-time-picker.d.ts +21 -0
- package/dist/components/features/form/components/form-time-picker.d.ts.map +1 -0
- package/dist/components/features/form/components/form-transfer.d.ts +37 -0
- package/dist/components/features/form/components/form-transfer.d.ts.map +1 -0
- package/dist/components/features/form/components/index.d.ts +6 -1
- package/dist/components/features/form/components/index.d.ts.map +1 -1
- package/dist/components/features/form/components/stepper/form-stepper.d.ts.map +1 -1
- package/dist/components/features/form/context/form-context.d.ts +2 -2
- package/dist/components/features/form/context/form-context.d.ts.map +1 -1
- package/dist/components/features/form/hooks/index.d.ts +1 -1
- package/dist/components/features/form/hooks/index.d.ts.map +1 -1
- package/dist/components/features/form/hooks/use-field.d.ts +12 -18
- package/dist/components/features/form/hooks/use-field.d.ts.map +1 -1
- package/dist/components/features/form/hooks/use-form-state.d.ts +36 -0
- package/dist/components/features/form/hooks/use-form-state.d.ts.map +1 -0
- package/dist/components/features/form/hooks/use-watch.d.ts +9 -20
- package/dist/components/features/form/hooks/use-watch.d.ts.map +1 -1
- package/dist/components/features/form/index.d.ts +69 -44
- package/dist/components/features/form/index.d.ts.map +1 -1
- package/dist/components/features/form/stepper/index.d.ts +17 -0
- package/dist/components/features/form/stepper/index.d.ts.map +1 -0
- package/dist/components/features/form/types/index.d.ts +68 -32
- package/dist/components/features/form/types/index.d.ts.map +1 -1
- package/dist/components/features/form/utils/get-field-constraints.d.ts +43 -0
- package/dist/components/features/form/utils/get-field-constraints.d.ts.map +1 -0
- package/dist/components/features/form/utils/get-schema-defaults.d.ts +24 -0
- package/dist/components/features/form/utils/get-schema-defaults.d.ts.map +1 -0
- package/dist/components/features/time-picker/index.d.ts +3 -0
- package/dist/components/features/time-picker/index.d.ts.map +1 -0
- package/dist/components/features/time-picker/time-picker.d.ts +22 -0
- package/dist/components/features/time-picker/time-picker.d.ts.map +1 -0
- package/dist/components/features/time-picker/types.d.ts +31 -0
- package/dist/components/features/time-picker/types.d.ts.map +1 -0
- package/dist/components/features/transfer/components/index.d.ts +9 -0
- package/dist/components/features/transfer/components/index.d.ts.map +1 -0
- package/dist/components/features/transfer/components/transfer-group.d.ts +7 -0
- package/dist/components/features/transfer/components/transfer-group.d.ts.map +1 -0
- package/dist/components/features/transfer/components/transfer-item.d.ts +10 -0
- package/dist/components/features/transfer/components/transfer-item.d.ts.map +1 -0
- package/dist/components/features/transfer/components/transfer-panel.d.ts +18 -0
- package/dist/components/features/transfer/components/transfer-panel.d.ts.map +1 -0
- package/dist/components/features/transfer/components/transfer-search.d.ts +9 -0
- package/dist/components/features/transfer/components/transfer-search.d.ts.map +1 -0
- package/dist/components/features/transfer/hooks/use-transfer-dnd.d.ts +26 -0
- package/dist/components/features/transfer/hooks/use-transfer-dnd.d.ts.map +1 -0
- package/dist/components/features/transfer/hooks/use-transfer-state.d.ts +20 -0
- package/dist/components/features/transfer/hooks/use-transfer-state.d.ts.map +1 -0
- package/dist/components/features/transfer/index.d.ts +3 -0
- package/dist/components/features/transfer/index.d.ts.map +1 -0
- package/dist/components/features/transfer/transfer.d.ts +6 -0
- package/dist/components/features/transfer/transfer.d.ts.map +1 -0
- package/dist/components/features/transfer/types.d.ts +69 -0
- package/dist/components/features/transfer/types.d.ts.map +1 -0
- package/dist/date-picker/index.mjs +1 -1
- package/dist/date-time-picker/index.mjs +2 -0
- package/dist/date-time-picker-Bx_n4nEJ.mjs +177 -0
- package/dist/form/adapters/conform/index.mjs +326 -0
- package/dist/form/adapters/rhf/index.mjs +275 -0
- package/dist/form/index.mjs +3 -2
- package/dist/form/stepper/index.mjs +542 -0
- package/dist/form-context-Ccxm-wqL.mjs +17 -0
- package/dist/form-zf5QOnAk.mjs +1611 -0
- package/dist/get-field-constraints-CxfZ753o.mjs +49 -0
- package/dist/grid/index.mjs +1 -1
- package/dist/hooks/index.mjs +2 -2
- package/dist/index.mjs +14 -13
- package/dist/input-number/index.mjs +1 -1
- package/dist/map/index.mjs +1 -1
- package/dist/{map-ClxB41Hg.mjs → map-CWIQ-eql.mjs} +1 -1
- package/dist/more-actions/index.mjs +1 -1
- package/dist/page-title/index.mjs +1 -1
- package/dist/stepper/index.mjs +1 -320
- package/dist/stepper-DvIOp0hh.mjs +321 -0
- package/dist/tag-input/index.mjs +1 -1
- package/dist/task-queue/index.mjs +1 -1
- package/dist/time-picker/index.mjs +2 -0
- package/dist/time-picker-BoF7pZZ2.mjs +43 -0
- package/dist/transfer/index.mjs +2 -0
- package/dist/transfer-C55XfEXy.mjs +243 -0
- package/package.json +58 -2
- package/dist/form-Co3fM4B7.mjs +0 -2114
- /package/dist/{col-q-J99UHe.mjs → col-1T0Q3SlH.mjs} +0 -0
- /package/dist/{hooks-Cb7YlxN4.mjs → hooks-D8r2M2U6.mjs} +0 -0
- /package/dist/{input-number-mDB-5M5C.mjs → input-number-a7uydAsw.mjs} +0 -0
- /package/dist/{map-leaflet-imports-CaMm_rdF.mjs → map-leaflet-imports-CRSKA79m.mjs} +0 -0
- /package/dist/{more-actions-CGagbIDT.mjs → more-actions-ILnEZq_E.mjs} +0 -0
- /package/dist/{page-title-R7QbfbWp.mjs → page-title-ChsnpBiH.mjs} +0 -0
- /package/dist/{tag-input-BVSwNcRd.mjs → tag-input-T9cUX9-G.mjs} +0 -0
- /package/dist/{task-queue-dropdown-DyM5R8KF.mjs → task-queue-dropdown-Wcbj-f0V.mjs} +0 -0
- /package/dist/{to-api-format-BnbRFYQI.mjs → to-api-format-Bh3c01gr.mjs} +0 -0
- /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-
|
|
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,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 };
|