@alepha/ui 0.16.0 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/{AdminApiKeys-GMORg-1l.js → AdminApiKeys-CoTOTfgU.js} +4 -3
- package/dist/admin/{AdminApiKeys-GMORg-1l.js.map → AdminApiKeys-CoTOTfgU.js.map} +1 -1
- package/dist/admin/{AdminAudits-pkWrjq1Z.js → AdminAudits-BmsxFbDa.js} +4 -3
- package/dist/admin/{AdminAudits-pkWrjq1Z.js.map → AdminAudits-BmsxFbDa.js.map} +1 -1
- package/dist/admin/{AdminFiles-WeQbsCsl.js → AdminFiles-BBB8knca.js} +4 -3
- package/dist/admin/{AdminFiles-WeQbsCsl.js.map → AdminFiles-BBB8knca.js.map} +1 -1
- package/dist/admin/{AdminJobs-B-q9iGO3.js → AdminJobs-C604joTz.js} +4 -3
- package/dist/admin/{AdminJobs-B-q9iGO3.js.map → AdminJobs-C604joTz.js.map} +1 -1
- package/dist/admin/{AdminLayout-D8yZ-8lG.js → AdminLayout-CsjvpeD1.js} +6 -10
- package/dist/admin/AdminLayout-CsjvpeD1.js.map +1 -0
- package/dist/admin/{AdminNotifications-Ds5Un0NJ.js → AdminNotifications-LwR6RKrx.js} +4 -3
- package/dist/admin/{AdminNotifications-Ds5Un0NJ.js.map → AdminNotifications-LwR6RKrx.js.map} +1 -1
- package/dist/admin/AdminParameters-B_83Vie9.js +767 -0
- package/dist/admin/AdminParameters-B_83Vie9.js.map +1 -0
- package/dist/admin/{AdminSessions-DzIOxM3b.js → AdminSessions-CWnPosdd.js} +4 -3
- package/dist/admin/{AdminSessions-DzIOxM3b.js.map → AdminSessions-CWnPosdd.js.map} +1 -1
- package/dist/admin/{AdminUserAudits-CiUPN2BC.js → AdminUserAudits-nHv636E_.js} +4 -3
- package/dist/admin/{AdminUserAudits-CiUPN2BC.js.map → AdminUserAudits-nHv636E_.js.map} +1 -1
- package/dist/admin/{AdminUserCreate-BwQKr4xE.js → AdminUserCreate-CjYD3Kjc.js} +4 -3
- package/dist/admin/{AdminUserCreate-BwQKr4xE.js.map → AdminUserCreate-CjYD3Kjc.js.map} +1 -1
- package/dist/admin/{AdminUserDetails-uqtC5aJ1.js → AdminUserDetails-Ccq-LsZ0.js} +4 -3
- package/dist/admin/{AdminUserDetails-uqtC5aJ1.js.map → AdminUserDetails-Ccq-LsZ0.js.map} +1 -1
- package/dist/admin/{AdminUserLayout-CiPay35T.js → AdminUserLayout-7s41DiF_.js} +6 -7
- package/dist/admin/AdminUserLayout-7s41DiF_.js.map +1 -0
- package/dist/admin/{AdminUserSessions-DAE8Nf1F.js → AdminUserSessions-Ds3ODq_d.js} +4 -3
- package/dist/admin/{AdminUserSessions-DAE8Nf1F.js.map → AdminUserSessions-Ds3ODq_d.js.map} +1 -1
- package/dist/admin/{AdminUserSettings-EbahaV2a.js → AdminUserSettings-CGh4gROo.js} +4 -3
- package/dist/admin/{AdminUserSettings-EbahaV2a.js.map → AdminUserSettings-CGh4gROo.js.map} +1 -1
- package/dist/admin/{AdminUsers-Dcjh0KNW.js → AdminUsers-CvPiBzQK.js} +4 -3
- package/dist/admin/{AdminUsers-Dcjh0KNW.js.map → AdminUsers-CvPiBzQK.js.map} +1 -1
- package/dist/admin/index.d.ts +22 -10
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +47 -48
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/rolldown-runtime-CjeV3_4I.js +18 -0
- package/dist/auth/{AuthLayout-mFOWbiSP.js → AuthLayout-CdJcrPs4.js} +2 -4
- package/dist/auth/AuthLayout-CdJcrPs4.js.map +1 -0
- package/dist/{demo/IconGoogle-CbBF8Hqq.js → auth/IconGoogle-Bm18QD2q.js} +2 -4
- package/dist/auth/{IconGoogle-DpSlPZ1u.js.map → IconGoogle-Bm18QD2q.js.map} +1 -1
- package/dist/auth/{Login-BBqTosqZ.js → Login-DS_OqA0G.js} +7 -6
- package/dist/auth/Login-DS_OqA0G.js.map +1 -0
- package/dist/auth/{Profile-Bxj8Nwom.js → Profile-Di7N7HZL.js} +2 -3
- package/dist/auth/{Profile-Bxj8Nwom.js.map → Profile-Di7N7HZL.js.map} +1 -1
- package/dist/auth/{Register-Ce675Crg.js → Register-BRR2_gux.js} +7 -6
- package/dist/auth/Register-BRR2_gux.js.map +1 -0
- package/dist/auth/{ResetPassword-DWdt7c40.js → ResetPassword-oQu72lod.js} +4 -3
- package/dist/auth/{ResetPassword-DWdt7c40.js.map → ResetPassword-oQu72lod.js.map} +1 -1
- package/dist/auth/{VerifyEmail-CI4JwByV.js → VerifyEmail-DC6HPZjd.js} +4 -3
- package/dist/auth/{VerifyEmail-CI4JwByV.js.map → VerifyEmail-DC6HPZjd.js.map} +1 -1
- package/dist/auth/index.d.ts +14 -14
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +13 -17
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/rolldown-runtime-CjeV3_4I.js +18 -0
- package/dist/core/index.d.ts +147 -68
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +349 -287
- package/dist/core/index.js.map +1 -1
- package/dist/demo/{DemoDataTable-CguplbR7.js → DemoDataTable-DCsJq8v5.js} +4 -5
- package/dist/demo/DemoDataTable-DCsJq8v5.js.map +1 -0
- package/dist/demo/{DemoHome-Cce2bWmg.js → DemoHome-DpRrPlBC.js} +4 -3
- package/dist/demo/{DemoHome-Cce2bWmg.js.map → DemoHome-DpRrPlBC.js.map} +1 -1
- package/dist/demo/{DemoJsonViewer-Dgdk3Txb.js → DemoJsonViewer-zeucGKHV.js} +6 -5
- package/dist/demo/DemoJsonViewer-zeucGKHV.js.map +1 -0
- package/dist/demo/{DemoLayout-B20TEuhV.js → DemoLayout-PhgbAAiQ.js} +6 -5
- package/dist/demo/DemoLayout-PhgbAAiQ.js.map +1 -0
- package/dist/demo/{DemoLogin-CvCG2WVh.js → DemoLogin-DSzP0Lkv.js} +8 -10
- package/dist/demo/DemoLogin-DSzP0Lkv.js.map +1 -0
- package/dist/demo/{DemoRegister-CmeHbOAs.js → DemoRegister-DavFBsCz.js} +8 -10
- package/dist/demo/DemoRegister-DavFBsCz.js.map +1 -0
- package/dist/demo/{DemoResetPassword-CKO5iA_6.js → DemoResetPassword-BS2rIAQK.js} +5 -7
- package/dist/demo/DemoResetPassword-BS2rIAQK.js.map +1 -0
- package/dist/demo/{DemoSidebar-MVmQKfMt.js → DemoSidebar-zNkUmHRl.js} +4 -5
- package/dist/demo/DemoSidebar-zNkUmHRl.js.map +1 -0
- package/dist/demo/{DemoTypeForm-w-qtfRlC.js → DemoTypeForm-B9q7oT0b.js} +4 -5
- package/dist/demo/DemoTypeForm-B9q7oT0b.js.map +1 -0
- package/dist/demo/{DemoVerifyEmail-C8FFJT5A.js → DemoVerifyEmail-Bi4SdWz0.js} +5 -7
- package/dist/demo/DemoVerifyEmail-Bi4SdWz0.js.map +1 -0
- package/dist/{auth/IconGoogle-DpSlPZ1u.js → demo/IconGoogle-CTeZyrek.js} +2 -4
- package/dist/demo/{IconGoogle-CbBF8Hqq.js.map → IconGoogle-CTeZyrek.js.map} +1 -1
- package/dist/demo/{Showcase-CQrMWars.js → Showcase-C9btr_SJ.js} +3 -5
- package/dist/demo/Showcase-C9btr_SJ.js.map +1 -0
- package/dist/demo/index.d.ts +2 -2
- package/dist/demo/index.d.ts.map +1 -1
- package/dist/demo/index.js +15 -15
- package/dist/demo/rolldown-runtime-CjeV3_4I.js +18 -0
- package/package.json +5 -3
- package/src/admin/AdminRouter.ts +15 -24
- package/src/admin/components/AdminLayout.tsx +6 -10
- package/src/admin/components/parameters/AdminParameters.tsx +154 -76
- package/src/admin/components/parameters/ParameterDetails.tsx +153 -93
- package/src/admin/components/parameters/ParameterEmptyState.tsx +27 -0
- package/src/admin/components/parameters/ParameterHistory.tsx +15 -20
- package/src/admin/components/parameters/ParameterTree.tsx +280 -104
- package/src/admin/components/parameters/types.ts +3 -3
- package/src/admin/primitives/$uiAdmin.ts +2 -2
- package/src/auth/AuthRouter.ts +1 -4
- package/src/auth/components/AuthLayout.tsx +0 -1
- package/src/core/components/buttons/ActionButton.tsx +4 -15
- package/src/core/components/buttons/DarkModeButton.tsx +8 -4
- package/src/core/components/buttons/ToggleSidebarButton.tsx +3 -5
- package/src/core/components/form/Control.tsx +10 -32
- package/src/core/components/form/ControlArray.tsx +200 -89
- package/src/core/components/form/TypeForm.browser.spec.tsx +727 -0
- package/src/core/components/layout/AlephaMantineProvider.tsx +1 -0
- package/src/core/components/layout/Breadcrumb.tsx +91 -0
- package/src/core/components/layout/{AdminShell.tsx → DashboardShell.tsx} +77 -32
- package/src/core/components/layout/Sidebar.tsx +58 -18
- package/src/core/constants/ui.ts +1 -1
- package/src/core/helpers/renderIcon.tsx +5 -2
- package/src/core/index.ts +9 -5
- package/src/core/styles.css +7 -6
- package/src/core/utils/string.ts +28 -4
- package/src/demo/components/DemoLayout.tsx +6 -2
- package/dist/admin/AdminApiKeys-DsmGnHNh.js +0 -3
- package/dist/admin/AdminAudits-8SM96viT.js +0 -3
- package/dist/admin/AdminFiles-B56ocq4H.js +0 -3
- package/dist/admin/AdminJobs-CED1syCn.js +0 -3
- package/dist/admin/AdminLayout-D8yZ-8lG.js.map +0 -1
- package/dist/admin/AdminNotifications-B0B1rdc4.js +0 -3
- package/dist/admin/AdminParameters-BU3lATdJ.js +0 -3
- package/dist/admin/AdminParameters-CfDUpc78.js +0 -575
- package/dist/admin/AdminParameters-CfDUpc78.js.map +0 -1
- package/dist/admin/AdminSessions-BDGK2MS6.js +0 -3
- package/dist/admin/AdminUserAudits-Cj79gENT.js +0 -3
- package/dist/admin/AdminUserCreate-Cq-mUmBs.js +0 -3
- package/dist/admin/AdminUserDetails-DRjVAPFd.js +0 -3
- package/dist/admin/AdminUserLayout-CGzmHHby.js +0 -3
- package/dist/admin/AdminUserLayout-CiPay35T.js.map +0 -1
- package/dist/admin/AdminUserSessions-DcdzuNZ9.js +0 -3
- package/dist/admin/AdminUserSettings-D7V6-ceX.js +0 -3
- package/dist/admin/AdminUsers-D9nyzGqQ.js +0 -3
- package/dist/auth/AuthLayout-mFOWbiSP.js.map +0 -1
- package/dist/auth/Login-BBqTosqZ.js.map +0 -1
- package/dist/auth/Login-CoU63mMR.js +0 -4
- package/dist/auth/Register-BV_oa_AK.js +0 -4
- package/dist/auth/Register-Ce675Crg.js.map +0 -1
- package/dist/auth/ResetPassword-D5wC8GAA.js +0 -3
- package/dist/auth/VerifyEmail-DAfqVm5s.js +0 -3
- package/dist/demo/DemoDataTable-CguplbR7.js.map +0 -1
- package/dist/demo/DemoHome-DC9qkMNe.js +0 -3
- package/dist/demo/DemoJsonViewer-DIssGVlJ.js +0 -4
- package/dist/demo/DemoJsonViewer-Dgdk3Txb.js.map +0 -1
- package/dist/demo/DemoLayout-B20TEuhV.js.map +0 -1
- package/dist/demo/DemoLayout-DSRyf4qJ.js +0 -3
- package/dist/demo/DemoLogin-CvCG2WVh.js.map +0 -1
- package/dist/demo/DemoRegister-CmeHbOAs.js.map +0 -1
- package/dist/demo/DemoResetPassword-CKO5iA_6.js.map +0 -1
- package/dist/demo/DemoSidebar-MVmQKfMt.js.map +0 -1
- package/dist/demo/DemoTypeForm-w-qtfRlC.js.map +0 -1
- package/dist/demo/DemoVerifyEmail-C8FFJT5A.js.map +0 -1
- package/dist/demo/Showcase-CQrMWars.js.map +0 -1
|
@@ -264,40 +264,18 @@ const Control = (_props: ControlProps) => {
|
|
|
264
264
|
"type" in props.input.schema &&
|
|
265
265
|
props.input.schema.type === "boolean"
|
|
266
266
|
) {
|
|
267
|
-
|
|
268
|
-
const switchProps = typeof props.switch === "object" ? props.switch : {};
|
|
269
|
-
|
|
270
|
-
return (
|
|
271
|
-
<Switch
|
|
272
|
-
{...inputProps}
|
|
273
|
-
id={id}
|
|
274
|
-
color={"blue"}
|
|
275
|
-
defaultChecked={props.input.props.defaultValue}
|
|
276
|
-
onChange={(event) => {
|
|
277
|
-
props.input.set(event.currentTarget.checked);
|
|
278
|
-
}}
|
|
279
|
-
{...switchProps}
|
|
280
|
-
/>
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// by default, render as <Select/> with Yes/No/Empty options
|
|
285
|
-
const selectProps: Partial<ControlSelectProps> = {
|
|
286
|
-
loader: async () => [
|
|
287
|
-
{ value: "true", label: "Yes" },
|
|
288
|
-
{ value: "false", label: "No" },
|
|
289
|
-
{ value: "", label: "" },
|
|
290
|
-
],
|
|
291
|
-
...props.input.props,
|
|
292
|
-
};
|
|
267
|
+
const switchProps = typeof props.switch === "object" ? props.switch : {};
|
|
293
268
|
|
|
294
269
|
return (
|
|
295
|
-
<
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
{
|
|
270
|
+
<Switch
|
|
271
|
+
{...inputProps}
|
|
272
|
+
id={id}
|
|
273
|
+
color={"blue"}
|
|
274
|
+
defaultChecked={props.input.props.defaultValue}
|
|
275
|
+
onChange={(event) => {
|
|
276
|
+
props.input.set(event.currentTarget.checked);
|
|
277
|
+
}}
|
|
278
|
+
{...switchProps}
|
|
301
279
|
/>
|
|
302
280
|
);
|
|
303
281
|
}
|
|
@@ -9,9 +9,9 @@ import {
|
|
|
9
9
|
} from "@mantine/core";
|
|
10
10
|
import { IconGripVertical, IconPlus, IconTrash } from "@tabler/icons-react";
|
|
11
11
|
import type { TObject, TSchema } from "alepha";
|
|
12
|
-
import {
|
|
12
|
+
import { useAlepha } from "alepha/react";
|
|
13
13
|
import type { BaseInputField } from "alepha/react/form";
|
|
14
|
-
import { useRef, useState } from "react";
|
|
14
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
15
15
|
import { ui } from "../../constants/ui.ts";
|
|
16
16
|
import {
|
|
17
17
|
type GenericControlProps,
|
|
@@ -19,6 +19,14 @@ import {
|
|
|
19
19
|
} from "../../utils/parseInput.ts";
|
|
20
20
|
import Control, { type ControlProps } from "./Control.tsx";
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Represents an array item with a stable key for React reconciliation.
|
|
24
|
+
*/
|
|
25
|
+
interface ArrayItem {
|
|
26
|
+
key: number;
|
|
27
|
+
value: any;
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
export interface ControlArrayProps extends GenericControlProps {
|
|
23
31
|
/**
|
|
24
32
|
* Minimum number of items allowed.
|
|
@@ -70,6 +78,169 @@ export interface ControlArrayProps extends GenericControlProps {
|
|
|
70
78
|
sortable?: boolean;
|
|
71
79
|
}
|
|
72
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Custom hook to sync array items with form state.
|
|
83
|
+
* Uses form events as the source of truth, eliminating dual-state issues.
|
|
84
|
+
*/
|
|
85
|
+
const useArrayItems = (
|
|
86
|
+
input: BaseInputField | undefined,
|
|
87
|
+
): {
|
|
88
|
+
items: ArrayItem[];
|
|
89
|
+
setItems: (items: ArrayItem[]) => void;
|
|
90
|
+
nextKey: () => number;
|
|
91
|
+
} => {
|
|
92
|
+
const alepha = useAlepha();
|
|
93
|
+
const keyCounter = useRef(0);
|
|
94
|
+
|
|
95
|
+
// Initialize from defaultValue
|
|
96
|
+
const [items, setItemsState] = useState<ArrayItem[]>(() => {
|
|
97
|
+
const defaultValue = input?.props?.defaultValue;
|
|
98
|
+
if (Array.isArray(defaultValue)) {
|
|
99
|
+
return defaultValue.map((value) => ({
|
|
100
|
+
key: keyCounter.current++,
|
|
101
|
+
value,
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
return [];
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Sync form value to local state
|
|
108
|
+
const syncFromFormValue = useCallback((formValue: any[] | undefined) => {
|
|
109
|
+
if (!Array.isArray(formValue)) {
|
|
110
|
+
setItemsState([]);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Preserve keys for existing items where possible
|
|
115
|
+
setItemsState((prevItems) => {
|
|
116
|
+
// If lengths match and values are same references, keep existing keys
|
|
117
|
+
if (prevItems.length === formValue.length) {
|
|
118
|
+
const allSame = prevItems.every(
|
|
119
|
+
(item, i) => item.value === formValue[i],
|
|
120
|
+
);
|
|
121
|
+
if (allSame) return prevItems;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Otherwise, create new items with fresh keys
|
|
125
|
+
keyCounter.current = 0;
|
|
126
|
+
return formValue.map((value) => ({
|
|
127
|
+
key: keyCounter.current++,
|
|
128
|
+
value,
|
|
129
|
+
}));
|
|
130
|
+
});
|
|
131
|
+
}, []);
|
|
132
|
+
|
|
133
|
+
// Listen for form changes and reset events
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
if (!input?.form) return;
|
|
136
|
+
|
|
137
|
+
const formId = input.form.id;
|
|
138
|
+
const fieldPath = input.path;
|
|
139
|
+
|
|
140
|
+
const listeners = [
|
|
141
|
+
// Handle form reset
|
|
142
|
+
alepha.events.on("form:reset", (event) => {
|
|
143
|
+
if (event.id === formId) {
|
|
144
|
+
const defaultValue = input.props?.defaultValue;
|
|
145
|
+
keyCounter.current = 0;
|
|
146
|
+
if (Array.isArray(defaultValue)) {
|
|
147
|
+
setItemsState(
|
|
148
|
+
defaultValue.map((value) => ({
|
|
149
|
+
key: keyCounter.current++,
|
|
150
|
+
value,
|
|
151
|
+
})),
|
|
152
|
+
);
|
|
153
|
+
} else {
|
|
154
|
+
setItemsState([]);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}),
|
|
158
|
+
|
|
159
|
+
// Handle external value changes (e.g., programmatic updates)
|
|
160
|
+
alepha.events.on("form:change", (event) => {
|
|
161
|
+
if (event.id === formId && event.path === fieldPath) {
|
|
162
|
+
// Value was changed externally, sync our state
|
|
163
|
+
syncFromFormValue(event.value);
|
|
164
|
+
}
|
|
165
|
+
}),
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
return () => {
|
|
169
|
+
for (const unsub of listeners) {
|
|
170
|
+
unsub();
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}, [alepha, input, syncFromFormValue]);
|
|
174
|
+
|
|
175
|
+
// Update form when items change
|
|
176
|
+
const setItems = useCallback(
|
|
177
|
+
(newItems: ArrayItem[]) => {
|
|
178
|
+
setItemsState(newItems);
|
|
179
|
+
// Update form value - this will trigger form:change but we'll detect it's from us
|
|
180
|
+
input?.set(newItems.map((item) => item.value));
|
|
181
|
+
},
|
|
182
|
+
[input],
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const nextKey = useCallback(() => keyCounter.current++, []);
|
|
186
|
+
|
|
187
|
+
return { items, setItems, nextKey };
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Creates a proper InputField for an array item that integrates with the form system.
|
|
192
|
+
* Uses array index for test IDs to ensure predictable, testable element identifiers.
|
|
193
|
+
*/
|
|
194
|
+
const createArrayItemInput = (
|
|
195
|
+
parentInput: BaseInputField,
|
|
196
|
+
itemSchema: TSchema,
|
|
197
|
+
index: number,
|
|
198
|
+
_itemKey: number,
|
|
199
|
+
value: any,
|
|
200
|
+
onValueChange: (value: any) => void,
|
|
201
|
+
): BaseInputField => {
|
|
202
|
+
return {
|
|
203
|
+
schema: itemSchema,
|
|
204
|
+
path: `${parentInput.path}/${index}`,
|
|
205
|
+
required: false,
|
|
206
|
+
form: parentInput.form,
|
|
207
|
+
props: {
|
|
208
|
+
id: `${parentInput.props.id}-${index}`,
|
|
209
|
+
name: `${parentInput.props.name}[${index}]`,
|
|
210
|
+
defaultValue: value,
|
|
211
|
+
},
|
|
212
|
+
set: onValueChange,
|
|
213
|
+
};
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Creates a proper InputField for a nested object field within an array item.
|
|
218
|
+
* Uses array index for test IDs to ensure predictable, testable element identifiers.
|
|
219
|
+
*/
|
|
220
|
+
const createArrayItemFieldInput = (
|
|
221
|
+
parentInput: BaseInputField,
|
|
222
|
+
itemSchema: TObject,
|
|
223
|
+
fieldName: string,
|
|
224
|
+
index: number,
|
|
225
|
+
_itemKey: number,
|
|
226
|
+
itemValue: any,
|
|
227
|
+
onFieldChange: (field: string, value: any) => void,
|
|
228
|
+
): BaseInputField => {
|
|
229
|
+
const fieldSchema = itemSchema.properties[fieldName];
|
|
230
|
+
return {
|
|
231
|
+
schema: fieldSchema,
|
|
232
|
+
path: `${parentInput.path}/${index}/${fieldName}`,
|
|
233
|
+
required: itemSchema.required?.includes(fieldName) ?? false,
|
|
234
|
+
form: parentInput.form,
|
|
235
|
+
props: {
|
|
236
|
+
id: `${parentInput.props.id}-${index}-${fieldName}`,
|
|
237
|
+
name: `${parentInput.props.name}[${index}].${fieldName}`,
|
|
238
|
+
defaultValue: itemValue?.[fieldName],
|
|
239
|
+
},
|
|
240
|
+
set: (value: any) => onFieldChange(fieldName, value),
|
|
241
|
+
};
|
|
242
|
+
};
|
|
243
|
+
|
|
73
244
|
/**
|
|
74
245
|
* ControlArray component for editing arrays of schema items.
|
|
75
246
|
*
|
|
@@ -79,6 +250,7 @@ export interface ControlArrayProps extends GenericControlProps {
|
|
|
79
250
|
* - Supports arrays of primitives
|
|
80
251
|
* - Grid layout for object items
|
|
81
252
|
* - Min/max constraints
|
|
253
|
+
* - Syncs with form state (handles external updates and resets)
|
|
82
254
|
*
|
|
83
255
|
* @example
|
|
84
256
|
* ```tsx
|
|
@@ -102,42 +274,7 @@ export interface ControlArrayProps extends GenericControlProps {
|
|
|
102
274
|
*/
|
|
103
275
|
const ControlArray = (props: ControlArrayProps) => {
|
|
104
276
|
const { inputProps } = parseInput(props, {});
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
// Initialize items with unique keys for React
|
|
108
|
-
const [items, setItems] = useState<Array<{ key: number; value: any }>>(() => {
|
|
109
|
-
const defaultValue = props.input?.props?.defaultValue;
|
|
110
|
-
if (Array.isArray(defaultValue)) {
|
|
111
|
-
return defaultValue.map((value) => ({
|
|
112
|
-
key: idCounter.current++,
|
|
113
|
-
value,
|
|
114
|
-
}));
|
|
115
|
-
}
|
|
116
|
-
return [];
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// Listen for form reset events
|
|
120
|
-
useEvents(
|
|
121
|
-
{
|
|
122
|
-
"form:reset": (event) => {
|
|
123
|
-
if (event.id === props.input?.form?.id) {
|
|
124
|
-
const defaultValue = props.input?.props?.defaultValue;
|
|
125
|
-
if (Array.isArray(defaultValue)) {
|
|
126
|
-
idCounter.current = 0;
|
|
127
|
-
setItems(
|
|
128
|
-
defaultValue.map((value) => ({
|
|
129
|
-
key: idCounter.current++,
|
|
130
|
-
value,
|
|
131
|
-
})),
|
|
132
|
-
);
|
|
133
|
-
} else {
|
|
134
|
-
setItems([]);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
[props.input],
|
|
140
|
-
);
|
|
277
|
+
const { items, setItems, nextKey } = useArrayItems(props.input);
|
|
141
278
|
|
|
142
279
|
if (!props.input?.props) {
|
|
143
280
|
return null;
|
|
@@ -148,14 +285,10 @@ const ControlArray = (props: ControlArrayProps) => {
|
|
|
148
285
|
return null;
|
|
149
286
|
}
|
|
150
287
|
|
|
151
|
-
const itemSchema = (schema as
|
|
288
|
+
const itemSchema = (schema as { items: TSchema }).items;
|
|
152
289
|
const isObjectItem = itemSchema && "properties" in itemSchema;
|
|
153
290
|
const { min = 0, max = Number.POSITIVE_INFINITY, columns = 1 } = props;
|
|
154
291
|
|
|
155
|
-
const updateFormValue = (newItems: Array<{ key: number; value: any }>) => {
|
|
156
|
-
props.input.set(newItems.map((item) => item.value));
|
|
157
|
-
};
|
|
158
|
-
|
|
159
292
|
const handleAdd = () => {
|
|
160
293
|
if (items.length >= max) return;
|
|
161
294
|
|
|
@@ -174,23 +307,18 @@ const ControlArray = (props: ControlArrayProps) => {
|
|
|
174
307
|
newValue = "";
|
|
175
308
|
}
|
|
176
309
|
|
|
177
|
-
|
|
178
|
-
setItems(newItems);
|
|
179
|
-
updateFormValue(newItems);
|
|
310
|
+
setItems([...items, { key: nextKey(), value: newValue }]);
|
|
180
311
|
};
|
|
181
312
|
|
|
182
313
|
const handleRemove = (index: number) => {
|
|
183
314
|
if (items.length <= min) return;
|
|
184
|
-
|
|
185
|
-
setItems(newItems);
|
|
186
|
-
updateFormValue(newItems);
|
|
315
|
+
setItems(items.filter((_, i) => i !== index));
|
|
187
316
|
};
|
|
188
317
|
|
|
189
318
|
const handleItemChange = (index: number, value: any) => {
|
|
190
319
|
const newItems = [...items];
|
|
191
320
|
newItems[index] = { ...newItems[index], value };
|
|
192
321
|
setItems(newItems);
|
|
193
|
-
updateFormValue(newItems);
|
|
194
322
|
};
|
|
195
323
|
|
|
196
324
|
const handleFieldChange = (index: number, field: string, value: any) => {
|
|
@@ -200,12 +328,12 @@ const ControlArray = (props: ControlArrayProps) => {
|
|
|
200
328
|
value: { ...newItems[index].value, [field]: value },
|
|
201
329
|
};
|
|
202
330
|
setItems(newItems);
|
|
203
|
-
updateFormValue(newItems);
|
|
204
331
|
};
|
|
205
332
|
|
|
206
333
|
const colSpan = 12 / columns;
|
|
207
|
-
const
|
|
208
|
-
|
|
334
|
+
const objectItemSchema = isObjectItem ? (itemSchema as TObject) : null;
|
|
335
|
+
const fieldNames = objectItemSchema
|
|
336
|
+
? Object.keys(objectItemSchema.properties)
|
|
209
337
|
: [];
|
|
210
338
|
|
|
211
339
|
const renderItems = () => (
|
|
@@ -229,34 +357,23 @@ const ControlArray = (props: ControlArrayProps) => {
|
|
|
229
357
|
</ActionIcon>
|
|
230
358
|
)}
|
|
231
359
|
|
|
232
|
-
{
|
|
360
|
+
{objectItemSchema ? (
|
|
233
361
|
<Grid style={{ flex: 1 }} gutter="sm">
|
|
234
362
|
{fieldNames.map((fieldName) => {
|
|
235
|
-
const fieldSchema = (itemSchema as TObject).properties[
|
|
236
|
-
fieldName
|
|
237
|
-
];
|
|
238
363
|
const fieldControlProps = props.controlProps?.[fieldName] ?? {};
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
path: `${props.input.path}/${index}/${fieldName}`,
|
|
249
|
-
required:
|
|
250
|
-
(itemSchema as TObject).required?.includes(fieldName) ??
|
|
251
|
-
false,
|
|
252
|
-
form: props.input.form,
|
|
253
|
-
set: (value: any) =>
|
|
254
|
-
handleFieldChange(index, fieldName, value),
|
|
255
|
-
};
|
|
364
|
+
const fieldInput = createArrayItemFieldInput(
|
|
365
|
+
props.input,
|
|
366
|
+
objectItemSchema,
|
|
367
|
+
fieldName,
|
|
368
|
+
index,
|
|
369
|
+
item.key,
|
|
370
|
+
item.value,
|
|
371
|
+
(field, value) => handleFieldChange(index, field, value),
|
|
372
|
+
);
|
|
256
373
|
|
|
257
374
|
return (
|
|
258
375
|
<Grid.Col key={fieldName} span={colSpan}>
|
|
259
|
-
<Control input={
|
|
376
|
+
<Control input={fieldInput} {...fieldControlProps} />
|
|
260
377
|
</Grid.Col>
|
|
261
378
|
);
|
|
262
379
|
})}
|
|
@@ -264,20 +381,14 @@ const ControlArray = (props: ControlArrayProps) => {
|
|
|
264
381
|
) : (
|
|
265
382
|
<Flex style={{ flex: 1 }}>
|
|
266
383
|
<Control
|
|
267
|
-
input={
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
path: `${props.input.path}/${index}`,
|
|
276
|
-
required: false,
|
|
277
|
-
form: props.input.form,
|
|
278
|
-
set: (value: any) => handleItemChange(index, value),
|
|
279
|
-
} as BaseInputField
|
|
280
|
-
}
|
|
384
|
+
input={createArrayItemInput(
|
|
385
|
+
props.input,
|
|
386
|
+
itemSchema,
|
|
387
|
+
index,
|
|
388
|
+
item.key,
|
|
389
|
+
item.value,
|
|
390
|
+
(value) => handleItemChange(index, value),
|
|
391
|
+
)}
|
|
281
392
|
{...props.itemControlProps}
|
|
282
393
|
/>
|
|
283
394
|
</Flex>
|