@alepha/ui 0.16.1 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/{AdminApiKeys-GMORg-1l.js → AdminApiKeys-CF_qOO3u.js} +20 -19
- package/dist/admin/AdminApiKeys-CF_qOO3u.js.map +1 -0
- package/dist/admin/{AdminAudits-pkWrjq1Z.js → AdminAudits-BQno3hZG.js} +7 -7
- package/dist/admin/AdminAudits-BQno3hZG.js.map +1 -0
- package/dist/admin/{AdminFiles-WeQbsCsl.js → AdminFiles-kvuUaASF.js} +3 -4
- package/dist/admin/{AdminFiles-WeQbsCsl.js.map → AdminFiles-kvuUaASF.js.map} +1 -1
- package/dist/admin/AdminJobDashboard-CrPxp0W1.js +485 -0
- package/dist/admin/AdminJobDashboard-CrPxp0W1.js.map +1 -0
- package/dist/admin/AdminJobExecutions-D-b4Zt7W.js +678 -0
- package/dist/admin/AdminJobExecutions-D-b4Zt7W.js.map +1 -0
- package/dist/admin/AdminJobRegistry-CNX5cpDx.js +301 -0
- package/dist/admin/AdminJobRegistry-CNX5cpDx.js.map +1 -0
- package/dist/admin/{AdminLayout-BqZiXx4H.js → AdminLayout-e-ZP5nWw.js} +6 -9
- package/dist/admin/AdminLayout-e-ZP5nWw.js.map +1 -0
- package/dist/admin/{AdminNotifications-Ds5Un0NJ.js → AdminNotifications-DeHJFf6W.js} +3 -4
- package/dist/admin/{AdminNotifications-Ds5Un0NJ.js.map → AdminNotifications-DeHJFf6W.js.map} +1 -1
- package/dist/admin/AdminParameters-iQE8o7a7.js +774 -0
- package/dist/admin/AdminParameters-iQE8o7a7.js.map +1 -0
- package/dist/admin/{AdminSessions-DzIOxM3b.js → AdminSessions-oKJCbd7w.js} +5 -6
- package/dist/admin/AdminSessions-oKJCbd7w.js.map +1 -0
- package/dist/admin/{AdminUserAudits-CiUPN2BC.js → AdminUserAudits-BNCEle_E.js} +6 -7
- package/dist/admin/AdminUserAudits-BNCEle_E.js.map +1 -0
- package/dist/admin/{AdminUserCreate-BwQKr4xE.js → AdminUserCreate-CgqeFwCt.js} +6 -6
- package/dist/admin/AdminUserCreate-CgqeFwCt.js.map +1 -0
- package/dist/admin/{AdminUserDetails-uqtC5aJ1.js → AdminUserDetails-DDe1A1GP.js} +30 -28
- package/dist/admin/AdminUserDetails-DDe1A1GP.js.map +1 -0
- package/dist/admin/{AdminUserLayout-CiPay35T.js → AdminUserLayout-HAlobhWf.js} +20 -19
- package/dist/admin/AdminUserLayout-HAlobhWf.js.map +1 -0
- package/dist/admin/{AdminUserSessions-DAE8Nf1F.js → AdminUserSessions-Bq1LnVLf.js} +5 -6
- package/dist/admin/AdminUserSessions-Bq1LnVLf.js.map +1 -0
- package/dist/admin/{AdminUserSettings-EbahaV2a.js → AdminUserSettings-BRsBZoxV.js} +10 -9
- package/dist/admin/AdminUserSettings-BRsBZoxV.js.map +1 -0
- package/dist/admin/{AdminUsers-Dcjh0KNW.js → AdminUsers-D71kIOSn.js} +6 -7
- package/dist/admin/AdminUsers-D71kIOSn.js.map +1 -0
- package/dist/admin/index.d.ts +21 -85
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +66 -88
- package/dist/admin/index.js.map +1 -1
- package/dist/auth/{AuthLayout-Dj5K4SIN.js → AuthLayout-CdJcrPs4.js} +2 -3
- package/dist/auth/{AuthLayout-Dj5K4SIN.js.map → AuthLayout-CdJcrPs4.js.map} +1 -1
- 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-BS_FYTy0.js} +19 -13
- package/dist/auth/Login-BS_FYTy0.js.map +1 -0
- package/dist/auth/{Profile-Bxj8Nwom.js → Profile-CjDsW378.js} +17 -12
- package/dist/auth/Profile-CjDsW378.js.map +1 -0
- package/dist/auth/{Register-Ce675Crg.js → Register-C5eqzAaD.js} +27 -17
- package/dist/auth/Register-C5eqzAaD.js.map +1 -0
- package/dist/auth/{ResetPassword-DWdt7c40.js → ResetPassword-XifinVao.js} +17 -10
- package/dist/auth/ResetPassword-XifinVao.js.map +1 -0
- package/dist/auth/{VerifyEmail-CI4JwByV.js → VerifyEmail-DTgbeJOO.js} +9 -6
- package/dist/auth/VerifyEmail-DTgbeJOO.js.map +1 -0
- package/dist/auth/index.d.ts +18 -14
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +19 -18
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/rolldown-runtime-CjeV3_4I.js +18 -0
- package/dist/core/index.d.ts +182 -92
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +789 -476
- package/dist/core/index.js.map +1 -1
- package/dist/demo/DemoDataTable-lnBKWBf8.js +362 -0
- package/dist/demo/DemoDataTable-lnBKWBf8.js.map +1 -0
- package/dist/demo/{DemoHome-Cce2bWmg.js → DemoHome-CUMZsYaH.js} +6 -6
- package/dist/demo/DemoHome-CUMZsYaH.js.map +1 -0
- package/dist/demo/{DemoJsonViewer-Dgdk3Txb.js → DemoJsonViewer-_uokbGaW.js} +18 -19
- package/dist/demo/DemoJsonViewer-_uokbGaW.js.map +1 -0
- package/dist/demo/{DemoLayout-B20TEuhV.js → DemoLayout-DHVoacE6.js} +4 -5
- package/dist/demo/DemoLayout-DHVoacE6.js.map +1 -0
- package/dist/demo/{DemoLogin-CvCG2WVh.js → DemoLogin-DjJ9314c.js} +27 -24
- package/dist/demo/DemoLogin-DjJ9314c.js.map +1 -0
- package/dist/demo/{DemoRegister-CmeHbOAs.js → DemoRegister-DzkJ5M83.js} +39 -32
- package/dist/demo/DemoRegister-DzkJ5M83.js.map +1 -0
- package/dist/demo/{DemoResetPassword-CKO5iA_6.js → DemoResetPassword-DWh4_BpQ.js} +30 -26
- package/dist/demo/DemoResetPassword-DWh4_BpQ.js.map +1 -0
- package/dist/demo/{DemoSidebar-MVmQKfMt.js → DemoSidebar-C1csnGhX.js} +4 -5
- package/dist/demo/DemoSidebar-C1csnGhX.js.map +1 -0
- package/dist/demo/{DemoTypeForm-w-qtfRlC.js → DemoTypeForm-CWz6fJrJ.js} +4 -5
- package/dist/demo/DemoTypeForm-CWz6fJrJ.js.map +1 -0
- package/dist/demo/{DemoVerifyEmail-C8FFJT5A.js → DemoVerifyEmail-DbU_tCj8.js} +16 -16
- package/dist/demo/DemoVerifyEmail-DbU_tCj8.js.map +1 -0
- package/dist/{auth/IconGoogle-DpSlPZ1u.js → demo/IconGoogle-Ch1m3Uzl.js} +2 -4
- package/dist/demo/{IconGoogle-CbBF8Hqq.js.map → IconGoogle-Ch1m3Uzl.js.map} +1 -1
- package/dist/demo/{Showcase-CQrMWars.js → Showcase-BzoXNlCn.js} +11 -13
- package/dist/demo/Showcase-BzoXNlCn.js.map +1 -0
- package/dist/demo/index.d.ts +3 -70
- package/dist/demo/index.d.ts.map +1 -1
- package/dist/demo/index.js +11 -15
- package/dist/demo/index.js.map +1 -1
- package/dist/json/index.js +2 -2
- package/dist/json/index.js.map +1 -1
- package/package.json +11 -5
- package/src/admin/AdminRouter.ts +51 -29
- package/src/admin/components/AdminLayout.tsx +6 -9
- package/src/admin/components/audits/AdminAudits.tsx +5 -5
- package/src/admin/components/jobs/AdminJobDashboard.tsx +455 -0
- package/src/admin/components/jobs/AdminJobExecutions.tsx +693 -0
- package/src/admin/components/jobs/AdminJobRegistry.tsx +325 -0
- package/src/admin/components/keys/AdminApiKeys.tsx +28 -31
- package/src/admin/components/parameters/AdminParameters.tsx +156 -78
- package/src/admin/components/parameters/ParameterDetails.tsx +173 -108
- package/src/admin/components/parameters/ParameterEmptyState.tsx +27 -0
- package/src/admin/components/parameters/ParameterHistory.tsx +22 -35
- package/src/admin/components/parameters/ParameterTree.tsx +283 -109
- package/src/admin/components/parameters/types.ts +3 -3
- package/src/admin/components/sessions/AdminSessions.tsx +3 -3
- package/src/admin/components/shared/AdminResourceHeader.tsx +20 -16
- package/src/admin/components/users/AdminUserAudits.tsx +5 -5
- package/src/admin/components/users/AdminUserCreate.tsx +3 -3
- package/src/admin/components/users/AdminUserDetails.tsx +51 -53
- package/src/admin/components/users/AdminUserLayout.tsx +7 -7
- package/src/admin/components/users/AdminUserSessions.tsx +3 -3
- package/src/admin/components/users/AdminUserSettings.tsx +9 -9
- package/src/admin/components/users/AdminUsers.tsx +5 -5
- package/src/admin/components/verifications/AdminVerifications.tsx +3 -3
- package/src/admin/index.ts +0 -24
- package/src/admin/primitives/$uiAdmin.ts +2 -2
- package/src/auth/AuthRouter.ts +1 -0
- package/src/auth/components/Login.tsx +13 -13
- package/src/auth/components/Profile.tsx +17 -26
- package/src/auth/components/Register.tsx +21 -31
- package/src/auth/components/ResetPassword.tsx +13 -22
- package/src/auth/components/VerifyEmail.tsx +5 -5
- package/src/auth/components/buttons/UserButton.tsx +14 -4
- package/src/core/components/buttons/ActionButton.tsx +13 -17
- package/src/core/components/buttons/DarkModeButton.tsx +8 -4
- package/src/core/components/buttons/ToggleSidebarButton.tsx +3 -5
- package/src/core/components/data/ErrorViewer.tsx +15 -15
- package/src/core/components/dialogs/AlertDialog.tsx +3 -3
- package/src/core/components/dialogs/ConfirmDialog.tsx +3 -3
- package/src/core/components/dialogs/PromptDialog.tsx +3 -3
- package/src/core/components/form/Control.tsx +19 -32
- package/src/core/components/form/ControlArray.tsx +206 -96
- package/src/core/components/form/ControlObject.tsx +3 -3
- package/src/core/components/form/ControlQueryBuilder.tsx +20 -22
- package/src/core/components/form/ControlSelect.tsx +4 -0
- package/src/core/components/form/TypeForm.browser.spec.tsx +727 -0
- package/src/core/components/form/TypeForm.tsx +7 -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/Omnibar.tsx +2 -1
- package/src/core/components/layout/Sidebar.tsx +63 -19
- package/src/core/components/table/ColumnPicker.tsx +47 -31
- package/src/core/components/table/DataTable.tsx +277 -201
- package/src/core/components/table/DataTableFilters.tsx +8 -0
- package/src/core/components/table/DataTableToolbar.tsx +98 -5
- package/src/core/components/table/FilterPicker.tsx +28 -26
- package/src/core/components/table/types.ts +52 -37
- package/src/core/components/table/useTableSelection.ts +83 -0
- 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 +8 -7
- package/src/core/utils/parseInput.ts +1 -0
- package/src/core/utils/string.ts +28 -4
- package/src/demo/components/DemoHome.tsx +5 -5
- package/src/demo/components/DemoLayout.tsx +6 -2
- package/src/demo/components/core/DemoDataTable.tsx +209 -5
- package/src/demo/components/json/DemoJsonViewer.tsx +1 -1
- package/src/demo/components/shared/MacWindow.tsx +7 -7
- package/src/demo/components/shared/Showcase.tsx +3 -3
- package/src/demo/index.ts +0 -11
- package/src/json/components/JsonViewer.tsx +3 -3
- package/dist/admin/AdminApiKeys-DsmGnHNh.js +0 -3
- package/dist/admin/AdminApiKeys-GMORg-1l.js.map +0 -1
- package/dist/admin/AdminAudits-8SM96viT.js +0 -3
- package/dist/admin/AdminAudits-pkWrjq1Z.js.map +0 -1
- package/dist/admin/AdminFiles-B56ocq4H.js +0 -3
- package/dist/admin/AdminJobs-B-q9iGO3.js +0 -697
- package/dist/admin/AdminJobs-B-q9iGO3.js.map +0 -1
- package/dist/admin/AdminJobs-CED1syCn.js +0 -3
- package/dist/admin/AdminLayout-BqZiXx4H.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/AdminSessions-DzIOxM3b.js.map +0 -1
- package/dist/admin/AdminUserAudits-CiUPN2BC.js.map +0 -1
- package/dist/admin/AdminUserAudits-Cj79gENT.js +0 -3
- package/dist/admin/AdminUserCreate-BwQKr4xE.js.map +0 -1
- package/dist/admin/AdminUserCreate-Cq-mUmBs.js +0 -3
- package/dist/admin/AdminUserDetails-DRjVAPFd.js +0 -3
- package/dist/admin/AdminUserDetails-uqtC5aJ1.js.map +0 -1
- package/dist/admin/AdminUserLayout-CGzmHHby.js +0 -3
- package/dist/admin/AdminUserLayout-CiPay35T.js.map +0 -1
- package/dist/admin/AdminUserSessions-DAE8Nf1F.js.map +0 -1
- package/dist/admin/AdminUserSessions-DcdzuNZ9.js +0 -3
- package/dist/admin/AdminUserSettings-D7V6-ceX.js +0 -3
- package/dist/admin/AdminUserSettings-EbahaV2a.js.map +0 -1
- package/dist/admin/AdminUsers-D9nyzGqQ.js +0 -3
- package/dist/admin/AdminUsers-Dcjh0KNW.js.map +0 -1
- package/dist/auth/Login-BBqTosqZ.js.map +0 -1
- package/dist/auth/Login-CoU63mMR.js +0 -4
- package/dist/auth/Profile-Bxj8Nwom.js.map +0 -1
- 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/ResetPassword-DWdt7c40.js.map +0 -1
- package/dist/auth/VerifyEmail-CI4JwByV.js.map +0 -1
- package/dist/auth/VerifyEmail-DAfqVm5s.js +0 -3
- package/dist/demo/DemoDataTable-CguplbR7.js +0 -150
- package/dist/demo/DemoDataTable-CguplbR7.js.map +0 -1
- package/dist/demo/DemoHome-Cce2bWmg.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
- package/src/admin/components/jobs/AdminJobs.tsx +0 -772
|
@@ -3,15 +3,14 @@ import {
|
|
|
3
3
|
Fieldset,
|
|
4
4
|
Flex,
|
|
5
5
|
Grid,
|
|
6
|
-
Stack,
|
|
7
6
|
Text,
|
|
8
7
|
UnstyledButton,
|
|
9
8
|
} from "@mantine/core";
|
|
10
9
|
import { IconGripVertical, IconPlus, IconTrash } from "@tabler/icons-react";
|
|
11
10
|
import type { TObject, TSchema } from "alepha";
|
|
12
|
-
import {
|
|
11
|
+
import { useAlepha } from "alepha/react";
|
|
13
12
|
import type { BaseInputField } from "alepha/react/form";
|
|
14
|
-
import { useRef, useState } from "react";
|
|
13
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
15
14
|
import { ui } from "../../constants/ui.ts";
|
|
16
15
|
import {
|
|
17
16
|
type GenericControlProps,
|
|
@@ -19,6 +18,14 @@ import {
|
|
|
19
18
|
} from "../../utils/parseInput.ts";
|
|
20
19
|
import Control, { type ControlProps } from "./Control.tsx";
|
|
21
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Represents an array item with a stable key for React reconciliation.
|
|
23
|
+
*/
|
|
24
|
+
interface ArrayItem {
|
|
25
|
+
key: number;
|
|
26
|
+
value: any;
|
|
27
|
+
}
|
|
28
|
+
|
|
22
29
|
export interface ControlArrayProps extends GenericControlProps {
|
|
23
30
|
/**
|
|
24
31
|
* Minimum number of items allowed.
|
|
@@ -70,6 +77,169 @@ export interface ControlArrayProps extends GenericControlProps {
|
|
|
70
77
|
sortable?: boolean;
|
|
71
78
|
}
|
|
72
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Custom hook to sync array items with form state.
|
|
82
|
+
* Uses form events as the source of truth, eliminating dual-state issues.
|
|
83
|
+
*/
|
|
84
|
+
const useArrayItems = (
|
|
85
|
+
input: BaseInputField | undefined,
|
|
86
|
+
): {
|
|
87
|
+
items: ArrayItem[];
|
|
88
|
+
setItems: (items: ArrayItem[]) => void;
|
|
89
|
+
nextKey: () => number;
|
|
90
|
+
} => {
|
|
91
|
+
const alepha = useAlepha();
|
|
92
|
+
const keyCounter = useRef(0);
|
|
93
|
+
|
|
94
|
+
// Initialize from defaultValue
|
|
95
|
+
const [items, setItemsState] = useState<ArrayItem[]>(() => {
|
|
96
|
+
const defaultValue = input?.props?.defaultValue;
|
|
97
|
+
if (Array.isArray(defaultValue)) {
|
|
98
|
+
return defaultValue.map((value) => ({
|
|
99
|
+
key: keyCounter.current++,
|
|
100
|
+
value,
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
return [];
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Sync form value to local state
|
|
107
|
+
const syncFromFormValue = useCallback((formValue: any[] | undefined) => {
|
|
108
|
+
if (!Array.isArray(formValue)) {
|
|
109
|
+
setItemsState([]);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Preserve keys for existing items where possible
|
|
114
|
+
setItemsState((prevItems) => {
|
|
115
|
+
// If lengths match and values are same references, keep existing keys
|
|
116
|
+
if (prevItems.length === formValue.length) {
|
|
117
|
+
const allSame = prevItems.every(
|
|
118
|
+
(item, i) => item.value === formValue[i],
|
|
119
|
+
);
|
|
120
|
+
if (allSame) return prevItems;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Otherwise, create new items with fresh keys
|
|
124
|
+
keyCounter.current = 0;
|
|
125
|
+
return formValue.map((value) => ({
|
|
126
|
+
key: keyCounter.current++,
|
|
127
|
+
value,
|
|
128
|
+
}));
|
|
129
|
+
});
|
|
130
|
+
}, []);
|
|
131
|
+
|
|
132
|
+
// Listen for form changes and reset events
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
if (!input?.form) return;
|
|
135
|
+
|
|
136
|
+
const formId = input.form.id;
|
|
137
|
+
const fieldPath = input.path;
|
|
138
|
+
|
|
139
|
+
const listeners = [
|
|
140
|
+
// Handle form reset
|
|
141
|
+
alepha.events.on("form:reset", (event) => {
|
|
142
|
+
if (event.id === formId) {
|
|
143
|
+
const defaultValue = input.props?.defaultValue;
|
|
144
|
+
keyCounter.current = 0;
|
|
145
|
+
if (Array.isArray(defaultValue)) {
|
|
146
|
+
setItemsState(
|
|
147
|
+
defaultValue.map((value) => ({
|
|
148
|
+
key: keyCounter.current++,
|
|
149
|
+
value,
|
|
150
|
+
})),
|
|
151
|
+
);
|
|
152
|
+
} else {
|
|
153
|
+
setItemsState([]);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}),
|
|
157
|
+
|
|
158
|
+
// Handle external value changes (e.g., programmatic updates)
|
|
159
|
+
alepha.events.on("form:change", (event) => {
|
|
160
|
+
if (event.id === formId && event.path === fieldPath) {
|
|
161
|
+
// Value was changed externally, sync our state
|
|
162
|
+
syncFromFormValue(event.value);
|
|
163
|
+
}
|
|
164
|
+
}),
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
return () => {
|
|
168
|
+
for (const unsub of listeners) {
|
|
169
|
+
unsub();
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}, [alepha, input, syncFromFormValue]);
|
|
173
|
+
|
|
174
|
+
// Update form when items change
|
|
175
|
+
const setItems = useCallback(
|
|
176
|
+
(newItems: ArrayItem[]) => {
|
|
177
|
+
setItemsState(newItems);
|
|
178
|
+
// Update form value - this will trigger form:change but we'll detect it's from us
|
|
179
|
+
input?.set(newItems.map((item) => item.value));
|
|
180
|
+
},
|
|
181
|
+
[input],
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const nextKey = useCallback(() => keyCounter.current++, []);
|
|
185
|
+
|
|
186
|
+
return { items, setItems, nextKey };
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Creates a proper InputField for an array item that integrates with the form system.
|
|
191
|
+
* Uses array index for test IDs to ensure predictable, testable element identifiers.
|
|
192
|
+
*/
|
|
193
|
+
const createArrayItemInput = (
|
|
194
|
+
parentInput: BaseInputField,
|
|
195
|
+
itemSchema: TSchema,
|
|
196
|
+
index: number,
|
|
197
|
+
_itemKey: number,
|
|
198
|
+
value: any,
|
|
199
|
+
onValueChange: (value: any) => void,
|
|
200
|
+
): BaseInputField => {
|
|
201
|
+
return {
|
|
202
|
+
schema: itemSchema,
|
|
203
|
+
path: `${parentInput.path}/${index}`,
|
|
204
|
+
required: false,
|
|
205
|
+
form: parentInput.form,
|
|
206
|
+
props: {
|
|
207
|
+
id: `${parentInput.props.id}-${index}`,
|
|
208
|
+
name: `${parentInput.props.name}[${index}]`,
|
|
209
|
+
defaultValue: value,
|
|
210
|
+
},
|
|
211
|
+
set: onValueChange,
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Creates a proper InputField for a nested object field within an array item.
|
|
217
|
+
* Uses array index for test IDs to ensure predictable, testable element identifiers.
|
|
218
|
+
*/
|
|
219
|
+
const createArrayItemFieldInput = (
|
|
220
|
+
parentInput: BaseInputField,
|
|
221
|
+
itemSchema: TObject,
|
|
222
|
+
fieldName: string,
|
|
223
|
+
index: number,
|
|
224
|
+
_itemKey: number,
|
|
225
|
+
itemValue: any,
|
|
226
|
+
onFieldChange: (field: string, value: any) => void,
|
|
227
|
+
): BaseInputField => {
|
|
228
|
+
const fieldSchema = itemSchema.properties[fieldName];
|
|
229
|
+
return {
|
|
230
|
+
schema: fieldSchema,
|
|
231
|
+
path: `${parentInput.path}/${index}/${fieldName}`,
|
|
232
|
+
required: itemSchema.required?.includes(fieldName) ?? false,
|
|
233
|
+
form: parentInput.form,
|
|
234
|
+
props: {
|
|
235
|
+
id: `${parentInput.props.id}-${index}-${fieldName}`,
|
|
236
|
+
name: `${parentInput.props.name}[${index}].${fieldName}`,
|
|
237
|
+
defaultValue: itemValue?.[fieldName],
|
|
238
|
+
},
|
|
239
|
+
set: (value: any) => onFieldChange(fieldName, value),
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
|
|
73
243
|
/**
|
|
74
244
|
* ControlArray component for editing arrays of schema items.
|
|
75
245
|
*
|
|
@@ -79,6 +249,7 @@ export interface ControlArrayProps extends GenericControlProps {
|
|
|
79
249
|
* - Supports arrays of primitives
|
|
80
250
|
* - Grid layout for object items
|
|
81
251
|
* - Min/max constraints
|
|
252
|
+
* - Syncs with form state (handles external updates and resets)
|
|
82
253
|
*
|
|
83
254
|
* @example
|
|
84
255
|
* ```tsx
|
|
@@ -102,42 +273,7 @@ export interface ControlArrayProps extends GenericControlProps {
|
|
|
102
273
|
*/
|
|
103
274
|
const ControlArray = (props: ControlArrayProps) => {
|
|
104
275
|
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
|
-
);
|
|
276
|
+
const { items, setItems, nextKey } = useArrayItems(props.input);
|
|
141
277
|
|
|
142
278
|
if (!props.input?.props) {
|
|
143
279
|
return null;
|
|
@@ -148,14 +284,10 @@ const ControlArray = (props: ControlArrayProps) => {
|
|
|
148
284
|
return null;
|
|
149
285
|
}
|
|
150
286
|
|
|
151
|
-
const itemSchema = (schema as
|
|
287
|
+
const itemSchema = (schema as { items: TSchema }).items;
|
|
152
288
|
const isObjectItem = itemSchema && "properties" in itemSchema;
|
|
153
289
|
const { min = 0, max = Number.POSITIVE_INFINITY, columns = 1 } = props;
|
|
154
290
|
|
|
155
|
-
const updateFormValue = (newItems: Array<{ key: number; value: any }>) => {
|
|
156
|
-
props.input.set(newItems.map((item) => item.value));
|
|
157
|
-
};
|
|
158
|
-
|
|
159
291
|
const handleAdd = () => {
|
|
160
292
|
if (items.length >= max) return;
|
|
161
293
|
|
|
@@ -174,23 +306,18 @@ const ControlArray = (props: ControlArrayProps) => {
|
|
|
174
306
|
newValue = "";
|
|
175
307
|
}
|
|
176
308
|
|
|
177
|
-
|
|
178
|
-
setItems(newItems);
|
|
179
|
-
updateFormValue(newItems);
|
|
309
|
+
setItems([...items, { key: nextKey(), value: newValue }]);
|
|
180
310
|
};
|
|
181
311
|
|
|
182
312
|
const handleRemove = (index: number) => {
|
|
183
313
|
if (items.length <= min) return;
|
|
184
|
-
|
|
185
|
-
setItems(newItems);
|
|
186
|
-
updateFormValue(newItems);
|
|
314
|
+
setItems(items.filter((_, i) => i !== index));
|
|
187
315
|
};
|
|
188
316
|
|
|
189
317
|
const handleItemChange = (index: number, value: any) => {
|
|
190
318
|
const newItems = [...items];
|
|
191
319
|
newItems[index] = { ...newItems[index], value };
|
|
192
320
|
setItems(newItems);
|
|
193
|
-
updateFormValue(newItems);
|
|
194
321
|
};
|
|
195
322
|
|
|
196
323
|
const handleFieldChange = (index: number, field: string, value: any) => {
|
|
@@ -200,16 +327,16 @@ const ControlArray = (props: ControlArrayProps) => {
|
|
|
200
327
|
value: { ...newItems[index].value, [field]: value },
|
|
201
328
|
};
|
|
202
329
|
setItems(newItems);
|
|
203
|
-
updateFormValue(newItems);
|
|
204
330
|
};
|
|
205
331
|
|
|
206
332
|
const colSpan = 12 / columns;
|
|
207
|
-
const
|
|
208
|
-
|
|
333
|
+
const objectItemSchema = isObjectItem ? (itemSchema as TObject) : null;
|
|
334
|
+
const fieldNames = objectItemSchema
|
|
335
|
+
? Object.keys(objectItemSchema.properties)
|
|
209
336
|
: [];
|
|
210
337
|
|
|
211
338
|
const renderItems = () => (
|
|
212
|
-
<
|
|
339
|
+
<Flex direction="column" gap="sm">
|
|
213
340
|
{items.map((item, index) => (
|
|
214
341
|
<Flex
|
|
215
342
|
key={item.key}
|
|
@@ -229,34 +356,23 @@ const ControlArray = (props: ControlArrayProps) => {
|
|
|
229
356
|
</ActionIcon>
|
|
230
357
|
)}
|
|
231
358
|
|
|
232
|
-
{
|
|
359
|
+
{objectItemSchema ? (
|
|
233
360
|
<Grid style={{ flex: 1 }} gutter="sm">
|
|
234
361
|
{fieldNames.map((fieldName) => {
|
|
235
|
-
const fieldSchema = (itemSchema as TObject).properties[
|
|
236
|
-
fieldName
|
|
237
|
-
];
|
|
238
362
|
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
|
-
};
|
|
363
|
+
const fieldInput = createArrayItemFieldInput(
|
|
364
|
+
props.input,
|
|
365
|
+
objectItemSchema,
|
|
366
|
+
fieldName,
|
|
367
|
+
index,
|
|
368
|
+
item.key,
|
|
369
|
+
item.value,
|
|
370
|
+
(field, value) => handleFieldChange(index, field, value),
|
|
371
|
+
);
|
|
256
372
|
|
|
257
373
|
return (
|
|
258
374
|
<Grid.Col key={fieldName} span={colSpan}>
|
|
259
|
-
<Control input={
|
|
375
|
+
<Control input={fieldInput} {...fieldControlProps} />
|
|
260
376
|
</Grid.Col>
|
|
261
377
|
);
|
|
262
378
|
})}
|
|
@@ -264,20 +380,14 @@ const ControlArray = (props: ControlArrayProps) => {
|
|
|
264
380
|
) : (
|
|
265
381
|
<Flex style={{ flex: 1 }}>
|
|
266
382
|
<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
|
-
}
|
|
383
|
+
input={createArrayItemInput(
|
|
384
|
+
props.input,
|
|
385
|
+
itemSchema,
|
|
386
|
+
index,
|
|
387
|
+
item.key,
|
|
388
|
+
item.value,
|
|
389
|
+
(value) => handleItemChange(index, value),
|
|
390
|
+
)}
|
|
281
391
|
{...props.itemControlProps}
|
|
282
392
|
/>
|
|
283
393
|
</Flex>
|
|
@@ -329,12 +439,12 @@ const ControlArray = (props: ControlArrayProps) => {
|
|
|
329
439
|
<IconPlus size={14} />
|
|
330
440
|
{props.addLabel ?? "Add"}
|
|
331
441
|
</UnstyledButton>
|
|
332
|
-
</
|
|
442
|
+
</Flex>
|
|
333
443
|
);
|
|
334
444
|
|
|
335
445
|
if (props.variant === "plain") {
|
|
336
446
|
return (
|
|
337
|
-
<
|
|
447
|
+
<Flex direction="column" gap="xs">
|
|
338
448
|
{inputProps.label && (
|
|
339
449
|
<Text size="sm" fw={500}>
|
|
340
450
|
{inputProps.label}
|
|
@@ -351,13 +461,13 @@ const ControlArray = (props: ControlArrayProps) => {
|
|
|
351
461
|
{inputProps.error}
|
|
352
462
|
</Text>
|
|
353
463
|
)}
|
|
354
|
-
</
|
|
464
|
+
</Flex>
|
|
355
465
|
);
|
|
356
466
|
}
|
|
357
467
|
|
|
358
468
|
return (
|
|
359
469
|
<Fieldset legend={inputProps.label}>
|
|
360
|
-
<
|
|
470
|
+
<Flex direction="column" gap="xs">
|
|
361
471
|
{inputProps.description && (
|
|
362
472
|
<Text size="sm" c="dimmed">
|
|
363
473
|
{inputProps.description}
|
|
@@ -369,7 +479,7 @@ const ControlArray = (props: ControlArrayProps) => {
|
|
|
369
479
|
{inputProps.error}
|
|
370
480
|
</Text>
|
|
371
481
|
)}
|
|
372
|
-
</
|
|
482
|
+
</Flex>
|
|
373
483
|
</Fieldset>
|
|
374
484
|
);
|
|
375
485
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fieldset,
|
|
1
|
+
import { Fieldset, Flex, Grid, Text } from "@mantine/core";
|
|
2
2
|
import type { TObject } from "alepha";
|
|
3
3
|
import type { BaseInputField, ObjectInputField } from "alepha/react/form";
|
|
4
4
|
import {
|
|
@@ -107,7 +107,7 @@ const ControlObject = (props: ControlObjectProps) => {
|
|
|
107
107
|
|
|
108
108
|
return (
|
|
109
109
|
<Fieldset legend={inputProps.label}>
|
|
110
|
-
<
|
|
110
|
+
<Flex direction="column" gap="xs">
|
|
111
111
|
{inputProps.description && (
|
|
112
112
|
<Text size="sm" c="dimmed">
|
|
113
113
|
{inputProps.description}
|
|
@@ -119,7 +119,7 @@ const ControlObject = (props: ControlObjectProps) => {
|
|
|
119
119
|
{inputProps.error}
|
|
120
120
|
</Text>
|
|
121
121
|
)}
|
|
122
|
-
</
|
|
122
|
+
</Flex>
|
|
123
123
|
</Fieldset>
|
|
124
124
|
);
|
|
125
125
|
};
|
|
@@ -3,9 +3,7 @@ import {
|
|
|
3
3
|
Badge,
|
|
4
4
|
Divider,
|
|
5
5
|
Flex,
|
|
6
|
-
Group,
|
|
7
6
|
Popover,
|
|
8
|
-
Stack,
|
|
9
7
|
Text,
|
|
10
8
|
TextInput,
|
|
11
9
|
type TextInputProps,
|
|
@@ -165,7 +163,7 @@ interface QueryHelpProps {
|
|
|
165
163
|
|
|
166
164
|
function QueryHelp({ fields, onInsert }: QueryHelpProps) {
|
|
167
165
|
return (
|
|
168
|
-
<
|
|
166
|
+
<Flex
|
|
169
167
|
gap="md"
|
|
170
168
|
align="flex-start"
|
|
171
169
|
wrap="nowrap"
|
|
@@ -174,15 +172,15 @@ function QueryHelp({ fields, onInsert }: QueryHelpProps) {
|
|
|
174
172
|
bdrs={"sm"}
|
|
175
173
|
>
|
|
176
174
|
{/* Left Column: Operators */}
|
|
177
|
-
<
|
|
175
|
+
<Flex direction="column" gap="md" style={{ flex: 1 }}>
|
|
178
176
|
{/* Available Operators */}
|
|
179
|
-
<
|
|
177
|
+
<Flex direction="column" gap="xs">
|
|
180
178
|
<Text size="sm" fw={600}>
|
|
181
179
|
Operators
|
|
182
180
|
</Text>
|
|
183
|
-
<
|
|
181
|
+
<Flex direction="column" gap={4}>
|
|
184
182
|
{Object.entries(OPERATOR_INFO).map(([key, info]) => (
|
|
185
|
-
<
|
|
183
|
+
<Flex key={key} gap="xs" wrap="nowrap">
|
|
186
184
|
<ActionButton
|
|
187
185
|
px={"xs"}
|
|
188
186
|
size={"xs"}
|
|
@@ -197,20 +195,20 @@ function QueryHelp({ fields, onInsert }: QueryHelpProps) {
|
|
|
197
195
|
<Text size="xs" c="dimmed" style={{ flex: 1 }}>
|
|
198
196
|
{info.label}
|
|
199
197
|
</Text>
|
|
200
|
-
</
|
|
198
|
+
</Flex>
|
|
201
199
|
))}
|
|
202
|
-
</
|
|
203
|
-
</
|
|
200
|
+
</Flex>
|
|
201
|
+
</Flex>
|
|
204
202
|
|
|
205
203
|
<Divider />
|
|
206
204
|
|
|
207
205
|
{/* Logic Operators */}
|
|
208
|
-
<
|
|
206
|
+
<Flex direction="column" gap="xs">
|
|
209
207
|
<Text size="sm" fw={600}>
|
|
210
208
|
Logic
|
|
211
209
|
</Text>
|
|
212
|
-
<
|
|
213
|
-
<
|
|
210
|
+
<Flex direction="column" gap={4}>
|
|
211
|
+
<Flex gap="xs" wrap="nowrap">
|
|
214
212
|
<ActionButton
|
|
215
213
|
px={"xs"}
|
|
216
214
|
size={"xs"}
|
|
@@ -225,8 +223,8 @@ function QueryHelp({ fields, onInsert }: QueryHelpProps) {
|
|
|
225
223
|
<Text size="xs" c="dimmed">
|
|
226
224
|
AND
|
|
227
225
|
</Text>
|
|
228
|
-
</
|
|
229
|
-
<
|
|
226
|
+
</Flex>
|
|
227
|
+
<Flex gap="xs" wrap="nowrap">
|
|
230
228
|
<ActionButton
|
|
231
229
|
px={"xs"}
|
|
232
230
|
size={"xs"}
|
|
@@ -241,10 +239,10 @@ function QueryHelp({ fields, onInsert }: QueryHelpProps) {
|
|
|
241
239
|
<Text size="xs" c="dimmed">
|
|
242
240
|
OR
|
|
243
241
|
</Text>
|
|
244
|
-
</
|
|
245
|
-
</
|
|
246
|
-
</
|
|
247
|
-
</
|
|
242
|
+
</Flex>
|
|
243
|
+
</Flex>
|
|
244
|
+
</Flex>
|
|
245
|
+
</Flex>
|
|
248
246
|
|
|
249
247
|
{/* Divider */}
|
|
250
248
|
{fields.length > 0 && <Divider orientation="vertical" />}
|
|
@@ -283,7 +281,7 @@ function QueryHelp({ fields, onInsert }: QueryHelpProps) {
|
|
|
283
281
|
{field.description || field.type}
|
|
284
282
|
</Text>
|
|
285
283
|
{field.enum && (
|
|
286
|
-
<
|
|
284
|
+
<Flex gap={0} wrap="wrap">
|
|
287
285
|
{field.enum.map((enumValue) => (
|
|
288
286
|
<ActionButton
|
|
289
287
|
px={"xs"}
|
|
@@ -295,7 +293,7 @@ function QueryHelp({ fields, onInsert }: QueryHelpProps) {
|
|
|
295
293
|
{enumValue}
|
|
296
294
|
</ActionButton>
|
|
297
295
|
))}
|
|
298
|
-
</
|
|
296
|
+
</Flex>
|
|
299
297
|
)}
|
|
300
298
|
</Flex>
|
|
301
299
|
<Badge size="xs" variant="light" style={{ flexShrink: 0 }}>
|
|
@@ -306,7 +304,7 @@ function QueryHelp({ fields, onInsert }: QueryHelpProps) {
|
|
|
306
304
|
</Flex>
|
|
307
305
|
</Flex>
|
|
308
306
|
)}
|
|
309
|
-
</
|
|
307
|
+
</Flex>
|
|
310
308
|
);
|
|
311
309
|
}
|
|
312
310
|
|
|
@@ -119,6 +119,7 @@ const ControlSelect = (props: ControlSelectProps) => {
|
|
|
119
119
|
return (
|
|
120
120
|
<Autocomplete
|
|
121
121
|
{...inputProps}
|
|
122
|
+
size={props.size}
|
|
122
123
|
id={id}
|
|
123
124
|
leftSection={icon}
|
|
124
125
|
data={data}
|
|
@@ -134,6 +135,7 @@ const ControlSelect = (props: ControlSelectProps) => {
|
|
|
134
135
|
return (
|
|
135
136
|
<TagsInput
|
|
136
137
|
{...inputProps}
|
|
138
|
+
size={props.size}
|
|
137
139
|
id={id}
|
|
138
140
|
leftSection={icon}
|
|
139
141
|
defaultValue={
|
|
@@ -163,6 +165,7 @@ const ControlSelect = (props: ControlSelectProps) => {
|
|
|
163
165
|
return (
|
|
164
166
|
<MultiSelect
|
|
165
167
|
{...inputProps}
|
|
168
|
+
size={props.size}
|
|
166
169
|
id={id}
|
|
167
170
|
leftSection={icon}
|
|
168
171
|
data={data}
|
|
@@ -186,6 +189,7 @@ const ControlSelect = (props: ControlSelectProps) => {
|
|
|
186
189
|
return (
|
|
187
190
|
<Select
|
|
188
191
|
{...inputProps}
|
|
192
|
+
size={props.size}
|
|
189
193
|
id={id}
|
|
190
194
|
leftSection={icon}
|
|
191
195
|
data={data}
|