@godxjp/ui 20.0.0 → 20.2.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 (273) hide show
  1. package/README.md +8 -2
  2. package/dist/app/app-provider.js +37 -21
  3. package/dist/app/index.d.ts +2 -0
  4. package/dist/app/index.js +2 -0
  5. package/dist/app/storage.d.ts +17 -0
  6. package/dist/app/storage.js +28 -0
  7. package/dist/app/theme-axes.d.ts +2 -0
  8. package/dist/app/theme-axes.js +45 -0
  9. package/dist/components/charts/chart-cartesian.d.ts +2 -1
  10. package/dist/components/charts/chart-cartesian.js +52 -3
  11. package/dist/components/charts/chart-category-axis.d.ts +55 -0
  12. package/dist/components/charts/chart-category-axis.js +93 -0
  13. package/dist/components/charts/chart-frame.d.ts +10 -1
  14. package/dist/components/charts/chart-frame.js +19 -3
  15. package/dist/components/charts/compact-bar-trend.d.ts +1 -1
  16. package/dist/components/charts/compact-bar-trend.js +2 -0
  17. package/dist/components/charts/pie-chart.d.ts +1 -1
  18. package/dist/components/charts/pie-chart.js +12 -1
  19. package/dist/components/charts/recharts-peer.d.ts +49 -0
  20. package/dist/components/charts/recharts-peer.js +45 -0
  21. package/dist/components/data-display/card.d.ts +12 -1
  22. package/dist/components/data-display/card.js +22 -4
  23. package/dist/components/data-display/code-block.js +6 -2
  24. package/dist/components/data-display/data-table.d.ts +39 -4
  25. package/dist/components/data-display/data-table.js +802 -270
  26. package/dist/components/data-display/descriptions.d.ts +27 -6
  27. package/dist/components/data-display/descriptions.js +58 -17
  28. package/dist/components/data-display/index.d.ts +2 -0
  29. package/dist/components/data-display/index.js +2 -0
  30. package/dist/components/data-display/list-row.d.ts +10 -1
  31. package/dist/components/data-display/list-row.js +7 -1
  32. package/dist/components/data-display/popover.d.ts +9 -3
  33. package/dist/components/data-display/popover.js +65 -11
  34. package/dist/components/data-display/range-timeline.d.ts +38 -0
  35. package/dist/components/data-display/range-timeline.js +161 -0
  36. package/dist/components/data-display/scroll-area.js +13 -2
  37. package/dist/components/data-display/service-launcher-card.js +14 -12
  38. package/dist/components/data-display/table.d.ts +71 -3
  39. package/dist/components/data-display/table.js +56 -18
  40. package/dist/components/data-display/tree-list.js +4 -9
  41. package/dist/components/data-entry/calendar.d.ts +2 -2
  42. package/dist/components/data-entry/calendar.js +23 -3
  43. package/dist/components/data-entry/cascader.d.ts +1 -1
  44. package/dist/components/data-entry/cascader.js +188 -24
  45. package/dist/components/data-entry/color-picker.d.ts +1 -1
  46. package/dist/components/data-entry/color-picker.js +17 -5
  47. package/dist/components/data-entry/control-appearance.d.ts +64 -0
  48. package/dist/components/data-entry/control-appearance.js +39 -0
  49. package/dist/components/data-entry/control-surface.d.ts +61 -0
  50. package/dist/components/data-entry/control-surface.js +39 -0
  51. package/dist/components/data-entry/date-picker.d.ts +1 -1
  52. package/dist/components/data-entry/date-picker.js +344 -113
  53. package/dist/components/data-entry/date-range-picker.d.ts +1 -1
  54. package/dist/components/data-entry/date-range-picker.js +278 -140
  55. package/dist/components/data-entry/form-field.d.ts +1 -1
  56. package/dist/components/data-entry/form-field.js +39 -4
  57. package/dist/components/data-entry/form.d.ts +4 -0
  58. package/dist/components/data-entry/form.js +4 -2
  59. package/dist/components/data-entry/index.d.ts +7 -3
  60. package/dist/components/data-entry/index.js +10 -1
  61. package/dist/components/data-entry/input-otp.d.ts +1 -1
  62. package/dist/components/data-entry/input.d.ts +12 -34
  63. package/dist/components/data-entry/input.js +92 -24
  64. package/dist/components/data-entry/month-picker.d.ts +1 -1
  65. package/dist/components/data-entry/month-picker.js +47 -10
  66. package/dist/components/data-entry/month-range-picker.d.ts +1 -1
  67. package/dist/components/data-entry/month-range-picker.js +51 -11
  68. package/dist/components/data-entry/number-input.d.ts +7 -0
  69. package/dist/components/data-entry/number-input.js +147 -98
  70. package/dist/components/data-entry/password-input.d.ts +1 -1
  71. package/dist/components/data-entry/radio.d.ts +1 -1
  72. package/dist/components/data-entry/radio.js +61 -13
  73. package/dist/components/data-entry/search-input.d.ts +1 -1
  74. package/dist/components/data-entry/search-input.js +16 -2
  75. package/dist/components/data-entry/search-select.d.ts +2 -2
  76. package/dist/components/data-entry/search-select.js +209 -78
  77. package/dist/components/data-entry/select.d.ts +22 -4
  78. package/dist/components/data-entry/select.js +215 -163
  79. package/dist/components/data-entry/slider.d.ts +9 -1
  80. package/dist/components/data-entry/slider.js +87 -9
  81. package/dist/components/data-entry/switch.d.ts +3 -0
  82. package/dist/components/data-entry/switch.js +29 -3
  83. package/dist/components/data-entry/textarea.d.ts +14 -56
  84. package/dist/components/data-entry/textarea.js +80 -33
  85. package/dist/components/data-entry/time-picker.d.ts +2 -2
  86. package/dist/components/data-entry/time-picker.js +333 -109
  87. package/dist/components/data-entry/time-range-picker.d.ts +5 -0
  88. package/dist/components/data-entry/time-range-picker.js +89 -0
  89. package/dist/components/data-entry/transfer.d.ts +1 -1
  90. package/dist/components/data-entry/transfer.js +86 -28
  91. package/dist/components/data-entry/tree-select.d.ts +1 -1
  92. package/dist/components/data-entry/tree-select.js +201 -113
  93. package/dist/components/data-entry/tree-utils.js +7 -14
  94. package/dist/components/data-entry/upload-files.d.ts +2 -0
  95. package/dist/components/data-entry/upload-files.js +31 -0
  96. package/dist/components/data-entry/upload-request.d.ts +4 -0
  97. package/dist/components/data-entry/upload-request.js +53 -0
  98. package/dist/components/data-entry/upload-types.d.ts +28 -0
  99. package/dist/components/data-entry/upload-types.js +2 -0
  100. package/dist/components/data-entry/upload.d.ts +2 -2
  101. package/dist/components/data-entry/upload.js +475 -115
  102. package/dist/components/feedback/dialog.js +19 -19
  103. package/dist/components/feedback/sheet.js +3 -0
  104. package/dist/components/feedback/tooltip.js +3 -0
  105. package/dist/components/general/button.d.ts +2 -1
  106. package/dist/components/general/button.js +7 -1
  107. package/dist/components/general/index.d.ts +1 -0
  108. package/dist/components/general/index.js +2 -0
  109. package/dist/components/general/typography.d.ts +3 -0
  110. package/dist/components/general/typography.js +15 -1
  111. package/dist/components/general/visually-hidden.d.ts +3 -0
  112. package/dist/components/general/visually-hidden.js +9 -0
  113. package/dist/components/layout/app-launcher.d.ts +34 -0
  114. package/dist/components/layout/app-launcher.js +261 -0
  115. package/dist/components/layout/app-shell.d.ts +3 -1
  116. package/dist/components/layout/app-shell.js +34 -14
  117. package/dist/components/layout/auth-shell.d.ts +1 -1
  118. package/dist/components/layout/auth-shell.js +2 -0
  119. package/dist/components/layout/breadcrumb.d.ts +14 -2
  120. package/dist/components/layout/breadcrumb.js +46 -4
  121. package/dist/components/layout/flex.d.ts +1 -1
  122. package/dist/components/layout/flex.js +23 -2
  123. package/dist/components/layout/index.d.ts +3 -0
  124. package/dist/components/layout/index.js +5 -1
  125. package/dist/components/layout/org-switcher.js +20 -1
  126. package/dist/components/layout/page-container.d.ts +1 -1
  127. package/dist/components/layout/page-container.js +5 -3
  128. package/dist/components/layout/responsive-grid.d.ts +16 -2
  129. package/dist/components/layout/responsive-grid.js +29 -2
  130. package/dist/components/layout/sidebar.js +2 -1
  131. package/dist/components/layout/topbar-item.d.ts +3 -0
  132. package/dist/components/layout/topbar-item.js +18 -3
  133. package/dist/components/layout/topbar.d.ts +1 -1
  134. package/dist/components/layout/topbar.js +27 -6
  135. package/dist/components/navigation/dropdown-menu.d.ts +30 -2
  136. package/dist/components/navigation/dropdown-menu.js +73 -11
  137. package/dist/components/navigation/pagination.d.ts +2 -2
  138. package/dist/components/navigation/pagination.js +155 -83
  139. package/dist/components/navigation/steps.d.ts +2 -2
  140. package/dist/components/navigation/steps.js +29 -4
  141. package/dist/components/navigation/tabs.d.ts +5 -33
  142. package/dist/components/navigation/tabs.js +232 -64
  143. package/dist/components/ui/hover-card.js +3 -0
  144. package/dist/components/ui/input-otp.d.ts +26 -28
  145. package/dist/components/ui/input-otp.js +50 -7
  146. package/dist/components/ui/password-input.d.ts +36 -2
  147. package/dist/components/ui/password-input.js +27 -7
  148. package/dist/components/ui/rating.d.ts +25 -0
  149. package/dist/components/ui/rating.js +67 -27
  150. package/dist/components/ui/segmented.d.ts +9 -0
  151. package/dist/components/ui/segmented.js +17 -2
  152. package/dist/components/ui/separator.d.ts +4 -0
  153. package/dist/components/ui/separator.js +13 -0
  154. package/dist/components/ui/tag-input.d.ts +45 -0
  155. package/dist/components/ui/tag-input.js +109 -27
  156. package/dist/form/form-context.d.ts +29 -0
  157. package/dist/form/form-context.js +44 -4
  158. package/dist/form/form-field-array.d.ts +22 -0
  159. package/dist/form/form-field-array.js +50 -0
  160. package/dist/form/form-field-control.d.ts +2 -1
  161. package/dist/form/form-field-control.js +116 -40
  162. package/dist/form/form-root.d.ts +2 -1
  163. package/dist/form/form-root.js +142 -23
  164. package/dist/form/index.d.ts +3 -1
  165. package/dist/form/index.js +12 -1
  166. package/dist/i18n/index.d.ts +1 -1
  167. package/dist/i18n/index.js +3 -1
  168. package/dist/i18n/messages/en.json +59 -6
  169. package/dist/i18n/messages/ja.json +59 -6
  170. package/dist/i18n/messages/vi.json +59 -6
  171. package/dist/i18n/translate.d.ts +19 -0
  172. package/dist/i18n/translate.js +24 -0
  173. package/dist/inertia/index.d.ts +20 -0
  174. package/dist/inertia/index.js +60 -1
  175. package/dist/lib/control-styles.d.ts +25 -3
  176. package/dist/lib/control-styles.js +9 -3
  177. package/dist/lib/datetime/picker-format.d.ts +8 -0
  178. package/dist/lib/datetime/picker-format.js +54 -0
  179. package/dist/lib/overlay-portal.d.ts +18 -0
  180. package/dist/lib/overlay-portal.js +17 -0
  181. package/dist/props/components/app.prop.d.ts +13 -2
  182. package/dist/props/components/charts.prop.d.ts +21 -0
  183. package/dist/props/components/data-display.prop.d.ts +55 -4
  184. package/dist/props/components/data-entry.prop.d.ts +687 -43
  185. package/dist/props/components/form.prop.d.ts +142 -4
  186. package/dist/props/components/general.prop.d.ts +30 -1
  187. package/dist/props/components/index.d.ts +2 -2
  188. package/dist/props/components/layout.prop.d.ts +255 -8
  189. package/dist/props/components/navigation.prop.d.ts +113 -3
  190. package/dist/props/registry.d.ts +376 -10
  191. package/dist/props/registry.js +493 -9
  192. package/dist/props/vocabulary/data.prop.d.ts +179 -1
  193. package/dist/props/vocabulary/index.d.ts +4 -4
  194. package/dist/props/vocabulary/interaction.prop.d.ts +51 -2
  195. package/dist/props/vocabulary/navigation.prop.d.ts +40 -0
  196. package/dist/props/vocabulary/shared.prop.d.ts +33 -0
  197. package/dist/styles/badge-layout.css +2 -1
  198. package/dist/styles/card-layout.css +36 -5
  199. package/dist/styles/chart-layout.css +17 -1
  200. package/dist/styles/control.css +520 -14
  201. package/dist/styles/data-display-layout.css +248 -7
  202. package/dist/styles/data-entry-layout.css +13 -0
  203. package/dist/styles/layout.css +221 -4
  204. package/dist/styles/navigation-layout.css +230 -1
  205. package/dist/styles/shell-layout.css +690 -21
  206. package/dist/styles/table-layout.css +95 -6
  207. package/dist/styles/text-layout.css +5 -0
  208. package/dist/tokens/base.css +1 -0
  209. package/dist/tokens/components/badge.css +1 -0
  210. package/dist/tokens/components/chart.css +6 -0
  211. package/dist/tokens/components/control.css +81 -3
  212. package/dist/tokens/components/data-display.css +9 -0
  213. package/dist/tokens/components/descriptions.css +11 -0
  214. package/dist/tokens/components/flex.css +8 -0
  215. package/dist/tokens/components/navigation.css +55 -0
  216. package/dist/tokens/components/shell.css +100 -5
  217. package/dist/tokens/components/table.css +14 -0
  218. package/dist/tokens/foundation.css +2 -0
  219. package/dist/tokens/semantic/layout.css +9 -0
  220. package/docs/CONSUMER-RULES.md +13 -0
  221. package/docs/DESIGN-AUTHORITY.md +43 -6
  222. package/docs/DEVELOPMENT.md +4 -3
  223. package/docs/FORMS.md +151 -6
  224. package/docs/FRAME-COVERAGE-REPORT.md +29 -16
  225. package/docs/README.md +1 -1
  226. package/docs/STANDARDS-vocabulary-tokens.md +1 -1
  227. package/docs/TESTING.md +15 -6
  228. package/docs/charts/cjk-category-axis.tsx +81 -0
  229. package/docs/data-display/card/index.tsx +22 -0
  230. package/docs/data-display/data-table/examples/antd-parity.tsx +257 -0
  231. package/docs/data-display/data-table/index.tsx +23 -0
  232. package/docs/data-display/descriptions.tsx +41 -0
  233. package/docs/data-display/list-row.tsx +6 -6
  234. package/docs/data-display/scroll-area.tsx +31 -25
  235. package/docs/data-display/table.tsx +104 -45
  236. package/docs/data-display/timeline.tsx +41 -0
  237. package/docs/data-entry/date-picker.tsx +85 -0
  238. package/docs/data-entry/date-range-picker.tsx +26 -0
  239. package/docs/data-entry/form-dynamic-fields.tsx +199 -0
  240. package/docs/data-entry/form.tsx +74 -4
  241. package/docs/data-entry/input-otp.tsx +24 -0
  242. package/docs/data-entry/input.tsx +46 -1
  243. package/docs/data-entry/month-range-picker.tsx +1 -1
  244. package/docs/data-entry/select.tsx +27 -0
  245. package/docs/data-entry/switch.tsx +41 -0
  246. package/docs/data-entry/textarea.tsx +38 -0
  247. package/docs/data-entry/time-picker.tsx +85 -1
  248. package/docs/data-entry/time-range-picker.tsx +55 -0
  249. package/docs/data-entry/transfer.tsx +17 -0
  250. package/docs/data-entry/upload.tsx +56 -12
  251. package/docs/feedback/tooltip.tsx +1 -1
  252. package/docs/general/activity.tsx +2 -2
  253. package/docs/general/button/index.tsx +14 -1
  254. package/docs/general/typography.tsx +14 -1
  255. package/docs/layout/app-launcher.tsx +195 -0
  256. package/docs/layout/app-shell-arrangements.tsx +5 -5
  257. package/docs/layout/app-shell.tsx +11 -0
  258. package/docs/layout/flex.tsx +50 -0
  259. package/docs/layout/responsive-grid.tsx +21 -1
  260. package/docs/layout/topbar.tsx +5 -9
  261. package/docs/navigation/app-setting-picker.tsx +11 -0
  262. package/docs/navigation/breadcrumb.tsx +48 -0
  263. package/docs/navigation/dropdown-menu.tsx +21 -0
  264. package/docs/navigation/pagination.tsx +52 -0
  265. package/docs/navigation/steps.tsx +37 -0
  266. package/docs/navigation/tabs.tsx +97 -1
  267. package/docs/query/button-refetch.tsx +1 -0
  268. package/docs/showcase/settings-security-mfa.tsx +3 -3
  269. package/package.json +24 -4
  270. package/scripts/_agent-setup.mjs +76 -9
  271. package/scripts/init-guinea-pig.mjs +26 -4
  272. package/scripts/ui-audit.mjs +243 -30
  273. package/scripts/visual-audit.mjs +45 -7
