@svadmin/ui 0.29.3 → 0.29.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (444) hide show
  1. package/dist/README.md +18 -0
  2. package/dist/actions.d.ts +35 -0
  3. package/dist/actions.js +84 -0
  4. package/dist/app.css +673 -0
  5. package/dist/component-registry.svelte.d.ts +36 -0
  6. package/dist/component-registry.svelte.js +20 -0
  7. package/dist/components/AICommandBar.svelte +180 -0
  8. package/dist/components/AICommandBar.svelte.d.ts +6 -0
  9. package/dist/components/AboutSettings.svelte +106 -0
  10. package/dist/components/AboutSettings.svelte.d.ts +18 -0
  11. package/dist/components/AdminApp.svelte +199 -0
  12. package/dist/components/AdminApp.svelte.d.ts +26 -0
  13. package/dist/components/AnimatedCounter.svelte +46 -0
  14. package/dist/components/AnimatedCounter.svelte.d.ts +11 -0
  15. package/dist/components/AnomalyBadge.svelte +65 -0
  16. package/dist/components/AnomalyBadge.svelte.d.ts +16 -0
  17. package/dist/components/AppearanceSettings.svelte +183 -0
  18. package/dist/components/AppearanceSettings.svelte.d.ts +3 -0
  19. package/dist/components/AuditLogDrawer.svelte +93 -0
  20. package/dist/components/AuditLogDrawer.svelte.d.ts +8 -0
  21. package/dist/components/AuditLogViewer.svelte +226 -0
  22. package/dist/components/AuditLogViewer.svelte.d.ts +3 -0
  23. package/dist/components/Authenticated.svelte +51 -0
  24. package/dist/components/Authenticated.svelte.d.ts +11 -0
  25. package/dist/components/AutoForm.svelte +242 -0
  26. package/dist/components/AutoForm.svelte.d.ts +20 -0
  27. package/dist/components/AutoSaveIndicator.svelte +35 -0
  28. package/dist/components/AutoSaveIndicator.svelte.d.ts +9 -0
  29. package/dist/components/AutoTable.svelte +839 -0
  30. package/dist/components/AutoTable.svelte.d.ts +44 -0
  31. package/dist/components/Breadcrumbs.svelte +58 -0
  32. package/dist/components/Breadcrumbs.svelte.d.ts +3 -0
  33. package/dist/components/CanAccess.svelte +23 -0
  34. package/dist/components/CanAccess.svelte.d.ts +12 -0
  35. package/dist/components/CatchAllNavigate.svelte +21 -0
  36. package/dist/components/CatchAllNavigate.svelte.d.ts +3 -0
  37. package/dist/components/ChatDialog.svelte +535 -0
  38. package/dist/components/ChatDialog.svelte.d.ts +14 -0
  39. package/dist/components/ComboboxField.svelte +129 -0
  40. package/dist/components/ComboboxField.svelte.d.ts +17 -0
  41. package/dist/components/CommandPalette.svelte +135 -0
  42. package/dist/components/CommandPalette.svelte.d.ts +8 -0
  43. package/dist/components/ConfigErrorScreen.svelte +126 -0
  44. package/dist/components/ConfigErrorScreen.svelte.d.ts +11 -0
  45. package/dist/components/ConfirmDialog.svelte +53 -0
  46. package/dist/components/ConfirmDialog.svelte.d.ts +13 -0
  47. package/dist/components/CopilotPanel.svelte +163 -0
  48. package/dist/components/CopilotPanel.svelte.d.ts +6 -0
  49. package/dist/components/CreatePage.svelte +39 -0
  50. package/dist/components/CreatePage.svelte.d.ts +10 -0
  51. package/dist/components/DevTools.svelte +149 -0
  52. package/dist/components/DevTools.svelte.d.ts +3 -0
  53. package/dist/components/DraggableGrid.svelte +86 -0
  54. package/dist/components/DraggableGrid.svelte.d.ts +17 -0
  55. package/dist/components/DraggableHeader.svelte +112 -0
  56. package/dist/components/DraggableHeader.svelte.d.ts +25 -0
  57. package/dist/components/DrawerForm.svelte +35 -0
  58. package/dist/components/DrawerForm.svelte.d.ts +11 -0
  59. package/dist/components/EditPage.svelte +52 -0
  60. package/dist/components/EditPage.svelte.d.ts +12 -0
  61. package/dist/components/EmptyState.svelte +31 -0
  62. package/dist/components/EmptyState.svelte.d.ts +8 -0
  63. package/dist/components/ErrorBoundary.svelte +49 -0
  64. package/dist/components/ErrorBoundary.svelte.d.ts +12 -0
  65. package/dist/components/ErrorComponent.svelte +36 -0
  66. package/dist/components/ErrorComponent.svelte.d.ts +9 -0
  67. package/dist/components/FieldRenderer.svelte +357 -0
  68. package/dist/components/FieldRenderer.svelte.d.ts +13 -0
  69. package/dist/components/ForgotPasswordPage.svelte +112 -0
  70. package/dist/components/ForgotPasswordPage.svelte.d.ts +6 -0
  71. package/dist/components/Header.svelte +94 -0
  72. package/dist/components/Header.svelte.d.ts +12 -0
  73. package/dist/components/InferencerPanel.svelte +192 -0
  74. package/dist/components/InferencerPanel.svelte.d.ts +3 -0
  75. package/dist/components/InfiniteList.svelte +93 -0
  76. package/dist/components/InfiniteList.svelte.d.ts +20 -0
  77. package/dist/components/InlineEdit.svelte +88 -0
  78. package/dist/components/InlineEdit.svelte.d.ts +12 -0
  79. package/dist/components/InsightCard.svelte +98 -0
  80. package/dist/components/InsightCard.svelte.d.ts +11 -0
  81. package/dist/components/KeyboardShortcuts.svelte +61 -0
  82. package/dist/components/KeyboardShortcuts.svelte.d.ts +6 -0
  83. package/dist/components/Layout.svelte +167 -0
  84. package/dist/components/Layout.svelte.d.ts +11 -0
  85. package/dist/components/ListPage.svelte +71 -0
  86. package/dist/components/ListPage.svelte.d.ts +32 -0
  87. package/dist/components/LiveIndicator.svelte +58 -0
  88. package/dist/components/LiveIndicator.svelte.d.ts +12 -0
  89. package/dist/components/LoginPage.svelte +163 -0
  90. package/dist/components/LoginPage.svelte.d.ts +28 -0
  91. package/dist/components/MarkdownRenderer.svelte +127 -0
  92. package/dist/components/MarkdownRenderer.svelte.d.ts +11 -0
  93. package/dist/components/ModalForm.svelte +38 -0
  94. package/dist/components/ModalForm.svelte.d.ts +10 -0
  95. package/dist/components/NavigateToResource.svelte +20 -0
  96. package/dist/components/NavigateToResource.svelte.d.ts +3 -0
  97. package/dist/components/PageHeader.svelte +39 -0
  98. package/dist/components/PageHeader.svelte.d.ts +11 -0
  99. package/dist/components/PageSkeleton.svelte +71 -0
  100. package/dist/components/PageSkeleton.svelte.d.ts +7 -0
  101. package/dist/components/PasswordInput.svelte +96 -0
  102. package/dist/components/PasswordInput.svelte.d.ts +12 -0
  103. package/dist/components/PermissionMatrix.svelte +185 -0
  104. package/dist/components/PermissionMatrix.svelte.d.ts +19 -0
  105. package/dist/components/ProfilePage.svelte +284 -0
  106. package/dist/components/ProfilePage.svelte.d.ts +3 -0
  107. package/dist/components/RegisterPage.svelte +112 -0
  108. package/dist/components/RegisterPage.svelte.d.ts +7 -0
  109. package/dist/components/RolesSettings.svelte +146 -0
  110. package/dist/components/RolesSettings.svelte.d.ts +3 -0
  111. package/dist/components/SettingsPage.svelte +113 -0
  112. package/dist/components/SettingsPage.svelte.d.ts +3 -0
  113. package/dist/components/ShowPage.svelte +73 -0
  114. package/dist/components/ShowPage.svelte.d.ts +7 -0
  115. package/dist/components/Sidebar.svelte +417 -0
  116. package/dist/components/Sidebar.svelte.d.ts +14 -0
  117. package/dist/components/SidebarItem.svelte +129 -0
  118. package/dist/components/SidebarItem.svelte.d.ts +11 -0
  119. package/dist/components/SmartSuggest.svelte +158 -0
  120. package/dist/components/SmartSuggest.svelte.d.ts +19 -0
  121. package/dist/components/StatsCard.svelte +79 -0
  122. package/dist/components/StatsCard.svelte.d.ts +23 -0
  123. package/dist/components/StepsForm.svelte +192 -0
  124. package/dist/components/StepsForm.svelte.d.ts +13 -0
  125. package/dist/components/TaskQueueDrawer.svelte +158 -0
  126. package/dist/components/TaskQueueDrawer.svelte.d.ts +11 -0
  127. package/dist/components/TenantSwitcher.svelte +84 -0
  128. package/dist/components/TenantSwitcher.svelte.d.ts +12 -0
  129. package/dist/components/ThemedTitle.svelte +32 -0
  130. package/dist/components/ThemedTitle.svelte.d.ts +9 -0
  131. package/dist/components/Toast.svelte +46 -0
  132. package/dist/components/Toast.svelte.d.ts +3 -0
  133. package/dist/components/TooltipButton.svelte +42 -0
  134. package/dist/components/TooltipButton.svelte.d.ts +14 -0
  135. package/dist/components/UndoableNotification.svelte +69 -0
  136. package/dist/components/UndoableNotification.svelte.d.ts +9 -0
  137. package/dist/components/UpdatePasswordPage.svelte +89 -0
  138. package/dist/components/UpdatePasswordPage.svelte.d.ts +6 -0
  139. package/dist/components/VirtualTable.svelte +76 -0
  140. package/dist/components/VirtualTable.svelte.d.ts +27 -0
  141. package/dist/components/VoiceInput.svelte +120 -0
  142. package/dist/components/VoiceInput.svelte.d.ts +15 -0
  143. package/dist/components/buttons/CloneButton.svelte +30 -0
  144. package/dist/components/buttons/CloneButton.svelte.d.ts +13 -0
  145. package/dist/components/buttons/CreateButton.svelte +29 -0
  146. package/dist/components/buttons/CreateButton.svelte.d.ts +12 -0
  147. package/dist/components/buttons/DeleteButton.svelte +59 -0
  148. package/dist/components/buttons/DeleteButton.svelte.d.ts +15 -0
  149. package/dist/components/buttons/EditButton.svelte +30 -0
  150. package/dist/components/buttons/EditButton.svelte.d.ts +13 -0
  151. package/dist/components/buttons/ExportButton.svelte +30 -0
  152. package/dist/components/buttons/ExportButton.svelte.d.ts +12 -0
  153. package/dist/components/buttons/ImportButton.svelte +56 -0
  154. package/dist/components/buttons/ImportButton.svelte.d.ts +19 -0
  155. package/dist/components/buttons/ListButton.svelte +23 -0
  156. package/dist/components/buttons/ListButton.svelte.d.ts +8 -0
  157. package/dist/components/buttons/RefreshButton.svelte +36 -0
  158. package/dist/components/buttons/RefreshButton.svelte.d.ts +8 -0
  159. package/dist/components/buttons/SaveButton.svelte +27 -0
  160. package/dist/components/buttons/SaveButton.svelte.d.ts +9 -0
  161. package/dist/components/buttons/ShowButton.svelte +31 -0
  162. package/dist/components/buttons/ShowButton.svelte.d.ts +13 -0
  163. package/dist/components/buttons/index.d.ts +10 -0
  164. package/dist/components/buttons/index.js +10 -0
  165. package/dist/components/charts/BarChart.svelte +69 -0
  166. package/dist/components/charts/BarChart.svelte.d.ts +15 -0
  167. package/dist/components/charts/LineChart.svelte +115 -0
  168. package/dist/components/charts/LineChart.svelte.d.ts +17 -0
  169. package/dist/components/charts/PieChart.svelte +95 -0
  170. package/dist/components/charts/PieChart.svelte.d.ts +15 -0
  171. package/dist/components/charts/index.d.ts +3 -0
  172. package/dist/components/charts/index.js +3 -0
  173. package/dist/components/fieldComponentMap.d.ts +24 -0
  174. package/dist/components/fieldComponentMap.js +65 -0
  175. package/dist/components/fields/ArrayField.svelte +86 -0
  176. package/dist/components/fields/ArrayField.svelte.d.ts +9 -0
  177. package/dist/components/fields/BooleanField.svelte +13 -0
  178. package/dist/components/fields/BooleanField.svelte.d.ts +8 -0
  179. package/dist/components/fields/DateField.svelte +6 -0
  180. package/dist/components/fields/DateField.svelte.d.ts +8 -0
  181. package/dist/components/fields/EmailField.svelte +9 -0
  182. package/dist/components/fields/EmailField.svelte.d.ts +6 -0
  183. package/dist/components/fields/FileField.svelte +14 -0
  184. package/dist/components/fields/FileField.svelte.d.ts +7 -0
  185. package/dist/components/fields/ImageField.svelte +9 -0
  186. package/dist/components/fields/ImageField.svelte.d.ts +9 -0
  187. package/dist/components/fields/JsonField.svelte +72 -0
  188. package/dist/components/fields/JsonField.svelte.d.ts +6 -0
  189. package/dist/components/fields/MarkdownField.svelte +9 -0
  190. package/dist/components/fields/MarkdownField.svelte.d.ts +6 -0
  191. package/dist/components/fields/MultiSelectField.svelte +21 -0
  192. package/dist/components/fields/MultiSelectField.svelte.d.ts +10 -0
  193. package/dist/components/fields/NumberField.svelte +6 -0
  194. package/dist/components/fields/NumberField.svelte.d.ts +8 -0
  195. package/dist/components/fields/RelationField.svelte +24 -0
  196. package/dist/components/fields/RelationField.svelte.d.ts +8 -0
  197. package/dist/components/fields/RichTextField.svelte +35 -0
  198. package/dist/components/fields/RichTextField.svelte.d.ts +7 -0
  199. package/dist/components/fields/SelectField.svelte +14 -0
  200. package/dist/components/fields/SelectField.svelte.d.ts +10 -0
  201. package/dist/components/fields/TagField.svelte +12 -0
  202. package/dist/components/fields/TagField.svelte.d.ts +6 -0
  203. package/dist/components/fields/TextField.svelte +11 -0
  204. package/dist/components/fields/TextField.svelte.d.ts +7 -0
  205. package/dist/components/fields/UrlField.svelte +14 -0
  206. package/dist/components/fields/UrlField.svelte.d.ts +7 -0
  207. package/dist/components/ui/alert/alert-description.svelte +17 -0
  208. package/dist/components/ui/alert/alert-description.svelte.d.ts +6 -0
  209. package/dist/components/ui/alert/alert-title.svelte +17 -0
  210. package/dist/components/ui/alert/alert-title.svelte.d.ts +6 -0
  211. package/dist/components/ui/alert/alert.svelte +44 -0
  212. package/dist/components/ui/alert/alert.svelte.d.ts +32 -0
  213. package/dist/components/ui/alert/index.d.ts +4 -0
  214. package/dist/components/ui/alert/index.js +5 -0
  215. package/dist/components/ui/alert-dialog/alert-dialog-content.svelte +29 -0
  216. package/dist/components/ui/alert-dialog/alert-dialog-content.svelte.d.ts +10 -0
  217. package/dist/components/ui/alert-dialog/alert-dialog-description.svelte +16 -0
  218. package/dist/components/ui/alert-dialog/alert-dialog-description.svelte.d.ts +7 -0
  219. package/dist/components/ui/alert-dialog/alert-dialog-footer.svelte +11 -0
  220. package/dist/components/ui/alert-dialog/alert-dialog-footer.svelte.d.ts +8 -0
  221. package/dist/components/ui/alert-dialog/alert-dialog-header.svelte +11 -0
  222. package/dist/components/ui/alert-dialog/alert-dialog-header.svelte.d.ts +8 -0
  223. package/dist/components/ui/alert-dialog/alert-dialog-overlay.svelte +16 -0
  224. package/dist/components/ui/alert-dialog/alert-dialog-overlay.svelte.d.ts +7 -0
  225. package/dist/components/ui/alert-dialog/alert-dialog-portal.svelte +6 -0
  226. package/dist/components/ui/alert-dialog/alert-dialog-portal.svelte.d.ts +3 -0
  227. package/dist/components/ui/alert-dialog/alert-dialog-title.svelte +16 -0
  228. package/dist/components/ui/alert-dialog/alert-dialog-title.svelte.d.ts +7 -0
  229. package/dist/components/ui/alert-dialog/index.d.ts +13 -0
  230. package/dist/components/ui/alert-dialog/index.js +13 -0
  231. package/dist/components/ui/avatar/avatar.svelte +63 -0
  232. package/dist/components/ui/avatar/avatar.svelte.d.ts +32 -0
  233. package/dist/components/ui/avatar/index.d.ts +2 -0
  234. package/dist/components/ui/avatar/index.js +2 -0
  235. package/dist/components/ui/badge/badge.svelte +49 -0
  236. package/dist/components/ui/badge/badge.svelte.d.ts +38 -0
  237. package/dist/components/ui/badge/index.d.ts +2 -0
  238. package/dist/components/ui/badge/index.js +3 -0
  239. package/dist/components/ui/breadcrumb/breadcrumb-ellipsis.svelte +18 -0
  240. package/dist/components/ui/breadcrumb/breadcrumb-ellipsis.svelte.d.ts +4 -0
  241. package/dist/components/ui/breadcrumb/breadcrumb-item.svelte +15 -0
  242. package/dist/components/ui/breadcrumb/breadcrumb-item.svelte.d.ts +8 -0
  243. package/dist/components/ui/breadcrumb/breadcrumb-link.svelte +15 -0
  244. package/dist/components/ui/breadcrumb/breadcrumb-link.svelte.d.ts +8 -0
  245. package/dist/components/ui/breadcrumb/breadcrumb-list.svelte +15 -0
  246. package/dist/components/ui/breadcrumb/breadcrumb-list.svelte.d.ts +8 -0
  247. package/dist/components/ui/breadcrumb/breadcrumb-page.svelte +18 -0
  248. package/dist/components/ui/breadcrumb/breadcrumb-page.svelte.d.ts +8 -0
  249. package/dist/components/ui/breadcrumb/breadcrumb-separator.svelte +22 -0
  250. package/dist/components/ui/breadcrumb/breadcrumb-separator.svelte.d.ts +8 -0
  251. package/dist/components/ui/breadcrumb/breadcrumb.svelte +11 -0
  252. package/dist/components/ui/breadcrumb/breadcrumb.svelte.d.ts +8 -0
  253. package/dist/components/ui/breadcrumb/index.d.ts +7 -0
  254. package/dist/components/ui/breadcrumb/index.js +7 -0
  255. package/dist/components/ui/button/button.svelte +90 -0
  256. package/dist/components/ui/button/button.svelte.d.ts +70 -0
  257. package/dist/components/ui/button/index.d.ts +2 -0
  258. package/dist/components/ui/button/index.js +5 -0
  259. package/dist/components/ui/card/card-action.svelte +23 -0
  260. package/dist/components/ui/card/card-action.svelte.d.ts +5 -0
  261. package/dist/components/ui/card/card-content.svelte +20 -0
  262. package/dist/components/ui/card/card-content.svelte.d.ts +5 -0
  263. package/dist/components/ui/card/card-description.svelte +20 -0
  264. package/dist/components/ui/card/card-description.svelte.d.ts +5 -0
  265. package/dist/components/ui/card/card-footer.svelte +20 -0
  266. package/dist/components/ui/card/card-footer.svelte.d.ts +5 -0
  267. package/dist/components/ui/card/card-header.svelte +23 -0
  268. package/dist/components/ui/card/card-header.svelte.d.ts +5 -0
  269. package/dist/components/ui/card/card-title.svelte +15 -0
  270. package/dist/components/ui/card/card-title.svelte.d.ts +5 -0
  271. package/dist/components/ui/card/card.svelte +22 -0
  272. package/dist/components/ui/card/card.svelte.d.ts +8 -0
  273. package/dist/components/ui/card/index.d.ts +8 -0
  274. package/dist/components/ui/card/index.js +10 -0
  275. package/dist/components/ui/checkbox/checkbox.svelte +39 -0
  276. package/dist/components/ui/checkbox/checkbox.svelte.d.ts +4 -0
  277. package/dist/components/ui/checkbox/index.d.ts +2 -0
  278. package/dist/components/ui/checkbox/index.js +4 -0
  279. package/dist/components/ui/collapsible/index.d.ts +5 -0
  280. package/dist/components/ui/collapsible/index.js +5 -0
  281. package/dist/components/ui/command/command-dialog.svelte +31 -0
  282. package/dist/components/ui/command/command-dialog.svelte.d.ts +10 -0
  283. package/dist/components/ui/command/index.d.ts +2 -0
  284. package/dist/components/ui/command/index.js +2 -0
  285. package/dist/components/ui/context-menu/index.d.ts +9 -0
  286. package/dist/components/ui/context-menu/index.js +9 -0
  287. package/dist/components/ui/dialog/dialog-close.svelte +11 -0
  288. package/dist/components/ui/dialog/dialog-close.svelte.d.ts +4 -0
  289. package/dist/components/ui/dialog/dialog-content.svelte +48 -0
  290. package/dist/components/ui/dialog/dialog-content.svelte.d.ts +13 -0
  291. package/dist/components/ui/dialog/dialog-description.svelte +17 -0
  292. package/dist/components/ui/dialog/dialog-description.svelte.d.ts +4 -0
  293. package/dist/components/ui/dialog/dialog-footer.svelte +32 -0
  294. package/dist/components/ui/dialog/dialog-footer.svelte.d.ts +8 -0
  295. package/dist/components/ui/dialog/dialog-header.svelte +20 -0
  296. package/dist/components/ui/dialog/dialog-header.svelte.d.ts +5 -0
  297. package/dist/components/ui/dialog/dialog-overlay.svelte +17 -0
  298. package/dist/components/ui/dialog/dialog-overlay.svelte.d.ts +4 -0
  299. package/dist/components/ui/dialog/dialog-portal.svelte +7 -0
  300. package/dist/components/ui/dialog/dialog-portal.svelte.d.ts +3 -0
  301. package/dist/components/ui/dialog/dialog-title.svelte +17 -0
  302. package/dist/components/ui/dialog/dialog-title.svelte.d.ts +4 -0
  303. package/dist/components/ui/dialog/dialog-trigger.svelte +11 -0
  304. package/dist/components/ui/dialog/dialog-trigger.svelte.d.ts +4 -0
  305. package/dist/components/ui/dialog/dialog.svelte +7 -0
  306. package/dist/components/ui/dialog/dialog.svelte.d.ts +3 -0
  307. package/dist/components/ui/dialog/index.d.ts +11 -0
  308. package/dist/components/ui/dialog/index.js +13 -0
  309. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +43 -0
  310. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte.d.ts +9 -0
  311. package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte +35 -0
  312. package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte.d.ts +8 -0
  313. package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte +36 -0
  314. package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte.d.ts +9 -0
  315. package/dist/components/ui/dropdown-menu/dropdown-menu-separator.svelte +10 -0
  316. package/dist/components/ui/dropdown-menu/dropdown-menu-separator.svelte.d.ts +6 -0
  317. package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +27 -0
  318. package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte.d.ts +11 -0
  319. package/dist/components/ui/dropdown-menu/dropdown-menu.svelte +45 -0
  320. package/dist/components/ui/dropdown-menu/dropdown-menu.svelte.d.ts +9 -0
  321. package/dist/components/ui/dropdown-menu/index.d.ts +7 -0
  322. package/dist/components/ui/dropdown-menu/index.js +7 -0
  323. package/dist/components/ui/input/index.d.ts +2 -0
  324. package/dist/components/ui/input/index.js +4 -0
  325. package/dist/components/ui/input/input.svelte +48 -0
  326. package/dist/components/ui/input/input.svelte.d.ts +13 -0
  327. package/dist/components/ui/label/index.d.ts +1 -0
  328. package/dist/components/ui/label/index.js +1 -0
  329. package/dist/components/ui/label/label.svelte +18 -0
  330. package/dist/components/ui/label/label.svelte.d.ts +5 -0
  331. package/dist/components/ui/pagination/index.d.ts +7 -0
  332. package/dist/components/ui/pagination/index.js +7 -0
  333. package/dist/components/ui/pagination/pagination-content.svelte +11 -0
  334. package/dist/components/ui/pagination/pagination-content.svelte.d.ts +8 -0
  335. package/dist/components/ui/pagination/pagination-ellipsis.svelte +12 -0
  336. package/dist/components/ui/pagination/pagination-ellipsis.svelte.d.ts +4 -0
  337. package/dist/components/ui/pagination/pagination-item.svelte +11 -0
  338. package/dist/components/ui/pagination/pagination-item.svelte.d.ts +8 -0
  339. package/dist/components/ui/pagination/pagination-link.svelte +26 -0
  340. package/dist/components/ui/pagination/pagination-link.svelte.d.ts +9 -0
  341. package/dist/components/ui/pagination/pagination-next.svelte +22 -0
  342. package/dist/components/ui/pagination/pagination-next.svelte.d.ts +8 -0
  343. package/dist/components/ui/pagination/pagination-previous.svelte +22 -0
  344. package/dist/components/ui/pagination/pagination-previous.svelte.d.ts +8 -0
  345. package/dist/components/ui/pagination/pagination.svelte +11 -0
  346. package/dist/components/ui/pagination/pagination.svelte.d.ts +8 -0
  347. package/dist/components/ui/popover/index.d.ts +6 -0
  348. package/dist/components/ui/popover/index.js +6 -0
  349. package/dist/components/ui/popover/popover-content.svelte +25 -0
  350. package/dist/components/ui/popover/popover-content.svelte.d.ts +7 -0
  351. package/dist/components/ui/progress/index.d.ts +1 -0
  352. package/dist/components/ui/progress/index.js +1 -0
  353. package/dist/components/ui/progress/progress.svelte +22 -0
  354. package/dist/components/ui/progress/progress.svelte.d.ts +8 -0
  355. package/dist/components/ui/scroll-area/index.d.ts +1 -0
  356. package/dist/components/ui/scroll-area/index.js +1 -0
  357. package/dist/components/ui/scroll-area/scroll-area.svelte +20 -0
  358. package/dist/components/ui/scroll-area/scroll-area.svelte.d.ts +9 -0
  359. package/dist/components/ui/select/index.d.ts +2 -0
  360. package/dist/components/ui/select/index.js +2 -0
  361. package/dist/components/ui/select/select.svelte +36 -0
  362. package/dist/components/ui/select/select.svelte.d.ts +8 -0
  363. package/dist/components/ui/separator/index.d.ts +2 -0
  364. package/dist/components/ui/separator/index.js +4 -0
  365. package/dist/components/ui/separator/separator.svelte +23 -0
  366. package/dist/components/ui/separator/separator.svelte.d.ts +4 -0
  367. package/dist/components/ui/sheet/index.d.ts +7 -0
  368. package/dist/components/ui/sheet/index.js +7 -0
  369. package/dist/components/ui/sheet/sheet-content.svelte +28 -0
  370. package/dist/components/ui/sheet/sheet-content.svelte.d.ts +8 -0
  371. package/dist/components/ui/sheet/sheet-description.svelte +22 -0
  372. package/dist/components/ui/sheet/sheet-description.svelte.d.ts +6 -0
  373. package/dist/components/ui/sheet/sheet-footer.svelte +22 -0
  374. package/dist/components/ui/sheet/sheet-footer.svelte.d.ts +6 -0
  375. package/dist/components/ui/sheet/sheet-header.svelte +22 -0
  376. package/dist/components/ui/sheet/sheet-header.svelte.d.ts +6 -0
  377. package/dist/components/ui/sheet/sheet-title.svelte +22 -0
  378. package/dist/components/ui/sheet/sheet-title.svelte.d.ts +6 -0
  379. package/dist/components/ui/sheet/sheet.svelte +78 -0
  380. package/dist/components/ui/sheet/sheet.svelte.d.ts +10 -0
  381. package/dist/components/ui/skeleton/index.d.ts +2 -0
  382. package/dist/components/ui/skeleton/index.js +2 -0
  383. package/dist/components/ui/skeleton/skeleton.svelte +21 -0
  384. package/dist/components/ui/skeleton/skeleton.svelte.d.ts +8 -0
  385. package/dist/components/ui/switch/index.d.ts +2 -0
  386. package/dist/components/ui/switch/index.js +2 -0
  387. package/dist/components/ui/switch/switch.svelte +49 -0
  388. package/dist/components/ui/switch/switch.svelte.d.ts +10 -0
  389. package/dist/components/ui/table/index.d.ts +9 -0
  390. package/dist/components/ui/table/index.js +11 -0
  391. package/dist/components/ui/table/table-body.svelte +15 -0
  392. package/dist/components/ui/table/table-body.svelte.d.ts +5 -0
  393. package/dist/components/ui/table/table-caption.svelte +20 -0
  394. package/dist/components/ui/table/table-caption.svelte.d.ts +5 -0
  395. package/dist/components/ui/table/table-cell.svelte +15 -0
  396. package/dist/components/ui/table/table-cell.svelte.d.ts +5 -0
  397. package/dist/components/ui/table/table-footer.svelte +20 -0
  398. package/dist/components/ui/table/table-footer.svelte.d.ts +5 -0
  399. package/dist/components/ui/table/table-head.svelte +15 -0
  400. package/dist/components/ui/table/table-head.svelte.d.ts +5 -0
  401. package/dist/components/ui/table/table-header.svelte +20 -0
  402. package/dist/components/ui/table/table-header.svelte.d.ts +5 -0
  403. package/dist/components/ui/table/table-row.svelte +15 -0
  404. package/dist/components/ui/table/table-row.svelte.d.ts +5 -0
  405. package/dist/components/ui/table/table.svelte +17 -0
  406. package/dist/components/ui/table/table.svelte.d.ts +5 -0
  407. package/dist/components/ui/tabs/index.d.ts +5 -0
  408. package/dist/components/ui/tabs/index.js +5 -0
  409. package/dist/components/ui/tabs/tabs-content.svelte +30 -0
  410. package/dist/components/ui/tabs/tabs-content.svelte.d.ts +9 -0
  411. package/dist/components/ui/tabs/tabs-list.svelte +26 -0
  412. package/dist/components/ui/tabs/tabs-list.svelte.d.ts +6 -0
  413. package/dist/components/ui/tabs/tabs-trigger.svelte +37 -0
  414. package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +9 -0
  415. package/dist/components/ui/tabs/tabs.svelte +27 -0
  416. package/dist/components/ui/tabs/tabs.svelte.d.ts +9 -0
  417. package/dist/components/ui/textarea/index.d.ts +2 -0
  418. package/dist/components/ui/textarea/index.js +2 -0
  419. package/dist/components/ui/textarea/textarea.svelte +24 -0
  420. package/dist/components/ui/textarea/textarea.svelte.d.ts +6 -0
  421. package/dist/components/ui/tooltip/index.d.ts +6 -0
  422. package/dist/components/ui/tooltip/index.js +8 -0
  423. package/dist/components/ui/tooltip/tooltip-content.svelte +52 -0
  424. package/dist/components/ui/tooltip/tooltip-content.svelte.d.ts +11 -0
  425. package/dist/components/ui/tooltip/tooltip-portal.svelte +7 -0
  426. package/dist/components/ui/tooltip/tooltip-portal.svelte.d.ts +4 -0
  427. package/dist/components/ui/tooltip/tooltip-provider.svelte +7 -0
  428. package/dist/components/ui/tooltip/tooltip-provider.svelte.d.ts +4 -0
  429. package/dist/components/ui/tooltip/tooltip-trigger.svelte +7 -0
  430. package/dist/components/ui/tooltip/tooltip-trigger.svelte.d.ts +4 -0
  431. package/dist/components/ui/tooltip/tooltip.svelte +10 -0
  432. package/dist/components/ui/tooltip/tooltip.svelte.d.ts +4 -0
  433. package/dist/editor-config.svelte.d.ts +15 -0
  434. package/dist/editor-config.svelte.js +19 -0
  435. package/dist/index.d.ts +117 -0
  436. package/dist/index.js +127 -0
  437. package/dist/markdown-deps.d.ts +21 -0
  438. package/dist/router-state.svelte.d.ts +5 -0
  439. package/dist/router-state.svelte.js +58 -0
  440. package/dist/types.d.ts +42 -0
  441. package/dist/types.js +1 -0
  442. package/dist/utils.d.ts +16 -0
  443. package/dist/utils.js +5 -0
  444. package/package.json +2 -2
