@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.
Files changed (218) hide show
  1. package/dist/admin/{AdminApiKeys-GMORg-1l.js → AdminApiKeys-CF_qOO3u.js} +20 -19
  2. package/dist/admin/AdminApiKeys-CF_qOO3u.js.map +1 -0
  3. package/dist/admin/{AdminAudits-pkWrjq1Z.js → AdminAudits-BQno3hZG.js} +7 -7
  4. package/dist/admin/AdminAudits-BQno3hZG.js.map +1 -0
  5. package/dist/admin/{AdminFiles-WeQbsCsl.js → AdminFiles-kvuUaASF.js} +3 -4
  6. package/dist/admin/{AdminFiles-WeQbsCsl.js.map → AdminFiles-kvuUaASF.js.map} +1 -1
  7. package/dist/admin/AdminJobDashboard-CrPxp0W1.js +485 -0
  8. package/dist/admin/AdminJobDashboard-CrPxp0W1.js.map +1 -0
  9. package/dist/admin/AdminJobExecutions-D-b4Zt7W.js +678 -0
  10. package/dist/admin/AdminJobExecutions-D-b4Zt7W.js.map +1 -0
  11. package/dist/admin/AdminJobRegistry-CNX5cpDx.js +301 -0
  12. package/dist/admin/AdminJobRegistry-CNX5cpDx.js.map +1 -0
  13. package/dist/admin/{AdminLayout-BqZiXx4H.js → AdminLayout-e-ZP5nWw.js} +6 -9
  14. package/dist/admin/AdminLayout-e-ZP5nWw.js.map +1 -0
  15. package/dist/admin/{AdminNotifications-Ds5Un0NJ.js → AdminNotifications-DeHJFf6W.js} +3 -4
  16. package/dist/admin/{AdminNotifications-Ds5Un0NJ.js.map → AdminNotifications-DeHJFf6W.js.map} +1 -1
  17. package/dist/admin/AdminParameters-iQE8o7a7.js +774 -0
  18. package/dist/admin/AdminParameters-iQE8o7a7.js.map +1 -0
  19. package/dist/admin/{AdminSessions-DzIOxM3b.js → AdminSessions-oKJCbd7w.js} +5 -6
  20. package/dist/admin/AdminSessions-oKJCbd7w.js.map +1 -0
  21. package/dist/admin/{AdminUserAudits-CiUPN2BC.js → AdminUserAudits-BNCEle_E.js} +6 -7
  22. package/dist/admin/AdminUserAudits-BNCEle_E.js.map +1 -0
  23. package/dist/admin/{AdminUserCreate-BwQKr4xE.js → AdminUserCreate-CgqeFwCt.js} +6 -6
  24. package/dist/admin/AdminUserCreate-CgqeFwCt.js.map +1 -0
  25. package/dist/admin/{AdminUserDetails-uqtC5aJ1.js → AdminUserDetails-DDe1A1GP.js} +30 -28
  26. package/dist/admin/AdminUserDetails-DDe1A1GP.js.map +1 -0
  27. package/dist/admin/{AdminUserLayout-CiPay35T.js → AdminUserLayout-HAlobhWf.js} +20 -19
  28. package/dist/admin/AdminUserLayout-HAlobhWf.js.map +1 -0
  29. package/dist/admin/{AdminUserSessions-DAE8Nf1F.js → AdminUserSessions-Bq1LnVLf.js} +5 -6
  30. package/dist/admin/AdminUserSessions-Bq1LnVLf.js.map +1 -0
  31. package/dist/admin/{AdminUserSettings-EbahaV2a.js → AdminUserSettings-BRsBZoxV.js} +10 -9
  32. package/dist/admin/AdminUserSettings-BRsBZoxV.js.map +1 -0
  33. package/dist/admin/{AdminUsers-Dcjh0KNW.js → AdminUsers-D71kIOSn.js} +6 -7
  34. package/dist/admin/AdminUsers-D71kIOSn.js.map +1 -0
  35. package/dist/admin/index.d.ts +21 -85
  36. package/dist/admin/index.d.ts.map +1 -1
  37. package/dist/admin/index.js +66 -88
  38. package/dist/admin/index.js.map +1 -1
  39. package/dist/auth/{AuthLayout-Dj5K4SIN.js → AuthLayout-CdJcrPs4.js} +2 -3
  40. package/dist/auth/{AuthLayout-Dj5K4SIN.js.map → AuthLayout-CdJcrPs4.js.map} +1 -1
  41. package/dist/{demo/IconGoogle-CbBF8Hqq.js → auth/IconGoogle-Bm18QD2q.js} +2 -4
  42. package/dist/auth/{IconGoogle-DpSlPZ1u.js.map → IconGoogle-Bm18QD2q.js.map} +1 -1
  43. package/dist/auth/{Login-BBqTosqZ.js → Login-BS_FYTy0.js} +19 -13
  44. package/dist/auth/Login-BS_FYTy0.js.map +1 -0
  45. package/dist/auth/{Profile-Bxj8Nwom.js → Profile-CjDsW378.js} +17 -12
  46. package/dist/auth/Profile-CjDsW378.js.map +1 -0
  47. package/dist/auth/{Register-Ce675Crg.js → Register-C5eqzAaD.js} +27 -17
  48. package/dist/auth/Register-C5eqzAaD.js.map +1 -0
  49. package/dist/auth/{ResetPassword-DWdt7c40.js → ResetPassword-XifinVao.js} +17 -10
  50. package/dist/auth/ResetPassword-XifinVao.js.map +1 -0
  51. package/dist/auth/{VerifyEmail-CI4JwByV.js → VerifyEmail-DTgbeJOO.js} +9 -6
  52. package/dist/auth/VerifyEmail-DTgbeJOO.js.map +1 -0
  53. package/dist/auth/index.d.ts +18 -14
  54. package/dist/auth/index.d.ts.map +1 -1
  55. package/dist/auth/index.js +19 -18
  56. package/dist/auth/index.js.map +1 -1
  57. package/dist/auth/rolldown-runtime-CjeV3_4I.js +18 -0
  58. package/dist/core/index.d.ts +182 -92
  59. package/dist/core/index.d.ts.map +1 -1
  60. package/dist/core/index.js +789 -476
  61. package/dist/core/index.js.map +1 -1
  62. package/dist/demo/DemoDataTable-lnBKWBf8.js +362 -0
  63. package/dist/demo/DemoDataTable-lnBKWBf8.js.map +1 -0
  64. package/dist/demo/{DemoHome-Cce2bWmg.js → DemoHome-CUMZsYaH.js} +6 -6
  65. package/dist/demo/DemoHome-CUMZsYaH.js.map +1 -0
  66. package/dist/demo/{DemoJsonViewer-Dgdk3Txb.js → DemoJsonViewer-_uokbGaW.js} +18 -19
  67. package/dist/demo/DemoJsonViewer-_uokbGaW.js.map +1 -0
  68. package/dist/demo/{DemoLayout-B20TEuhV.js → DemoLayout-DHVoacE6.js} +4 -5
  69. package/dist/demo/DemoLayout-DHVoacE6.js.map +1 -0
  70. package/dist/demo/{DemoLogin-CvCG2WVh.js → DemoLogin-DjJ9314c.js} +27 -24
  71. package/dist/demo/DemoLogin-DjJ9314c.js.map +1 -0
  72. package/dist/demo/{DemoRegister-CmeHbOAs.js → DemoRegister-DzkJ5M83.js} +39 -32
  73. package/dist/demo/DemoRegister-DzkJ5M83.js.map +1 -0
  74. package/dist/demo/{DemoResetPassword-CKO5iA_6.js → DemoResetPassword-DWh4_BpQ.js} +30 -26
  75. package/dist/demo/DemoResetPassword-DWh4_BpQ.js.map +1 -0
  76. package/dist/demo/{DemoSidebar-MVmQKfMt.js → DemoSidebar-C1csnGhX.js} +4 -5
  77. package/dist/demo/DemoSidebar-C1csnGhX.js.map +1 -0
  78. package/dist/demo/{DemoTypeForm-w-qtfRlC.js → DemoTypeForm-CWz6fJrJ.js} +4 -5
  79. package/dist/demo/DemoTypeForm-CWz6fJrJ.js.map +1 -0
  80. package/dist/demo/{DemoVerifyEmail-C8FFJT5A.js → DemoVerifyEmail-DbU_tCj8.js} +16 -16
  81. package/dist/demo/DemoVerifyEmail-DbU_tCj8.js.map +1 -0
  82. package/dist/{auth/IconGoogle-DpSlPZ1u.js → demo/IconGoogle-Ch1m3Uzl.js} +2 -4
  83. package/dist/demo/{IconGoogle-CbBF8Hqq.js.map → IconGoogle-Ch1m3Uzl.js.map} +1 -1
  84. package/dist/demo/{Showcase-CQrMWars.js → Showcase-BzoXNlCn.js} +11 -13
  85. package/dist/demo/Showcase-BzoXNlCn.js.map +1 -0
  86. package/dist/demo/index.d.ts +3 -70
  87. package/dist/demo/index.d.ts.map +1 -1
  88. package/dist/demo/index.js +11 -15
  89. package/dist/demo/index.js.map +1 -1
  90. package/dist/json/index.js +2 -2
  91. package/dist/json/index.js.map +1 -1
  92. package/package.json +11 -5
  93. package/src/admin/AdminRouter.ts +51 -29
  94. package/src/admin/components/AdminLayout.tsx +6 -9
  95. package/src/admin/components/audits/AdminAudits.tsx +5 -5
  96. package/src/admin/components/jobs/AdminJobDashboard.tsx +455 -0
  97. package/src/admin/components/jobs/AdminJobExecutions.tsx +693 -0
  98. package/src/admin/components/jobs/AdminJobRegistry.tsx +325 -0
  99. package/src/admin/components/keys/AdminApiKeys.tsx +28 -31
  100. package/src/admin/components/parameters/AdminParameters.tsx +156 -78
  101. package/src/admin/components/parameters/ParameterDetails.tsx +173 -108
  102. package/src/admin/components/parameters/ParameterEmptyState.tsx +27 -0
  103. package/src/admin/components/parameters/ParameterHistory.tsx +22 -35
  104. package/src/admin/components/parameters/ParameterTree.tsx +283 -109
  105. package/src/admin/components/parameters/types.ts +3 -3
  106. package/src/admin/components/sessions/AdminSessions.tsx +3 -3
  107. package/src/admin/components/shared/AdminResourceHeader.tsx +20 -16
  108. package/src/admin/components/users/AdminUserAudits.tsx +5 -5
  109. package/src/admin/components/users/AdminUserCreate.tsx +3 -3
  110. package/src/admin/components/users/AdminUserDetails.tsx +51 -53
  111. package/src/admin/components/users/AdminUserLayout.tsx +7 -7
  112. package/src/admin/components/users/AdminUserSessions.tsx +3 -3
  113. package/src/admin/components/users/AdminUserSettings.tsx +9 -9
  114. package/src/admin/components/users/AdminUsers.tsx +5 -5
  115. package/src/admin/components/verifications/AdminVerifications.tsx +3 -3
  116. package/src/admin/index.ts +0 -24
  117. package/src/admin/primitives/$uiAdmin.ts +2 -2
  118. package/src/auth/AuthRouter.ts +1 -0
  119. package/src/auth/components/Login.tsx +13 -13
  120. package/src/auth/components/Profile.tsx +17 -26
  121. package/src/auth/components/Register.tsx +21 -31
  122. package/src/auth/components/ResetPassword.tsx +13 -22
  123. package/src/auth/components/VerifyEmail.tsx +5 -5
  124. package/src/auth/components/buttons/UserButton.tsx +14 -4
  125. package/src/core/components/buttons/ActionButton.tsx +13 -17
  126. package/src/core/components/buttons/DarkModeButton.tsx +8 -4
  127. package/src/core/components/buttons/ToggleSidebarButton.tsx +3 -5
  128. package/src/core/components/data/ErrorViewer.tsx +15 -15
  129. package/src/core/components/dialogs/AlertDialog.tsx +3 -3
  130. package/src/core/components/dialogs/ConfirmDialog.tsx +3 -3
  131. package/src/core/components/dialogs/PromptDialog.tsx +3 -3
  132. package/src/core/components/form/Control.tsx +19 -32
  133. package/src/core/components/form/ControlArray.tsx +206 -96
  134. package/src/core/components/form/ControlObject.tsx +3 -3
  135. package/src/core/components/form/ControlQueryBuilder.tsx +20 -22
  136. package/src/core/components/form/ControlSelect.tsx +4 -0
  137. package/src/core/components/form/TypeForm.browser.spec.tsx +727 -0
  138. package/src/core/components/form/TypeForm.tsx +7 -0
  139. package/src/core/components/layout/AlephaMantineProvider.tsx +1 -0
  140. package/src/core/components/layout/Breadcrumb.tsx +91 -0
  141. package/src/core/components/layout/{AdminShell.tsx → DashboardShell.tsx} +77 -32
  142. package/src/core/components/layout/Omnibar.tsx +2 -1
  143. package/src/core/components/layout/Sidebar.tsx +63 -19
  144. package/src/core/components/table/ColumnPicker.tsx +47 -31
  145. package/src/core/components/table/DataTable.tsx +277 -201
  146. package/src/core/components/table/DataTableFilters.tsx +8 -0
  147. package/src/core/components/table/DataTableToolbar.tsx +98 -5
  148. package/src/core/components/table/FilterPicker.tsx +28 -26
  149. package/src/core/components/table/types.ts +52 -37
  150. package/src/core/components/table/useTableSelection.ts +83 -0
  151. package/src/core/constants/ui.ts +1 -1
  152. package/src/core/helpers/renderIcon.tsx +5 -2
  153. package/src/core/index.ts +9 -5
  154. package/src/core/styles.css +8 -7
  155. package/src/core/utils/parseInput.ts +1 -0
  156. package/src/core/utils/string.ts +28 -4
  157. package/src/demo/components/DemoHome.tsx +5 -5
  158. package/src/demo/components/DemoLayout.tsx +6 -2
  159. package/src/demo/components/core/DemoDataTable.tsx +209 -5
  160. package/src/demo/components/json/DemoJsonViewer.tsx +1 -1
  161. package/src/demo/components/shared/MacWindow.tsx +7 -7
  162. package/src/demo/components/shared/Showcase.tsx +3 -3
  163. package/src/demo/index.ts +0 -11
  164. package/src/json/components/JsonViewer.tsx +3 -3
  165. package/dist/admin/AdminApiKeys-DsmGnHNh.js +0 -3
  166. package/dist/admin/AdminApiKeys-GMORg-1l.js.map +0 -1
  167. package/dist/admin/AdminAudits-8SM96viT.js +0 -3
  168. package/dist/admin/AdminAudits-pkWrjq1Z.js.map +0 -1
  169. package/dist/admin/AdminFiles-B56ocq4H.js +0 -3
  170. package/dist/admin/AdminJobs-B-q9iGO3.js +0 -697
  171. package/dist/admin/AdminJobs-B-q9iGO3.js.map +0 -1
  172. package/dist/admin/AdminJobs-CED1syCn.js +0 -3
  173. package/dist/admin/AdminLayout-BqZiXx4H.js.map +0 -1
  174. package/dist/admin/AdminNotifications-B0B1rdc4.js +0 -3
  175. package/dist/admin/AdminParameters-BU3lATdJ.js +0 -3
  176. package/dist/admin/AdminParameters-CfDUpc78.js +0 -575
  177. package/dist/admin/AdminParameters-CfDUpc78.js.map +0 -1
  178. package/dist/admin/AdminSessions-BDGK2MS6.js +0 -3
  179. package/dist/admin/AdminSessions-DzIOxM3b.js.map +0 -1
  180. package/dist/admin/AdminUserAudits-CiUPN2BC.js.map +0 -1
  181. package/dist/admin/AdminUserAudits-Cj79gENT.js +0 -3
  182. package/dist/admin/AdminUserCreate-BwQKr4xE.js.map +0 -1
  183. package/dist/admin/AdminUserCreate-Cq-mUmBs.js +0 -3
  184. package/dist/admin/AdminUserDetails-DRjVAPFd.js +0 -3
  185. package/dist/admin/AdminUserDetails-uqtC5aJ1.js.map +0 -1
  186. package/dist/admin/AdminUserLayout-CGzmHHby.js +0 -3
  187. package/dist/admin/AdminUserLayout-CiPay35T.js.map +0 -1
  188. package/dist/admin/AdminUserSessions-DAE8Nf1F.js.map +0 -1
  189. package/dist/admin/AdminUserSessions-DcdzuNZ9.js +0 -3
  190. package/dist/admin/AdminUserSettings-D7V6-ceX.js +0 -3
  191. package/dist/admin/AdminUserSettings-EbahaV2a.js.map +0 -1
  192. package/dist/admin/AdminUsers-D9nyzGqQ.js +0 -3
  193. package/dist/admin/AdminUsers-Dcjh0KNW.js.map +0 -1
  194. package/dist/auth/Login-BBqTosqZ.js.map +0 -1
  195. package/dist/auth/Login-CoU63mMR.js +0 -4
  196. package/dist/auth/Profile-Bxj8Nwom.js.map +0 -1
  197. package/dist/auth/Register-BV_oa_AK.js +0 -4
  198. package/dist/auth/Register-Ce675Crg.js.map +0 -1
  199. package/dist/auth/ResetPassword-D5wC8GAA.js +0 -3
  200. package/dist/auth/ResetPassword-DWdt7c40.js.map +0 -1
  201. package/dist/auth/VerifyEmail-CI4JwByV.js.map +0 -1
  202. package/dist/auth/VerifyEmail-DAfqVm5s.js +0 -3
  203. package/dist/demo/DemoDataTable-CguplbR7.js +0 -150
  204. package/dist/demo/DemoDataTable-CguplbR7.js.map +0 -1
  205. package/dist/demo/DemoHome-Cce2bWmg.js.map +0 -1
  206. package/dist/demo/DemoHome-DC9qkMNe.js +0 -3
  207. package/dist/demo/DemoJsonViewer-DIssGVlJ.js +0 -4
  208. package/dist/demo/DemoJsonViewer-Dgdk3Txb.js.map +0 -1
  209. package/dist/demo/DemoLayout-B20TEuhV.js.map +0 -1
  210. package/dist/demo/DemoLayout-DSRyf4qJ.js +0 -3
  211. package/dist/demo/DemoLogin-CvCG2WVh.js.map +0 -1
  212. package/dist/demo/DemoRegister-CmeHbOAs.js.map +0 -1
  213. package/dist/demo/DemoResetPassword-CKO5iA_6.js.map +0 -1
  214. package/dist/demo/DemoSidebar-MVmQKfMt.js.map +0 -1
  215. package/dist/demo/DemoTypeForm-w-qtfRlC.js.map +0 -1
  216. package/dist/demo/DemoVerifyEmail-C8FFJT5A.js.map +0 -1
  217. package/dist/demo/Showcase-CQrMWars.js.map +0 -1
  218. package/src/admin/components/jobs/AdminJobs.tsx +0 -772