@@ -1,15 +1,50 @@
1
1
  "use client";
2
2
  import { createContext, useContext } from "react";
3
- import { useFormContext } from "react-hook-form";
3
+ import {
4
+ createFormControl,
5
+ useFormContext,
6
+ useWatch
7
+ } from "react-hook-form";
4
8
  const FormAdapterContext = createContext(null);
5
9
  function useFormAdapter() {
6
10
  return useContext(FormAdapterContext);
7
11
  }
12
+ const FormOptionsContext = createContext({ disabled: false });
13
+ function useFormDisabled() {
14
+ return useContext(FormOptionsContext).disabled;
15
+ }
8
16
  function useFormSubmitting() {
17
+ const { submitting } = useContext(FormOptionsContext);
18
+ const adapter = useContext(FormAdapterContext);
19
+ const rhf = useFormContext();
20
+ if (adapter) return Boolean(submitting || adapter.isSubmitting);
21
+ return Boolean(submitting || rhf?.formState?.isSubmitting);
22
+ }
23
+ function useFormInstance() {
24
+ const rhf = useFormContext();
25
+ if (!rhf) {
26
+ throw new Error(
27
+ "useFormInstance must be rendered inside <FormRoot form={\u2026}> (react-hook-form path). On the adapter path use useFormAdapter()."
28
+ );
29
+ }
30
+ return rhf;
31
+ }
32
+ let fallbackControl;
33
+ function getFallbackFormControl() {
34
+ fallbackControl ??= createFormControl().control;
35
+ return fallbackControl;
36
+ }
37
+ function useFormWatch(name) {
9
38
  const adapter = useContext(FormAdapterContext);
10
39
  const rhf = useFormContext();
11
- if (adapter) return adapter.isSubmitting;
12
- return rhf?.formState?.isSubmitting ?? false;
40
+ const control = rhf?.control ?? getFallbackFormControl();
41
+ const watched = useWatch({
42
+ control,
43
+ name,
44
+ disabled: !rhf
45
+ });
46
+ if (adapter) return name === void 0 ? adapter.getValues?.() : adapter.getValue(name);
47
+ return watched;
13
48
  }