@@ -0,0 +1,839 @@
1
+ <script lang="ts">
2
+ import { untrack } from 'svelte';
3
+ import { cn } from '../utils.js';
4
+ import {
5
+ createTable,
6
+ type ColumnDef,
7
+ type SortingState,
8
+ type RowSelectionState,
9
+ type VisibilityState,
10
+ type ExpandedState,
11
+ } from '@tanstack/svelte-table';
12
+ import { getCoreRowModel } from '@tanstack/table-core';
13
+
14
+ import { useList, useDelete, getResource } from '@svadmin/core';
15
+ import type { Pagination as PaginationState, Sort, Filter, FieldDefinition } from '@svadmin/core';
16
+ import { navigate } from '@svadmin/core/router';
17
+ import { useCan, getAccessControlProvider } from '@svadmin/core';
18
+ import { readURLState, writeURLState } from '@svadmin/core';
19
+ import { t } from '@svadmin/core/i18n';
20
+ import { fade } from 'svelte/transition';
21
+ import { Button } from './ui/button/index.js';
22
+ import { Input } from './ui/input/index.js';
23
+ import { Checkbox } from './ui/checkbox/index.js';
24
+ import { Badge } from './ui/badge/index.js';
25
+ import * as Table from './ui/table/index.js';
26
+ import { Skeleton } from './ui/skeleton/index.js';
27
+ import * as Popover from './ui/popover/index.js';
28
+ import * as DropdownMenu from './ui/dropdown-menu/index.js';
29
+ import * as PaginationUI from './ui/pagination/index.js';
30
+ import * as ContextMenu from './ui/context-menu/index.js';
31
+ import {
32
+ Plus, Pencil, Trash2,
33
+ Search, Download, Upload, ChevronDown, ChevronUp, SlidersHorizontal, Filter as FilterIcon,
34
+ Eye, Copy
35
+ } from '@lucide/svelte';
36
+ import ConfirmDialog from './ConfirmDialog.svelte';
37
+ import TooltipButton from './TooltipButton.svelte';
38
+ import InlineEdit from './InlineEdit.svelte';
39
+ import DraggableHeader from './DraggableHeader.svelte';
40
+ import type { Snippet } from 'svelte';
41
+
42
+ // ─── Props with Snippet composability ─────────────────────────
43
+ interface Props {
44
+ resourceName: string;
45
+ selectable?: boolean;
46
+ /** Custom header actions (right side) */
47
+ headerActions?: Snippet;
48
+ /** Custom cell renderers by field key */
49
+ columns?: Record<string, Snippet<[{ value: unknown; record: Record<string, unknown> }]>>;
50
+ /** Global fallback cell renderer */
51
+ defaultCellRenderer?: Snippet<[{ field: FieldDefinition; value: unknown; record: Record<string, unknown> }]>;
52
+ /** Custom row actions (edit/delete column) */
53
+ rowActions?: Snippet<[{ record: Record<string, unknown>; id: string | number }]>;
54
+ /** Custom empty state */
55
+ emptyState?: Snippet;
56
+ /** Expandable row content */
57
+ expandedRowRender?: Snippet<[{ record: Record<string, unknown> }]>;
58
+ /** Externally controlled pagination */
59
+ pagination?: { current: number; pageSize: number };
60
+ /** Externally controlled sorters */
61
+ sorters?: Sort[];
62
+ /** Custom batch actions to render when rows are selected */
63
+ batchActions?: Snippet<[{ selectedIds: string[] }]>;
64
+ }
65
+
66
+ let {
67
+ resourceName,
68
+ selectable = true,
69
+ headerActions,
70
+ columns: customColumns,
71
+ defaultCellRenderer,
72
+ rowActions,
73
+ emptyState,
74
+ expandedRowRender,
75
+ batchActions,
76
+ pagination: externalPagination,
77
+ sorters: externalSorters,
78
+ }: Props = $props();
79
+
80
+ const resource = $derived(getResource(resourceName));
81
+ const primaryKey = $derived(resource.primaryKey ?? 'id');
82
+
83
+ // ─── URL state + server-side state ────────────────────────────
84
+ const urlState = readURLState();
85
+
86
+ // Snapshot resource values for initial state (untrack to avoid reactive tracking)
87
+ const storedPageSize = typeof localStorage !== 'undefined' ? parseInt(localStorage.getItem('svadmin-default-page-size') ?? '', 10) : NaN;
88
+ const initPageSize = untrack(() => resource.pageSize ?? (isNaN(storedPageSize) ? 10 : storedPageSize));
89
+ const initDefaultSort = untrack(() => resource.defaultSort);
90
+
91
+ let pagination = $state<{ current: number; pageSize: number }>(untrack(() => externalPagination ?? {
92
+ current: urlState.page ?? 1,
93
+ pageSize: urlState.pageSize ?? initPageSize,
94
+ }));
95
+ let sorters = $state<Sort[]>(untrack(() =>
96
+ externalSorters ??
97
+ (urlState.sortField
98
+ ? [{ field: urlState.sortField, order: urlState.sortOrder ?? 'asc' }]
99
+ : initDefaultSort ? [initDefaultSort] : []))
100
+ );
101
+ let filters = $state<Filter[]>([]);
102
+ let searchText = $state(urlState.search ?? '');
103
+
104
+ // URL sync
105
+ $effect(() => {
106
+ writeURLState({
107
+ page: pagination.current,
108
+ pageSize: pagination.pageSize,
109
+ sortField: sorters[0]?.field,
110
+ sortOrder: sorters[0]?.order,
111
+ search: searchText || undefined,
112
+ });
113
+ });
114
+
115
+ // ─── Build active filters with search ─────────────────────────
116
+ const searchableFields = $derived(resource.fields.filter(f => f.searchable));
117
+ const filterableFields = $derived(resource.fields.filter(f => f.filterable));
118
+ let filterValues = $state<Record<string, string>>({});
119
+ const activeFilterCount = $derived(Object.values(filterValues).filter(v => v.trim()).length);
120
+ const activeFilters = $derived.by(() => {
121
+ const result: Filter[] = [...filters];
122
+ if (searchText.trim() && searchableFields.length > 0) {
123
+ if (searchableFields.length === 1) {
124
+ result.push({ field: searchableFields[0].key, operator: 'contains', value: searchText });
125
+ } else {
126
+ result.push({
127
+ operator: 'or',
128
+ value: searchableFields.map(f => ({
129
+ field: f.key,
130
+ operator: 'contains',
131
+ value: searchText
132
+ }))
133
+ } as any);
134
+ }
135
+ }
136
+ // Add popover filters
137
+ for (const [key, value] of Object.entries(filterValues)) {
138
+ if (value.trim()) {
139
+ result.push({ field: key, operator: 'contains', value: value.trim() });
140
+ }
141
+ }
142
+ return result;
143
+ });
144
+
145
+ // ─── Data fetching ────────────────────────────────────────────
146
+ const listResult = useList({
147
+ get resource() { return resourceName; },
148
+ get pagination() { return pagination; },
149
+ get sorters() { return sorters; },
150
+ get filters() { return activeFilters; },
151
+ });
152
+ const query = listResult.query;
153
+ const deleteResult = useDelete({ get resource() { return resourceName; } });
154
+ const deleteMutation = deleteResult.mutation;
155
+
156
+ // ─── Permissions ──────────────────────────────────────────────
157
+ const acEnabled = $derived(!!getAccessControlProvider());
158
+ const canCreatePerm = useCan(() => ({ resource: resourceName, action: 'create', queryOptions: { enabled: acEnabled } }));
159
+ const canEditPerm = useCan(() => ({ resource: resourceName, action: 'edit', queryOptions: { enabled: acEnabled } }));
160
+ const canDeletePerm = useCan(() => ({ resource: resourceName, action: 'delete', queryOptions: { enabled: acEnabled } }));
161
+ const canExportPerm = useCan(() => ({ resource: resourceName, action: 'export', queryOptions: { enabled: acEnabled } }));
162
+ const canCreate = $derived(canCreatePerm.allowed && resource.canCreate !== false);
163
+ const canEdit = $derived(canEditPerm.allowed && resource.canEdit !== false);
164
+ const canDelete = $derived(canDeletePerm.allowed && resource.canDelete !== false);
165
+ const canExport = $derived(canExportPerm.allowed);
166
+
167
+ // ─── TanStack Table state ────────────────────────────────────
168
+ let sorting = $state<SortingState>(untrack(() =>
169
+ sorters.map(s => ({ id: s.field, desc: s.order === 'desc' }))
170
+ ));
171
+ let columnVisibility = $state<VisibilityState>((() => {
172
+ // Try to restore from localStorage first
173
+ const storageKey = `svadmin-columns-${resourceName}`;
174
+ if (typeof localStorage !== 'undefined') {
175
+ try {
176
+ const stored = localStorage.getItem(storageKey);
177
+ if (stored) return JSON.parse(stored);
178
+ } catch { /* ignore */ }
179
+ }
180
+ // Fall back to resource field definitions
181
+ const vis: VisibilityState = {};
182
+ for (const f of resource.fields) {
183
+ if (f.showInList === false) vis[f.key] = false;
184
+ }
185
+ return vis;
186
+ })());
187
+ let rowSelection = $state<RowSelectionState>({});
188
+ let expanded = $state.raw<ExpandedState>({});
189
+ let columnOrder = $state<string[]>([]);
190
+
191
+ // Persist column visibility to localStorage
192
+ $effect(() => {
193
+ const storageKey = `svadmin-columns-${resourceName}`;
194
+ if (typeof localStorage !== 'undefined') {
195
+ try {
196
+ localStorage.setItem(storageKey, JSON.stringify(columnVisibility));
197
+ } catch { /* ignore quota errors */ }
198
+ }
199
+ });
200
+
201
+ // Sync TanStack sorting → server sorters
202
+ $effect(() => {
203
+ const newSorters: Sort[] = sorting.map(s => ({
204
+ field: s.id,
205
+ order: s.desc ? 'desc' as const : 'asc' as const,
206
+ }));
207
+ if (JSON.stringify(newSorters) !== JSON.stringify(sorters)) {
208
+ sorters = newSorters;
209
+ }
210
+ });
211
+
212
+ // ─── Auto-generate columns from resource fields ──────────────
213
+ const visibleFields = $derived(
214
+ resource.fields.filter(f => f.showInList !== false)
215
+ );
216
+
217
+ const columns = $derived<ColumnDef<Record<string, unknown>>[]>([
218
+ // Selection column
219
+ ...(selectable && canDelete ? [{
220
+ id: '_select',
221
+ header: () => '',
222
+ cell: () => '',
223
+ size: 40,
224
+ enableSorting: false,
225
+ } satisfies ColumnDef<Record<string, unknown>>] : []),
226
+ // Expand column
227
+ ...(expandedRowRender ? [{
228
+ id: '_expand',
229
+ header: () => '',
230
+ cell: () => '',
231
+ size: 40,
232
+ enableSorting: false,
233
+ } satisfies ColumnDef<Record<string, unknown>>] : []),
234
+ // Data columns
235
+ ...visibleFields.map((field): ColumnDef<Record<string, unknown>> => ({
236
+ id: field.key,
237
+ accessorKey: field.key,
238
+ header: () => field.label,
239
+ size: field.width ? parseInt(String(field.width)) : undefined,
240
+ })),
241
+ // Actions column
242
+ {
243
+ id: '_actions',
244
+ header: () => t('common.actions'),
245
+ cell: () => '',
246
+ size: 100,
247
+ enableSorting: false,
248
+ },
249
+ ]);
250
+
251
+ // ─── Create TanStack Table ────────────────────────────────────
252
+ const tbl = createTable({
253
+ get data() { return query.data?.data ?? []; },
254
+ get columns() { return columns; },
255
+ getCoreRowModel: getCoreRowModel(),
256
+ manualPagination: true,
257
+ manualSorting: true,
258
+ getRowId: (row) => String(row[primaryKey]),
259
+ state: {
260
+ get sorting() { return sorting; },
261
+ get columnVisibility() { return columnVisibility; },
262
+ get rowSelection() { return rowSelection; },
263
+ get expanded() { return expanded; },
264
+ get columnOrder() { return columnOrder; },
265
+ },
266
+ onSortingChange: (updater) => {
267
+ sorting = typeof updater === 'function' ? updater(sorting) : updater;
268
+ },
269
+ onColumnVisibilityChange: (updater) => {
270
+ columnVisibility = typeof updater === 'function' ? updater(columnVisibility) : updater;
271
+ },
272
+ onRowSelectionChange: (updater) => {
273
+ rowSelection = typeof updater === 'function' ? updater(rowSelection) : updater;
274
+ },
275
+ onExpandedChange: (updater) => {
276
+ expanded = typeof updater === 'function' ? updater(expanded) : updater;
277
+ },
278
+ onColumnOrderChange: (updater) => {
279
+ columnOrder = typeof updater === 'function' ? updater(columnOrder) : updater;
280
+ },
281
+ get enableRowSelection() { return selectable && canDelete; },
282
+ get enableExpanding() { return !!expandedRowRender; },
283
+ });
284
+
285
+ const selectedCount = $derived(Object.keys(rowSelection).length);
286
+ const totalPages = $derived(Math.ceil((query.data?.total ?? 0) / (pagination.pageSize ?? 10)));
287
+
288
+ // ─── Pagination helpers ───────────────────────────────────────
289
+ const currentPage = $derived(pagination.current ?? 1);
290
+ const pages = $derived.by(() => {
291
+ const p: (number | '...')[] = [];
292
+ if (totalPages <= 7) {
293
+ for (let i = 1; i <= totalPages; i++) p.push(i);
294
+ } else {
295
+ p.push(1);
296
+ if (currentPage > 3) p.push('...');
297
+ const start = Math.max(2, currentPage - 1);
298
+ const end = Math.min(totalPages - 1, currentPage + 1);
299
+ for (let i = start; i <= end; i++) p.push(i);
300
+ if (currentPage < totalPages - 2) p.push('...');
301
+ p.push(totalPages);
302
+ }
303
+ return p;
304
+ });
305
+
306
+ // ─── Confirm dialog ───────────────────────────────────────────
307
+ let confirmOpen = $state(false);
308
+ let confirmMessage = $state('');
309
+ let confirmAction = $state<() => void>(() => {});
310
+
311
+ function confirmDelete(id: string | number) {
312
+ confirmMessage = t('common.deleteConfirm');
313
+ confirmAction = async () => {
314
+ await deleteMutation.mutateAsync({ id, resource: resourceName });
315
+ confirmOpen = false;
316
+ };
317
+ confirmOpen = true;
318
+ }
319
+
320
+ function confirmBatchDelete() {
321
+ const ids = Object.keys(rowSelection);
322
+ confirmMessage = t('common.batchDeleteConfirm', { count: ids.length });
323
+ confirmAction = async () => {
324
+ await Promise.allSettled(ids.map(id => deleteMutation.mutateAsync({ id, resource: resourceName })));
325
+ rowSelection = {};
326
+ confirmOpen = false;
327
+ };
328
+ confirmOpen = true;
329
+ }
330
+
331
+ // ─── CSV Export ───────────────────────────────────────────────
332
+ function exportCSV() {
333
+ const data = query.data?.data ?? [];
334
+ if (data.length === 0) return;
335
+ const headers = visibleFields.map(f => f.label);
336
+ const rows = data.map(record =>
337
+ visibleFields.map(f => {
338
+ const val = record[f.key];
339
+ if (val == null) return '';
340
+ if (typeof val === 'object') return JSON.stringify(val);
341
+ return String(val);
342
+ })
343
+ );
344
+ const csv = [headers.join(','), ...rows.map(r => r.map(v => `"${v.replace(new RegExp('"', 'g'), '""')}"`).join(','))].join('\n');
345
+ const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' });
346
+ const link = document.createElement('a');
347
+ link.href = URL.createObjectURL(blob);
348
+ link.download = `${resourceName}_${new Date().toISOString().slice(0, 10)}.csv`;
349
+ link.click();
350
+ URL.revokeObjectURL(link.href);
351
+ }
352
+
353
+ function goToPage(page: number) {
354
+ pagination = { ...pagination, current: page };
355
+ }
356
+ </script>
357
+
358
+ <div class="space-y-4">
359
+ <!-- Header -->
360
+ <div class="flex flex-wrap items-center justify-between gap-2">
361
+ <h1 class="text-lg sm:text-xl font-semibold text-foreground">{resource.label}</h1>
362
+ <div class="flex flex-wrap items-center gap-2">
363
+ {#if canExport}
364
+ <Button variant="outline" size="sm" onclick={exportCSV}>
365
+ <Download class="h-4 w-4" data-icon="inline-start" /> {t('common.export')}
366
+ </Button>
367
+ {/if}
368
+ {#if selectedCount > 0 && canDelete}
369
+ <Button
370
+ variant="destructive"
371
+ size="sm"
372
+ class="h-9 whitespace-nowrap"
373
+ onclick={confirmBatchDelete}
374
+ >
375
+ <Trash2 class="h-4 w-4" data-icon="inline-start" /> {t('common.batchDelete', { count: selectedCount })}
376
+ </Button>
377
+ {/if}
378
+
379
+ {#if selectedCount > 0 && batchActions}
380
+ {@render batchActions({ selectedIds: Object.keys(rowSelection) })}
381
+ {/if}
382
+
383
+ <!-- Column Visibility Picker (DropdownMenu) -->
384
+ <DropdownMenu.Root>
385
+ <DropdownMenu.Trigger>
386
+ {#snippet child({ props })}
387
+ <Button variant="outline" size="sm" {...props}>
388
+ <SlidersHorizontal class="h-4 w-4" data-icon="inline-start" /> {t('common.columns') || 'Columns'}
389
+ </Button>
390
+ {/snippet}
391
+ </DropdownMenu.Trigger>
392
+ <DropdownMenu.Content align="end" class="w-48">
393
+ {#each tbl.getAllLeafColumns().filter(c => !c.id.startsWith('_')) as column}
394
+ <DropdownMenu.CheckboxItem
395
+ checked={column.getIsVisible()}
396
+ onCheckedChange={(v) => column.toggleVisibility(!!v)}
397
+ >
398
+ {visibleFields.find(f => f.key === column.id)?.label ?? column.id}
399
+ </DropdownMenu.CheckboxItem>
400
+ {/each}
401
+ </DropdownMenu.Content>
402
+ </DropdownMenu.Root>
403
+ {#if headerActions}
404
+ {@render headerActions()}
405
+ {/if}
406
+ {#if canCreate}
407
+ <Button onclick={() => navigate(`/${resourceName}/create`)}>
408
+ <Plus class="h-4 w-4" data-icon="inline-start" /> {t('common.create')}
409
+ </Button>
410
+ {/if}
411
+ </div>
412
+ </div>
413
+
414
+ <!-- Search and Advanced Filters -->
415
+ <div class="flex flex-wrap items-center gap-2">
416
+ {#if searchableFields.length > 0}
417
+ <div class="relative max-w-sm flex-1 sm:min-w-[250px]">
418
+ <Search class="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
419
+ <Input
420
+ type="text"
421
+ bind:value={searchText}
422
+ oninput={() => { pagination = { ...pagination, current: 1 }; }}
423
+ placeholder={t('common.search')}
424
+ class="pl-10 h-9"
425
+ />
426
+ </div>
427
+ {/if}
428
+
429
+ {#if filterableFields.length > 0}
430
+ <Popover.Root>
431
+ <Popover.Trigger>
432
+ {#snippet child({ props })}
433
+ <Button variant="outline" size="sm" class="h-9 px-3" {...props}>
434
+ <FilterIcon class="h-4 w-4" data-icon="inline-start" />
435
+ {t('common.filter')}
436
+ {#if activeFilterCount > 0}
437
+ <Badge variant="secondary" class="ml-1 h-5 min-w-5 px-1">{activeFilterCount}</Badge>
438
+ {/if}
439
+ </Button>
440
+ {/snippet}
441
+ </Popover.Trigger>
442
+ <Popover.Content class="w-80">
443
+ <div class="space-y-3">
444
+ <h4 class="font-medium text-sm">{t('common.filter')}</h4>
445
+ {#each filterableFields as field}
446
+ <div class="space-y-1">
447
+ <label class="text-xs text-muted-foreground" for="filter-{field.key}">{field.label}</label>
448
+ {#if field.type === 'select' && field.options}
449
+ <select
450
+ id="filter-{field.key}"
451
+ class="h-9 text-sm w-full font-normal flex w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
452
+ value={filterValues[field.key] ?? ''}
453
+ onchange={(e) => filterValues[field.key] = (e.currentTarget as HTMLSelectElement).value}
454
+ >
455
+ <option value="">全部</option>
456
+ {#each field.options as opt}
457
+ <option value={opt.value}>{opt.label}</option>
458
+ {/each}
459
+ </select>
460
+ {:else}
461
+ <Input
462
+ id="filter-{field.key}"
463
+ type="text"
464
+ value={filterValues[field.key] ?? ''}
465
+ oninput={(e) => filterValues[field.key] = (e.currentTarget as HTMLInputElement).value}
466
+ placeholder={field.label}
467
+ class="h-9 text-sm"
468
+ />
469
+ {/if}
470
+ </div>
471
+ {/each}
472
+ <div class="flex gap-2 pt-2">
473
+ <Button size="sm" class="flex-1" onclick={() => { pagination = { ...pagination, current: 1 }; }}>
474
+ {t('common.confirm')}
475
+ </Button>
476
+ <Button variant="outline" size="sm" onclick={() => {
477
+ filterValues = {};
478
+ pagination = { ...pagination, current: 1 };
479
+ }}>
480
+ {t('common.reset')}
481
+ </Button>
482
+ </div>
483
+ </div>
484
+ </Popover.Content>
485
+ </Popover.Root>
486
+ {/if}
487
+ </div>
488
+
489
+ <!-- Table (TanStack-powered) -->
490
+ <div class="rounded-lg bg-card shadow-sm ring-1 ring-border/10 overflow-hidden" role="region" aria-label="{resource.label} {t('common.list')}">
491
+ {#if query.isLoading}
492
+ <div class="p-4 space-y-3">
493
+ <div class="flex gap-4 mb-2">
494
+ {#each visibleFields.slice(0, 4) as _}
495
+ <Skeleton class="h-4 flex-1" />
496
+ {/each}
497
+ </div>
498
+ {#each Array(5) as _}
499
+ <div class="flex gap-4">
500
+ {#each visibleFields.slice(0, 4) as __}
501
+ <Skeleton class="h-8 flex-1" />
502
+ {/each}
503
+ </div>
504
+ {/each}
505
+ </div>
506
+ {:else if query.error}
507
+ <div class="flex h-64 items-center justify-center text-destructive text-sm">
508
+ {t('common.loadFailed', { message: (query.error as Error).message })}
509
+ </div>
510
+ {:else}
511
+ <div in:fade={{ duration: 150 }}>
512
+ <!-- Desktop Table (hidden on mobile) -->
513
+ <div class="hidden md:block">
514
+ <Table.Root>
515
+ <Table.Header>
516
+ {#each tbl.getHeaderGroups() as headerGroup}
517
+ <DraggableHeader
518
+ columns={headerGroup.headers.map(h => ({ id: h.column.id, header: h }))}
519
+ resourceName={resourceName}
520
+ onReorder={(newOrder) => { columnOrder = newOrder.map(c => c.id); }}
521
+ >
522
+ {#snippet header(col, index, dragProps)}
523
+ {@const header = col.header as typeof headerGroup.headers[0]}
524
+ <Table.Head
525
+ {...dragProps}
526
+ class={cn("bg-muted/50 hover:bg-muted/50", dragProps.class)}
527
+ style={header.getSize() !== 150 ? `width:${header.getSize()}px` : undefined}
528
+ >
529
+ {#if header.id === '_select'}
530
+ <Checkbox
531
+ checked={tbl.getIsAllRowsSelected()}
532
+ onCheckedChange={() => tbl.toggleAllRowsSelected()}
533
+ />
534
+ {:else if header.id === '_expand'}
535
+ <!-- empty -->
536
+ {:else if header.id === '_actions'}
537
+ <span class="text-right block">{t('common.actions')}</span>
538
+ {:else if header.column.getCanSort()}
539
+ <Button
540
+ variant="ghost"
541
+ size="sm"
542
+ class="flex items-center gap-1 hover:text-foreground -ml-3 h-auto py-1 px-2"
543
+ onclick={() => header.column.toggleSorting()}
544
+ >
545
+ {visibleFields.find(f => f.key === header.id)?.label ?? header.id}
546
+ <span class="text-xs opacity-50">
547
+ {#if header.column.getIsSorted() === 'asc'}↑
548
+ {:else if header.column.getIsSorted() === 'desc'}↓
549
+ {:else}⇅
550
+ {/if}
551
+ </span>
552
+ </Button>
553
+ {:else}
554
+ {visibleFields.find(f => f.key === header.id)?.label ?? header.id}
555
+ {/if}
556
+ </Table.Head>
557
+ {/snippet}
558
+ </DraggableHeader>
559
+ {/each}
560
+ </Table.Header>
561
+ <Table.Body>
562
+ {#each tbl.getRowModel().rows as row}
563
+ {@const record = row.original}
564
+ {@const id = record[primaryKey] as string | number}
565
+ <ContextMenu.Root>
566
+ <ContextMenu.Trigger>
567
+ {#snippet child({ props })}
568
+ <Table.Row {...props} class="transition-colors border-0 even:bg-muted/20 {row.getIsSelected() ? 'bg-accent/50' : 'hover:bg-muted/40'}">
569
+ {#each row.getVisibleCells() as cell}
570
+ <Table.Cell>
571
+ {#if cell.column.id === '_select'}
572
+ <Checkbox
573
+ checked={row.getIsSelected()}
574
+ onCheckedChange={() => row.toggleSelected()}
575
+ />
576
+ {:else if cell.column.id === '_expand'}
577
+ <TooltipButton tooltip={row.getIsExpanded() ? t('common.collapse') : t('common.expand')} variant="ghost" size="icon" class="h-7 w-7" onclick={() => row.toggleExpanded()}>
578
+ {#if row.getIsExpanded()}
579
+ <ChevronUp class="h-4 w-4" />
580
+ {:else}
581
+ <ChevronDown class="h-4 w-4" />
582
+ {/if}
583
+ </TooltipButton>
584
+ {:else if cell.column.id === '_actions'}
585
+ <div class="flex items-center justify-end gap-1">
586
+ {#if rowActions}
587
+ {@render rowActions({ record, id })}
588
+ {:else}
589
+ {#if canEdit}
590
+ <TooltipButton tooltip={t('common.edit')} variant="ghost" size="icon-sm" onclick={() => navigate(`/${resourceName}/edit/${id}`)}>
591
+ <Pencil class="h-4 w-4" />
592
+ </TooltipButton>
593
+ {/if}
594
+ {#if canDelete}
595
+ <TooltipButton tooltip={t('common.delete')} variant="ghost" size="icon-sm" onclick={() => confirmDelete(id)} class="hover:text-destructive">
596
+ <Trash2 class="h-4 w-4" />
597
+ </TooltipButton>
598
+ {/if}
599
+ {/if}
600
+ </div>
601
+ {:else}
602
+ {@const field = visibleFields.find(f => f.key === cell.column.id)}
603
+ {#if customColumns && field && customColumns[field.key]}
604
+ {@render customColumns[field.key]({ value: cell.getValue(), record })}
605
+ {:else if defaultCellRenderer && field}
606
+ {@render defaultCellRenderer({ field, value: cell.getValue(), record })}
607
+ {:else if field?.type === 'boolean'}
608
+ <span class="inline-block h-2 w-2 rounded-full {cell.getValue() ? 'bg-success' : 'bg-muted-foreground/30'}"></span>
609
+ {:else if field?.type === 'date' && cell.getValue()}
610
+ {new Date(cell.getValue() as string).toLocaleDateString()}
611
+ {:else if field?.type === 'tags' && Array.isArray(cell.getValue())}
612
+ <div class="flex flex-wrap gap-1">
613
+ {#each (cell.getValue() as string[]).slice(0, 3) as tag}
614
+ <Badge variant="secondary">{tag}</Badge>
615
+ {/each}
616
+ </div>
617
+ {:else if field?.type === 'select' && field.options}
618
+ {@const opt = field.options.find(o => o.value === cell.getValue())}
619
+ <Badge variant="outline">{opt?.label ?? cell.getValue() ?? '—'}</Badge>
620
+ {:else if canEdit && field && ['text', 'number', 'email', 'url'].includes(field.type)}
621
+ <InlineEdit
622
+ {resourceName}
623
+ recordId={id}
624
+ {field}
625
+ value={cell.getValue()}
626
+ onSave={() => listResult.query.refetch()}
627
+ />
628
+ {:else if field?.key === primaryKey}
629
+ <span class="font-mono text-xs">{cell.getValue() ?? '—'}</span>
630
+ {:else}
631
+ {cell.getValue() ?? '—'}
632
+ {/if}
633
+ {/if}
634
+ </Table.Cell>
635
+ {/each}
636
+ </Table.Row>
637
+ {/snippet}
638
+ </ContextMenu.Trigger>
639
+ <ContextMenu.Content class="w-48">
640
+ {#if canEdit}
641
+ <ContextMenu.Item onclick={() => navigate(`/${resourceName}/edit/${id}`)} class="gap-2">
642
+ <Pencil class="h-4 w-4" /> {t('common.edit')}
643
+ </ContextMenu.Item>
644
+ {/if}
645
+ <ContextMenu.Item onclick={() => navigate(`/${resourceName}/show/${id}`)} class="gap-2">
646
+ <Eye class="h-4 w-4" /> {t('common.detail')}
647
+ </ContextMenu.Item>
648
+ <ContextMenu.Item onclick={() => navigator.clipboard?.writeText(String(id))} class="gap-2">
649
+ <Copy class="h-4 w-4" /> {t('common.copyId')}
650
+ </ContextMenu.Item>
651
+ {#if canDelete}
652
+ <ContextMenu.Separator />
653
+ <ContextMenu.Item onclick={() => confirmDelete(id)} class="gap-2 text-destructive">
654
+ <Trash2 class="h-4 w-4" /> {t('common.delete')}
655
+ </ContextMenu.Item>
656
+ {/if}
657
+ </ContextMenu.Content>
658
+ </ContextMenu.Root>
659
+ {#if expandedRowRender && row.getIsExpanded()}
660
+ <Table.Row class="bg-muted/30">
661
+ <Table.Cell colspan={row.getVisibleCells().length}>
662
+ {@render expandedRowRender({ record })}
663
+ </Table.Cell>
664
+ </Table.Row>
665
+ {/if}
666
+ {:else}
667
+ <Table.Row>
668
+ <Table.Cell colspan={columns.length} class="h-48 text-center">
669
+ {#if emptyState}
670
+ {@render emptyState()}
671
+ {:else}
672
+ <div class="flex flex-col items-center justify-center py-8">
673
+ <svg class="h-16 w-16 text-muted-foreground/30 mb-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor">
674
+ <path stroke-linecap="round" stroke-linejoin="round" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
675
+ </svg>
676
+ <p class="text-sm font-medium text-muted-foreground mb-1">{t('common.noData')}</p>
677
+ <p class="text-xs text-muted-foreground/60 mb-4">{t('common.noDataHint') || 'Get started by creating your first record.'}</p>
678
+ {#if canCreate}
679
+ <Button variant="outline" size="sm" class="gap-2" onclick={() => navigate(`/${resourceName}/create`)}>
680
+ <Plus class="h-3.5 w-3.5" />
681
+ {t('common.create')}
682
+ </Button>
683
+ {/if}
684
+ </div>
685
+ {/if}
686
+ </Table.Cell>
687
+ </Table.Row>
688
+ {/each}
689
+ </Table.Body>
690
+ </Table.Root>
691
+ </div>
692
+
693
+ <!-- Mobile Card View (visible only on small screens) -->
694
+ <div class="md:hidden space-y-3 p-2">
695
+ {#each tbl.getRowModel().rows as row}
696
+ {@const record = row.original}
697
+ {@const id = record[primaryKey] as string | number}
698
+ <div
699
+ class="rounded-xl shadow-sm ring-1 ring-border/10 bg-card p-4 transition-colors {row.getIsSelected() ? 'ring-2 ring-primary bg-accent/30' : ''}"
700
+ >
701
+ <!-- Card header: ID + select + actions -->
702
+ <div class="flex items-center justify-between mb-3">
703
+ <div class="flex items-center gap-2">
704
+ {#if selectable && canDelete}
705
+ <Checkbox
706
+ checked={row.getIsSelected()}
707
+ onCheckedChange={() => row.toggleSelected()}
708
+ />
709
+ {/if}
710
+ <span class="text-xs font-mono text-muted-foreground">#{id}</span>
711
+ </div>
712
+ <div class="flex items-center gap-1">
713
+ {#if rowActions}
714
+ {@render rowActions({ record, id })}
715
+ {:else}
716
+ {#if canEdit}
717
+ <TooltipButton tooltip={t('common.edit')} variant="ghost" size="icon-sm" onclick={() => navigate(`/${resourceName}/edit/${id}`)}>
718
+ <Pencil class="h-4 w-4" />
719
+ </TooltipButton>
720
+ {/if}
721
+ <TooltipButton tooltip={t('common.detail')} variant="ghost" size="icon-sm" onclick={() => navigate(`/${resourceName}/show/${id}`)}>
722
+ <Eye class="h-4 w-4" />
723
+ </TooltipButton>
724
+ {#if canDelete}
725
+ <TooltipButton tooltip={t('common.delete')} variant="ghost" size="icon-sm" onclick={() => confirmDelete(id)} class="hover:text-destructive">
726
+ <Trash2 class="h-4 w-4" />
727
+ </TooltipButton>
728
+ {/if}
729
+ {/if}
730
+ </div>
731
+ </div>
732
+ <!-- Card fields -->
733
+ <div class="space-y-2">
734
+ {#each visibleFields.slice(0, 6) as field}
735
+ {@const value = record[field.key]}
736
+ <div class="flex items-start justify-between gap-4">
737
+ <span class="text-xs font-medium text-muted-foreground shrink-0">{field.label}</span>
738
+ <span class="text-sm text-right truncate max-w-[60%]">
739
+ {#if customColumns && field && customColumns[field.key]}
740
+ {@render customColumns[field.key]({ value, record })}
741
+ {:else if defaultCellRenderer && field}
742
+ {@render defaultCellRenderer({ field, value, record })}
743
+ {:else if field.type === 'boolean'}
744
+ <span class="inline-block h-2 w-2 rounded-full {value ? 'bg-success' : 'bg-muted-foreground/30'}"></span>
745
+ {:else if field.type === 'date' && value}
746
+ {new Date(value as string).toLocaleDateString()}
747
+ {:else if field.type === 'tags' && Array.isArray(value)}
748
+ <div class="flex flex-wrap gap-1 justify-end">
749
+ {#each (value as string[]).slice(0, 2) as tag}
750
+ <Badge variant="secondary" class="text-[10px]">{tag}</Badge>
751
+ {/each}
752
+ </div>
753
+ {:else if field.type === 'select' && field.options}
754
+ {@const opt = field.options.find(o => o.value === value)}
755
+ <Badge variant="outline" class="text-[10px]">{opt?.label ?? value ?? '—'}</Badge>
756
+ {:else}
757
+ {value ?? '—'}
758
+ {/if}
759
+ </span>
760
+ </div>
761
+ {/each}
762
+ </div>
763
+ </div>
764
+ {:else}
765
+ <div class="text-center py-10 text-muted-foreground">
766
+ {#if emptyState}
767
+ {@render emptyState()}
768
+ {:else}
769
+ {t('common.noData')}
770
+ {/if}
771
+ </div>
772
+ {/each}
773
+ </div>
774
+ </div>
775
+ {/if}
776
+ </div>
777
+
778
+ <!-- Pagination (shadcn) -->
779
+ {#if totalPages > 0}
780
+ <div class="flex flex-col sm:flex-row items-center justify-between gap-2 text-sm text-muted-foreground">
781
+ <div class="flex items-center gap-2">
782
+ <span>{t('common.total', { total: query.data?.total ?? 0 })}</span>
783
+ <select
784
+ class="h-8 w-[70px] px-1 py-1 flex items-center justify-between rounded-md border border-input bg-background text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
785
+ value={String(pagination.pageSize ?? 10)}
786
+ onchange={(e) => {
787
+ const size = Number((e.target as HTMLSelectElement).value);
788
+ if (!isNaN(size)) {
789
+ pagination = { ...pagination, pageSize: size, current: 1 };
790
+ }
791
+ }}
792
+ >
793
+ <option value="10">10</option>
794
+ <option value="20">20</option>
795
+ <option value="50">50</option>
796
+ <option value="100">100</option>
797
+ </select>
798
+ </div>
799
+ <PaginationUI.Root>
800
+ <PaginationUI.Content>
801
+ <PaginationUI.Item>
802
+ <PaginationUI.Previous
803
+ onclick={() => goToPage(Math.max(1, currentPage - 1))}
804
+ disabled={currentPage <= 1}
805
+ />
806
+ </PaginationUI.Item>
807
+ {#each pages as page}
808
+ <PaginationUI.Item>
809
+ {#if page === '...'}
810
+ <PaginationUI.Ellipsis />
811
+ {:else}
812
+ <PaginationUI.Link
813
+ isActive={page === currentPage}
814
+ onclick={() => goToPage(page as number)}
815
+ >
816
+ {page}
817
+ </PaginationUI.Link>
818
+ {/if}
819
+ </PaginationUI.Item>
820
+ {/each}
821
+ <PaginationUI.Item>
822
+ <PaginationUI.Next
823
+ onclick={() => goToPage(Math.min(totalPages, currentPage + 1))}
824
+ disabled={currentPage >= totalPages}
825
+ />
826
+ </PaginationUI.Item>
827
+ </PaginationUI.Content>
828
+ </PaginationUI.Root>
829
+ </div>
830
+ {/if}
831
+ </div>
832
+
833
+ <ConfirmDialog
834
+ open={confirmOpen}
835
+ message={confirmMessage}
836
+ confirmText={t('common.delete')}
837
+ onconfirm={confirmAction}
838
+ oncancel={() => { confirmOpen = false; }}
839
+ />