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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/README.md +4 -0
  2. package/dist/autocomplete/index.mjs +1 -1
  3. package/dist/{autocomplete-V5-qslzS.mjs → autocomplete-CkYJueBL.mjs} +2 -2
  4. package/dist/autosearch/index.mjs +199 -0
  5. package/dist/{calendar-date-picker-DWK94_DC.mjs → calendar-date-picker-CDT-8Ha8.mjs} +2 -1
  6. package/dist/combobox/index.mjs +2 -0
  7. package/dist/combobox-B-C9lJeD.mjs +97 -0
  8. package/dist/components/features/autocomplete/autocomplete.d.ts +1 -1
  9. package/dist/components/features/autocomplete/autocomplete.d.ts.map +1 -1
  10. package/dist/components/features/autocomplete/autocomplete.types.d.ts +2 -0
  11. package/dist/components/features/autocomplete/autocomplete.types.d.ts.map +1 -1
  12. package/dist/components/features/autosearch/autosearch.d.ts +35 -0
  13. package/dist/components/features/autosearch/autosearch.d.ts.map +1 -0
  14. package/dist/components/features/autosearch/autosearch.types.d.ts +51 -0
  15. package/dist/components/features/autosearch/autosearch.types.d.ts.map +1 -0
  16. package/dist/components/features/autosearch/index.d.ts +3 -0
  17. package/dist/components/features/autosearch/index.d.ts.map +1 -0
  18. package/dist/components/features/calendar-date-picker/calendar-date-picker.d.ts +2 -1
  19. package/dist/components/features/calendar-date-picker/calendar-date-picker.d.ts.map +1 -1
  20. package/dist/components/features/combobox/combobox.d.ts +27 -0
  21. package/dist/components/features/combobox/combobox.d.ts.map +1 -0
  22. package/dist/components/features/combobox/index.d.ts +3 -0
  23. package/dist/components/features/combobox/index.d.ts.map +1 -0
  24. package/dist/components/features/combobox/types.d.ts +84 -0
  25. package/dist/components/features/combobox/types.d.ts.map +1 -0
  26. package/dist/components/features/date-time-picker/date-time-picker.d.ts +9 -0
  27. package/dist/components/features/date-time-picker/date-time-picker.d.ts.map +1 -0
  28. package/dist/components/features/date-time-picker/index.d.ts +3 -0
  29. package/dist/components/features/date-time-picker/index.d.ts.map +1 -0
  30. package/dist/components/features/date-time-picker/types.d.ts +59 -0
  31. package/dist/components/features/date-time-picker/types.d.ts.map +1 -0
  32. package/dist/components/features/date-time-picker/utils/format.d.ts +13 -0
  33. package/dist/components/features/date-time-picker/utils/format.d.ts.map +1 -0
  34. package/dist/components/features/date-time-picker/utils/index.d.ts +3 -0
  35. package/dist/components/features/date-time-picker/utils/index.d.ts.map +1 -0
  36. package/dist/components/features/date-time-picker/utils/timezone.d.ts +23 -0
  37. package/dist/components/features/date-time-picker/utils/timezone.d.ts.map +1 -0
  38. package/dist/components/features/form/adapter-types.d.ts +26 -0
  39. package/dist/components/features/form/adapter-types.d.ts.map +1 -1
  40. package/dist/components/features/form/adapters/conform/conform-adapter.d.ts.map +1 -1
  41. package/dist/components/features/form/adapters/rhf/rhf-adapter.d.ts.map +1 -1
  42. package/dist/components/features/form/components/form-autocomplete.d.ts.map +1 -1
  43. package/dist/components/features/form/components/form-autosearch.d.ts +37 -0
  44. package/dist/components/features/form/components/form-autosearch.d.ts.map +1 -0
  45. package/dist/components/features/form/components/form-checkbox.d.ts.map +1 -1
  46. package/dist/components/features/form/components/form-combobox.d.ts +80 -0
  47. package/dist/components/features/form/components/form-combobox.d.ts.map +1 -0
  48. package/dist/components/features/form/components/form-copy-box.d.ts +3 -0
  49. package/dist/components/features/form/components/form-copy-box.d.ts.map +1 -1
  50. package/dist/components/features/form/components/form-custom.d.ts.map +1 -1
  51. package/dist/components/features/form/components/form-date-picker.d.ts +40 -0
  52. package/dist/components/features/form/components/form-date-picker.d.ts.map +1 -0
  53. package/dist/components/features/form/components/form-date-time-picker.d.ts +39 -0
  54. package/dist/components/features/form/components/form-date-time-picker.d.ts.map +1 -0
  55. package/dist/components/features/form/components/form-dialog.d.ts.map +1 -1
  56. package/dist/components/features/form/components/form-field.d.ts.map +1 -1
  57. package/dist/components/features/form/components/form-radio-group.d.ts.map +1 -1
  58. package/dist/components/features/form/components/form-root.d.ts.map +1 -1
  59. package/dist/components/features/form/components/form-switch.d.ts.map +1 -1
  60. package/dist/components/features/form/components/form-time-picker.d.ts +21 -0
  61. package/dist/components/features/form/components/form-time-picker.d.ts.map +1 -0
  62. package/dist/components/features/form/components/form-transfer.d.ts +37 -0
  63. package/dist/components/features/form/components/form-transfer.d.ts.map +1 -0
  64. package/dist/components/features/form/components/index.d.ts +7 -1
  65. package/dist/components/features/form/components/index.d.ts.map +1 -1
  66. package/dist/components/features/form/components/stepper/form-stepper.d.ts +3 -1
  67. package/dist/components/features/form/components/stepper/form-stepper.d.ts.map +1 -1
  68. package/dist/components/features/form/hooks/index.d.ts +1 -1
  69. package/dist/components/features/form/hooks/index.d.ts.map +1 -1
  70. package/dist/components/features/form/hooks/use-display-touched.d.ts +20 -0
  71. package/dist/components/features/form/hooks/use-display-touched.d.ts.map +1 -0
  72. package/dist/components/features/form/hooks/use-field.d.ts +4 -0
  73. package/dist/components/features/form/hooks/use-field.d.ts.map +1 -1
  74. package/dist/components/features/form/hooks/use-form-state.d.ts +36 -0
  75. package/dist/components/features/form/hooks/use-form-state.d.ts.map +1 -0
  76. package/dist/components/features/form/index.d.ts +39 -21
  77. package/dist/components/features/form/index.d.ts.map +1 -1
  78. package/dist/components/features/form/stepper/index.d.ts +17 -0
  79. package/dist/components/features/form/stepper/index.d.ts.map +1 -0
  80. package/dist/components/features/form/types/index.d.ts +46 -0
  81. package/dist/components/features/form/types/index.d.ts.map +1 -1
  82. package/dist/components/features/form/utils/get-field-constraints.d.ts +23 -1
  83. package/dist/components/features/form/utils/get-field-constraints.d.ts.map +1 -1
  84. package/dist/components/features/form/utils/get-schema-defaults.d.ts +24 -0
  85. package/dist/components/features/form/utils/get-schema-defaults.d.ts.map +1 -0
  86. package/dist/components/features/form/utils/zod-helpers.d.ts +12 -0
  87. package/dist/components/features/form/utils/zod-helpers.d.ts.map +1 -0
  88. package/dist/components/features/time-picker/index.d.ts +3 -0
  89. package/dist/components/features/time-picker/index.d.ts.map +1 -0
  90. package/dist/components/features/time-picker/time-picker.d.ts +22 -0
  91. package/dist/components/features/time-picker/time-picker.d.ts.map +1 -0
  92. package/dist/components/features/time-picker/types.d.ts +31 -0
  93. package/dist/components/features/time-picker/types.d.ts.map +1 -0
  94. package/dist/components/features/transfer/components/index.d.ts +9 -0
  95. package/dist/components/features/transfer/components/index.d.ts.map +1 -0
  96. package/dist/components/features/transfer/components/transfer-group.d.ts +7 -0
  97. package/dist/components/features/transfer/components/transfer-group.d.ts.map +1 -0
  98. package/dist/components/features/transfer/components/transfer-item.d.ts +10 -0
  99. package/dist/components/features/transfer/components/transfer-item.d.ts.map +1 -0
  100. package/dist/components/features/transfer/components/transfer-panel.d.ts +18 -0
  101. package/dist/components/features/transfer/components/transfer-panel.d.ts.map +1 -0
  102. package/dist/components/features/transfer/components/transfer-search.d.ts +9 -0
  103. package/dist/components/features/transfer/components/transfer-search.d.ts.map +1 -0
  104. package/dist/components/features/transfer/hooks/use-transfer-dnd.d.ts +26 -0
  105. package/dist/components/features/transfer/hooks/use-transfer-dnd.d.ts.map +1 -0
  106. package/dist/components/features/transfer/hooks/use-transfer-state.d.ts +20 -0
  107. package/dist/components/features/transfer/hooks/use-transfer-state.d.ts.map +1 -0
  108. package/dist/components/features/transfer/index.d.ts +3 -0
  109. package/dist/components/features/transfer/index.d.ts.map +1 -0
  110. package/dist/components/features/transfer/transfer.d.ts +6 -0
  111. package/dist/components/features/transfer/transfer.d.ts.map +1 -0
  112. package/dist/components/features/transfer/types.d.ts +69 -0
  113. package/dist/components/features/transfer/types.d.ts.map +1 -0
  114. package/dist/data-table/index.mjs +1 -1
  115. package/dist/date-picker/index.mjs +2 -2
  116. package/dist/date-time-picker/index.mjs +2 -0
  117. package/dist/date-time-picker-BomrW07W.mjs +178 -0
  118. package/dist/form/adapters/conform/index.mjs +110 -13
  119. package/dist/form/adapters/rhf/index.mjs +116 -27
  120. package/dist/form/index.mjs +3 -3
  121. package/dist/form/stepper/index.mjs +519 -0
  122. package/dist/{form-BE1xBne4.mjs → form-B3rQ4CH9.mjs} +447 -605
  123. package/dist/form-context-Ccxm-wqL.mjs +17 -0
  124. package/dist/grid/index.mjs +1 -1
  125. package/dist/hooks/index.mjs +2 -2
  126. package/dist/index.mjs +16 -16
  127. package/dist/input-number/index.mjs +1 -1
  128. package/dist/map/index.mjs +1 -1
  129. package/dist/{map-Cw7u8r6E.mjs → map-CWIQ-eql.mjs} +1 -1
  130. package/dist/more-actions/index.mjs +1 -1
  131. package/dist/page-title/index.mjs +1 -1
  132. package/dist/stepper/index.mjs +1 -320
  133. package/dist/stepper-DvIOp0hh.mjs +321 -0
  134. package/dist/tag-input/index.mjs +1 -1
  135. package/dist/task-queue/index.mjs +1 -1
  136. package/dist/time-picker/index.mjs +2 -0
  137. package/dist/time-picker-BoF7pZZ2.mjs +43 -0
  138. package/dist/transfer/index.mjs +2 -0
  139. package/dist/transfer-46C-rFFW.mjs +264 -0
  140. package/dist/{get-field-constraints-BPMW8VvY.mjs → use-display-touched-I39aXEBD.mjs} +51 -16
  141. package/package.json +42 -1
  142. /package/dist/{adapter-context-B7L2ucTr.mjs → adapter-context-rWveHhDd.mjs} +0 -0
  143. /package/dist/{col-YBbQ5wlb.mjs → col-1T0Q3SlH.mjs} +0 -0
  144. /package/dist/{hooks-DYjN7lvC.mjs → hooks-D8r2M2U6.mjs} +0 -0
  145. /package/dist/{input-number-DEjXG2I6.mjs → input-number-a7uydAsw.mjs} +0 -0
  146. /package/dist/{map-leaflet-imports-D6nTEOIh.mjs → map-leaflet-imports-CRSKA79m.mjs} +0 -0
  147. /package/dist/{more-actions-BNQ2yfWZ.mjs → more-actions-ILnEZq_E.mjs} +0 -0
  148. /package/dist/{page-title-CNiRNZ7p.mjs → page-title-ChsnpBiH.mjs} +0 -0
  149. /package/dist/{tag-input-BKed-cul.mjs → tag-input-T9cUX9-G.mjs} +0 -0
  150. /package/dist/{task-queue-dropdown-Di_Wjspz.mjs → task-queue-dropdown-Wcbj-f0V.mjs} +0 -0
  151. /package/dist/{to-api-format-Cq4prffn.mjs → to-api-format-Bh3c01gr.mjs} +0 -0
  152. /package/dist/{use-copy-to-clipboard-BGdTmkFV.mjs → use-copy-to-clipboard-uNeeVHC4.mjs} +0 -0
