@alepha/ui 0.17.2 → 0.18.1

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 (270) hide show
  1. package/dist/admin/{AdminApiKeys-CF_qOO3u.js → AdminApiKeys-C-6_Q-lH.js} +56 -192
  2. package/dist/admin/AdminApiKeys-C-6_Q-lH.js.map +1 -0
  3. package/dist/admin/{AdminAudits-BQno3hZG.js → AdminAudits-Bgbf04hO.js} +25 -61
  4. package/dist/admin/AdminAudits-Bgbf04hO.js.map +1 -0
  5. package/dist/admin/{AdminFiles-kvuUaASF.js → AdminFiles-B9a7G3cY.js} +6 -8
  6. package/dist/admin/AdminFiles-B9a7G3cY.js.map +1 -0
  7. package/dist/admin/{AdminJobDashboard-CrPxp0W1.js → AdminJobDashboard-DaTwf5OY.js} +55 -186
  8. package/dist/admin/AdminJobDashboard-DaTwf5OY.js.map +1 -0
  9. package/dist/admin/{AdminJobExecutions-D-b4Zt7W.js → AdminJobExecutions-B9cek5dl.js} +132 -168
  10. package/dist/admin/AdminJobExecutions-B9cek5dl.js.map +1 -0
  11. package/dist/admin/{AdminJobRegistry-CNX5cpDx.js → AdminJobRegistry-DFgV3oqx.js} +60 -83
  12. package/dist/admin/AdminJobRegistry-DFgV3oqx.js.map +1 -0
  13. package/dist/admin/AdminLayout-DHsvWxVB.js +70 -0
  14. package/dist/admin/AdminLayout-DHsvWxVB.js.map +1 -0
  15. package/dist/admin/{AdminParameters-DCGbpt2c.js → AdminParameters-DHw9ATgl.js} +53 -53
  16. package/dist/admin/AdminParameters-DHw9ATgl.js.map +1 -0
  17. package/dist/admin/{AdminSessions-DyhW6RZv.js → AdminSessions-BhGJPI3z.js} +11 -18
  18. package/dist/admin/AdminSessions-BhGJPI3z.js.map +1 -0
  19. package/dist/admin/{AdminUserLayout-CrBj4UuI.js → AdminUserLayout-BdC4Te8m.js} +112 -151
  20. package/dist/admin/AdminUserLayout-BdC4Te8m.js.map +1 -0
  21. package/dist/admin/AdminUserProfile-DAt23fqY.js +69 -0
  22. package/dist/admin/AdminUserProfile-DAt23fqY.js.map +1 -0
  23. package/dist/admin/AdminUserSessions-1uzcx02z.js +109 -0
  24. package/dist/admin/AdminUserSessions-1uzcx02z.js.map +1 -0
  25. package/dist/admin/AdminUsers-C85c3eiQ.js +121 -0
  26. package/dist/admin/AdminUsers-C85c3eiQ.js.map +1 -0
  27. package/dist/{auth/AuthLayout-CdJcrPs4.js → admin/AuthLayout-DFJvCvzw.js} +3 -3
  28. package/dist/{auth/AuthLayout-CdJcrPs4.js.map → admin/AuthLayout-DFJvCvzw.js.map} +1 -1
  29. package/dist/{auth/IconGoogle-Bm18QD2q.js → admin/IconGoogle-CSQLPYwX.js} +1 -1
  30. package/dist/{auth/IconGoogle-Bm18QD2q.js.map → admin/IconGoogle-CSQLPYwX.js.map} +1 -1
  31. package/dist/{demo/DemoLogin-DjJ9314c.js → admin/Login-BGheURrg.js} +15 -129
  32. package/dist/{auth/Login-BS_FYTy0.js.map → admin/Login-BGheURrg.js.map} +1 -1
  33. package/dist/{auth/Profile-CjDsW378.js → admin/Profile-B-c9pCPf.js} +5 -5
  34. package/dist/{auth/Profile-CjDsW378.js.map → admin/Profile-B-c9pCPf.js.map} +1 -1
  35. package/dist/{demo/DemoRegister-DzkJ5M83.js → admin/Register-Cs10l8vX.js} +20 -146
  36. package/dist/{auth/Register-C5eqzAaD.js.map → admin/Register-Cs10l8vX.js.map} +1 -1
  37. package/dist/{demo/DemoResetPassword-DWh4_BpQ.js → admin/ResetPassword-BwDdfkGH.js} +20 -82
  38. package/dist/{auth/ResetPassword-XifinVao.js.map → admin/ResetPassword-BwDdfkGH.js.map} +1 -1
  39. package/dist/{demo/DemoVerifyEmail-DbU_tCj8.js → admin/VerifyEmail-DfXHAiQl.js} +15 -32
  40. package/dist/{auth/VerifyEmail-DTgbeJOO.js.map → admin/VerifyEmail-DfXHAiQl.js.map} +1 -1
  41. package/dist/admin/auth-Dr0Cf8I7.js +319 -0
  42. package/dist/admin/auth-Dr0Cf8I7.js.map +1 -0
  43. package/dist/admin/core-2xoLiT0o.js +4031 -0
  44. package/dist/admin/core-2xoLiT0o.js.map +1 -0
  45. package/dist/admin/index.d.ts +739 -13
  46. package/dist/admin/index.d.ts.map +1 -1
  47. package/dist/admin/index.js +79 -111
  48. package/dist/admin/index.js.map +1 -1
  49. package/dist/admin/rolldown-runtime-CjeV3_4I.js +18 -0
  50. package/dist/auth/AuthLayout-CAE1pX9s.js +22 -0
  51. package/dist/auth/AuthLayout-CAE1pX9s.js.map +1 -0
  52. package/dist/auth/{Login-BS_FYTy0.js → Login-Denw_UGy.js} +8 -8
  53. package/dist/auth/Login-Denw_UGy.js.map +1 -0
  54. package/dist/auth/Profile-BMX_Ar_s.js +155 -0
  55. package/dist/auth/Profile-BMX_Ar_s.js.map +1 -0
  56. package/dist/auth/{Register-C5eqzAaD.js → Register-6hi_cpfF.js} +8 -8
  57. package/dist/auth/Register-6hi_cpfF.js.map +1 -0
  58. package/dist/auth/{ResetPassword-XifinVao.js → ResetPassword-CqfTk1FI.js} +6 -6
  59. package/dist/auth/ResetPassword-CqfTk1FI.js.map +1 -0
  60. package/dist/auth/{VerifyEmail-DTgbeJOO.js → VerifyEmail-nWiSTMjF.js} +5 -5
  61. package/dist/auth/VerifyEmail-nWiSTMjF.js.map +1 -0
  62. package/dist/auth/core-niW0sFLv.js +2264 -0
  63. package/dist/auth/core-niW0sFLv.js.map +1 -0
  64. package/dist/auth/index.d.ts +336 -8
  65. package/dist/auth/index.d.ts.map +1 -1
  66. package/dist/auth/index.js +18 -22
  67. package/dist/auth/index.js.map +1 -1
  68. package/dist/core/index.d.ts +1033 -843
  69. package/dist/core/index.d.ts.map +1 -1
  70. package/dist/core/index.js +1626 -1354
  71. package/dist/core/index.js.map +1 -1
  72. package/dist/demo/AuthLayout-jLa0aKsI.js +22 -0
  73. package/dist/demo/AuthLayout-jLa0aKsI.js.map +1 -0
  74. package/dist/demo/DemoButton-BmaWZVwf.js +178 -0
  75. package/dist/demo/DemoButton-BmaWZVwf.js.map +1 -0
  76. package/dist/demo/{DemoDataTable-lnBKWBf8.js → DemoDataTable-Z9xyV221.js} +18 -18
  77. package/dist/demo/DemoDataTable-Z9xyV221.js.map +1 -0
  78. package/dist/demo/DemoDialog-4ItHLf9t.js +101 -0
  79. package/dist/demo/DemoDialog-4ItHLf9t.js.map +1 -0
  80. package/dist/demo/DemoFlex-EtVq8QfX.js +105 -0
  81. package/dist/demo/DemoFlex-EtVq8QfX.js.map +1 -0
  82. package/dist/demo/DemoHeading-BS-vGfkI.js +18 -0
  83. package/dist/demo/DemoHeading-BS-vGfkI.js.map +1 -0
  84. package/dist/demo/{DemoHome-CUMZsYaH.js → DemoHome-Clbn8AmS.js} +9 -12
  85. package/dist/demo/DemoHome-Clbn8AmS.js.map +1 -0
  86. package/dist/demo/DemoJsonViewer-DkIX_ky2.js +109 -0
  87. package/dist/demo/DemoJsonViewer-DkIX_ky2.js.map +1 -0
  88. package/dist/demo/DemoLayout-C56xb5EE.js +73 -0
  89. package/dist/demo/DemoLayout-C56xb5EE.js.map +1 -0
  90. package/dist/demo/DemoLogin-BZwpicOS.js +128 -0
  91. package/dist/demo/DemoLogin-BZwpicOS.js.map +1 -0
  92. package/dist/demo/DemoRegister-C7_qc4MJ.js +140 -0
  93. package/dist/demo/DemoRegister-C7_qc4MJ.js.map +1 -0
  94. package/dist/demo/DemoResetPassword-BI1Ct4Dw.js +76 -0
  95. package/dist/demo/DemoResetPassword-BI1Ct4Dw.js.map +1 -0
  96. package/dist/demo/{DemoSidebar-C1csnGhX.js → DemoSidebar-CcBo4ltC.js} +6 -9
  97. package/dist/demo/DemoSidebar-CcBo4ltC.js.map +1 -0
  98. package/dist/demo/DemoText-CzXuUn3g.js +124 -0
  99. package/dist/demo/DemoText-CzXuUn3g.js.map +1 -0
  100. package/dist/demo/DemoToast-BgHDhWrX.js +95 -0
  101. package/dist/demo/DemoToast-BgHDhWrX.js.map +1 -0
  102. package/dist/demo/{DemoTypeForm-CWz6fJrJ.js → DemoTypeForm-DDzWoMSV.js} +4 -4
  103. package/dist/demo/{DemoTypeForm-CWz6fJrJ.js.map → DemoTypeForm-DDzWoMSV.js.map} +1 -1
  104. package/dist/demo/DemoVerifyEmail-C_Irdnov.js +30 -0
  105. package/dist/demo/DemoVerifyEmail-C_Irdnov.js.map +1 -0
  106. package/dist/demo/IconGoogle-CSQLPYwX.js +56 -0
  107. package/dist/demo/IconGoogle-CSQLPYwX.js.map +1 -0
  108. package/dist/demo/Login-hSOU3jZc.js +219 -0
  109. package/dist/demo/Login-hSOU3jZc.js.map +1 -0
  110. package/dist/demo/Profile-CWqti7FB.js +155 -0
  111. package/dist/demo/Profile-CWqti7FB.js.map +1 -0
  112. package/dist/demo/Register-a70LPgs2.js +375 -0
  113. package/dist/demo/Register-a70LPgs2.js.map +1 -0
  114. package/dist/demo/ResetPassword-DWN0lzr5.js +286 -0
  115. package/dist/demo/ResetPassword-DWN0lzr5.js.map +1 -0
  116. package/dist/demo/Showcase-Dq3MISpd.js +232 -0
  117. package/dist/demo/Showcase-Dq3MISpd.js.map +1 -0
  118. package/dist/demo/VerifyEmail-DZWL72K4.js +135 -0
  119. package/dist/demo/VerifyEmail-DZWL72K4.js.map +1 -0
  120. package/dist/demo/auth-d6n3xbug.js +257 -0
  121. package/dist/demo/auth-d6n3xbug.js.map +1 -0
  122. package/dist/demo/core-RCUw1Q-a.js +4217 -0
  123. package/dist/demo/core-RCUw1Q-a.js.map +1 -0
  124. package/dist/demo/index.d.ts +17 -6
  125. package/dist/demo/index.d.ts.map +1 -1
  126. package/dist/demo/index.js +92 -24
  127. package/dist/demo/index.js.map +1 -1
  128. package/dist/demo/rolldown-runtime-CjeV3_4I.js +18 -0
  129. package/package.json +16 -20
  130. package/src/admin/AdminRouter.ts +10 -39
  131. package/src/admin/components/AdminLayout.tsx +42 -10
  132. package/src/admin/components/audits/AdminAudits.tsx +10 -64
  133. package/src/admin/components/files/AdminFiles.tsx +2 -3
  134. package/src/admin/components/jobs/AdminJobDashboard.tsx +36 -142
  135. package/src/admin/components/jobs/AdminJobExecutions.tsx +117 -175
  136. package/src/admin/components/jobs/AdminJobRegistry.tsx +58 -73
  137. package/src/admin/components/keys/AdminApiKeys.tsx +21 -169
  138. package/src/admin/components/parameters/AdminParameters.tsx +4 -4
  139. package/src/admin/components/parameters/ParameterEmptyState.tsx +1 -2
  140. package/src/admin/components/parameters/ParameterHistory.tsx +3 -3
  141. package/src/admin/components/parameters/ParameterTree.tsx +2 -8
  142. package/src/admin/components/parameters/types.ts +3 -3
  143. package/src/admin/components/sessions/AdminSessions.tsx +8 -16
  144. package/src/admin/components/users/AdminUserLayout.tsx +113 -150
  145. package/src/admin/components/users/AdminUserProfile.tsx +50 -0
  146. package/src/admin/components/users/AdminUserSessions.tsx +106 -126
  147. package/src/admin/components/users/AdminUsers.tsx +46 -62
  148. package/src/admin/index.ts +0 -4
  149. package/src/auth/components/buttons/UserButton.tsx +1 -1
  150. package/src/auth/index.ts +0 -4
  151. package/src/core/UiRouter.ts +1 -1
  152. package/src/core/atoms/alephaSidebarAtom.ts +7 -31
  153. package/src/core/components/{layout/AlephaMantineProvider.tsx → AlephaMantineProvider.tsx} +3 -4
  154. package/src/core/components/Flex.tsx +63 -0
  155. package/src/core/components/Heading.tsx +19 -0
  156. package/src/core/components/Text.tsx +140 -0
  157. package/src/core/components/buttons/ActionButton.tsx +12 -1
  158. package/src/core/components/buttons/BurgerButton.tsx +3 -3
  159. package/src/core/components/buttons/LanguageButton.tsx +1 -1
  160. package/src/core/components/buttons/ToggleSidebarButton.tsx +1 -4
  161. package/src/core/components/data/DetailDrawer.tsx +144 -0
  162. package/src/core/components/data/DetailList.tsx +64 -0
  163. package/src/core/components/data/StatCards.tsx +50 -0
  164. package/src/core/components/layout/AppBar.tsx +11 -10
  165. package/src/core/components/layout/Breadcrumb.tsx +8 -8
  166. package/src/core/components/layout/Container.tsx +15 -0
  167. package/src/core/components/layout/DashboardShell.tsx +23 -238
  168. package/src/core/components/layout/Omnibar.tsx +1 -2
  169. package/src/core/components/layout/Sidebar.tsx +103 -71
  170. package/src/core/components/layout/index.ts +65 -0
  171. package/src/core/{components/form → form/components}/Control.tsx +32 -14
  172. package/src/core/{components/form → form/components}/ControlArray.tsx +2 -5
  173. package/src/core/{components/form → form/components}/ControlDate.tsx +1 -4
  174. package/src/core/{components/form → form/components}/ControlNumber.tsx +1 -4
  175. package/src/core/{components/form → form/components}/ControlObject.tsx +1 -4
  176. package/src/core/{components/form → form/components}/ControlQueryBuilder.tsx +7 -7
  177. package/src/core/{components/form → form/components}/ControlSelect.tsx +2 -4
  178. package/src/core/{components/form → form/components}/TypeForm.browser.spec.tsx +22 -64
  179. package/src/core/{components/form → form/components}/TypeForm.tsx +1 -3
  180. package/src/core/form/factories/dialogForm.tsx +31 -0
  181. package/src/core/form/index.ts +23 -0
  182. package/src/core/{utils → form/utils}/parseInput.ts +2 -4
  183. package/src/core/index.ts +43 -51
  184. package/src/core/interfaces/AlephaIntent.ts +6 -0
  185. package/src/core/interfaces/AlephaTheme.ts +0 -1
  186. package/src/core/json/factories/dialogJson.tsx +24 -0
  187. package/src/core/json/index.ts +2 -0
  188. package/src/core/primitives/$ui.ts +17 -0
  189. package/src/core/services/DialogService.tsx +1 -48
  190. package/src/core/styles.css +1 -8
  191. package/src/core/{components/table → table/components}/ColumnPicker.tsx +2 -3
  192. package/src/core/{components/table → table/components}/DataTable.tsx +8 -9
  193. package/src/core/{components/table → table/components}/DataTableFilters.tsx +6 -3
  194. package/src/core/{components/table → table/components}/DataTableToolbar.tsx +4 -5
  195. package/src/core/{components/table → table/components}/FilterPicker.tsx +2 -3
  196. package/src/core/table/index.ts +12 -0
  197. package/src/core/{components/table → table/interfaces}/types.ts +2 -2
  198. package/src/demo/DemoRouter.ts +87 -6
  199. package/src/demo/components/DemoHome.tsx +6 -10
  200. package/src/demo/components/DemoLayout.tsx +38 -8
  201. package/src/demo/components/auth/DemoLogin.tsx +1 -1
  202. package/src/demo/components/auth/DemoRegister.tsx +1 -1
  203. package/src/demo/components/auth/DemoResetPassword.tsx +1 -1
  204. package/src/demo/components/auth/DemoVerifyEmail.tsx +1 -1
  205. package/src/demo/components/core/DemoButton.tsx +160 -0
  206. package/src/demo/components/core/DemoFlex.tsx +101 -0
  207. package/src/demo/components/core/DemoHeading.tsx +13 -0
  208. package/src/demo/components/core/DemoText.tsx +110 -0
  209. package/src/demo/components/json/DemoJsonViewer.tsx +1 -1
  210. package/src/demo/components/layout/DemoDialog.tsx +103 -0
  211. package/src/demo/components/{core → layout}/DemoSidebar.tsx +0 -1
  212. package/src/demo/components/layout/DemoToast.tsx +96 -0
  213. package/src/demo/components/shared/MacWindow.tsx +149 -74
  214. package/src/demo/components/shared/Showcase.tsx +4 -8
  215. package/src/demo/index.ts +1 -4
  216. package/src/demo/primitives/$uiDemo.ts +10 -0
  217. package/dist/admin/AdminApiKeys-CF_qOO3u.js.map +0 -1
  218. package/dist/admin/AdminAudits-BQno3hZG.js.map +0 -1
  219. package/dist/admin/AdminFiles-kvuUaASF.js.map +0 -1
  220. package/dist/admin/AdminJobDashboard-CrPxp0W1.js.map +0 -1
  221. package/dist/admin/AdminJobExecutions-D-b4Zt7W.js.map +0 -1
  222. package/dist/admin/AdminJobRegistry-CNX5cpDx.js.map +0 -1
  223. package/dist/admin/AdminLayout-e-ZP5nWw.js +0 -37
  224. package/dist/admin/AdminLayout-e-ZP5nWw.js.map +0 -1
  225. package/dist/admin/AdminParameters-DCGbpt2c.js.map +0 -1
  226. package/dist/admin/AdminSessions-DyhW6RZv.js.map +0 -1
  227. package/dist/admin/AdminUserAudits-D1GcREEE.js +0 -177
  228. package/dist/admin/AdminUserAudits-D1GcREEE.js.map +0 -1
  229. package/dist/admin/AdminUserCreate-DR8LA0tv.js +0 -104
  230. package/dist/admin/AdminUserCreate-DR8LA0tv.js.map +0 -1
  231. package/dist/admin/AdminUserDetails-CDkZNHQD.js +0 -477
  232. package/dist/admin/AdminUserDetails-CDkZNHQD.js.map +0 -1
  233. package/dist/admin/AdminUserLayout-CrBj4UuI.js.map +0 -1
  234. package/dist/admin/AdminUserSessions-srgFHrqy.js +0 -129
  235. package/dist/admin/AdminUserSessions-srgFHrqy.js.map +0 -1
  236. package/dist/admin/AdminUserSettings-BFuxl-xT.js +0 -167
  237. package/dist/admin/AdminUserSettings-BFuxl-xT.js.map +0 -1
  238. package/dist/admin/AdminUsers-D1pDpiwK.js +0 -118
  239. package/dist/admin/AdminUsers-D1pDpiwK.js.map +0 -1
  240. package/dist/demo/DemoDataTable-lnBKWBf8.js.map +0 -1
  241. package/dist/demo/DemoHome-CUMZsYaH.js.map +0 -1
  242. package/dist/demo/DemoJsonViewer-_uokbGaW.js +0 -429
  243. package/dist/demo/DemoJsonViewer-_uokbGaW.js.map +0 -1
  244. package/dist/demo/DemoLayout-DHVoacE6.js +0 -46
  245. package/dist/demo/DemoLayout-DHVoacE6.js.map +0 -1
  246. package/dist/demo/DemoLogin-DjJ9314c.js.map +0 -1
  247. package/dist/demo/DemoRegister-DzkJ5M83.js.map +0 -1
  248. package/dist/demo/DemoResetPassword-DWh4_BpQ.js.map +0 -1
  249. package/dist/demo/DemoSidebar-C1csnGhX.js.map +0 -1
  250. package/dist/demo/DemoVerifyEmail-DbU_tCj8.js.map +0 -1
  251. package/dist/demo/Showcase-BzoXNlCn.js +0 -185
  252. package/dist/demo/Showcase-BzoXNlCn.js.map +0 -1
  253. package/dist/json/index.d.ts +0 -57
  254. package/dist/json/index.d.ts.map +0 -1
  255. package/dist/json/index.js +0 -325
  256. package/dist/json/index.js.map +0 -1
  257. package/src/admin/components/users/AdminUserAudits.tsx +0 -184
  258. package/src/admin/components/users/AdminUserCreate.tsx +0 -85
  259. package/src/admin/components/users/AdminUserDetails.tsx +0 -431
  260. package/src/admin/components/users/AdminUserSettings.tsx +0 -171
  261. package/src/core/components/data/ErrorViewer.tsx +0 -171
  262. package/src/json/extensions/DialogService.tsx +0 -31
  263. package/src/json/index.ts +0 -18
  264. package/src/json/styles.css +0 -1
  265. /package/dist/{demo → auth}/IconGoogle-Ch1m3Uzl.js +0 -0
  266. /package/dist/{demo → auth}/IconGoogle-Ch1m3Uzl.js.map +0 -0
  267. /package/src/{json → core/json}/components/JsonViewer.css +0 -0
  268. /package/src/{json → core/json}/components/JsonViewer.tsx +0 -0
  269. /package/src/core/{components/table → table/components}/DataTablePagination.tsx +0 -0
  270. /package/src/core/{components/table → table/components}/useTableSelection.ts +0 -0