@@ -3,18 +3,19 @@ import { AlephaReactForm, FormValidationError, useForm, useFormState } from "ale
3
3
  import { $head, AlephaReactHead } from "alepha/react/head";
4
4
  import { AlephaReactI18n, useI18n } from "alepha/react/i18n";
5
5
  import { $cookie } from "alepha/server/cookies";
6
- import { ActionIcon, Anchor, AppShell, Autocomplete, Badge, Box, Burger, Button, Card, Checkbox, Collapse, ColorInput, ColorSchemeScript, Container, CopyButton, Divider, Fieldset, FileInput, Flex, Flex as Flex$1, Grid, Group, Image, Input, Kbd, MantineProvider, Menu, MultiSelect, NumberInput, Pagination, PasswordInput, Popover, ScrollArea, SegmentedControl, Select, Slider, Stack, Switch, Table, TagsInput, Text, Text as Text$1, TextInput, Textarea, ThemeIcon, Tooltip, UnstyledButton, useMantineColorScheme, useMantineTheme } from "@mantine/core";
6
+ import { ActionIcon, Anchor, AppShell, Autocomplete, Badge, Burger, Button, Card, Checkbox, Collapse, ColorInput, ColorSchemeScript, Container, CopyButton, Divider, Drawer, Fieldset, FileInput, Flex, Flex as Flex$1, Grid, Image, Input, Kbd, MantineProvider, Menu, MultiSelect, NumberInput, Pagination, PasswordInput, Popover, ScrollArea, SegmentedControl, Select, Slider, Switch, Table, TagsInput, Text, Text as Text$1, TextInput, Textarea, ThemeIcon, Tooltip, UnstyledButton, useMantineColorScheme, useMantineTheme } from "@mantine/core";
7
7
  import { ModalsProvider, modals } from "@mantine/modals";
8
- import { IconAlertTriangle, IconArrowDown, IconArrowLeft, IconArrowUp, IconArrowsSort, IconAt, IconCalendar, IconCheck, IconChevronDown, IconChevronRight, IconClock, IconColorPicker, IconColumns, IconCopy, IconFile, IconFilter, IconGripVertical, IconHash, IconInfoCircle, IconInfoTriangle, IconKey, IconLanguage, IconLayoutSidebarLeftCollapse, IconLayoutSidebarRightCollapse, IconLetterCase, IconLink, IconList, IconMail, IconMoon, IconPalette, IconPhone, IconPlus, IconRefresh, IconSearch, IconSelector, IconSquareRounded, IconSun, IconToggleLeft, IconTrash, IconX } from "@tabler/icons-react";
9
- import { Children, createElement, isValidElement, useCallback, useEffect, useMemo, useRef, useState } from "react";
10
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ import { IconAlertTriangle, IconArrowDown, IconArrowLeft, IconArrowUp, IconArrowsSort, IconAt, IconCalendar, IconCheck, IconChevronDown, IconChevronRight, IconClipboard, IconClock, IconColorPicker, IconColumns, IconCopy, IconDownload, IconFile, IconFilter, IconGripVertical, IconHash, IconInfoCircle, IconInfoTriangle, IconKey, IconLanguage, IconLayoutSidebarLeftCollapse, IconLayoutSidebarRightCollapse, IconLetterCase, IconLink, IconList, IconMail, IconMoon, IconPalette, IconPhone, IconPlus, IconRefresh, IconSearch, IconSelector, IconSquareRounded, IconSun, IconToggleLeft, IconTrash, IconX } from "@tabler/icons-react";
9
+ import { Children, Fragment, createElement, isValidElement, useCallback, useEffect, useMemo, useRef, useState } from "react";
10
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
11
11
  import { Notifications, notifications } from "@mantine/notifications";
12
- import { $page, Link, NestedView, useActive, useRouter } from "alepha/react/router";
12
+ import { $page, Link, NestedView, useActive, useRouter, useRouterState } from "alepha/react/router";
13
13
  import { NavigationProgress, nprogress } from "@mantine/nprogress";
14
- import { ClientOnly, useAction, useEvents, useInject, useStore } from "alepha/react";
14
+ import { ClientOnly, useAction, useAlepha, useEvents, useInject, useStore } from "alepha/react";
15
15
  import { Spotlight, spotlight } from "@mantine/spotlight";
16
+ import { currentUserAtom } from "alepha/security";
16
17
  import { ui as ui$1 } from "@alepha/ui";
17
- import { useDebouncedCallback, useOs } from "@mantine/hooks";
18
+ import { useOs } from "@mantine/hooks";
18
19
  import { DateInput, DateTimePicker, TimeInput } from "@mantine/dates";
19
20
  import { parseQueryString } from "alepha/orm";
20
21
  import { DateTimeProvider } from "alepha/datetime";
@@ -267,10 +268,10 @@ const ErrorViewer = ({ error, showStack = true, copyable = true, size = "sm" })
267
268
  if (isError) return `${errorName}: ${errorMessage}${errorStack ? `\n\n${errorStack}` : ""}`;
268
269
  return String(error);
269
270
  };