@@ -1,11 +1,17 @@
1
- import { t as FormAdapterProvider } from "../../../adapter-context-B7L2ucTr.mjs";
2
- import { t as getFieldConstraints } from "../../../get-field-constraints-BPMW8VvY.mjs";
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" || value === "true") return true;
39
- if (value === "" || value === "false") return false;
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
- formRef
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
- value: convertFromString(control.value),
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
- }), [fieldMeta, control]);
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, names]);
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-B7L2ucTr.mjs";
2
- import { t as getFieldConstraints } from "../../../get-field-constraints-BPMW8VvY.mjs";
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
- onBlur: "onBlur",
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
- form.formState.errors,
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: useFormContext().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
- }, [names, values]);
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: React$1.useCallback((index) => {
122
- remove(index);
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(FormProvider, {
136
- ...form,
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
  /**
@@ -1,3 +1,3 @@
1
- import { A as FormButton, C as FormField, D as FormCustom, E as FormDescription, O as FormCopyBox, S as FormFieldArray, T as FormDialog, _ as FormSelectItem, a as useField, b as FormRadioItem, c as FormStep, d as useWatch, f as useWatchAll, g as FormSelect, h as FormSubmit, i as useFieldContext, j as FormAutocomplete, k as FormCheckbox, l as FormStepper, m as FormSwitch, n as useStepper, o as StepperNavigation, p as FormTextarea, r as useFormContext, s as StepperControls, t as Form, u as FormWhen, v as FormRoot, w as FormError, x as FormInput, y as FormRadioGroup } from "../form-BE1xBne4.mjs";
2
- import { n as useAdapter, t as FormAdapterProvider } from "../adapter-context-B7L2ucTr.mjs";
3
- export { Form, FormAdapterProvider, FormAutocomplete, FormButton, FormCheckbox, FormCopyBox, FormCustom, FormDescription, FormDialog, FormError, FormField, FormFieldArray, FormInput, FormRadioGroup, FormRadioItem, FormRoot, FormSelect, FormSelectItem, FormStep, FormStepper, FormSubmit, FormSwitch, FormTextarea, FormWhen, StepperControls, StepperNavigation, useAdapter, useField, useFieldContext, useFormContext, useStepper, useWatch, useWatchAll };
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 };