ding-react-admin 1.0.4 → 2.0.4

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 (230) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +254 -201
  3. package/dist/index.css +2 -0
  4. package/dist/index.js +2073 -1202
  5. package/dist/src/components/NavMenuLabel.d.ts +8 -0
  6. package/dist/src/components/NavMenuLabel.d.ts.map +1 -0
  7. package/dist/src/components/NavMenuSearch.d.ts +9 -0
  8. package/dist/src/components/NavMenuSearch.d.ts.map +1 -0
  9. package/dist/src/components/ScrollableArea.d.ts +10 -0
  10. package/dist/src/components/ScrollableArea.d.ts.map +1 -0
  11. package/dist/src/components/index.d.ts +3 -0
  12. package/dist/src/components/index.d.ts.map +1 -1
  13. package/dist/src/context/AuthProvider.d.ts +2 -0
  14. package/dist/src/context/AuthProvider.d.ts.map +1 -1
  15. package/dist/src/crud/FormGlobalErrorsAlert.d.ts +5 -0
  16. package/dist/src/crud/FormGlobalErrorsAlert.d.ts.map +1 -0
  17. package/dist/src/crud/InlineFormSet.d.ts +2 -17
  18. package/dist/src/crud/InlineFormSet.d.ts.map +1 -1
  19. package/dist/src/crud/ResourceForm.d.ts +2 -10
  20. package/dist/src/crud/ResourceForm.d.ts.map +1 -1
  21. package/dist/src/crud/ResourceFormModal.d.ts +7 -1
  22. package/dist/src/crud/ResourceFormModal.d.ts.map +1 -1
  23. package/dist/src/crud/ResourceList.d.ts.map +1 -1
  24. package/dist/src/crud/ResourceRecordForm.d.ts +27 -0
  25. package/dist/src/crud/ResourceRecordForm.d.ts.map +1 -0
  26. package/dist/src/crud/columns/ImageColumn.d.ts +14 -0
  27. package/dist/src/crud/columns/ImageColumn.d.ts.map +1 -0
  28. package/dist/src/crud/context/InlineFieldsRegistry.d.ts +15 -0
  29. package/dist/src/crud/context/InlineFieldsRegistry.d.ts.map +1 -0
  30. package/dist/src/crud/context/PayloadFieldsContext.d.ts +18 -0
  31. package/dist/src/crud/context/PayloadFieldsContext.d.ts.map +1 -0
  32. package/dist/src/crud/fields/FileField.d.ts +12 -0
  33. package/dist/src/crud/fields/FileField.d.ts.map +1 -0
  34. package/dist/src/crud/fields/ImageField.d.ts +14 -0
  35. package/dist/src/crud/fields/ImageField.d.ts.map +1 -0
  36. package/dist/src/crud/fields/ReferenceField.d.ts +12 -2
  37. package/dist/src/crud/fields/ReferenceField.d.ts.map +1 -1
  38. package/dist/src/crud/fields/ReferenceInputActions.d.ts +16 -0
  39. package/dist/src/crud/fields/ReferenceInputActions.d.ts.map +1 -0
  40. package/dist/src/crud/fields/ReferenceManyField.d.ts +10 -1
  41. package/dist/src/crud/fields/ReferenceManyField.d.ts.map +1 -1
  42. package/dist/src/crud/fields/uploadFieldUtils.d.ts +5 -0
  43. package/dist/src/crud/fields/uploadFieldUtils.d.ts.map +1 -0
  44. package/dist/src/crud/fields/useUploadPreviewUrl.d.ts +4 -0
  45. package/dist/src/crud/fields/useUploadPreviewUrl.d.ts.map +1 -0
  46. package/dist/src/crud/filters/ReferenceFilter.d.ts +1 -1
  47. package/dist/src/crud/filters/ReferenceFilter.d.ts.map +1 -1
  48. package/dist/src/crud/index.d.ts +27 -9
  49. package/dist/src/crud/index.d.ts.map +1 -1
  50. package/dist/src/crud/types.d.ts +21 -7
  51. package/dist/src/crud/types.d.ts.map +1 -1
  52. package/dist/src/crud/utils/buildFormPayload.d.ts +6 -0
  53. package/dist/src/crud/utils/buildFormPayload.d.ts.map +1 -0
  54. package/dist/src/crud/utils/buildInlineRowsPayload.d.ts +6 -0
  55. package/dist/src/crud/utils/buildInlineRowsPayload.d.ts.map +1 -0
  56. package/dist/src/crud/utils/buildResourceFormSubmitBody.d.ts +5 -0
  57. package/dist/src/crud/utils/buildResourceFormSubmitBody.d.ts.map +1 -0
  58. package/dist/src/crud/utils/choiceSelectionUtils.d.ts +10 -0
  59. package/dist/src/crud/utils/choiceSelectionUtils.d.ts.map +1 -0
  60. package/dist/src/crud/utils/choiceSelectionUtils.test.d.ts +2 -0
  61. package/dist/src/crud/utils/choiceSelectionUtils.test.d.ts.map +1 -0
  62. package/dist/src/crud/utils/formErrors.d.ts +18 -7
  63. package/dist/src/crud/utils/formErrors.d.ts.map +1 -1
  64. package/dist/src/crud/utils/formErrors.test.d.ts +2 -0
  65. package/dist/src/crud/utils/formErrors.test.d.ts.map +1 -0
  66. package/dist/src/crud/utils/getFormValue.d.ts +3 -0
  67. package/dist/src/crud/utils/getFormValue.d.ts.map +1 -0
  68. package/dist/src/crud/utils/hasUploadValues.d.ts +3 -0
  69. package/dist/src/crud/utils/hasUploadValues.d.ts.map +1 -0
  70. package/dist/src/crud/utils/nestedFieldPath.d.ts +3 -0
  71. package/dist/src/crud/utils/nestedFieldPath.d.ts.map +1 -0
  72. package/dist/src/crud/utils/prepareFormSubmitBody.d.ts +8 -0
  73. package/dist/src/crud/utils/prepareFormSubmitBody.d.ts.map +1 -0
  74. package/dist/src/crud/utils/referenceSelectNotFoundContent.d.ts +4 -0
  75. package/dist/src/crud/utils/referenceSelectNotFoundContent.d.ts.map +1 -0
  76. package/dist/src/crud/utils/setFormValue.d.ts +3 -0
  77. package/dist/src/crud/utils/setFormValue.d.ts.map +1 -0
  78. package/dist/src/crud/utils/toFormData.d.ts +13 -0
  79. package/dist/src/crud/utils/toFormData.d.ts.map +1 -0
  80. package/dist/src/crud/utils/uploadReferenceUtils.d.ts +5 -0
  81. package/dist/src/crud/utils/uploadReferenceUtils.d.ts.map +1 -0
  82. package/dist/src/crud/utils/useChoices.d.ts +27 -1
  83. package/dist/src/crud/utils/useChoices.d.ts.map +1 -1
  84. package/dist/src/crud/utils/useFormRecord.d.ts +44 -0
  85. package/dist/src/crud/utils/useFormRecord.d.ts.map +1 -0
  86. package/dist/src/data/createRestResourceHandlers.d.ts +5 -5
  87. package/dist/src/data/createRestResourceHandlers.d.ts.map +1 -1
  88. package/dist/src/data/dataProviderTypes.d.ts +6 -5
  89. package/dist/src/data/dataProviderTypes.d.ts.map +1 -1
  90. package/dist/src/data/parseFormErrorHelpers.d.ts +23 -6
  91. package/dist/src/data/parseFormErrorHelpers.d.ts.map +1 -1
  92. package/dist/src/data/resourceHandlers.d.ts +1 -1
  93. package/dist/src/data/resourceHandlers.d.ts.map +1 -1
  94. package/dist/src/index.d.ts +4 -4
  95. package/dist/src/index.d.ts.map +1 -1
  96. package/dist/src/layouts/AdminLayout.d.ts +1 -1
  97. package/dist/src/layouts/AdminLayout.d.ts.map +1 -1
  98. package/dist/src/layouts/AuthPageLayout.d.ts.map +1 -1
  99. package/dist/src/layouts/navFilter.d.ts +5 -0
  100. package/dist/src/layouts/navFilter.d.ts.map +1 -0
  101. package/dist/src/layouts/navMenuItems.d.ts +6 -0
  102. package/dist/src/layouts/navMenuItems.d.ts.map +1 -0
  103. package/dist/src/types.d.ts +12 -0
  104. package/dist/src/types.d.ts.map +1 -1
  105. package/package.json +79 -59
  106. package/src/app/AdminApp.tsx +34 -34
  107. package/src/components/AuthAlternateLink.tsx +23 -23
  108. package/src/components/DensitySwitch.tsx +31 -31
  109. package/src/components/NavMenuLabel.tsx +26 -0
  110. package/src/components/NavMenuSearch.tsx +76 -0
  111. package/src/components/ScrollableArea.tsx +46 -0
  112. package/src/components/ThemeSwitch.tsx +28 -28
  113. package/src/components/ThemeToolbar.tsx +34 -34
  114. package/src/components/index.ts +7 -4
  115. package/src/components/navMenu.css +21 -0
  116. package/src/components/scrollable.css +28 -0
  117. package/src/context/AppThemeProvider.tsx +128 -128
  118. package/src/context/AuthProvider.tsx +89 -77
  119. package/src/context/DataProvider.tsx +30 -30
  120. package/src/context/PermissionsProvider.tsx +57 -57
  121. package/src/crud/FilterBar.tsx +36 -36
  122. package/src/crud/FormGlobalErrorsAlert.tsx +36 -0
  123. package/src/crud/FormSteps.tsx +135 -135
  124. package/src/crud/FormTabs.tsx +126 -126
  125. package/src/crud/InlineFormSet.tsx +162 -219
  126. package/src/crud/ListActionsBar.tsx +106 -106
  127. package/src/crud/ResourceForm.tsx +83 -166
  128. package/src/crud/ResourceFormModal.tsx +75 -133
  129. package/src/crud/ResourceList.tsx +1 -0
  130. package/src/crud/ResourceRecordForm.tsx +166 -0
  131. package/src/crud/columns/BooleanColumn.tsx +32 -32
  132. package/src/crud/columns/CustomColumn.tsx +33 -33
  133. package/src/crud/columns/DateColumn.tsx +28 -28
  134. package/src/crud/columns/ImageColumn.tsx +58 -0
  135. package/src/crud/columns/NumberColumn.tsx +31 -31
  136. package/src/crud/columns/ReferenceColumn.tsx +85 -85
  137. package/src/crud/columns/ReferenceManyColumn.tsx +67 -67
  138. package/src/crud/columns/TextColumn.tsx +38 -38
  139. package/src/crud/context/FilterContext.tsx +71 -71
  140. package/src/crud/context/FormContext.tsx +39 -39
  141. package/src/crud/context/FormSectionContext.tsx +22 -22
  142. package/src/crud/context/InlineFieldsRegistry.tsx +61 -0
  143. package/src/crud/context/ListContext.tsx +93 -93
  144. package/src/crud/context/PayloadFieldsContext.tsx +62 -0
  145. package/src/crud/fields/BooleanField.tsx +38 -38
  146. package/src/crud/fields/DateField.tsx +53 -53
  147. package/src/crud/fields/FieldWrapper.tsx +87 -87
  148. package/src/crud/fields/FileField.tsx +123 -0
  149. package/src/crud/fields/ImageField.tsx +120 -0
  150. package/src/crud/fields/NumberField.tsx +52 -52
  151. package/src/crud/fields/PasswordField.tsx +99 -99
  152. package/src/crud/fields/ReferenceField.tsx +237 -96
  153. package/src/crud/fields/ReferenceInputActions.tsx +106 -0
  154. package/src/crud/fields/ReferenceManyField.tsx +200 -76
  155. package/src/crud/fields/SelectField.tsx +48 -48
  156. package/src/crud/fields/TextField.tsx +46 -46
  157. package/src/crud/fields/uploadFieldUtils.ts +21 -0
  158. package/src/crud/fields/useUploadPreviewUrl.ts +20 -0
  159. package/src/crud/filters/BooleanFilter.tsx +38 -38
  160. package/src/crud/filters/DateFilter.tsx +35 -35
  161. package/src/crud/filters/NumberFilter.tsx +33 -33
  162. package/src/crud/filters/ReferenceFilter.tsx +192 -173
  163. package/src/crud/filters/SelectFilter.tsx +44 -44
  164. package/src/crud/filters/TextFilter.tsx +36 -36
  165. package/src/crud/index.ts +99 -90
  166. package/src/crud/types.ts +146 -132
  167. package/src/crud/utils/buildFormPayload.ts +24 -0
  168. package/src/crud/utils/buildInlineRowsPayload.ts +37 -0
  169. package/src/crud/utils/buildResourceFormSubmitBody.ts +29 -0
  170. package/src/crud/utils/choiceSelectionUtils.test.ts +45 -0
  171. package/src/crud/utils/choiceSelectionUtils.ts +116 -0
  172. package/src/crud/utils/formErrors.test.ts +168 -0
  173. package/src/crud/utils/formErrors.ts +138 -38
  174. package/src/crud/utils/formSectionErrors.ts +85 -85
  175. package/src/crud/utils/{getByPath.ts → getFormValue.ts} +13 -13
  176. package/src/crud/utils/hasUploadValues.ts +9 -0
  177. package/src/crud/utils/nestedFieldPath.ts +8 -0
  178. package/src/crud/utils/prepareFormSubmitBody.ts +22 -0
  179. package/src/crud/utils/referenceSelectNotFoundContent.tsx +7 -0
  180. package/src/crud/utils/{setByPath.ts → setFormValue.ts} +18 -18
  181. package/src/crud/utils/sortQueryParam.ts +37 -37
  182. package/src/crud/utils/toFormData.ts +79 -0
  183. package/src/crud/utils/uploadReferenceUtils.ts +44 -0
  184. package/src/crud/utils/useAbortableEffect.ts +17 -17
  185. package/src/crud/utils/useChoices.ts +330 -182
  186. package/src/crud/utils/useFormRecord.ts +179 -0
  187. package/src/crud/utils/useListQueryState.ts +191 -191
  188. package/src/data/abortError.test.ts +19 -19
  189. package/src/data/abortError.ts +10 -10
  190. package/src/data/createMemoryResourceHandlers.ts +78 -78
  191. package/src/data/createRestResourceHandlers.ts +63 -57
  192. package/src/data/dataProviderTypes.ts +7 -5
  193. package/src/data/dataUtils.test.ts +228 -108
  194. package/src/data/inMemoryList.ts +97 -97
  195. package/src/data/parseFormErrorHelpers.test.ts +201 -84
  196. package/src/data/parseFormErrorHelpers.ts +376 -224
  197. package/src/data/resourceHandlers.ts +1 -1
  198. package/src/data/sortHelpers.ts +48 -48
  199. package/src/index.ts +211 -195
  200. package/src/layouts/AdminLayout.tsx +530 -381
  201. package/src/layouts/AuthPageLayout.tsx +87 -81
  202. package/src/layouts/navFilter.ts +49 -0
  203. package/src/layouts/navMenuItems.tsx +44 -0
  204. package/src/pages/LoginPage.tsx +80 -80
  205. package/src/pages/PlaceholderPage.tsx +9 -9
  206. package/src/permissions/resourcePermissions.ts +47 -47
  207. package/src/router/createAdminRouter.tsx +64 -64
  208. package/src/router/guards.tsx +63 -63
  209. package/src/router/routeAccess.ts +89 -89
  210. package/src/types.ts +10 -0
  211. package/src/vite-env.d.ts +1 -1
  212. package/dist/src/crud/context/SubmitFieldsContext.d.ts +0 -18
  213. package/dist/src/crud/context/SubmitFieldsContext.d.ts.map +0 -1
  214. package/dist/src/crud/utils/getByPath.d.ts +0 -3
  215. package/dist/src/crud/utils/getByPath.d.ts.map +0 -1
  216. package/dist/src/crud/utils/inlineArrayName.d.ts +0 -3
  217. package/dist/src/crud/utils/inlineArrayName.d.ts.map +0 -1
  218. package/dist/src/crud/utils/inlineFieldName.d.ts +0 -3
  219. package/dist/src/crud/utils/inlineFieldName.d.ts.map +0 -1
  220. package/dist/src/crud/utils/pickBySources.d.ts +0 -6
  221. package/dist/src/crud/utils/pickBySources.d.ts.map +0 -1
  222. package/dist/src/crud/utils/setByPath.d.ts +0 -3
  223. package/dist/src/crud/utils/setByPath.d.ts.map +0 -1
  224. package/dist/src/crud/utils/useResourceFormData.d.ts +0 -47
  225. package/dist/src/crud/utils/useResourceFormData.d.ts.map +0 -1
  226. package/src/crud/context/SubmitFieldsContext.tsx +0 -62
  227. package/src/crud/utils/inlineArrayName.ts +0 -4
  228. package/src/crud/utils/inlineFieldName.ts +0 -8
  229. package/src/crud/utils/pickBySources.ts +0 -24
  230. package/src/crud/utils/useResourceFormData.ts +0 -231