14
49
  function extractControlValue(arg) {
15
50
  if (arg && typeof arg === "object" && "target" in arg) {
@@ -23,7 +58,12 @@ function extractControlValue(arg) {
23
58
  }
24
59
  export {
25
60
  FormAdapterContext,
61
+ FormOptionsContext,
26
62
  extractControlValue,
63
+ getFallbackFormControl,
27
64
  useFormAdapter,
28
- useFormSubmitting
65
+ useFormDisabled,
66
+ useFormInstance,
67
+ useFormSubmitting,
68
+ useFormWatch
29
69
  };
@@ -0,0 +1,22 @@
1
+ import * as React from "react";
2
+ import { type FieldArrayPath, type FieldValues } from "react-hook-form";
3
+ import type { FormFieldArrayProp } from "../props/components/form.prop.js";
4
+ export type { FormFieldArrayProp } from "../props/components/form.prop.js";
5
+ /**
6
+ * FormFieldArray — repeating fields (antd `Form.List`) on react-hook-form's `useFieldArray`, so
7
+ * rows keep their identity across insert/remove/move instead of being re-keyed by index.
8
+ *
9
+ * Each row hands back its dotted path prefix; build the child field name from it, which is what
10
+ * makes nested validation messages land on the right row:
11
+ *
12
+ * ```tsx
13
+ * <FormFieldArray<Values, "contacts"> name="contacts">
14
+ * {({ fields, append, remove }) => (…
15
+ * <FormFieldControl name={`${row.name}.email`} label={t("contact.email")}>…
16
+ * )}
17
+ * </FormFieldArray>
18
+ * ```
19
+ *
20
+ * react-hook-form path only — a server-driven `adapter` owns its own collection state.
21
+ */
22
+ export declare function FormFieldArray<TFieldValues extends FieldValues, TName extends FieldArrayPath<TFieldValues> = FieldArrayPath<TFieldValues>>({ name, children }: FormFieldArrayProp<TFieldValues, TName>): React.JSX.Element;
@@ -0,0 +1,50 @@
1
+ "use client";
2
+ import { Fragment, jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import {
5
+ get,
6
+ useFieldArray,
7
+ useFormContext,
8
+ useFormState
9
+ } from "react-hook-form";
10
+ import { getFallbackFormControl, useFormDisabled } from "./form-context.js";
11
+ function arrayErrorMessage(errors, name) {
12
+ const entry = get(errors, name);
13
+ const message = entry?.root?.message ?? entry?.message;
14
+ return typeof message === "string" && message ? message : void 0;
15
+ }
16
+ function FormFieldArray({ name, children }) {
17
+ const rhf = useFormContext();
18
+ const control = rhf?.control ?? getFallbackFormControl();
19
+ const { fields, append, prepend, insert, remove, move, swap, replace } = useFieldArray({ control, name });
20
+ const { errors } = useFormState({ control, name });
21
+ const disabled = useFormDisabled();
22
+ const rows = React.useMemo(
23
+ () => fields.map((row, index) => ({
24
+ key: row.id,
25
+ name: `${String(name)}.${index}`,
26
+ index
27
+ })),
28
+ [fields, name]
29
+ );
30
+ if (!rhf) {
31
+ throw new Error(
32
+ "FormFieldArray must be rendered inside <FormRoot form={\u2026}> (react-hook-form path)."
33
+ );
34
+ }
35
+ return /* @__PURE__ */ jsx(Fragment, { children: children({
36
+ fields: rows,
37
+ append,
38
+ prepend,
39
+ insert,
40
+ remove,
41
+ move,
42
+ swap,
43
+ replace,
44
+ error: arrayErrorMessage(errors, String(name)),
45
+ disabled
46
+ }) });
47
+ }
48
+ export {
49
+ FormFieldArray
50
+ };
@@ -1,4 +1,5 @@
1
+ import * as React from "react";
1
2
  import { type FieldValues } from "react-hook-form";
2
3
  import type { FormFieldControlProp } from "../props/components/form.prop.js";
3
4
  export type { FormFieldControlProp } from "../props/components/form.prop.js";
4
- export declare function FormFieldControl<TFieldValues extends FieldValues>({ name, label, required, helper, className, children, }: FormFieldControlProp<TFieldValues>): import("react").JSX.Element;
5
+ export declare function FormFieldControl<TFieldValues extends FieldValues>({ name, id, field: fieldKey, labelAddon, layout, labelWidth, controlWidth, colSpan, label, required, helper, disabled, dependencies, getValueFromEvent, normalize, help, validateStatus, hasFeedback, feedback, preserve, className, children, }: FormFieldControlProp<TFieldValues>): React.JSX.Element;
@@ -1,70 +1,146 @@
1
1
  "use client";
2
- import { jsx } from "react/jsx-runtime";
3
- import { Controller, useFormContext } from "react-hook-form";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import {
5
+ Controller,
6
+ useFormContext,
7
+ useFormState,
8
+ useWatch
9
+ } from "react-hook-form";
4
10
  import { FormField } from "../components/data-entry/form-field.js";
5
- import { extractControlValue, useFormAdapter } from "./form-context.js";
11
+ import { extractControlValue, useFormAdapter, useFormDisabled } from "./form-context.js";
6
12
  const noopRef = () => {
7
13
  };
14
+ function bindField(field) {
15
+ return { ...field, onValueChange: field.onChange };
16
+ }
17
+ function FieldDependencies({
18
+ control,
19
+ name,
20
+ dependencies,
21
+ trigger
22
+ }) {
23
+ const values = useWatch({ control, name: dependencies });
24
+ const { isSubmitted } = useFormState({ control });
25
+ const signature = JSON.stringify(values ?? null);
26
+ React.useEffect(() => {
27
+ if (!isSubmitted) return;
28
+ void trigger(name);
29
+ }, [signature, isSubmitted, name, trigger]);
30
+ return null;
31
+ }
8
32
  function FormFieldControl({
9
33
  name,
34
+ id,
35
+ field: fieldKey,
36
+ labelAddon,
37
+ layout,
38
+ labelWidth,
39
+ controlWidth,
40
+ colSpan,
10
41
  label,
11
42
  required,
12
43
  helper,
44
+ disabled,
45
+ dependencies,
46
+ getValueFromEvent,
47
+ normalize,
48
+ help,
49
+ validateStatus,
50
+ hasFeedback,
51
+ feedback,
52
+ preserve,
13
53
  className,
14
54
  children
15
55
  }) {
56
+ const autoId = React.useId();
57
+ const resolvedId = id ?? `${autoId}-${String(name)}`;
58
+ const presentation = { field: fieldKey, labelAddon, layout, labelWidth, controlWidth, colSpan };
16
59
  const adapter = useFormAdapter();
17
60
  const rhf = useFormContext();
61
+ const formDisabled = useFormDisabled();
62
+ const isDisabled = disabled ?? formDisabled;
63
+ const disabledProp = isDisabled ? { disabled: true } : disabled === false ? { disabled: false } : void 0;
64
+ const readValue = (args, previousValue) => {
65
+ const raw = getValueFromEvent ? getValueFromEvent(...args) : extractControlValue(args[0]);
66
+ return normalize ? normalize(raw, previousValue) : raw;
67
+ };
18
68
  if (adapter) {
19
69
  const fieldName = String(name);
20
70
  return /* @__PURE__ */ jsx(
21
71
  FormField,
22
72
  {
23
- id: fieldName,
73
+ ...presentation,
74
+ id: resolvedId,
24
75
  name: fieldName,
25
76
  label,
26
77
  required,
27
78
  helper,
28
- error: adapter.getError(fieldName),
79
+ error: help ?? adapter.getError(fieldName),
80
+ validateStatus,
81
+ hasFeedback,
82
+ feedback,
29
83
  className,
30
- children: children({
31
- id: fieldName,
32
- name: fieldName,
33
- value: adapter.getValue(fieldName),
34
- onChange: (...args) => adapter.setValue(fieldName, extractControlValue(args[0])),
35
- onBlur: () => adapter.onBlur?.(fieldName),
36
- ref: noopRef
37
- })
84
+ children: children(
85
+ bindField({
86
+ id: resolvedId,
87
+ name: fieldName,
88
+ value: adapter.getValue(fieldName),
89
+ onChange: (...args) => adapter.setValue(fieldName, readValue(args, adapter.getValue(fieldName))),
90
+ onBlur: () => adapter.onBlur?.(fieldName),
91
+ ref: noopRef,
92
+ ...disabledProp
93
+ })
94
+ )
38
95
  }
39
96
  );
40
97
  }
41
- return /* @__PURE__ */ jsx(
42
- Controller,
43
- {
44
- name,
45
- control: rhf.control,
46
- render: ({ field, fieldState }) => /* @__PURE__ */ jsx(
47
- FormField,
48
- {
49
- id: String(name),
50
- name: String(name),
51
- label,
52
- required,
53
- helper,
54
- error: fieldState.error?.message,
55
- className,
56
- children: children({
57
- id: String(name),
58
- name: field.name,
59
- value: field.value,
60
- onChange: field.onChange,
61
- onBlur: field.onBlur,
62
- ref: field.ref
63
- })
64
- }
65
- )
66
- }
67
- );
98
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
99
+ dependencies?.length ? /* @__PURE__ */ jsx(
100
+ FieldDependencies,
101
+ {
102
+ control: rhf.control,
103
+ name,
104
+ dependencies,
105
+ trigger: rhf.trigger
106
+ }
107
+ ) : null,
108
+ /* @__PURE__ */ jsx(
109
+ Controller,
110
+ {
111
+ name,
112
+ control: rhf.control,
113
+ shouldUnregister: preserve === false,
114
+ render: ({ field, fieldState }) => /* @__PURE__ */ jsx(
115
+ FormField,
116
+ {
117
+ ...presentation,
118
+ id: resolvedId,
119
+ name: String(name),
120
+ label,
121
+ required,
122
+ helper,
123
+ error: help ?? fieldState.error?.message,
124
+ validateStatus,
125
+ hasFeedback,
126
+ feedback,
127
+ className,
128
+ children: children(
129
+ bindField({
130
+ id: resolvedId,
131
+ name: field.name,
132
+ value: field.value,
133
+ onChange: (...args) => field.onChange(readValue(args, field.value)),
134
+ onBlur: field.onBlur,
135
+ ref: field.ref,
136
+ ...disabledProp
137
+ })
138
+ )
139
+ }
140
+ )
141
+ }
142
+ )
143
+ ] });
68
144
  }
