@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.
- package/README.md +4 -0
- package/dist/autocomplete/index.mjs +1 -1
- package/dist/{autocomplete-V5-qslzS.mjs → autocomplete-CkYJueBL.mjs} +2 -2
- package/dist/autosearch/index.mjs +199 -0
- package/dist/{calendar-date-picker-DWK94_DC.mjs → calendar-date-picker-CDT-8Ha8.mjs} +2 -1
- package/dist/combobox/index.mjs +2 -0
- package/dist/combobox-B-C9lJeD.mjs +97 -0
- package/dist/components/features/autocomplete/autocomplete.d.ts +1 -1
- package/dist/components/features/autocomplete/autocomplete.d.ts.map +1 -1
- package/dist/components/features/autocomplete/autocomplete.types.d.ts +2 -0
- package/dist/components/features/autocomplete/autocomplete.types.d.ts.map +1 -1
- package/dist/components/features/autosearch/autosearch.d.ts +35 -0
- package/dist/components/features/autosearch/autosearch.d.ts.map +1 -0
- package/dist/components/features/autosearch/autosearch.types.d.ts +51 -0
- package/dist/components/features/autosearch/autosearch.types.d.ts.map +1 -0
- package/dist/components/features/autosearch/index.d.ts +3 -0
- package/dist/components/features/autosearch/index.d.ts.map +1 -0
- package/dist/components/features/calendar-date-picker/calendar-date-picker.d.ts +2 -1
- package/dist/components/features/calendar-date-picker/calendar-date-picker.d.ts.map +1 -1
- 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 +84 -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 +59 -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-types.d.ts +26 -0
- package/dist/components/features/form/adapter-types.d.ts.map +1 -1
- package/dist/components/features/form/adapters/conform/conform-adapter.d.ts.map +1 -1
- package/dist/components/features/form/adapters/rhf/rhf-adapter.d.ts.map +1 -1
- package/dist/components/features/form/components/form-autocomplete.d.ts.map +1 -1
- package/dist/components/features/form/components/form-autosearch.d.ts +37 -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 +80 -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 +3 -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 +40 -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 +39 -0
- package/dist/components/features/form/components/form-date-time-picker.d.ts.map +1 -0
- package/dist/components/features/form/components/form-dialog.d.ts.map +1 -1
- package/dist/components/features/form/components/form-field.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.map +1 -1
- package/dist/components/features/form/components/form-switch.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 +7 -1
- package/dist/components/features/form/components/index.d.ts.map +1 -1
- package/dist/components/features/form/components/stepper/form-stepper.d.ts +3 -1
- package/dist/components/features/form/components/stepper/form-stepper.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-display-touched.d.ts +20 -0
- package/dist/components/features/form/hooks/use-display-touched.d.ts.map +1 -0
- package/dist/components/features/form/hooks/use-field.d.ts +4 -0
- 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/index.d.ts +39 -21
- 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 +46 -0
- package/dist/components/features/form/types/index.d.ts.map +1 -1
- package/dist/components/features/form/utils/get-field-constraints.d.ts +23 -1
- package/dist/components/features/form/utils/get-field-constraints.d.ts.map +1 -1
- 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/form/utils/zod-helpers.d.ts +12 -0
- package/dist/components/features/form/utils/zod-helpers.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/data-table/index.mjs +1 -1
- package/dist/date-picker/index.mjs +2 -2
- package/dist/date-time-picker/index.mjs +2 -0
- package/dist/date-time-picker-BomrW07W.mjs +178 -0
- package/dist/form/adapters/conform/index.mjs +110 -13
- package/dist/form/adapters/rhf/index.mjs +116 -27
- package/dist/form/index.mjs +3 -3
- package/dist/form/stepper/index.mjs +519 -0
- package/dist/{form-BE1xBne4.mjs → form-B3rQ4CH9.mjs} +447 -605
- package/dist/form-context-Ccxm-wqL.mjs +17 -0
- package/dist/grid/index.mjs +1 -1
- package/dist/hooks/index.mjs +2 -2
- package/dist/index.mjs +16 -16
- package/dist/input-number/index.mjs +1 -1
- package/dist/map/index.mjs +1 -1
- package/dist/{map-Cw7u8r6E.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-46C-rFFW.mjs +264 -0
- package/dist/{get-field-constraints-BPMW8VvY.mjs → use-display-touched-I39aXEBD.mjs} +51 -16
- package/package.json +42 -1
- /package/dist/{adapter-context-B7L2ucTr.mjs → adapter-context-rWveHhDd.mjs} +0 -0
- /package/dist/{col-YBbQ5wlb.mjs → col-1T0Q3SlH.mjs} +0 -0
- /package/dist/{hooks-DYjN7lvC.mjs → hooks-D8r2M2U6.mjs} +0 -0
- /package/dist/{input-number-DEjXG2I6.mjs → input-number-a7uydAsw.mjs} +0 -0
- /package/dist/{map-leaflet-imports-D6nTEOIh.mjs → map-leaflet-imports-CRSKA79m.mjs} +0 -0
- /package/dist/{more-actions-BNQ2yfWZ.mjs → more-actions-ILnEZq_E.mjs} +0 -0
- /package/dist/{page-title-CNiRNZ7p.mjs → page-title-ChsnpBiH.mjs} +0 -0
- /package/dist/{tag-input-BKed-cul.mjs → tag-input-T9cUX9-G.mjs} +0 -0
- /package/dist/{task-queue-dropdown-Di_Wjspz.mjs → task-queue-dropdown-Wcbj-f0V.mjs} +0 -0
- /package/dist/{to-api-format-Cq4prffn.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,11 +1,17 @@
|
|
|
1
|
-
import { t as FormAdapterProvider } from "../../../adapter-context-
|
|
2
|
-
import {
|
|
1
|
+
import { t as FormAdapterProvider } from "../../../adapter-context-rWveHhDd.mjs";
|
|
2
|
+
import { n as getFieldConstraints, t as useDisplayTouched } from "../../../use-display-touched-I39aXEBD.mjs";
|
|
3
3
|
import * as React$1 from "react";
|
|
4
4
|
import { jsx } from "react/jsx-runtime";
|
|
5
5
|
import { FormProvider, getFormProps, getInputProps, useForm, useFormMetadata, useInputControl } from "@conform-to/react";
|
|
6
|
+
import { isDirty, useFormData } from "@conform-to/react/future";
|
|
6
7
|
import { getZodConstraint, parseWithZod } from "@conform-to/zod/v4";
|
|
7
8
|
//#region src/components/features/form/adapters/conform/conform-adapter.tsx
|
|
8
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
|
+
/**
|
|
9
15
|
* Resolve a Conform field metadata object by dot-notation path.
|
|
10
16
|
*
|
|
11
17
|
* Handles:
|
|
@@ -35,13 +41,18 @@ function resolveConformField(fields, name) {
|
|
|
35
41
|
function convertFromString(value) {
|
|
36
42
|
if (value === void 0) return void 0;
|
|
37
43
|
if (Array.isArray(value)) return value[0];
|
|
38
|
-
if (value === "on"
|
|
39
|
-
if (value
|
|
44
|
+
if (value === "on") return true;
|
|
45
|
+
if (value.startsWith("[\"") && value.endsWith("]")) try {
|
|
46
|
+
return JSON.parse(value);
|
|
47
|
+
} catch {
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
40
50
|
return value;
|
|
41
51
|
}
|
|
42
52
|
function convertToString(value) {
|
|
43
53
|
if (typeof value === "boolean") return value ? "on" : "";
|
|
44
54
|
if (value === null || value === void 0) return "";
|
|
55
|
+
if (Array.isArray(value)) return JSON.stringify(value);
|
|
45
56
|
return String(value);
|
|
46
57
|
}
|
|
47
58
|
/** Create a Conform form instance and normalize it to the adapter interface. */
|
|
@@ -65,25 +76,85 @@ function useConformCreateForm(props) {
|
|
|
65
76
|
} : void 0
|
|
66
77
|
});
|
|
67
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
|
+
]);
|
|
68
133
|
const normalizedFields = React$1.useMemo(() => {
|
|
69
134
|
const result = {};
|
|
70
135
|
for (const [key, fieldMeta] of Object.entries(fields)) result[key] = {
|
|
71
136
|
id: fieldMeta.id ?? `${id ?? form.id}-${key}`,
|
|
72
137
|
errors: fieldMeta.errors ?? [],
|
|
73
|
-
required: constraints[key]?.required ?? false
|
|
138
|
+
required: constraints[key]?.required ?? false,
|
|
139
|
+
isDirty: dirtyFields[key] ?? false,
|
|
140
|
+
isTouched: touchedFields[key] ?? false
|
|
74
141
|
};
|
|
75
142
|
return result;
|
|
76
143
|
}, [
|
|
77
144
|
fields,
|
|
78
145
|
id,
|
|
79
146
|
form.id,
|
|
80
|
-
constraints
|
|
147
|
+
constraints,
|
|
148
|
+
dirtyFields,
|
|
149
|
+
touchedFields
|
|
81
150
|
]);
|
|
82
151
|
const conformFormProps = React$1.useMemo(() => getFormProps(form), [form]);
|
|
152
|
+
const { displayTouchedFields, markFieldTouched, markAllFieldsTouched } = useDisplayTouched(schema);
|
|
83
153
|
return React$1.useMemo(() => ({
|
|
84
154
|
id: form.id,
|
|
85
155
|
fields: normalizedFields,
|
|
86
156
|
formProps: conformFormProps,
|
|
157
|
+
formState,
|
|
87
158
|
submit: () => formRef?.current?.requestSubmit(),
|
|
88
159
|
reset: () => form.reset(),
|
|
89
160
|
getValues: () => {
|
|
@@ -93,38 +164,59 @@ function useConformCreateForm(props) {
|
|
|
93
164
|
for (const [key, value] of formData.entries()) values[key] = value;
|
|
94
165
|
return values;
|
|
95
166
|
},
|
|
167
|
+
touchedFields: displayTouchedFields,
|
|
168
|
+
markFieldTouched,
|
|
169
|
+
markAllFieldsTouched,
|
|
96
170
|
raw: {
|
|
97
171
|
form,
|
|
98
|
-
fields
|
|
172
|
+
fields,
|
|
173
|
+
touchedFieldsRef
|
|
99
174
|
}
|
|
100
175
|
}), [
|
|
101
176
|
form,
|
|
102
177
|
fields,
|
|
103
178
|
normalizedFields,
|
|
104
179
|
conformFormProps,
|
|
105
|
-
|
|
180
|
+
formState,
|
|
181
|
+
formRef,
|
|
182
|
+
displayTouchedFields,
|
|
183
|
+
markFieldTouched,
|
|
184
|
+
markAllFieldsTouched
|
|
106
185
|
]);
|
|
107
186
|
}
|
|
108
187
|
/** Resolve a field by dot-notation path and return its normalized state. */
|
|
109
188
|
function useConformField(name) {
|
|
110
189
|
const fieldMeta = resolveConformField(useFormMetadata().getFieldset(), name);
|
|
190
|
+
const touchedFieldsRef = React$1.use(TouchedFieldsContext);
|
|
111
191
|
const control = useInputControl(fieldMeta ?? {
|
|
112
192
|
name,
|
|
113
193
|
key: void 0,
|
|
114
194
|
id: name
|
|
115
195
|
});
|
|
116
196
|
if (!fieldMeta) throw new Error(`[Conform Adapter] Field "${name}" not found. Make sure the field name matches your schema.`);
|
|
197
|
+
const currentValue = convertFromString(control.value);
|
|
198
|
+
const defaultValue = convertFromString(fieldMeta.initialValue);
|
|
199
|
+
const fieldIsDirty = currentValue !== (defaultValue === void 0 ? "" : defaultValue);
|
|
200
|
+
const fieldIsTouched = touchedFieldsRef.current.has(name);
|
|
117
201
|
return React$1.useMemo(() => ({
|
|
118
202
|
name: fieldMeta.name,
|
|
119
203
|
id: fieldMeta.id,
|
|
120
204
|
errors: fieldMeta.errors ?? [],
|
|
121
205
|
required: fieldMeta.required ?? false,
|
|
122
|
-
|
|
206
|
+
isDirty: fieldIsDirty,
|
|
207
|
+
isTouched: fieldIsTouched,
|
|
208
|
+
value: currentValue,
|
|
123
209
|
change: (value) => control.change(convertToString(value)),
|
|
124
210
|
blur: () => control.blur(),
|
|
125
211
|
focus: () => control.focus(),
|
|
126
212
|
inputProps: getInputProps(fieldMeta, { type: "text" })
|
|
127
|
-
}), [
|
|
213
|
+
}), [
|
|
214
|
+
fieldMeta,
|
|
215
|
+
control,
|
|
216
|
+
currentValue,
|
|
217
|
+
fieldIsDirty,
|
|
218
|
+
fieldIsTouched
|
|
219
|
+
]);
|
|
128
220
|
}
|
|
129
221
|
/** Watch a single field's value reactively. */
|
|
130
222
|
function useConformWatch(name) {
|
|
@@ -135,6 +227,7 @@ function useConformWatch(name) {
|
|
|
135
227
|
/** Watch multiple fields' values reactively. */
|
|
136
228
|
function useConformWatchAll(names) {
|
|
137
229
|
const allFields = useFormMetadata().getFieldset();
|
|
230
|
+
const namesKey = names.join(",");
|
|
138
231
|
return React$1.useMemo(() => {
|
|
139
232
|
const result = {};
|
|
140
233
|
for (const name of names) {
|
|
@@ -142,7 +235,7 @@ function useConformWatchAll(names) {
|
|
|
142
235
|
if (fieldMeta) result[name] = convertFromString(fieldMeta.value);
|
|
143
236
|
}
|
|
144
237
|
return result;
|
|
145
|
-
}, [allFields,
|
|
238
|
+
}, [allFields, namesKey]);
|
|
146
239
|
}
|
|
147
240
|
/** Get field array helpers for a given array field name. */
|
|
148
241
|
function useConformFieldArray(name) {
|
|
@@ -186,12 +279,16 @@ function useConformFieldArray(name) {
|
|
|
186
279
|
/**
|
|
187
280
|
* Wraps children in Conform's FormProvider so that useFormMetadata() and
|
|
188
281
|
* useInputControl() work inside field components.
|
|
282
|
+
* Also provides the touched fields ref via context for useConformField.
|
|
189
283
|
*/
|
|
190
284
|
function ConformFormProviderWrapper({ instance, children }) {
|
|
191
|
-
const { form } = instance.raw;
|
|
285
|
+
const { form, touchedFieldsRef } = instance.raw;
|
|
192
286
|
return /* @__PURE__ */ jsx(FormProvider, {
|
|
193
287
|
context: form.context,
|
|
194
|
-
children
|
|
288
|
+
children: /* @__PURE__ */ jsx(TouchedFieldsContext, {
|
|
289
|
+
value: touchedFieldsRef,
|
|
290
|
+
children
|
|
291
|
+
})
|
|
195
292
|
});
|
|
196
293
|
}
|
|
197
294
|
/**
|
|
@@ -1,41 +1,115 @@
|
|
|
1
|
-
import { t as FormAdapterProvider } from "../../../adapter-context-
|
|
2
|
-
import {
|
|
1
|
+
import { t as FormAdapterProvider } from "../../../adapter-context-rWveHhDd.mjs";
|
|
2
|
+
import { n as getFieldConstraints, r as getObjectShape, t as useDisplayTouched } from "../../../use-display-touched-I39aXEBD.mjs";
|
|
3
3
|
import * as React$1 from "react";
|
|
4
4
|
import { jsx } from "react/jsx-runtime";
|
|
5
5
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
6
6
|
import { FormProvider, useController, useFieldArray, useForm, useFormContext, useWatch } from "react-hook-form";
|
|
7
|
+
//#region src/components/features/form/utils/get-schema-defaults.ts
|
|
8
|
+
/**
|
|
9
|
+
* Derive default values from a Zod schema that match what React Hook Form's
|
|
10
|
+
* `useController` will produce when fields register.
|
|
11
|
+
*
|
|
12
|
+
* This is critical for RHF's `isDirty` tracking: RHF compares current values
|
|
13
|
+
* against `_defaultValues`. Without explicit defaults, RHF uses `{}` as the
|
|
14
|
+
* baseline, so when `useController` registers fields (e.g. `username: ''`),
|
|
15
|
+
* the form is immediately considered dirty.
|
|
16
|
+
*
|
|
17
|
+
* By passing these schema-derived defaults to `useForm({ defaultValues })`,
|
|
18
|
+
* the baseline matches the registered values and `isDirty` starts as `false`.
|
|
19
|
+
*
|
|
20
|
+
* Maps Zod field types to their `useController` registration values:
|
|
21
|
+
* - string -> `''`
|
|
22
|
+
* - number -> `undefined`
|
|
23
|
+
* - boolean -> `false`
|
|
24
|
+
* - array -> `[]`
|
|
25
|
+
* - object -> `{}` (recursive)
|
|
26
|
+
* - optional/nullable wrappers -> unwrap and derive inner default
|
|
27
|
+
* - `.default()` -> use the provided default value
|
|
28
|
+
*/
|
|
29
|
+
function getSchemaDefaults(schema) {
|
|
30
|
+
const shape = getObjectShape(schema);
|
|
31
|
+
if (!shape) return {};
|
|
32
|
+
const defaults = {};
|
|
33
|
+
for (const [key, fieldSchema] of Object.entries(shape)) defaults[key] = getFieldDefault(fieldSchema);
|
|
34
|
+
return defaults;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get the default value for a Zod field type that matches what RHF's
|
|
38
|
+
* `useController` will produce when registering the field.
|
|
39
|
+
*
|
|
40
|
+
* Unwraps optional/nullable/default wrappers to find the inner type,
|
|
41
|
+
* then returns the natural "empty" value for that type.
|
|
42
|
+
*/
|
|
43
|
+
function getFieldDefault(schema) {
|
|
44
|
+
const { type } = schema.def;
|
|
45
|
+
if (type === "default") return schema.def.defaultValue;
|
|
46
|
+
if (type === "optional" || type === "nullable") {
|
|
47
|
+
const innerDef = schema.def;
|
|
48
|
+
return getFieldDefault(innerDef.innerType);
|
|
49
|
+
}
|
|
50
|
+
if (type === "pipe") {
|
|
51
|
+
const pipeDef = schema.def;
|
|
52
|
+
return getFieldDefault(pipeDef.in);
|
|
53
|
+
}
|
|
54
|
+
if (type === "string") return "";
|
|
55
|
+
if (type === "number" || type === "bigint" || type === "nan") return void 0;
|
|
56
|
+
if (type === "boolean") return false;
|
|
57
|
+
if (type === "array") return [];
|
|
58
|
+
if (type === "object") return getSchemaDefaults(schema);
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
7
61
|
//#region src/components/features/form/adapters/rhf/rhf-adapter.tsx
|
|
62
|
+
const RHFFormIdContext = React$1.createContext("form");
|
|
8
63
|
/** Create a React Hook Form instance and normalize it to the adapter interface. */
|
|
9
64
|
function useRHFCreateForm(props) {
|
|
10
65
|
const { schema, defaultValues, mode, id, onSubmit, formRef } = props;
|
|
66
|
+
const schemaDefaults = React$1.useMemo(() => getSchemaDefaults(schema), [schema]);
|
|
67
|
+
const mergedDefaults = React$1.useMemo(() => ({
|
|
68
|
+
...schemaDefaults,
|
|
69
|
+
...defaultValues
|
|
70
|
+
}), [schemaDefaults, defaultValues]);
|
|
11
71
|
const form = useForm({
|
|
12
72
|
resolver: zodResolver(schema),
|
|
13
|
-
defaultValues,
|
|
14
|
-
mode
|
|
15
|
-
|
|
16
|
-
onChange: "onChange",
|
|
17
|
-
onSubmit: "onSubmit"
|
|
18
|
-
}[mode]
|
|
73
|
+
defaultValues: mergedDefaults,
|
|
74
|
+
mode,
|
|
75
|
+
reValidateMode: "onChange"
|
|
19
76
|
});
|
|
77
|
+
const { errors, isDirty, isValid, dirtyFields, touchedFields } = form.formState;
|
|
20
78
|
const constraints = React$1.useMemo(() => getFieldConstraints(schema), [schema]);
|
|
21
79
|
const onSubmitRef = React$1.useRef(onSubmit);
|
|
22
80
|
onSubmitRef.current = onSubmit;
|
|
23
81
|
const normalizedFields = React$1.useMemo(() => {
|
|
24
82
|
const result = {};
|
|
25
|
-
const errors = form.formState.errors;
|
|
26
83
|
for (const [key, constraint] of Object.entries(constraints)) {
|
|
27
84
|
const fieldError = errors[key];
|
|
28
85
|
result[key] = {
|
|
29
86
|
id: `${id ?? "form"}-${key}`,
|
|
30
87
|
errors: fieldError?.message ? [String(fieldError.message)] : [],
|
|
31
|
-
required: constraint.required
|
|
88
|
+
required: constraint.required,
|
|
89
|
+
isDirty: Boolean(dirtyFields[key]),
|
|
90
|
+
isTouched: Boolean(touchedFields[key])
|
|
32
91
|
};
|
|
33
92
|
}
|
|
34
93
|
return result;
|
|
35
94
|
}, [
|
|
36
95
|
constraints,
|
|
37
|
-
|
|
38
|
-
id
|
|
96
|
+
errors,
|
|
97
|
+
id,
|
|
98
|
+
dirtyFields,
|
|
99
|
+
touchedFields
|
|
100
|
+
]);
|
|
101
|
+
const formState = React$1.useMemo(() => ({
|
|
102
|
+
isDirty,
|
|
103
|
+
isValid,
|
|
104
|
+
isSubmitted: false,
|
|
105
|
+
submitCount: 0,
|
|
106
|
+
dirtyFields: Object.fromEntries(Object.entries(dirtyFields).map(([k, v]) => [k, Boolean(v)])),
|
|
107
|
+
touchedFields: Object.fromEntries(Object.entries(touchedFields).map(([k, v]) => [k, Boolean(v)]))
|
|
108
|
+
}), [
|
|
109
|
+
isDirty,
|
|
110
|
+
isValid,
|
|
111
|
+
dirtyFields,
|
|
112
|
+
touchedFields
|
|
39
113
|
]);
|
|
40
114
|
const handleSubmit = React$1.useMemo(() => form.handleSubmit((data) => {
|
|
41
115
|
onSubmitRef.current?.(data);
|
|
@@ -45,41 +119,56 @@ function useRHFCreateForm(props) {
|
|
|
45
119
|
onSubmit: handleSubmit,
|
|
46
120
|
noValidate: true
|
|
47
121
|
}), [id, handleSubmit]);
|
|
122
|
+
const { displayTouchedFields, markFieldTouched, markAllFieldsTouched } = useDisplayTouched(schema);
|
|
48
123
|
return React$1.useMemo(() => ({
|
|
49
124
|
id: id ?? "form",
|
|
50
125
|
fields: normalizedFields,
|
|
51
126
|
formProps,
|
|
127
|
+
formState,
|
|
52
128
|
submit: () => formRef?.current?.requestSubmit(),
|
|
53
129
|
reset: () => form.reset(),
|
|
54
130
|
getValues: () => form.getValues(),
|
|
131
|
+
touchedFields: displayTouchedFields,
|
|
132
|
+
markFieldTouched,
|
|
133
|
+
markAllFieldsTouched,
|
|
55
134
|
raw: form
|
|
56
135
|
}), [
|
|
57
136
|
id,
|
|
58
137
|
normalizedFields,
|
|
59
138
|
formProps,
|
|
139
|
+
formState,
|
|
60
140
|
form,
|
|
61
|
-
formRef
|
|
141
|
+
formRef,
|
|
142
|
+
displayTouchedFields,
|
|
143
|
+
markFieldTouched,
|
|
144
|
+
markAllFieldsTouched
|
|
62
145
|
]);
|
|
63
146
|
}
|
|
64
147
|
/** Resolve a field by name and return its normalized state. */
|
|
65
148
|
function useRHFField(name) {
|
|
149
|
+
const form = useFormContext();
|
|
150
|
+
const formId = React$1.use(RHFFormIdContext);
|
|
66
151
|
const { field, fieldState } = useController({
|
|
67
152
|
name,
|
|
68
|
-
control:
|
|
153
|
+
control: form.control
|
|
69
154
|
});
|
|
70
155
|
return React$1.useMemo(() => ({
|
|
71
156
|
name: field.name,
|
|
72
|
-
id: name
|
|
157
|
+
id: `${formId}-${name}`,
|
|
73
158
|
errors: fieldState.error?.message ? [String(fieldState.error.message)] : [],
|
|
74
159
|
required: false,
|
|
160
|
+
isDirty: fieldState.isDirty,
|
|
161
|
+
isTouched: fieldState.isTouched,
|
|
75
162
|
value: field.value,
|
|
76
163
|
change: (value) => field.onChange(value),
|
|
77
164
|
blur: () => field.onBlur(),
|
|
78
|
-
focus: () =>
|
|
165
|
+
focus: () => form.setFocus(name)
|
|
79
166
|
}), [
|
|
80
167
|
field,
|
|
81
168
|
fieldState,
|
|
82
|
-
name
|
|
169
|
+
name,
|
|
170
|
+
formId,
|
|
171
|
+
form
|
|
83
172
|
]);
|
|
84
173
|
}
|
|
85
174
|
/** Watch a single field's value reactively. */
|
|
@@ -95,13 +184,14 @@ function useRHFWatchAllHook(names) {
|
|
|
95
184
|
name: names,
|
|
96
185
|
control: useFormContext().control
|
|
97
186
|
});
|
|
187
|
+
const namesKey = names.join(",");
|
|
98
188
|
return React$1.useMemo(() => {
|
|
99
189
|
const result = {};
|
|
100
190
|
names.forEach((n, index) => {
|
|
101
191
|
result[n] = values[index];
|
|
102
192
|
});
|
|
103
193
|
return result;
|
|
104
|
-
}, [
|
|
194
|
+
}, [namesKey, values]);
|
|
105
195
|
}
|
|
106
196
|
/** Get field array helpers for a given array field name. */
|
|
107
197
|
function useRHFFieldArrayHook(name) {
|
|
@@ -118,12 +208,8 @@ function useRHFFieldArrayHook(name) {
|
|
|
118
208
|
append: React$1.useCallback((defaultValue) => {
|
|
119
209
|
append(defaultValue ?? {});
|
|
120
210
|
}, [append]),
|
|
121
|
-
remove
|
|
122
|
-
|
|
123
|
-
}, [remove]),
|
|
124
|
-
move: React$1.useCallback((from, to) => {
|
|
125
|
-
move(from, to);
|
|
126
|
-
}, [move])
|
|
211
|
+
remove,
|
|
212
|
+
move
|
|
127
213
|
};
|
|
128
214
|
}
|
|
129
215
|
/**
|
|
@@ -132,9 +218,12 @@ function useRHFFieldArrayHook(name) {
|
|
|
132
218
|
*/
|
|
133
219
|
function RHFFormProviderWrapper({ instance, children }) {
|
|
134
220
|
const form = instance.raw;
|
|
135
|
-
return /* @__PURE__ */ jsx(
|
|
136
|
-
|
|
137
|
-
children
|
|
221
|
+
return /* @__PURE__ */ jsx(RHFFormIdContext, {
|
|
222
|
+
value: instance.id,
|
|
223
|
+
children: /* @__PURE__ */ jsx(FormProvider, {
|
|
224
|
+
...form,
|
|
225
|
+
children
|
|
226
|
+
})
|
|
138
227
|
});
|
|
139
228
|
}
|
|
140
229
|
/**
|
package/dist/form/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { n as useAdapter, t as FormAdapterProvider } from "../adapter-context-
|
|
3
|
-
export { Form, FormAdapterProvider, FormAutocomplete, FormButton, FormCheckbox, FormCopyBox, FormCustom, FormDescription, FormDialog, FormError, FormField, FormFieldArray, FormInput, FormRadioGroup, FormRadioItem, FormRoot, FormSelect, FormSelectItem,
|
|
1
|
+
import { A as FormCheckbox, C as FormDialog, D as FormCustom, E as FormDatePicker, M as FormAutosearch, N as FormAutocomplete, O as FormCopyBox, S as FormError, T as FormDateTimePicker, _ as FormRadioGroup, a as useField, b as FormFieldArray, c as useWatchAll, d as FormTextarea, f as FormSwitch, g as FormRoot, h as FormSelectItem, i as useFieldContext, j as FormButton, k as FormCombobox, l as FormTransfer, m as FormSelect, n as useFormState, o as FormWhen, p as FormSubmit, r as useFormContext, s as useWatch, t as Form, u as FormTimePicker, v as FormRadioItem, w as FormDescription, x as FormField, y as FormInput } from "../form-B3rQ4CH9.mjs";
|
|
2
|
+
import { n as useAdapter, t as FormAdapterProvider } from "../adapter-context-rWveHhDd.mjs";
|
|
3
|
+
export { Form, FormAdapterProvider, FormAutocomplete, FormAutosearch, FormButton, FormCheckbox, FormCombobox, FormCopyBox, FormCustom, FormDatePicker, FormDateTimePicker, FormDescription, FormDialog, FormError, FormField, FormFieldArray, FormInput, FormRadioGroup, FormRadioItem, FormRoot, FormSelect, FormSelectItem, FormSubmit, FormSwitch, FormTextarea, FormTimePicker, FormTransfer, FormWhen, useAdapter, useField, useFieldContext, useFormContext, useFormState, useWatch, useWatchAll };
|