@@ -1,93 +1,93 @@
1
- import {
2
- createContext,
3
- useCallback,
4
- useContext,
5
- useEffect,
6
- useMemo,
7
- useState,
8
- type ReactNode,
9
- } from "react";
10
- import type { ColumnDefinition } from "../types";
11
- import { sortPrioritiesFromSpecs } from "../utils/sortQueryParam";
12
-
13
- type ListContextValue = {
14
- registerColumn: (def: ColumnDefinition<Record<string, unknown>>) => () => void;
15
- columns: ColumnDefinition<Record<string, unknown>>[];
16
- toggleSort: (field: string) => void;
17
- sortFields: Set<string>;
18
- sortOrders: Map<string, "ASC" | "DESC">;
19
- sortPriorities: Map<string, number>;
20
- };
21
-
22
- const ListContext = createContext<ListContextValue | null>(null);
23
-
24
- export function ListContextProvider({
25
- children,
26
- toggleSort,
27
- sort,
28
- }: {
29
- children: ReactNode;
30
- toggleSort: (field: string) => void;
31
- sort: { field: string; order: "ASC" | "DESC" }[];
32
- }) {
33
- const [columns, setColumns] = useState<
34
- ColumnDefinition<Record<string, unknown>>[]
35
- >([]);
36
-
37
- const sortFields = useMemo(() => new Set(sort.map((s) => s.field)), [sort]);
38
- const sortOrders = useMemo(
39
- () => new Map(sort.map((s) => [s.field, s.order])),
40
- [sort],
41
- );
42
- const sortPriorities = useMemo(() => sortPrioritiesFromSpecs(sort), [sort]);
43
-
44
- const registerColumn = useCallback(
45
- (def: ColumnDefinition<Record<string, unknown>>) => {
46
- setColumns((prev) => {
47
- const idx = prev.findIndex((c) => c.key === def.key);
48
- if (idx < 0) return [...prev, def];
49
- if (prev[idx] === def) return prev;
50
- const next = [...prev];
51
- next[idx] = def;
52
- return next;
53
- });
54
- return () => {
55
- setColumns((prev) => prev.filter((c) => c.key !== def.key));
56
- };
57
- },
58
- [],
59
- );
60
-
61
- const value = useMemo(
62
- (): ListContextValue => ({
63
- columns,
64
- toggleSort,
65
- sortFields,
66
- sortOrders,
67
- sortPriorities,
68
- registerColumn,
69
- }),
70
- [columns, toggleSort, sortFields, sortOrders, sortPriorities, registerColumn],
71
- );
72
-
73
- return (
74
- <ListContext.Provider value={value}>{children}</ListContext.Provider>
75
- );
76
- }
77
-
78
- export function useListContext() {
79
- const ctx = useContext(ListContext);
80
- if (!ctx) {
81
- throw new Error("Column components must be used within ResourceList");
82
- }
83
- return ctx;
84
- }
85
-
86
- export function useRegisterColumn(
87
- def: ColumnDefinition<Record<string, unknown>>,
88
- ) {
89
- const { registerColumn } = useListContext();
90
- useEffect(() => {
91
- return registerColumn(def);
92
- }, [registerColumn, def]);
93
- }
1
+ import {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useEffect,
6
+ useMemo,
7
+ useState,
8
+ type ReactNode,
9
+ } from "react";
10
+ import type { ColumnDefinition } from "../types";
11
+ import { sortPrioritiesFromSpecs } from "../utils/sortQueryParam";
12
+
13
+ type ListContextValue = {
14
+ registerColumn: (def: ColumnDefinition<Record<string, unknown>>) => () => void;
15
+ columns: ColumnDefinition<Record<string, unknown>>[];
16
+ toggleSort: (field: string) => void;
17
+ sortFields: Set<string>;
18
+ sortOrders: Map<string, "ASC" | "DESC">;
19
+ sortPriorities: Map<string, number>;
20
+ };
21
+
22
+ const ListContext = createContext<ListContextValue | null>(null);
23
+
24
+ export function ListContextProvider({
25
+ children,
26
+ toggleSort,
27
+ sort,
28
+ }: {
29
+ children: ReactNode;
30
+ toggleSort: (field: string) => void;
31
+ sort: { field: string; order: "ASC" | "DESC" }[];
32
+ }) {
33
+ const [columns, setColumns] = useState<
34
+ ColumnDefinition<Record<string, unknown>>[]
35
+ >([]);
36
+
37
+ const sortFields = useMemo(() => new Set(sort.map((s) => s.field)), [sort]);
38
+ const sortOrders = useMemo(
39
+ () => new Map(sort.map((s) => [s.field, s.order])),
40
+ [sort],
41
+ );
42
+ const sortPriorities = useMemo(() => sortPrioritiesFromSpecs(sort), [sort]);
43
+
44
+ const registerColumn = useCallback(
45
+ (def: ColumnDefinition<Record<string, unknown>>) => {
46
+ setColumns((prev) => {
47
+ const idx = prev.findIndex((c) => c.key === def.key);
48
+ if (idx < 0) return [...prev, def];
49
+ if (prev[idx] === def) return prev;
50
+ const next = [...prev];
51
+ next[idx] = def;
52
+ return next;
53
+ });
54
+ return () => {
55
+ setColumns((prev) => prev.filter((c) => c.key !== def.key));
56
+ };
57
+ },
58
+ [],
59
+ );
60
+
61
+ const value = useMemo(
62
+ (): ListContextValue => ({
63
+ columns,
64
+ toggleSort,
65
+ sortFields,
66
+ sortOrders,
67
+ sortPriorities,
68
+ registerColumn,
69
+ }),
70
+ [columns, toggleSort, sortFields, sortOrders, sortPriorities, registerColumn],
71
+ );
72
+
73
+ return (
74
+ <ListContext.Provider value={value}>{children}</ListContext.Provider>
75
+ );
76
+ }
77
+
78
+ export function useListContext() {
79
+ const ctx = useContext(ListContext);
80
+ if (!ctx) {
81
+ throw new Error("Column components must be used within ResourceList");
82
+ }
83
+ return ctx;
84
+ }
85
+
86
+ export function useRegisterColumn(
87
+ def: ColumnDefinition<Record<string, unknown>>,
88
+ ) {
89
+ const { registerColumn } = useListContext();
90
+ useEffect(() => {
91
+ return registerColumn(def);
92
+ }, [registerColumn, def]);
93
+ }
@@ -0,0 +1,62 @@
1
+ import {
2
+ createContext,
3
+ useContext,
4
+ useEffect,
5
+ type RefObject,
6
+ type ReactNode,
7
+ } from "react";
8
+ import { useFormSectionSourcesOptional } from "./FormSectionContext";
9
+
10
+ const PayloadFieldsContext =
11
+ createContext<RefObject<Set<string>> | null>(null);
12
+
13
+ /** Tracks which field paths are included in the save payload. */
14
+ export function PayloadFieldsProvider({
15
+ children,
16
+ fieldsRef,
17
+ }: {
18
+ children: ReactNode;
19
+ fieldsRef: RefObject<Set<string>>;
20
+ }) {
21
+ return (
22
+ <PayloadFieldsContext.Provider value={fieldsRef}>
23
+ {children}
24
+ </PayloadFieldsContext.Provider>
25
+ );
26
+ }
27
+
28
+ export function usePayloadFieldsRefOptional() {
29
+ return useContext(PayloadFieldsContext);
30
+ }
31
+
32
+ /**
33
+ * Register a field path for the save payload (`buildFormPayload`).
34
+ * Top-level fields use `source`; inline arrays register their `field` name.
35
+ */
36
+ export function useRegisterPayloadField(fieldPath: string, enabled = true) {
37
+ const fieldsRef = usePayloadFieldsRefOptional();
38
+
39
+ useEffect(() => {
40
+ if (!enabled || !fieldsRef) return;
41
+ fieldsRef.current.add(fieldPath);
42
+ return () => {
43
+ fieldsRef.current.delete(fieldPath);
44
+ };
45
+ }, [fieldsRef, fieldPath, enabled]);
46
+ }
47
+
48
+ /**
49
+ * Register a field for tab/step error highlighting.
50
+ * Top-level fields only — no-op outside FormTabs / FormSteps.
51
+ */
52
+ export function useRegisterSectionField(source: string, enabled = true) {
53
+ const sectionRef = useFormSectionSourcesOptional();
54
+
55
+ useEffect(() => {
56
+ if (!enabled || !sectionRef) return;
57
+ sectionRef.current.add(source);
58
+ return () => {
59
+ sectionRef.current.delete(source);
60
+ };
61
+ }, [sectionRef, source, enabled]);
62
+ }
@@ -1,38 +1,38 @@
1
- import { Switch } from "antd";
2
- import type { BaseSourceProps, FieldRules } from "../types";
3
- import { FieldWrapper } from "./FieldWrapper";
4
-
5
- export type BooleanFieldProps = BaseSourceProps & {
6
- name?: string;
7
- required?: boolean;
8
- rules?: FieldRules;
9
- hideLabel?: boolean;
10
- };
11
-
12
- export function BooleanField({
13
- source,
14
- name,
15
- label,
16
- required,
17
- rules,
18
- hideLabel,
19
- }: BooleanFieldProps) {
20
- return (
21
- <FieldWrapper
22
- source={source}
23
- name={name}
24
- label={label}
25
- required={required}
26
- rules={rules}
27
- hideLabel={hideLabel}
28
- >
29
- {({ value, onChange, disabled }) => (
30
- <Switch
31
- checked={Boolean(value)}
32
- onChange={onChange}
33
- disabled={disabled}
34
- />
35
- )}
36
- </FieldWrapper>
37
- );
38
- }
1
+ import { Switch } from "antd";
2
+ import type { BaseSourceProps, FieldRules } from "../types";
3
+ import { FieldWrapper } from "./FieldWrapper";
4
+
5
+ export type BooleanFieldProps = BaseSourceProps & {
6
+ name?: string;
7
+ required?: boolean;
8
+ rules?: FieldRules;
9
+ hideLabel?: boolean;
10
+ };
11
+
12
+ export function BooleanField({
13
+ source,
14
+ name,
15
+ label,
16
+ required,
17
+ rules,
18
+ hideLabel,
19
+ }: BooleanFieldProps) {
20
+ return (
21
+ <FieldWrapper
22
+ source={source}
23
+ name={name}
24
+ label={label}
25
+ required={required}
26
+ rules={rules}
27
+ hideLabel={hideLabel}
28
+ >
29
+ {({ value, onChange, disabled }) => (
30
+ <Switch
31
+ checked={Boolean(value)}
32
+ onChange={onChange}
33
+ disabled={disabled}
34
+ />
35
+ )}
36
+ </FieldWrapper>
37
+ );
38
+ }
@@ -1,53 +1,53 @@
1
- import { DatePicker } from "antd";
2
- import dayjs from "dayjs";
3
- import type { BaseSourceProps, FieldRules } from "../types";
4
- import { FieldWrapper } from "./FieldWrapper";
5
-
6
- const DATE_FORMAT = "YYYY-MM-DD";
7
-
8
- export type DateFieldProps = BaseSourceProps & {
9
- name?: string;
10
- required?: boolean;
11
- rules?: FieldRules;
12
- showTime?: boolean;
13
- hideLabel?: boolean;
14
- };
15
-
16
- export function DateField({
17
- source,
18
- name,
19
- label,
20
- required,
21
- rules,
22
- showTime,
23
- hideLabel,
24
- }: DateFieldProps) {
25
- return (
26
- <FieldWrapper
27
- source={source}
28
- name={name}
29
- label={label}
30
- required={required}
31
- rules={rules}
32
- hideLabel={hideLabel}
33
- >
34
- {({ value, onChange, onBlur, disabled }) => (
35
- <DatePicker
36
- value={value ? dayjs(String(value)) : null}
37
- onChange={(d) =>
38
- onChange(
39
- d
40
- ? d.format(showTime ? `${DATE_FORMAT} HH:mm:ss` : DATE_FORMAT)
41
- : null,
42
- )
43
- }
44
- onBlur={onBlur}
45
- showTime={showTime}
46
- disabled={disabled}
47
- format={showTime ? `${DATE_FORMAT} HH:mm:ss` : DATE_FORMAT}
48
- style={{ width: "100%" }}
49
- />
50
- )}
51
- </FieldWrapper>
52
- );
53
- }
1
+ import { DatePicker } from "antd";
2
+ import dayjs from "dayjs";
3
+ import type { BaseSourceProps, FieldRules } from "../types";
4
+ import { FieldWrapper } from "./FieldWrapper";
5
+
6
+ const DATE_FORMAT = "YYYY-MM-DD";
7
+
8
+ export type DateFieldProps = BaseSourceProps & {
9
+ name?: string;
10
+ required?: boolean;
11
+ rules?: FieldRules;
12
+ showTime?: boolean;
13
+ hideLabel?: boolean;
14
+ };
15
+
16
+ export function DateField({
17
+ source,
18
+ name,
19
+ label,
20
+ required,
21
+ rules,
22
+ showTime,
23
+ hideLabel,
24
+ }: DateFieldProps) {
25
+ return (
26
+ <FieldWrapper
27
+ source={source}
28
+ name={name}
29
+ label={label}
30
+ required={required}
31
+ rules={rules}
32
+ hideLabel={hideLabel}
33
+ >
34
+ {({ value, onChange, onBlur, disabled }) => (
35
+ <DatePicker
36
+ value={value ? dayjs(String(value)) : null}
37
+ onChange={(d) =>
38
+ onChange(
39
+ d
40
+ ? d.format(showTime ? `${DATE_FORMAT} HH:mm:ss` : DATE_FORMAT)
41
+ : null,
42
+ )
43
+ }
44
+ onBlur={onBlur}
45
+ showTime={showTime}
46
+ disabled={disabled}
47
+ format={showTime ? `${DATE_FORMAT} HH:mm:ss` : DATE_FORMAT}
48
+ style={{ width: "100%" }}
49
+ />
50
+ )}
51
+ </FieldWrapper>
52
+ );
53
+ }
@@ -1,87 +1,87 @@
1
- import { Form } from "antd";
2
- import { Controller, useFormContext, type FieldValues } from "react-hook-form";
3
- import type { ReactNode } from "react";
4
- import type { FieldRules } from "../types";
5
- import { useFormMetaOptional } from "../context/FormContext";
6
- import {
7
- useSectionField,
8
- useSubmitField,
9
- } from "../context/SubmitFieldsContext";
10
-
11
- export type FieldWrapperProps = {
12
- /** Logical field name — used for submit payload tracking on top-level fields. */
13
- source: string;
14
- /** Full RHF path. Defaults to `source`. Inline cells pass e.g. `lines.0.label`. */
15
- name?: string;
16
- label?: string;
17
- required?: boolean;
18
- rules?: FieldRules;
19
- /** Tabular inline cells: column header replaces the label. */
20
- hideLabel?: boolean;
21
- children: (props: {
22
- value: unknown;
23
- onChange: (value: unknown) => void;
24
- onBlur: () => void;
25
- disabled?: boolean;
26
- name: string;
27
- }) => ReactNode;
28
- };
29
-
30
- export function FieldWrapper({
31
- source,
32
- name,
33
- label,
34
- required,
35
- rules,
36
- hideLabel,
37
- children,
38
- }: FieldWrapperProps) {
39
- const fieldName = name ?? source;
40
- const isTopLevel = !fieldName.includes(".");
41
-
42
- // react-hook-form: connect this field to the form from <ResourceForm>'s FormProvider.
43
- // `control` is passed to <Controller> below so value, onChange, and validation work.
44
- const { control } = useFormContext<FieldValues>();
45
-
46
- // ding-react-admin: optional form-wide flags from <FormMetaProvider> (inside ResourceForm).
47
- // Used here for `disabled` on inputs (e.g. read-only view mode). Returns null outside a form page.
48
- const meta = useFormMetaOptional();
49
- const fieldLabel = hideLabel ? undefined : (label ?? source);
50
- const requiredMessage = label ?? source;
51
-
52
- // ding-react-admin: remember this `source` so Save sends only fields you rendered in JSX
53
- // (see pickBySources in ResourceForm). Skipped for inline row cells nested `name` paths.
54
- useSubmitField(source, isTopLevel);
55
-
56
- // ding-react-admin: group this field with the current FormTab / FormStep for error highlighting
57
- // and "jump to first invalid tab" on Save. No-op outside tabs/steps.
58
- useSectionField(source, isTopLevel);
59
-
60
- return (
61
- <Controller
62
- name={fieldName}
63
- control={control}
64
- rules={{
65
- required: required ? `${requiredMessage} is required` : false,
66
- ...rules,
67
- }}
68
- render={({ field, fieldState }) => (
69
- <Form.Item
70
- label={fieldLabel}
71
- validateStatus={fieldState.error ? "error" : undefined}
72
- help={fieldState.error?.message}
73
- required={required && !hideLabel}
74
- style={hideLabel ? { marginBottom: 0 } : undefined}
75
- >
76
- {children({
77
- value: field.value,
78
- onChange: field.onChange,
79
- onBlur: field.onBlur,
80
- disabled: meta?.disabled,
81
- name: fieldName,
82
- })}
83
- </Form.Item>
84
- )}
85
- />
86
- );
87
- }
1
+ import { Form } from "antd";
2
+ import { Controller, useFormContext, type FieldValues } from "react-hook-form";
3
+ import type { ReactNode } from "react";
4
+ import type { FieldRules } from "../types";
5
+ import { useFormMetaOptional } from "../context/FormContext";
6
+ import {
7
+ useRegisterPayloadField,
8
+ useRegisterSectionField,
9
+ } from "../context/PayloadFieldsContext";
10
+
11
+ export type FieldWrapperProps = {
12
+ /** Logical field name — used for submit payload tracking on top-level fields. */
13
+ source: string;
14
+ /** Full RHF path. Defaults to `source`. Inline cells pass e.g. `lines.0.label`. */
15
+ name?: string;
16
+ label?: string;
17
+ required?: boolean;
18
+ rules?: FieldRules;
19
+ /** Tabular inline cells: column header replaces the label. */
20
+ hideLabel?: boolean;
21
+ children: (props: {
22
+ value: unknown;
23
+ onChange: (value: unknown) => void;
24
+ onBlur: () => void;
25
+ disabled?: boolean;
26
+ name: string;
27
+ }) => ReactNode;
28
+ };
29
+
30
+ export function FieldWrapper({
31
+ source,
32
+ name,
33
+ label,
34
+ required,
35
+ rules,
36
+ hideLabel,
37
+ children,
38
+ }: FieldWrapperProps) {
39
+ const fieldName = name ?? source;
40
+ const isTopLevel = !fieldName.includes(".");
41
+
42
+ // react-hook-form: connect this field to the form from <ResourceForm>'s FormProvider.
43
+ // `control` is passed to <Controller> below so value, onChange, and validation work.
44
+ const { control } = useFormContext<FieldValues>();
45
+
46
+ // ding-react-admin: optional form-wide flags from <FormMetaProvider> (inside ResourceForm).
47
+ // Used here for `disabled` on inputs (e.g. read-only view mode). Returns null outside a form page.
48
+ const meta = useFormMetaOptional();
49
+ const fieldLabel = hideLabel ? undefined : (label ?? source);
50
+ const requiredMessage = label ?? source;
51
+
52
+ // Add this field's `source` to the Save payload (ResourceForm buildFormPayload).
53
+ // `isTopLevel` is false when `name` contains "." (e.g. inline cell `__inline_lines.0.label`)
54
+ // those values live in the inline array and are saved by saveInlineRows, not the parent PATCH.
55
+ useRegisterPayloadField(source, isTopLevel);
56
+
57
+ // Same top-level check: attach this field to the current FormTab / FormStep for error highlighting.
58
+ useRegisterSectionField(source, isTopLevel);
59
+
60
+ return (
61
+ <Controller
62
+ name={fieldName}
63
+ control={control}
64
+ rules={{
65
+ required: required ? `${requiredMessage} is required` : false,
66
+ ...rules,
67
+ }}
68
+ render={({ field, fieldState }) => (
69
+ <Form.Item
70
+ label={fieldLabel}
71
+ validateStatus={fieldState.error ? "error" : undefined}
72
+ help={fieldState.error?.message}
73
+ required={required && !hideLabel}
74
+ style={hideLabel ? { marginBottom: 0 } : undefined}
75
+ >
76
+ {children({
77
+ value: field.value,
78
+ onChange: field.onChange,
79
+ onBlur: field.onBlur,
80
+ disabled: meta?.disabled,
81
+ name: fieldName,
82
+ })}
83
+ </Form.Item>
84
+ )}
85
+ />
86
+ );
87
+ }