69
145
  export {
70
146
  FormFieldControl
@@ -1,3 +1,4 @@
1
+ import * as React from "react";
1
2
  import { type FieldValues } from "react-hook-form";
2
3
  import type { FormRootProp } from "../props/components/form.prop.js";
3
4
  export type { FormRootProp } from "../props/components/form.prop.js";
@@ -6,4 +7,4 @@ export type { FormRootProp } from "../props/components/form.prop.js";
6
7
  * validation) OR `adapter` (a framework-agnostic {@link FormStateAdapter} for a server-driven form
7
8
  * library — Inertia's `useForm` via `@godxjp/ui/inertia`, formik, TanStack Form).
8
9
  */
9
- export declare function FormRoot<TFieldValues extends FieldValues>({ form, adapter, onSubmit, children, className, id, }: FormRootProp<TFieldValues>): import("react").JSX.Element;
10
+ export declare function FormRoot<TFieldValues extends FieldValues>({ form, adapter, onSubmit, onSubmitFailed, onSubmitError, onReset, scrollToFirstError, disabled, layout, labelWidth, controlWidth, labelAlign, collapseBelow, density, columns, requiredMark, errors, children, className, id, }: FormRootProp<TFieldValues>): React.JSX.Element;
@@ -1,46 +1,165 @@
1
1
  "use client";
2
- import { jsx } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
3
4
  import { FormProvider } from "react-hook-form";
5
+ import { Alert, AlertDescription } from "../components/feedback/alert.js";
6
+ import { ResponsiveGrid } from "../components/layout/responsive-grid.js";
7
+ import { useTranslation } from "../i18n/use-translation.js";
4
8
  import { cn } from "../lib/utils.js";
5
- import { FormAdapterContext } from "./form-context.js";
9
+ import { Form } from "../components/data-entry/form.js";
10
+ import { FormAdapterContext, FormOptionsContext } from "./form-context.js";
11
+ function collectErrorNames(errors, prefix = "", out = []) {
12
+ if (!errors || typeof errors !== "object") return out;
13
+ for (const [key, value] of Object.entries(errors)) {
14
+ if (!value || typeof value !== "object") continue;
15
+ const path = prefix ? `${prefix}.${key}` : key;
16
+ if ("message" in value || "type" in value) out.push(path);
17
+ else collectErrorNames(value, path, out);
18
+ }
19
+ return out;
20
+ }
21
+ function scrollFirstErrorIntoView(formEl, errors) {
22
+ if (!formEl) return;
23
+ const names = new Set(collectErrorNames(errors));
24
+ if (names.size === 0) return;
25
+ const control = Array.from(formEl.querySelectorAll("[data-field]")).find(
26
+ (el) => names.has(el.dataset.field ?? "")
27
+ );
28
+ if (!control) return;
29
+ const focusTarget = control.matches("input, select, textarea, button, [tabindex]") ? control : control.querySelector("input, select, textarea, button, [tabindex]");
30
+ focusTarget?.focus({ preventScroll: true });
31
+ const target = control.closest('[data-slot="form-field"]') ?? control;
32
+ if (typeof target.scrollIntoView !== "function") return;
33
+ const reduceMotion = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
34
+ target.scrollIntoView({ block: "center", behavior: reduceMotion ? "auto" : "smooth" });
35
+ }
36
+ function afterPaint(run) {
37
+ if (typeof requestAnimationFrame === "function") requestAnimationFrame(() => run());
38
+ else setTimeout(run, 0);
39
+ }
6
40
  function FormRoot({
7
41
  form,
8
42
  adapter,
9
43
  onSubmit,
44
+ onSubmitFailed,
45
+ onSubmitError,
46
+ onReset,
47
+ scrollToFirstError = true,
48
+ disabled = false,
49
+ layout,
50
+ labelWidth,
51
+ controlWidth,
52
+ labelAlign,
53
+ collapseBelow,
54
+ density,
55
+ columns,
56
+ requiredMark,
57
+ errors,
10
58
  children,
11
59
  className,
12
60
  id
13
61
  }) {
62
+ const formRef = React.useRef(null);
63
+ const { t } = useTranslation();
64
+ const pending = React.useRef(false);
65
+ const [submitting, setSubmitting] = React.useState(false);
66
+ const [submitFailed, setSubmitFailed] = React.useState(false);
67
+ const options = React.useMemo(() => ({ disabled, submitting }), [disabled, submitting]);
68
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
69
+ submitFailed && /* @__PURE__ */ jsx(Alert, { tone: "destructive", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("dataEntry.form.submitFailed") }) }),
70
+ columns ? /* @__PURE__ */ jsx(ResponsiveGrid, { columns, children }) : children
71
+ ] });
72
+ const submit = async (work) => {
73
+ if (disabled || pending.current || adapter?.isSubmitting) return;
74
+ pending.current = true;
75
+ setSubmitting(true);
76
+ setSubmitFailed(false);
77
+ try {
78
+ await work();
79
+ } catch (error) {
80
+ setSubmitFailed(true);
81
+ onSubmitError?.(error);
82
+ } finally {
83
+ pending.current = false;
84
+ setSubmitting(false);
85
+ }
86
+ };
87
+ const layoutShell = layout !== void 0 || labelWidth !== void 0 || controlWidth !== void 0 || labelAlign !== void 0 || collapseBelow !== void 0 || density !== void 0 || requiredMark !== void 0 || errors !== void 0;
88
+ const formClassName = layoutShell ? className : cn("ui-stack-md", className);
89
+ const withLayout = (node) => layoutShell ? /* @__PURE__ */ jsx(
90
+ Form,
91
+ {
92
+ asChild: true,
93
+ layout,
94
+ labelWidth,
95
+ controlWidth,
96
+ labelAlign,
97
+ collapseBelow,
98
+ density,
99
+ requiredMark,
100
+ disabled,
101
+ errors,
102
+ children: node
103
+ }
104
+ ) : node;
14
105
  if (adapter) {
15
- return /* @__PURE__ */ jsx(FormAdapterContext.Provider, { value: adapter, children: /* @__PURE__ */ jsx(
106
+ return /* @__PURE__ */ jsx(FormAdapterContext.Provider, { value: adapter, children: /* @__PURE__ */ jsx(FormOptionsContext.Provider, { value: options, children: withLayout(
107
+ /* @__PURE__ */ jsx(
108
+ "form",
109
+ {
110
+ ref: formRef,
111
+ id,
112
+ className: formClassName,
113
+ onSubmit: (event) => {
114
+ event.preventDefault();
115
+ void submit(() => onSubmit(adapter.getValues?.() ?? {}));
116
+ },
117
+ onReset: (event) => {
118
+ event.preventDefault();
119
+ if (pending.current) return;
120
+ setSubmitFailed(false);
121
+ adapter.reset?.();
122
+ onReset?.();
123
+ },
124
+ noValidate: true,
125
+ children: content
126
+ }
127
+ )
128
+ ) }) });
129
+ }
130
+ if (!form) {
131
+ throw new Error("FormRoot requires either a `form` (react-hook-form) or an `adapter` prop.");
132
+ }
133
+ const handleInvalid = (fieldErrors) => {
134
+ onSubmitFailed?.(fieldErrors);
135
+ if (scrollToFirstError)
136
+ afterPaint(() => scrollFirstErrorIntoView(formRef.current, fieldErrors));
137
+ };
138
+ return /* @__PURE__ */ jsx(FormProvider, { ...form, children: /* @__PURE__ */ jsx(FormOptionsContext.Provider, { value: options, children: withLayout(
139
+ /* @__PURE__ */ jsx(
16
140
  "form",
17
141
  {
142
+ ref: formRef,
18
143
  id,
19
- className: cn("ui-stack-md", className),
144
+ className: formClassName,
20
145
  onSubmit: (event) => {
21
146
  event.preventDefault();
22
- void onSubmit(adapter.getValues?.() ?? {});
147
+ void submit(
148
+ () => form.handleSubmit((values) => onSubmit(values), handleInvalid)(event)
149
+ );
150
+ },
151
+ onReset: (event) => {
152
+ event.preventDefault();
153
+ if (pending.current) return;
154
+ setSubmitFailed(false);
155
+ form.reset();
156
+ onReset?.();
23
157
  },
24
158
  noValidate: true,
25
- children
159
+ children: content
26
160
  }
27
- ) });
28
- }
29
- if (!form) {
30
- throw new Error("FormRoot requires either a `form` (react-hook-form) or an `adapter` prop.");
31
- }
32
- return /* @__PURE__ */ jsx(FormProvider, { ...form, children: /* @__PURE__ */ jsx(
33
- "form",
34
- {
35
- id,
36
- className: cn("ui-stack-md", className),
37
- onSubmit: (event) => {
38
- void form.handleSubmit((values) => onSubmit(values))(event);
39
- },
40
- noValidate: true,
41
- children
42
- }
43
- ) });
161
+ )
162
+ ) }) });
44
163
  }