270
- return /* @__PURE__ */ jsxs(Box, {
271
+ return /* @__PURE__ */ jsxs(Flex$1, {
271
272
  pos: "relative",
272
273
  w: "100%",
273
- children: [copyable && /* @__PURE__ */ jsx(Box, {
274
+ children: [copyable && /* @__PURE__ */ jsx(Flex$1, {
274
275
  pos: "absolute",
275
276
  top: 0,
276
277
  right: 0,
@@ -288,9 +289,9 @@ const ErrorViewer = ({ error, showStack = true, copyable = true, size = "sm" })
288
289
  })
289
290
  })
290
291
  })
291
- }), /* @__PURE__ */ jsxs(Box, {
292
+ }), /* @__PURE__ */ jsxs(Flex$1, {
292
293
  pt: copyable ? 30 : 0,
293
- children: [/* @__PURE__ */ jsxs(Box, {
294
+ children: [/* @__PURE__ */ jsxs(Flex$1, {
294
295
  style: {
295
296
  display: "flex",
296
297
  alignItems: "flex-start",
@@ -310,9 +311,9 @@ const ErrorViewer = ({ error, showStack = true, copyable = true, size = "sm" })
310
311
  style: { wordBreak: "break-word" },
311
312
  children: errorMessage
312
313
  })]
313
- }), showStack && stackLines.length > 1 && /* @__PURE__ */ jsxs(Box, {
314
+ }), showStack && stackLines.length > 1 && /* @__PURE__ */ jsxs(Flex$1, {
314
315
  mt: "sm",
315
- children: [/* @__PURE__ */ jsxs(Box, {
316
+ children: [/* @__PURE__ */ jsxs(Flex$1, {
316
317
  style: {
317
318
  display: "flex",
318
319
  alignItems: "center",
@@ -337,7 +338,7 @@ const ErrorViewer = ({ error, showStack = true, copyable = true, size = "sm" })
337
338
  })]
338
339
  }), /* @__PURE__ */ jsx(Collapse, {
339
340
  in: stackExpanded,
340
- children: /* @__PURE__ */ jsx(Box, {
341
+ children: /* @__PURE__ */ jsx(Flex$1, {
341
342
  mt: "xs",
342
343
  pl: "md",
343
344
  style: { borderLeft: "1px solid var(--mantine-color-default-border)" },
@@ -357,28 +358,26 @@ const ErrorViewer = ({ error, showStack = true, copyable = true, size = "sm" })
357
358
  })]
358
359
  });
359
360
  };
360
- var ErrorViewer_default = ErrorViewer;
361
361
 
362
362
  //#endregion
363
363
  //#region ../../src/core/components/dialogs/AlertDialog.tsx
364
- const AlertDialog = ({ options, onClose }) => /* @__PURE__ */ jsxs(Fragment, { children: [options?.message && /* @__PURE__ */ jsx(Text$1, {
364
+ const AlertDialog = ({ options, onClose }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [options?.message && /* @__PURE__ */ jsx(Text$1, {
365
365
  mb: "md",
366
366
  children: options.message
367
- }), /* @__PURE__ */ jsx(Group, {
367
+ }), /* @__PURE__ */ jsx(Flex$1, {
368
368
  justify: "flex-end",
369
369
  children: /* @__PURE__ */ jsx(Button, {
370
370
  onClick: onClose,
371
371
  children: options?.okLabel || "OK"
372
372
  })
373
373
  })] });
374
- var AlertDialog_default = AlertDialog;
375
374
 
376
375
  //#endregion
377
376
  //#region ../../src/core/components/dialogs/ConfirmDialog.tsx
378
- const ConfirmDialog = ({ options, onConfirm }) => /* @__PURE__ */ jsxs(Fragment, { children: [options?.message && /* @__PURE__ */ jsx(Text$1, {
377
+ const ConfirmDialog = ({ options, onConfirm }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [options?.message && /* @__PURE__ */ jsx(Text$1, {
379
378
  mb: "md",
380
379
  children: options.message
381
- }), /* @__PURE__ */ jsxs(Group, {
380
+ }), /* @__PURE__ */ jsxs(Flex$1, {
382
381
  justify: "flex-end",
383
382
  children: [/* @__PURE__ */ jsx(Button, {
384
383
  variant: "subtle",
@@ -390,7 +389,6 @@ const ConfirmDialog = ({ options, onConfirm }) => /* @__PURE__ */ jsxs(Fragment,
390
389
  children: options?.confirmLabel || "Confirm"
391
390
  })]
392
391
  })] });
393
- var ConfirmDialog_default = ConfirmDialog;
394
392
 
395
393
  //#endregion
396
394
  //#region ../../src/core/components/dialogs/PromptDialog.tsx
@@ -406,7 +404,7 @@ const PromptDialog = ({ options, onSubmit }) => {
406
404
  const handleKeyDown = (event) => {
407
405
  if (event.key === "Enter") handleSubmit();
408
406
  };
409
- return /* @__PURE__ */ jsxs(Fragment, { children: [
407
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
410
408
  options?.message && /* @__PURE__ */ jsx(Text$1, {
411
409
  mb: "md",
412
410
  children: options.message
@@ -421,7 +419,7 @@ const PromptDialog = ({ options, onSubmit }) => {
421
419
  required: options?.required,
422
420
  mb: "md"
423
421
  }),
424
- /* @__PURE__ */ jsxs(Group, {
422
+ /* @__PURE__ */ jsxs(Flex$1, {
425
423
  justify: "flex-end",
426
424
  children: [/* @__PURE__ */ jsx(Button, {
427
425
  variant: "subtle",
@@ -435,7 +433,6 @@ const PromptDialog = ({ options, onSubmit }) => {
435
433
  })
436
434
  ] });
437
435
  };
438
- var PromptDialog_default = PromptDialog;
439
436
 
440
437
  //#endregion
441
438
  //#region ../../src/core/constants/ui.ts
@@ -448,7 +445,7 @@ const ui = {
448
445
  border: "var(--alepha-border)"
449
446
  },
450
447
  sizes: { icon: {
451
- xs: 12,
448
+ xs: 14,
452
449
  sm: 16,
453
450
  md: 20,
454
451
  lg: 24,
@@ -480,7 +477,7 @@ var DialogService = class {
480
477
  const modalId = this.open({
481
478
  ...options,
482
479
  title: options?.title || "Alert",
483
- content: /* @__PURE__ */ jsx(AlertDialog_default, {
480
+ content: /* @__PURE__ */ jsx(AlertDialog, {
484
481
  options,
485
482
  onClose: () => {
486
483
  this.close(modalId);
@@ -500,7 +497,7 @@ var DialogService = class {
500
497
  title: options?.title || "Confirm",
501
498
  closeOnClickOutside: false,
502
499
  closeOnEscape: false,
503
- content: /* @__PURE__ */ jsx(ConfirmDialog_default, {
500
+ content: /* @__PURE__ */ jsx(ConfirmDialog, {
504
501
  options,
505
502
  onConfirm: (confirmed) => {
506
503
  this.close(modalId);
@@ -520,7 +517,7 @@ var DialogService = class {
520
517
  title: options?.title || "Input",
521
518
  closeOnClickOutside: false,
522
519
  closeOnEscape: false,
523
- content: /* @__PURE__ */ jsx(PromptDialog_default, {
520
+ content: /* @__PURE__ */ jsx(PromptDialog, {
524
521
  options,
525
522
  onSubmit: (value) => {
526
523
  this.close(modalId);
@@ -561,7 +558,7 @@ var DialogService = class {
561
558
  flex: 1,
562
559
  p: "sm",
563
560
  bg: ui.colors.surface,
564
- children: /* @__PURE__ */ jsx(ErrorViewer_default, {
561
+ children: /* @__PURE__ */ jsx(ErrorViewer, {
565
562
  size: "xs",
566
563
  error,
567
564
  showStack: options?.showStack ?? true
@@ -690,10 +687,10 @@ function isComponentType(param) {
690
687
 
691
688
  //#endregion
692
689
  //#region ../../src/core/helpers/renderIcon.tsx
693
- const renderIcon = (icon) => {
690
+ const renderIcon = (icon, size) => {
694
691
  if (!icon) return null;
695
692
  if (isValidElement(icon)) return icon;
696
- if (isComponentType(icon)) return /* @__PURE__ */ jsx(icon, { size: ui$1.sizes.icon.md });
693
+ if (isComponentType(icon)) return /* @__PURE__ */ jsx(icon, { size: size ?? ui$1.sizes.icon.md });
697
694
  return icon;
698
695
  };
699
696
 
@@ -704,7 +701,7 @@ const Omnibar = (props) => {
704
701
  const searchPlaceholder = props.searchPlaceholder ?? "Search...";
705
702
  const nothingFound = props.nothingFound ?? "Nothing found...";
706
703
  const router = useRouter();
707
- const [user] = useStore("alepha.server.request.user");
704
+ const [user] = useStore(currentUserAtom);
708
705
  return /* @__PURE__ */ jsx(Spotlight, {
709
706
  actions: useMemo(() => router.concretePages.filter((page) => {
710
707
  if (page.can && !page.can()) return false;
@@ -728,7 +725,6 @@ const Omnibar = (props) => {
728
725
  nothingFound
729
726
  });
730
727
  };
731
- var Omnibar_default = Omnibar;
732
728
 
733
729
  //#endregion
734
730
  //#region ../../src/core/components/layout/AlephaMantineProvider.tsx
@@ -751,13 +747,14 @@ const AlephaMantineProvider = (props) => {
751
747
  }
752
748
  }, []);
753
749
  const defaultColorScheme = props.mantine?.defaultColorScheme ?? theme.defaultColorScheme;
754
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ColorSchemeScript, {
750
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ColorSchemeScript, {
755
751
  defaultColorScheme,
756
752
  ...props.colorSchemeScript
757
753
  }), /* @__PURE__ */ jsxs(MantineProvider, {
758
754
  ...props.mantine,
759
755
  defaultColorScheme,
760
756
  theme: {
757
+ cursorType: "pointer",
761
758
  ...theme,
762
759
  ...props.mantine?.theme
763
760
  },
@@ -766,12 +763,11 @@ const AlephaMantineProvider = (props) => {
766
763
  /* @__PURE__ */ jsx(NavigationProgress, { ...props.navigationProgress }),
767
764
  /* @__PURE__ */ jsxs(ModalsProvider, {
768
765
  ...props.modals,
769
- children: [props.omnibar !== false && /* @__PURE__ */ jsx(Omnibar_default, { ...props.omnibar }), props.children ?? /* @__PURE__ */ jsx(NestedView, {})]
766
+ children: [props.omnibar !== false && /* @__PURE__ */ jsx(Omnibar, { ...props.omnibar }), props.children ?? /* @__PURE__ */ jsx(NestedView, {})]
770
767
  })
771
768
  ]
772
769
  })] });
773
770
  };
774
- var AlephaMantineProvider_default = AlephaMantineProvider;
775
771
 
776
772
  //#endregion
777
773
  //#region ../../src/core/UiRouter.ts
@@ -784,7 +780,7 @@ var AlephaMantineProvider_default = AlephaMantineProvider;
784
780
  var UiRouter = class {
785
781
  root = $page({
786
782
  path: "/",
787
- component: AlephaMantineProvider_default
783
+ component: AlephaMantineProvider
788
784
  });
789
785
  };
790
786
 
@@ -831,40 +827,25 @@ const ActionButton = (_props) => {
831
827
  const theme = useMantineTheme();
832
828
  const props = { ..._props };
833
829
  const { tooltip, menu, icon, ...restProps } = props;
834
- if (props.variant === "subtle") {
835
- restProps.c ??= "var(--mantine-color-text)";
836
- restProps.color ??= "gray";
837
- }
830
+ if (props.variant === "subtle" || props.variant === "outline") restProps.color ??= "gray";
838
831
  if (props.intent) {
839
- if (props.intent === "none") {
840
- restProps.c ??= "var(--mantine-color-text)";
841
- restProps.color ??= "gray";
842
- } else if (props.intent === "primary") {
843
- restProps.c ??= "white";
844
- restProps.color ??= theme.primaryColor;
845
- } else if (props.intent === "success") {
832
+ if (props.intent === "none") restProps.color ??= "gray";
833
+ else if (props.intent === "primary") restProps.color ??= theme.primaryColor;
834
+ else if (props.intent === "success") {
846
835
  restProps.c ??= "white";
847
836
  restProps.color ??= "green";
848
837
  } else if (props.intent === "danger") {
849
838
  restProps.c ??= "white";
850
839
  restProps.color ??= "red";
851
- } else if (props.intent === "warning") {
852
- restProps.c ??= "var(--mantine-color-text)";
853
- restProps.color ??= "yellow";
854
- } else if (props.intent === "info") {
840
+ } else if (props.intent === "warning") restProps.color ??= "yellow";
841
+ else if (props.intent === "info") {
855
842
  restProps.c ??= "white";
856
843
  restProps.color ??= "blue";
857
844
  }
858
845
  }
859
846
  if (props.icon) {
860
- const icon = isComponentType(props.icon) ? /* @__PURE__ */ jsx(props.icon, { size: ui.sizes.icon.md }) : /* @__PURE__ */ jsx(ThemeIcon, {
861
- w: 24,
862
- variant: "transparent",
863
- size: "sm",
864
- c: "var(--mantine-color-text)",
865
- ...props.themeIconProps,
866
- children: props.icon
867
- });
847
+ const sizes = ui.sizes.icon;
848
+ const icon = isComponentType(props.icon) ? /* @__PURE__ */ jsx(props.icon, { size: sizes[props.size || "md"] }) : /* @__PURE__ */ jsx("span", { children: props.icon });
868
849
  if (!props.children) {
869
850
  restProps.children = Children.only(icon);
870
851
  restProps.px ??= "xs";
@@ -873,7 +854,7 @@ const ActionButton = (_props) => {
873
854
  if (props.leftSection && !props.children) restProps.px ??= "xs";
874
855
  if (props.textVisibleFrom) {
875
856
  const { children, textVisibleFrom, leftSection, ...rest } = restProps;
876
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Flex$1, {
857
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Flex$1, {
877
858
  w: "100%",
878
859
  visibleFrom: textVisibleFrom,
879
860
  children: /* @__PURE__ */ jsx(ActionButton, {
@@ -967,7 +948,6 @@ const ActionButton = (_props) => {
967
948
  }
968
949
  return actionElement;
969
950
  };
970
- var ActionButton_default = ActionButton;
971
951
  /**
972
952
  * Action button that submits a form with loading and disabled state handling.
973
953
  */
@@ -1029,10 +1009,11 @@ const ActionHookButton = (props) => {
1029
1009
  * </ActionButton>
1030
1010
  * ```
1031
1011
  */
1032
- const ActionClickButton = (props) => {
1012
+ const ActionClickButton = ({ preventDefault, ...props }) => {
1033
1013
  const action = useAction({ handler: async (e) => {
1014
+ if (preventDefault) e.preventDefault();
1034
1015
  await props.onClick(e);
1035
- } }, [props.onClick]);
1016
+ } }, [props.onClick, preventDefault]);
1036
1017
  return /* @__PURE__ */ jsx(Button, {
1037
1018
  ...props,
1038
1019
  disabled: action.loading || props.disabled,
@@ -1100,7 +1081,6 @@ const BurgerButton = (props) => {
1100
1081
  ...props
1101
1082
  });
1102
1083
  };
1103
- var BurgerButton_default = BurgerButton;
1104
1084
 
1105
1085
  //#endregion
1106
1086
  //#region ../../src/core/components/buttons/ClipboardButton.tsx
@@ -1112,7 +1092,7 @@ const ClipboardButton = (props) => {
1112
1092
  children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, {
1113
1093
  label: copied ? copiedLabel : copyLabel,
1114
1094
  openDelay: 500,
1115
- children: /* @__PURE__ */ jsx(ActionButton_default, {
1095
+ children: /* @__PURE__ */ jsx(ActionButton, {
1116
1096
  color: copied ? "teal" : void 0,
1117
1097
  onClick: copy,
1118
1098
  icon: copied ? IconCheck : IconCopy,
@@ -1122,7 +1102,6 @@ const ClipboardButton = (props) => {
1122
1102
  })
1123
1103
  });
1124
1104
  };
1125
- var ClipboardButton_default = ClipboardButton;
1126
1105
 
1127
1106
  //#endregion
1128
1107
  //#region ../../src/core/components/buttons/DarkModeButton.tsx
@@ -1138,23 +1117,29 @@ const DarkModeButton = (props) => {
1138
1117
  const toggleColorScheme = () => {
1139
1118
  setColorScheme((document.documentElement.getAttribute("data-mantine-color-scheme") ?? "light") === "dark" ? "light" : "dark");
1140
1119
  };
1141
- return /* @__PURE__ */ jsx(ActionButton_default, {
1120
+ const size = props.size ?? "md";
1121
+ const iconSize = ui.sizes.icon[size] ?? ui.sizes.icon.md;
1122
+ return /* @__PURE__ */ jsx(ActionButton, {
1142
1123
  onClick: toggleColorScheme,
1143
1124
  variant: props.variant ?? "subtle",
1144
- size: props.size ?? "sm",
1125
+ size,
1145
1126
  "aria-label": "Toggle color scheme",
1146
- px: "xs",
1147
- icon: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(IconSun, { className: "alepha-light-hidden" }), /* @__PURE__ */ jsx(IconMoon, { className: "alepha-dark-hidden" })] }),
1127
+ icon: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(IconSun, {
1128
+ size: iconSize,
1129
+ className: "alepha-light-hidden"
1130
+ }), /* @__PURE__ */ jsx(IconMoon, {
1131
+ size: iconSize,
1132
+ className: "alepha-dark-hidden"
1133
+ })] }),
1148
1134
  ...props
1149
1135
  });
1150
1136
  };
1151
- var DarkModeButton_default = DarkModeButton;
1152
1137
 
1153
1138
  //#endregion
1154
1139
  //#region ../../src/core/components/buttons/LanguageButton.tsx
1155
1140
  const LanguageButton = (props) => {
1156
1141
  const i18n = useI18n();
1157
- return /* @__PURE__ */ jsx(ActionButton_default, {
1142
+ return /* @__PURE__ */ jsx(ActionButton, {
1158
1143
  variant: "default",
1159
1144
  icon: IconLanguage,
1160
1145
  menu: { items: i18n.languages.map((lang) => ({
@@ -1165,14 +1150,13 @@ const LanguageButton = (props) => {
1165
1150
  ...props
1166
1151
  });
1167
1152
  };
1168
- var LanguageButton_default = LanguageButton;
1169
1153
 
1170
1154
  //#endregion
1171
1155
  //#region ../../src/core/components/buttons/OmnibarButton.tsx
1172
1156
  const OmnibarButton = (props) => {
1173
1157
  const os = useOs();
1174
1158
  const shortcut = os === "macos" || os === "ios" ? "⌘" : "Ctrl";
1175
- if (props.collapsed) return /* @__PURE__ */ jsx(ActionButton_default, {
1159
+ if (props.collapsed) return /* @__PURE__ */ jsx(ActionButton, {
1176
1160
  variant: "subtle",
1177
1161
  onClick: spotlight.open,
1178
1162
  radius: "md",
@@ -1183,7 +1167,7 @@ const OmnibarButton = (props) => {
1183
1167
  },
1184
1168
  ...props.actionProps
1185
1169
  });
1186
- return /* @__PURE__ */ jsx(ActionButton_default, {
1170
+ return /* @__PURE__ */ jsx(ActionButton, {
1187
1171
  variant: "default",
1188
1172
  onClick: spotlight.open,
1189
1173
  justify: "space-between",
@@ -1212,14 +1196,13 @@ const OmnibarButton = (props) => {
1212
1196
  })
1213
1197
  });
1214
1198
  };
1215
- var OmnibarButton_default = OmnibarButton;
1216
1199
 
1217
1200
  //#endregion
1218
1201
  //#region ../../src/core/components/buttons/ThemeButton.tsx
1219
1202
  const ThemeButton = (props) => {
1220
1203
  const [theme, setTheme] = useTheme();
1221
1204
  const themeList = useStore(alephaThemeListAtom)[0];
1222
- return /* @__PURE__ */ jsx(ActionButton_default, {
1205
+ return /* @__PURE__ */ jsx(ActionButton, {
1223
1206
  variant: "subtle",
1224
1207
  icon: IconPalette,
1225
1208
  menu: { items: themeList.map((it, index) => ({
@@ -1230,14 +1213,13 @@ const ThemeButton = (props) => {
1230
1213
  ...props
1231
1214
  });
1232
1215
  };
1233
- var ThemeButton_default = ThemeButton;
1234
1216
 
1235
1217
  //#endregion
1236
1218
  //#region ../../src/core/components/buttons/ToggleSidebarButton.tsx
1237
1219
  const ToggleSidebarButton = (props) => {
1238
1220
  const [sidebar, setSidebar] = useStore(alephaSidebarAtom);
1239
- return /* @__PURE__ */ jsx(ActionButton_default, {
1240
- icon: sidebar.collapsed ? /* @__PURE__ */ jsx(IconLayoutSidebarRightCollapse, {}) : /* @__PURE__ */ jsx(IconLayoutSidebarLeftCollapse, {}),
1221
+ return /* @__PURE__ */ jsx(ActionButton, {
1222
+ icon: sidebar.collapsed ? IconLayoutSidebarRightCollapse : IconLayoutSidebarLeftCollapse,
1241
1223
  visibleFrom: "sm",
1242
1224
  variant: "subtle",
1243
1225
  size: "md",
@@ -1256,7 +1238,6 @@ const ToggleSidebarButton = (props) => {
1256
1238
  ...props
1257
1239
  });
1258
1240
  };
1259
- var ToggleSidebarButton_default = ToggleSidebarButton;
1260
1241
 
1261
1242
  //#endregion
1262
1243
  //#region ../../src/core/utils/icons.tsx
@@ -1312,15 +1293,30 @@ const capitalize = (str) => {
1312
1293
  return str.charAt(0).toUpperCase() + str.slice(1);
1313
1294
  };
1314
1295
  /**
1296
+ * Converts camelCase or snake_case to Title Case with spaces.
1297
+ *
1298
+ * @example
1299
+ * toTitleCase("userName") // "User Name"
1300
+ * toTitleCase("first_name") // "First Name"
1301
+ * toTitleCase("email") // "Email"
1302
+ */
1303
+ const toTitleCase = (str) => {
1304
+ return str.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1305
+ };
1306
+ /**
1315
1307
  * Converts a path or identifier string into a pretty display name.
1316
- * Removes slashes and capitalizes the first letter.
1308
+ * For paths like "/contacts/0/name", extracts just the field name "Name".
1309
+ * Handles camelCase and snake_case conversion to Title Case.
1317
1310
  *
1318
1311
  * @example
1319
- * prettyName("/userName") // "UserName"
1320
- * prettyName("email") // "Email"
1312
+ * prettyName("/userName") // "User Name"
1313
+ * prettyName("/contacts/0/email") // "Email"
1314
+ * prettyName("/address/streetName") // "Street Name"
1315
+ * prettyName("first_name") // "First Name"
1321
1316
  */
1322
1317
  const prettyName = (name) => {
1323
- return capitalize(name.replaceAll("/", ""));
1318
+ const segments = name.split("/").filter((s) => s && !/^\d+$/.test(s));
1319
+ return toTitleCase(segments[segments.length - 1] || name.replaceAll("/", ""));
1324
1320
  };
1325
1321
 
1326
1322
  //#endregion
@@ -1364,6 +1360,107 @@ const parseInput = (props, form) => {
1364
1360
  //#endregion
1365
1361
  //#region ../../src/core/components/form/ControlArray.tsx
1366
1362
  /**
1363
+ * Custom hook to sync array items with form state.
1364
+ * Uses form events as the source of truth, eliminating dual-state issues.
1365
+ */
1366
+ const useArrayItems = (input) => {
1367
+ const alepha = useAlepha();
1368
+ const keyCounter = useRef(0);
1369
+ const [items, setItemsState] = useState(() => {
1370
+ const defaultValue = input?.props?.defaultValue;
1371
+ if (Array.isArray(defaultValue)) return defaultValue.map((value) => ({
1372
+ key: keyCounter.current++,
1373
+ value
1374
+ }));
1375
+ return [];
1376
+ });
1377
+ const syncFromFormValue = useCallback((formValue) => {
1378
+ if (!Array.isArray(formValue)) {
1379
+ setItemsState([]);
1380
+ return;
1381
+ }
1382
+ setItemsState((prevItems) => {
1383
+ if (prevItems.length === formValue.length) {
1384
+ if (prevItems.every((item, i) => item.value === formValue[i])) return prevItems;
1385
+ }
1386
+ keyCounter.current = 0;
1387
+ return formValue.map((value) => ({
1388
+ key: keyCounter.current++,
1389
+ value
1390
+ }));
1391
+ });
1392
+ }, []);
1393
+ useEffect(() => {
1394
+ if (!input?.form) return;
1395
+ const formId = input.form.id;
1396
+ const fieldPath = input.path;
1397
+ const listeners = [alepha.events.on("form:reset", (event) => {
1398
+ if (event.id === formId) {
1399
+ const defaultValue = input.props?.defaultValue;
1400
+ keyCounter.current = 0;
1401
+ if (Array.isArray(defaultValue)) setItemsState(defaultValue.map((value) => ({
1402
+ key: keyCounter.current++,
1403
+ value
1404
+ })));
1405
+ else setItemsState([]);
1406
+ }
1407
+ }), alepha.events.on("form:change", (event) => {
1408
+ if (event.id === formId && event.path === fieldPath) syncFromFormValue(event.value);
1409
+ })];
1410
+ return () => {
1411
+ for (const unsub of listeners) unsub();
1412
+ };
1413
+ }, [
1414
+ alepha,
1415
+ input,
1416
+ syncFromFormValue
1417
+ ]);
1418
+ return {
1419
+ items,
1420
+ setItems: useCallback((newItems) => {
1421
+ setItemsState(newItems);
1422
+ input?.set(newItems.map((item) => item.value));
1423
+ }, [input]),
1424
+ nextKey: useCallback(() => keyCounter.current++, [])
1425
+ };
1426
+ };
1427
+ /**
1428
+ * Creates a proper InputField for an array item that integrates with the form system.
1429
+ * Uses array index for test IDs to ensure predictable, testable element identifiers.
1430
+ */
1431
+ const createArrayItemInput = (parentInput, itemSchema, index, _itemKey, value, onValueChange) => {
1432
+ return {
1433
+ schema: itemSchema,
1434
+ path: `${parentInput.path}/${index}`,
1435
+ required: false,
1436
+ form: parentInput.form,
1437
+ props: {
1438
+ id: `${parentInput.props.id}-${index}`,
1439
+ name: `${parentInput.props.name}[${index}]`,
1440
+ defaultValue: value
1441
+ },
1442
+ set: onValueChange
1443
+ };
1444
+ };
1445
+ /**
1446
+ * Creates a proper InputField for a nested object field within an array item.
1447
+ * Uses array index for test IDs to ensure predictable, testable element identifiers.
1448
+ */
1449
+ const createArrayItemFieldInput = (parentInput, itemSchema, fieldName, index, _itemKey, itemValue, onFieldChange) => {
1450
+ return {
1451
+ schema: itemSchema.properties[fieldName],
1452
+ path: `${parentInput.path}/${index}/${fieldName}`,
1453
+ required: itemSchema.required?.includes(fieldName) ?? false,
1454
+ form: parentInput.form,
1455
+ props: {
1456
+ id: `${parentInput.props.id}-${index}-${fieldName}`,
1457
+ name: `${parentInput.props.name}[${index}].${fieldName}`,
1458
+ defaultValue: itemValue?.[fieldName]
1459
+ },
1460
+ set: (value) => onFieldChange(fieldName, value)
1461
+ };
1462
+ };
1463
+ /**
1367
1464
  * ControlArray component for editing arrays of schema items.
1368
1465
  *
1369
1466
  * Features:
@@ -1372,6 +1469,7 @@ const parseInput = (props, form) => {
1372
1469
  * - Supports arrays of primitives
1373
1470
  * - Grid layout for object items
1374
1471
  * - Min/max constraints
1472
+ * - Syncs with form state (handles external updates and resets)
1375
1473
  *
1376
1474
  * @example
1377
1475
  * ```tsx
@@ -1395,36 +1493,13 @@ const parseInput = (props, form) => {
1395
1493
  */
1396
1494
  const ControlArray = (props) => {
1397
1495
  const { inputProps } = parseInput(props, {});
1398
- const idCounter = useRef(0);
1399
- const [items, setItems] = useState(() => {
1400
- const defaultValue = props.input?.props?.defaultValue;
1401
- if (Array.isArray(defaultValue)) return defaultValue.map((value) => ({
1402
- key: idCounter.current++,
1403
- value
1404
- }));
1405
- return [];
1406
- });
1407
- useEvents({ "form:reset": (event) => {
1408
- if (event.id === props.input?.form?.id) {
1409
- const defaultValue = props.input?.props?.defaultValue;
1410
- if (Array.isArray(defaultValue)) {
1411
- idCounter.current = 0;
1412
- setItems(defaultValue.map((value) => ({
1413
- key: idCounter.current++,
1414
- value
1415
- })));
1416
- } else setItems([]);
1417
- }
1418
- } }, [props.input]);
1496
+ const { items, setItems, nextKey } = useArrayItems(props.input);
1419
1497
  if (!props.input?.props) return null;
1420
1498
  const schema = props.input.schema;
1421
1499
  if (!schema || !("items" in schema)) return null;
1422
1500
  const itemSchema = schema.items;
1423
1501
  const isObjectItem = itemSchema && "properties" in itemSchema;
1424
1502
  const { min = 0, max = Number.POSITIVE_INFINITY, columns = 1 } = props;
1425
- const updateFormValue = (newItems) => {
1426
- props.input.set(newItems.map((item) => item.value));
1427
- };
1428
1503
  const handleAdd = () => {
1429
1504
  if (items.length >= max) return;
1430
1505
  let newValue;
@@ -1433,18 +1508,14 @@ const ControlArray = (props) => {
1433
1508
  const objSchema = itemSchema;
1434
1509
  for (const [key, propSchema] of Object.entries(objSchema.properties)) if ("default" in propSchema) newValue[key] = propSchema.default;
1435
1510
  } else newValue = "";
1436
- const newItems = [...items, {
1437
- key: idCounter.current++,
1511
+ setItems([...items, {
1512
+ key: nextKey(),
1438
1513
  value: newValue
1439
- }];
1440
- setItems(newItems);
1441
- updateFormValue(newItems);
1514
+ }]);
1442
1515
  };
1443
1516
  const handleRemove = (index) => {
1444
1517
  if (items.length <= min) return;
1445
- const newItems = items.filter((_, i) => i !== index);
1446
- setItems(newItems);
1447
- updateFormValue(newItems);
1518
+ setItems(items.filter((_, i) => i !== index));
1448
1519
  };
1449
1520
  const handleItemChange = (index, value) => {
1450
1521
  const newItems = [...items];
@@ -1453,7 +1524,6 @@ const ControlArray = (props) => {
1453
1524
  value
1454
1525
  };
1455
1526
  setItems(newItems);
1456
- updateFormValue(newItems);
1457
1527
  };
1458
1528
  const handleFieldChange = (index, field, value) => {
1459
1529
  const newItems = [...items];
@@ -1465,11 +1535,12 @@ const ControlArray = (props) => {
1465
1535
  }
1466
1536
  };
1467
1537
  setItems(newItems);
1468
- updateFormValue(newItems);
1469
1538
  };
1470
1539
  const colSpan = 12 / columns;
1471
- const fieldNames = isObjectItem ? Object.keys(itemSchema.properties) : [];
1472
- const renderItems = () => /* @__PURE__ */ jsxs(Stack, {
1540
+ const objectItemSchema = isObjectItem ? itemSchema : null;
1541
+ const fieldNames = objectItemSchema ? Object.keys(objectItemSchema.properties) : [];
1542
+ const renderItems = () => /* @__PURE__ */ jsxs(Flex$1, {
1543
+ direction: "column",
1473
1544
  gap: "sm",
1474
1545
  children: [items.map((item, index) => /* @__PURE__ */ jsxs(Flex$1, {
1475
1546
  gap: "sm",
@@ -1484,47 +1555,24 @@ const ControlArray = (props) => {
1484
1555
  style: { cursor: "grab" },
1485
1556
  children: /* @__PURE__ */ jsx(IconGripVertical, { size: 16 })
1486
1557
  }),
1487
- isObjectItem ? /* @__PURE__ */ jsx(Grid, {
1558
+ objectItemSchema ? /* @__PURE__ */ jsx(Grid, {
1488
1559
  style: { flex: 1 },
1489
1560
  gutter: "sm",
1490
1561
  children: fieldNames.map((fieldName) => {
1491
- const fieldSchema = itemSchema.properties[fieldName];
1492
1562
  const fieldControlProps = props.controlProps?.[fieldName] ?? {};
1493
- const virtualInput = {
1494
- schema: fieldSchema,
1495
- props: {
1496
- id: `${props.input.props.id}-${item.key}-${fieldName}`,
1497
- name: `${props.input.props.name}[${index}].${fieldName}`,
1498
- defaultValue: item.value?.[fieldName]
1499
- },
1500
- path: `${props.input.path}/${index}/${fieldName}`,
1501
- required: itemSchema.required?.includes(fieldName) ?? false,
1502
- form: props.input.form,
1503
- set: (value) => handleFieldChange(index, fieldName, value)
1504
- };
1563
+ const fieldInput = createArrayItemFieldInput(props.input, objectItemSchema, fieldName, index, item.key, item.value, (field, value) => handleFieldChange(index, field, value));
1505
1564
  return /* @__PURE__ */ jsx(Grid.Col, {
1506
1565
  span: colSpan,
1507
- children: /* @__PURE__ */ jsx(Control_default, {
1508
- input: virtualInput,
1566
+ children: /* @__PURE__ */ jsx(Control, {
1567
+ input: fieldInput,
1509
1568
  ...fieldControlProps
1510
1569
  })
1511
1570
  }, fieldName);
1512
1571
  })
1513
1572
  }) : /* @__PURE__ */ jsx(Flex$1, {
1514
1573
  style: { flex: 1 },
1515
- children: /* @__PURE__ */ jsx(Control_default, {
1516
- input: {
1517
- schema: itemSchema,
1518
- props: {
1519
- id: `${props.input.props.id}-${item.key}`,
1520
- name: `${props.input.props.name}[${index}]`,
1521
- defaultValue: item.value
1522
- },
1523
- path: `${props.input.path}/${index}`,
1524
- required: false,
1525
- form: props.input.form,
1526
- set: (value) => handleItemChange(index, value)
1527
- },
1574
+ children: /* @__PURE__ */ jsx(Control, {
1575
+ input: createArrayItemInput(props.input, itemSchema, index, item.key, item.value, (value) => handleItemChange(index, value)),
1528
1576
  ...props.itemControlProps
1529
1577
  })
1530
1578
  }),
@@ -1568,7 +1616,8 @@ const ControlArray = (props) => {
1568
1616
  children: [/* @__PURE__ */ jsx(IconPlus, { size: 14 }), props.addLabel ?? "Add"]
1569
1617
  })]
1570
1618
  });
1571
- if (props.variant === "plain") return /* @__PURE__ */ jsxs(Stack, {
1619
+ if (props.variant === "plain") return /* @__PURE__ */ jsxs(Flex$1, {
1620
+ direction: "column",
1572
1621
  gap: "xs",
1573
1622
  children: [
1574
1623
  inputProps.label && /* @__PURE__ */ jsx(Text$1, {
@@ -1591,7 +1640,8 @@ const ControlArray = (props) => {
1591
1640
  });
1592
1641
  return /* @__PURE__ */ jsx(Fieldset, {
1593
1642
  legend: inputProps.label,
1594
- children: /* @__PURE__ */ jsxs(Stack, {
1643
+ children: /* @__PURE__ */ jsxs(Flex$1, {
1644
+ direction: "column",
1595
1645
  gap: "xs",
1596
1646
  children: [
1597
1647
  inputProps.description && /* @__PURE__ */ jsx(Text$1, {
@@ -1609,7 +1659,6 @@ const ControlArray = (props) => {
1609
1659
  })
1610
1660
  });
1611
1661
  };
1612
- var ControlArray_default = ControlArray;
1613
1662
 
1614
1663
  //#endregion
1615
1664
  //#region ../../src/core/components/form/ControlDate.tsx
@@ -1667,7 +1716,6 @@ const ControlDate = (props) => {
1667
1716
  }
1668
1717
  return null;
1669
1718
  };
1670
- var ControlDate_default = ControlDate;
1671
1719
 
1672
1720
  //#endregion
1673
1721
  //#region ../../src/core/components/form/ControlNumber.tsx
@@ -1726,7 +1774,6 @@ const ControlNumber = (props) => {
1726
1774
  }
1727
1775
  });
1728
1776
  };
1729
- var ControlNumber_default = ControlNumber;
1730
1777
 
1731
1778
  //#endregion
1732
1779
  //#region ../../src/core/components/form/ControlObject.tsx
@@ -1776,7 +1823,7 @@ const ControlObject = (props) => {
1776
1823
  if (!field) return null;
1777
1824
  return /* @__PURE__ */ jsx(Grid.Col, {
1778
1825
  span: colSpan,
1779
- children: /* @__PURE__ */ jsx(Control_default, {
1826
+ children: /* @__PURE__ */ jsx(Control, {
1780
1827
  input: field,
1781
1828
  ...fieldControlProps
1782
1829
  })
@@ -1785,7 +1832,8 @@ const ControlObject = (props) => {
1785
1832
  if (props.variant === "plain") return renderFields();
1786
1833
  return /* @__PURE__ */ jsx(Fieldset, {
1787
1834
  legend: inputProps.label,
1788
- children: /* @__PURE__ */ jsxs(Stack, {
1835
+ children: /* @__PURE__ */ jsxs(Flex$1, {
1836
+ direction: "column",
1789
1837
  gap: "xs",
1790
1838
  children: [
1791
1839
  inputProps.description && /* @__PURE__ */ jsx(Text$1, {
@@ -1803,7 +1851,6 @@ const ControlObject = (props) => {
1803
1851
  })
1804
1852
  });
1805
1853
  };
1806
- var ControlObject_default = ControlObject;
1807
1854
 
1808
1855
  //#endregion
1809
1856
  //#region ../../src/core/utils/extractSchemaFields.ts
@@ -2010,7 +2057,7 @@ const ControlQueryBuilder = ({ schema, value = "", onChange, placeholder = "Ente
2010
2057
  });
2011
2058
  };
2012
2059
  function QueryHelp({ fields, onInsert }) {
2013
- return /* @__PURE__ */ jsxs(Group, {
2060
+ return /* @__PURE__ */ jsxs(Flex$1, {
2014
2061
  gap: "md",
2015
2062
  align: "flex-start",
2016
2063
  wrap: "nowrap",
@@ -2018,22 +2065,25 @@ function QueryHelp({ fields, onInsert }) {
2018
2065
  p: "sm",
2019
2066
  bdrs: "sm",
2020
2067
  children: [
2021
- /* @__PURE__ */ jsxs(Stack, {
2068
+ /* @__PURE__ */ jsxs(Flex$1, {
2069
+ direction: "column",
2022
2070
  gap: "md",
2023
2071
  style: { flex: 1 },
2024
2072
  children: [
2025
- /* @__PURE__ */ jsxs(Stack, {
2073
+ /* @__PURE__ */ jsxs(Flex$1, {
2074
+ direction: "column",
2026
2075
  gap: "xs",
2027
2076
  children: [/* @__PURE__ */ jsx(Text$1, {
2028
2077
  size: "sm",
2029
2078
  fw: 600,
2030
2079
  children: "Operators"
2031
- }), /* @__PURE__ */ jsx(Stack, {
2080
+ }), /* @__PURE__ */ jsx(Flex$1, {
2081
+ direction: "column",
2032
2082
  gap: 4,
2033
- children: Object.entries(OPERATOR_INFO).map(([key, info]) => /* @__PURE__ */ jsxs(Group, {
2083
+ children: Object.entries(OPERATOR_INFO).map(([key, info]) => /* @__PURE__ */ jsxs(Flex$1, {
2034
2084
  gap: "xs",
2035
2085
  wrap: "nowrap",
2036
- children: [/* @__PURE__ */ jsx(ActionButton_default, {
2086
+ children: [/* @__PURE__ */ jsx(ActionButton, {
2037
2087
  px: "xs",
2038
2088
  size: "xs",
2039
2089
  h: 24,
@@ -2052,18 +2102,20 @@ function QueryHelp({ fields, onInsert }) {
2052
2102
  })]
2053
2103
  }),
2054
2104
  /* @__PURE__ */ jsx(Divider, {}),
2055
- /* @__PURE__ */ jsxs(Stack, {
2105
+ /* @__PURE__ */ jsxs(Flex$1, {
2106
+ direction: "column",
2056
2107
  gap: "xs",
2057
2108
  children: [/* @__PURE__ */ jsx(Text$1, {
2058
2109
  size: "sm",
2059
2110
  fw: 600,
2060
2111
  children: "Logic"
2061
- }), /* @__PURE__ */ jsxs(Stack, {
2112
+ }), /* @__PURE__ */ jsxs(Flex$1, {
2113
+ direction: "column",
2062
2114
  gap: 4,
2063
- children: [/* @__PURE__ */ jsxs(Group, {
2115
+ children: [/* @__PURE__ */ jsxs(Flex$1, {
2064
2116
  gap: "xs",
2065
2117
  wrap: "nowrap",
2066
- children: [/* @__PURE__ */ jsx(ActionButton_default, {
2118
+ children: [/* @__PURE__ */ jsx(ActionButton, {
2067
2119
  px: "xs",
2068
2120
  size: "xs",
2069
2121
  h: 24,
@@ -2077,10 +2129,10 @@ function QueryHelp({ fields, onInsert }) {
2077
2129
  c: "dimmed",
2078
2130
  children: "AND"
2079
2131
  })]
2080
- }), /* @__PURE__ */ jsxs(Group, {
2132
+ }), /* @__PURE__ */ jsxs(Flex$1, {
2081
2133
  gap: "xs",
2082
2134
  wrap: "nowrap",
2083
- children: [/* @__PURE__ */ jsx(ActionButton_default, {
2135
+ children: [/* @__PURE__ */ jsx(ActionButton, {
2084
2136
  px: "xs",
2085
2137
  size: "xs",
2086
2138
  h: 24,
@@ -2120,7 +2172,7 @@ function QueryHelp({ fields, onInsert }) {
2120
2172
  wrap: "nowrap",
2121
2173
  align: "flex-start",
2122
2174
  children: [
2123
- /* @__PURE__ */ jsx(ActionButton_default, {
2175
+ /* @__PURE__ */ jsx(ActionButton, {
2124
2176
  px: "xs",
2125
2177
  size: "xs",
2126
2178
  h: 24,
@@ -2143,10 +2195,10 @@ function QueryHelp({ fields, onInsert }) {
2143
2195
  c: "dimmed",
2144
2196
  lineClamp: 1,
2145
2197
  children: field.description || field.type
2146
- }), field.enum && /* @__PURE__ */ jsx(Group, {
2198
+ }), field.enum && /* @__PURE__ */ jsx(Flex$1, {
2147
2199
  gap: 0,
2148
2200
  wrap: "wrap",
2149
- children: field.enum.map((enumValue) => /* @__PURE__ */ jsx(ActionButton_default, {
2201
+ children: field.enum.map((enumValue) => /* @__PURE__ */ jsx(ActionButton, {
2150
2202
  px: "xs",
2151
2203
  size: "xs",
2152
2204
  h: 24,
@@ -2168,7 +2220,6 @@ function QueryHelp({ fields, onInsert }) {
2168
2220
  ]
2169
2221
  });
2170
2222
  }
2171
- var ControlQueryBuilder_default = ControlQueryBuilder;
2172
2223
 
2173
2224
  //#endregion
2174
2225
  //#region ../../src/core/components/form/ControlSelect.tsx
@@ -2220,6 +2271,7 @@ const ControlSelect = (props) => {
2220
2271
  const autocompleteProps = typeof props.autocomplete === "object" ? props.autocomplete : {};
2221
2272
  return /* @__PURE__ */ jsx(Autocomplete, {
2222
2273
  ...inputProps,
2274
+ size: props.size,
2223
2275
  id,
2224
2276
  leftSection: icon,
2225
2277
  data,
@@ -2231,6 +2283,7 @@ const ControlSelect = (props) => {
2231
2283
  const tagsInputProps = typeof props.tags === "object" ? props.tags : {};
2232
2284
  return /* @__PURE__ */ jsx(TagsInput, {
2233
2285
  ...inputProps,
2286
+ size: props.size,
2234
2287
  id,
2235
2288
  leftSection: icon,
2236
2289
  defaultValue: Array.isArray(props.input.props.defaultValue) ? props.input.props.defaultValue : [],
@@ -2248,6 +2301,7 @@ const ControlSelect = (props) => {
2248
2301
  const multiSelectProps = typeof props.multi === "object" ? props.multi : {};
2249
2302
  return /* @__PURE__ */ jsx(MultiSelect, {
2250
2303
  ...inputProps,
2304
+ size: props.size,
2251
2305
  id,
2252
2306
  leftSection: icon,
2253
2307
  data,
@@ -2261,6 +2315,7 @@ const ControlSelect = (props) => {
2261
2315
  const selectProps = typeof props.select === "object" ? props.select : {};
2262
2316
  return /* @__PURE__ */ jsx(Select, {
2263
2317
  ...inputProps,
2318
+ size: props.size,
2264
2319
  id,
2265
2320
  leftSection: icon,
2266
2321
  data,
@@ -2268,7 +2323,6 @@ const ControlSelect = (props) => {
2268
2323
  ...selectProps
2269
2324
  });
2270
2325
  };
2271
- var ControlSelect_default = ControlSelect;
2272
2326
 
2273
2327
  //#endregion
2274
2328
  //#region ../../src/core/components/form/Control.tsx
@@ -2304,7 +2358,7 @@ const Control = (_props) => {
2304
2358
  ..._props,
2305
2359
  ...schema.$control
2306
2360
  };
2307
- if (props.query) return /* @__PURE__ */ jsx(ControlQueryBuilder_default, {
2361
+ if (props.query) return /* @__PURE__ */ jsx(ControlQueryBuilder, {
2308
2362
  ...props.input.props,
2309
2363
  ...inputProps,
2310
2364
  schema: props.query,
@@ -2332,7 +2386,7 @@ const Control = (_props) => {
2332
2386
  const isObject = props.input.schema && "type" in props.input.schema && props.input.schema.type === "object" && "properties" in props.input.schema;
2333
2387
  if (props.object || isObject) {
2334
2388
  const controlObjectProps = typeof props.object === "object" ? props.object : {};
2335
- return /* @__PURE__ */ jsx(ControlObject_default, {
2389
+ return /* @__PURE__ */ jsx(ControlObject, {
2336
2390
  input: props.input,
2337
2391
  title: props.title,
2338
2392
  description: props.description,
@@ -2343,7 +2397,7 @@ const Control = (_props) => {
2343
2397
  const isArrayOfObjects = isArray && "items" in props.input.schema && props.input.schema.items && typeof props.input.schema.items === "object" && "properties" in props.input.schema.items;
2344
2398
  if (props.array || isArrayOfObjects) {
2345
2399
  const controlArrayProps = typeof props.array === "object" ? props.array : {};
2346
- return /* @__PURE__ */ jsx(ControlArray_default, {
2400
+ return /* @__PURE__ */ jsx(ControlArray, {
2347
2401
  input: props.input,
2348
2402
  title: props.title,
2349
2403
  description: props.description,
@@ -2353,7 +2407,8 @@ const Control = (_props) => {
2353
2407
  if (props.number || props.input.schema && "type" in props.input.schema && (props.input.schema.type === "number" || props.input.schema.type === "integer")) {
2354
2408
  const controlNumberProps = typeof props.number === "object" ? props.number : {};
2355
2409
  if (props.slider) controlNumberProps.sliderProps ??= {};
2356
- return /* @__PURE__ */ jsx(ControlNumber_default, {
2410
+ return /* @__PURE__ */ jsx(ControlNumber, {
2411
+ size: props.size,
2357
2412
  input: props.input,
2358
2413
  title: props.title,
2359
2414
  description: props.description,
@@ -2365,6 +2420,7 @@ const Control = (_props) => {
2365
2420
  const fileInputProps = typeof props.file === "object" ? props.file : {};
2366
2421
  return /* @__PURE__ */ jsx(FileInput, {
2367
2422
  ...inputProps,
2423
+ size: props.size,
2368
2424
  id,
2369
2425
  leftSection: icon,
2370
2426
  onChange: (file) => {
@@ -2377,6 +2433,7 @@ const Control = (_props) => {
2377
2433
  const colorInputProps = typeof props.color === "object" ? props.color : {};
2378
2434
  return /* @__PURE__ */ jsx(ColorInput, {
2379
2435
  ...inputProps,
2436
+ size: props.size,
2380
2437
  id,
2381
2438
  leftSection: icon,
2382
2439
  ...props.input.props,
@@ -2386,7 +2443,8 @@ const Control = (_props) => {
2386
2443
  if (props.input.schema && "enum" in props.input.schema && props.input.schema.enum || isArray && !isArrayOfObjects || props.select) {
2387
2444
  const opts = typeof props.select === "object" ? props.select : {};
2388
2445
  if (props.segmented) opts.segmented ??= {};
2389
- return /* @__PURE__ */ jsx(ControlSelect_default, {
2446
+ return /* @__PURE__ */ jsx(ControlSelect, {
2447
+ size: props.size,
2390
2448
  input: props.input,
2391
2449
  title: props.title,
2392
2450
  description: props.description,
@@ -2395,48 +2453,24 @@ const Control = (_props) => {
2395
2453
  });
2396
2454
  }
2397
2455
  if (props.input.schema && "type" in props.input.schema && props.input.schema.type === "boolean") {
2398
- if (props.switch) {
2399
- const switchProps = typeof props.switch === "object" ? props.switch : {};
2400
- return /* @__PURE__ */ jsx(Switch, {
2401
- ...inputProps,
2402
- id,
2403
- color: "blue",
2404
- defaultChecked: props.input.props.defaultValue,
2405
- onChange: (event) => {
2406
- props.input.set(event.currentTarget.checked);
2407
- },
2408
- ...switchProps
2409
- });
2410
- }
2411
- const selectProps = {
2412
- loader: async () => [
2413
- {
2414
- value: "true",
2415
- label: "Yes"
2416
- },
2417
- {
2418
- value: "false",
2419
- label: "No"
2420
- },
2421
- {
2422
- value: "",
2423
- label: ""
2424
- }
2425
- ],
2426
- ...props.input.props
2427
- };
2428
- return /* @__PURE__ */ jsx(ControlSelect_default, {
2429
- input: props.input,
2430
- title: props.title,
2431
- description: props.description,
2432
- icon,
2433
- ...selectProps
2456
+ const switchProps = typeof props.switch === "object" ? props.switch : {};
2457
+ return /* @__PURE__ */ jsx(Switch, {
2458
+ ...inputProps,
2459
+ size: props.size,
2460
+ id,
2461
+ color: "blue",
2462
+ defaultChecked: props.input.props.defaultValue,
2463
+ onChange: (event) => {
2464
+ props.input.set(event.currentTarget.checked);
2465
+ },
2466
+ ...switchProps
2434
2467
  });
2435
2468
  }
2436
2469
  if (props.password || props.input.props.name?.includes("password")) {
2437
2470
  const passwordInputProps = typeof props.password === "object" ? props.password : {};
2438
2471
  return /* @__PURE__ */ jsx(PasswordInput, {
2439
2472
  ...inputProps,
2473
+ size: props.size,
2440
2474
  id,
2441
2475
  leftSection: icon,
2442
2476
  ...props.input.props,
@@ -2447,13 +2481,15 @@ const Control = (_props) => {
2447
2481
  const textAreaProps = typeof props.area === "object" ? props.area : {};
2448
2482
  return /* @__PURE__ */ jsx(Textarea, {
2449
2483
  ...inputProps,
2484
+ size: props.size,
2450
2485
  id,
2451
2486
  leftSection: icon,
2452
2487
  ...props.input.props,
2453
2488
  ...textAreaProps
2454
2489
  });
2455
2490
  }
2456
- if (props.date || props.datetime || props.time || format === "date" || format === "date-time" || format === "time") return /* @__PURE__ */ jsx(ControlDate_default, {
2491
+ if (props.date || props.datetime || props.time || format === "date" || format === "date-time" || format === "time") return /* @__PURE__ */ jsx(ControlDate, {
2492
+ size: props.size,
2457
2493
  input: props.input,
2458
2494
  title: props.title,
2459
2495
  description: props.description,
@@ -2475,6 +2511,7 @@ const Control = (_props) => {
2475
2511
  };
2476
2512
  return /* @__PURE__ */ jsx(TextInput, {
2477
2513
  ...inputProps,
2514
+ size: props.size,
2478
2515
  id,
2479
2516
  leftSection: icon,
2480
2517
  type: getInputType(),
@@ -2488,7 +2525,6 @@ const Control = (_props) => {
2488
2525
  ]
2489
2526
  });
2490
2527
  };
2491
- var Control_default = Control;
2492
2528
 
2493
2529
  //#endregion
2494
2530
  //#region ../../src/core/components/form/TypeForm.tsx
@@ -2534,7 +2570,7 @@ var Control_default = Control;
2534
2570
  * ```
2535
2571
  */
2536
2572
  const TypeForm = (props) => {
2537
- const { form, columns = 3, children, controlProps, fieldControlProps, skipFormElement = false, skipSubmitButton = false, submitButtonProps, fill = true } = props;
2573
+ const { form, columns = 3, children, controlProps, fieldControlProps, skipFormElement = false, skipSubmitButton = false, submitButtonProps, fill = true, size } = props;
2538
2574
  const schema = props.schema || form.options.schema;
2539
2575
  if (!schema?.properties) return null;
2540
2576
  const supportedFields = Object.keys(schema.properties);
@@ -2551,7 +2587,7 @@ const TypeForm = (props) => {
2551
2587
  xl: columns.xl ? 12 / columns.xl : void 0
2552
2588
  };
2553
2589
  const renderFields = () => {
2554
- if (children) return /* @__PURE__ */ jsx(Fragment, { children: children(form.input) });
2590
+ if (children) return /* @__PURE__ */ jsx(Fragment$1, { children: children(form.input) });
2555
2591
  return /* @__PURE__ */ jsx(Grid, { children: supportedFields.map((fieldName) => {
2556
2592
  const field = form.input[fieldName];
2557
2593
  const fieldSchema = schema.properties[fieldName];
@@ -2563,9 +2599,10 @@ const TypeForm = (props) => {
2563
2599
  ...controlProps,
2564
2600
  ...fieldControlProps?.[fieldName]
2565
2601
  };
2602
+ if (size) mergedControlProps.size = size;
2566
2603
  return /* @__PURE__ */ jsx(Grid.Col, {
2567
2604
  span,
2568
- children: /* @__PURE__ */ jsx(Control_default, {
2605
+ children: /* @__PURE__ */ jsx(Control, {
2569
2606
  input: field,
2570
2607
  ...mergedControlProps
2571
2608
  })
@@ -2593,11 +2630,11 @@ const TypeForm = (props) => {
2593
2630
  /* @__PURE__ */ jsx(Flex$1, { flex: 1 }),
2594
2631
  /* @__PURE__ */ jsxs(Flex$1, {
2595
2632
  gap: "sm",
2596
- children: [/* @__PURE__ */ jsx(ActionButton_default, {
2633
+ children: [/* @__PURE__ */ jsx(ActionButton, {
2597
2634
  variant: "subtle",
2598
2635
  type: "reset",
2599
2636
  children: "Reset"
2600
- }), /* @__PURE__ */ jsx(ActionButton_default, {
2637
+ }), /* @__PURE__ */ jsx(ActionButton, {
2601
2638
  intent: "primary",
2602
2639
  form,
2603
2640
  ...submitButtonProps,
@@ -2617,7 +2654,6 @@ const TypeForm = (props) => {
2617
2654
  children: content
2618
2655
  });
2619
2656
  };
2620
- var TypeForm_default = TypeForm;
2621
2657
 
2622
2658
  //#endregion
2623
2659
  //#region ../../src/core/components/layout/AppBar.tsx
@@ -2627,10 +2663,10 @@ const AppBar = (props) => {
2627
2663
  const renderItem = (item, index) => {
2628
2664
  if (item.can && !item.can()) return null;
2629
2665
  if ("type" in item) {
2630
- if (item.type === "burger") return /* @__PURE__ */ jsx(BurgerButton_default, {}, index);
2631
- if (item.type === "dark") return /* @__PURE__ */ jsx(DarkModeButton_default, { ...item.props }, index);
2632
- if (item.type === "search") return /* @__PURE__ */ jsx(OmnibarButton_default, { ...item.props }, index);
2633
- if (item.type === "lang") return /* @__PURE__ */ jsx(LanguageButton_default, { ...item.props }, index);
2666
+ if (item.type === "burger") return /* @__PURE__ */ jsx(BurgerButton, {}, index);
2667
+ if (item.type === "dark") return /* @__PURE__ */ jsx(DarkModeButton, { ...item.props }, index);
2668
+ if (item.type === "search") return /* @__PURE__ */ jsx(OmnibarButton, { ...item.props }, index);
2669
+ if (item.type === "lang") return /* @__PURE__ */ jsx(LanguageButton, { ...item.props }, index);
2634
2670
  if (item.type === "spacer") return /* @__PURE__ */ jsx(Flex$1, { w: 16 }, index);
2635
2671
  if (item.type === "divider") return /* @__PURE__ */ jsx(Divider, { orientation: "vertical" }, index);
2636
2672
  if (item.type === "logo") return renderLogo(item, index);
@@ -2672,7 +2708,7 @@ const AppBar = (props) => {
2672
2708
  if (href) router.push(href);
2673
2709
  else router.back();
2674
2710
  };
2675
- if (iconOnly) return /* @__PURE__ */ jsx(ActionButton_default, {
2711
+ if (iconOnly) return /* @__PURE__ */ jsx(ActionButton, {
2676
2712
  icon: iconElement,
2677
2713
  variant: "subtle",
2678
2714
  color: "gray",
@@ -2682,7 +2718,7 @@ const AppBar = (props) => {
2682
2718
  position: "bottom"
2683
2719
  }
2684
2720
  }, index);
2685
- return /* @__PURE__ */ jsx(ActionButton_default, {
2721
+ return /* @__PURE__ */ jsx(ActionButton, {
2686
2722
  leftSection: iconElement,
2687
2723
  variant: "subtle",
2688
2724
  color: "gray",
@@ -2732,19 +2768,71 @@ const AppBar = (props) => {
2732
2768
  });
2733
2769
  return content;
2734
2770
  };
2735
- var AppBar_default = AppBar;
2771
+
2772
+ //#endregion
2773
+ //#region ../../src/core/components/layout/Breadcrumb.tsx
2774
+ /**
2775
+ * Automatic breadcrumb component that reads the current route hierarchy
2776
+ * from the Alepha router's layer stack.
2777
+ *
2778
+ * Pages should define a `label` in their `$page()` options for best results.
2779
+ * Falls back to the page name converted to Title Case.
2780
+ */
2781
+ const Breadcrumb = ({ home = "Home", separator, size = "xs", ...groupProps }) => {
2782
+ const state = useRouterState();
2783
+ const router = useRouter();
2784
+ const crumbs = [];
2785
+ if (home !== false) crumbs.push({
2786
+ label: home,
2787
+ href: "/"
2788
+ });
2789
+ for (let i = 1; i < state.layers.length; i++) {
2790
+ const layer = state.layers[i];
2791
+ const route = layer.route;
2792
+ if (route?.path === "/" || route?.path === "") continue;
2793
+ const label = route?.label ?? toTitleCase(layer.name);
2794
+ const href = router.path(layer.name);
2795
+ crumbs.push({
2796
+ label,
2797
+ href
2798
+ });
2799
+ }
2800
+ if (crumbs.length === 0) return null;
2801
+ const sep = separator ?? /* @__PURE__ */ jsx(IconChevronRight, {
2802
+ size: 12,
2803
+ color: "#9ca3af"
2804
+ });
2805
+ return /* @__PURE__ */ jsx(Flex$1, {
2806
+ gap: 4,
2807
+ ...groupProps,
2808
+ children: crumbs.map((crumb, i) => /* @__PURE__ */ jsxs(Flex$1, {
2809
+ gap: 4,
2810
+ children: [i > 0 && sep, i < crumbs.length - 1 ? /* @__PURE__ */ jsx(Anchor, {
2811
+ component: Link,
2812
+ href: crumb.href,
2813
+ size,
2814
+ c: "dimmed",
2815
+ children: crumb.label
2816
+ }) : /* @__PURE__ */ jsx(Text$1, {
2817
+ size,
2818
+ fw: 500,
2819
+ children: crumb.label
2820
+ })]
2821
+ }, crumb.href))
2822
+ });
2823
+ };
2736
2824
 
2737
2825
  //#endregion
2738
2826
  //#region ../../src/core/components/layout/Sidebar.tsx
2739
2827
  const Sidebar = (props) => {
2740
2828
  const router = useRouter();
2741
2829
  const { onItemClick } = props;
2742
- const divider = (key) => {
2830
+ const divider = (key, fill) => {
2743
2831
  return /* @__PURE__ */ jsx(Flex$1, {
2744
2832
  h: 1,
2745
- bg: "var(--alepha-border)",
2833
+ bg: "var(--mantine-color-default-border)",
2746
2834
  my: "xs",
2747
- mx: props.collapsed ? 0 : "sm"
2835
+ mx: fill ? "calc(-1 * var(--mantine-spacing-md))" : props.collapsed ? 0 : "sm"
2748
2836
  }, key);
2749
2837
  };
2750
2838
  const renderNode = (item, key) => {
@@ -2753,29 +2841,32 @@ const Sidebar = (props) => {
2753
2841
  if (props.collapsed) return null;
2754
2842
  return /* @__PURE__ */ jsx(Flex$1, { h: 16 }, key);
2755
2843
  }
2756
- if (item.type === "divider") return divider(key);
2844
+ if (item.type === "divider") return divider(key, item.fill);
2757
2845
  if (item.type === "search") return /* @__PURE__ */ jsx(Flex$1, {
2758
2846
  mb: "xs",
2759
- children: /* @__PURE__ */ jsx(OmnibarButton_default, { collapsed: props.collapsed })
2847
+ children: /* @__PURE__ */ jsx(OmnibarButton, { collapsed: props.collapsed })
2760
2848
  }, key);
2761
- if (item.type === "toggle") return /* @__PURE__ */ jsx(ToggleSidebarButton_default, {}, key);
2849
+ if (item.type === "toggle") return /* @__PURE__ */ jsx(ToggleSidebarButton, {}, key);
2762
2850
  if (item.type === "section") {
2763
- if (props.collapsed) return divider(key);
2764
- return /* @__PURE__ */ jsxs(Flex$1, {
2851
+ if (item.children && item.children.length > 0) {
2852
+ if (!item.children.some((child) => !("can" in child) || !child.can || child.can())) return null;
2853
+ }
2854
+ if (props.collapsed) return /* @__PURE__ */ jsxs(Fragment, { children: [divider(`${key}-d`), item.children?.map((child, index) => renderNode(child, `s${key}-${index}`))] }, key);
2855
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Flex$1, {
2765
2856
  mt: "md",
2766
2857
  align: "center",
2767
2858
  gap: "xs",
2768
- children: [renderIcon(item.icon), /* @__PURE__ */ jsx(Text$1, {
2859
+ children: [renderIcon(item.icon, ui.sizes.icon.sm), /* @__PURE__ */ jsx(Text$1, {
2769
2860
  size: "xs",
2770
2861
  c: "dimmed",
2771
2862
  tt: "uppercase",
2772
2863
  fw: "bold",
2773
2864
  children: item.label
2774
2865
  })]
2775
- }, key);
2866
+ }), item.children?.map((child, index) => renderNode(child, `s${key}-${index}`))] }, key);
2776
2867
  }
2777
2868
  }
2778
- if ("element" in item) return /* @__PURE__ */ jsx(Flex$1, { children: item.element }, key);
2869
+ if ("element" in item) return /* @__PURE__ */ jsx(Fragment, { children: item.element }, key);
2779
2870
  if (item.can && !item.can()) return null;
2780
2871
  if (item.children && item.children.length > 0) {
2781
2872
  if (!item.children.some((child) => !child.can || child.can())) return null;
@@ -2810,7 +2901,7 @@ const Sidebar = (props) => {
2810
2901
  return [];
2811
2902
  };
2812
2903
  const padding = "md";
2813
- const gap = props.items ? props.gap ?? 2 : "xs";
2904
+ const gap = props.items ? props.gap ?? 4 : "xs";
2814
2905
  const menu = useMemo(() => getSidebarNodes(), [props.items, props.autoPopulateMenu]);
2815
2906
  return /* @__PURE__ */ jsxs(Flex$1, {
2816
2907
  flex: 1,
@@ -2873,14 +2964,16 @@ const SidebarItem = (props) => {
2873
2964
  direction: "column",
2874
2965
  ps: level === 0 ? 0 : 32,
2875
2966
  pos: "relative",
2876
- children: [/* @__PURE__ */ jsx(ActionButton_default, {
2967
+ children: [/* @__PURE__ */ jsx(ActionButton, {
2877
2968
  w: "100%",
2878
2969
  justify: "space-between",
2879
2970
  href: props.item.href,
2880
2971
  target: props.item.target,
2881
2972
  size: props.item.theme?.size ?? props.theme.button?.size ?? (level === 0 ? "sm" : "xs"),
2882
- tooltip: item.description,
2883
- c: "var(--mantine-color-text)",
2973
+ tooltip: item.description ? {
2974
+ label: item.description,
2975
+ position: "right"
2976
+ } : void 0,
2884
2977
  color: "gray",
2885
2978
  variant: "subtle",
2886
2979
  variantActive: "default",
@@ -2890,7 +2983,7 @@ const SidebarItem = (props) => {
2890
2983
  w: "100%",
2891
2984
  align: "center",
2892
2985
  gap: "sm",
2893
- children: [renderIcon(item.icon), /* @__PURE__ */ jsx(Flex$1, {
2986
+ children: [renderIcon(item.icon, ui.sizes.icon.sm), /* @__PURE__ */ jsx(Flex$1, {
2894
2987
  direction: "column",
2895
2988
  children: /* @__PURE__ */ jsx(Flex$1, { children: item.label })
2896
2989
  })]
@@ -2903,7 +2996,7 @@ const SidebarItem = (props) => {
2903
2996
  children: [/* @__PURE__ */ jsx(Flex$1, { style: {
2904
2997
  position: "absolute",
2905
2998
  width: 1,
2906
- background: "linear-gradient(to bottom, transparent, var(--alepha-border), transparent)",
2999
+ background: "linear-gradient(to bottom, transparent, var(--mantine-color-default-border), transparent)",
2907
3000
  top: 48,
2908
3001
  left: 20 + 32 * level,
2909
3002
  bottom: 16
@@ -2922,7 +3015,7 @@ const SidebarCollapsedItem = (props) => {
2922
3015
  props.onItemClick?.(item);
2923
3016
  item.onClick?.();
2924
3017
  };
2925
- return /* @__PURE__ */ jsx(ActionButton_default, {
3018
+ return /* @__PURE__ */ jsx(ActionButton, {
2926
3019
  size: props.item.theme?.size ?? props.theme.button?.size ?? (level === 0 ? "sm" : "xs"),
2927
3020
  variant: "subtle",
2928
3021
  variantActive: "default",
@@ -2932,7 +3025,7 @@ const SidebarCollapsedItem = (props) => {
2932
3025
  },
2933
3026
  radius: props.item.theme?.radius ?? props.theme.button?.radius ?? "md",
2934
3027
  onClick: handleItemClick,
2935
- icon: renderIcon(item.icon) ?? /* @__PURE__ */ jsx(IconSquareRounded, {}),
3028
+ icon: renderIcon(item.icon, ui.sizes.icon.sm) ?? /* @__PURE__ */ jsx(IconSquareRounded, { size: ui.sizes.icon.sm }),
2936
3029
  href: props.item.href,
2937
3030
  target: props.item.target,
2938
3031
  ...props.item.actionProps
@@ -2940,17 +3033,12 @@ const SidebarCollapsedItem = (props) => {
2940
3033
  };
2941
3034
 
2942
3035
  //#endregion
2943
- //#region ../../src/core/components/layout/AdminShell.tsx
2944
- const AdminShell = (props) => {
3036
+ //#region ../../src/core/components/layout/DashboardShell.tsx
3037
+ const DashboardShell = (props) => {
2945
3038
  const router = useRouter();
2946
3039
  const [sidebar, setSidebar] = useStore(alephaSidebarAtom);
2947
- const { opened, collapsed } = sidebar;
2948
- useEffect(() => {
2949
- if (props.sidebarProps?.collapsed !== void 0) setSidebar({
2950
- ...sidebar,
2951
- collapsed: props.sidebarProps.collapsed
2952
- });
2953
- }, []);
3040
+ const { opened } = sidebar;
3041
+ const collapsed = props.sidebarProps?.collapsed !== void 0 ? props.sidebarProps.collapsed : sidebar.collapsed;
2954
3042
  const [isResizing, setIsResizing] = useState(false);
2955
3043
  const [isHovering, setIsHovering] = useState(false);
2956
3044
  const [collapseEffect, setCollapseEffect] = useState({
@@ -3045,11 +3133,16 @@ const AdminShell = (props) => {
3045
3133
  collapseEffect.offset
3046
3134
  ]);
3047
3135
  const hoverTimeoutRef = useRef(null);
3136
+ const expandOnHover = props.sidebarProps?.expandOnHover !== false;
3048
3137
  const handleNavbarMouseEnter = useCallback(() => {
3049
- if (collapsed) hoverTimeoutRef.current = setTimeout(() => {
3138
+ if (collapsed && expandOnHover) hoverTimeoutRef.current = setTimeout(() => {
3050
3139
  setIsHovering(true);
3051
3140
  }, hoverDelay);
3052
- }, [collapsed, hoverDelay]);
3141
+ }, [
3142
+ collapsed,
3143
+ expandOnHover,
3144
+ hoverDelay
3145
+ ]);
3053
3146
  const handleNavbarMouseLeave = useCallback(() => {
3054
3147
  if (hoverTimeoutRef.current) {
3055
3148
  clearTimeout(hoverTimeoutRef.current);
@@ -3091,9 +3184,11 @@ const AdminShell = (props) => {
3091
3184
  const appBarProps = { ...props.appBarProps };
3092
3185
  appBarProps.container ??= props.container;
3093
3186
  const hasSidebar = showSidebar && props.sidebarProps !== void 0;
3094
- const hasAppBar = hasSidebar || props.appBarProps || props.header;
3095
- const headerHeight = hasAppBar ? 60 : 0;
3096
- const footerHeight = props.footer ? 24 : 0;
3187
+ const hasAppBar = props.appBarProps || props.header;
3188
+ const hHeight = props.headerHeight ?? 60;
3189
+ const fHeight = props.footerHeight ?? 24;
3190
+ const headerHeight = hasAppBar ? hHeight : 0;
3191
+ const footerHeight = props.footer ? fHeight : 0;
3097
3192
  const expandedWidth = Math.max(sidebar.width, collapsedWidth);
3098
3193
  const isExpandedByHover = collapsed && isHovering;
3099
3194
  const effectiveCollapsed = collapsed && !isHovering;
@@ -3102,31 +3197,28 @@ const AdminShell = (props) => {
3102
3197
  props.sidebarProps?.onItemClick?.(item);
3103
3198
  }, [isExpandedByHover, props.sidebarProps?.onItemClick]);
3104
3199
  const hoverWidth = Math.max(defaultWidth, collapsedWidth);
3105
- const sidebarWidth = hasSidebar ? effectiveCollapsed || isExpandedByHover ? collapsedWidth : expandedWidth : 0;
3106
3200
  const canResize = props.sidebarResizable && !collapsed;
3107
3201
  return /* @__PURE__ */ jsxs(AppShell, {
3202
+ layout: props.layout,
3108
3203
  w: "100%",
3109
3204
  flex: 1,
3110
- padding: "md",
3111
- header: hasAppBar ? { height: 60 } : void 0,
3205
+ header: hasAppBar ? { height: hHeight } : void 0,
3112
3206
  navbar: hasSidebar ? {
3113
3207
  width: effectiveCollapsed || isExpandedByHover ? { base: collapsedWidth } : { base: expandedWidth },
3114
3208
  breakpoint: "sm",
3115
3209
  collapsed: { mobile: !opened }
3116
3210
  } : void 0,
3117
- footer: props.footer ? { height: 24 } : void 0,
3211
+ footer: props.footer ? { height: fHeight } : void 0,
3118
3212
  ...props.appShellProps,
3119
3213
  children: [
3120
- /* @__PURE__ */ jsx(AppShell.Header, {
3121
- bg: ui.colors.surface,
3214
+ hasAppBar && /* @__PURE__ */ jsx(AppShell.Header, {
3122
3215
  ...props.appShellHeaderProps,
3123
- children: props.header ?? /* @__PURE__ */ jsx(AppBar_default, {
3216
+ children: props.header ?? /* @__PURE__ */ jsx(AppBar, {
3124
3217
  items: defaultAppBarItems,
3125
3218
  ...appBarProps
3126
3219
  })
3127
3220
  }),
3128
3221
  hasSidebar && /* @__PURE__ */ jsxs(AppShell.Navbar, {
3129
- bg: ui.colors.surface,
3130
3222
  className: "alepha-sidebar-navbar",
3131
3223
  "data-resizing": isResizing,
3132
3224
  "data-hover-expanded": isExpandedByHover,
@@ -3142,31 +3234,37 @@ const AdminShell = (props) => {
3142
3234
  }
3143
3235
  },
3144
3236
  ...props.appShellNavbarProps,
3145
- children: [/* @__PURE__ */ jsx(Sidebar, {
3146
- ...props.sidebarProps ?? {},
3147
- collapsed: effectiveCollapsed,
3148
- onItemClick: handleSidebarItemClick
3149
- }), (canResize || isExpandedByHover) && /* @__PURE__ */ jsx(Flex$1, {
3150
- pos: "absolute",
3151
- right: -6,
3152
- top: 0,
3153
- bottom: 0,
3154
- w: 12,
3155
- style: {
3156
- cursor: "col-resize",
3157
- userSelect: "none"
3158
- },
3159
- onMouseDown: handleResizeStart
3160
- })]
3237
+ children: [
3238
+ props.navbarHeader ? /* @__PURE__ */ jsx(Flex$1, {
3239
+ style: { borderBottom: "1px solid var(--mantine-color-default-border)" },
3240
+ h: headerHeight,
3241
+ children: props.navbarHeader
3242
+ }) : null,
3243
+ /* @__PURE__ */ jsx(Sidebar, {
3244
+ ...props.sidebarProps ?? {},
3245
+ collapsed: effectiveCollapsed,
3246
+ onItemClick: handleSidebarItemClick
3247
+ }),
3248
+ props.navbarFooter ? /* @__PURE__ */ jsx(Flex$1, {
3249
+ style: { borderTop: "1px solid var(--mantine-color-default-border)" },
3250
+ h: footerHeight,
3251
+ children: props.navbarFooter
3252
+ }) : null,
3253
+ (canResize || isExpandedByHover) && /* @__PURE__ */ jsx(Flex$1, {
3254
+ pos: "absolute",
3255
+ right: -6,
3256
+ top: 0,
3257
+ bottom: 0,
3258
+ w: 12,
3259
+ style: {
3260
+ cursor: "col-resize",
3261
+ userSelect: "none"
3262
+ },
3263
+ onMouseDown: handleResizeStart
3264
+ })
3265
+ ]
3161
3266
  }),
3162
3267
  /* @__PURE__ */ jsx(AppShell.Main, {
3163
- pl: sidebarWidth,
3164
- pt: headerHeight,
3165
- pb: footerHeight,
3166
- pr: 0,
3167
- display: "flex",
3168
- flex: 1,
3169
- style: { flexDirection: "column" },
3170
3268
  className: "alepha-sidebar-main",
3171
3269
  "data-resizing": isResizing,
3172
3270
  ...props.appShellMainProps,
@@ -3180,14 +3278,12 @@ const AdminShell = (props) => {
3180
3278
  }) : props.children ?? /* @__PURE__ */ jsx(NestedView, {})
3181
3279
  }),
3182
3280
  props.footer && /* @__PURE__ */ jsx(AppShell.Footer, {
3183
- bg: ui.colors.surface,
3184
3281
  ...props.appShellFooterProps,
3185
3282
  children: props.footer
3186
3283
  })
3187
3284
  ]
3188
3285
  });
3189
3286
  };
3190
- var AdminShell_default = AdminShell;
3191
3287
 
3192
3288
  //#endregion
3193
3289
  //#region ../../src/core/components/table/DataTableFilters.tsx
@@ -3207,16 +3303,23 @@ const DataTableFilters = ({ schema, form, typeFormProps, filterVisibility }) =>
3207
3303
  p: "xs",
3208
3304
  bg: ui.colors.surface,
3209
3305
  style: { borderBottom: "1px solid var(--alepha-border)" },
3210
- children: /* @__PURE__ */ jsx(TypeForm_default, {
3306
+ children: /* @__PURE__ */ jsx(TypeForm, {
3307
+ size: "xs",
3211
3308
  ...typeFormProps,
3212
3309
  skipSubmitButton: true,
3213
3310
  fill: true,
3214
3311
  form,
3215
- schema: visibleSchema
3312
+ schema: visibleSchema,
3313
+ columns: {
3314
+ base: 1,
3315
+ sm: 2,
3316
+ md: 3,
3317
+ lg: 4,
3318
+ xl: 6
3319
+ }
3216
3320
  })
3217
3321
  });
3218
3322
  };
3219
- var DataTableFilters_default = DataTableFilters;
3220
3323
 
3221
3324
  //#endregion
3222
3325
  //#region ../../src/core/components/table/DataTablePagination.tsx
@@ -3264,7 +3367,10 @@ const DataTablePagination = ({ page, size, totalPages, onPageChange, onSizeChang
3264
3367
  }) })]
3265
3368
  });
3266
3369
  };
3267
- var DataTablePagination_default = DataTablePagination;
3370
+
3371
+ //#endregion
3372
+ //#region ../../src/core/components/table/types.ts
3373
+ const DEFAULT_MAX_VISIBLE_COLUMNS = 8;
3268
3374
 
3269
3375
  //#endregion
3270
3376
  //#region ../../src/core/components/table/ColumnPicker.tsx
@@ -3277,11 +3383,16 @@ const ColumnPicker = ({ columns, visibility, onVisibilityChange }) => {
3277
3383
  [key]: true
3278
3384
  }), {}));
3279
3385
  };
3280
- const handleHideAll = () => {
3281
- onVisibilityChange(columnEntries.reduce((acc, [key]) => ({
3282
- ...acc,
3283
- [key]: false
3284
- }), {}));
3386
+ const handleDefault = () => {
3387
+ let count = 0;
3388
+ onVisibilityChange(columnEntries.reduce((acc, [key, col]) => {
3389
+ if (col.defaultHidden) acc[key] = false;
3390
+ else if (count < DEFAULT_MAX_VISIBLE_COLUMNS) {
3391
+ acc[key] = true;
3392
+ count++;
3393
+ } else acc[key] = false;
3394
+ return acc;
3395
+ }, {}));
3285
3396
  };
3286
3397
  const handleToggle = (key, checked) => {
3287
3398
  onVisibilityChange({
@@ -3303,20 +3414,22 @@ const ColumnPicker = ({ columns, visibility, onVisibilityChange }) => {
3303
3414
  duration: 200,
3304
3415
  timingFunction: "ease"
3305
3416
  },
3306
- children: [/* @__PURE__ */ jsx(Popover.Target, { children: /* @__PURE__ */ jsx(ActionButton_default, {
3417
+ children: [/* @__PURE__ */ jsx(Popover.Target, { children: /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(ActionButton, {
3307
3418
  variant: "subtle",
3308
- icon: IconColumns
3309
- }) }), /* @__PURE__ */ jsx(Popover.Dropdown, {
3419
+ icon: IconColumns,
3420
+ onClick: () => setOpened((o) => !o)
3421
+ }) }) }), /* @__PURE__ */ jsx(Popover.Dropdown, {
3310
3422
  bg: "transparent",
3311
3423
  p: "xs",
3312
3424
  bd: `1px solid ${ui.colors.border}`,
3313
3425
  style: { backdropFilter: "blur(20px)" },
3314
- children: /* @__PURE__ */ jsxs(Stack, {
3426
+ children: /* @__PURE__ */ jsxs(Flex$1, {
3427
+ direction: "column",
3315
3428
  gap: "xs",
3316
3429
  bg: ui.colors.surface,
3317
3430
  p: "sm",
3318
3431
  bdrs: "sm",
3319
- children: [/* @__PURE__ */ jsxs(Group, {
3432
+ children: [/* @__PURE__ */ jsxs(Flex$1, {
3320
3433
  justify: "space-between",
3321
3434
  children: [/* @__PURE__ */ jsxs(Text$1, {
3322
3435
  size: "sm",
@@ -3328,26 +3441,27 @@ const ColumnPicker = ({ columns, visibility, onVisibilityChange }) => {
3328
3441
  columnEntries.length,
3329
3442
  ")"
3330
3443
  ]
3331
- }), /* @__PURE__ */ jsxs(Group, {
3444
+ }), /* @__PURE__ */ jsxs(Flex$1, {
3332
3445
  gap: 4,
3333
- children: [/* @__PURE__ */ jsx(Button, {
3446
+ children: [/* @__PURE__ */ jsx(ActionButton, {
3334
3447
  size: "compact-xs",
3335
3448
  variant: "subtle",
3336
3449
  onClick: handleShowAll,
3337
3450
  children: "All"
3338
- }), /* @__PURE__ */ jsx(Button, {
3451
+ }), /* @__PURE__ */ jsx(ActionButton, {
3339
3452
  size: "compact-xs",
3340
3453
  variant: "subtle",
3341
- onClick: handleHideAll,
3342
- children: "None"
3454
+ onClick: handleDefault,
3455
+ children: "Default"
3343
3456
  })]
3344
3457
  })]
3345
3458
  }), /* @__PURE__ */ jsx(ScrollArea.Autosize, {
3346
3459
  mah: 300,
3347
- children: /* @__PURE__ */ jsx(Stack, {
3460
+ children: /* @__PURE__ */ jsx(Flex$1, {
3461
+ direction: "column",
3348
3462
  gap: 4,
3349
3463
  children: columnEntries.map(([key, col]) => /* @__PURE__ */ jsx(Checkbox, {
3350
- label: col.label,
3464
+ label: col.label || key,
3351
3465
  checked: visibility[key] !== false,
3352
3466
  onChange: (e) => handleToggle(key, e.currentTarget.checked),
3353
3467
  size: "sm"
@@ -3358,7 +3472,6 @@ const ColumnPicker = ({ columns, visibility, onVisibilityChange }) => {
3358
3472
  })]
3359
3473
  });
3360
3474
  };
3361
- var ColumnPicker_default = ColumnPicker;
3362
3475
 
3363
3476
  //#endregion
3364
3477
  //#region ../../src/core/components/table/FilterPicker.tsx
@@ -3388,7 +3501,7 @@ const FilterPicker = ({ schema, visibility, onVisibilityChange }) => {
3388
3501
  [key]: checked
3389
3502
  });
3390
3503
  };
3391
- const visibleCount = filterKeys.filter((key) => visibility[key] !== false).length;
3504
+ const visibleCount = filterKeys.filter((key) => visibility[key]).length;
3392
3505
  return /* @__PURE__ */ jsxs(Popover, {
3393
3506
  width: 280,
3394
3507
  position: "bottom-start",
@@ -3402,20 +3515,22 @@ const FilterPicker = ({ schema, visibility, onVisibilityChange }) => {
3402
3515
  duration: 200,
3403
3516
  timingFunction: "ease"
3404
3517
  },
3405
- children: [/* @__PURE__ */ jsx(Popover.Target, { children: /* @__PURE__ */ jsx(ActionButton_default, {
3518
+ children: [/* @__PURE__ */ jsx(Popover.Target, { children: /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(ActionButton, {
3406
3519
  variant: "subtle",
3407
- icon: IconFilter
3408
- }) }), /* @__PURE__ */ jsx(Popover.Dropdown, {
3520
+ icon: IconFilter,
3521
+ onClick: () => setOpened((o) => !o)
3522
+ }) }) }), /* @__PURE__ */ jsx(Popover.Dropdown, {
3409
3523
  bg: "transparent",
3410
3524
  p: "xs",
3411
3525
  bd: `1px solid ${ui.colors.border}`,
3412
3526
  style: { backdropFilter: "blur(20px)" },
3413
- children: /* @__PURE__ */ jsxs(Stack, {
3527
+ children: /* @__PURE__ */ jsxs(Flex$1, {
3528
+ direction: "column",
3414
3529
  gap: "xs",
3415
3530
  bg: ui.colors.surface,
3416
3531
  p: "sm",
3417
3532
  bdrs: "sm",
3418
- children: [/* @__PURE__ */ jsxs(Group, {
3533
+ children: [/* @__PURE__ */ jsxs(Flex$1, {
3419
3534
  justify: "space-between",
3420
3535
  children: [/* @__PURE__ */ jsxs(Text$1, {
3421
3536
  size: "sm",
@@ -3427,14 +3542,14 @@ const FilterPicker = ({ schema, visibility, onVisibilityChange }) => {
3427
3542
  filterKeys.length,
3428
3543
  ")"
3429
3544
  ]
3430
- }), /* @__PURE__ */ jsxs(Group, {
3545
+ }), /* @__PURE__ */ jsxs(Flex$1, {
3431
3546
  gap: 4,
3432
- children: [/* @__PURE__ */ jsx(Button, {
3547
+ children: [/* @__PURE__ */ jsx(ActionButton, {
3433
3548
  size: "compact-xs",
3434
3549
  variant: "subtle",
3435
3550
  onClick: handleShowAll,
3436
3551
  children: "All"
3437
- }), /* @__PURE__ */ jsx(Button, {
3552
+ }), /* @__PURE__ */ jsx(ActionButton, {
3438
3553
  size: "compact-xs",
3439
3554
  variant: "subtle",
3440
3555
  onClick: handleHideAll,
@@ -3443,11 +3558,12 @@ const FilterPicker = ({ schema, visibility, onVisibilityChange }) => {
3443
3558
  })]
3444
3559
  }), /* @__PURE__ */ jsx(ScrollArea.Autosize, {
3445
3560
  mah: 300,
3446
- children: /* @__PURE__ */ jsx(Stack, {
3561
+ children: /* @__PURE__ */ jsx(Flex$1, {
3562
+ direction: "column",
3447
3563
  gap: 4,
3448
3564
  children: filterKeys.map((key) => /* @__PURE__ */ jsx(Checkbox, {
3449
3565
  label: getFieldLabel(schema, key),
3450
- checked: visibility[key] !== false,
3566
+ checked: visibility[key] === true,
3451
3567
  onChange: (e) => handleToggle(key, e.currentTarget.checked),
3452
3568
  size: "sm"
3453
3569
  }, key))
@@ -3457,12 +3573,53 @@ const FilterPicker = ({ schema, visibility, onVisibilityChange }) => {
3457
3573
  })]
3458
3574
  });
3459
3575
  };
3460
- var FilterPicker_default = FilterPicker;
3461
3576
 
3462
3577
  //#endregion
3463
3578
  //#region ../../src/core/components/table/DataTableToolbar.tsx
3464
- const DataTableToolbar = ({ columns, filters, columnVisibility, filterVisibility, onColumnVisibilityChange, onFilterVisibilityChange, actions, onRefresh, selectedItems = [], checkboxActions, onClearSelection }) => {
3579
+ const escapeCsvField = (value) => {
3580
+ if (value.includes(",") || value.includes("\"") || value.includes("\n")) return `"${value.replace(/"/g, "\"\"")}"`;
3581
+ return value;
3582
+ };
3583
+ const extractText = (node) => {
3584
+ if (node == null || typeof node === "boolean") return "";
3585
+ if (typeof node === "string" || typeof node === "number") return String(node);
3586
+ if (Array.isArray(node)) return node.map(extractText).join("");
3587
+ if (typeof node === "object" && "props" in node) return extractText(node.props.children);
3588
+ return "";
3589
+ };
3590
+ const DataTableToolbar = ({ columns, filters, columnVisibility, filterVisibility, onColumnVisibilityChange, onFilterVisibilityChange, actions, onRefresh, items, withExport, selectedItems = [], checkboxActions, onClearSelection }) => {
3465
3591
  const hasSelection = selectedItems.length > 0;
3592
+ const exportableColumns = useCallback(() => {
3593
+ return Object.entries(columns).filter(([key, col]) => !col.actions && columnVisibility[key] !== false);
3594
+ }, [columns, columnVisibility]);
3595
+ const buildRows = useCallback(() => {
3596
+ const cols = exportableColumns();
3597
+ return items.map((item) => cols.map(([_key, col]) => {
3598
+ if (!col.value) return "";
3599
+ return extractText(col.value(item, {}));
3600
+ }));
3601
+ }, [items, exportableColumns]);
3602
+ const buildCsv = useCallback(() => {
3603
+ const header = exportableColumns().map(([_key, col]) => escapeCsvField(col.label));
3604
+ const rows = buildRows().map((row) => row.map(escapeCsvField));
3605
+ return [header.join(","), ...rows.map((r) => r.join(","))].join("\n");
3606
+ }, [exportableColumns, buildRows]);
3607
+ const exportCsv = useCallback(() => {
3608
+ const csv = buildCsv();
3609
+ const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
3610
+ const url = URL.createObjectURL(blob);
3611
+ const a = document.createElement("a");
3612
+ a.href = url;
3613
+ a.download = "export.csv";
3614
+ a.click();
3615
+ URL.revokeObjectURL(url);
3616
+ }, [buildCsv]);
3617
+ const exportClipboard = useCallback(async () => {
3618
+ const header = exportableColumns().map(([_key, col]) => col.label);
3619
+ const rows = buildRows();
3620
+ const text = [header.join(" "), ...rows.map((r) => r.join(" "))].join("\n");
3621
+ await navigator.clipboard.writeText(text);
3622
+ }, [exportableColumns, buildRows]);
3466
3623
  const handleCheckboxAction = async (action) => {
3467
3624
  const ctx = {
3468
3625
  selectedItems,
@@ -3478,17 +3635,30 @@ const DataTableToolbar = ({ columns, filters, columnVisibility, filterVisibility
3478
3635
  gap: 4,
3479
3636
  align: "center",
3480
3637
  children: [
3481
- filters && /* @__PURE__ */ jsx(FilterPicker_default, {
3638
+ filters && /* @__PURE__ */ jsx(FilterPicker, {
3482
3639
  schema: filters,
3483
3640
  visibility: filterVisibility,
3484
3641
  onVisibilityChange: onFilterVisibilityChange
3485
3642
  }),
3486
- /* @__PURE__ */ jsx(ColumnPicker_default, {
3643
+ /* @__PURE__ */ jsx(ColumnPicker, {
3487
3644
  columns,
3488
3645
  visibility: columnVisibility,
3489
3646
  onVisibilityChange: onColumnVisibilityChange
3490
3647
  }),
3491
- hasSelection && /* @__PURE__ */ jsxs(Fragment, { children: [
3648
+ withExport && /* @__PURE__ */ jsx(ActionButton, {
3649
+ variant: "subtle",
3650
+ icon: IconDownload,
3651
+ menu: { items: [{
3652
+ label: "Export as CSV",
3653
+ icon: /* @__PURE__ */ jsx(IconDownload, { size: 14 }),
3654
+ onClick: exportCsv
3655
+ }, {
3656
+ label: "Copy to clipboard",
3657
+ icon: /* @__PURE__ */ jsx(IconClipboard, { size: 14 }),
3658
+ onClick: exportClipboard
3659
+ }] }
3660
+ }),
3661
+ hasSelection && /* @__PURE__ */ jsxs(Fragment$1, { children: [
3492
3662
  /* @__PURE__ */ jsx(Divider, {
3493
3663
  orientation: "vertical",
3494
3664
  mx: "xs"
@@ -3498,14 +3668,14 @@ const DataTableToolbar = ({ columns, filters, columnVisibility, filterVisibility
3498
3668
  size: "lg",
3499
3669
  children: [selectedItems.length, " selected"]
3500
3670
  }),
3501
- /* @__PURE__ */ jsx(ActionButton_default, {
3671
+ /* @__PURE__ */ jsx(ActionButton, {
3502
3672
  variant: "subtle",
3503
3673
  size: "compact-sm",
3504
3674
  icon: IconX,
3505
3675
  onClick: onClearSelection,
3506
3676
  children: "Clear"
3507
3677
  }),
3508
- checkboxActions?.map((action, index) => /* @__PURE__ */ jsx(ActionButton_default, {
3678
+ checkboxActions?.map((action, index) => /* @__PURE__ */ jsx(ActionButton, {
3509
3679
  variant: "light",
3510
3680
  size: "compact-sm",
3511
3681
  intent: action.intent,
@@ -3519,23 +3689,84 @@ const DataTableToolbar = ({ columns, filters, columnVisibility, filterVisibility
3519
3689
  /* @__PURE__ */ jsx(Flex$1, { flex: 1 }),
3520
3690
  /* @__PURE__ */ jsxs(Flex$1, {
3521
3691
  gap: "xs",
3522
- children: [actions?.map((props, index) => !isValidElement(props) ? /* @__PURE__ */ jsx(ActionButton_default, {
3692
+ children: [actions?.map((props, index) => !isValidElement(props) ? /* @__PURE__ */ jsx(ActionButton, {
3523
3693
  ...props,
3524
3694
  children: props.label
3525
- }, index) : props), /* @__PURE__ */ jsx(ActionButton_default, {
3695
+ }, index) : props), /* @__PURE__ */ jsx(ActionButton, {
3696
+ variant: "subtle",
3526
3697
  icon: IconRefresh,
3527
- onClick: onRefresh,
3528
- children: "Refresh"
3698
+ onClick: onRefresh
3529
3699
  })]
3530
3700
  })
3531
3701
  ]
3532
3702
  });
3533
3703
  };
3534
- var DataTableToolbar_default = DataTableToolbar;
3704
+
3705
+ //#endregion
3706
+ //#region ../../src/core/components/table/useTableSelection.ts
3707
+ const useTableSelection = (items, getItemKey, enabled) => {
3708
+ const [selectedKeys, setSelectedKeys] = useState(/* @__PURE__ */ new Set());
3709
+ const selectedItems = useMemo(() => {
3710
+ if (!enabled) return [];
3711
+ return items.filter((item) => selectedKeys.has(getItemKey(item)));
3712
+ }, [
3713
+ items,
3714
+ selectedKeys,
3715
+ getItemKey,
3716
+ enabled
3717
+ ]);
3718
+ const allSelected = useMemo(() => {
3719
+ if (items.length === 0) return false;
3720
+ return items.every((item) => selectedKeys.has(getItemKey(item)));
3721
+ }, [
3722
+ items,
3723
+ selectedKeys,
3724
+ getItemKey
3725
+ ]);
3726
+ return {
3727
+ selectedItems,
3728
+ allSelected,
3729
+ someSelected: useMemo(() => {
3730
+ if (items.length === 0) return false;
3731
+ const count = items.filter((item) => selectedKeys.has(getItemKey(item))).length;
3732
+ return count > 0 && count < items.length;
3733
+ }, [
3734
+ items,
3735
+ selectedKeys,
3736
+ getItemKey
3737
+ ]),
3738
+ toggleItem: useCallback((item) => {
3739
+ const key = getItemKey(item);
3740
+ setSelectedKeys((prev) => {
3741
+ const next = new Set(prev);
3742
+ if (next.has(key)) next.delete(key);
3743
+ else next.add(key);
3744
+ return next;
3745
+ });
3746
+ }, [getItemKey]),
3747
+ toggleAll: useCallback(() => {
3748
+ if (allSelected) setSelectedKeys((prev) => {
3749
+ const next = new Set(prev);
3750
+ for (const item of items) next.delete(getItemKey(item));
3751
+ return next;
3752
+ });
3753
+ else setSelectedKeys((prev) => {
3754
+ const next = new Set(prev);
3755
+ for (const item of items) next.add(getItemKey(item));
3756
+ return next;
3757
+ });
3758
+ }, [
3759
+ allSelected,
3760
+ items,
3761
+ getItemKey
3762
+ ]),
3763
+ clear: useCallback(() => setSelectedKeys(/* @__PURE__ */ new Set()), []),
3764
+ isSelected: useCallback((item) => selectedKeys.has(getItemKey(item)), [selectedKeys, getItemKey])
3765
+ };
3766
+ };
3535
3767
 
3536
3768
  //#endregion
3537
3769
  //#region ../../src/core/components/table/DataTable.tsx
3538
- const DEFAULT_VISIBLE_COLUMN_COUNT = 10;
3539
3770
  /**
3540
3771
  * Parse the sort string to get direction for a specific field.
3541
3772
  * Alepha convention: 'field' = ASC, '-field' = DESC
@@ -3560,6 +3791,15 @@ const toggleSort = (sortString, field) => {
3560
3791
  else if (current === "asc") parts.unshift(`-${field}`);
3561
3792
  return parts.length > 0 ? parts.join(",") : void 0;
3562
3793
  };
3794
+ const toAriaSort = (dir) => {
3795
+ if (dir === "asc") return "ascending";
3796
+ if (dir === "desc") return "descending";
3797
+ return "none";
3798
+ };
3799
+ const FIT_STYLE = {
3800
+ width: 1,
3801
+ whiteSpace: "nowrap"
3802
+ };
3563
3803
  const DataTable = (props) => {
3564
3804
  const [items, setItems] = useState(typeof props.items === "function" ? { content: [] } : props.items);
3565
3805
  const defaultSize = props.infinityScroll ? 100 : props.defaultSize || 10;
@@ -3567,31 +3807,27 @@ const DataTable = (props) => {
3567
3807
  const [size, setSize] = useState(String(defaultSize));
3568
3808
  const [currentPage, setCurrentPage] = useState(0);
3569
3809
  const alepha = useInject(Alepha);
3810
+ const sentinelRef = useRef(null);
3570
3811
  const [columnVisibility, setColumnVisibility] = useState(() => {
3571
- if (props.defaultColumnVisibility) return props.defaultColumnVisibility;
3572
- const columnKeys = Object.keys(props.columns);
3573
- const maxVisible = props.defaultVisibleColumnCount ?? DEFAULT_VISIBLE_COLUMN_COUNT;
3574
- return columnKeys.reduce((acc, key, index) => ({
3575
- ...acc,
3576
- [key]: index < maxVisible
3577
- }), {});
3812
+ const entries = Object.entries(props.columns);
3813
+ let visibleCount = 0;
3814
+ return entries.reduce((acc, [key, col]) => {
3815
+ if (col.defaultHidden) acc[key] = false;
3816
+ else if (visibleCount < DEFAULT_MAX_VISIBLE_COLUMNS) {
3817
+ acc[key] = true;
3818
+ visibleCount++;
3819
+ } else acc[key] = false;
3820
+ return acc;
3821
+ }, {});
3578
3822
  });
3579
3823
  const [filterVisibility, setFilterVisibility] = useState(() => {
3580
- if (props.defaultFilterVisibility) return props.defaultFilterVisibility;
3581
3824
  if (!props.filters?.properties) return {};
3825
+ const defaults = new Set(props.defaultFilters ?? []);
3582
3826
  return Object.keys(props.filters.properties).reduce((acc, key) => ({
3583
3827
  ...acc,
3584
- [key]: true
3828
+ [key]: defaults.has(key)
3585
3829
  }), {});
3586
3830
  });
3587
- const handleColumnVisibilityChange = (visibility) => {
3588
- setColumnVisibility(visibility);
3589
- props.onColumnVisibilityChange?.(visibility);
3590
- };
3591
- const handleFilterVisibilityChange = (visibility) => {
3592
- setFilterVisibility(visibility);
3593
- props.onFilterVisibilityChange?.(visibility);
3594
- };
3595
3831
  const visibleColumns = useMemo(() => {
3596
3832
  return Object.entries(props.columns).filter(([key]) => columnVisibility[key] !== false);
3597
3833
  }, [props.columns, columnVisibility]);
@@ -3602,64 +3838,38 @@ const DataTable = (props) => {
3602
3838
  form.input.sort.set(newSort);
3603
3839
  form.input.page.set(0);
3604
3840
  };
3605
- const [selectedKeys, setSelectedKeys] = useState(/* @__PURE__ */ new Set());
3606
3841
  const getItemKey = useCallback((item) => {
3607
3842
  if (props.getItemKey) return props.getItemKey(item);
3843
+ if ("id" in item) return String(item.id);
3608
3844
  return JSON.stringify(item);
3609
3845
  }, [props.getItemKey]);
3610
- const selectedItems = useMemo(() => {
3611
- if (!props.withCheckbox) return [];
3612
- return items.content.filter((item) => selectedKeys.has(getItemKey(item)));
3613
- }, [
3614
- items.content,
3615
- selectedKeys,
3616
- getItemKey,
3617
- props.withCheckbox
3618
- ]);
3619
- const allSelected = useMemo(() => {
3620
- if (items.content.length === 0) return false;
3621
- return items.content.every((item) => selectedKeys.has(getItemKey(item)));
3622
- }, [
3623
- items.content,
3624
- selectedKeys,
3625
- getItemKey
3626
- ]);
3627
- const someSelected = useMemo(() => {
3628
- if (items.content.length === 0) return false;
3629
- const selectedCount = items.content.filter((item) => selectedKeys.has(getItemKey(item))).length;
3630
- return selectedCount > 0 && selectedCount < items.content.length;
3631
- }, [
3632
- items.content,
3633
- selectedKeys,
3634
- getItemKey
3635
- ]);
3636
- const toggleItemSelection = useCallback((item) => {
3637
- const key = getItemKey(item);
3638
- setSelectedKeys((prev) => {
3846
+ const selection = useTableSelection(items.content, getItemKey, props.withCheckbox ?? false);
3847
+ const panelConfig = useMemo(() => {
3848
+ if (!props.panel) return null;
3849
+ if (typeof props.panel === "function") return {
3850
+ render: props.panel,
3851
+ can: void 0
3852
+ };
3853
+ return props.panel;
3854
+ }, [props.panel]);
3855
+ const [drawerItem, setDrawerItem] = useState(null);
3856
+ const drawerConfig = useMemo(() => {
3857
+ if (!props.drawer) return null;
3858
+ if (typeof props.drawer === "function") return {
3859
+ render: props.drawer,
3860
+ can: void 0,
3861
+ props: void 0
3862
+ };
3863
+ return props.drawer;
3864
+ }, [props.drawer]);
3865
+ const [expandedKeys, setExpandedKeys] = useState(/* @__PURE__ */ new Set());
3866
+ const toggleExpand = useCallback((key) => {
3867
+ setExpandedKeys((prev) => {
3639
3868
  const next = new Set(prev);
3640
3869
  if (next.has(key)) next.delete(key);
3641
3870
  else next.add(key);
3642
3871
  return next;
3643
3872
  });
3644
- }, [getItemKey]);
3645
- const toggleAllSelection = useCallback(() => {
3646
- if (allSelected) setSelectedKeys((prev) => {
3647
- const next = new Set(prev);
3648
- for (const item of items.content) next.delete(getItemKey(item));
3649
- return next;
3650
- });
3651
- else setSelectedKeys((prev) => {
3652
- const next = new Set(prev);
3653
- for (const item of items.content) next.add(getItemKey(item));
3654
- return next;
3655
- });
3656
- }, [
3657
- allSelected,
3658
- items.content,
3659
- getItemKey
3660
- ]);
3661
- const clearSelection = useCallback(() => {
3662
- setSelectedKeys(/* @__PURE__ */ new Set());
3663
3873
  }, []);
3664
3874
  const form = useForm({
3665
3875
  schema: t.object({
@@ -3681,7 +3891,7 @@ const DataTable = (props) => {
3681
3891
  },
3682
3892
  onReset: async () => {
3683
3893
  setPage(1);
3684
- setSize("10");
3894
+ setSize(String(defaultSize));
3685
3895
  await form.submit();
3686
3896
  },
3687
3897
  onChange: async (key, value) => {
@@ -3698,7 +3908,6 @@ const DataTable = (props) => {
3698
3908
  props.onFilterChange?.(key, value, form);
3699
3909
  }
3700
3910
  }, [items]);
3701
- useDebouncedCallback(() => form.submit(), { delay: 800 });
3702
3911
  const dt = useInject(DateTimeProvider);
3703
3912
  useEffect(() => {
3704
3913
  if (props.submitOnInit) form.submit();
@@ -3714,18 +3923,15 @@ const DataTable = (props) => {
3714
3923
  }, [props.items]);
3715
3924
  useEffect(() => {
3716
3925
  if (!props.infinityScroll || typeof props.items !== "function") return;
3717
- const handleScroll = () => {
3718
- if (form.submitting) return;
3719
- const scrollTop = window.scrollY;
3720
- const windowHeight = window.innerHeight;
3721
- const docHeight = document.documentElement.scrollHeight;
3722
- if (scrollTop + windowHeight >= docHeight - 300) {
3723
- const totalPages = items.page?.totalPages ?? 1;
3724
- if (currentPage + 1 < totalPages) form.input.page.set(currentPage + 1);
3725
- }
3726
- };
3727
- window.addEventListener("scroll", handleScroll);
3728
- return () => window.removeEventListener("scroll", handleScroll);
3926
+ const sentinel = sentinelRef.current;
3927
+ if (!sentinel) return;
3928
+ const observer = new IntersectionObserver((entries) => {
3929
+ if (!entries[0].isIntersecting || form.submitting) return;
3930
+ const totalPages = items.page?.totalPages ?? 1;
3931
+ if (currentPage + 1 < totalPages) form.input.page.set(currentPage + 1);
3932
+ }, { rootMargin: "300px" });
3933
+ observer.observe(sentinel);
3934
+ return () => observer.disconnect();
3729
3935
  }, [
3730
3936
  props.infinityScroll,
3731
3937
  form.submitting,
@@ -3733,63 +3939,134 @@ const DataTable = (props) => {
3733
3939
  currentPage,
3734
3940
  form
3735
3941
  ]);
3942
+ const totalColumns = visibleColumns.length + (panelConfig ? 1 : 0) + (props.withCheckbox ? 1 : 0);
3736
3943
  const checkboxHeader = props.withCheckbox ? /* @__PURE__ */ jsx(Table.Th, {
3737
3944
  style: { width: 40 },
3738
3945
  children: /* @__PURE__ */ jsx(Checkbox, {
3739
- checked: allSelected,
3740
- indeterminate: someSelected,
3741
- onChange: toggleAllSelection,
3946
+ checked: selection.allSelected,
3947
+ indeterminate: selection.someSelected,
3948
+ onChange: selection.toggleAll,
3742
3949
  "aria-label": "Select all"
3743
3950
  })
3744
3951
  }) : null;
3745
3952
  const head = visibleColumns.map(([key, col]) => {
3746
3953
  const sortField = col.sortKey || key;
3747
3954
  const sortDir = col.sortable ? getSortDirection(sortString, sortField) : null;
3748
- const headerContent = /* @__PURE__ */ jsxs(Flex$1, {
3749
- align: "center",
3750
- gap: 4,
3751
- children: [/* @__PURE__ */ jsx(Text$1, {
3752
- size: "xs",
3753
- children: col.label
3754
- }), col.sortable && /* @__PURE__ */ jsxs(Flex$1, {
3755
- c: "dimmed",
3756
- children: [
3757
- sortDir === "asc" && /* @__PURE__ */ jsx(IconArrowUp, { size: ui.sizes.icon.sm }),
3758
- sortDir === "desc" && /* @__PURE__ */ jsx(IconArrowDown, { size: ui.sizes.icon.sm }),
3759
- sortDir === null && /* @__PURE__ */ jsx(IconArrowsSort, { size: ui.sizes.icon.sm })
3760
- ]
3761
- })]
3762
- });
3763
3955
  return /* @__PURE__ */ jsx(Table.Th, {
3956
+ onClick: col.sortable ? () => handleSortClick(key, col.sortKey) : void 0,
3957
+ "aria-sort": col.sortable ? toAriaSort(sortDir) : void 0,
3764
3958
  style: {
3765
- ...col.fit ? {} : {},
3766
- ...col.sortable ? { cursor: "pointer" } : {}
3959
+ ...col.fit ? FIT_STYLE : {},
3960
+ ...col.sortable ? {
3961
+ cursor: "pointer",
3962
+ userSelect: "none"
3963
+ } : {}
3767
3964
  },
3768
- children: col.sortable ? /* @__PURE__ */ jsx(UnstyledButton, {
3769
- onClick: () => handleSortClick(key, col.sortKey),
3770
- children: headerContent
3771
- }) : headerContent
3965
+ children: /* @__PURE__ */ jsxs(Flex$1, {
3966
+ align: "center",
3967
+ gap: 4,
3968
+ children: [/* @__PURE__ */ jsx(Text$1, {
3969
+ size: "xs",
3970
+ children: col.label
3971
+ }), col.sortable && /* @__PURE__ */ jsxs(Flex$1, {
3972
+ c: "dimmed",
3973
+ children: [
3974
+ sortDir === "asc" && /* @__PURE__ */ jsx(IconArrowUp, { size: ui.sizes.icon.sm }),
3975
+ sortDir === "desc" && /* @__PURE__ */ jsx(IconArrowDown, { size: ui.sizes.icon.sm }),
3976
+ sortDir === null && /* @__PURE__ */ jsx(IconArrowsSort, { size: ui.sizes.icon.sm })
3977
+ ]
3978
+ })]
3979
+ })
3772
3980
  }, key);
3773
3981
  });
3774
- const rows = items.content.map((item, index) => {
3982
+ const rows = items.content.flatMap((item, index) => {
3775
3983
  const trProps = props.tableTrProps ? props.tableTrProps(item) : {};
3776
3984
  const itemKey = getItemKey(item);
3777
- const isSelected = selectedKeys.has(itemKey);
3778
- return /* @__PURE__ */ jsxs(Table.Tr, {
3985
+ const isSelected = selection.isSelected(item);
3986
+ const showPanel = panelConfig && (panelConfig.can ? panelConfig.can(item) : true);
3987
+ const isExpanded = expandedKeys.has(itemKey);
3988
+ const canOpenDrawer = drawerConfig && (drawerConfig.can ? drawerConfig.can(item) : true);
3989
+ const elements = [/* @__PURE__ */ jsxs(Table.Tr, {
3779
3990
  ...trProps,
3780
- children: [props.withCheckbox && /* @__PURE__ */ jsx(Table.Td, {
3781
- style: { width: 40 },
3782
- children: /* @__PURE__ */ jsx(Checkbox, {
3783
- checked: isSelected,
3784
- onChange: () => toggleItemSelection(item),
3785
- "aria-label": "Select row"
3991
+ style: {
3992
+ ...canOpenDrawer ? { cursor: "pointer" } : {},
3993
+ ...trProps.style ?? {}
3994
+ },
3995
+ onClick: (e) => {
3996
+ if (canOpenDrawer) setDrawerItem(item);
3997
+ trProps.onClick?.(e);
3998
+ },
3999
+ children: [
4000
+ panelConfig && /* @__PURE__ */ jsx(Table.Td, {
4001
+ style: {
4002
+ width: 36,
4003
+ textAlign: "center"
4004
+ },
4005
+ py: 2,
4006
+ px: 0,
4007
+ children: showPanel && /* @__PURE__ */ jsx(UnstyledButton, {
4008
+ onClick: (e) => {
4009
+ e.stopPropagation();
4010
+ toggleExpand(itemKey);
4011
+ },
4012
+ style: { display: "inline-flex" },
4013
+ children: /* @__PURE__ */ jsx(Flex$1, {
4014
+ c: "dimmed",
4015
+ align: "center",
4016
+ justify: "center",
4017
+ children: isExpanded ? /* @__PURE__ */ jsx(IconChevronDown, { size: ui.sizes.icon.sm }) : /* @__PURE__ */ jsx(IconChevronRight, { size: ui.sizes.icon.sm })
4018
+ })
4019
+ })
4020
+ }),
4021
+ props.withCheckbox && /* @__PURE__ */ jsx(Table.Td, {
4022
+ style: { width: 40 },
4023
+ onClick: (e) => e.stopPropagation(),
4024
+ children: /* @__PURE__ */ jsx(Checkbox, {
4025
+ checked: isSelected,
4026
+ onChange: () => selection.toggleItem(item),
4027
+ "aria-label": "Select row"
4028
+ })
4029
+ }),
4030
+ visibleColumns.map(([key, col]) => {
4031
+ const ctx = {
4032
+ index,
4033
+ form,
4034
+ alepha
4035
+ };
4036
+ if (col.actions) {
4037
+ const rowActions = col.actions(item, ctx).filter((a) => a.visible !== false);
4038
+ return /* @__PURE__ */ jsx(Table.Td, {
4039
+ py: 2,
4040
+ px: 4,
4041
+ style: col.fit ? FIT_STYLE : void 0,
4042
+ onClick: (e) => e.stopPropagation(),
4043
+ children: /* @__PURE__ */ jsx(Flex$1, {
4044
+ gap: 4,
4045
+ children: rowActions.map(({ visible: _, ...actionProps }, i) => /* @__PURE__ */ jsx(ActionButton, {
4046
+ variant: "subtle",
4047
+ size: "xs",
4048
+ preventDefault: true,
4049
+ h: 20,
4050
+ ...actionProps
4051
+ }, i))
4052
+ })
4053
+ }, key);
4054
+ }
4055
+ return /* @__PURE__ */ jsx(Table.Td, {
4056
+ py: 2,
4057
+ px: 4,
4058
+ style: col.fit ? FIT_STYLE : void 0,
4059
+ children: col.value?.(item, ctx)
4060
+ }, key);
3786
4061
  })
3787
- }), visibleColumns.map(([key, col]) => /* @__PURE__ */ jsx(Table.Td, { children: col.value(item, {
3788
- index,
3789
- form,
3790
- alepha
3791
- }) }, key))]
3792
- }, itemKey);
4062
+ ]
4063
+ }, itemKey)];
4064
+ if (panelConfig && showPanel && isExpanded) elements.push(/* @__PURE__ */ jsx(Table.Tr, { children: /* @__PURE__ */ jsx(Table.Td, {
4065
+ colSpan: totalColumns,
4066
+ p: 0,
4067
+ children: panelConfig.render(item)
4068
+ }) }, `${itemKey}-panel`));
4069
+ return elements;
3793
4070
  });
3794
4071
  const filterSchema = useMemo(() => {
3795
4072
  if (!props.filters) return null;
@@ -3802,25 +4079,25 @@ const DataTable = (props) => {
3802
4079
  return /* @__PURE__ */ jsxs(Flex$1, {
3803
4080
  flex: 1,
3804
4081
  p: 0,
3805
- bg: "var(--alepha-elevated)",
3806
4082
  bdrs: "sm",
3807
- bd: "1px solid var(--alepha-border)",
3808
4083
  direction: "column",
3809
4084
  children: [
3810
- /* @__PURE__ */ jsx(DataTableToolbar_default, {
4085
+ /* @__PURE__ */ jsx(DataTableToolbar, {
3811
4086
  columns: props.columns,
3812
4087
  filters: props.filters,
3813
4088
  columnVisibility,
3814
4089
  filterVisibility,
3815
- onColumnVisibilityChange: handleColumnVisibilityChange,
3816
- onFilterVisibilityChange: handleFilterVisibilityChange,
4090
+ onColumnVisibilityChange: setColumnVisibility,
4091
+ onFilterVisibilityChange: setFilterVisibility,
3817
4092
  actions: props.actions,
3818
4093
  onRefresh: () => form.submit(),
3819
- selectedItems,
4094
+ items: items.content,
4095
+ withExport: props.withExport,
4096
+ selectedItems: selection.selectedItems,
3820
4097
  checkboxActions: props.checkboxActions,
3821
- onClearSelection: clearSelection
4098
+ onClearSelection: selection.clear
3822
4099
  }),
3823
- filterSchema && props.filters && /* @__PURE__ */ jsx(DataTableFilters_default, {
4100
+ filterSchema && props.filters && /* @__PURE__ */ jsx(DataTableFilters, {
3824
4101
  schema: filterSchema,
3825
4102
  form,
3826
4103
  typeFormProps: props.typeFormProps,
@@ -3829,13 +4106,42 @@ const DataTable = (props) => {
3829
4106
  /* @__PURE__ */ jsx(Flex$1, {
3830
4107
  className: "overflow-auto",
3831
4108
  children: /* @__PURE__ */ jsxs(Table, {
4109
+ "aria-label": "Data table",
3832
4110
  withColumnBorders: true,
3833
4111
  withRowBorders: true,
3834
4112
  ...props.tableProps,
3835
- children: [/* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [checkboxHeader, head] }) }), /* @__PURE__ */ jsx(Table.Tbody, { children: rows })]
4113
+ children: [/* @__PURE__ */ jsx(Table.Thead, {
4114
+ style: {
4115
+ position: "sticky",
4116
+ top: 0,
4117
+ zIndex: 1,
4118
+ backgroundColor: "var(--mantine-color-body)"
4119
+ },
4120
+ children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
4121
+ panelConfig && /* @__PURE__ */ jsx(Table.Th, { style: { width: 36 } }),
4122
+ checkboxHeader,
4123
+ head
4124
+ ] })
4125
+ }), /* @__PURE__ */ jsxs(Table.Tbody, {
4126
+ style: {
4127
+ opacity: form.submitting ? .5 : 1,
4128
+ transition: "opacity 150ms ease"
4129
+ },
4130
+ children: [rows, items.content.length === 0 && /* @__PURE__ */ jsx(Table.Tr, { children: /* @__PURE__ */ jsx(Table.Td, {
4131
+ colSpan: totalColumns || 1,
4132
+ py: "xl",
4133
+ style: { textAlign: "center" },
4134
+ children: /* @__PURE__ */ jsx(Text$1, {
4135
+ c: "dimmed",
4136
+ size: "sm",
4137
+ children: form.submitting ? "Loading…" : "No results"
4138
+ })
4139
+ }) })]
4140
+ })]
3836
4141
  })
3837
4142
  }),
3838
- !props.infinityScroll && /* @__PURE__ */ jsx(DataTablePagination_default, {
4143
+ props.infinityScroll && /* @__PURE__ */ jsx("div", { ref: sentinelRef }),
4144
+ !props.infinityScroll && /* @__PURE__ */ jsx(DataTablePagination, {
3839
4145
  page,
3840
4146
  size,
3841
4147
  totalPages: items.page?.totalPages ?? 1,
@@ -3845,11 +4151,18 @@ const DataTable = (props) => {
3845
4151
  onSizeChange: (value) => {
3846
4152
  form.input.size.set(value);
3847
4153
  }
4154
+ }),
4155
+ drawerConfig && /* @__PURE__ */ jsx(Drawer, {
4156
+ opened: drawerItem !== null,
4157
+ onClose: () => setDrawerItem(null),
4158
+ position: "right",
4159
+ size: "xl",
4160
+ ...drawerConfig.props,
4161
+ children: drawerItem && drawerConfig.render(drawerItem)
3848
4162
  })
3849
4163
  ]
3850
4164
  });
3851
4165
  };
3852
- var DataTable_default = DataTable;
3853
4166
 
3854
4167
  //#endregion
3855
4168
  //#region ../../src/core/hooks/useDialog.ts
@@ -3883,7 +4196,7 @@ const useDialog = () => {
3883
4196
  * - AlertDialog, ConfirmDialog, PromptDialog
3884
4197
  * - Form controls: Control, ControlArray, ControlDate, ControlNumber, ControlObject, ControlSelect, ControlQueryBuilder
3885
4198
  * - TypeForm for automatic form generation from TypeBox schemas
3886
- * - AdminShell layout component
4199
+ * - DashboardShell layout component
3887
4200
  * - AppBar with configurable elements
3888
4201
  * - Sidebar navigation with sections and menu items
3889
4202
  * - Omnibar for command palette / search
@@ -3920,5 +4233,5 @@ const $ui = (options = {}) => {
3920
4233
  };
3921
4234
 
3922
4235
  //#endregion
3923
- export { $ui, ActionButton_default as ActionButton, AdminShell_default as AdminShell, AlephaMantineProvider_default as AlephaMantineProvider, AlephaUI, AlertDialog_default as AlertDialog, AppBar_default as AppBar, BurgerButton_default as BurgerButton, ClipboardButton_default as ClipboardButton, ConfirmDialog_default as ConfirmDialog, Control_default as Control, ControlArray_default as ControlArray, ControlDate_default as ControlDate, ControlNumber_default as ControlNumber, ControlObject_default as ControlObject, ControlQueryBuilder_default as ControlQueryBuilder, ControlSelect_default as ControlSelect, DarkModeButton_default as DarkModeButton, DataTable_default as DataTable, DialogService, Flex, LanguageButton_default as LanguageButton, OPERATOR_INFO, Omnibar_default as Omnibar, OmnibarButton_default as OmnibarButton, PromptDialog_default as PromptDialog, Sidebar, Text, ThemeButton_default as ThemeButton, ThemeProvider, ToastService, ToggleSidebarButton_default as ToggleSidebarButton, TypeForm_default as TypeForm, UiRouter, alephaSidebarAtom, alephaThemeAtom, alephaThemeListAtom, capitalize, defaultTheme, extractSchemaFields, getDefaultIcon, getOperatorsForField, midnightTheme, prettyName, ui, useDialog, useToast };
4236
+ export { $ui, ActionButton, DashboardShell as AdminShell, DashboardShell, AlephaMantineProvider, AlephaUI, AlertDialog, AppBar, Breadcrumb, BurgerButton, ClipboardButton, ConfirmDialog, Control, ControlArray, ControlDate, ControlNumber, ControlObject, ControlQueryBuilder, ControlSelect, DarkModeButton, DataTable, DialogService, Flex, LanguageButton, OPERATOR_INFO, Omnibar, OmnibarButton, PromptDialog, Sidebar, Text, ThemeButton, ThemeProvider, ToastService, ToggleSidebarButton, TypeForm, UiRouter, alephaSidebarAtom, alephaThemeAtom, alephaThemeListAtom, capitalize, defaultTheme, extractSchemaFields, getDefaultIcon, getOperatorsForField, midnightTheme, prettyName, toTitleCase, ui, useDialog, useToast };
3924
4237
  //# sourceMappingURL=index.js.map