@@ -0,0 +1,4031 @@
1
+ import { $atom, $context, $inject, $module, Alepha, AlephaError, TypeBoxError, t } from "alepha";
2
+ import { AlephaReactForm, FormValidationError, useForm, useFormState } from "alepha/react/form";
3
+ import { $head, AlephaReactHead } from "alepha/react/head";
4
+ import { AlephaReactI18n, useI18n } from "alepha/react/i18n";
5
+ import { $cookie } from "alepha/server/cookies";
6
+ import { ModalsProvider, modals } from "@mantine/modals";
7
+ import { ActionIcon, Anchor, AppShell, Autocomplete, Badge, Burger, Button, Card, Checkbox, ColorInput, ColorSchemeScript, Container, CopyButton, Divider, Drawer, Fieldset, FileInput, Flex, Grid, Image, Input, Kbd, Loader, MantineProvider, Menu, MultiSelect, NumberInput, Pagination, Paper, PasswordInput, Popover, ScrollArea, SegmentedControl, Select, Slider, Switch, Table, Tabs, TagsInput, Text, TextInput, Textarea, ThemeIcon, Tooltip, UnstyledButton, useMantineColorScheme, useMantineTheme } from "@mantine/core";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ import { Children, Fragment as Fragment$1, createElement, forwardRef, isValidElement, useCallback, useEffect, useMemo, useRef, useState } from "react";
10
+ import { Notifications, notifications } from "@mantine/notifications";
11
+ 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";
12
+ import { $page, Link, NestedView, useActive, useRouter, useRouterState } from "alepha/react/router";
13
+ import { NavigationProgress, nprogress } from "@mantine/nprogress";
14
+ import { ClientOnly, useAction, useAlepha, useEvents, useInject, useStore } from "alepha/react";
15
+ import { Spotlight, spotlight } from "@mantine/spotlight";
16
+ import { currentUserAtom } from "alepha/security";
17
+ import { useOs } from "@mantine/hooks";
18
+ import { DateInput, DateTimePicker, TimeInput } from "@mantine/dates";
19
+ import { parseQueryString } from "alepha/orm";
20
+ import { DateTimeProvider } from "alepha/datetime";
21
+
22
+ //#region ../../src/core/atoms/alephaSidebarAtom.ts
23
+ const alephaSidebarAtom = $atom({
24
+ name: "alepha.ui.sidebar",
25
+ schema: t.object({
26
+ closed: t.boolean(),
27
+ collapsed: t.boolean(),
28
+ expandedWidth: t.number(),
29
+ collapsedWidth: t.number()
30
+ }),
31
+ default: {
32
+ closed: true,
33
+ collapsed: false,
34
+ expandedWidth: 300,
35
+ collapsedWidth: 78
36
+ }
37
+ });
38
+
39
+ //#endregion
40
+ //#region ../../src/core/atoms/alephaThemeAtom.ts
41
+ const alephaThemeAtom = $atom({
42
+ name: "alepha.ui.theme",
43
+ schema: t.object({ index: t.integer() }),
44
+ default: { index: 0 }
45
+ });
46
+
47
+ //#endregion
48
+ //#region ../../src/core/atoms/themes/default.ts
49
+ const defaultTheme = {
50
+ name: "Default",
51
+ description: "Default Alepha Theme"
52
+ };
53
+
54
+ //#endregion
55
+ //#region ../../src/core/atoms/themes/midnight.ts
56
+ const midnightTheme = {
57
+ name: "Midnight",
58
+ description: "Clean, developer-focused design",
59
+ primaryColor: "pink",
60
+ primaryShade: {
61
+ light: 7,
62
+ dark: 8
63
+ },
64
+ fontFamily: "-apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Noto Sans\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"",
65
+ fontFamilyMonospace: "ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace",
66
+ headings: {
67
+ fontFamily: "-apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Noto Sans\", Helvetica, Arial, sans-serif",
68
+ fontWeight: "600",
69
+ textWrap: "wrap",
70
+ sizes: {
71
+ h1: {
72
+ fontSize: "2rem",
73
+ lineHeight: "1.25"
74
+ },
75
+ h2: {
76
+ fontSize: "1.5rem",
77
+ lineHeight: "1.3"
78
+ },
79
+ h3: {
80
+ fontSize: "1.25rem",
81
+ lineHeight: "1.4"
82
+ },
83
+ h4: {
84
+ fontSize: "1rem",
85
+ lineHeight: "1.5"
86
+ },
87
+ h5: {
88
+ fontSize: "0.875rem",
89
+ lineHeight: "1.5"
90
+ },
91
+ h6: {
92
+ fontSize: "0.75rem",
93
+ lineHeight: "1.5"
94
+ }
95
+ }
96
+ },
97
+ radius: {
98
+ xs: "3px",
99
+ sm: "6px",
100
+ md: "6px",
101
+ lg: "8px",
102
+ xl: "12px"
103
+ },
104
+ defaultRadius: "sm",
105
+ colors: {
106
+ dark: [
107
+ "#d0d7de",
108
+ "#8b949e",
109
+ "#6e7681",
110
+ "#484f58",
111
+ "#30363d",
112
+ "#21262d",
113
+ "#161b22",
114
+ "#151b23",
115
+ "#0d1117",
116
+ "#010409"
117
+ ],
118
+ gray: [
119
+ "#f6f8fa",
120
+ "#eaeef2",
121
+ "#d0d7de",
122
+ "#afb8c1",
123
+ "#8c959f",
124
+ "#6e7781",
125
+ "#57606a",
126
+ "#424a53",
127
+ "#32383f",
128
+ "#24292f"
129
+ ],
130
+ blue: [
131
+ "#ddf4ff",
132
+ "#b6e3ff",
133
+ "#80ccff",
134
+ "#54aeff",
135
+ "#218bff",
136
+ "#0969da",
137
+ "#0550ae",
138
+ "#033d8b",
139
+ "#0a3069",
140
+ "#002155"
141
+ ],
142
+ green: [
143
+ "#dafbe1",
144
+ "#aceebb",
145
+ "#6fdd8b",
146
+ "#4ac26b",
147
+ "#2da44e",
148
+ "#1a7f37",
149
+ "#116329",
150
+ "#044f1e",
151
+ "#003d16",
152
+ "#002d11"
153
+ ],
154
+ red: [
155
+ "#ffebe9",
156
+ "#ffcecb",
157
+ "#ffaba8",
158
+ "#ff8182",
159
+ "#fa4549",
160
+ "#cf222e",
161
+ "#a40e26",
162
+ "#82071e",
163
+ "#660018",
164
+ "#4c0014"
165
+ ]
166
+ }
167
+ };
168
+
169
+ //#endregion
170
+ //#region ../../src/core/atoms/alephaThemeListAtom.ts
171
+ const alephaThemeListAtom = $atom({
172
+ name: "alepha.ui.themeList",
173
+ schema: t.array(t.json()),
174
+ default: [defaultTheme, midnightTheme]
175
+ });
176
+
177
+ //#endregion
178
+ //#region ../../src/core/providers/ThemeProvider.ts
179
+ var ThemeProvider = class {
180
+ alepha = $inject(Alepha);
181
+ cookie = $cookie({
182
+ name: "theme",
183
+ schema: alephaThemeAtom.schema,
184
+ ttl: [1, "year"]
185
+ });
186
+ head = $head(() => {
187
+ const theme = this.getTheme();
188
+ if (!theme || !theme.name) return {};
189
+ return { htmlAttributes: { "data-theme": theme.name } };
190
+ });
191
+ setTheme(index) {
192
+ const newTheme = this.alepha.store.get(alephaThemeListAtom)[index];
193
+ if (!newTheme) throw new AlephaError(`Theme with index ${index} not found`);
194
+ this.cookie.set({ index });
195
+ this.alepha.store.set(alephaThemeAtom, { index });
196
+ if (typeof document === "undefined") return;
197
+ document.documentElement.removeAttribute("data-theme");
198
+ if (newTheme.name) document.documentElement.setAttribute("data-theme", newTheme.name);
199
+ }
200
+ getTheme() {
201
+ const index = this.getThemeIndex();
202
+ const list = this.alepha.store.get(alephaThemeListAtom);
203
+ return list[index] || list[0] || defaultTheme;
204
+ }
205
+ getThemeIndex() {
206
+ try {
207
+ return this.cookie.get()?.index ?? this.alepha.store.get(alephaThemeAtom)?.index ?? 0;
208
+ } catch {
209
+ return this.alepha.store.get(alephaThemeAtom)?.index ?? 0;
210
+ }
211
+ }
212
+ };
213
+
214
+ //#endregion
215
+ //#region ../../src/core/components/dialogs/AlertDialog.tsx
216
+ const AlertDialog = ({ options, onClose }) => /* @__PURE__ */ jsxs(Fragment, { children: [options?.message && /* @__PURE__ */ jsx(Text, {
217
+ mb: "md",
218
+ children: options.message
219
+ }), /* @__PURE__ */ jsx(Flex, {
220
+ justify: "flex-end",
221
+ children: /* @__PURE__ */ jsx(Button, {
222
+ onClick: onClose,
223
+ children: options?.okLabel || "OK"
224
+ })
225
+ })] });
226
+
227
+ //#endregion
228
+ //#region ../../src/core/components/dialogs/ConfirmDialog.tsx
229
+ const ConfirmDialog = ({ options, onConfirm }) => /* @__PURE__ */ jsxs(Fragment, { children: [options?.message && /* @__PURE__ */ jsx(Text, {
230
+ mb: "md",
231
+ children: options.message
232
+ }), /* @__PURE__ */ jsxs(Flex, {
233
+ justify: "flex-end",
234
+ children: [/* @__PURE__ */ jsx(Button, {
235
+ variant: "subtle",
236
+ onClick: () => onConfirm(false),
237
+ children: options?.cancelLabel || "Cancel"
238
+ }), /* @__PURE__ */ jsx(Button, {
239
+ color: options?.confirmColor || "blue",
240
+ onClick: () => onConfirm(true),
241
+ children: options?.confirmLabel || "Confirm"
242
+ })]
243
+ })] });
244
+
245
+ //#endregion
246
+ //#region ../../src/core/components/dialogs/PromptDialog.tsx
247
+ const PromptDialog = ({ options, onSubmit }) => {
248
+ const [value, setValue] = useState(options?.defaultValue || "");
249
+ const inputRef = useRef(null);
250
+ useEffect(() => {
251
+ inputRef.current?.focus();
252
+ }, []);
253
+ const handleSubmit = () => {
254
+ if (!options?.required || value.trim()) onSubmit(value);
255
+ };
256
+ const handleKeyDown = (event) => {
257
+ if (event.key === "Enter") handleSubmit();
258
+ };
259
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
260
+ options?.message && /* @__PURE__ */ jsx(Text, {
261
+ mb: "md",
262
+ children: options.message
263
+ }),
264
+ /* @__PURE__ */ jsx(TextInput, {
265
+ ref: inputRef,
266
+ label: options?.label,
267
+ placeholder: options?.placeholder,
268
+ value,
269
+ onChange: (event) => setValue(event.currentTarget.value),
270
+ onKeyDown: handleKeyDown,
271
+ required: options?.required,
272
+ mb: "md"
273
+ }),
274
+ /* @__PURE__ */ jsxs(Flex, {
275
+ justify: "flex-end",
276
+ children: [/* @__PURE__ */ jsx(Button, {
277
+ variant: "subtle",
278
+ onClick: () => onSubmit(null),
279
+ children: options?.cancelLabel || "Cancel"
280
+ }), /* @__PURE__ */ jsx(Button, {
281
+ onClick: handleSubmit,
282
+ disabled: options?.required && !value.trim(),
283
+ children: options?.submitLabel || "OK"
284
+ })]
285
+ })
286
+ ] });
287
+ };
288
+
289
+ //#endregion
290
+ //#region ../../src/core/services/DialogService.tsx
291
+ var DialogService = class {
292
+ options = { default: {
293
+ centered: true,
294
+ withCloseButton: true,
295
+ size: "md",
296
+ overlayProps: {
297
+ backgroundOpacity: .55,
298
+ blur: 3
299
+ },
300
+ transitionProps: {
301
+ transition: "pop",
302
+ duration: 200
303
+ }
304
+ } };
305
+ /**
306
+ * Show an alert dialog with a message
307
+ */
308
+ alert(options) {
309
+ return new Promise((resolve) => {
310
+ const modalId = this.open({
311
+ ...options,
312
+ title: options?.title || "Alert",
313
+ content: /* @__PURE__ */ jsx(AlertDialog, {
314
+ options,
315
+ onClose: () => {
316
+ this.close(modalId);
317
+ resolve();
318
+ }
319
+ })
320
+ });
321
+ });
322
+ }
323
+ /**
324
+ * Show a confirmation dialog that returns a promise
325
+ */
326
+ confirm(options) {
327
+ return new Promise((resolve) => {
328
+ const modalId = this.open({
329
+ ...options,
330
+ title: options?.title || "Confirm",
331
+ closeOnClickOutside: false,
332
+ closeOnEscape: false,
333
+ content: /* @__PURE__ */ jsx(ConfirmDialog, {
334
+ options,
335
+ onConfirm: (confirmed) => {
336
+ this.close(modalId);
337
+ resolve(confirmed);
338
+ }
339
+ })
340
+ });
341
+ });
342
+ }
343
+ /**
344
+ * Show a prompt dialog to get user input
345
+ */
346
+ prompt(options) {
347
+ return new Promise((resolve) => {
348
+ const modalId = this.open({
349
+ ...options,
350
+ title: options?.title || "Input",
351
+ closeOnClickOutside: false,
352
+ closeOnEscape: false,
353
+ content: /* @__PURE__ */ jsx(PromptDialog, {
354
+ options,
355
+ onSubmit: (value) => {
356
+ this.close(modalId);
357
+ resolve(value);
358
+ }
359
+ })
360
+ });
361
+ });
362
+ }
363
+ /**
364
+ * Open a custom dialog with provided content
365
+ */
366
+ open(options) {
367
+ return modals.open({
368
+ ...this.options.default,
369
+ ...options,
370
+ children: options?.content || options?.message
371
+ });
372
+ }
373
+ /**
374
+ * Close the currently open dialog or a specific dialog by ID
375
+ */
376
+ close(modalId) {
377
+ if (modalId) modals.close(modalId);
378
+ else modals.closeAll();
379
+ }
380
+ };
381
+
382
+ //#endregion
383
+ //#region ../../src/core/services/ToastService.tsx
384
+ var ToastService = class {
385
+ raw = notifications;
386
+ options = { default: {
387
+ radius: "md",
388
+ withBorder: true,
389
+ withCloseButton: true,
390
+ autoClose: 5e3,
391
+ position: "top-center"
392
+ } };
393
+ show(options) {
394
+ notifications.show({
395
+ ...this.options.default,
396
+ ...options
397
+ });
398
+ }
399
+ info(options) {
400
+ if (typeof options === "string") options = { message: options };
401
+ this.show({
402
+ color: "blue",
403
+ icon: /* @__PURE__ */ jsx(IconInfoCircle, { size: 20 }),
404
+ title: "Info",
405
+ message: "Information notification",
406
+ ...options
407
+ });
408
+ }
409
+ success(options) {
410
+ if (typeof options === "string") options = { message: options };
411
+ this.show({
412
+ color: "green",
413
+ icon: /* @__PURE__ */ jsx(IconCheck, { size: 16 }),
414
+ title: "Success",
415
+ message: "Operation completed successfully",
416
+ ...options
417
+ });
418
+ }
419
+ warning(options) {
420
+ if (typeof options === "string") options = { message: options };
421
+ this.show({
422
+ color: "yellow",
423
+ icon: /* @__PURE__ */ jsx(IconAlertTriangle, { size: 20 }),
424
+ title: "Warning",
425
+ message: "Please review this warning",
426
+ ...options
427
+ });
428
+ }
429
+ danger(options) {
430
+ if (typeof options === "string") options = { message: options };
431
+ this.show({
432
+ color: "red",
433
+ icon: /* @__PURE__ */ jsx(IconX, { size: 20 }),
434
+ title: "Error",
435
+ message: "An error occurred",
436
+ ...options
437
+ });
438
+ }
439
+ };
440
+
441
+ //#endregion
442
+ //#region ../../src/core/UiRouter.ts
443
+ /**
444
+ * UI Router defining the root page with AlephaMantineProvider.
445
+ *
446
+ * - Use UiRouter when you need Alepha's Mantine-based UI components and theming.
447
+ * - Prefer to use $ui() for convenience. (Custom Factory of UiRouter)
448
+ */
449
+ var UiRouter = class {
450
+ root = $page({
451
+ path: "/",
452
+ component: AlephaMantineProvider
453
+ });
454
+ };
455
+
456
+ //#endregion
457
+ //#region ../../src/core/hooks/useTheme.ts
458
+ /**
459
+ * Hook to get and set the current theme.
460
+ *
461
+ * Returns a tuple with the current theme and a function to set the theme.
462
+ *
463
+ * ```tsx
464
+ * const [theme, setTheme] = useTheme();
465
+ * ```
466
+ */
467
+ const useTheme = () => {
468
+ useStore(alephaThemeAtom);
469
+ const themeProvider = useInject(ThemeProvider);
470
+ const theme = themeProvider.getTheme();
471
+ const setTheme = (theme) => {
472
+ themeProvider.setTheme(theme.index);
473
+ };
474
+ return [theme, setTheme];
475
+ };
476
+
477
+ //#endregion
478
+ //#region ../../src/core/hooks/useToast.ts
479
+ /**
480
+ * Use this hook to access the Toast Service for showing notifications.
481
+ *
482
+ * @example
483
+ * ```tsx
484
+ * const toast = useToast();
485
+ * toast.success({ message: "Operation completed successfully!" });
486
+ * toast.error({ title: "Error", message: "Something went wrong" });
487
+ * ```
488
+ */
489
+ const useToast = () => {
490
+ return useInject(ToastService);
491
+ };
492
+
493
+ //#endregion
494
+ //#region ../../src/core/components/layout/Omnibar.tsx
495
+ const Omnibar = (props) => {
496
+ const shortcut = props.shortcut ?? "mod+K";
497
+ const searchPlaceholder = props.searchPlaceholder ?? "Search...";
498
+ const nothingFound = props.nothingFound ?? "Nothing found...";
499
+ const router = useRouter();
500
+ const [user] = useStore(currentUserAtom);
501
+ return /* @__PURE__ */ jsx(Spotlight, {
502
+ actions: useMemo(() => router.concretePages.filter((page) => {
503
+ if (page.can && !page.can()) return false;
504
+ return true;
505
+ }).map((page) => ({
506
+ id: page.name,
507
+ label: page.label ?? page.name,
508
+ description: page.description,
509
+ onClick: () => {
510
+ if (page.staticName) return router.push(page.staticName, { params: page.params });
511
+ return router.push(page.name);
512
+ },
513
+ leftSection: renderIcon(page.icon)
514
+ })), [user]),
515
+ shortcut,
516
+ limit: 10,
517
+ searchProps: {
518
+ leftSection: /* @__PURE__ */ jsx(IconSearch, { size: ui.sizes.icon.md }),
519
+ placeholder: searchPlaceholder
520
+ },
521
+ nothingFound
522
+ });
523
+ };
524
+
525
+ //#endregion
526
+ //#region ../../src/core/components/AlephaMantineProvider.tsx
527
+ const AlephaMantineProvider = (props) => {
528
+ const toast = useToast();
529
+ const [theme] = useTheme();
530
+ useEvents({
531
+ "react:transition:begin": () => {
532
+ nprogress.start();
533
+ },
534
+ "react:transition:end": () => {
535
+ nprogress.complete();
536
+ },
537
+ "react:action:error": ({ error, type }) => {
538
+ if (type === "transition" || error instanceof FormValidationError || error instanceof TypeBoxError) return;
539
+ toast.danger({
540
+ title: error.name || "Error",
541
+ message: error.message ?? "An error occurred while processing your action."
542
+ });
543
+ }
544
+ }, []);
545
+ const defaultColorScheme = props.mantine?.defaultColorScheme ?? theme.defaultColorScheme;
546
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ColorSchemeScript, {
547
+ defaultColorScheme,
548
+ ...props.colorSchemeScript
549
+ }), /* @__PURE__ */ jsxs(MantineProvider, {
550
+ ...props.mantine,
551
+ defaultColorScheme,
552
+ theme: {
553
+ ...theme,
554
+ ...props.mantine?.theme
555
+ },
556
+ children: [
557
+ /* @__PURE__ */ jsx(Notifications, { ...props.notifications }),
558
+ /* @__PURE__ */ jsx(NavigationProgress, { ...props.navigationProgress }),
559
+ /* @__PURE__ */ jsxs(ModalsProvider, {
560
+ ...props.modals,
561
+ children: [props.omnibar !== false && /* @__PURE__ */ jsx(Omnibar, { ...props.omnibar }), props.children ?? /* @__PURE__ */ jsx(NestedView, {})]
562
+ })
563
+ ]
564
+ })] });
565
+ };
566
+
567
+ //#endregion
568
+ //#region ../../src/core/constants/ui.ts
569
+ const ui = {
570
+ colors: {
571
+ transparent: "transparent",
572
+ background: "var(--alepha-background)",
573
+ surface: "var(--alepha-surface)",
574
+ elevated: "var(--alepha-elevated)",
575
+ border: "var(--alepha-border)"
576
+ },
577
+ sizes: { icon: {
578
+ xs: 14,
579
+ sm: 16,
580
+ md: 20,
581
+ lg: 24,
582
+ xl: 32
583
+ } }
584
+ };
585
+
586
+ //#endregion
587
+ //#region ../../src/core/helpers/isComponentType.ts
588
+ function isComponentType(param) {
589
+ if (isValidElement(param)) return false;
590
+ return typeof param === "function" || typeof param === "object" && param !== null && "$$typeof" in param;
591
+ }
592
+
593
+ //#endregion
594
+ //#region ../../src/core/components/buttons/ActionButton.tsx
595
+ const ActionMenuItem = (props) => {
596
+ const { item, index } = props;
597
+ const router = useRouter();
598
+ const action = useAction({ handler: async (e) => {
599
+ await item.onClick?.();
600
+ } }, [item.onClick]);
601
+ if (item.type === "divider") return /* @__PURE__ */ jsx(Menu.Divider, {}, index);
602
+ if (item.type === "label") return /* @__PURE__ */ jsx(Menu.Label, { children: item.label }, index);
603
+ if (item.children && item.children.length > 0) return /* @__PURE__ */ jsxs(Menu, {
604
+ trigger: "hover",
605
+ position: "right-start",
606
+ offset: 2,
607
+ children: [/* @__PURE__ */ jsx(Menu.Target, { children: /* @__PURE__ */ jsx(Menu.Item, {
608
+ leftSection: item.icon,
609
+ rightSection: /* @__PURE__ */ jsx(IconChevronRight, { size: 14 }),
610
+ children: item.label
611
+ }) }), /* @__PURE__ */ jsx(Menu.Dropdown, { children: item.children.map((child, childIndex) => /* @__PURE__ */ jsx(ActionMenuItem, {
612
+ item: child,
613
+ index: childIndex
614
+ }, childIndex)) })]
615
+ }, index);
616
+ const menuItemProps = {};
617
+ if (props.item.onClick) menuItemProps.onClick = action.run;
618
+ else if (props.item.href) Object.assign(menuItemProps, router.anchor(props.item.href));
619
+ return /* @__PURE__ */ jsx(Menu.Item, {
620
+ leftSection: item.icon,
621
+ onClick: item.onClick,
622
+ color: item.color,
623
+ rightSection: item.active ? /* @__PURE__ */ jsx(ThemeIcon, {
624
+ size: "xs",
625
+ variant: "transparent",
626
+ children: /* @__PURE__ */ jsx(IconCheck, {})
627
+ }) : void 0,
628
+ ...menuItemProps,
629
+ children: item.label
630
+ }, index);
631
+ };
632
+ const ActionButton = (_props) => {
633
+ const theme = useMantineTheme();
634
+ const props = { ..._props };
635
+ const { tooltip, menu, icon, ...restProps } = props;
636
+ if (props.variant === "subtle" || props.variant === "outline") restProps.color ??= "gray";
637
+ if (props.intent) {
638
+ if (props.intent === "none") restProps.color ??= "gray";
639
+ else if (props.intent === "primary") restProps.color ??= theme.primaryColor;
640
+ else if (props.intent === "success") {
641
+ restProps.c ??= "white";
642
+ restProps.color ??= "green";
643
+ } else if (props.intent === "danger") {
644
+ restProps.c ??= "white";
645
+ restProps.color ??= "red";
646
+ } else if (props.intent === "warning") restProps.color ??= "yellow";
647
+ else if (props.intent === "info") {
648
+ restProps.c ??= "white";
649
+ restProps.color ??= "blue";
650
+ }
651
+ }
652
+ if (props.icon) {
653
+ const sizes = ui.sizes.icon;
654
+ const icon = isComponentType(props.icon) ? /* @__PURE__ */ jsx(props.icon, { size: sizes[props.size || "md"] }) : /* @__PURE__ */ jsx("span", { children: props.icon });
655
+ if (!props.children) {
656
+ restProps.children = Children.only(icon);
657
+ restProps.px ??= "xs";
658
+ } else restProps.leftSection = icon;
659
+ }
660
+ if (props.leftSection && !props.children) restProps.px ??= "xs";
661
+ if (props.textVisibleFrom) {
662
+ const { children, textVisibleFrom, leftSection, ...rest } = restProps;
663
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Flex, {
664
+ w: "100%",
665
+ visibleFrom: textVisibleFrom,
666
+ children: /* @__PURE__ */ jsx(ActionButton, {
667
+ flex: 1,
668
+ ...rest,
669
+ leftSection,
670
+ tooltip,
671
+ menu,
672
+ children
673
+ })
674
+ }), /* @__PURE__ */ jsx(Flex, {
675
+ w: "100%",
676
+ hiddenFrom: textVisibleFrom,
677
+ children: /* @__PURE__ */ jsx(ActionButton, {
678
+ px: "xs",
679
+ ...rest,
680
+ tooltip,
681
+ menu,
682
+ children: leftSection
683
+ })
684
+ })] });
685
+ }
686
+ const renderAction = () => {
687
+ if ("href" in restProps && restProps.href) {
688
+ if (restProps.href.startsWith("http") || restProps.target) return /* @__PURE__ */ jsx(ActionHrefButton, {
689
+ ...restProps,
690
+ href: restProps.href,
691
+ children: restProps.children
692
+ });
693
+ return /* @__PURE__ */ jsx(ActionNavigationButton, {
694
+ ...restProps,
695
+ href: restProps.href,
696
+ children: restProps.children
697
+ });
698
+ }
699
+ delete restProps.classNameActive;
700
+ delete restProps.variantActive;
701
+ delete restProps.propsActive;
702
+ if ("action" in restProps && restProps.action) return /* @__PURE__ */ jsx(ActionHookButton, {
703
+ ...restProps,
704
+ action: restProps.action,
705
+ children: restProps.children
706
+ });
707
+ if ("onClick" in restProps && restProps.onClick) return /* @__PURE__ */ jsx(ActionClickButton, {
708
+ ...restProps,
709
+ onClick: restProps.onClick,
710
+ children: restProps.children
711
+ });
712
+ if ("form" in restProps && restProps.form) {
713
+ if (restProps.type === "reset") return /* @__PURE__ */ jsx(ActionResetButton, {
714
+ ...restProps,
715
+ form: restProps.form,
716
+ children: restProps.children
717
+ });
718
+ return /* @__PURE__ */ jsx(ActionSubmitButton, {
719
+ ...restProps,
720
+ form: restProps.form,
721
+ children: restProps.children
722
+ });
723
+ }
724
+ return /* @__PURE__ */ jsx(Button, {
725
+ ...restProps,
726
+ children: restProps.children
727
+ });
728
+ };
729
+ let actionElement = renderAction();
730
+ if (menu) actionElement = /* @__PURE__ */ jsxs(Menu, {
731
+ position: menu.position || "bottom-start",
732
+ width: menu.width || 200,
733
+ shadow: menu.shadow || "md",
734
+ trigger: menu.on === "hover" ? "hover" : "click",
735
+ ...menu.menuProps,
736
+ children: [/* @__PURE__ */ jsx(Menu.Target, {
737
+ ...menu.targetProps,
738
+ children: actionElement
739
+ }), /* @__PURE__ */ jsx(Menu.Dropdown, { children: menu.items.map((item, index) => /* @__PURE__ */ jsx(ActionMenuItem, {
740
+ item,
741
+ index
742
+ }, index)) })]
743
+ });
744
+ if (tooltip) {
745
+ const defaultTooltipProps = { openDelay: 1e3 };
746
+ return /* @__PURE__ */ jsx(Tooltip, { ...typeof tooltip === "string" || typeof tooltip === "number" ? {
747
+ ...defaultTooltipProps,
748
+ label: tooltip,
749
+ children: actionElement
750
+ } : {
751
+ ...defaultTooltipProps,
752
+ ...tooltip,
753
+ children: actionElement
754
+ } });
755
+ }
756
+ return actionElement;
757
+ };
758
+ /**
759
+ * Action button that submits a form with loading and disabled state handling.
760
+ */
761
+ const ActionSubmitButton = (props) => {
762
+ const { form, ...buttonProps } = props;
763
+ const state = useFormState(form);
764
+ return /* @__PURE__ */ jsx(Button, {
765
+ ...buttonProps,
766
+ loading: state.loading,
767
+ disabled: state.loading,
768
+ type: "submit",
769
+ children: props.children
770
+ });
771
+ };
772
+ const ActionResetButton = (props) => {
773
+ const { form, ...buttonProps } = props;
774
+ const state = useFormState(form);
775
+ return /* @__PURE__ */ jsx(Button, {
776
+ ...buttonProps,
777
+ disabled: state.loading,
778
+ type: "reset",
779
+ children: props.children
780
+ });
781
+ };
782
+ /**
783
+ * Action button that integrates with useAction hook return value.
784
+ * Automatically handles loading state and executes the action on click.
785
+ *
786
+ * @example
787
+ * ```tsx
788
+ * const saveAction = useAction({
789
+ * handler: async (data) => {
790
+ * await api.save(data);
791
+ * }
792
+ * }, []);
793
+ *
794
+ * <ActionButton action={saveAction}>
795
+ * Save
796
+ * </ActionButton>
797
+ * ```
798
+ */
799
+ const ActionHookButton = (props) => {
800
+ const { action, ...buttonProps } = props;
801
+ return /* @__PURE__ */ jsx(Button, {
802
+ ...buttonProps,
803
+ disabled: action.loading || props.disabled,
804
+ loading: action.loading,
805
+ onClick: () => action.run(),
806
+ children: props.children
807
+ });
808
+ };
809
+ /**
810
+ * Basic action button that handles click events with loading and error handling.
811
+ *
812
+ * @example
813
+ * ```tsx
814
+ * <ActionButton onClick={() => api.doSomething()}>
815
+ * Do Something
816
+ * </ActionButton>
817
+ * ```
818
+ */
819
+ const ActionClickButton = ({ preventDefault, ...props }) => {
820
+ const action = useAction({ handler: async (e) => {
821
+ if (preventDefault) e.preventDefault();
822
+ await props.onClick(e);
823
+ } }, [props.onClick, preventDefault]);
824
+ return /* @__PURE__ */ jsx(Button, {
825
+ ...props,
826
+ disabled: action.loading || props.disabled,
827
+ loading: action.loading,
828
+ onClick: action.run,
829
+ children: props.children
830
+ });
831
+ };
832
+ /**
833
+ * Action for navigation with active state support.
834
+ */
835
+ const ActionNavigationButton = (props) => {
836
+ const { active: options, classNameActive, variantActive, propsActive, routerGoOptions, onClick: propsOnClick, anchor, ...buttonProps } = props;
837
+ const router = useRouter();
838
+ const { isPending, isActive } = useActive(options ? {
839
+ href: props.href,
840
+ ...options
841
+ } : { href: props.href });
842
+ const anchorProps = router.anchor(props.href, routerGoOptions);
843
+ if (propsActive && isActive) Object.assign(buttonProps, propsActive);
844
+ const combinedOnClick = (e) => {
845
+ propsOnClick?.(e);
846
+ anchorProps.onClick?.(e);
847
+ };
848
+ const className = buttonProps.className || "";
849
+ if (isActive && options !== false && classNameActive) buttonProps.className = `${className} ${classNameActive}`.trim();
850
+ if (props.anchorProps || anchor) return /* @__PURE__ */ jsx(Anchor, {
851
+ component: "a",
852
+ ...anchorProps,
853
+ ...buttonProps,
854
+ ...props.anchorProps,
855
+ onClick: combinedOnClick,
856
+ children: props.children
857
+ });
858
+ return /* @__PURE__ */ jsx(Button, {
859
+ component: "a",
860
+ loading: isPending,
861
+ ...buttonProps,
862
+ ...anchorProps,
863
+ onClick: combinedOnClick,
864
+ variant: isActive && options !== false ? variantActive ?? "filled" : buttonProps.variant ?? "subtle",
865
+ children: props.children
866
+ });
867
+ };
868
+ const ActionHrefButton = (props) => {
869
+ const { active: options, classNameActive, variantActive, propsActive, routerGoOptions, target, ...buttonProps } = props;
870
+ return /* @__PURE__ */ jsx(Button, {
871
+ component: "a",
872
+ target,
873
+ ...buttonProps,
874
+ children: props.children
875
+ });
876
+ };
877
+
878
+ //#endregion
879
+ //#region ../../src/core/components/buttons/BurgerButton.tsx
880
+ const BurgerButton = (props) => {
881
+ const [sidebar, setSidebar] = useStore(alephaSidebarAtom);
882
+ return /* @__PURE__ */ jsx(Burger, {
883
+ opened: !sidebar.closed,
884
+ onClick: () => setSidebar({
885
+ ...sidebar,
886
+ closed: !sidebar.closed
887
+ }),
888
+ hiddenFrom: "md",
889
+ size: "sm",
890
+ ...props
891
+ });
892
+ };
893
+
894
+ //#endregion
895
+ //#region ../../src/core/components/buttons/ClipboardButton.tsx
896
+ const ClipboardButton = (props) => {
897
+ const { value, timeout = 2e3, copyLabel = "Copy", copiedLabel = "Copied", children, ...buttonProps } = props;
898
+ return /* @__PURE__ */ jsx(CopyButton, {
899
+ value,
900
+ timeout,
901
+ children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, {
902
+ label: copied ? copiedLabel : copyLabel,
903
+ openDelay: 500,
904
+ children: /* @__PURE__ */ jsx(ActionButton, {
905
+ color: copied ? "teal" : void 0,
906
+ onClick: copy,
907
+ icon: copied ? IconCheck : IconCopy,
908
+ ...buttonProps,
909
+ children
910
+ })
911
+ })
912
+ });
913
+ };
914
+
915
+ //#endregion
916
+ //#region ../../src/core/components/buttons/DarkModeButton.tsx
917
+ /**
918
+ * SSR-safe dark mode toggle button.
919
+ *
920
+ * Uses CSS-based icon switching to avoid hydration mismatches.
921
+ * Both icons are rendered, CSS hides the wrong one based on
922
+ * `data-mantine-color-scheme` attribute.
923
+ */
924
+ const DarkModeButton = (props) => {
925
+ const { setColorScheme } = useMantineColorScheme();
926
+ const toggleColorScheme = () => {
927
+ setColorScheme((document.documentElement.getAttribute("data-mantine-color-scheme") ?? "light") === "dark" ? "light" : "dark");
928
+ };
929
+ const size = props.size ?? "md";
930
+ const iconSize = ui.sizes.icon[size] ?? ui.sizes.icon.md;
931
+ return /* @__PURE__ */ jsx(ActionButton, {
932
+ onClick: toggleColorScheme,
933
+ variant: props.variant ?? "subtle",
934
+ size,
935
+ "aria-label": "Toggle color scheme",
936
+ icon: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(IconSun, {
937
+ size: iconSize,
938
+ className: "alepha-light-hidden"
939
+ }), /* @__PURE__ */ jsx(IconMoon, {
940
+ size: iconSize,
941
+ className: "alepha-dark-hidden"
942
+ })] }),
943
+ ...props
944
+ });
945
+ };
946
+
947
+ //#endregion
948
+ //#region ../../src/core/components/buttons/LanguageButton.tsx
949
+ const LanguageButton = (props) => {
950
+ const i18n = useI18n();
951
+ return /* @__PURE__ */ jsx(ActionButton, {
952
+ variant: "subtle",
953
+ icon: IconLanguage,
954
+ menu: { items: i18n.languages.map((lang) => ({
955
+ label: i18n.tr(lang),
956
+ onClick: () => i18n.setLang(lang),
957
+ active: i18n.lang === lang
958
+ })) },
959
+ ...props
960
+ });
961
+ };
962
+
963
+ //#endregion
964
+ //#region ../../src/core/components/buttons/OmnibarButton.tsx
965
+ const OmnibarButton = (props) => {
966
+ const os = useOs();
967
+ const shortcut = os === "macos" || os === "ios" ? "⌘" : "Ctrl";
968
+ if (props.collapsed) return /* @__PURE__ */ jsx(ActionButton, {
969
+ variant: "subtle",
970
+ onClick: spotlight.open,
971
+ radius: "md",
972
+ icon: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
973
+ tooltip: {
974
+ label: "Search",
975
+ position: "right"
976
+ },
977
+ ...props.actionProps
978
+ });
979
+ return /* @__PURE__ */ jsx(ActionButton, {
980
+ variant: "default",
981
+ onClick: spotlight.open,
982
+ justify: "space-between",
983
+ rightSection: /* @__PURE__ */ jsxs(Kbd, {
984
+ visibleFrom: "sm",
985
+ size: "sm",
986
+ children: [/* @__PURE__ */ jsx(ClientOnly, { children: shortcut }), "+K"]
987
+ }),
988
+ radius: "md",
989
+ ...props.actionProps,
990
+ children: /* @__PURE__ */ jsxs(Flex, {
991
+ align: "center",
992
+ gap: "xs",
993
+ children: [/* @__PURE__ */ jsx(IconSearch, {
994
+ size: 16,
995
+ color: "gray"
996
+ }), /* @__PURE__ */ jsx(Flex, {
997
+ visibleFrom: "sm",
998
+ miw: 192,
999
+ children: /* @__PURE__ */ jsx(Text, {
1000
+ size: "xs",
1001
+ c: "dimmed",
1002
+ children: "Search..."
1003
+ })
1004
+ })]
1005
+ })
1006
+ });
1007
+ };
1008
+
1009
+ //#endregion
1010
+ //#region ../../src/core/components/buttons/ThemeButton.tsx
1011
+ const ThemeButton = (props) => {
1012
+ const [theme, setTheme] = useTheme();
1013
+ const themeList = useStore(alephaThemeListAtom)[0];
1014
+ return /* @__PURE__ */ jsx(ActionButton, {
1015
+ variant: "subtle",
1016
+ icon: IconPalette,
1017
+ menu: { items: themeList.map((it, index) => ({
1018
+ label: it.name,
1019
+ onClick: () => setTheme({ index }),
1020
+ active: theme.name === it.name
1021
+ })) },
1022
+ ...props
1023
+ });
1024
+ };
1025
+
1026
+ //#endregion
1027
+ //#region ../../src/core/components/buttons/ToggleSidebarButton.tsx
1028
+ const ToggleSidebarButton = (props) => {
1029
+ const [sidebar, setSidebar] = useStore(alephaSidebarAtom);
1030
+ return /* @__PURE__ */ jsx(ActionButton, {
1031
+ icon: sidebar.collapsed ? IconLayoutSidebarRightCollapse : IconLayoutSidebarLeftCollapse,
1032
+ visibleFrom: "md",
1033
+ variant: "subtle",
1034
+ size: "md",
1035
+ onClick: () => {
1036
+ setSidebar({
1037
+ ...sidebar,
1038
+ collapsed: !sidebar.collapsed
1039
+ });
1040
+ },
1041
+ tooltip: {
1042
+ position: "right",
1043
+ label: sidebar.collapsed ? "Show sidebar" : "Hide sidebar"
1044
+ },
1045
+ ...props
1046
+ });
1047
+ };
1048
+
1049
+ //#endregion
1050
+ //#region ../../src/core/components/data/DetailList.tsx
1051
+ const DetailList = ({ items, columns = 1 }) => {
1052
+ return /* @__PURE__ */ jsx(Grid, {
1053
+ gutter: "xs",
1054
+ children: items.filter((item) => !item.hidden).map((item) => /* @__PURE__ */ jsx(Grid.Col, {
1055
+ span: 12 / columns,
1056
+ children: /* @__PURE__ */ jsxs(Flex, {
1057
+ py: 6,
1058
+ justify: "space-between",
1059
+ align: "center",
1060
+ style: { borderBottom: "1px solid var(--mantine-color-default-border)" },
1061
+ children: [/* @__PURE__ */ jsx(Text, {
1062
+ size: "xs",
1063
+ c: "dimmed",
1064
+ style: { flexShrink: 0 },
1065
+ children: item.label
1066
+ }), /* @__PURE__ */ jsxs(Flex, {
1067
+ gap: 4,
1068
+ align: "center",
1069
+ style: { minWidth: 0 },
1070
+ children: [typeof item.value === "string" || typeof item.value === "number" ? /* @__PURE__ */ jsx(Text, {
1071
+ size: "sm",
1072
+ fw: 500,
1073
+ truncate: true,
1074
+ children: item.value || "—"
1075
+ }) : item.value ?? /* @__PURE__ */ jsx(Text, {
1076
+ size: "sm",
1077
+ c: "dimmed",
1078
+ children: "—"
1079
+ }), item.copyable && /* @__PURE__ */ jsx(ClipboardButton, {
1080
+ value: item.copyable,
1081
+ size: "xs",
1082
+ variant: "subtle",
1083
+ c: "dimmed"
1084
+ })]
1085
+ })]
1086
+ })
1087
+ }, item.label))
1088
+ });
1089
+ };
1090
+
1091
+ //#endregion
1092
+ //#region ../../src/core/components/data/StatCards.tsx
1093
+ const StatCards = ({ items }) => /* @__PURE__ */ jsx(Flex, {
1094
+ gap: "sm",
1095
+ wrap: "wrap",
1096
+ children: items.map((item) => {
1097
+ (item.icon && typeof item.icon === "function" ? item.icon : null) ? ui.sizes.icon.md : item.icon;
1098
+ return /* @__PURE__ */ jsx(Paper, {
1099
+ p: "md",
1100
+ radius: "md",
1101
+ withBorder: true,
1102
+ style: {
1103
+ flex: "1 1 0",
1104
+ minWidth: 120
1105
+ },
1106
+ children: /* @__PURE__ */ jsx(Flex, {
1107
+ gap: "sm",
1108
+ align: "center",
1109
+ children: /* @__PURE__ */ jsxs(Flex, {
1110
+ direction: "column",
1111
+ children: [/* @__PURE__ */ jsx(Text, {
1112
+ size: "xl",
1113
+ fw: 700,
1114
+ lh: 1,
1115
+ children: item.value
1116
+ }), /* @__PURE__ */ jsx(Text, {
1117
+ size: "xs",
1118
+ c: "dimmed",
1119
+ children: item.label
1120
+ })]
1121
+ })
1122
+ })
1123
+ }, item.label);
1124
+ })
1125
+ });
1126
+
1127
+ //#endregion
1128
+ //#region ../../src/core/components/Flex.tsx
1129
+ const Flex$1 = forwardRef((props, ref) => {
1130
+ const { fill, center, centerX, centerY, col, ...rest } = props;
1131
+ if (fill) rest.flex ??= 1;
1132
+ if (col) rest.direction ??= "column";
1133
+ if (center) {
1134
+ rest.align ??= "center";
1135
+ rest.justify ??= "center";
1136
+ }
1137
+ if (centerX) rest.justify ??= "center";
1138
+ if (centerY) rest.align ??= "center";
1139
+ return /* @__PURE__ */ jsx(Flex, {
1140
+ ref,
1141
+ ...rest
1142
+ });
1143
+ });
1144
+ Flex$1.displayName = "Flex";
1145
+
1146
+ //#endregion
1147
+ //#region ../../src/core/components/layout/AppBar.tsx
1148
+ const AppBar = (props) => {
1149
+ const { items = [] } = props;
1150
+ const router = useRouter();
1151
+ const renderItem = (item, index) => {
1152
+ if (item.can && !item.can()) return null;
1153
+ if ("type" in item) {
1154
+ if (item.type === "burger") return /* @__PURE__ */ jsx(BurgerButton, {}, index);
1155
+ if (item.type === "dark") return /* @__PURE__ */ jsx(DarkModeButton, { ...item.props }, index);
1156
+ if (item.type === "search") return /* @__PURE__ */ jsx(OmnibarButton, { ...item.props }, index);
1157
+ if (item.type === "lang") return /* @__PURE__ */ jsx(LanguageButton, { ...item.props }, index);
1158
+ if (item.type === "spacer") return /* @__PURE__ */ jsx(Flex$1, { w: 16 }, index);
1159
+ if (item.type === "divider") return /* @__PURE__ */ jsx(Divider, { orientation: "vertical" }, index);
1160
+ if (item.type === "logo") return renderLogo(item, index);
1161
+ if (item.type === "back") return renderBack(item, index);
1162
+ }
1163
+ if ("element" in item) return item.element;
1164
+ return null;
1165
+ };
1166
+ const renderLogo = (item, index) => {
1167
+ const { src, text, icon, href, height = 32, width, fontWeight = 700, fontSize = "lg" } = item.props ?? {};
1168
+ const logoContent = src ? /* @__PURE__ */ jsx(Image, {
1169
+ src,
1170
+ h: height,
1171
+ w: width,
1172
+ fit: "contain"
1173
+ }) : icon ? typeof icon === "function" ? /* @__PURE__ */ jsx(icon, {}) : icon : text ? /* @__PURE__ */ jsx(Text$1, {
1174
+ fw: fontWeight,
1175
+ size: fontSize,
1176
+ children: text
1177
+ }) : null;
1178
+ if (href) return /* @__PURE__ */ jsx(Anchor, {
1179
+ component: Link,
1180
+ href,
1181
+ underline: "never",
1182
+ c: "inherit",
1183
+ children: logoContent
1184
+ }, index);
1185
+ return /* @__PURE__ */ jsx(Flex$1, { children: logoContent }, index);
1186
+ };
1187
+ const renderBack = (item, index) => {
1188
+ const { label = "Back", iconOnly = true, href, icon } = item.props ?? {};
1189
+ const renderIcon = () => {
1190
+ if (!icon) return /* @__PURE__ */ jsx(IconArrowLeft, { size: 18 });
1191
+ if (typeof icon === "function") return /* @__PURE__ */ jsx(icon, { size: 18 });
1192
+ return icon;
1193
+ };
1194
+ const iconElement = renderIcon();
1195
+ const handleClick = () => {
1196
+ if (href) router.push(href);
1197
+ else router.back();
1198
+ };
1199
+ if (iconOnly) return /* @__PURE__ */ jsx(ActionButton, {
1200
+ icon: iconElement,
1201
+ variant: "subtle",
1202
+ color: "gray",
1203
+ onClick: handleClick,
1204
+ tooltip: {
1205
+ label,
1206
+ position: "bottom"
1207
+ }
1208
+ }, index);
1209
+ return /* @__PURE__ */ jsx(ActionButton, {
1210
+ leftSection: iconElement,
1211
+ variant: "subtle",
1212
+ color: "gray",
1213
+ onClick: handleClick,
1214
+ children: label
1215
+ }, index);
1216
+ };
1217
+ const leftItems = items.filter((item) => item.position === "left");
1218
+ const centerItems = items.filter((item) => item.position === "center");
1219
+ const rightItems = items.filter((item) => item.position === "right");
1220
+ const content = /* @__PURE__ */ jsxs(Flex$1, {
1221
+ h: "100%",
1222
+ align: "center",
1223
+ px: props.container ? 0 : "md",
1224
+ justify: "space-between",
1225
+ ...props.flexProps,
1226
+ children: [
1227
+ /* @__PURE__ */ jsx(Flex$1, {
1228
+ flex: 1,
1229
+ children: leftItems.map((item, index) => /* @__PURE__ */ jsx(Flex$1, {
1230
+ ml: index === 0 ? 0 : "md",
1231
+ align: "center",
1232
+ children: renderItem(item, index)
1233
+ }, index))
1234
+ }),
1235
+ /* @__PURE__ */ jsx(Flex$1, { children: centerItems.map((item, index) => /* @__PURE__ */ jsx(Flex$1, {
1236
+ mx: "md",
1237
+ align: "center",
1238
+ children: renderItem(item, index)
1239
+ }, index)) }),
1240
+ /* @__PURE__ */ jsx(Flex$1, {
1241
+ flex: 1,
1242
+ align: "center",
1243
+ justify: "end",
1244
+ children: rightItems.map((item, index) => /* @__PURE__ */ jsx(Flex$1, {
1245
+ ml: index === 0 ? 0 : "md",
1246
+ align: "center",
1247
+ children: renderItem(item, index)
1248
+ }, index))
1249
+ })
1250
+ ]
1251
+ });
1252
+ if (props.container) return /* @__PURE__ */ jsx(Container, {
1253
+ h: "100%",
1254
+ ...typeof props.container === "boolean" ? {} : props.container,
1255
+ children: content
1256
+ });
1257
+ return content;
1258
+ };
1259
+
1260
+ //#endregion
1261
+ //#region ../../src/core/components/layout/Breadcrumb.tsx
1262
+ /**
1263
+ * Automatic breadcrumb component that reads the current route hierarchy
1264
+ * from the Alepha router's layer stack.
1265
+ *
1266
+ * Pages should define a `label` in their `$page()` options for best results.
1267
+ * Falls back to the page name converted to Title Case.
1268
+ */
1269
+ const Breadcrumb = ({ home = "Home", separator, size = "sm", ...groupProps }) => {
1270
+ const state = useRouterState();
1271
+ const router = useRouter();
1272
+ const crumbs = [];
1273
+ if (home !== false) crumbs.push({
1274
+ label: home,
1275
+ href: "/"
1276
+ });
1277
+ for (let i = 1; i < state.layers.length; i++) {
1278
+ const layer = state.layers[i];
1279
+ const route = layer.route;
1280
+ if (route?.path === "/" || route?.path === "") continue;
1281
+ const label = route?.label ?? toTitleCase(layer.name);
1282
+ const href = router.path(layer.name);
1283
+ crumbs.push({
1284
+ label,
1285
+ href
1286
+ });
1287
+ }
1288
+ if (crumbs.length === 0) return null;
1289
+ const sep = separator ?? /* @__PURE__ */ jsx(IconChevronRight, {
1290
+ size: 12,
1291
+ color: "#9ca3af"
1292
+ });
1293
+ return /* @__PURE__ */ jsx(Flex$1, {
1294
+ gap: "sm",
1295
+ ...groupProps,
1296
+ children: crumbs.map((crumb, i) => /* @__PURE__ */ jsxs(Flex$1, {
1297
+ align: "center",
1298
+ gap: "sm",
1299
+ children: [i > 0 && sep, i < crumbs.length - 1 ? /* @__PURE__ */ jsx(ActionButton, {
1300
+ anchor: true,
1301
+ href: crumb.href,
1302
+ size,
1303
+ c: "dimmed",
1304
+ children: crumb.label
1305
+ }) : /* @__PURE__ */ jsx(Text$1, {
1306
+ size,
1307
+ fw: 500,
1308
+ children: crumb.label
1309
+ })]
1310
+ }, crumb.href))
1311
+ });
1312
+ };
1313
+
1314
+ //#endregion
1315
+ //#region ../../src/core/components/layout/Container.tsx
1316
+ const Container$1 = forwardRef((props, ref) => {
1317
+ return /* @__PURE__ */ jsx(Container, {
1318
+ ref,
1319
+ ...props
1320
+ });
1321
+ });
1322
+ Container$1.displayName = "Container";
1323
+
1324
+ //#endregion
1325
+ //#region ../../src/core/components/layout/Sidebar.tsx
1326
+ const Sidebar = (props) => {
1327
+ const router = useRouter();
1328
+ const { onItemClick } = props;
1329
+ const divider = (key, fill, collapsed) => {
1330
+ return /* @__PURE__ */ jsx(Flex$1, {
1331
+ h: 1,
1332
+ bg: "var(--mantine-color-default-border)",
1333
+ my: "xs",
1334
+ mx: fill ? "calc(-1 * var(--mantine-spacing-md))" : collapsed ? 0 : "sm"
1335
+ }, key);
1336
+ };
1337
+ const renderNode = (item, key, collapsed) => {
1338
+ if ("type" in item) {
1339
+ if (item.type === "spacer") {
1340
+ if (collapsed) return null;
1341
+ return /* @__PURE__ */ jsx(Flex$1, { h: 16 }, key);
1342
+ }
1343
+ if (item.type === "divider") return divider(key, item.fill, collapsed);
1344
+ if (item.type === "search") return /* @__PURE__ */ jsx(Flex$1, {
1345
+ mb: "xs",
1346
+ children: /* @__PURE__ */ jsx(OmnibarButton, { collapsed })
1347
+ }, key);
1348
+ if (item.type === "toggle") return /* @__PURE__ */ jsx(ToggleSidebarButton, {}, key);
1349
+ if (item.type === "section") {
1350
+ if (item.children && item.children.length > 0) {
1351
+ if (!item.children.some((child) => !("can" in child) || !child.can || child.can())) return null;
1352
+ }
1353
+ if (collapsed) return /* @__PURE__ */ jsxs(Fragment$1, { children: [divider(`${key}-d`, void 0, collapsed), item.children?.map((child, index) => renderNode(child, `s${key}-${index}`, collapsed))] }, key);
1354
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Flex$1, {
1355
+ mt: "md",
1356
+ align: "center",
1357
+ gap: "xs",
1358
+ children: [renderIcon(item.icon, ui.sizes.icon.sm), /* @__PURE__ */ jsx(Text$1, {
1359
+ size: "xs",
1360
+ c: "dimmed",
1361
+ tt: "uppercase",
1362
+ fw: "bold",
1363
+ children: item.label
1364
+ })]
1365
+ }), item.children?.map((child, index) => renderNode(child, `s${key}-${index}`, collapsed))] }, key);
1366
+ }
1367
+ }
1368
+ if ("element" in item) return /* @__PURE__ */ jsx(Fragment$1, { children: item.element }, key);
1369
+ if (item.can && !item.can()) return null;
1370
+ if (item.children && item.children.length > 0) {
1371
+ if (!item.children.some((child) => !child.can || child.can())) return null;
1372
+ }
1373
+ if (collapsed) return /* @__PURE__ */ jsx(SidebarCollapsedItem, {
1374
+ item,
1375
+ level: 0,
1376
+ onItemClick,
1377
+ theme: props.theme ?? {}
1378
+ }, key);
1379
+ return /* @__PURE__ */ jsx(SidebarItem, {
1380
+ item,
1381
+ level: 0,
1382
+ onItemClick,
1383
+ theme: props.theme ?? {}
1384
+ }, key);
1385
+ };
1386
+ const getSidebarNodes = () => {
1387
+ if (props.items) return props.items;
1388
+ if (props.autoPopulateMenu) {
1389
+ const items = router.concretePages.filter((page) => !page.can || page.can()).map((page) => ({
1390
+ label: page.label ?? page.name,
1391
+ icon: renderIcon(page.icon),
1392
+ href: router.path(page.name)
1393
+ }));
1394
+ if (typeof props.autoPopulateMenu === "object" && props.autoPopulateMenu.startsWith) {
1395
+ const startsWith = props.autoPopulateMenu.startsWith;
1396
+ return items.filter((item) => item.href?.startsWith(startsWith));
1397
+ }
1398
+ return items;
1399
+ }
1400
+ return [];
1401
+ };
1402
+ const padding = "md";
1403
+ const gap = props.items ? props.gap ?? 4 : "xs";
1404
+ const menu = useMemo(() => getSidebarNodes(), [props.items, props.autoPopulateMenu]);
1405
+ const renderSidebar = (collapsed) => /* @__PURE__ */ jsxs(Flex$1, {
1406
+ flex: 1,
1407
+ py: padding,
1408
+ direction: "column",
1409
+ ...props.flexProps,
1410
+ children: [
1411
+ /* @__PURE__ */ jsx(Flex$1, {
1412
+ gap,
1413
+ px: padding,
1414
+ direction: "column",
1415
+ children: menu.filter((it) => it.position === "top").map((item, index) => renderNode(item, index, collapsed))
1416
+ }),
1417
+ /* @__PURE__ */ jsx(Flex$1, {
1418
+ gap,
1419
+ px: padding,
1420
+ direction: "column",
1421
+ flex: 1,
1422
+ children: menu.filter((it) => !it.position).map((item, index) => renderNode(item, index, collapsed))
1423
+ }),
1424
+ /* @__PURE__ */ jsx(Flex$1, {
1425
+ gap,
1426
+ px: padding,
1427
+ direction: "column",
1428
+ children: menu.filter((it) => it.position === "bottom").map((item, index) => renderNode(item, index, collapsed))
1429
+ })
1430
+ ]
1431
+ });
1432
+ if (props.collapsed) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Flex$1, {
1433
+ flex: 1,
1434
+ direction: "column",
1435
+ visibleFrom: "md",
1436
+ children: renderSidebar(true)
1437
+ }), /* @__PURE__ */ jsx(Flex$1, {
1438
+ flex: 1,
1439
+ direction: "column",
1440
+ hiddenFrom: "md",
1441
+ children: renderSidebar(false)
1442
+ })] });
1443
+ return renderSidebar(false);
1444
+ };
1445
+ const SidebarItem = (props) => {
1446
+ const { item, level } = props;
1447
+ const maxLevel = 2;
1448
+ const router = useRouter();
1449
+ const isActive = useCallback((item) => {
1450
+ if (!item.children) return false;
1451
+ for (const child of item.children) {
1452
+ if (child.href) {
1453
+ if (router.isActive(child.href)) return true;
1454
+ }
1455
+ if (isActive(child)) return true;
1456
+ }
1457
+ return false;
1458
+ }, []);
1459
+ const [isOpen, setIsOpen] = useState(isActive(item));
1460
+ useEvents({ "react:transition:end": () => {
1461
+ if (isActive(item)) setIsOpen(true);
1462
+ } }, []);
1463
+ if (level > maxLevel) return null;
1464
+ const handleItemClick = (e) => {
1465
+ if (!props.item.target) e.preventDefault();
1466
+ if (item.children && item.children.length > 0) setIsOpen(!isOpen);
1467
+ else {
1468
+ props.onItemClick?.(item);
1469
+ item.onClick?.();
1470
+ }
1471
+ };
1472
+ return /* @__PURE__ */ jsxs(Flex$1, {
1473
+ direction: "column",
1474
+ ps: level === 0 ? 0 : 32,
1475
+ pos: "relative",
1476
+ children: [/* @__PURE__ */ jsx(ActionButton, {
1477
+ w: "100%",
1478
+ justify: "space-between",
1479
+ href: props.item.href,
1480
+ target: props.item.target,
1481
+ size: props.item.theme?.size ?? props.theme.button?.size ?? (level === 0 ? "sm" : "xs"),
1482
+ bd: 0,
1483
+ fw: "normal",
1484
+ variant: "default",
1485
+ propsActive: {
1486
+ variant: "outline",
1487
+ fw: "bold"
1488
+ },
1489
+ radius: props.item.theme?.radius ?? props.theme.button?.radius ?? "md",
1490
+ onClick: handleItemClick,
1491
+ leftSection: /* @__PURE__ */ jsxs(Flex$1, {
1492
+ w: "100%",
1493
+ align: "center",
1494
+ gap: "sm",
1495
+ children: [renderIcon(item.icon, ui.sizes.icon.sm), /* @__PURE__ */ jsx(Flex$1, {
1496
+ direction: "column",
1497
+ children: /* @__PURE__ */ jsx(Flex$1, { children: item.label })
1498
+ })]
1499
+ }),
1500
+ rightSection: item.children ? /* @__PURE__ */ jsx(Flex$1, { children: isOpen ? /* @__PURE__ */ jsx(IconChevronDown, { size: 14 }) : /* @__PURE__ */ jsx(IconChevronRight, { size: 14 }) }) : props.item.rightSection,
1501
+ ...props.item.actionProps
1502
+ }), item.children && isOpen && /* @__PURE__ */ jsxs(Flex$1, {
1503
+ direction: "column",
1504
+ "data-parent-level": level,
1505
+ children: [/* @__PURE__ */ jsx(Flex$1, { style: {
1506
+ position: "absolute",
1507
+ width: 1,
1508
+ background: "linear-gradient(to bottom, transparent, var(--mantine-color-default-border), transparent)",
1509
+ top: 48,
1510
+ left: 20 + 32 * level,
1511
+ bottom: 16
1512
+ } }), item.children.filter((child) => !child.can || child.can()).map((child, index) => /* @__PURE__ */ jsx(SidebarItem, {
1513
+ item: child,
1514
+ level: level + 1,
1515
+ onItemClick: props.onItemClick,
1516
+ theme: props.theme
1517
+ }, index))]
1518
+ })]
1519
+ });
1520
+ };
1521
+ const SidebarCollapsedItem = (props) => {
1522
+ const { item, level } = props;
1523
+ const router = useRouter();
1524
+ const handleItemClick = () => {
1525
+ props.onItemClick?.(item);
1526
+ item.onClick?.();
1527
+ };
1528
+ const hasChildren = item.children && item.children.length > 0;
1529
+ const menu = hasChildren ? {
1530
+ on: "hover",
1531
+ position: "right",
1532
+ items: item.children.filter((child) => !child.can || child.can()).map((child) => ({
1533
+ label: child.label,
1534
+ icon: renderIcon(child.icon, ui.sizes.icon.sm),
1535
+ href: child.href,
1536
+ active: child.href ? router.isActive(child.href, { startWith: child.activeStartsWith }) : void 0
1537
+ }))
1538
+ } : void 0;
1539
+ return /* @__PURE__ */ jsx(ActionButton, {
1540
+ size: props.item.theme?.size ?? props.theme.button?.size ?? (level === 0 ? "sm" : "xs"),
1541
+ variant: "subtle",
1542
+ variantActive: "default",
1543
+ tooltip: hasChildren ? void 0 : {
1544
+ label: item.label,
1545
+ position: "right"
1546
+ },
1547
+ radius: props.item.theme?.radius ?? props.theme.button?.radius ?? "md",
1548
+ onClick: hasChildren ? void 0 : handleItemClick,
1549
+ icon: renderIcon(item.icon, ui.sizes.icon.sm) ?? /* @__PURE__ */ jsx(IconSquareRounded, { size: ui.sizes.icon.sm }),
1550
+ href: hasChildren ? void 0 : props.item.href,
1551
+ target: hasChildren ? void 0 : props.item.target,
1552
+ menu,
1553
+ ...props.item.actionProps
1554
+ });
1555
+ };
1556
+
1557
+ //#endregion
1558
+ //#region ../../src/core/components/layout/DashboardShell.tsx
1559
+ const DashboardShell = (props) => {
1560
+ const router = useRouter();
1561
+ const [sidebar, setSidebar] = useStore(alephaSidebarAtom);
1562
+ const collapsed = props.sidebarProps?.collapsed !== void 0 ? props.sidebarProps.collapsed : sidebar.collapsed;
1563
+ const { collapsedWidth, expandedWidth } = sidebar;
1564
+ const shouldShowSidebar = () => {
1565
+ if (props.noSidebarWhen?.paths) {
1566
+ for (const path of props.noSidebarWhen.paths) if (router.isActive(path, { startWith: true })) return false;
1567
+ }
1568
+ return true;
1569
+ };
1570
+ const [showSidebar, setShowSidebar] = useState(shouldShowSidebar());
1571
+ useEvents({
1572
+ "react:transition:end": () => {
1573
+ setShowSidebar(shouldShowSidebar());
1574
+ },
1575
+ "react:transition:begin": () => {
1576
+ setSidebar({
1577
+ ...sidebar,
1578
+ closed: true
1579
+ });
1580
+ }
1581
+ }, [sidebar]);
1582
+ const defaultAppBarItems = [{
1583
+ position: "left",
1584
+ type: "burger"
1585
+ }];
1586
+ const appBarProps = { ...props.appBarProps };
1587
+ appBarProps.container ??= props.container;
1588
+ const hasSidebar = showSidebar && props.sidebarProps !== void 0;
1589
+ const hasAppBar = props.appBarProps || props.header;
1590
+ let footerElement = props.footer;
1591
+ if (props.footerHeight) footerElement ??= /* @__PURE__ */ jsx(Flex$1, { h: props.footerHeight });
1592
+ const hHeight = props.headerHeight ?? 60;
1593
+ const fHeight = props.footerHeight ?? 24;
1594
+ const headerHeight = hasAppBar ? hHeight : 0;
1595
+ const footerHeight = footerElement ? fHeight : 0;
1596
+ return /* @__PURE__ */ jsxs(AppShell, {
1597
+ layout: "alt",
1598
+ w: "100%",
1599
+ flex: 1,
1600
+ header: hasAppBar ? { height: hHeight } : void 0,
1601
+ navbar: hasSidebar ? {
1602
+ width: { base: collapsed ? collapsedWidth : expandedWidth },
1603
+ breakpoint: "md",
1604
+ collapsed: { mobile: sidebar.closed }
1605
+ } : void 0,
1606
+ footer: footerElement ? { height: fHeight } : void 0,
1607
+ ...props.appShellProps,
1608
+ children: [
1609
+ hasAppBar && /* @__PURE__ */ jsx(AppShell.Header, {
1610
+ ...props.appShellHeaderProps,
1611
+ children: props.header ?? /* @__PURE__ */ jsx(AppBar, {
1612
+ items: defaultAppBarItems,
1613
+ ...appBarProps
1614
+ })
1615
+ }),
1616
+ hasSidebar && /* @__PURE__ */ jsxs(AppShell.Navbar, {
1617
+ className: "alepha-sidebar-navbar",
1618
+ ...props.appShellNavbarProps,
1619
+ children: [
1620
+ props.navbarHeader ? /* @__PURE__ */ jsx(Flex$1, {
1621
+ style: { borderBottom: "1px solid var(--mantine-color-default-border)" },
1622
+ h: headerHeight,
1623
+ children: props.navbarHeader
1624
+ }) : null,
1625
+ /* @__PURE__ */ jsx(Sidebar, {
1626
+ ...props.sidebarProps ?? {},
1627
+ collapsed
1628
+ }),
1629
+ props.navbarFooter ? /* @__PURE__ */ jsx(Flex$1, {
1630
+ style: { borderTop: "1px solid var(--mantine-color-default-border)" },
1631
+ h: footerHeight,
1632
+ children: props.navbarFooter
1633
+ }) : null
1634
+ ]
1635
+ }),
1636
+ /* @__PURE__ */ jsx(AppShell.Main, {
1637
+ pos: "relative",
1638
+ ...props.appShellMainProps,
1639
+ children: props.children ?? /* @__PURE__ */ jsx(NestedView, {})
1640
+ }),
1641
+ footerElement && /* @__PURE__ */ jsx(AppShell.Footer, {
1642
+ ...props.appShellFooterProps,
1643
+ children: footerElement
1644
+ })
1645
+ ]
1646
+ });
1647
+ };
1648
+
1649
+ //#endregion
1650
+ //#region ../../src/core/components/Text.tsx
1651
+ const INTENT_COLORS = {
1652
+ primary: "blue",
1653
+ info: "cyan",
1654
+ success: "green",
1655
+ warning: "yellow",
1656
+ danger: "red"
1657
+ };
1658
+ const Text$1 = forwardRef((props, ref) => {
1659
+ const { intent, bold, italic, light, muted, small, uppercase, capitalize, center, monospace, title, ...rest } = props;
1660
+ if (intent) rest.c ??= INTENT_COLORS[intent];
1661
+ if (bold) rest.fw ??= 700;
1662
+ if (light) rest.fw ??= 300;
1663
+ if (italic) rest.fs ??= "italic";
1664
+ if (muted) rest.c ??= "dimmed";
1665
+ if (small) rest.size ??= "sm";
1666
+ if (uppercase) rest.tt ??= "uppercase";
1667
+ if (capitalize) rest.tt ??= "capitalize";
1668
+ if (center) rest.ta ??= "center";
1669
+ if (monospace) rest.ff ??= "monospace";
1670
+ if (title) rest.size ??= "xl";
1671
+ return /* @__PURE__ */ jsx(Text, {
1672
+ ref,
1673
+ ...rest
1674
+ });
1675
+ });
1676
+ Text$1.displayName = "Text";
1677
+
1678
+ //#endregion
1679
+ //#region ../../src/core/form/utils/parseInput.ts
1680
+ const parseInput = (props, form) => {
1681
+ const disabled = false;
1682
+ const id = props.input.props.id;
1683
+ const label = props.title ?? ("title" in props.input.schema && typeof props.input.schema.title === "string" ? props.input.schema.title : void 0) ?? prettyName(props.input.path);
1684
+ const description = props.description ?? ("description" in props.input.schema && typeof props.input.schema.description === "string" ? props.input.schema.description : void 0);
1685
+ const error = form.error && form.error instanceof TypeBoxError ? form.error.value.message : void 0;
1686
+ const icon = !props.icon ? getDefaultIcon({
1687
+ type: props.input.schema && "type" in props.input.schema ? String(props.input.schema.type) : void 0,
1688
+ format: props.input.schema && "format" in props.input.schema && typeof props.input.schema.format === "string" ? props.input.schema.format : void 0,
1689
+ name: props.input.props.name,
1690
+ isEnum: props.input.schema && "enum" in props.input.schema && Boolean(props.input.schema.enum),
1691
+ isArray: props.input.schema && "type" in props.input.schema && props.input.schema.type === "array"
1692
+ }) : isValidElement(props.icon) ? props.icon : createElement(props.icon, { size: ui.sizes.icon.md });
1693
+ const format = props.input.schema && "format" in props.input.schema && typeof props.input.schema.format === "string" ? props.input.schema.format : void 0;
1694
+ const required = props.input.required;
1695
+ const schema = props.input.schema;
1696
+ const inputProps = {
1697
+ label,
1698
+ description,
1699
+ error,
1700
+ required,
1701
+ disabled
1702
+ };
1703
+ if ("minLength" in schema && typeof schema.minLength === "number") inputProps.minLength = schema.minLength;
1704
+ if ("maxLength" in schema && typeof schema.maxLength === "number") inputProps.maxLength = schema.maxLength;
1705
+ if ("minimum" in schema && typeof schema.minimum === "number") inputProps.minimum = schema.minimum;
1706
+ if ("maximum" in schema && typeof schema.maximum === "number") inputProps.maximum = schema.maximum;
1707
+ return {
1708
+ id,
1709
+ icon,
1710
+ format,
1711
+ schema: props.input.schema,
1712
+ inputProps
1713
+ };
1714
+ };
1715
+
1716
+ //#endregion
1717
+ //#region ../../src/core/form/components/ControlArray.tsx
1718
+ /**
1719
+ * Custom hook to sync array items with form state.
1720
+ * Uses form events as the source of truth, eliminating dual-state issues.
1721
+ */
1722
+ const useArrayItems = (input) => {
1723
+ const alepha = useAlepha();
1724
+ const keyCounter = useRef(0);
1725
+ const [items, setItemsState] = useState(() => {
1726
+ const defaultValue = input?.props?.defaultValue;
1727
+ if (Array.isArray(defaultValue)) return defaultValue.map((value) => ({
1728
+ key: keyCounter.current++,
1729
+ value
1730
+ }));
1731
+ return [];
1732
+ });
1733
+ const syncFromFormValue = useCallback((formValue) => {
1734
+ if (!Array.isArray(formValue)) {
1735
+ setItemsState([]);
1736
+ return;
1737
+ }
1738
+ setItemsState((prevItems) => {
1739
+ if (prevItems.length === formValue.length) {
1740
+ if (prevItems.every((item, i) => item.value === formValue[i])) return prevItems;
1741
+ }
1742
+ keyCounter.current = 0;
1743
+ return formValue.map((value) => ({
1744
+ key: keyCounter.current++,
1745
+ value
1746
+ }));
1747
+ });
1748
+ }, []);
1749
+ useEffect(() => {
1750
+ if (!input?.form) return;
1751
+ const formId = input.form.id;
1752
+ const fieldPath = input.path;
1753
+ const listeners = [alepha.events.on("form:reset", (event) => {
1754
+ if (event.id === formId) {
1755
+ const defaultValue = input.props?.defaultValue;
1756
+ keyCounter.current = 0;
1757
+ if (Array.isArray(defaultValue)) setItemsState(defaultValue.map((value) => ({
1758
+ key: keyCounter.current++,
1759
+ value
1760
+ })));
1761
+ else setItemsState([]);
1762
+ }
1763
+ }), alepha.events.on("form:change", (event) => {
1764
+ if (event.id === formId && event.path === fieldPath) syncFromFormValue(event.value);
1765
+ })];
1766
+ return () => {
1767
+ for (const unsub of listeners) unsub();
1768
+ };
1769
+ }, [
1770
+ alepha,
1771
+ input,
1772
+ syncFromFormValue
1773
+ ]);
1774
+ return {
1775
+ items,
1776
+ setItems: useCallback((newItems) => {
1777
+ setItemsState(newItems);
1778
+ input?.set(newItems.map((item) => item.value));
1779
+ }, [input]),
1780
+ nextKey: useCallback(() => keyCounter.current++, [])
1781
+ };
1782
+ };
1783
+ /**
1784
+ * Creates a proper InputField for an array item that integrates with the form system.
1785
+ * Uses array index for test IDs to ensure predictable, testable element identifiers.
1786
+ */
1787
+ const createArrayItemInput = (parentInput, itemSchema, index, _itemKey, value, onValueChange) => {
1788
+ return {
1789
+ schema: itemSchema,
1790
+ path: `${parentInput.path}/${index}`,
1791
+ required: false,
1792
+ form: parentInput.form,
1793
+ props: {
1794
+ id: `${parentInput.props.id}-${index}`,
1795
+ name: `${parentInput.props.name}[${index}]`,
1796
+ defaultValue: value
1797
+ },
1798
+ set: onValueChange
1799
+ };
1800
+ };
1801
+ /**
1802
+ * Creates a proper InputField for a nested object field within an array item.
1803
+ * Uses array index for test IDs to ensure predictable, testable element identifiers.
1804
+ */
1805
+ const createArrayItemFieldInput = (parentInput, itemSchema, fieldName, index, _itemKey, itemValue, onFieldChange) => {
1806
+ return {
1807
+ schema: itemSchema.properties[fieldName],
1808
+ path: `${parentInput.path}/${index}/${fieldName}`,
1809
+ required: itemSchema.required?.includes(fieldName) ?? false,
1810
+ form: parentInput.form,
1811
+ props: {
1812
+ id: `${parentInput.props.id}-${index}-${fieldName}`,
1813
+ name: `${parentInput.props.name}[${index}].${fieldName}`,
1814
+ defaultValue: itemValue?.[fieldName]
1815
+ },
1816
+ set: (value) => onFieldChange(fieldName, value)
1817
+ };
1818
+ };
1819
+ /**
1820
+ * ControlArray component for editing arrays of schema items.
1821
+ *
1822
+ * Features:
1823
+ * - Dynamic add/remove of items
1824
+ * - Supports arrays of objects with nested fields
1825
+ * - Supports arrays of primitives
1826
+ * - Grid layout for object items
1827
+ * - Min/max constraints
1828
+ * - Syncs with form state (handles external updates and resets)
1829
+ *
1830
+ * @example
1831
+ * ```tsx
1832
+ * // For a schema like:
1833
+ * // t.object({
1834
+ * // contacts: t.array(t.object({
1835
+ * // name: t.text(),
1836
+ * // email: t.text({ format: "email" }),
1837
+ * // }))
1838
+ * // })
1839
+ *
1840
+ * <ControlArray
1841
+ * input={form.input.contacts}
1842
+ * columns={2}
1843
+ * addLabel="Add contact"
1844
+ * controlProps={{
1845
+ * email: { text: { placeholder: "email@example.com" } }
1846
+ * }}
1847
+ * />
1848
+ * ```
1849
+ */
1850
+ const ControlArray = (props) => {
1851
+ const { inputProps } = parseInput(props, {});
1852
+ const { items, setItems, nextKey } = useArrayItems(props.input);
1853
+ if (!props.input?.props) return null;
1854
+ const schema = props.input.schema;
1855
+ if (!schema || !("items" in schema)) return null;
1856
+ const itemSchema = schema.items;
1857
+ const isObjectItem = itemSchema && "properties" in itemSchema;
1858
+ const { min = 0, max = Number.POSITIVE_INFINITY, columns = 1 } = props;
1859
+ const handleAdd = () => {
1860
+ if (items.length >= max) return;
1861
+ let newValue;
1862
+ if (isObjectItem) {
1863
+ newValue = {};
1864
+ const objSchema = itemSchema;
1865
+ for (const [key, propSchema] of Object.entries(objSchema.properties)) if ("default" in propSchema) newValue[key] = propSchema.default;
1866
+ } else newValue = "";
1867
+ setItems([...items, {
1868
+ key: nextKey(),
1869
+ value: newValue
1870
+ }]);
1871
+ };
1872
+ const handleRemove = (index) => {
1873
+ if (items.length <= min) return;
1874
+ setItems(items.filter((_, i) => i !== index));
1875
+ };
1876
+ const handleItemChange = (index, value) => {
1877
+ const newItems = [...items];
1878
+ newItems[index] = {
1879
+ ...newItems[index],
1880
+ value
1881
+ };
1882
+ setItems(newItems);
1883
+ };
1884
+ const handleFieldChange = (index, field, value) => {
1885
+ const newItems = [...items];
1886
+ newItems[index] = {
1887
+ ...newItems[index],
1888
+ value: {
1889
+ ...newItems[index].value,
1890
+ [field]: value
1891
+ }
1892
+ };
1893
+ setItems(newItems);
1894
+ };
1895
+ const colSpan = 12 / columns;
1896
+ const objectItemSchema = isObjectItem ? itemSchema : null;
1897
+ const fieldNames = objectItemSchema ? Object.keys(objectItemSchema.properties) : [];
1898
+ const renderItems = () => /* @__PURE__ */ jsxs(Flex, {
1899
+ direction: "column",
1900
+ gap: "sm",
1901
+ children: [items.map((item, index) => /* @__PURE__ */ jsxs(Flex, {
1902
+ gap: "sm",
1903
+ align: "flex-start",
1904
+ p: "xs",
1905
+ bg: ui.colors.surface,
1906
+ style: { borderRadius: "var(--mantine-radius-sm)" },
1907
+ children: [
1908
+ props.sortable && /* @__PURE__ */ jsx(ActionIcon, {
1909
+ variant: "subtle",
1910
+ color: "gray",
1911
+ style: { cursor: "grab" },
1912
+ children: /* @__PURE__ */ jsx(IconGripVertical, { size: 16 })
1913
+ }),
1914
+ objectItemSchema ? /* @__PURE__ */ jsx(Grid, {
1915
+ style: { flex: 1 },
1916
+ gutter: "sm",
1917
+ children: fieldNames.map((fieldName) => {
1918
+ const fieldControlProps = props.controlProps?.[fieldName] ?? {};
1919
+ const fieldInput = createArrayItemFieldInput(props.input, objectItemSchema, fieldName, index, item.key, item.value, (field, value) => handleFieldChange(index, field, value));
1920
+ return /* @__PURE__ */ jsx(Grid.Col, {
1921
+ span: colSpan,
1922
+ children: /* @__PURE__ */ jsx(Control, {
1923
+ input: fieldInput,
1924
+ ...fieldControlProps
1925
+ })
1926
+ }, fieldName);
1927
+ })
1928
+ }) : /* @__PURE__ */ jsx(Flex, {
1929
+ style: { flex: 1 },
1930
+ children: /* @__PURE__ */ jsx(Control, {
1931
+ input: createArrayItemInput(props.input, itemSchema, index, item.key, item.value, (value) => handleItemChange(index, value)),
1932
+ ...props.itemControlProps
1933
+ })
1934
+ }),
1935
+ /* @__PURE__ */ jsx(ActionIcon, {
1936
+ variant: "subtle",
1937
+ color: "red",
1938
+ onClick: () => handleRemove(index),
1939
+ disabled: items.length <= min,
1940
+ children: /* @__PURE__ */ jsx(IconTrash, { size: 16 })
1941
+ })
1942
+ ]
1943
+ }, item.key)), /* @__PURE__ */ jsxs(UnstyledButton, {
1944
+ onClick: handleAdd,
1945
+ disabled: items.length >= max,
1946
+ style: {
1947
+ display: "flex",
1948
+ alignItems: "center",
1949
+ justifyContent: "center",
1950
+ gap: 6,
1951
+ padding: "8px 12px",
1952
+ borderRadius: "var(--mantine-radius-sm)",
1953
+ border: "1px dashed var(--mantine-color-dimmed)",
1954
+ color: "var(--mantine-color-dimmed)",
1955
+ fontSize: "var(--mantine-font-size-sm)",
1956
+ cursor: items.length >= max ? "not-allowed" : "pointer",
1957
+ opacity: items.length >= max ? .5 : 1,
1958
+ transition: "all 150ms ease"
1959
+ },
1960
+ onMouseEnter: (e) => {
1961
+ if (items.length < max) {
1962
+ e.currentTarget.style.borderColor = "var(--mantine-color-blue-filled)";
1963
+ e.currentTarget.style.color = "var(--mantine-color-blue-filled)";
1964
+ e.currentTarget.style.background = "var(--mantine-color-blue-light)";
1965
+ }
1966
+ },
1967
+ onMouseLeave: (e) => {
1968
+ e.currentTarget.style.borderColor = "var(--mantine-color-dimmed)";
1969
+ e.currentTarget.style.color = "var(--mantine-color-dimmed)";
1970
+ e.currentTarget.style.background = "transparent";
1971
+ },
1972
+ children: [/* @__PURE__ */ jsx(IconPlus, { size: 14 }), props.addLabel ?? "Add"]
1973
+ })]
1974
+ });
1975
+ if (props.variant === "plain") return /* @__PURE__ */ jsxs(Flex, {
1976
+ direction: "column",
1977
+ gap: "xs",
1978
+ children: [
1979
+ inputProps.label && /* @__PURE__ */ jsx(Text, {
1980
+ size: "sm",
1981
+ fw: 500,
1982
+ children: inputProps.label
1983
+ }),
1984
+ inputProps.description && /* @__PURE__ */ jsx(Text, {
1985
+ size: "sm",
1986
+ c: "dimmed",
1987
+ children: inputProps.description
1988
+ }),
1989
+ renderItems(),
1990
+ inputProps.error && /* @__PURE__ */ jsx(Text, {
1991
+ size: "sm",
1992
+ c: "red",
1993
+ children: inputProps.error
1994
+ })
1995
+ ]
1996
+ });
1997
+ return /* @__PURE__ */ jsx(Fieldset, {
1998
+ legend: inputProps.label,
1999
+ children: /* @__PURE__ */ jsxs(Flex, {
2000
+ direction: "column",
2001
+ gap: "xs",
2002
+ children: [
2003
+ inputProps.description && /* @__PURE__ */ jsx(Text, {
2004
+ size: "sm",
2005
+ c: "dimmed",
2006
+ children: inputProps.description
2007
+ }),
2008
+ renderItems(),
2009
+ inputProps.error && /* @__PURE__ */ jsx(Text, {
2010
+ size: "sm",
2011
+ c: "red",
2012
+ children: inputProps.error
2013
+ })
2014
+ ]
2015
+ })
2016
+ });
2017
+ };
2018
+
2019
+ //#endregion
2020
+ //#region ../../src/core/form/components/ControlDate.tsx
2021
+ /**
2022
+ * ControlDate component for handling date, datetime, and time inputs.
2023
+ *
2024
+ * Features:
2025
+ * - DateInput for date format
2026
+ * - DateTimePicker for date-time format
2027
+ * - TimeInput for time format
2028
+ *
2029
+ * Automatically detects date formats from schema and renders appropriate picker.
2030
+ */
2031
+ const ControlDate = (props) => {
2032
+ const { inputProps, id, icon, format } = parseInput(props, useFormState(props.input));
2033
+ if (!props.input?.props) return null;
2034
+ if (props.datetime || format === "date-time") {
2035
+ const dateTimePickerProps = typeof props.datetime === "object" ? props.datetime : {};
2036
+ return /* @__PURE__ */ jsx(DateTimePicker, {
2037
+ ...inputProps,
2038
+ id,
2039
+ leftSection: icon,
2040
+ defaultValue: props.input.props.defaultValue ? new Date(props.input.props.defaultValue) : void 0,
2041
+ onChange: (value) => {
2042
+ props.input.set(value ? new Date(value).toISOString() : void 0);
2043
+ },
2044
+ ...dateTimePickerProps
2045
+ });
2046
+ }
2047
+ if (props.date || format === "date") {
2048
+ const dateInputProps = typeof props.date === "object" ? props.date : {};
2049
+ return /* @__PURE__ */ jsx(DateInput, {
2050
+ ...inputProps,
2051
+ id,
2052
+ leftSection: icon,
2053
+ defaultValue: props.input.props.defaultValue ? new Date(props.input.props.defaultValue) : void 0,
2054
+ onChange: (value) => {
2055
+ props.input.set(value ? new Date(value).toISOString().slice(0, 10) : void 0);
2056
+ },
2057
+ ...dateInputProps
2058
+ });
2059
+ }
2060
+ if (props.time || format === "time") {
2061
+ const timeInputProps = typeof props.time === "object" ? props.time : {};
2062
+ return /* @__PURE__ */ jsx(TimeInput, {
2063
+ ...inputProps,
2064
+ id,
2065
+ leftSection: icon,
2066
+ defaultValue: props.input.props.defaultValue,
2067
+ onChange: (event) => {
2068
+ props.input.set(event.currentTarget.value);
2069
+ },
2070
+ ...timeInputProps
2071
+ });
2072
+ }
2073
+ return null;
2074
+ };
2075
+
2076
+ //#endregion
2077
+ //#region ../../src/core/form/components/ControlNumber.tsx
2078
+ /**
2079
+ *
2080
+ */
2081
+ const ControlNumber = (props) => {
2082
+ const { inputProps, id, icon } = parseInput(props, useFormState(props.input));
2083
+ const ref = useRef(null);
2084
+ const [value, setValue] = useState(props.input.props.defaultValue);
2085
+ useEvents({ "form:reset": (event) => {
2086
+ if (event.id === props.input?.form.id && ref.current) setValue(props.input.props.defaultValue);
2087
+ } }, [props.input]);
2088
+ if (!props.input?.props) return null;
2089
+ const { type, ...inputPropsWithoutType } = props.input.props;
2090
+ if (props.sliderProps) {
2091
+ const min = props.sliderProps.min ?? inputProps.minimum ?? 0;
2092
+ const max = props.sliderProps.max ?? inputProps.maximum ?? 100;
2093
+ return /* @__PURE__ */ jsx(Input.Wrapper, {
2094
+ ...inputProps,
2095
+ children: /* @__PURE__ */ jsx("div", {
2096
+ style: {
2097
+ height: 32,
2098
+ padding: 8
2099
+ },
2100
+ children: /* @__PURE__ */ jsx(Slider, {
2101
+ ...inputProps,
2102
+ ref,
2103
+ id,
2104
+ ...inputPropsWithoutType,
2105
+ ...props.sliderProps,
2106
+ value,
2107
+ min,
2108
+ max,
2109
+ label: () => value,
2110
+ onChange: (val) => {
2111
+ setValue(val);
2112
+ props.input.set(val);
2113
+ }
2114
+ })
2115
+ })
2116
+ });
2117
+ }
2118
+ return /* @__PURE__ */ jsx(NumberInput, {
2119
+ ...inputProps,
2120
+ ref,
2121
+ id,
2122
+ leftSection: icon,
2123
+ ...inputPropsWithoutType,
2124
+ ...props.numberInputProps,
2125
+ value: value ?? "",
2126
+ onChange: (val) => {
2127
+ const newValue = val !== null ? Number(val) : void 0;
2128
+ setValue(newValue);
2129
+ props.input.set(newValue);
2130
+ }
2131
+ });
2132
+ };
2133
+
2134
+ //#endregion
2135
+ //#region ../../src/core/form/components/ControlObject.tsx
2136
+ /**
2137
+ * ControlObject component for editing nested object schemas.
2138
+ *
2139
+ * Features:
2140
+ * - Renders all properties of an object schema
2141
+ * - Supports grid layout with configurable columns
2142
+ * - Per-field customization via controlProps
2143
+ * - Recursive support for deeply nested objects
2144
+ *
2145
+ * The form system provides nested InputFields under the `.items` property.
2146
+ * For example: form.input.address.items.street
2147
+ *
2148
+ * @example
2149
+ * ```tsx
2150
+ * // For a schema like:
2151
+ * // t.object({
2152
+ * // address: t.object({
2153
+ * // street: t.text(),
2154
+ * // city: t.text(),
2155
+ * // zip: t.text(),
2156
+ * // })
2157
+ * // })
2158
+ *
2159
+ * <ControlObject
2160
+ * input={form.input.address}
2161
+ * columns={2}
2162
+ * controlProps={{
2163
+ * zip: { text: { maxLength: 10 } }
2164
+ * }}
2165
+ * />
2166
+ * ```
2167
+ */
2168
+ const ControlObject = (props) => {
2169
+ const { inputProps } = parseInput(props, {});
2170
+ if (!props.input?.props) return null;
2171
+ const schema = props.input.schema;
2172
+ if (!schema?.properties) return null;
2173
+ const fieldNames = Object.keys(schema.properties);
2174
+ const colSpan = 12 / (props.columns ?? 1);
2175
+ const nestedItems = props.input.items;
2176
+ const renderFields = () => /* @__PURE__ */ jsx(Grid, { children: fieldNames.map((fieldName) => {
2177
+ const fieldControlProps = props.controlProps?.[fieldName] ?? {};
2178
+ const field = nestedItems?.[fieldName];
2179
+ if (!field) return null;
2180
+ return /* @__PURE__ */ jsx(Grid.Col, {
2181
+ span: colSpan,
2182
+ children: /* @__PURE__ */ jsx(Control, {
2183
+ input: field,
2184
+ ...fieldControlProps
2185
+ })
2186
+ }, fieldName);
2187
+ }) });
2188
+ if (props.variant === "plain") return renderFields();
2189
+ return /* @__PURE__ */ jsx(Fieldset, {
2190
+ legend: inputProps.label,
2191
+ children: /* @__PURE__ */ jsxs(Flex, {
2192
+ direction: "column",
2193
+ gap: "xs",
2194
+ children: [
2195
+ inputProps.description && /* @__PURE__ */ jsx(Text, {
2196
+ size: "sm",
2197
+ c: "dimmed",
2198
+ children: inputProps.description
2199
+ }),
2200
+ renderFields(),
2201
+ inputProps.error && /* @__PURE__ */ jsx(Text, {
2202
+ size: "sm",
2203
+ c: "red",
2204
+ children: inputProps.error
2205
+ })
2206
+ ]
2207
+ })
2208
+ });
2209
+ };
2210
+
2211
+ //#endregion
2212
+ //#region ../../src/core/form/components/ControlQueryBuilder.tsx
2213
+ /**
2214
+ * Query builder with text input and help popover.
2215
+ * Generates query strings for parseQueryString syntax.
2216
+ */
2217
+ const ControlQueryBuilder = ({ schema, value = "", onChange, placeholder = "Enter query or click for assistance...", ...textInputProps }) => {
2218
+ const [helpOpened, setHelpOpened] = useState(false);
2219
+ const [textValue, setTextValue] = useState(value);
2220
+ const inputRef = useRef(null);
2221
+ const fields = schema ? extractSchemaFields(schema) : [];
2222
+ const [error, setError] = useState(null);
2223
+ const isValid = (value) => {
2224
+ try {
2225
+ parseQueryString(value.trim());
2226
+ } catch (e) {
2227
+ setError(e.message);
2228
+ return false;
2229
+ }
2230
+ setError(null);
2231
+ return true;
2232
+ };
2233
+ const handleTextChange = (newValue) => {
2234
+ setTextValue(newValue);
2235
+ if (isValid(newValue)) onChange?.(newValue);
2236
+ };
2237
+ const handleClear = () => {
2238
+ setTextValue("");
2239
+ onChange?.("");
2240
+ isValid("");
2241
+ };
2242
+ const handleInsert = (text) => {
2243
+ const newValue = textValue ? `${textValue}${text} ` : `${text} `;
2244
+ setTextValue(newValue);
2245
+ if (isValid(newValue)) onChange?.(newValue);
2246
+ setTimeout(() => {
2247
+ inputRef.current?.focus();
2248
+ const length = inputRef.current?.value.length || 0;
2249
+ inputRef.current?.setSelectionRange(length, length);
2250
+ }, 0);
2251
+ };
2252
+ useEvents({ "form:change": (event) => {
2253
+ if (event.id === inputRef.current?.form?.id) {
2254
+ if (event.path === textInputProps["data-path"]) setTextValue(event.value ?? "");
2255
+ }
2256
+ } }, []);
2257
+ return /* @__PURE__ */ jsxs(Popover, {
2258
+ width: 800,
2259
+ position: "bottom-start",
2260
+ shadow: "md",
2261
+ opened: helpOpened,
2262
+ onChange: setHelpOpened,
2263
+ closeOnClickOutside: true,
2264
+ closeOnEscape: true,
2265
+ transitionProps: {
2266
+ transition: "fade-up",
2267
+ duration: 200,
2268
+ timingFunction: "ease"
2269
+ },
2270
+ children: [/* @__PURE__ */ jsx(Popover.Target, { children: /* @__PURE__ */ jsx(TextInput, {
2271
+ ref: inputRef,
2272
+ placeholder,
2273
+ value: textValue,
2274
+ onChange: (e) => handleTextChange(e.currentTarget.value),
2275
+ onFocus: () => setHelpOpened(true),
2276
+ leftSection: error ? /* @__PURE__ */ jsx(IconInfoTriangle, { size: 16 }) : /* @__PURE__ */ jsx(IconFilter, { size: 16 }),
2277
+ rightSection: textValue && /* @__PURE__ */ jsx(ActionIcon, {
2278
+ size: "sm",
2279
+ variant: "subtle",
2280
+ color: "gray",
2281
+ onClick: handleClear,
2282
+ children: /* @__PURE__ */ jsx(IconX, { size: 14 })
2283
+ }),
2284
+ ...textInputProps
2285
+ }) }), /* @__PURE__ */ jsx(Popover.Dropdown, {
2286
+ bg: "transparent",
2287
+ p: "xs",
2288
+ bd: `1px solid ${ui.colors.border}`,
2289
+ style: { backdropFilter: "blur(20px)" },
2290
+ children: /* @__PURE__ */ jsx(QueryHelp, {
2291
+ fields,
2292
+ onInsert: handleInsert
2293
+ })
2294
+ })]
2295
+ });
2296
+ };
2297
+ function QueryHelp({ fields, onInsert }) {
2298
+ return /* @__PURE__ */ jsxs(Flex, {
2299
+ gap: "md",
2300
+ align: "flex-start",
2301
+ wrap: "nowrap",
2302
+ bg: ui.colors.surface,
2303
+ p: "sm",
2304
+ bdrs: "sm",
2305
+ children: [
2306
+ /* @__PURE__ */ jsxs(Flex, {
2307
+ direction: "column",
2308
+ gap: "md",
2309
+ style: { flex: 1 },
2310
+ children: [
2311
+ /* @__PURE__ */ jsxs(Flex, {
2312
+ direction: "column",
2313
+ gap: "xs",
2314
+ children: [/* @__PURE__ */ jsx(Text, {
2315
+ size: "sm",
2316
+ fw: 600,
2317
+ children: "Operators"
2318
+ }), /* @__PURE__ */ jsx(Flex, {
2319
+ direction: "column",
2320
+ gap: 4,
2321
+ children: Object.entries(OPERATOR_INFO).map(([key, info]) => /* @__PURE__ */ jsxs(Flex, {
2322
+ gap: "xs",
2323
+ wrap: "nowrap",
2324
+ children: [/* @__PURE__ */ jsx(ActionButton, {
2325
+ px: "xs",
2326
+ size: "xs",
2327
+ h: 24,
2328
+ variant: "default",
2329
+ justify: "center",
2330
+ miw: 48,
2331
+ onClick: () => onInsert(info.symbol),
2332
+ children: info.symbol
2333
+ }), /* @__PURE__ */ jsx(Text, {
2334
+ size: "xs",
2335
+ c: "dimmed",
2336
+ style: { flex: 1 },
2337
+ children: info.label
2338
+ })]
2339
+ }, key))
2340
+ })]
2341
+ }),
2342
+ /* @__PURE__ */ jsx(Divider, {}),
2343
+ /* @__PURE__ */ jsxs(Flex, {
2344
+ direction: "column",
2345
+ gap: "xs",
2346
+ children: [/* @__PURE__ */ jsx(Text, {
2347
+ size: "sm",
2348
+ fw: 600,
2349
+ children: "Logic"
2350
+ }), /* @__PURE__ */ jsxs(Flex, {
2351
+ direction: "column",
2352
+ gap: 4,
2353
+ children: [/* @__PURE__ */ jsxs(Flex, {
2354
+ gap: "xs",
2355
+ wrap: "nowrap",
2356
+ children: [/* @__PURE__ */ jsx(ActionButton, {
2357
+ px: "xs",
2358
+ size: "xs",
2359
+ h: 24,
2360
+ variant: "default",
2361
+ justify: "center",
2362
+ miw: 48,
2363
+ onClick: () => onInsert("&"),
2364
+ children: "&"
2365
+ }), /* @__PURE__ */ jsx(Text, {
2366
+ size: "xs",
2367
+ c: "dimmed",
2368
+ children: "AND"
2369
+ })]
2370
+ }), /* @__PURE__ */ jsxs(Flex, {
2371
+ gap: "xs",
2372
+ wrap: "nowrap",
2373
+ children: [/* @__PURE__ */ jsx(ActionButton, {
2374
+ px: "xs",
2375
+ size: "xs",
2376
+ h: 24,
2377
+ variant: "default",
2378
+ justify: "center",
2379
+ miw: 48,
2380
+ onClick: () => onInsert("|"),
2381
+ children: "|"
2382
+ }), /* @__PURE__ */ jsx(Text, {
2383
+ size: "xs",
2384
+ c: "dimmed",
2385
+ children: "OR"
2386
+ })]
2387
+ })]
2388
+ })]
2389
+ })
2390
+ ]
2391
+ }),
2392
+ fields.length > 0 && /* @__PURE__ */ jsx(Divider, { orientation: "vertical" }),
2393
+ fields.length > 0 && /* @__PURE__ */ jsxs(Flex, {
2394
+ direction: "column",
2395
+ gap: "xs",
2396
+ style: { flex: 2 },
2397
+ children: [/* @__PURE__ */ jsx(Text, {
2398
+ size: "sm",
2399
+ fw: 600,
2400
+ children: "Fields"
2401
+ }), /* @__PURE__ */ jsx(Flex, {
2402
+ direction: "column",
2403
+ gap: 4,
2404
+ style: {
2405
+ maxHeight: 300,
2406
+ overflowY: "auto"
2407
+ },
2408
+ children: fields.map((field) => /* @__PURE__ */ jsxs(Flex, {
2409
+ gap: "xs",
2410
+ wrap: "nowrap",
2411
+ align: "flex-start",
2412
+ children: [
2413
+ /* @__PURE__ */ jsx(ActionButton, {
2414
+ px: "xs",
2415
+ size: "xs",
2416
+ h: 24,
2417
+ variant: "default",
2418
+ justify: "end",
2419
+ miw: 120,
2420
+ onClick: () => onInsert(field.path),
2421
+ children: field.path
2422
+ }),
2423
+ /* @__PURE__ */ jsxs(Flex, {
2424
+ mt: 3,
2425
+ direction: "column",
2426
+ gap: 2,
2427
+ style: {
2428
+ flex: 1,
2429
+ minWidth: 0
2430
+ },
2431
+ children: [/* @__PURE__ */ jsx(Text, {
2432
+ size: "xs",
2433
+ c: "dimmed",
2434
+ lineClamp: 1,
2435
+ children: field.description || field.type
2436
+ }), field.enum && /* @__PURE__ */ jsx(Flex, {
2437
+ gap: 0,
2438
+ wrap: "wrap",
2439
+ children: field.enum.map((enumValue) => /* @__PURE__ */ jsx(ActionButton, {
2440
+ px: "xs",
2441
+ size: "xs",
2442
+ h: 24,
2443
+ onClick: () => onInsert(enumValue),
2444
+ children: enumValue
2445
+ }, enumValue))
2446
+ })]
2447
+ }),
2448
+ /* @__PURE__ */ jsx(Badge, {
2449
+ size: "xs",
2450
+ variant: "light",
2451
+ style: { flexShrink: 0 },
2452
+ children: field.type
2453
+ })
2454
+ ]
2455
+ }, field.path))
2456
+ })]
2457
+ })
2458
+ ]
2459
+ });
2460
+ }
2461
+
2462
+ //#endregion
2463
+ //#region ../../src/core/form/components/ControlSelect.tsx
2464
+ /**
2465
+ * ControlSelect component for handling Select, MultiSelect, and TagsInput.
2466
+ *
2467
+ * Features:
2468
+ * - Basic Select with enum support
2469
+ * - MultiSelect for array of enums
2470
+ * - TagsInput for array of strings (no enum)
2471
+ * - Future: Lazy loading
2472
+ * - Future: Searchable/filterable options
2473
+ * - Future: Custom option rendering
2474
+ *
2475
+ * Automatically detects enum values and array types from schema.
2476
+ */
2477
+ const ControlSelect = (props) => {
2478
+ const { inputProps, id, icon } = parseInput(props, useFormState(props.input));
2479
+ const isArray = props.input.schema && "type" in props.input.schema && props.input.schema.type === "array";
2480
+ let itemsEnum;
2481
+ if (isArray && "items" in props.input.schema && props.input.schema.items) {
2482
+ const items = props.input.schema.items;
2483
+ if ("enum" in items && Array.isArray(items.enum)) itemsEnum = items.enum;
2484
+ }
2485
+ const enumValues = props.input.schema && "enum" in props.input.schema && Array.isArray(props.input.schema.enum) ? props.input.schema.enum : [];
2486
+ const [data, setData] = useState([]);
2487
+ useEffect(() => {
2488
+ if (!props.input?.props) return;
2489
+ if (props.loader) props.loader().then(setData);
2490
+ else setData(enumValues);
2491
+ }, [props.input, props.loader]);
2492
+ if (!props.input?.props) return null;
2493
+ if (props.segmented) {
2494
+ const segmentedControlProps = typeof props.segmented === "object" ? props.segmented : {};
2495
+ return /* @__PURE__ */ jsx(Input.Wrapper, {
2496
+ ...inputProps,
2497
+ children: /* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(SegmentedControl, {
2498
+ disabled: inputProps.disabled,
2499
+ defaultValue: String(props.input.props.defaultValue),
2500
+ ...segmentedControlProps,
2501
+ onChange: (value) => {
2502
+ props.input.set(value);
2503
+ },
2504
+ data: data.slice(0, 10)
2505
+ }) })
2506
+ });
2507
+ }
2508
+ if (props.autocomplete) {
2509
+ const autocompleteProps = typeof props.autocomplete === "object" ? props.autocomplete : {};
2510
+ return /* @__PURE__ */ jsx(Autocomplete, {
2511
+ ...inputProps,
2512
+ size: props.size,
2513
+ id,
2514
+ leftSection: icon,
2515
+ data,
2516
+ ...props.input.props,
2517
+ ...autocompleteProps
2518
+ });
2519
+ }
2520
+ if (isArray && !itemsEnum || props.tags) {
2521
+ const tagsInputProps = typeof props.tags === "object" ? props.tags : {};
2522
+ return /* @__PURE__ */ jsx(TagsInput, {
2523
+ ...inputProps,
2524
+ size: props.size,
2525
+ id,
2526
+ leftSection: icon,
2527
+ defaultValue: Array.isArray(props.input.props.defaultValue) ? props.input.props.defaultValue : [],
2528
+ onChange: (value) => {
2529
+ props.input.set(value);
2530
+ },
2531
+ ...tagsInputProps
2532
+ });
2533
+ }
2534
+ if (isArray && itemsEnum || props.multi) {
2535
+ const data = itemsEnum?.map((value) => ({
2536
+ value,
2537
+ label: value
2538
+ })) || [];
2539
+ const multiSelectProps = typeof props.multi === "object" ? props.multi : {};
2540
+ return /* @__PURE__ */ jsx(MultiSelect, {
2541
+ ...inputProps,
2542
+ size: props.size,
2543
+ id,
2544
+ leftSection: icon,
2545
+ data,
2546
+ defaultValue: Array.isArray(props.input.props.defaultValue) ? props.input.props.defaultValue : [],
2547
+ onChange: (value) => {
2548
+ props.input.set(value);
2549
+ },
2550
+ ...multiSelectProps
2551
+ });
2552
+ }
2553
+ const selectProps = typeof props.select === "object" ? props.select : {};
2554
+ return /* @__PURE__ */ jsx(Select, {
2555
+ ...inputProps,
2556
+ size: props.size,
2557
+ id,
2558
+ leftSection: icon,
2559
+ rightSection: null,
2560
+ data,
2561
+ ...props.input.props,
2562
+ ...selectProps
2563
+ });
2564
+ };
2565
+
2566
+ //#endregion
2567
+ //#region ../../src/core/form/components/Control.tsx
2568
+ /**
2569
+ * Generic form control that renders the appropriate input based on the schema and props.
2570
+ *
2571
+ * Supports:
2572
+ * - TextInput (with format detection: email, url, tel)
2573
+ * - Textarea
2574
+ * - NumberInput (for number/integer types)
2575
+ * - FileInput
2576
+ * - ColorInput (for color format)
2577
+ * - Select (for enum types)
2578
+ * - Autocomplete
2579
+ * - PasswordInput
2580
+ * - Switch (for boolean types)
2581
+ * - SegmentedControl (for enum types)
2582
+ * - DateInput (for date format)
2583
+ * - DateTimePicker (for date-time format)
2584
+ * - TimeInput (for time format)
2585
+ * - QueryBuilder (for building type-safe queries with autocomplete)
2586
+ * - ControlObject (for nested object schemas)
2587
+ * - ControlArray (for arrays of objects)
2588
+ * - Custom component
2589
+ *
2590
+ * Automatically handles labels, descriptions, error messages, required state, and default icons.
2591
+ */
2592
+ const Control = (_props) => {
2593
+ const form = useFormState(_props.input, ["error"]);
2594
+ if (!_props.input?.props) return null;
2595
+ const { inputProps, id, icon, format, schema } = parseInput(_props, form);
2596
+ const props = {
2597
+ ..._props,
2598
+ ...schema.$control
2599
+ };
2600
+ if (props.query) return /* @__PURE__ */ jsx(ControlQueryBuilder, {
2601
+ ...props.input.props,
2602
+ ...inputProps,
2603
+ schema: props.query,
2604
+ value: props.input.props.value,
2605
+ onChange: (value) => {
2606
+ props.input.set(value);
2607
+ }
2608
+ });
2609
+ if (props.custom) {
2610
+ const Custom = props.custom;
2611
+ return /* @__PURE__ */ jsx(Input.Wrapper, {
2612
+ ...inputProps,
2613
+ children: /* @__PURE__ */ jsx(Flex, {
2614
+ flex: 1,
2615
+ mt: "calc(var(--mantine-spacing-xs) / 2)",
2616
+ children: /* @__PURE__ */ jsx(Custom, {
2617
+ defaultValue: props.input.props.defaultValue,
2618
+ onChange: (value) => {
2619
+ props.input.set(value);
2620
+ }
2621
+ })
2622
+ })
2623
+ });
2624
+ }
2625
+ const isObject = props.input.schema && "type" in props.input.schema && props.input.schema.type === "object" && "properties" in props.input.schema;
2626
+ if (props.object || isObject) {
2627
+ const controlObjectProps = typeof props.object === "object" ? props.object : {};
2628
+ return /* @__PURE__ */ jsx(ControlObject, {
2629
+ input: props.input,
2630
+ title: props.title,
2631
+ description: props.description,
2632
+ ...controlObjectProps
2633
+ });
2634
+ }
2635
+ const isArray = props.input.schema && "type" in props.input.schema && props.input.schema.type === "array";
2636
+ const isArrayOfObjects = isArray && "items" in props.input.schema && props.input.schema.items && typeof props.input.schema.items === "object" && "properties" in props.input.schema.items;
2637
+ if (props.array || isArrayOfObjects) {
2638
+ const controlArrayProps = typeof props.array === "object" ? props.array : {};
2639
+ return /* @__PURE__ */ jsx(ControlArray, {
2640
+ input: props.input,
2641
+ title: props.title,
2642
+ description: props.description,
2643
+ ...controlArrayProps
2644
+ });
2645
+ }
2646
+ if (props.number || props.input.schema && "type" in props.input.schema && (props.input.schema.type === "number" || props.input.schema.type === "integer")) {
2647
+ const controlNumberProps = typeof props.number === "object" ? props.number : {};
2648
+ if (props.slider) controlNumberProps.sliderProps ??= {};
2649
+ return /* @__PURE__ */ jsx(ControlNumber, {
2650
+ size: props.size,
2651
+ input: props.input,
2652
+ title: props.title,
2653
+ description: props.description,
2654
+ icon,
2655
+ ...controlNumberProps
2656
+ });
2657
+ }
2658
+ if (props.file) {
2659
+ const fileInputProps = typeof props.file === "object" ? props.file : {};
2660
+ return /* @__PURE__ */ jsx(FileInput, {
2661
+ ...inputProps,
2662
+ size: props.size,
2663
+ id,
2664
+ leftSection: icon,
2665
+ onChange: (file) => {
2666
+ props.input.set(file);
2667
+ },
2668
+ ...fileInputProps
2669
+ });
2670
+ }
2671
+ if (props.color || format === "color") {
2672
+ const colorInputProps = typeof props.color === "object" ? props.color : {};
2673
+ return /* @__PURE__ */ jsx(ColorInput, {
2674
+ ...inputProps,
2675
+ size: props.size,
2676
+ id,
2677
+ leftSection: icon,
2678
+ ...props.input.props,
2679
+ ...colorInputProps
2680
+ });
2681
+ }
2682
+ if (props.input.schema && "enum" in props.input.schema && props.input.schema.enum || isArray && !isArrayOfObjects || props.select) {
2683
+ const opts = typeof props.select === "object" ? props.select : {};
2684
+ if (props.segmented) opts.segmented ??= {};
2685
+ return /* @__PURE__ */ jsx(ControlSelect, {
2686
+ size: props.size,
2687
+ input: props.input,
2688
+ title: props.title,
2689
+ description: props.description,
2690
+ icon,
2691
+ ...opts
2692
+ });
2693
+ }
2694
+ if (props.input.schema && "type" in props.input.schema && props.input.schema.type === "boolean") {
2695
+ if (props.switch) {
2696
+ const switchProps = typeof props.switch === "object" ? props.switch : {};
2697
+ return /* @__PURE__ */ jsx(Switch, {
2698
+ ...inputProps,
2699
+ size: props.size,
2700
+ id,
2701
+ color: "blue",
2702
+ defaultChecked: props.input.props.defaultValue,
2703
+ onChange: (event) => {
2704
+ props.input.set(event.currentTarget.checked);
2705
+ },
2706
+ ...switchProps
2707
+ });
2708
+ }
2709
+ const opts = {
2710
+ input: props.input,
2711
+ select: { data: [{
2712
+ value: "true",
2713
+ label: "Yes"
2714
+ }, {
2715
+ value: "false",
2716
+ label: "No"
2717
+ }] }
2718
+ };
2719
+ return /* @__PURE__ */ jsx(ControlSelect, {
2720
+ size: props.size,
2721
+ title: props.title,
2722
+ description: props.description,
2723
+ icon,
2724
+ ...opts
2725
+ });
2726
+ }
2727
+ if (props.password || props.input.props.name?.includes("password")) {
2728
+ const passwordInputProps = typeof props.password === "object" ? props.password : {};
2729
+ return /* @__PURE__ */ jsx(PasswordInput, {
2730
+ ...inputProps,
2731
+ size: props.size,
2732
+ id,
2733
+ leftSection: icon,
2734
+ ...props.input.props,
2735
+ ...passwordInputProps
2736
+ });
2737
+ }
2738
+ if (props.area) {
2739
+ const textAreaProps = typeof props.area === "object" ? props.area : {};
2740
+ return /* @__PURE__ */ jsx(Textarea, {
2741
+ ...inputProps,
2742
+ size: props.size,
2743
+ id,
2744
+ leftSection: icon,
2745
+ ...props.input.props,
2746
+ ...textAreaProps
2747
+ });
2748
+ }
2749
+ if (props.date || props.datetime || props.time || format === "date" || format === "date-time" || format === "time") return /* @__PURE__ */ jsx(ControlDate, {
2750
+ size: props.size,
2751
+ input: props.input,
2752
+ title: props.title,
2753
+ description: props.description,
2754
+ icon,
2755
+ date: props.date,
2756
+ datetime: props.datetime,
2757
+ time: props.time
2758
+ });
2759
+ const textInputProps = typeof props.text === "object" ? props.text : {};
2760
+ const getInputType = () => {
2761
+ switch (format) {
2762
+ case "email": return "email";
2763
+ case "url":
2764
+ case "uri": return "url";
2765
+ case "tel":
2766
+ case "phone": return "tel";
2767
+ default: return;
2768
+ }
2769
+ };
2770
+ return /* @__PURE__ */ jsx(TextInput, {
2771
+ ...inputProps,
2772
+ size: props.size,
2773
+ id,
2774
+ leftSection: icon,
2775
+ type: getInputType(),
2776
+ ...props.input.props,
2777
+ ...textInputProps,
2778
+ inputWrapperOrder: [
2779
+ "label",
2780
+ "input",
2781
+ "description",
2782
+ "error"
2783
+ ]
2784
+ });
2785
+ };
2786
+
2787
+ //#endregion
2788
+ //#region ../../src/core/form/components/TypeForm.tsx
2789
+ /**
2790
+ * TypeForm component that automatically renders all form inputs based on schema.
2791
+ * Uses the Control component to render individual fields and Mantine Grid for responsive layout.
2792
+ *
2793
+ * Supports all field types including:
2794
+ * - Primitive types (string, number, boolean, etc.)
2795
+ * - Enum types (rendered as Select)
2796
+ * - Arrays of primitives (rendered as MultiSelect/TagsInput)
2797
+ * - Arrays of objects (rendered as ControlArray)
2798
+ * - Nested objects (rendered as ControlObject)
2799
+ *
2800
+ * @example
2801
+ * ```tsx
2802
+ * import { t } from "alepha";
2803
+ * import { useForm } from "alepha/react/form";
2804
+ * import { TypeForm } from "@alepha/ui";
2805
+ *
2806
+ * const form = useForm({
2807
+ * schema: t.object({
2808
+ * username: t.text(),
2809
+ * email: t.text(),
2810
+ * age: t.integer(),
2811
+ * subscribe: t.boolean(),
2812
+ * address: t.object({
2813
+ * street: t.text(),
2814
+ * city: t.text(),
2815
+ * }),
2816
+ * tags: t.array(t.text()),
2817
+ * contacts: t.array(t.object({
2818
+ * name: t.text(),
2819
+ * email: t.text(),
2820
+ * })),
2821
+ * }),
2822
+ * handler: (values) => {
2823
+ * console.log(values);
2824
+ * },
2825
+ * });
2826
+ *
2827
+ * return <TypeForm form={form} columns={2} />;
2828
+ * ```
2829
+ */
2830
+ const TypeForm = (props) => {
2831
+ const { form, columns = 3, children, controlProps, fieldControlProps, skipFormElement = false, skipSubmitButton = false, submitButtonProps, fill = true, size } = props;
2832
+ const schema = props.schema || form.options.schema;
2833
+ if (!schema?.properties) return null;
2834
+ const supportedFields = Object.keys(schema.properties);
2835
+ const colSpan = typeof columns === "number" ? {
2836
+ xs: 12,
2837
+ sm: 6,
2838
+ lg: 12 / columns
2839
+ } : {
2840
+ base: columns.base ? 12 / columns.base : void 0,
2841
+ xs: columns.xs ? 12 / columns.xs : 12,
2842
+ sm: columns.sm ? 12 / columns.sm : 6,
2843
+ md: columns.md ? 12 / columns.md : void 0,
2844
+ lg: columns.lg ? 12 / columns.lg : 4,
2845
+ xl: columns.xl ? 12 / columns.xl : void 0
2846
+ };
2847
+ const renderFields = () => {
2848
+ if (children) return /* @__PURE__ */ jsx(Fragment, { children: children(form.input) });
2849
+ return /* @__PURE__ */ jsx(Grid, { children: supportedFields.map((fieldName) => {
2850
+ const field = form.input[fieldName];
2851
+ const fieldSchema = schema.properties[fieldName];
2852
+ if (!field || !fieldSchema) return null;
2853
+ const isObject = fieldSchema && "type" in fieldSchema && fieldSchema.type === "object";
2854
+ const isArrayOfObjects = fieldSchema && "type" in fieldSchema && fieldSchema.type === "array" && "items" in fieldSchema && fieldSchema.items && "properties" in fieldSchema.items;
2855
+ const span = isObject || isArrayOfObjects ? 12 : colSpan;
2856
+ const mergedControlProps = {
2857
+ ...controlProps,
2858
+ ...fieldControlProps?.[fieldName]
2859
+ };
2860
+ if (size) mergedControlProps.size = size;
2861
+ return /* @__PURE__ */ jsx(Grid.Col, {
2862
+ span,
2863
+ children: /* @__PURE__ */ jsx(Control, {
2864
+ input: field,
2865
+ ...mergedControlProps
2866
+ })
2867
+ }, fieldName);
2868
+ }) });
2869
+ };
2870
+ const content = /* @__PURE__ */ jsxs(Flex, {
2871
+ direction: "column",
2872
+ gap: "sm",
2873
+ flex: fill ? 1 : void 0,
2874
+ ...props.flexProps,
2875
+ children: [/* @__PURE__ */ jsx(Flex, {
2876
+ direction: "column",
2877
+ gap: "sm",
2878
+ flex: 1,
2879
+ children: renderFields()
2880
+ }), !skipSubmitButton && /* @__PURE__ */ jsx(Card, {
2881
+ w: "100%",
2882
+ withBorder: true,
2883
+ children: /* @__PURE__ */ jsxs(Flex, {
2884
+ gap: "sm",
2885
+ flex: 1,
2886
+ children: [
2887
+ /* @__PURE__ */ jsx(Flex, {}),
2888
+ /* @__PURE__ */ jsx(Flex, { flex: 1 }),
2889
+ /* @__PURE__ */ jsxs(Flex, {
2890
+ gap: "sm",
2891
+ children: [/* @__PURE__ */ jsx(ActionButton, {
2892
+ variant: "subtle",
2893
+ type: "reset",
2894
+ children: "Reset"
2895
+ }), /* @__PURE__ */ jsx(ActionButton, {
2896
+ intent: "primary",
2897
+ form,
2898
+ ...submitButtonProps,
2899
+ children: submitButtonProps?.children ?? "Submit"
2900
+ })]
2901
+ })
2902
+ ]
2903
+ })
2904
+ })]
2905
+ });
2906
+ if (skipFormElement) return content;
2907
+ return /* @__PURE__ */ jsx(Flex, {
2908
+ component: "form",
2909
+ flex: fill ? 1 : void 0,
2910
+ ...form.props,
2911
+ ...props.flexProps,
2912
+ children: content
2913
+ });
2914
+ };
2915
+
2916
+ //#endregion
2917
+ //#region ../../src/core/helpers/renderIcon.tsx
2918
+ const renderIcon = (icon, size) => {
2919
+ if (!icon) return null;
2920
+ if (isValidElement(icon)) return icon;
2921
+ if (isComponentType(icon)) return /* @__PURE__ */ jsx(icon, { size: size ?? ui.sizes.icon.md });
2922
+ return icon;
2923
+ };
2924
+
2925
+ //#endregion
2926
+ //#region ../../src/core/hooks/useDialog.ts
2927
+ /**
2928
+ * Use this hook to access the Dialog Service for showing various dialog types.
2929
+ *
2930
+ * @example
2931
+ * ```tsx
2932
+ * const dialog = useDialog();
2933
+ * await dialog.alert({ title: "Alert", message: "This is an alert message" });
2934
+ * const confirmed = await dialog.confirm({ title: "Confirm", message: "Are you sure?" });
2935
+ * const input = await dialog.prompt({ title: "Input", message: "Enter your name:" });
2936
+ * ```
2937
+ */
2938
+ const useDialog = () => {
2939
+ return useInject(DialogService);
2940
+ };
2941
+
2942
+ //#endregion
2943
+ //#region ../../src/core/table/interfaces/types.ts
2944
+ const DEFAULT_MAX_VISIBLE_COLUMNS = 8;
2945
+
2946
+ //#endregion
2947
+ //#region ../../src/core/table/components/DataTableFilters.tsx
2948
+ const DataTableFilters = ({ schema, form, typeFormProps, filterVisibility }) => {
2949
+ const visibleSchema = useMemo(() => {
2950
+ const visibleKeys = Object.keys(schema.properties).filter((key) => filterVisibility[key] !== false);
2951
+ if (visibleKeys.length === 0) return null;
2952
+ const visibleProps = visibleKeys.reduce((acc, key) => {
2953
+ acc[key] = schema.properties[key];
2954
+ return acc;
2955
+ }, {});
2956
+ return t.object(visibleProps);
2957
+ }, [schema, filterVisibility]);
2958
+ if (!visibleSchema) return null;
2959
+ return /* @__PURE__ */ jsx(Flex, {
2960
+ w: "100%",
2961
+ p: "xs",
2962
+ bg: ui.colors.surface,
2963
+ style: { borderBottom: "1px solid var(--alepha-border)" },
2964
+ children: /* @__PURE__ */ jsx(TypeForm, {
2965
+ size: "xs",
2966
+ ...typeFormProps,
2967
+ skipSubmitButton: true,
2968
+ fill: true,
2969
+ form,
2970
+ schema: visibleSchema,
2971
+ columns: {
2972
+ base: 1,
2973
+ sm: 2,
2974
+ md: 3,
2975
+ lg: 4,
2976
+ xl: 6
2977
+ }
2978
+ })
2979
+ });
2980
+ };
2981
+
2982
+ //#endregion
2983
+ //#region ../../src/core/table/components/DataTablePagination.tsx
2984
+ const DataTablePagination = ({ page, size, totalPages, onPageChange, onSizeChange }) => {
2985
+ return /* @__PURE__ */ jsxs(Flex, {
2986
+ align: "center",
2987
+ justify: "end",
2988
+ gap: "md",
2989
+ p: "xs",
2990
+ style: { borderTop: "1px solid var(--alepha-border)" },
2991
+ children: [/* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(Select, {
2992
+ w: 96,
2993
+ variant: "default",
2994
+ value: size,
2995
+ onChange: (value) => {
2996
+ if (value) onSizeChange(Number(value));
2997
+ },
2998
+ data: [
2999
+ {
3000
+ value: "5",
3001
+ label: "5"
3002
+ },
3003
+ {
3004
+ value: "10",
3005
+ label: "10"
3006
+ },
3007
+ {
3008
+ value: "25",
3009
+ label: "25"
3010
+ },
3011
+ {
3012
+ value: "50",
3013
+ label: "50"
3014
+ },
3015
+ {
3016
+ value: "100",
3017
+ label: "100"
3018
+ }
3019
+ ]
3020
+ }) }), /* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(Pagination, {
3021
+ withEdges: true,
3022
+ total: totalPages,
3023
+ value: page,
3024
+ onChange: onPageChange
3025
+ }) })]
3026
+ });
3027
+ };
3028
+
3029
+ //#endregion
3030
+ //#region ../../src/core/table/components/ColumnPicker.tsx
3031
+ const ColumnPicker = ({ columns, visibility, onVisibilityChange }) => {
3032
+ const [opened, setOpened] = useState(false);
3033
+ const columnEntries = Object.entries(columns);
3034
+ const handleShowAll = () => {
3035
+ onVisibilityChange(columnEntries.reduce((acc, [key]) => ({
3036
+ ...acc,
3037
+ [key]: true
3038
+ }), {}));
3039
+ };
3040
+ const handleDefault = () => {
3041
+ let count = 0;
3042
+ onVisibilityChange(columnEntries.reduce((acc, [key, col]) => {
3043
+ if (col.defaultHidden) acc[key] = false;
3044
+ else if (count < DEFAULT_MAX_VISIBLE_COLUMNS) {
3045
+ acc[key] = true;
3046
+ count++;
3047
+ } else acc[key] = false;
3048
+ return acc;
3049
+ }, {}));
3050
+ };
3051
+ const handleToggle = (key, checked) => {
3052
+ onVisibilityChange({
3053
+ ...visibility,
3054
+ [key]: checked
3055
+ });
3056
+ };
3057
+ const visibleCount = columnEntries.filter(([key]) => visibility[key] !== false).length;
3058
+ return /* @__PURE__ */ jsxs(Popover, {
3059
+ width: 280,
3060
+ position: "bottom-start",
3061
+ shadow: "md",
3062
+ opened,
3063
+ onChange: setOpened,
3064
+ closeOnClickOutside: true,
3065
+ closeOnEscape: true,
3066
+ transitionProps: {
3067
+ transition: "fade-up",
3068
+ duration: 200,
3069
+ timingFunction: "ease"
3070
+ },
3071
+ children: [/* @__PURE__ */ jsx(Popover.Target, { children: /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(ActionButton, {
3072
+ variant: "subtle",
3073
+ icon: IconColumns,
3074
+ onClick: () => setOpened((o) => !o)
3075
+ }) }) }), /* @__PURE__ */ jsx(Popover.Dropdown, {
3076
+ bg: "transparent",
3077
+ p: "xs",
3078
+ bd: `1px solid ${ui.colors.border}`,
3079
+ style: { backdropFilter: "blur(20px)" },
3080
+ children: /* @__PURE__ */ jsxs(Flex, {
3081
+ direction: "column",
3082
+ gap: "xs",
3083
+ bg: ui.colors.surface,
3084
+ p: "sm",
3085
+ bdrs: "sm",
3086
+ children: [/* @__PURE__ */ jsxs(Flex, {
3087
+ justify: "space-between",
3088
+ children: [/* @__PURE__ */ jsxs(Text, {
3089
+ size: "sm",
3090
+ fw: 500,
3091
+ children: [
3092
+ "Columns (",
3093
+ visibleCount,
3094
+ "/",
3095
+ columnEntries.length,
3096
+ ")"
3097
+ ]
3098
+ }), /* @__PURE__ */ jsxs(Flex, {
3099
+ gap: 4,
3100
+ children: [/* @__PURE__ */ jsx(ActionButton, {
3101
+ size: "compact-xs",
3102
+ variant: "subtle",
3103
+ onClick: handleShowAll,
3104
+ children: "All"
3105
+ }), /* @__PURE__ */ jsx(ActionButton, {
3106
+ size: "compact-xs",
3107
+ variant: "subtle",
3108
+ onClick: handleDefault,
3109
+ children: "Default"
3110
+ })]
3111
+ })]
3112
+ }), /* @__PURE__ */ jsx(ScrollArea.Autosize, {
3113
+ mah: 300,
3114
+ children: /* @__PURE__ */ jsx(Flex, {
3115
+ direction: "column",
3116
+ gap: 4,
3117
+ children: columnEntries.map(([key, col]) => /* @__PURE__ */ jsx(Checkbox, {
3118
+ label: col.label || key,
3119
+ checked: visibility[key] !== false,
3120
+ onChange: (e) => handleToggle(key, e.currentTarget.checked),
3121
+ size: "sm"
3122
+ }, key))
3123
+ })
3124
+ })]
3125
+ })
3126
+ })]
3127
+ });
3128
+ };
3129
+
3130
+ //#endregion
3131
+ //#region ../../src/core/table/components/FilterPicker.tsx
3132
+ const getFieldLabel = (schema, key) => {
3133
+ const prop = schema.properties[key];
3134
+ if (prop && typeof prop === "object" && "title" in prop && prop.title) return prop.title;
3135
+ return key.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
3136
+ };
3137
+ const FilterPicker = ({ schema, visibility, onVisibilityChange }) => {
3138
+ const [opened, setOpened] = useState(false);
3139
+ const filterKeys = Object.keys(schema.properties);
3140
+ const handleShowAll = () => {
3141
+ onVisibilityChange(filterKeys.reduce((acc, key) => ({
3142
+ ...acc,
3143
+ [key]: true
3144
+ }), {}));
3145
+ };
3146
+ const handleHideAll = () => {
3147
+ onVisibilityChange(filterKeys.reduce((acc, key) => ({
3148
+ ...acc,
3149
+ [key]: false
3150
+ }), {}));
3151
+ };
3152
+ const handleToggle = (key, checked) => {
3153
+ onVisibilityChange({
3154
+ ...visibility,
3155
+ [key]: checked
3156
+ });
3157
+ };
3158
+ const visibleCount = filterKeys.filter((key) => visibility[key]).length;
3159
+ return /* @__PURE__ */ jsxs(Popover, {
3160
+ width: 280,
3161
+ position: "bottom-start",
3162
+ shadow: "md",
3163
+ opened,
3164
+ onChange: setOpened,
3165
+ closeOnClickOutside: true,
3166
+ closeOnEscape: true,
3167
+ transitionProps: {
3168
+ transition: "fade-up",
3169
+ duration: 200,
3170
+ timingFunction: "ease"
3171
+ },
3172
+ children: [/* @__PURE__ */ jsx(Popover.Target, { children: /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(ActionButton, {
3173
+ variant: "subtle",
3174
+ icon: IconFilter,
3175
+ onClick: () => setOpened((o) => !o)
3176
+ }) }) }), /* @__PURE__ */ jsx(Popover.Dropdown, {
3177
+ bg: "transparent",
3178
+ p: "xs",
3179
+ bd: `1px solid ${ui.colors.border}`,
3180
+ style: { backdropFilter: "blur(20px)" },
3181
+ children: /* @__PURE__ */ jsxs(Flex, {
3182
+ direction: "column",
3183
+ gap: "xs",
3184
+ bg: ui.colors.surface,
3185
+ p: "sm",
3186
+ bdrs: "sm",
3187
+ children: [/* @__PURE__ */ jsxs(Flex, {
3188
+ justify: "space-between",
3189
+ children: [/* @__PURE__ */ jsxs(Text, {
3190
+ size: "sm",
3191
+ fw: 500,
3192
+ children: [
3193
+ "Filters (",
3194
+ visibleCount,
3195
+ "/",
3196
+ filterKeys.length,
3197
+ ")"
3198
+ ]
3199
+ }), /* @__PURE__ */ jsxs(Flex, {
3200
+ gap: 4,
3201
+ children: [/* @__PURE__ */ jsx(ActionButton, {
3202
+ size: "compact-xs",
3203
+ variant: "subtle",
3204
+ onClick: handleShowAll,
3205
+ children: "All"
3206
+ }), /* @__PURE__ */ jsx(ActionButton, {
3207
+ size: "compact-xs",
3208
+ variant: "subtle",
3209
+ onClick: handleHideAll,
3210
+ children: "None"
3211
+ })]
3212
+ })]
3213
+ }), /* @__PURE__ */ jsx(ScrollArea.Autosize, {
3214
+ mah: 300,
3215
+ children: /* @__PURE__ */ jsx(Flex, {
3216
+ direction: "column",
3217
+ gap: 4,
3218
+ children: filterKeys.map((key) => /* @__PURE__ */ jsx(Checkbox, {
3219
+ label: getFieldLabel(schema, key),
3220
+ checked: visibility[key] === true,
3221
+ onChange: (e) => handleToggle(key, e.currentTarget.checked),
3222
+ size: "sm"
3223
+ }, key))
3224
+ })
3225
+ })]
3226
+ })
3227
+ })]
3228
+ });
3229
+ };
3230
+
3231
+ //#endregion
3232
+ //#region ../../src/core/table/components/DataTableToolbar.tsx
3233
+ const escapeCsvField = (value) => {
3234
+ if (value.includes(",") || value.includes("\"") || value.includes("\n")) return `"${value.replace(/"/g, "\"\"")}"`;
3235
+ return value;
3236
+ };
3237
+ const extractText = (node) => {
3238
+ if (node == null || typeof node === "boolean") return "";
3239
+ if (typeof node === "string" || typeof node === "number") return String(node);
3240
+ if (Array.isArray(node)) return node.map(extractText).join("");
3241
+ if (typeof node === "object" && "props" in node) return extractText(node.props.children);
3242
+ return "";
3243
+ };
3244
+ const DataTableToolbar = ({ columns, filters, columnVisibility, filterVisibility, onColumnVisibilityChange, onFilterVisibilityChange, actions, onRefresh, items, withExport, selectedItems = [], checkboxActions, onClearSelection }) => {
3245
+ const hasSelection = selectedItems.length > 0;
3246
+ const exportableColumns = useCallback(() => {
3247
+ return Object.entries(columns).filter(([key, col]) => !col.actions && columnVisibility[key] !== false);
3248
+ }, [columns, columnVisibility]);
3249
+ const buildRows = useCallback(() => {
3250
+ const cols = exportableColumns();
3251
+ return items.map((item) => cols.map(([_key, col]) => {
3252
+ if (!col.value) return "";
3253
+ return extractText(col.value(item, {}));
3254
+ }));
3255
+ }, [items, exportableColumns]);
3256
+ const buildCsv = useCallback(() => {
3257
+ const header = exportableColumns().map(([_key, col]) => escapeCsvField(col.label));
3258
+ const rows = buildRows().map((row) => row.map(escapeCsvField));
3259
+ return [header.join(","), ...rows.map((r) => r.join(","))].join("\n");
3260
+ }, [exportableColumns, buildRows]);
3261
+ const exportCsv = useCallback(() => {
3262
+ const csv = buildCsv();
3263
+ const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
3264
+ const url = URL.createObjectURL(blob);
3265
+ const a = document.createElement("a");
3266
+ a.href = url;
3267
+ a.download = "export.csv";
3268
+ a.click();
3269
+ URL.revokeObjectURL(url);
3270
+ }, [buildCsv]);
3271
+ const exportClipboard = useCallback(async () => {
3272
+ const header = exportableColumns().map(([_key, col]) => col.label);
3273
+ const rows = buildRows();
3274
+ const text = [header.join(" "), ...rows.map((r) => r.join(" "))].join("\n");
3275
+ await navigator.clipboard.writeText(text);
3276
+ }, [exportableColumns, buildRows]);
3277
+ const handleCheckboxAction = async (action) => {
3278
+ const ctx = {
3279
+ selectedItems,
3280
+ clearSelection: onClearSelection || (() => {})
3281
+ };
3282
+ await action.onClick(ctx);
3283
+ };
3284
+ return /* @__PURE__ */ jsxs(Flex, {
3285
+ p: "xs",
3286
+ style: { borderBottom: "1px solid var(--alepha-border)" },
3287
+ children: [
3288
+ /* @__PURE__ */ jsxs(Flex, {
3289
+ gap: 4,
3290
+ align: "center",
3291
+ children: [
3292
+ filters && /* @__PURE__ */ jsx(FilterPicker, {
3293
+ schema: filters,
3294
+ visibility: filterVisibility,
3295
+ onVisibilityChange: onFilterVisibilityChange
3296
+ }),
3297
+ /* @__PURE__ */ jsx(ColumnPicker, {
3298
+ columns,
3299
+ visibility: columnVisibility,
3300
+ onVisibilityChange: onColumnVisibilityChange
3301
+ }),
3302
+ withExport && /* @__PURE__ */ jsx(ActionButton, {
3303
+ variant: "subtle",
3304
+ icon: IconDownload,
3305
+ menu: { items: [{
3306
+ label: "Export as CSV",
3307
+ icon: /* @__PURE__ */ jsx(IconDownload, { size: 14 }),
3308
+ onClick: exportCsv
3309
+ }, {
3310
+ label: "Copy to clipboard",
3311
+ icon: /* @__PURE__ */ jsx(IconClipboard, { size: 14 }),
3312
+ onClick: exportClipboard
3313
+ }] }
3314
+ }),
3315
+ hasSelection && /* @__PURE__ */ jsxs(Fragment, { children: [
3316
+ /* @__PURE__ */ jsx(Divider, {
3317
+ orientation: "vertical",
3318
+ mx: "xs"
3319
+ }),
3320
+ /* @__PURE__ */ jsxs(Badge, {
3321
+ variant: "light",
3322
+ size: "lg",
3323
+ children: [selectedItems.length, " selected"]
3324
+ }),
3325
+ /* @__PURE__ */ jsx(ActionButton, {
3326
+ variant: "subtle",
3327
+ size: "compact-sm",
3328
+ icon: IconX,
3329
+ onClick: onClearSelection,
3330
+ children: "Clear"
3331
+ }),
3332
+ checkboxActions?.map((action, index) => /* @__PURE__ */ jsx(ActionButton, {
3333
+ variant: "light",
3334
+ size: "compact-sm",
3335
+ intent: action.intent,
3336
+ icon: action.icon && isComponentType(action.icon) ? action.icon : void 0,
3337
+ onClick: () => handleCheckboxAction(action),
3338
+ children: action.label
3339
+ }, index))
3340
+ ] })
3341
+ ]
3342
+ }),
3343
+ /* @__PURE__ */ jsx(Flex, { flex: 1 }),
3344
+ /* @__PURE__ */ jsxs(Flex, {
3345
+ gap: "xs",
3346
+ children: [actions?.map((props, index) => !isValidElement(props) ? /* @__PURE__ */ jsx(ActionButton, {
3347
+ ...props,
3348
+ children: props.label
3349
+ }, index) : props), /* @__PURE__ */ jsx(ActionButton, {
3350
+ variant: "subtle",
3351
+ icon: IconRefresh,
3352
+ onClick: onRefresh
3353
+ })]
3354
+ })
3355
+ ]
3356
+ });
3357
+ };
3358
+
3359
+ //#endregion
3360
+ //#region ../../src/core/table/components/useTableSelection.ts
3361
+ const useTableSelection = (items, getItemKey, enabled) => {
3362
+ const [selectedKeys, setSelectedKeys] = useState(/* @__PURE__ */ new Set());
3363
+ const selectedItems = useMemo(() => {
3364
+ if (!enabled) return [];
3365
+ return items.filter((item) => selectedKeys.has(getItemKey(item)));
3366
+ }, [
3367
+ items,
3368
+ selectedKeys,
3369
+ getItemKey,
3370
+ enabled
3371
+ ]);
3372
+ const allSelected = useMemo(() => {
3373
+ if (items.length === 0) return false;
3374
+ return items.every((item) => selectedKeys.has(getItemKey(item)));
3375
+ }, [
3376
+ items,
3377
+ selectedKeys,
3378
+ getItemKey
3379
+ ]);
3380
+ return {
3381
+ selectedItems,
3382
+ allSelected,
3383
+ someSelected: useMemo(() => {
3384
+ if (items.length === 0) return false;
3385
+ const count = items.filter((item) => selectedKeys.has(getItemKey(item))).length;
3386
+ return count > 0 && count < items.length;
3387
+ }, [
3388
+ items,
3389
+ selectedKeys,
3390
+ getItemKey
3391
+ ]),
3392
+ toggleItem: useCallback((item) => {
3393
+ const key = getItemKey(item);
3394
+ setSelectedKeys((prev) => {
3395
+ const next = new Set(prev);
3396
+ if (next.has(key)) next.delete(key);
3397
+ else next.add(key);
3398
+ return next;
3399
+ });
3400
+ }, [getItemKey]),
3401
+ toggleAll: useCallback(() => {
3402
+ if (allSelected) setSelectedKeys((prev) => {
3403
+ const next = new Set(prev);
3404
+ for (const item of items) next.delete(getItemKey(item));
3405
+ return next;
3406
+ });
3407
+ else setSelectedKeys((prev) => {
3408
+ const next = new Set(prev);
3409
+ for (const item of items) next.add(getItemKey(item));
3410
+ return next;
3411
+ });
3412
+ }, [
3413
+ allSelected,
3414
+ items,
3415
+ getItemKey
3416
+ ]),
3417
+ clear: useCallback(() => setSelectedKeys(/* @__PURE__ */ new Set()), []),
3418
+ isSelected: useCallback((item) => selectedKeys.has(getItemKey(item)), [selectedKeys, getItemKey])
3419
+ };
3420
+ };
3421
+
3422
+ //#endregion
3423
+ //#region ../../src/core/table/components/DataTable.tsx
3424
+ /**
3425
+ * Parse the sort string to get direction for a specific field.
3426
+ * Alepha convention: 'field' = ASC, '-field' = DESC
3427
+ */
3428
+ const getSortDirection = (sortString, field) => {
3429
+ if (!sortString) return null;
3430
+ const parts = sortString.split(",").map((s) => s.trim());
3431
+ for (const part of parts) {
3432
+ if (part === field) return "asc";
3433
+ if (part === `-${field}`) return "desc";
3434
+ }
3435
+ return null;
3436
+ };
3437
+ /**
3438
+ * Toggle sort for a field in the sort string.
3439
+ * Cycles: null -> asc -> desc -> null
3440
+ */
3441
+ const toggleSort = (sortString, field) => {
3442
+ const current = getSortDirection(sortString, field);
3443
+ const parts = (sortString || "").split(",").map((s) => s.trim()).filter((s) => s && s !== field && s !== `-${field}`);
3444
+ if (current === null) parts.unshift(field);
3445
+ else if (current === "asc") parts.unshift(`-${field}`);
3446
+ return parts.length > 0 ? parts.join(",") : void 0;
3447
+ };
3448
+ const toAriaSort = (dir) => {
3449
+ if (dir === "asc") return "ascending";
3450
+ if (dir === "desc") return "descending";
3451
+ return "none";
3452
+ };
3453
+ const FIT_STYLE = {
3454
+ width: 1,
3455
+ whiteSpace: "nowrap"
3456
+ };
3457
+ const DataTable = (props) => {
3458
+ const [items, setItems] = useState(typeof props.items === "function" ? { content: [] } : props.items);
3459
+ const defaultSize = props.infinityScroll ? 100 : props.defaultSize || 10;
3460
+ const [page, setPage] = useState(1);
3461
+ const [size, setSize] = useState(String(defaultSize));
3462
+ const [currentPage, setCurrentPage] = useState(0);
3463
+ const alepha = useInject(Alepha);
3464
+ const sentinelRef = useRef(null);
3465
+ const [columnVisibility, setColumnVisibility] = useState(() => {
3466
+ const entries = Object.entries(props.columns);
3467
+ let visibleCount = 0;
3468
+ return entries.reduce((acc, [key, col]) => {
3469
+ if (col.defaultHidden) acc[key] = false;
3470
+ else if (visibleCount < DEFAULT_MAX_VISIBLE_COLUMNS) {
3471
+ acc[key] = true;
3472
+ visibleCount++;
3473
+ } else acc[key] = false;
3474
+ return acc;
3475
+ }, {});
3476
+ });
3477
+ const [filterVisibility, setFilterVisibility] = useState(() => {
3478
+ if (!props.filters?.properties) return {};
3479
+ const defaults = new Set(props.defaultFilters ?? []);
3480
+ return Object.keys(props.filters.properties).reduce((acc, key) => ({
3481
+ ...acc,
3482
+ [key]: defaults.has(key)
3483
+ }), {});
3484
+ });
3485
+ const visibleColumns = useMemo(() => {
3486
+ return Object.entries(props.columns).filter(([key]) => columnVisibility[key] !== false);
3487
+ }, [props.columns, columnVisibility]);
3488
+ const [sortString, setSortString] = useState(void 0);
3489
+ const handleSortClick = (columnKey, sortKey) => {
3490
+ const newSort = toggleSort(sortString, sortKey || columnKey);
3491
+ setSortString(newSort);
3492
+ form.input.sort.set(newSort);
3493
+ form.input.page.set(0);
3494
+ };
3495
+ const getItemKey = useCallback((item) => {
3496
+ if (props.getItemKey) return props.getItemKey(item);
3497
+ if ("id" in item) return String(item.id);
3498
+ return JSON.stringify(item);
3499
+ }, [props.getItemKey]);
3500
+ const selection = useTableSelection(items.content, getItemKey, props.withCheckbox ?? false);
3501
+ const panelConfig = useMemo(() => {
3502
+ if (!props.panel) return null;
3503
+ if (typeof props.panel === "function") return {
3504
+ render: props.panel,
3505
+ can: void 0
3506
+ };
3507
+ return props.panel;
3508
+ }, [props.panel]);
3509
+ const [drawerItem, setDrawerItem] = useState(null);
3510
+ const drawerConfig = useMemo(() => {
3511
+ if (!props.drawer) return null;
3512
+ if (typeof props.drawer === "function") return {
3513
+ render: props.drawer,
3514
+ can: void 0,
3515
+ props: void 0
3516
+ };
3517
+ return props.drawer;
3518
+ }, [props.drawer]);
3519
+ const [expandedKeys, setExpandedKeys] = useState(/* @__PURE__ */ new Set());
3520
+ const toggleExpand = useCallback((key) => {
3521
+ setExpandedKeys((prev) => {
3522
+ const next = new Set(prev);
3523
+ if (next.has(key)) next.delete(key);
3524
+ else next.add(key);
3525
+ return next;
3526
+ });
3527
+ }, []);
3528
+ const form = useForm({
3529
+ schema: t.object({
3530
+ ...props.filters ? props.filters.properties : {},
3531
+ page: t.number({ default: 0 }),
3532
+ size: t.number({ default: defaultSize }),
3533
+ sort: t.optional(t.string())
3534
+ }),
3535
+ handler: async (values) => {
3536
+ if (typeof props.items === "function") {
3537
+ const response = await props.items(values, { items: items.content });
3538
+ if (props.infinityScroll && values.page > 0) setItems((prev) => ({
3539
+ ...response,
3540
+ content: [...prev.content, ...response.content]
3541
+ }));
3542
+ else setItems(response);
3543
+ setCurrentPage(values.page);
3544
+ }
3545
+ },
3546
+ onReset: async () => {
3547
+ setPage(1);
3548
+ setSize(String(defaultSize));
3549
+ await form.submit();
3550
+ },
3551
+ onChange: async (key, value) => {
3552
+ if (key === "page") {
3553
+ setPage(value + 1);
3554
+ await form.submit();
3555
+ return;
3556
+ }
3557
+ if (key === "size") {
3558
+ setSize(String(value));
3559
+ form.input.page.set(0);
3560
+ return;
3561
+ }
3562
+ props.onFilterChange?.(key, value, form);
3563
+ }
3564
+ }, [items]);
3565
+ const dt = useInject(DateTimeProvider);
3566
+ useEffect(() => {
3567
+ if (props.submitOnInit) form.submit();
3568
+ if (props.submitEvery) {
3569
+ const it = dt.createInterval(() => {
3570
+ form.submit();
3571
+ }, props.submitEvery);
3572
+ return () => dt.clearInterval(it);
3573
+ }
3574
+ }, []);
3575
+ useEffect(() => {
3576
+ if (typeof props.items !== "function") setItems(props.items);
3577
+ }, [props.items]);
3578
+ useEffect(() => {
3579
+ if (!props.infinityScroll || typeof props.items !== "function") return;
3580
+ const sentinel = sentinelRef.current;
3581
+ if (!sentinel) return;
3582
+ const observer = new IntersectionObserver((entries) => {
3583
+ if (!entries[0].isIntersecting || form.submitting) return;
3584
+ const totalPages = items.page?.totalPages ?? 1;
3585
+ if (currentPage + 1 < totalPages) form.input.page.set(currentPage + 1);
3586
+ }, { rootMargin: "300px" });
3587
+ observer.observe(sentinel);
3588
+ return () => observer.disconnect();
3589
+ }, [
3590
+ props.infinityScroll,
3591
+ form.submitting,
3592
+ items.page?.totalPages,
3593
+ currentPage,
3594
+ form
3595
+ ]);
3596
+ const totalColumns = visibleColumns.length + (panelConfig ? 1 : 0) + (props.withCheckbox ? 1 : 0);
3597
+ const checkboxHeader = props.withCheckbox ? /* @__PURE__ */ jsx(Table.Th, {
3598
+ style: { width: 40 },
3599
+ children: /* @__PURE__ */ jsx(Checkbox, {
3600
+ checked: selection.allSelected,
3601
+ indeterminate: selection.someSelected,
3602
+ onChange: selection.toggleAll,
3603
+ "aria-label": "Select all"
3604
+ })
3605
+ }) : null;
3606
+ const head = visibleColumns.map(([key, col]) => {
3607
+ const sortField = col.sortKey || key;
3608
+ const sortDir = col.sortable ? getSortDirection(sortString, sortField) : null;
3609
+ return /* @__PURE__ */ jsx(Table.Th, {
3610
+ onClick: col.sortable ? () => handleSortClick(key, col.sortKey) : void 0,
3611
+ "aria-sort": col.sortable ? toAriaSort(sortDir) : void 0,
3612
+ style: {
3613
+ ...col.fit ? FIT_STYLE : {},
3614
+ ...col.sortable ? {
3615
+ cursor: "pointer",
3616
+ userSelect: "none"
3617
+ } : {}
3618
+ },
3619
+ children: /* @__PURE__ */ jsxs(Flex, {
3620
+ align: "center",
3621
+ gap: 4,
3622
+ children: [/* @__PURE__ */ jsx(Text, {
3623
+ size: "xs",
3624
+ children: col.label
3625
+ }), col.sortable && /* @__PURE__ */ jsxs(Flex, {
3626
+ c: "dimmed",
3627
+ children: [
3628
+ sortDir === "asc" && /* @__PURE__ */ jsx(IconArrowUp, { size: ui.sizes.icon.sm }),
3629
+ sortDir === "desc" && /* @__PURE__ */ jsx(IconArrowDown, { size: ui.sizes.icon.sm }),
3630
+ sortDir === null && /* @__PURE__ */ jsx(IconArrowsSort, { size: ui.sizes.icon.sm })
3631
+ ]
3632
+ })]
3633
+ })
3634
+ }, key);
3635
+ });
3636
+ const rows = items.content.flatMap((item, index) => {
3637
+ const trProps = props.tableTrProps ? props.tableTrProps(item) : {};
3638
+ const itemKey = getItemKey(item);
3639
+ const isSelected = selection.isSelected(item);
3640
+ const showPanel = panelConfig && (panelConfig.can ? panelConfig.can(item) : true);
3641
+ const isExpanded = expandedKeys.has(itemKey);
3642
+ const canOpenDrawer = drawerConfig && (drawerConfig.can ? drawerConfig.can(item) : true);
3643
+ const elements = [/* @__PURE__ */ jsxs(Table.Tr, {
3644
+ ...trProps,
3645
+ style: {
3646
+ ...canOpenDrawer ? { cursor: "pointer" } : {},
3647
+ ...trProps.style ?? {}
3648
+ },
3649
+ onClick: (e) => {
3650
+ if (canOpenDrawer) setDrawerItem(item);
3651
+ trProps.onClick?.(e);
3652
+ },
3653
+ children: [
3654
+ panelConfig && /* @__PURE__ */ jsx(Table.Td, {
3655
+ style: {
3656
+ width: 36,
3657
+ textAlign: "center"
3658
+ },
3659
+ py: 2,
3660
+ px: 0,
3661
+ children: showPanel && /* @__PURE__ */ jsx(UnstyledButton, {
3662
+ onClick: (e) => {
3663
+ e.stopPropagation();
3664
+ toggleExpand(itemKey);
3665
+ },
3666
+ style: { display: "inline-flex" },
3667
+ children: /* @__PURE__ */ jsx(Flex, {
3668
+ c: "dimmed",
3669
+ align: "center",
3670
+ justify: "center",
3671
+ children: isExpanded ? /* @__PURE__ */ jsx(IconChevronDown, { size: ui.sizes.icon.sm }) : /* @__PURE__ */ jsx(IconChevronRight, { size: ui.sizes.icon.sm })
3672
+ })
3673
+ })
3674
+ }),
3675
+ props.withCheckbox && /* @__PURE__ */ jsx(Table.Td, {
3676
+ style: { width: 40 },
3677
+ onClick: (e) => e.stopPropagation(),
3678
+ children: /* @__PURE__ */ jsx(Checkbox, {
3679
+ checked: isSelected,
3680
+ onChange: () => selection.toggleItem(item),
3681
+ "aria-label": "Select row"
3682
+ })
3683
+ }),
3684
+ visibleColumns.map(([key, col]) => {
3685
+ const ctx = {
3686
+ index,
3687
+ form,
3688
+ alepha
3689
+ };
3690
+ if (col.actions) {
3691
+ const rowActions = col.actions(item, ctx).filter((a) => a.visible !== false);
3692
+ return /* @__PURE__ */ jsx(Table.Td, {
3693
+ py: 2,
3694
+ px: 4,
3695
+ style: col.fit ? FIT_STYLE : void 0,
3696
+ onClick: (e) => e.stopPropagation(),
3697
+ children: /* @__PURE__ */ jsx(Flex, {
3698
+ gap: 4,
3699
+ children: rowActions.map(({ visible: _, ...actionProps }, i) => /* @__PURE__ */ jsx(ActionButton, {
3700
+ variant: "subtle",
3701
+ size: "xs",
3702
+ preventDefault: true,
3703
+ h: 20,
3704
+ ...actionProps
3705
+ }, i))
3706
+ })
3707
+ }, key);
3708
+ }
3709
+ return /* @__PURE__ */ jsx(Table.Td, {
3710
+ py: 2,
3711
+ px: 4,
3712
+ style: col.fit ? FIT_STYLE : void 0,
3713
+ children: col.value?.(item, ctx)
3714
+ }, key);
3715
+ })
3716
+ ]
3717
+ }, itemKey)];
3718
+ if (panelConfig && showPanel && isExpanded) elements.push(/* @__PURE__ */ jsx(Table.Tr, { children: /* @__PURE__ */ jsx(Table.Td, {
3719
+ colSpan: totalColumns,
3720
+ p: 0,
3721
+ children: panelConfig.render(item)
3722
+ }) }, `${itemKey}-panel`));
3723
+ return elements;
3724
+ });
3725
+ const filterSchema = useMemo(() => {
3726
+ if (!props.filters) return null;
3727
+ return t.omit(form.options.schema, [
3728
+ "page",
3729
+ "size",
3730
+ "sort"
3731
+ ]);
3732
+ }, [props.filters, form.options.schema]);
3733
+ return /* @__PURE__ */ jsxs(Flex, {
3734
+ flex: 1,
3735
+ p: 0,
3736
+ bdrs: "sm",
3737
+ direction: "column",
3738
+ children: [
3739
+ /* @__PURE__ */ jsx(DataTableToolbar, {
3740
+ columns: props.columns,
3741
+ filters: props.filters,
3742
+ columnVisibility,
3743
+ filterVisibility,
3744
+ onColumnVisibilityChange: setColumnVisibility,
3745
+ onFilterVisibilityChange: setFilterVisibility,
3746
+ actions: props.actions,
3747
+ onRefresh: () => form.submit(),
3748
+ items: items.content,
3749
+ withExport: props.withExport,
3750
+ selectedItems: selection.selectedItems,
3751
+ checkboxActions: props.checkboxActions,
3752
+ onClearSelection: selection.clear
3753
+ }),
3754
+ filterSchema && props.filters && /* @__PURE__ */ jsx(DataTableFilters, {
3755
+ schema: filterSchema,
3756
+ form,
3757
+ typeFormProps: props.typeFormProps,
3758
+ filterVisibility
3759
+ }),
3760
+ /* @__PURE__ */ jsx(Flex, {
3761
+ className: "overflow-auto",
3762
+ children: /* @__PURE__ */ jsxs(Table, {
3763
+ "aria-label": "Data table",
3764
+ withColumnBorders: true,
3765
+ withRowBorders: true,
3766
+ ...props.tableProps,
3767
+ children: [/* @__PURE__ */ jsx(Table.Thead, {
3768
+ style: {
3769
+ position: "sticky",
3770
+ top: 0,
3771
+ zIndex: 1,
3772
+ backgroundColor: "var(--mantine-color-body)"
3773
+ },
3774
+ children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
3775
+ panelConfig && /* @__PURE__ */ jsx(Table.Th, { style: { width: 36 } }),
3776
+ checkboxHeader,
3777
+ head
3778
+ ] })
3779
+ }), /* @__PURE__ */ jsxs(Table.Tbody, {
3780
+ style: {
3781
+ opacity: form.submitting ? .5 : 1,
3782
+ transition: "opacity 150ms ease"
3783
+ },
3784
+ children: [rows, items.content.length === 0 && /* @__PURE__ */ jsx(Table.Tr, { children: /* @__PURE__ */ jsx(Table.Td, {
3785
+ colSpan: totalColumns || 1,
3786
+ py: "xl",
3787
+ style: { textAlign: "center" },
3788
+ children: /* @__PURE__ */ jsx(Text, {
3789
+ c: "dimmed",
3790
+ size: "sm",
3791
+ children: form.submitting ? "Loading…" : "No results"
3792
+ })
3793
+ }) })]
3794
+ })]
3795
+ })
3796
+ }),
3797
+ props.infinityScroll && /* @__PURE__ */ jsx("div", { ref: sentinelRef }),
3798
+ !props.infinityScroll && /* @__PURE__ */ jsx(DataTablePagination, {
3799
+ page,
3800
+ size,
3801
+ totalPages: items.page?.totalPages ?? 1,
3802
+ onPageChange: (value) => {
3803
+ form.input.page.set(value - 1);
3804
+ },
3805
+ onSizeChange: (value) => {
3806
+ form.input.size.set(value);
3807
+ }
3808
+ }),
3809
+ drawerConfig && /* @__PURE__ */ jsx(Drawer, {
3810
+ opened: drawerItem !== null,
3811
+ onClose: () => setDrawerItem(null),
3812
+ position: "right",
3813
+ size: "xl",
3814
+ ...drawerConfig.props,
3815
+ children: drawerItem && drawerConfig.render(drawerItem)
3816
+ })
3817
+ ]
3818
+ });
3819
+ };
3820
+
3821
+ //#endregion
3822
+ //#region ../../src/core/utils/extractSchemaFields.ts
3823
+ /**
3824
+ * Extract field information from a TypeBox schema for query building.
3825
+ * Supports nested objects and provides field metadata for autocomplete.
3826
+ */
3827
+ function extractSchemaFields(schema, prefix = "") {
3828
+ const fields = [];
3829
+ if (!schema || typeof schema !== "object") return fields;
3830
+ const properties = "properties" in schema ? schema.properties : schema;
3831
+ if (!properties || typeof properties !== "object") return fields;
3832
+ for (const [key, value] of Object.entries(properties)) {
3833
+ if (typeof value !== "object" || value === null) continue;
3834
+ const fieldSchema = value;
3835
+ const path = prefix ? `${prefix}.${key}` : key;
3836
+ const format = "format" in fieldSchema ? fieldSchema.format : void 0;
3837
+ let displayType = "type" in fieldSchema ? fieldSchema.type : "object";
3838
+ if (format === "date-time") displayType = "datetime";
3839
+ else if (format === "date") displayType = "date";
3840
+ else if (format === "time") displayType = "time";
3841
+ else if (format === "duration") displayType = "duration";
3842
+ const field = {
3843
+ name: key,
3844
+ path,
3845
+ type: displayType,
3846
+ format,
3847
+ description: "description" in fieldSchema ? fieldSchema.description : void 0
3848
+ };
3849
+ if ("enum" in fieldSchema && fieldSchema.enum) {
3850
+ field.enum = fieldSchema.enum;
3851
+ field.type = "enum";
3852
+ }
3853
+ if ("type" in fieldSchema && fieldSchema.type === "object" && "properties" in fieldSchema && typeof fieldSchema.properties === "object") field.nested = extractSchemaFields(fieldSchema.properties, path);
3854
+ fields.push(field);
3855
+ if (field.nested) fields.push(...field.nested);
3856
+ }
3857
+ return fields;
3858
+ }
3859
+ /**
3860
+ * Get operator symbol and description
3861
+ */
3862
+ const OPERATOR_INFO = {
3863
+ eq: {
3864
+ symbol: "=",
3865
+ label: "equals",
3866
+ example: "name=John"
3867
+ },
3868
+ ne: {
3869
+ symbol: "!=",
3870
+ label: "not equals",
3871
+ example: "status!=archived"
3872
+ },
3873
+ gt: {
3874
+ symbol: ">",
3875
+ label: "greater than",
3876
+ example: "age>18"
3877
+ },
3878
+ gte: {
3879
+ symbol: ">=",
3880
+ label: "greater or equal",
3881
+ example: "age>=18"
3882
+ },
3883
+ lt: {
3884
+ symbol: "<",
3885
+ label: "less than",
3886
+ example: "age<65"
3887
+ },
3888
+ lte: {
3889
+ symbol: "<=",
3890
+ label: "less or equal",
3891
+ example: "age<=65"
3892
+ },
3893
+ null: {
3894
+ symbol: "=null",
3895
+ label: "is null",
3896
+ example: "deletedAt=null"
3897
+ },
3898
+ notNull: {
3899
+ symbol: "!=null",
3900
+ label: "is not null",
3901
+ example: "email!=null"
3902
+ },
3903
+ in: {
3904
+ symbol: "[...]",
3905
+ label: "in array",
3906
+ example: "status=[active,pending]"
3907
+ }
3908
+ };
3909
+
3910
+ //#endregion
3911
+ //#region ../../src/core/utils/icons.tsx
3912
+ /**
3913
+ * Get the default icon for an input based on its type, format, or name.
3914
+ */
3915
+ const getDefaultIcon = (params) => {
3916
+ const { type, format, name, isEnum, isArray, size = "sm" } = params;
3917
+ const iconSize = ui.sizes.icon[size];
3918
+ if (format) switch (format) {
3919
+ case "email": return /* @__PURE__ */ jsx(IconMail, { size: iconSize });
3920
+ case "url":
3921
+ case "uri": return /* @__PURE__ */ jsx(IconLink, { size: iconSize });
3922
+ case "tel":
3923
+ case "phone": return /* @__PURE__ */ jsx(IconPhone, { size: iconSize });
3924
+ case "date": return /* @__PURE__ */ jsx(IconCalendar, { size: iconSize });
3925
+ case "date-time": return /* @__PURE__ */ jsx(IconCalendar, { size: iconSize });
3926
+ case "time": return /* @__PURE__ */ jsx(IconClock, { size: iconSize });
3927
+ case "color": return /* @__PURE__ */ jsx(IconColorPicker, { size: iconSize });
3928
+ case "uuid": return /* @__PURE__ */ jsx(IconKey, { size: iconSize });
3929
+ }
3930
+ if (name) {
3931
+ const nameLower = name.toLowerCase();
3932
+ if (nameLower.includes("password") || nameLower.includes("secret")) return /* @__PURE__ */ jsx(IconKey, { size: iconSize });
3933
+ if (nameLower.includes("email") || nameLower.includes("mail")) return /* @__PURE__ */ jsx(IconMail, { size: iconSize });
3934
+ if (nameLower.includes("url") || nameLower.includes("link")) return /* @__PURE__ */ jsx(IconLink, { size: iconSize });
3935
+ if (nameLower.includes("phone") || nameLower.includes("tel")) return /* @__PURE__ */ jsx(IconPhone, { size: iconSize });
3936
+ if (nameLower.includes("color")) return /* @__PURE__ */ jsx(IconPalette, { size: iconSize });
3937
+ if (nameLower.includes("file") || nameLower.includes("upload")) return /* @__PURE__ */ jsx(IconFile, { size: iconSize });
3938
+ if (nameLower.includes("date")) return /* @__PURE__ */ jsx(IconCalendar, { size: iconSize });
3939
+ if (nameLower.includes("time")) return /* @__PURE__ */ jsx(IconClock, { size: iconSize });
3940
+ }
3941
+ if (isEnum || isArray) return /* @__PURE__ */ jsx(IconSelector, { size: iconSize });
3942
+ if (type) switch (type) {
3943
+ case "boolean": return /* @__PURE__ */ jsx(IconToggleLeft, { size: iconSize });
3944
+ case "number":
3945
+ case "integer": return /* @__PURE__ */ jsx(IconHash, { size: iconSize });
3946
+ case "array": return /* @__PURE__ */ jsx(IconList, { size: iconSize });
3947
+ case "string": return /* @__PURE__ */ jsx(IconLetterCase, { size: iconSize });
3948
+ }
3949
+ return /* @__PURE__ */ jsx(IconAt, { size: iconSize });
3950
+ };
3951
+
3952
+ //#endregion
3953
+ //#region ../../src/core/utils/string.ts
3954
+ /**
3955
+ * Capitalizes the first letter of a string.
3956
+ *
3957
+ * @example
3958
+ * capitalize("hello") // "Hello"
3959
+ */
3960
+ const capitalize = (str) => {
3961
+ return str.charAt(0).toUpperCase() + str.slice(1);
3962
+ };
3963
+ /**
3964
+ * Converts camelCase or snake_case to Title Case with spaces.
3965
+ *
3966
+ * @example
3967
+ * toTitleCase("userName") // "User Name"
3968
+ * toTitleCase("first_name") // "First Name"
3969
+ * toTitleCase("email") // "Email"
3970
+ */
3971
+ const toTitleCase = (str) => {
3972
+ return str.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
3973
+ };
3974
+ /**
3975
+ * Converts a path or identifier string into a pretty display name.
3976
+ * For paths like "/contacts/0/name", extracts just the field name "Name".
3977
+ * Handles camelCase and snake_case conversion to Title Case.
3978
+ *
3979
+ * @example
3980
+ * prettyName("/userName") // "User Name"
3981
+ * prettyName("/contacts/0/email") // "Email"
3982
+ * prettyName("/address/streetName") // "Street Name"
3983
+ * prettyName("first_name") // "First Name"
3984
+ */
3985
+ const prettyName = (name) => {
3986
+ const segments = name.split("/").filter((s) => s && !/^\d+$/.test(s));
3987
+ return toTitleCase(segments[segments.length - 1] || name.replaceAll("/", ""));
3988
+ };
3989
+
3990
+ //#endregion
3991
+ //#region ../../src/core/index.ts
3992
+ /**
3993
+ * Core UI components based on Mantine UI v8.
3994
+ *
3995
+ * **Features:**
3996
+ * - Mantine integration with theme support
3997
+ * - ActionButton, BurgerButton, ClipboardButton, DarkModeButton, LanguageButton, ThemeButton
3998
+ * - AlertDialog, ConfirmDialog, PromptDialog
3999
+ * - Form controls: Control, ControlArray, ControlDate, ControlNumber, ControlObject, ControlSelect, ControlQueryBuilder
4000
+ * - TypeForm for automatic form generation from TypeBox schemas
4001
+ * - DashboardShell layout component
4002
+ * - AppBar with configurable elements
4003
+ * - Sidebar navigation with sections and menu items
4004
+ * - Omnibar for command palette / search
4005
+ * - DataTable with filtering, sorting, pagination
4006
+ * - Toast notifications
4007
+ * - Theme system with dark mode
4008
+ *
4009
+ * @module alepha.ui
4010
+ */
4011
+ const AlephaUI = $module({
4012
+ name: "alepha.ui",
4013
+ services: [
4014
+ DialogService,
4015
+ ToastService,
4016
+ ThemeProvider,
4017
+ UiRouter
4018
+ ],
4019
+ register: (alepha) => {
4020
+ alepha.with(AlephaReactI18n);
4021
+ alepha.with(AlephaReactHead);
4022
+ alepha.with(AlephaReactForm);
4023
+ alepha.with(ThemeProvider);
4024
+ alepha.with(DialogService);
4025
+ alepha.with(ToastService);
4026
+ }
4027
+ });
4028
+
4029
+ //#endregion
4030
+ export { ActionButton as _, TypeForm as a, useToast as b, DashboardShell as c, StatCards as d, DetailList as f, ClipboardButton as g, LanguageButton as h, useDialog as i, Breadcrumb as l, ThemeButton as m, capitalize as n, Control as o, ToggleSidebarButton as p, DataTable as r, Text$1 as s, AlephaUI as t, Flex$1 as u, ui as v, AlephaMantineProvider as y };
4031
+ //# sourceMappingURL=core-2xoLiT0o.js.map