45
164
  export {
46
165
  FormRoot
@@ -4,5 +4,7 @@ export { FormRoot } from "./form-root.js";
4
4
  export type { FormRootProp } from "./form-root.js";
5
5
  export { FormFieldControl } from "./form-field-control.js";
6
6
  export type { FormFieldControlProp } from "./form-field-control.js";
7
- export { useFormSubmitting, useFormAdapter } from "./form-context.js";
7
+ export { FormFieldArray } from "./form-field-array.js";
8
+ export type { FormFieldArrayProp } from "./form-field-array.js";
9
+ export { useFormSubmitting, useFormAdapter, useFormDisabled, useFormInstance, useFormWatch, } from "./form-context.js";
8
10
  export type { ZodSchemaProp, FieldErrorMessageProp, FormStateAdapter, } from "../props/components/form.prop.js";
@@ -1,11 +1,22 @@
1
1
  import { useZodForm } from "./use-zod-form.js";
2
2
  import { FormRoot } from "./form-root.js";
3
3
  import { FormFieldControl } from "./form-field-control.js";
4
- import { useFormSubmitting, useFormAdapter } from "./form-context.js";
4
+ import { FormFieldArray } from "./form-field-array.js";
5
+ import {
6
+ useFormSubmitting,
7
+ useFormAdapter,
8
+ useFormDisabled,
9
+ useFormInstance,
10
+ useFormWatch
11
+ } from "./form-context.js";
5
12
  export {
13
+ FormFieldArray,
6
14
  FormFieldControl,
7
15
  FormRoot,
8
16
  useFormAdapter,
17
+ useFormDisabled,
18
+ useFormInstance,
9
19
  useFormSubmitting,
20
+ useFormWatch,
10
21
  useZodForm
11
22
  };
@@ -1,3 +1,3 @@
1
- export { translate, translateCurrent, syncI18nLocale, resetI18nLocale, MESSAGE_CATALOG, } from "./translate.js";
1
+ export { translate, translateCurrent, syncI18nLocale, resetI18nLocale, MESSAGE_CATALOG, registerMessages, } from "./translate.js";
2
2
  export type { Messages, MessageKey, TranslateParams } from "./translate.js";
3
3
  export { useTranslation, usePickerLocales } from "./use-translation.js";
@@ -3,11 +3,13 @@ import {
3
3
  translateCurrent,
4
4
  syncI18nLocale,
5
5
  resetI18nLocale,
6
- MESSAGE_CATALOG
6
+ MESSAGE_CATALOG,
7
+ registerMessages
7
8
  } from "./translate.js";
8
9
  import { useTranslation, usePickerLocales } from "./use-translation.js";
9
10
  export {
10
11
  MESSAGE_CATALOG,
12
+ registerMessages,
11
13
  resetI18nLocale,
12
14
  syncI18nLocale,
13
15
  translate,