@svadmin/ui 0.70.0 → 0.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (372) hide show
  1. package/README.md +40 -25
  2. package/THIRD_PARTY_NOTICES.md +52 -0
  3. package/dist/actions.js +1 -1
  4. package/dist/app.css +7804 -5188
  5. package/dist/app.theme.css +7738 -5173
  6. package/dist/class-properties.generated.d.ts +1 -0
  7. package/dist/class-properties.generated.js +2 -0
  8. package/dist/classnames.d.ts +8 -0
  9. package/dist/classnames.js +26 -0
  10. package/dist/component-registry.svelte.d.ts +33 -19
  11. package/dist/component-registry.svelte.js +6 -4
  12. package/dist/components/AdminApp.svelte +55 -53
  13. package/dist/components/AdminApp.svelte.d.ts +2 -2
  14. package/dist/components/ApiSettings.svelte +19 -1
  15. package/dist/components/AuditLogDrawer.svelte +14 -3
  16. package/dist/components/AutoForm.svelte +162 -50
  17. package/dist/components/AutoForm.svelte.d.ts +4 -0
  18. package/dist/components/AutoTable.svelte +599 -259
  19. package/dist/components/AutoTable.svelte.d.ts +6 -0
  20. package/dist/components/BoundRecordDetailDrawer.svelte +172 -0
  21. package/dist/components/BoundRecordDetailDrawer.svelte.d.ts +11 -0
  22. package/dist/components/Breadcrumbs.svelte +7 -4
  23. package/dist/components/CancelTaskButton.svelte +9 -5
  24. package/dist/components/ColumnHeaderFilter.svelte +4 -3
  25. package/dist/components/ColumnSettings.svelte +1 -0
  26. package/dist/components/ComboboxField.svelte +118 -36
  27. package/dist/components/ComboboxField.svelte.d.ts +5 -4
  28. package/dist/components/CreatePage.svelte +7 -1
  29. package/dist/components/CreatePage.svelte.d.ts +2 -0
  30. package/dist/components/DateRangeInput.svelte +75 -0
  31. package/dist/components/DateRangeInput.svelte.d.ts +25 -0
  32. package/dist/components/DateTimeInput.svelte +65 -0
  33. package/dist/components/DateTimeInput.svelte.d.ts +19 -0
  34. package/dist/components/DetailDrawer.svelte +4 -0
  35. package/dist/components/DetailDrawer.svelte.d.ts +1 -0
  36. package/dist/components/DevTools.svelte +8 -21
  37. package/dist/components/DraggableGrid.svelte +1 -0
  38. package/dist/components/DraggableHeader.svelte +5 -9
  39. package/dist/components/DraggableHeader.svelte.d.ts +43 -18
  40. package/dist/components/DraggableRowTable.svelte +2 -2
  41. package/dist/components/DynamicFormList.svelte +17 -17
  42. package/dist/components/DynamicFormList.svelte.d.ts +2 -1
  43. package/dist/components/EditPage.svelte +7 -1
  44. package/dist/components/EditPage.svelte.d.ts +2 -0
  45. package/dist/components/EditableTable.svelte +4 -0
  46. package/dist/components/ErrorPage.svelte +3 -1
  47. package/dist/components/FieldDisplay.svelte +14 -0
  48. package/dist/components/FieldDisplay.svelte.d.ts +7 -0
  49. package/dist/components/FieldRenderer.svelte +96 -20
  50. package/dist/components/FileUpload.svelte +250 -0
  51. package/dist/components/FileUpload.svelte.d.ts +32 -0
  52. package/dist/components/FilterBuilder.svelte +236 -268
  53. package/dist/components/FilterBuilder.svelte.d.ts +3 -0
  54. package/dist/components/FilterDropdown.svelte.d.ts +1 -1
  55. package/dist/components/Header.svelte +5 -6
  56. package/dist/components/ImportWizard.svelte +182 -94
  57. package/dist/components/InfiniteList.svelte +22 -8
  58. package/dist/components/InfiniteList.svelte.d.ts +5 -3
  59. package/dist/components/InlineEdit.svelte +114 -44
  60. package/dist/components/JsonSchemaForm.svelte +256 -95
  61. package/dist/components/JsonSchemaForm.svelte.d.ts +12 -1
  62. package/dist/components/Layout.svelte +20 -17
  63. package/dist/components/LazyTaskQueueDrawer.svelte +26 -0
  64. package/dist/components/LazyTaskQueueDrawer.svelte.d.ts +8 -0
  65. package/dist/components/ListPage.svelte +11 -6
  66. package/dist/components/ListPage.svelte.d.ts +2 -0
  67. package/dist/components/LoginPage.svelte +1 -1
  68. package/dist/components/MasterDetailView.svelte +3 -3
  69. package/dist/components/MediaLibraryModal.svelte +5 -2
  70. package/dist/components/MentionsInput.svelte +4 -3
  71. package/dist/components/MoneyInput.svelte +25 -0
  72. package/dist/components/MoneyInput.svelte.d.ts +20 -0
  73. package/dist/components/NavigateToResource.svelte +3 -2
  74. package/dist/components/NumberInput.svelte +25 -0
  75. package/dist/components/NumberInput.svelte.d.ts +19 -0
  76. package/dist/components/NumericInput.svelte +110 -0
  77. package/dist/components/NumericInput.svelte.d.ts +23 -0
  78. package/dist/components/PercentInput.svelte +25 -0
  79. package/dist/components/PercentInput.svelte.d.ts +20 -0
  80. package/dist/components/PermissionMatrix.svelte +4 -3
  81. package/dist/components/ProfilePage.svelte +2 -2
  82. package/dist/components/QuickEditDrawer.svelte +42 -0
  83. package/dist/components/QuickEditDrawer.svelte.d.ts +10 -0
  84. package/dist/components/RecordDetailDrawer.svelte +16 -108
  85. package/dist/components/RecordDetailDrawer.svelte.d.ts +2 -0
  86. package/dist/components/RecordRowActions.svelte +41 -0
  87. package/dist/components/RecordRowActions.svelte.d.ts +14 -0
  88. package/dist/components/ResourceAccessGuard.svelte +7 -5
  89. package/dist/components/ResourceOperationsPage.svelte +25 -19
  90. package/dist/components/ResourceOperationsPage.svelte.d.ts +5 -1
  91. package/dist/components/RetryTaskButton.svelte +9 -5
  92. package/dist/components/RowActions.svelte +5 -3
  93. package/dist/components/SensitiveDataMask.svelte +3 -3
  94. package/dist/components/SettingsPage.svelte +1 -1
  95. package/dist/components/ShowInferencer.svelte +4 -2
  96. package/dist/components/ShowInferencer.svelte.d.ts +2 -0
  97. package/dist/components/ShowPage.svelte +49 -39
  98. package/dist/components/ShowPage.svelte.d.ts +2 -0
  99. package/dist/components/Sidebar.svelte +15 -13
  100. package/dist/components/SplitPaneLayout.svelte +4 -2
  101. package/dist/components/SpreadsheetView.svelte +81 -286
  102. package/dist/components/StepsForm.svelte +166 -49
  103. package/dist/components/StepsForm.svelte.d.ts +1 -0
  104. package/dist/components/SvarAutoTable.svelte +39 -0
  105. package/dist/components/SvarAutoTable.svelte.d.ts +13 -0
  106. package/dist/components/SvarDataGrid.svelte +362 -0
  107. package/dist/components/SvarDataGrid.svelte.d.ts +57 -0
  108. package/dist/components/SvarGridCell.svelte +10 -0
  109. package/dist/components/SvarGridCell.svelte.d.ts +7 -0
  110. package/dist/components/SvarResourceRead.svelte +50 -0
  111. package/dist/components/SvarResourceRead.svelte.d.ts +23 -0
  112. package/dist/components/SvarResourceTable.svelte +63 -0
  113. package/dist/components/SvarResourceTable.svelte.d.ts +42 -0
  114. package/dist/components/SvarResourceTableView.svelte +364 -0
  115. package/dist/components/SvarResourceTableView.svelte.d.ts +4 -0
  116. package/dist/components/TaskDetails.svelte +20 -7
  117. package/dist/components/TaskList.svelte +19 -7
  118. package/dist/components/TaskQueueDrawer.svelte +92 -37
  119. package/dist/components/Toast.svelte +4 -2
  120. package/dist/components/TooltipButton.svelte +1 -1
  121. package/dist/components/TreeSelect.svelte +212 -30
  122. package/dist/components/TreeSelect.svelte.d.ts +3 -1
  123. package/dist/components/ValidatedField.svelte +27 -0
  124. package/dist/components/ValidatedField.svelte.d.ts +35 -0
  125. package/dist/components/account/ImportMembersPage.svelte +2 -2
  126. package/dist/components/account/SecurityLogPage.svelte +3 -1
  127. package/dist/components/account/SettingsEnterprisePage.svelte +7 -5
  128. package/dist/components/account/TeamMembersPage.svelte +1 -1
  129. package/dist/components/auto-table-grid.d.ts +26 -0
  130. package/dist/components/auto-table-grid.js +1 -0
  131. package/dist/components/buttons/CloneButton.svelte +6 -4
  132. package/dist/components/buttons/DeleteButton.svelte +95 -29
  133. package/dist/components/buttons/DeleteButton.svelte.d.ts +2 -0
  134. package/dist/components/buttons/EditButton.svelte +6 -4
  135. package/dist/components/buttons/ExportButton.svelte +22 -4
  136. package/dist/components/buttons/ImportButton.svelte +83 -38
  137. package/dist/components/buttons/ImportButton.svelte.d.ts +6 -10
  138. package/dist/components/buttons/RefreshButton.svelte +48 -8
  139. package/dist/components/buttons/ShowButton.svelte +6 -4
  140. package/dist/components/content/ApiKeyList.svelte +3 -1
  141. package/dist/components/content/ContentPageHeader.svelte +12 -9
  142. package/dist/components/content/ContentPageShell.svelte +8 -9
  143. package/dist/components/content/FileBrowser.svelte +35 -0
  144. package/dist/components/content/FileBrowser.svelte.d.ts +14 -0
  145. package/dist/components/content/FileList.svelte +3 -1
  146. package/dist/components/content/MemberList.svelte +3 -1
  147. package/dist/components/content/MetricBlock.svelte +10 -13
  148. package/dist/components/content/NetworkTable.svelte +6 -4
  149. package/dist/components/content/NetworkTable.svelte.d.ts +1 -0
  150. package/dist/components/content/PageToolbar.svelte +6 -10
  151. package/dist/components/content/SectionHeader.svelte +8 -13
  152. package/dist/components/content/SecurityEventTable.svelte +3 -1
  153. package/dist/components/content/SettingsFieldRow.svelte +9 -23
  154. package/dist/components/content/SettingsGroup.svelte +11 -27
  155. package/dist/components/content/StatusBadge.svelte +3 -8
  156. package/dist/components/content/StatusTabs.svelte +2 -2
  157. package/dist/components/content/WorkspaceLayout.svelte +8 -12
  158. package/dist/components/enterprise/filter-tree.d.ts +48 -0
  159. package/dist/components/enterprise/filter-tree.js +224 -0
  160. package/dist/components/enterprise/json-schema-form.d.ts +39 -0
  161. package/dist/components/enterprise/json-schema-form.js +195 -0
  162. package/dist/components/enterprise/recursive-schema-form.d.ts +30 -0
  163. package/dist/components/enterprise/recursive-schema-form.js +196 -0
  164. package/dist/components/enterprise/spreadsheet-formula.d.ts +12 -0
  165. package/dist/components/enterprise/spreadsheet-formula.js +245 -0
  166. package/dist/components/fieldComponentMap.d.ts +18 -13
  167. package/dist/components/fieldComponentMap.js +100 -47
  168. package/dist/components/fields/AvatarField.svelte +1 -1
  169. package/dist/components/fields/ChoiceField.svelte +16 -0
  170. package/dist/components/fields/ChoiceField.svelte.d.ts +10 -0
  171. package/dist/components/fields/CodeField.svelte +11 -12
  172. package/dist/components/fields/CodeField.svelte.d.ts +1 -1
  173. package/dist/components/fields/DateField.svelte +4 -3
  174. package/dist/components/fields/DateRangeField.svelte +2 -4
  175. package/dist/components/fields/ImageField.svelte +3 -1
  176. package/dist/components/fields/ImagesField.svelte +23 -0
  177. package/dist/components/fields/ImagesField.svelte.d.ts +6 -0
  178. package/dist/components/fields/JsonField.svelte +19 -13
  179. package/dist/components/fields/PasswordField.svelte +5 -0
  180. package/dist/components/fields/PasswordField.svelte.d.ts +6 -0
  181. package/dist/components/fields/date-field-value.d.ts +2 -0
  182. package/dist/components/fields/date-field-value.js +13 -0
  183. package/dist/components/fields/json-value.d.ts +11 -0
  184. package/dist/components/fields/json-value.js +28 -0
  185. package/dist/components/json-schema-form-state.d.ts +37 -0
  186. package/dist/components/json-schema-form-state.js +168 -0
  187. package/dist/components/profile/ActivityTimeline.svelte +2 -2
  188. package/dist/components/record-detail.svelte.d.ts +19 -0
  189. package/dist/components/record-detail.svelte.js +34 -0
  190. package/dist/components/saved-list-views.js +29 -29
  191. package/dist/components/svar-grid-cells.d.ts +14 -0
  192. package/dist/components/svar-grid-cells.js +2 -0
  193. package/dist/components/svar-grid-contract.d.ts +80 -0
  194. package/dist/components/svar-grid-contract.js +159 -0
  195. package/dist/components/svar-grid-interactions.d.ts +54 -0
  196. package/dist/components/svar-grid-interactions.js +174 -0
  197. package/dist/components/svar-grid-loading.d.ts +40 -0
  198. package/dist/components/svar-grid-loading.js +180 -0
  199. package/dist/components/svar-grid-operations.d.ts +37 -0
  200. package/dist/components/svar-grid-operations.js +186 -0
  201. package/dist/components/svar-grid-panes.d.ts +34 -0
  202. package/dist/components/svar-grid-panes.js +66 -0
  203. package/dist/components/svar-grid-resource-loading.d.ts +18 -0
  204. package/dist/components/svar-grid-resource-loading.js +68 -0
  205. package/dist/components/svar-grid-resource-session.svelte.d.ts +29 -0
  206. package/dist/components/svar-grid-resource-session.svelte.js +61 -0
  207. package/dist/components/table-contract.d.ts +13 -0
  208. package/dist/components/table-contract.js +42 -0
  209. package/dist/components/task-utils.js +4 -2
  210. package/dist/components/ui/alert/alert-variants.d.ts +2 -3
  211. package/dist/components/ui/alert/alert-variants.js +1 -2
  212. package/dist/components/ui/alert/alert.svelte +4 -5
  213. package/dist/components/ui/alert/alert.svelte.d.ts +3 -8
  214. package/dist/components/ui/alert/index.js +0 -2
  215. package/dist/components/ui/avatar/avatar-variants.d.ts +2 -3
  216. package/dist/components/ui/avatar/avatar-variants.js +1 -2
  217. package/dist/components/ui/avatar/avatar.svelte +5 -6
  218. package/dist/components/ui/avatar/avatar.svelte.d.ts +3 -8
  219. package/dist/components/ui/badge/badge-variants.d.ts +2 -3
  220. package/dist/components/ui/badge/badge-variants.js +3 -2
  221. package/dist/components/ui/badge/badge.svelte +4 -5
  222. package/dist/components/ui/badge/badge.svelte.d.ts +3 -8
  223. package/dist/components/ui/badge/index.js +0 -2
  224. package/dist/components/ui/button/button-variants.d.ts +2 -3
  225. package/dist/components/ui/button/button-variants.js +5 -3
  226. package/dist/components/ui/button/button.svelte +100 -44
  227. package/dist/components/ui/button/button.svelte.d.ts +8 -12
  228. package/dist/components/ui/button/index.js +0 -2
  229. package/dist/components/ui/command/command-dialog.svelte +3 -1
  230. package/dist/components/ui/command/command-dialog.svelte.d.ts +1 -1
  231. package/dist/components/ui/command/command-group.svelte +4 -2
  232. package/dist/components/ui/command/command-item.svelte +4 -2
  233. package/dist/components/ui/command/command-separator.svelte +3 -1
  234. package/dist/components/ui/command/index.d.ts +5 -5
  235. package/dist/components/ui/input/input.svelte +41 -24
  236. package/dist/components/ui/input/input.svelte.d.ts +5 -4
  237. package/dist/components/ui/sheet/sheet.svelte +9 -3
  238. package/dist/components/ui/sheet/sheet.svelte.d.ts +1 -0
  239. package/dist/components/ui/textarea/textarea.svelte +2 -1
  240. package/dist/components/ui/tooltip/tooltip.svelte +4 -2
  241. package/dist/components.css +3079 -0
  242. package/dist/design-contract.d.ts +13 -0
  243. package/dist/design-contract.js +11 -0
  244. package/dist/design-system.d.ts +15 -0
  245. package/dist/design-system.js +7 -0
  246. package/dist/index.d.ts +15 -1
  247. package/dist/index.js +9 -0
  248. package/dist/inline-value.d.ts +5 -0
  249. package/dist/inline-value.js +24 -0
  250. package/dist/numeric-input.d.ts +2 -0
  251. package/dist/numeric-input.js +9 -0
  252. package/dist/page-chrome.css +12 -49
  253. package/dist/recipes.d.ts +4 -0
  254. package/dist/recipes.js +4 -0
  255. package/dist/rendering/index.d.ts +87 -0
  256. package/dist/rendering/index.js +117 -0
  257. package/dist/styled-system/css/conditions.js +36 -0
  258. package/dist/styled-system/css/css.d.ts +22 -0
  259. package/dist/styled-system/css/css.js +45 -0
  260. package/dist/styled-system/css/cva.d.ts +6 -0
  261. package/dist/styled-system/css/cva.js +87 -0
  262. package/dist/styled-system/css/cx.d.ts +5 -0
  263. package/dist/styled-system/css/cx.js +15 -0
  264. package/dist/styled-system/css/index.d.ts +5 -0
  265. package/dist/styled-system/css/index.js +4 -0
  266. package/dist/styled-system/css/sva.d.ts +4 -0
  267. package/dist/styled-system/css/sva.js +46 -0
  268. package/dist/styled-system/helpers.js +316 -0
  269. package/dist/styled-system/patterns/aspect-ratio.d.ts +20 -0
  270. package/dist/styled-system/patterns/aspect-ratio.js +38 -0
  271. package/dist/styled-system/patterns/bleed.d.ts +21 -0
  272. package/dist/styled-system/patterns/bleed.js +24 -0
  273. package/dist/styled-system/patterns/box.d.ts +20 -0
  274. package/dist/styled-system/patterns/box.js +15 -0
  275. package/dist/styled-system/patterns/center.d.ts +20 -0
  276. package/dist/styled-system/patterns/center.js +21 -0
  277. package/dist/styled-system/patterns/circle.d.ts +20 -0
  278. package/dist/styled-system/patterns/circle.js +25 -0
  279. package/dist/styled-system/patterns/container.d.ts +20 -0
  280. package/dist/styled-system/patterns/container.js +21 -0
  281. package/dist/styled-system/patterns/cq.d.ts +21 -0
  282. package/dist/styled-system/patterns/cq.js +21 -0
  283. package/dist/styled-system/patterns/divider.d.ts +22 -0
  284. package/dist/styled-system/patterns/divider.js +25 -0
  285. package/dist/styled-system/patterns/flex.d.ts +26 -0
  286. package/dist/styled-system/patterns/flex.js +26 -0
  287. package/dist/styled-system/patterns/float.d.ts +23 -0
  288. package/dist/styled-system/patterns/float.js +52 -0
  289. package/dist/styled-system/patterns/grid-item.d.ts +25 -0
  290. package/dist/styled-system/patterns/grid-item.js +25 -0
  291. package/dist/styled-system/patterns/grid.d.ts +24 -0
  292. package/dist/styled-system/patterns/grid.js +27 -0
  293. package/dist/styled-system/patterns/hstack.d.ts +21 -0
  294. package/dist/styled-system/patterns/hstack.js +24 -0
  295. package/dist/styled-system/patterns/index.d.ts +21 -0
  296. package/dist/styled-system/patterns/index.js +20 -0
  297. package/dist/styled-system/patterns/link-overlay.d.ts +20 -0
  298. package/dist/styled-system/patterns/link-overlay.js +24 -0
  299. package/dist/styled-system/patterns/spacer.d.ts +20 -0
  300. package/dist/styled-system/patterns/spacer.js +25 -0
  301. package/dist/styled-system/patterns/square.d.ts +20 -0
  302. package/dist/styled-system/patterns/square.js +24 -0
  303. package/dist/styled-system/patterns/stack.d.ts +23 -0
  304. package/dist/styled-system/patterns/stack.js +24 -0
  305. package/dist/styled-system/patterns/visually-hidden.d.ts +20 -0
  306. package/dist/styled-system/patterns/visually-hidden.js +18 -0
  307. package/dist/styled-system/patterns/vstack.d.ts +21 -0
  308. package/dist/styled-system/patterns/vstack.js +24 -0
  309. package/dist/styled-system/patterns/wrap.d.ts +24 -0
  310. package/dist/styled-system/patterns/wrap.js +25 -0
  311. package/dist/styled-system/recipes/content-header.d.ts +31 -0
  312. package/dist/styled-system/recipes/content-header.js +65 -0
  313. package/dist/styled-system/recipes/content-page.d.ts +34 -0
  314. package/dist/styled-system/recipes/content-page.js +43 -0
  315. package/dist/styled-system/recipes/create-recipe.js +82 -0
  316. package/dist/styled-system/recipes/index.d.ts +17 -0
  317. package/dist/styled-system/recipes/index.js +16 -0
  318. package/dist/styled-system/recipes/metric-block.d.ts +34 -0
  319. package/dist/styled-system/recipes/metric-block.js +76 -0
  320. package/dist/styled-system/recipes/product-list.d.ts +31 -0
  321. package/dist/styled-system/recipes/product-list.js +73 -0
  322. package/dist/styled-system/recipes/product-section.d.ts +31 -0
  323. package/dist/styled-system/recipes/product-section.js +49 -0
  324. package/dist/styled-system/recipes/product-settings-row.d.ts +34 -0
  325. package/dist/styled-system/recipes/product-settings-row.js +58 -0
  326. package/dist/styled-system/recipes/product-settings.d.ts +31 -0
  327. package/dist/styled-system/recipes/product-settings.js +57 -0
  328. package/dist/styled-system/recipes/product-status.d.ts +34 -0
  329. package/dist/styled-system/recipes/product-status.js +45 -0
  330. package/dist/styled-system/recipes/product-toolbar.d.ts +31 -0
  331. package/dist/styled-system/recipes/product-toolbar.js +41 -0
  332. package/dist/styled-system/recipes/product-workspace.d.ts +34 -0
  333. package/dist/styled-system/recipes/product-workspace.js +58 -0
  334. package/dist/styled-system/recipes/surface-metric.d.ts +38 -0
  335. package/dist/styled-system/recipes/surface-metric.js +63 -0
  336. package/dist/styled-system/recipes/surface-table.d.ts +34 -0
  337. package/dist/styled-system/recipes/surface-table.js +62 -0
  338. package/dist/styled-system/recipes/ui-badge.d.ts +31 -0
  339. package/dist/styled-system/recipes/ui-badge.js +38 -0
  340. package/dist/styled-system/recipes/ui-button.d.ts +32 -0
  341. package/dist/styled-system/recipes/ui-button.js +43 -0
  342. package/dist/styled-system/recipes/ui-input.d.ts +31 -0
  343. package/dist/styled-system/recipes/ui-input.js +49 -0
  344. package/dist/styled-system/recipes/ui-textarea.d.ts +31 -0
  345. package/dist/styled-system/recipes/ui-textarea.js +24 -0
  346. package/dist/styled-system/styles.css +1257 -0
  347. package/dist/styled-system/tokens/index.d.ts +9 -0
  348. package/dist/styled-system/tokens/index.js +268 -0
  349. package/dist/styled-system/tokens/tokens.d.ts +36 -0
  350. package/dist/styled-system/types/composition.d.ts +227 -0
  351. package/dist/styled-system/types/conditions.d.ts +236 -0
  352. package/dist/styled-system/types/csstype.d.ts +22570 -0
  353. package/dist/styled-system/types/index.d.ts +6 -0
  354. package/dist/styled-system/types/parts.d.ts +8 -0
  355. package/dist/styled-system/types/pattern.d.ts +78 -0
  356. package/dist/styled-system/types/prop-type.d.ts +223 -0
  357. package/dist/styled-system/types/recipe.d.ts +181 -0
  358. package/dist/styled-system/types/selectors.d.ts +59 -0
  359. package/dist/styled-system/types/static-css.d.ts +56 -0
  360. package/dist/styled-system/types/style-props.d.ts +8088 -0
  361. package/dist/styled-system/types/system-types.d.ts +151 -0
  362. package/dist/styles/aliases.css +49 -0
  363. package/dist/styles/compatibility.epilogue.css +384 -0
  364. package/dist/styles/compatibility.prelude.css +5513 -0
  365. package/dist/styles/conditional-states.css +28 -0
  366. package/dist/styles/conditional-utilities.css +22 -0
  367. package/dist/styles/recipes.css +1262 -0
  368. package/dist/types.d.ts +3 -3
  369. package/dist/utils.d.ts +1 -1
  370. package/dist/utils.js +1 -1
  371. package/package.json +38 -14
  372. package/styles-compatibility.json +12 -0
@@ -1,5 +1,9 @@
1
1
  <script lang="ts">
2
- import { onDestroy, untrack } from 'svelte';
2
+ import { bindResourceRendering, type ResourceRendering } from '../rendering/index.js';
3
+ import type { AutoTableGridState } from './auto-table-grid.js';
4
+ import { definedOptions } from '@svadmin/core/options';
5
+
6
+ import { onDestroy, tick, untrack } from 'svelte';
3
7
  import { SvelteMap } from 'svelte/reactivity';
4
8
  import { cn } from '../utils.js';
5
9
  import {
@@ -21,6 +25,7 @@
21
25
  import { createAtom, useSelector } from '@tanstack/svelte-store';
22
26
  import {
23
27
  column_getCanSort,
28
+ column_getSize,
24
29
  column_getIsSorted,
25
30
  column_toggleSorting,
26
31
  header_getSize,
@@ -28,13 +33,16 @@
28
33
  table_getRowModel,
29
34
  table_getAllLeafColumns,
30
35
  table_getIsAllRowsSelected,
36
+ table_resetRowSelection,
31
37
  table_toggleAllRowsSelected,
32
38
  row_toggleSelected,
33
39
  row_getVisibleCells,
34
40
  cell_getValue,
35
41
  } from '@tanstack/table-core/static-functions';
36
42
 
37
- import { captureAdminContext, DeleteManyPartialError, getAdminOptions, useList, useDelete, useDeleteMany, getResource, useNavigation, useParsed } from '@svadmin/core';
43
+ import { captureAdminContext, DeleteManyPartialError, getAdminOptions, getContractFormFields, useNavigation, useParsed, useResourceContract, useList, useDeleteMany, downloadData } from '@svadmin/core';
44
+ import { decodeBaseRecord, snapshotPlainData, parseContractRouteId, formatContractRouteId } from '@svadmin/core/schema';
45
+ import { checkedTableRows, copyTableRecord, tableExportRows, tableRowKey, type TableRecord } from './table-contract';
38
46
  import type {
39
47
  BaseRecord,
40
48
  FieldDefinition,
@@ -84,9 +92,12 @@
84
92
  } from '@lucide/svelte';
85
93
  import ConfirmDialog from './ConfirmDialog.svelte';
86
94
  import RecordDetailDrawer from './RecordDetailDrawer.svelte';
95
+ import QuickEditDrawer from './QuickEditDrawer.svelte';
96
+ import RecordRowActions from './RecordRowActions.svelte';
87
97
  import CanAccess from './CanAccess.svelte';
88
98
  import TooltipButton from './TooltipButton.svelte';
89
99
  import InlineEdit from './InlineEdit.svelte';
100
+ import FieldDisplay from './FieldDisplay.svelte';
90
101
  import DraggableHeader from './DraggableHeader.svelte';
91
102
  import DataState from './content/DataState.svelte';
92
103
  import type { Snippet } from 'svelte';
@@ -96,6 +107,9 @@
96
107
  // ─── Props with Snippet composability ─────────────────────────
97
108
  interface Props {
98
109
  resourceName: string;
110
+ rendering?: ResourceRendering | undefined;
111
+ /** 可选主体渲染器:不替换资源、权限、工具栏和持久化逻辑。 */
112
+ gridBody?: Snippet<[AutoTableGridState]>;
99
113
  selectable?: boolean;
100
114
  density?: 'compact' | 'comfortable';
101
115
  showHeader?: boolean;
@@ -123,10 +137,13 @@
123
137
  batchActions?: Snippet<[{ selectedIds: (string | number)[] }]>;
124
138
  /** Summary row rendered at table footer */
125
139
  summary?: Snippet<[{ data: BaseRecord[]; total: number; visibleColumnsCount: number }]>;
140
+ deleteVariables?: unknown;
126
141
  }
127
142
 
128
143
  let {
129
144
  resourceName,
145
+ rendering,
146
+ gridBody,
130
147
  selectable = true,
131
148
  density = 'comfortable',
132
149
  showHeader = true,
@@ -144,25 +161,28 @@
144
161
  summary,
145
162
  pagination: externalPagination,
146
163
  sorters: externalSorters,
164
+ deleteVariables,
147
165
  }: Props = $props();
148
166
 
149
167
  let densityOverride = $state<'compact' | 'comfortable' | undefined>(undefined);
150
168
  const currentDensity = $derived(densityOverride ?? density);
151
169
  const adminContext = captureAdminContext();
152
- const adminOptions = getAdminOptions();
153
- const mutationMode = $derived(adminOptions.mutationMode ?? 'pessimistic');
154
170
  const parsed = useParsed();
155
171
  const navigation = useNavigation();
156
172
 
157
- const resource = $derived(getResource(resourceName));
173
+ const binding = useResourceContract(() => resourceName);
174
+ const activeRendering = $derived(bindResourceRendering(rendering, binding.resource));
175
+ const resource = $derived(adminContext.getResource(resourceName));
158
176
  const primaryKey = $derived(resource.primaryKey ?? 'id');
177
+ const listPermission = useCan(() => ({ resource: resourceName, action: 'list' }));
178
+ const canRead = $derived(listPermission.allowed);
159
179
  const listPreferenceScope = $derived.by(() => {
160
180
  const matcher = adminContext.queryKeyMatcher(resourceName);
161
- return {
181
+ return definedOptions({
162
182
  resourceName,
163
183
  providerName: matcher.provider ?? 'default',
164
184
  tenantIdentity: matcher.tenant,
165
- };
185
+ });
166
186
  });
167
187
 
168
188
  function readLocalPreference(key: string): string | null {
@@ -189,10 +209,10 @@
189
209
  // ─── URL state + server-side state ────────────────────────────
190
210
  const urlState = readURLState(adminContext);
191
211
 
192
- const savedViewColumnIds = new Set([
193
- ...untrack(() => resource.fields.map((field) => field.key)),
212
+ const savedViewColumnIds = $derived(new Set([
213
+ ...resource.fields.map((field) => field.key),
194
214
  '_select', '_expand', '_actions',
195
- ]);
215
+ ]));
196
216
  const storedSavedViews = untrack(() => readSavedListViews(
197
217
  readScopedPreference(
198
218
  listPreferenceScope,
@@ -244,7 +264,7 @@
244
264
  const parsed: unknown = JSON.parse(stored);
245
265
  if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
246
266
  const visibility: ColumnVisibilityState = {};
247
- for (const [columnId, visible] of Object.entries(parsed as Record<string, unknown>)) {
267
+ for (const [columnId, visible] of Object.entries(decodeBaseRecord(parsed))) {
248
268
  if (savedViewColumnIds.has(columnId) && typeof visible === 'boolean') {
249
269
  visibility[columnId] = visible;
250
270
  }
@@ -271,7 +291,7 @@
271
291
  try {
272
292
  const parsed: unknown = JSON.parse(stored);
273
293
  return Array.isArray(parsed)
274
- ? [...new Set(parsed.filter((columnId): columnId is string => (
294
+ ? [...new Set(parsed.filter((columnId: unknown): columnId is string => (
275
295
  typeof columnId === 'string' && savedViewColumnIds.has(columnId)
276
296
  )))]
277
297
  : [];
@@ -301,19 +321,19 @@
301
321
  legacyActiveSavedListViewStorageKey(resourceName),
302
322
  );
303
323
  const activeSavedView = scopedSavedViews.find((view) => view.id === candidate);
304
- return {
324
+ return definedOptions({
305
325
  savedViews: scopedSavedViews,
306
326
  activeSavedViewId: activeSavedView?.id,
307
327
  activeSavedView,
308
328
  columnVisibility: readColumnVisibilityPreference(scope),
309
329
  columnOrder: readColumnOrderPreference(scope),
310
- };
330
+ });
311
331
  }
312
332
 
313
333
  // Snapshot resource values for initial state (untrack to avoid reactive tracking)
314
334
  const storedPageSize = parseInt(readLocalPreference('svadmin-default-page-size') ?? '', 10);
315
- const initPageSize = untrack(() => resource.pageSize ?? (isNaN(storedPageSize) ? 10 : storedPageSize));
316
- const initDefaultSort = untrack(() => resource.defaultSort);
335
+ const initPageSize = $derived(resource.pageSize ?? (Number.isSafeInteger(storedPageSize) && storedPageSize > 0 ? storedPageSize : 10));
336
+ const initDefaultSort = $derived(resource.defaultSort);
317
337
 
318
338
  let pagination = $state<PaginationState>(untrack(() => externalPagination ?? {
319
339
  current: urlState.page ?? initialViewState?.pagination.current ?? 1,
@@ -326,10 +346,10 @@
326
346
  : initialViewState?.sorters ?? (initDefaultSort ? [initDefaultSort] : [])))
327
347
  );
328
348
  const initialURLFilters = untrack(() => urlState.filters ?? initialViewState?.filters ?? []);
329
- const editableFilterKeys = new Set(untrack(() => resource.fields.filter((field) => field.filterable).map((field) => field.key)));
349
+ const editableFilterKeys = $derived(new Set(resource.fields.filter((field) => field.filterable).map((field) => field.key)));
330
350
  const editableFilterCounts: Record<string, number> = {};
331
351
  for (const filter of initialURLFilters) {
332
- if ('field' in filter && filter.operator === 'contains' && typeof filter.value === 'string' && editableFilterKeys.has(filter.field)) {
352
+ if ('field' in filter && filter.operator === 'contains' && typeof filter.value === 'string' && untrack(() => editableFilterKeys.has(filter.field))) {
333
353
  editableFilterCounts[filter.field] = (editableFilterCounts[filter.field] ?? 0) + 1;
334
354
  }
335
355
  }
@@ -350,7 +370,8 @@
350
370
  let searchDebounceTimer: ReturnType<typeof setTimeout> | undefined;
351
371
 
352
372
  function scheduleSearch(event: Event) {
353
- searchText = (event.currentTarget as HTMLInputElement).value;
373
+ if (!(event.currentTarget instanceof HTMLInputElement)) return;
374
+ searchText = event.currentTarget.value;
354
375
  if (searchDebounceTimer) clearTimeout(searchDebounceTimer);
355
376
  searchDebounceTimer = setTimeout(() => {
356
377
  appliedSearchText = searchText;
@@ -395,8 +416,9 @@
395
416
  const activeFilters = $derived.by(() => {
396
417
  const result: Filter[] = [...locationFilters];
397
418
  if (appliedSearchText.trim() && searchableFields.length > 0) {
398
- if (searchableFields.length === 1) {
399
- result.push({ field: searchableFields[0].key, operator: 'contains', value: appliedSearchText });
419
+ const firstSearchableField = searchableFields[0];
420
+ if (searchableFields.length === 1 && firstSearchableField) {
421
+ result.push({ field: firstSearchableField.key, operator: 'contains', value: appliedSearchText });
400
422
  } else {
401
423
  const searchFilter: LogicalFilter = {
402
424
  operator: 'or',
@@ -426,6 +448,10 @@
426
448
 
427
449
  function clearFilters(): void {
428
450
  markSavedViewDirty();
451
+ if (searchDebounceTimer) clearTimeout(searchDebounceTimer);
452
+ searchDebounceTimer = undefined;
453
+ searchText = '';
454
+ appliedSearchText = '';
429
455
  filters = [];
430
456
  filterValues = {};
431
457
  pagination = { ...pagination, current: 1 };
@@ -447,6 +473,12 @@
447
473
  pagination = { ...pagination, current: 1 };
448
474
  }
449
475
 
476
+ function setFilterFromEvent(field: string, event: Event): void {
477
+ if (event.currentTarget instanceof HTMLInputElement || event.currentTarget instanceof HTMLSelectElement) {
478
+ setFilterValue(field, event.currentTarget.value);
479
+ }
480
+ }
481
+
450
482
  function removeActiveFilter(index: number): void {
451
483
  markSavedViewDirty();
452
484
  const filter = locationFilters[index];
@@ -460,21 +492,16 @@
460
492
  && candidate.operator === 'contains'
461
493
  && typeof candidate.value === 'string'
462
494
  ));
463
- filterValues[filter.field] = remaining.length === 1 && 'field' in remaining[0]
464
- ? String(remaining[0].value)
495
+ const remainingFilter = remaining[0];
496
+ filterValues[filter.field] = remaining.length === 1 && remainingFilter && 'field' in remainingFilter
497
+ ? String(remainingFilter.value)
465
498
  : '';
466
499
  }
467
500
  pagination = { ...pagination, current: 1 };
468
501
  }
469
502
 
470
503
  function clonePlainValue(value: unknown): unknown {
471
- if (Array.isArray(value)) return value.map(clonePlainValue);
472
- if (value && typeof value === 'object') {
473
- return Object.fromEntries(
474
- Object.entries(value as Record<string, unknown>).map(([key, entry]) => [key, clonePlainValue(entry)])
475
- );
476
- }
477
- return value;
504
+ return value === undefined ? undefined : snapshotPlainData(value);
478
505
  }
479
506
 
480
507
  function cloneFilter(filter: Filter): Filter {
@@ -491,11 +518,11 @@
491
518
  };
492
519
  }
493
520
 
494
- const queryPagination = $derived<PaginationState>({
521
+ const queryPagination = $derived<PaginationState>(definedOptions({
495
522
  current: pagination.current,
496
523
  pageSize: pagination.pageSize,
497
524
  mode: pagination.mode,
498
- });
525
+ }));
499
526
  const querySorters = $derived<Sort[]>(sorters.map(sorter => ({
500
527
  field: sorter.field,
501
528
  order: sorter.order,
@@ -504,38 +531,63 @@
504
531
 
505
532
  // ─── Data fetching ────────────────────────────────────────────
506
533
  const listResult = useList({
507
- get resource() { return resourceName; },
534
+ get resource() { void activeRendering; return binding.resource; },
535
+ get dataProviderName() { return binding.dataProviderName; },
536
+ get queryOptions() { return { enabled: canRead }; },
508
537
  get pagination() { return queryPagination; },
509
538
  get sorters() { return querySorters; },
510
539
  get filters() { return queryFilters; },
511
540
  });
512
541
  const query = listResult;
513
- const deleteResult = useDelete({ get resource() { return resourceName; } });
514
- const deleteMutation = deleteResult.mutation;
515
- let batchOperationCount = $state(0);
542
+ const pageRecords = {
543
+ get ok(): boolean {
544
+ if (!canRead) return true;
545
+ try {
546
+ checkedTableRows(activeRendering ? activeRendering.records(query.data?.data ?? []) : query.data?.data ?? []);
547
+ return true;
548
+ } catch {
549
+ return false;
550
+ }
551
+ },
552
+ get data(): TableRecord[] {
553
+ if (!canRead) return [];
554
+ try {
555
+ return checkedTableRows(activeRendering ? activeRendering.records(query.data?.data ?? []) : query.data?.data ?? []);
556
+ } catch {
557
+ return [];
558
+ }
559
+ },
560
+ };
561
+ let deleteRequest = $state<{ ids: (string | number)[]; batch: boolean } | null>(null);
562
+ const deletePermission = useCan(() => definedOptions({
563
+ resource: resourceName, action: 'delete',
564
+ id: deleteRequest?.batch === false ? deleteRequest.ids[0] : undefined,
565
+ params: deleteRequest?.batch ? { ids: deleteRequest.ids } : undefined,
566
+ queryOptions: { enabled: deleteRequest !== null },
567
+ }));
568
+ const deleteAllowed = $derived(canRead && resource.canDelete !== false && deleteRequest !== null && deletePermission.allowed);
516
569
  const deleteManyResult = useDeleteMany({
517
- get resource() { return resourceName; },
518
- successNotification: () => ({
519
- message: i18n.t('common.batchDeleteSuccess', { count: batchOperationCount }),
520
- }),
521
- errorNotification: (error) => ({
522
- type: 'error',
523
- message: error instanceof DeleteManyPartialError
524
- ? i18n.t('common.batchDeletePartialFail', { failed: error.failedIds.length, total: batchOperationCount })
525
- : i18n.t('common.batchDeleteFailed', { count: batchOperationCount }),
526
- }),
570
+ get resource() { void activeRendering; return binding.resource; },
571
+ get enabled() { return deleteAllowed; },
527
572
  });
528
573
  const deleteManyMutation = deleteManyResult.mutation;
529
574
 
530
575
  // ─── Permissions ──────────────────────────────────────────────
531
576
  const acEnabled = $derived(!!adminContext.accessControlProvider);
577
+ const deleteResourcePermission = useCan(() => ({
578
+ resource: resourceName, action: 'delete', queryOptions: { enabled: acEnabled },
579
+ }));
532
580
  const canCreatePerm = useCan(() => ({ resource: resourceName, action: 'create', queryOptions: { enabled: acEnabled } }));
533
581
  const canExportPerm = useCan(() => ({ resource: resourceName, action: 'export', queryOptions: { enabled: acEnabled } }));
534
582
  const canCreate = $derived(resource.canCreate !== false && (!acEnabled || canCreatePerm.allowed));
535
583
  const canEdit = $derived(resource.canEdit !== false);
584
+ const writableFields = $derived.by(() => {
585
+ try { return new Set(getContractFormFields(binding.resource, 'edit')); }
586
+ catch { return new Set<string>(); }
587
+ });
536
588
  const canShow = $derived(resource.canShow !== false);
537
589
  const canDelete = $derived(resource.canDelete !== false);
538
- const canExport = $derived(canExportPerm.allowed);
590
+ const canExport = $derived(!acEnabled || canExportPerm.allowed);
539
591
 
540
592
  // ─── TanStack Table state ────────────────────────────────────
541
593
  const initialSorting = untrack<SortingState>(() =>
@@ -546,8 +598,8 @@
546
598
  ));
547
599
  const sortingAtom = createAtom(initialSorting);
548
600
  const columnVisibilityAtom = createAtom(initialColumnVisibility);
549
- const rowSelectionAtom = createAtom({} as RowSelectionState);
550
- const expandedAtom = createAtom({} as ExpandedState);
601
+ let rowSelection = $state<RowSelectionState>({});
602
+ const expandedAtom = createAtom<ExpandedState>({});
551
603
  const initialColumnOrder = untrack(() => (
552
604
  initialViewState?.columnOrder?.length
553
605
  ? initialViewState.columnOrder
@@ -565,7 +617,6 @@
565
617
 
566
618
  const tableSorting = useSelector(sortingAtom);
567
619
  const tableColumnVisibility = useSelector(columnVisibilityAtom);
568
- const tableRowSelection = useSelector(rowSelectionAtom);
569
620
  const tableExpanded = useSelector(expandedAtom);
570
621
  const tableColumnOrder = useSelector(columnOrderAtom);
571
622
  const selectedIdValueByKey = new SvelteMap<string, string | number>();
@@ -580,15 +631,15 @@
580
631
  }
581
632
 
582
633
  function rowIsSelected(rowId: string): boolean {
583
- return tableRowSelection.current[rowId] === true;
634
+ return rowSelection[rowId] === true;
584
635
  }
585
636
 
586
- function rowIdValue(row: Row<TableFeatures, BaseRecord>): string | number {
587
- const value = row.original[primaryKey];
588
- return typeof value === 'string' || typeof value === 'number' ? value : row.id;
637
+ function rowIdValue(row: Row<TableFeatures, TableRecord>): string | number {
638
+ return row.original.id;
589
639
  }
590
640
 
591
- function toggleRowSelection(row: Row<TableFeatures, BaseRecord>): void {
641
+ function toggleRowSelection(row: Row<TableFeatures, TableRecord>): void {
642
+ if (confirmPending || !canRead) return;
592
643
  const wasSelected = rowIsSelected(row.id);
593
644
  if (!wasSelected) {
594
645
  selectedIdValueByKey.set(row.id, rowIdValue(row));
@@ -696,7 +747,8 @@
696
747
  columnVisibilityAtom.set(state.columnVisibility);
697
748
  columnOrderAtom.set(state.columnOrder);
698
749
  persistColumnOrder(state.columnOrder);
699
- rowSelectionAtom.set({});
750
+ rowSelection = {};
751
+ selectedIdValueByKey.clear();
700
752
  }
701
753
 
702
754
  function resetToDefaultListState(): void {
@@ -733,7 +785,8 @@
733
785
  if (!hasExplicitURLState) resetToDefaultListState();
734
786
  columnVisibilityAtom.set(preferences.columnVisibility);
735
787
  columnOrderAtom.set(preferences.columnOrder);
736
- rowSelectionAtom.set({});
788
+ rowSelection = {};
789
+ selectedIdValueByKey.clear();
737
790
  }
738
791
 
739
792
  loadedPreferenceScopeId = scopeId;
@@ -785,7 +838,7 @@
785
838
  columnOrderAtom.set(ids);
786
839
  }
787
840
 
788
- function toggleColumnSort(column: Column<TableFeatures, BaseRecord, unknown>): void {
841
+ function toggleColumnSort(column: Column<TableFeatures, TableRecord, unknown>): void {
789
842
  markSavedViewDirty();
790
843
  column_toggleSorting(column);
791
844
  }
@@ -808,7 +861,7 @@
808
861
  resource.fields.filter(f => f.showInList !== false)
809
862
  );
810
863
 
811
- const columns = $derived<ColumnDef<TableFeatures, BaseRecord, unknown>[]>([
864
+ const columns = $derived<ColumnDef<TableFeatures, TableRecord, unknown>[]>([
812
865
  // Selection column
813
866
  ...(selectable && (canDelete || batchActions) ? [{
814
867
  id: '_select',
@@ -816,7 +869,7 @@
816
869
  cell: () => '',
817
870
  size: 40,
818
871
  enableSorting: false,
819
- } satisfies ColumnDef<TableFeatures, BaseRecord, unknown>] : []),
872
+ } satisfies ColumnDef<TableFeatures, TableRecord, unknown>] : []),
820
873
  // Expand column
821
874
  ...(expandedRowRender ? [{
822
875
  id: '_expand',
@@ -824,14 +877,14 @@
824
877
  cell: () => '',
825
878
  size: 40,
826
879
  enableSorting: false,
827
- } satisfies ColumnDef<TableFeatures, BaseRecord, unknown>] : []),
880
+ } satisfies ColumnDef<TableFeatures, TableRecord, unknown>] : []),
828
881
  // Data columns
829
- ...visibleFields.map((field): ColumnDef<TableFeatures, BaseRecord, unknown> => ({
882
+ ...visibleFields.map((field): ColumnDef<TableFeatures, TableRecord, unknown> => (definedOptions({
830
883
  id: field.key,
831
884
  accessorKey: field.key,
832
885
  header: () => field.label,
833
886
  size: field.width ? parseInt(String(field.width)) : undefined,
834
- })),
887
+ }))),
835
888
  // Actions column
836
889
  {
837
890
  id: '_actions',
@@ -856,22 +909,25 @@
856
909
  });
857
910
 
858
911
  // ─── Create TanStack Table ────────────────────────────────────
859
- const tbl = createTable<TableFeatures, BaseRecord>(
912
+ const tbl = createTable<TableFeatures, TableRecord>(
860
913
  {
861
914
  features,
862
- get data() { return query.data?.data ?? []; },
915
+ get data() { return pageRecords.data; },
863
916
  get columns() { return orderedColumns; },
864
917
  manualSorting: true,
865
- getRowId: (row: BaseRecord) => String(row[primaryKey]),
918
+ getRowId: (row: TableRecord) => tableRowKey(row.id),
866
919
  state: {
867
920
  get sorting() { return tableSorting.current; },
868
921
  get columnVisibility() { return tableColumnVisibility.current; },
922
+ get rowSelection() { return rowSelection; },
869
923
  },
870
924
  onSortingChange: (updater) => {
871
925
  sortingAtom.set(typeof updater === 'function' ? updater(tableSorting.current) : updater);
872
926
  },
927
+ onRowSelectionChange: (updater) => {
928
+ rowSelection = typeof updater === 'function' ? updater(rowSelection) : updater;
929
+ },
873
930
  atoms: {
874
- rowSelection: rowSelectionAtom,
875
931
  expanded: expandedAtom,
876
932
  },
877
933
  autoResetExpanded: false,
@@ -882,6 +938,7 @@
882
938
  );
883
939
 
884
940
  function toggleAllRowsSelection(): void {
941
+ if (confirmPending || !canRead) return;
885
942
  const willSelect = !table_getIsAllRowsSelected(tbl);
886
943
  for (const row of tableView.rows) {
887
944
  if (willSelect) {
@@ -894,9 +951,13 @@
894
951
  }
895
952
 
896
953
  const selectedIds = $derived(
897
- Object.keys(tableRowSelection.current).map((key) => selectedIdValueByKey.get(key) ?? key)
954
+ Object.keys(rowSelection).filter(key => rowSelection[key] === true)
955
+ .flatMap(key => {
956
+ const id = selectedIdValueByKey.get(key);
957
+ return id === undefined ? [] : [id];
958
+ })
898
959
  );
899
- const selectedCount = $derived(selectedIds.length);
960
+ const selectedCount = $derived(Object.keys(rowSelection).filter(key => rowSelection[key] === true).length);
900
961
  const batchDeletePerm = useCan(() => ({
901
962
  resource: resourceName,
902
963
  action: 'delete',
@@ -904,13 +965,13 @@
904
965
  queryOptions: { enabled: acEnabled && canDelete && selectedIds.length > 0 },
905
966
  }));
906
967
  const canBatchDelete = $derived(
907
- canDelete && (!acEnabled || (!batchDeletePerm.isLoading && batchDeletePerm.allowed))
968
+ canDelete && (!acEnabled || batchDeletePerm.isLoading || batchDeletePerm.allowed)
908
969
  );
909
970
 
910
971
  function clearSelection(): void {
911
972
  if (!deleteManyMutation.isPending) {
912
973
  selectedIdValueByKey.clear();
913
- rowSelectionAtom.set({});
974
+ table_resetRowSelection(tbl, true);
914
975
  }
915
976
  }
916
977
 
@@ -922,7 +983,7 @@
922
983
  void tableSorting.current;
923
984
  void tableColumnVisibility.current;
924
985
  void orderedColumns;
925
- void tableRowSelection.current;
986
+ void rowSelection;
926
987
  void tableExpanded.current;
927
988
  void tableColumnOrder.current;
928
989
  const headerGroups = table_getHeaderGroups(tbl);
@@ -951,7 +1012,8 @@
951
1012
  expandedAtom.set({ ...current, [rowId]: true });
952
1013
  }
953
1014
 
954
- const totalPages = $derived(Math.ceil((query.data?.total ?? 0) / (pagination.pageSize ?? 10)));
1015
+ const totalPages = $derived(canRead && pageRecords.ok && !query.isError
1016
+ ? Math.ceil((query.data?.total ?? 0) / (pagination.pageSize ?? 10)) : 0);
955
1017
 
956
1018
  // ─── Pagination helpers ───────────────────────────────────────
957
1019
  const currentPage = $derived(pagination.current ?? 1);
@@ -974,68 +1036,149 @@
974
1036
  // ─── Confirm dialog ───────────────────────────────────────────
975
1037
  let confirmOpen = $state(false);
976
1038
  let confirmMessage = $state('');
977
- let confirmAction = $state<() => void>(() => {});
978
1039
  let confirmPending = $state(false);
1040
+ let operationError = $state<string | null>(null);
1041
+ let activeDelete: object | undefined;
979
1042
  let detailOpenedInHistory = $state(false);
980
- const detailRecordId = $derived(parsed.params.detail);
1043
+ let quickEditId = $state<string | number | undefined>();
1044
+ const tableScope = $derived({
1045
+ contract: binding.resource, resourceName, provider: adminContext.providers?.[binding.dataProviderName],
1046
+ meta: JSON.stringify(binding.meta), tenant: adminContext.tenantCacheKey?.__svadminTenant,
1047
+ auth: adminContext.authProvider, router: adminContext.routerProvider,
1048
+ permissionProvider: adminContext.accessControlProvider, canRead, canDelete,
1049
+ variables: deleteVariables,
1050
+ });
1051
+ let previousTableScope: typeof tableScope | undefined;
1052
+ const detailState = $derived.by(() => {
1053
+ const route = parsed.params['detail'];
1054
+ if (route === undefined || !canRead) return { id: undefined, invalid: false };
1055
+ try { return { id: parseContractRouteId(binding.resource, route), invalid: false }; }
1056
+ catch { return { id: undefined, invalid: true }; }
1057
+ });
1058
+ const detailRecordId = $derived(detailState.id);
981
1059
  const detailOpen = $derived(detailRecordId != null);
1060
+ $effect.pre(() => {
1061
+ const scope = tableScope;
1062
+ if (previousTableScope && (
1063
+ previousTableScope.contract !== scope.contract ||
1064
+ previousTableScope.resourceName !== scope.resourceName ||
1065
+ previousTableScope.provider !== scope.provider ||
1066
+ previousTableScope.meta !== scope.meta ||
1067
+ previousTableScope.tenant !== scope.tenant ||
1068
+ previousTableScope.auth !== scope.auth ||
1069
+ previousTableScope.router !== scope.router ||
1070
+ previousTableScope.permissionProvider !== scope.permissionProvider ||
1071
+ previousTableScope.canRead !== scope.canRead ||
1072
+ previousTableScope.canDelete !== scope.canDelete ||
1073
+ previousTableScope.variables !== scope.variables
1074
+ )) {
1075
+ activeDelete = undefined;
1076
+ confirmOpen = false;
1077
+ confirmPending = false;
1078
+ deleteRequest = null;
1079
+ operationError = null;
1080
+ selectedIdValueByKey.clear();
1081
+ rowSelection = {};
1082
+ expandedAtom.set({});
1083
+ detailOpenedInHistory = false;
1084
+ quickEditId = undefined;
1085
+ if (searchDebounceTimer) clearTimeout(searchDebounceTimer);
1086
+ searchDebounceTimer = undefined;
1087
+ if (previousTableScope.resourceName !== scope.resourceName) resetToDefaultListState();
1088
+ }
1089
+ previousTableScope = scope;
1090
+ });
1091
+ $effect(() => {
1092
+ if (deleteRequest && !deletePermission.isLoading && !deletePermission.allowed) {
1093
+ confirmOpen = false;
1094
+ deleteRequest = null;
1095
+ operationError = i18n.t('common.operationFailed');
1096
+ }
1097
+ });
1098
+ onDestroy(() => { activeDelete = undefined; });
982
1099
 
983
1100
  function confirmDelete(id: string | number) {
1101
+ if (!canRead || !pageRecords.ok || query.isError || !canDelete || confirmPending) return;
984
1102
  confirmMessage = i18n.t('common.deleteConfirm');
985
- confirmAction = async () => {
986
- confirmPending = true;
987
- if (mutationMode !== 'pessimistic') confirmOpen = false;
988
- try {
989
- await deleteMutation.mutateAsync({
990
- id,
991
- resource: resourceName,
992
- onCancel: () => { confirmOpen = false; },
993
- });
994
- confirmOpen = false;
995
- } catch {
996
- // useDelete owns error feedback and restores optimistic cache state.
997
- } finally {
998
- confirmPending = false;
999
- }
1000
- };
1103
+ deleteRequest = { ids: [id], batch: false };
1104
+ operationError = null;
1001
1105
  confirmOpen = true;
1002
1106
  }
1003
1107
 
1004
1108
  function confirmBatchDelete() {
1005
1109
  const ids = [...selectedIds];
1006
- if (ids.length === 0 || deleteManyMutation.isPending) return;
1007
- batchOperationCount = ids.length;
1110
+ if (!canRead || !pageRecords.ok || query.isError || !canBatchDelete || ids.length === 0 || confirmPending) return;
1008
1111
  confirmMessage = i18n.t('common.batchDeleteConfirm', { count: ids.length });
1009
- confirmAction = async () => {
1010
- confirmPending = true;
1011
- if (mutationMode !== 'pessimistic') confirmOpen = false;
1012
- try {
1013
- await deleteManyMutation.mutateAsync({
1014
- ids,
1015
- resource: resourceName,
1016
- onCancel: () => { confirmOpen = false; },
1017
- });
1112
+ deleteRequest = { ids, batch: true };
1113
+ operationError = null;
1114
+ confirmOpen = true;
1115
+ }
1116
+
1117
+ async function confirmAction() {
1118
+ if (!confirmOpen || !pageRecords.ok || query.isError || !deleteAllowed || confirmPending || !deleteRequest) return;
1119
+ const request = deleteRequest;
1120
+ if (request.batch && JSON.stringify(request.ids) !== JSON.stringify(selectedIds)) {
1121
+ confirmOpen = false;
1122
+ deleteRequest = null;
1123
+ return;
1124
+ }
1125
+ const scope = tableScope;
1126
+ const token = {};
1127
+ activeDelete = token;
1128
+ confirmPending = true;
1129
+ confirmOpen = false;
1130
+ const clearBatchSelectionBeforeMutation = request.batch && getAdminOptions().mutationMode === 'undoable';
1131
+ const selectionBeforeDelete = clearBatchSelectionBeforeMutation ? new Map(selectedIdValueByKey) : undefined;
1132
+ if (clearBatchSelectionBeforeMutation) {
1133
+ selectedIdValueByKey.clear();
1134
+ table_resetRowSelection(tbl, true);
1135
+ }
1136
+ const current = () => activeDelete === token &&
1137
+ tableScope.contract === scope.contract &&
1138
+ tableScope.resourceName === scope.resourceName &&
1139
+ tableScope.provider === scope.provider &&
1140
+ tableScope.meta === scope.meta &&
1141
+ tableScope.tenant === scope.tenant &&
1142
+ tableScope.auth === scope.auth &&
1143
+ tableScope.router === scope.router &&
1144
+ tableScope.permissionProvider === scope.permissionProvider;
1145
+ try {
1146
+ await deleteManyMutation.mutateAsync({
1147
+ ids: [...request.ids],
1148
+ ...definedOptions({ variables: deleteVariables, dataProviderName: binding.dataProviderName }),
1149
+ });
1150
+ if (!current()) return;
1151
+ selectedIdValueByKey.clear();
1152
+ table_resetRowSelection(tbl, true);
1153
+ await tick();
1154
+ confirmOpen = false;
1155
+ } catch (error) {
1156
+ if (!current()) return;
1157
+ if (error instanceof DeleteManyPartialError) {
1158
+ rowSelection = Object.fromEntries(error.failedIds.map(id => [tableRowKey(id), true as const]));
1018
1159
  selectedIdValueByKey.clear();
1019
- rowSelectionAtom.set({});
1020
- confirmOpen = false;
1021
- } catch (error) {
1022
- if (error instanceof DeleteManyPartialError) {
1023
- const failedSelection: RowSelectionState = {};
1024
- for (const id of error.failedIds) failedSelection[String(id)] = true;
1025
- for (const id of error.succeededIds) selectedIdValueByKey.delete(String(id));
1026
- rowSelectionAtom.set(failedSelection);
1160
+ for (const id of error.failedIds) selectedIdValueByKey.set(tableRowKey(id), id);
1161
+ operationError = i18n.t('common.batchDeletePartialFail', { failed: error.failedIds.length, total: request.ids.length });
1162
+ } else {
1163
+ if (selectionBeforeDelete) {
1164
+ selectedIdValueByKey.clear();
1165
+ for (const [key, id] of selectionBeforeDelete) selectedIdValueByKey.set(key, id);
1166
+ rowSelection = Object.fromEntries([...selectionBeforeDelete.keys()].map(key => [key, true as const]));
1027
1167
  }
1028
- // useDeleteMany owns the error notification; keep failed selection for retry.
1029
- } finally {
1030
- confirmPending = false;
1168
+ operationError = i18n.t('common.operationFailed');
1031
1169
  }
1032
- };
1033
- confirmOpen = true;
1170
+ confirmOpen = false;
1171
+ } finally {
1172
+ if (activeDelete === token) { activeDelete = undefined; confirmPending = false; deleteRequest = null; }
1173
+ await tick();
1174
+ await tick();
1175
+ }
1034
1176
  }
1035
1177
 
1036
1178
  function openDetail(id: string | number): void {
1179
+ if (!canRead || !canShow) return;
1037
1180
  detailOpenedInHistory = true;
1038
- writeURLState({ detailId: String(id) }, adminContext, 'push');
1181
+ writeURLState({ detailId: formatContractRouteId(binding.resource, id) }, adminContext, 'push');
1039
1182
  }
1040
1183
 
1041
1184
  function closeDetail(): void {
@@ -1050,33 +1193,257 @@
1050
1193
 
1051
1194
  // ─── CSV Export ───────────────────────────────────────────────
1052
1195
  function exportCSV() {
1053
- const data = query.data?.data ?? [];
1054
- if (data.length === 0) return;
1055
- const headers = visibleFields.map(f => f.label);
1056
- const rows = data.map(record =>
1057
- visibleFields.map(f => {
1058
- const val = record[f.key];
1059
- if (val == null) return '';
1060
- if (typeof val === 'object') return JSON.stringify(val);
1061
- return String(val);
1062
- })
1063
- );
1064
- const csv = [headers.map(h => `"${h.replace(/"/g, '""')}"`).join(','), ...rows.map(r => r.map(v => `"${v.replace(/"/g, '""')}"`).join(','))].join('\n');
1065
- const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' });
1066
- const link = document.createElement('a');
1067
- link.href = URL.createObjectURL(blob);
1068
- link.download = `${resourceName}_${new Date().toISOString().slice(0, 10)}.csv`;
1069
- link.click();
1070
- setTimeout(() => URL.revokeObjectURL(link.href), 1000);
1196
+ if (!canRead || !canExport || !pageRecords.ok || query.isError || query.isFetching || !pageRecords.data.length) return;
1197
+ try {
1198
+ const exportFields = table_getAllLeafColumns(tbl).flatMap(column => {
1199
+ const field = visibleFields.find(field => field.key === column.id);
1200
+ return field && isColumnVisible(field.key) ? [field] : [];
1201
+ });
1202
+ if (!exportFields.length) return;
1203
+ const rows = tableExportRows(pageRecords.data, exportFields);
1204
+ downloadData(rows, resourceName, 'csv');
1205
+ } catch { operationError = i18n.t('common.operationFailed'); }
1071
1206
  }
1072
1207
 
1073
1208
  function goToPage(page: number) {
1209
+ if (!Number.isSafeInteger(page) || page < 1 || page > Math.max(1, totalPages)) return;
1074
1210
  markSavedViewDirty();
1075
1211
  pagination = { ...pagination, current: page };
1076
1212
  }
1213
+ function refreshList() {
1214
+ if (canRead && !query.isFetching) void listResult.refetch();
1215
+ }
1216
+ const gridRowIndex = $derived(new Map(tableView.rows.map(row => [rowIdValue(row), row])));
1217
+ const gridColumns = $derived.by(() => {
1218
+ void tableColumnOrder.current; void tableColumnVisibility.current;
1219
+ return table_getAllLeafColumns(tbl).filter(column => isColumnVisible(column.id)).map(column => ({
1220
+ key: column.id,
1221
+ label: column.id === '_actions' ? i18n.t('common.actions') : column.id === '_select' ? i18n.t('common.selectAll') : column.id === '_expand' ? i18n.t('common.expand') : visibleFields.find(field => field.key === column.id)?.label ?? column.id,
1222
+ width: Math.max(40, Math.min(4096, column_getSize(column))),
1223
+ sortable: column_getCanSort(column),
1224
+ }));
1225
+ });
1226
+ function changeGridSort(next: readonly Sort[]): void {
1227
+ if (!canRead || query.isFetching || deleteManyMutation.isPending) return;
1228
+ markSavedViewDirty();
1229
+ const accepted = next.filter(sort => gridColumns.some(column => column.key === sort.field && column.sortable));
1230
+ sorters = accepted.map(sort => ({ ...sort }));
1231
+ sortingAtom.set(accepted.map(sort => ({ id: sort.field, desc: sort.order === 'desc' })));
1232
+ pagination = { ...pagination, current: 1 };
1233
+ }
1077
1234
  </script>
1078
1235
 
1079
- <div class="svadmin-u-6ed543e2fbbb">
1236
+ {#snippet renderGridCell({ id, field }: { id: string | number; field: string })}
1237
+ {@const row = gridRowIndex.get(id)}
1238
+ {#if row}
1239
+ {@const record = copyTableRecord(row.original)}
1240
+ {@const cell = row_getVisibleCells(row).find(item => item.column.id === field)}
1241
+ {#if cell}
1242
+ <ContextMenu.Root>
1243
+ <ContextMenu.Trigger>
1244
+ {#snippet child({ props })}
1245
+ <div {...props}>
1246
+ {#if cell.column.id === '_select'}
1247
+ {#if batchActions}
1248
+ <Checkbox
1249
+ aria-label={i18n.t('common.selectRow', { id })}
1250
+ checked={rowIsSelected(row.id)}
1251
+ onCheckedChange={() => toggleRowSelection(row)}
1252
+ />
1253
+ {:else}
1254
+ <CanAccess resource={resourceName} action="delete" params={{ id }}>
1255
+ <Checkbox
1256
+ aria-label={i18n.t('common.selectRow', { id })}
1257
+ checked={rowIsSelected(row.id)}
1258
+ onCheckedChange={() => toggleRowSelection(row)}
1259
+ />
1260
+ </CanAccess>
1261
+ {/if}
1262
+ {:else if cell.column.id === '_expand'}
1263
+ <TooltipButton tooltip={rowIsExpanded(row.id) ? i18n.t('common.collapse') : i18n.t('common.expand')} variant="ghost" size="icon" class="svadmin-u-d0a52b312f7d svadmin-u-cbbf90f9a828" onclick={() => toggleRowExpanded(row.id)}>
1264
+ {#if rowIsExpanded(row.id)}
1265
+ <ChevronUp class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
1266
+ {:else}
1267
+ <ChevronDown class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
1268
+ {/if}
1269
+ </TooltipButton>
1270
+ {:else if cell.column.id === '_actions'}
1271
+ <div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-77c08e015d14 svadmin-u-44ee8ba0a421">
1272
+ {#if rowActions}
1273
+ {@render rowActions({ record, id })}
1274
+ {:else}
1275
+ {@render defaultRowActions(id)}
1276
+ {/if}
1277
+ </div>
1278
+ {:else}
1279
+ {@const field = visibleFields.find(f => f.key === cell.column.id)}
1280
+ {@const customColumn = field ? customColumns?.[field.key] : undefined}
1281
+ {#if customColumn}
1282
+ {@render customColumn({ value: clonePlainValue(cell_getValue(cell)), record: copyTableRecord(record) })}
1283
+ {:else if defaultCellRenderer && field}
1284
+ {@render defaultCellRenderer({ field, value: clonePlainValue(cell_getValue(cell)), record: copyTableRecord(record) })}
1285
+ {:else if canEdit && field && field.showInEdit !== false && writableFields.has(field.key) && field.key !== primaryKey && field.key !== 'id' && ['text', 'number', 'email', 'url'].includes(field.type)}
1286
+ <CanAccess resource={resourceName} action="edit" params={{ id }}>
1287
+ <InlineEdit
1288
+ {resourceName}
1289
+ recordId={id}
1290
+ {field}
1291
+ value={cell_getValue(cell)}
1292
+ />
1293
+ {#snippet fallback()}
1294
+ <FieldDisplay type={field.type} value={cell_getValue(cell)} options={field.options} />
1295
+ {/snippet}
1296
+ </CanAccess>
1297
+ {:else if field?.key === 'id'}
1298
+ <span class="svadmin-u-0214b4b355d1 svadmin-u-f283ea9bea0e svadmin-u-0e65706bcccd svadmin-u-359090c2d529" title={String(cell_getValue(cell) ?? '—')}>{cell_getValue(cell) ?? '—'}</span>
1299
+ {:else if field}
1300
+ <FieldDisplay type={field.type} value={cell_getValue(cell)} options={field.options} resourceName={field.resource} />
1301
+ {/if}
1302
+ {/if}
1303
+ </div>
1304
+ {/snippet}
1305
+ </ContextMenu.Trigger>
1306
+ <ContextMenu.Content class="svadmin-u-74b2435a1d40">
1307
+ {#if canEdit}
1308
+ <CanAccess resource={resourceName} action="edit" params={{ id }}>
1309
+ <ContextMenu.Item onclick={() => navigation.edit(resourceName, id)} class="svadmin-u-77a2a20e90d4">
1310
+ <Pencil class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" /> {i18n.t('common.edit')}
1311
+ </ContextMenu.Item>
1312
+ </CanAccess>
1313
+ {/if}
1314
+ {#if canShow}
1315
+ <CanAccess resource={resourceName} action="show" params={{ id }}>
1316
+ <ContextMenu.Item onclick={() => openDetail(id)} class="svadmin-u-77a2a20e90d4">
1317
+ <Eye class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" /> {i18n.t('common.detail')}
1318
+ </ContextMenu.Item>
1319
+ </CanAccess>
1320
+ {/if}
1321
+ <ContextMenu.Item onclick={() => navigator.clipboard?.writeText(String(id))} class="svadmin-u-77a2a20e90d4">
1322
+ <Copy class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" /> {i18n.t('common.copyId')}
1323
+ </ContextMenu.Item>
1324
+ {#if canDelete}
1325
+ <CanAccess resource={resourceName} action="delete" params={{ id }}>
1326
+ <ContextMenu.Separator />
1327
+ <ContextMenu.Item onclick={() => confirmDelete(id)} class="svadmin-u-77a2a20e90d4 svadmin-u-811148b13d1e">
1328
+ <Trash2 class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" /> {i18n.t('common.delete')}
1329
+ </ContextMenu.Item>
1330
+ </CanAccess>
1331
+ {/if}
1332
+ </ContextMenu.Content>
1333
+ </ContextMenu.Root>
1334
+ {/if}
1335
+ {/if}
1336
+ {/snippet}
1337
+
1338
+ {#snippet renderGridControls()}
1339
+ <Table.Root density={currentDensity}>
1340
+ <Table.Header>
1341
+ {#each tableView.headerGroups as headerGroup, _i (_i)}
1342
+ {@const visibleHeaders = headerGroup.headers.filter((header: Header<TableFeatures, TableRecord, unknown>) => isColumnVisible(header.column.id))}
1343
+ <DraggableHeader
1344
+ columns={visibleHeaders.map((header) => ({ id: header.column.id, header }))}
1345
+ onReorder={setColumnOrder}
1346
+ >
1347
+ {#snippet header(col, _index, dragProps)}
1348
+ {@const header = col.header}
1349
+ <Table.Head
1350
+ {...dragProps}
1351
+ class={cn('svadmin-u-65fdbade2025 svadmin-u-18049387f0af svadmin-u-b247a17a0d75 svadmin-u-2689f3958069 svadmin-u-d9256981a032 svadmin-u-bfa603190748 svadmin-u-f6e31b39b8e4', dragProps.class)}
1352
+ style={header_getSize(header) != null && header_getSize(header) !== 150 ? `width:${header_getSize(header)}px` : undefined}
1353
+ >
1354
+ {#if header.id === '_select'}
1355
+ {#if batchActions}
1356
+ <Checkbox
1357
+ aria-label={i18n.t('common.selectAll')}
1358
+ checked={table_getIsAllRowsSelected(tbl)}
1359
+ onCheckedChange={toggleAllRowsSelection}
1360
+ />
1361
+ {:else if !acEnabled || deleteResourcePermission.isLoading || deleteResourcePermission.allowed}
1362
+ <Checkbox
1363
+ aria-label={i18n.t('common.selectAll')}
1364
+ checked={table_getIsAllRowsSelected(tbl)}
1365
+ onCheckedChange={toggleAllRowsSelection}
1366
+ />
1367
+ {/if}
1368
+ {:else if header.id === '_expand'}
1369
+ <!-- empty -->
1370
+ {:else if header.id === '_actions'}
1371
+ <span class="svadmin-u-308fc069e46e svadmin-u-0214b4b355d1">{i18n.t('common.actions')}</span>
1372
+ {:else if column_getCanSort(header.column)}
1373
+ <Button
1374
+ variant="ghost"
1375
+ size="sm"
1376
+ class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-44ee8ba0a421 svadmin-u-ea7b2e9e070e svadmin-u-8ffe7a29179a svadmin-u-b8f0a08ece1e svadmin-u-660d2effb880 svadmin-u-d5eab218aa34 uppercase svadmin-u-8baf13a3e9d7 svadmin-u-7357df2b2e0c svadmin-u-e83a7042bc91"
1377
+ onclick={() => toggleColumnSort(header.column)}
1378
+ >
1379
+ {visibleFields.find(f => f.key === header.id)?.label ?? header.id}
1380
+ <span class="svadmin-u-359090c2d529 svadmin-u-0b8c506a0596">
1381
+ {#if column_getIsSorted(header.column) === 'asc'}↑
1382
+ {:else if column_getIsSorted(header.column) === 'desc'}↓
1383
+ {:else}⇅
1384
+ {/if}
1385
+ </span>
1386
+ </Button>
1387
+ {:else}
1388
+ {visibleFields.find(f => f.key === header.id)?.label ?? header.id}
1389
+ {/if}
1390
+ </Table.Head>
1391
+ {/snippet}
1392
+ </DraggableHeader>
1393
+ {/each}
1394
+ </Table.Header>
1395
+ </Table.Root>
1396
+ {/snippet}
1397
+
1398
+ {#snippet renderGridAfter()}
1399
+ <Table.Root density={currentDensity}>
1400
+ <Table.Body>
1401
+ {#each tableView.rows as row (row.id)}
1402
+ {#if expandedRowRender && rowIsExpanded(row.id)}
1403
+ <Table.Row><Table.Cell colspan={Math.max(1, gridColumns.length)}>
1404
+ {@render expandedRowRender({ record: copyTableRecord(row.original) })}
1405
+ </Table.Cell></Table.Row>
1406
+ {/if}
1407
+ {/each}
1408
+ </Table.Body>
1409
+ {#if summary}<Table.Footer>
1410
+ {@render summary({ data: pageRecords.data.map(copyTableRecord), total: query.data?.total ?? 0, visibleColumnsCount: gridColumns.length })}
1411
+ </Table.Footer>{/if}
1412
+ </Table.Root>
1413
+ {/snippet}
1414
+
1415
+ {#snippet defaultRowActions(id: string | number)}
1416
+ <RecordRowActions
1417
+ {resourceName} {id} {canShow} {canEdit} {canDelete}
1418
+ onShow={() => openDetail(id)}
1419
+ onEdit={() => navigation.edit(resourceName, id)}
1420
+ onQuickEdit={() => quickEditId = id}
1421
+ onDelete={() => confirmDelete(id)}
1422
+ />
1423
+ {/snippet}
1424
+
1425
+ {#snippet defaultEmptyState()}
1426
+ {@const hasCriteria = !!(searchText.trim() || appliedSearchText.trim() || activeFilterCount > 0)}
1427
+ <DataState state="empty" description={i18n.t(hasCriteria ? 'empty.description' : 'common.noDataHint')}>
1428
+ {#snippet action()}
1429
+ {#if hasCriteria}
1430
+ <Button variant="outline" size="sm" onclick={clearFilters}>
1431
+ <X class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
1432
+ {i18n.t('common.clearAllFilters')}
1433
+ </Button>
1434
+ {:else if canCreate}
1435
+ <Button variant="outline" size="sm" onclick={() => navigation.create(resourceName)}>
1436
+ <Plus class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
1437
+ {i18n.t('common.create')}
1438
+ </Button>
1439
+ {/if}
1440
+ {/snippet}
1441
+ </DataState>
1442
+ {/snippet}
1443
+
1444
+ <div class="svadmin-u-6ed543e2fbbb" data-svadmin-rendering-resource={activeRendering?.resource.name} data-svadmin-rendering-kind={activeRendering ? 'table' : undefined}>
1445
+ {#if operationError}<p role="alert">{operationError}</p>{/if}
1446
+ {#if detailState.invalid}<p role="alert">{i18n.t('common.operationFailed')}</p>{/if}
1080
1447
  {#if showHeader}
1081
1448
  <!-- Header -->
1082
1449
  <div class="svadmin-u-60fbb7713999 svadmin-u-1eb5c6df38c1 svadmin-u-3960ffc248d9 svadmin-u-8ef2268efbbc svadmin-u-77a2a20e90d4">
@@ -1101,7 +1468,7 @@
1101
1468
 
1102
1469
  <!-- Selection Banner (Enterprise Batch Actions) -->
1103
1470
  {#if selectedCount > 0}
1104
- <div
1471
+ <div
1105
1472
  class="svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed svadmin-u-1004c0c3954c svadmin-u-ca6bcd4b6f3f svadmin-u-a6afccfc915b svadmin-u-375dc44df6e9 svadmin-u-e0d9cc7f0647 svadmin-u-03b4dd7f172b svadmin-u-5f22e64f2282 svadmin-u-fc7473ca09eb svadmin-u-d4108abe6359 svadmin-u-40137e897961 fade-in svadmin-u-625a4c3fbeb2 svadmin-u-259ce51fc8f3 svadmin-u-020ba687fa12 svadmin-u-9f76a62f4f44 svadmin-u-3b9871a0bf93"
1106
1473
  aria-label={i18n.t("common.selectedCount", { count: selectedCount })}
1107
1474
  data-svadmin-batch-toolbar
@@ -1181,7 +1548,7 @@
1181
1548
  id="filter-{field.key}"
1182
1549
  class="svadmin-u-e7a768f922d2 svadmin-u-fc7473ca09eb"
1183
1550
  value={filterValues[field.key] ?? ""}
1184
- onchange={(e) => setFilterValue(field.key, (e.currentTarget as HTMLSelectElement).value)}
1551
+ onchange={(e: Event) => setFilterFromEvent(field.key, e)}
1185
1552
  >
1186
1553
  <option value="">{i18n.t("common.all")}</option>
1187
1554
  {#each field.options as opt, _i (_i)}
@@ -1193,7 +1560,7 @@
1193
1560
  id="filter-{field.key}"
1194
1561
  type="text"
1195
1562
  value={filterValues[field.key] ?? ""}
1196
- oninput={(e) => setFilterValue(field.key, e.currentTarget.value)}
1563
+ oninput={(e: Event) => setFilterFromEvent(field.key, e)}
1197
1564
  placeholder={field.label}
1198
1565
  class="svadmin-u-e7a768f922d2 svadmin-u-fc7473ca09eb"
1199
1566
  />
@@ -1225,7 +1592,7 @@
1225
1592
  {#if showDensitySwitcher}
1226
1593
  <DropdownMenu.Root>
1227
1594
  <DropdownMenu.Trigger>
1228
- {#snippet child({ props })}
1595
+ {#snippet child({ props }: { props: Record<string, unknown> })}
1229
1596
  <TooltipButton tooltip={i18n.t("common.density")} variant="outline" size="sm" class="svadmin-u-e7a768f922d2 svadmin-u-0b91436debbd" {...props}>
1230
1597
  <Rows class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" aria-hidden="true" />
1231
1598
  </TooltipButton>
@@ -1245,7 +1612,7 @@
1245
1612
  <!-- Column Visibility Picker -->
1246
1613
  <DropdownMenu.Root>
1247
1614
  <DropdownMenu.Trigger>
1248
- {#snippet child({ props })}
1615
+ {#snippet child({ props }: { props: Record<string, unknown> })}
1249
1616
  <TooltipButton tooltip={i18n.t("common.columns")} variant="outline" size="sm" class="svadmin-u-e7a768f922d2 svadmin-u-0b91436debbd" {...props}>
1250
1617
  <SlidersHorizontal class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
1251
1618
  </TooltipButton>
@@ -1255,7 +1622,7 @@
1255
1622
  {#each table_getAllLeafColumns(tbl).filter((column) => !column.id.startsWith("_")) as column, _i (_i)}
1256
1623
  <DropdownMenu.CheckboxItem
1257
1624
  checked={tableColumnVisibility.current[column.id] ?? true}
1258
- onCheckedChange={(v) => setColumnVisibility(column.id, !!v)}
1625
+ onCheckedChange={(v: boolean) => setColumnVisibility(column.id, v)}
1259
1626
  >
1260
1627
  {visibleFields.find(f => f.key === column.id)?.label ?? column.id}
1261
1628
  </DropdownMenu.CheckboxItem>
@@ -1284,8 +1651,9 @@
1284
1651
  id="saved-list-view"
1285
1652
  class="svadmin-u-e7a768f922d2 svadmin-u-6da6a3c3f741"
1286
1653
  value={activeSavedViewId ?? ""}
1287
- onchange={(event) => {
1288
- const id = (event.currentTarget as HTMLSelectElement).value;
1654
+ onchange={(event: Event) => {
1655
+ if (!(event.currentTarget instanceof HTMLSelectElement)) return;
1656
+ const id = event.currentTarget.value;
1289
1657
  const view = savedViews.find((candidate) => candidate.id === id);
1290
1658
  if (view) applySavedView(view);
1291
1659
  else {
@@ -1340,7 +1708,7 @@
1340
1708
  variant="outline"
1341
1709
  size="sm"
1342
1710
  class="svadmin-u-e7a768f922d2 svadmin-u-0b91436debbd"
1343
- onclick={() => listResult.refetch()}
1711
+ onclick={refreshList}
1344
1712
  >
1345
1713
  <RefreshCw class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3 {query.isFetching ? "svadmin-u-afbdd13a380e" : ""}" />
1346
1714
  </TooltipButton>
@@ -1398,7 +1766,40 @@
1398
1766
 
1399
1767
  <!-- Table (TanStack-powered) -->
1400
1768
  <div class="svadmin-u-2cd02d11d1af svadmin-u-5f22e64f2282 svadmin-u-ca6bcd4b6f3f svadmin-u-18049387f0af svadmin-u-cd0ad9a56558 svadmin-u-438b2237b8d6" role="region" aria-label="{resource.label} {i18n.t('common.list')}" data-table-density={currentDensity}>
1401
- {#if query.isLoading}
1769
+ {#if listPermission.isLoading || (query.isLoading && !query.isError)}
1770
+ <Table.Root density={currentDensity}>
1771
+ <Table.Header>
1772
+ {#each tableView.headerGroups as headerGroup, _i (_i)}
1773
+ {@const visibleHeaders = headerGroup.headers.filter((header: Header<TableFeatures, TableRecord, unknown>) => isColumnVisible(header.column.id))}
1774
+ <Table.Row>
1775
+ {#each visibleHeaders as header (header.id)}
1776
+ <Table.Head>
1777
+ {#if header.id === '_actions'}
1778
+ {i18n.t('common.actions')}
1779
+ {:else if column_getCanSort(header.column)}
1780
+ <Button
1781
+ variant="ghost"
1782
+ size="sm"
1783
+ class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-44ee8ba0a421 svadmin-u-ea7b2e9e070e svadmin-u-8ffe7a29179a svadmin-u-b8f0a08ece1e svadmin-u-660d2effb880 svadmin-u-d5eab218aa34 uppercase svadmin-u-8baf13a3e9d7 svadmin-u-7357df2b2e0c svadmin-u-e83a7042bc91"
1784
+ onclick={() => toggleColumnSort(header.column)}
1785
+ >
1786
+ {visibleFields.find(field => field.key === header.id)?.label ?? header.id}
1787
+ <span class="svadmin-u-359090c2d529 svadmin-u-0b8c506a0596">
1788
+ {#if column_getIsSorted(header.column) === 'asc'}↑
1789
+ {:else if column_getIsSorted(header.column) === 'desc'}↓
1790
+ {:else}⇅
1791
+ {/if}
1792
+ </span>
1793
+ </Button>
1794
+ {:else}
1795
+ {visibleFields.find(field => field.key === header.id)?.label ?? header.id}
1796
+ {/if}
1797
+ </Table.Head>
1798
+ {/each}
1799
+ </Table.Row>
1800
+ {/each}
1801
+ </Table.Header>
1802
+ </Table.Root>
1402
1803
  <div class="svadmin-u-8e63407b5ceb svadmin-u-6ed543e2fbbb">
1403
1804
  <div class="svadmin-u-60fbb7713999 svadmin-u-0c3bc98565dd svadmin-u-a77ed4d908c0">
1404
1805
  {#each visibleFields.slice(0, 4) as _, _i (_i)}
@@ -1413,12 +1814,20 @@
1413
1814
  </div>
1414
1815
  {/each}
1415
1816
  </div>
1416
- {:else if query.error}
1817
+ {:else if !canRead}
1818
+ <p role="alert">{i18n.t('common.operationFailed')}</p>
1819
+ {:else if query.error || query.isError || !pageRecords.ok}
1417
1820
  <DataState
1418
1821
  state="error"
1419
- description={i18n.t('common.loadFailed', { message: (query.error as Error).message })}
1420
- retry={() => listResult.refetch()}
1822
+ description={i18n.t('common.operationFailed')}
1823
+ retry={refreshList}
1421
1824
  />
1825
+ {:else if gridBody && pageRecords.data.length > 0}
1826
+ {@render gridBody({ records: pageRecords.data.map(copyTableRecord), columns: gridColumns, primaryKey: 'id',
1827
+ sorters: querySorters, density: currentDensity, locale: i18n.locale, label: resource.label,
1828
+ scopeKey: JSON.stringify([resourceName, queryPagination, querySorters, queryFilters, adminContext.tenantCacheKey?.__svadminTenant]),
1829
+ disabled: query.isFetching || deleteManyMutation.isPending,
1830
+ onSortChange: changeGridSort, cell: renderGridCell, controls: renderGridControls, after: renderGridAfter })}
1422
1831
  {:else}
1423
1832
  <div in:fade={{ duration: 150 }}>
1424
1833
  <!-- Desktop Table (hidden on mobile) -->
@@ -1426,13 +1835,13 @@
1426
1835
  <Table.Root density={currentDensity}>
1427
1836
  <Table.Header>
1428
1837
  {#each tableView.headerGroups as headerGroup, _i (_i)}
1429
- {@const visibleHeaders = headerGroup.headers.filter((header: Header<TableFeatures, BaseRecord, unknown>) => isColumnVisible(header.column.id))}
1838
+ {@const visibleHeaders = headerGroup.headers.filter((header: Header<TableFeatures, TableRecord, unknown>) => isColumnVisible(header.column.id))}
1430
1839
  <DraggableHeader
1431
1840
  columns={visibleHeaders.map((header) => ({ id: header.column.id, header }))}
1432
1841
  onReorder={setColumnOrder}
1433
1842
  >
1434
1843
  {#snippet header(col, _index, dragProps)}
1435
- {@const header = col.header as typeof headerGroup.headers[0]}
1844
+ {@const header = col.header}
1436
1845
  <Table.Head
1437
1846
  {...dragProps}
1438
1847
  class={cn('svadmin-u-65fdbade2025 svadmin-u-18049387f0af svadmin-u-b247a17a0d75 svadmin-u-2689f3958069 svadmin-u-d9256981a032 svadmin-u-bfa603190748 svadmin-u-f6e31b39b8e4', dragProps.class)}
@@ -1445,14 +1854,12 @@
1445
1854
  checked={table_getIsAllRowsSelected(tbl)}
1446
1855
  onCheckedChange={toggleAllRowsSelection}
1447
1856
  />
1448
- {:else}
1449
- <CanAccess resource={resourceName} action="delete" params={{ ids: tableView.rows.map(rowIdValue) }}>
1857
+ {:else if !acEnabled || deleteResourcePermission.isLoading || deleteResourcePermission.allowed}
1450
1858
  <Checkbox
1451
1859
  aria-label={i18n.t('common.selectAll')}
1452
1860
  checked={table_getIsAllRowsSelected(tbl)}
1453
1861
  onCheckedChange={toggleAllRowsSelection}
1454
1862
  />
1455
- </CanAccess>
1456
1863
  {/if}
1457
1864
  {:else if header.id === '_expand'}
1458
1865
  <!-- empty -->
@@ -1483,8 +1890,8 @@
1483
1890
  </Table.Header>
1484
1891
  <Table.Body>
1485
1892
  {#each tableView.rows as row (row.id)}
1486
- {@const record = row.original}
1487
- {@const id = record[primaryKey] as string | number}
1893
+ {@const record = copyTableRecord(row.original)}
1894
+ {@const id = rowIdValue(row)}
1488
1895
  {@const visibleCells = row_getVisibleCells(row).filter((cell) => isColumnVisible(cell.column.id))}
1489
1896
  <ContextMenu.Root>
1490
1897
  <ContextMenu.Trigger>
@@ -1521,65 +1928,32 @@
1521
1928
  {#if rowActions}
1522
1929
  {@render rowActions({ record, id })}
1523
1930
  {:else}
1524
- {#if canShow}
1525
- <CanAccess resource={resourceName} action="show" params={{ id }}>
1526
- <TooltipButton tooltip={i18n.t('common.detail')} variant="ghost" size="icon-sm" onclick={() => openDetail(id)}>
1527
- <Eye class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
1528
- </TooltipButton>
1529
- </CanAccess>
1530
- {/if}
1531
- {#if canEdit}
1532
- <CanAccess resource={resourceName} action="edit" params={{ id }}>
1533
- <TooltipButton tooltip={i18n.t('common.edit')} variant="ghost" size="icon-sm" onclick={() => navigation.edit(resourceName, id)}>
1534
- <Pencil class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
1535
- </TooltipButton>
1536
- </CanAccess>
1537
- {/if}
1538
- {#if canDelete}
1539
- <CanAccess resource={resourceName} action="delete" params={{ id }}>
1540
- <TooltipButton tooltip={i18n.t('common.delete')} variant="ghost" size="icon-sm" onclick={() => confirmDelete(id)} class="svadmin-u-51e95020d6f2">
1541
- <Trash2 class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
1542
- </TooltipButton>
1543
- </CanAccess>
1544
- {/if}
1931
+ {@render defaultRowActions(id)}
1545
1932
  {/if}
1546
1933
  </div>
1547
1934
  {:else}
1548
1935
  {@const field = visibleFields.find(f => f.key === cell.column.id)}
1549
- {#if customColumns && field && customColumns[field.key]}
1550
- {@render customColumns[field.key]({ value: cell_getValue(cell), record })}
1936
+ {@const customColumn = field ? customColumns?.[field.key] : undefined}
1937
+ {#if customColumn}
1938
+ {@render customColumn({ value: clonePlainValue(cell_getValue(cell)), record: copyTableRecord(record) })}
1551
1939
  {:else if defaultCellRenderer && field}
1552
- {@render defaultCellRenderer({ field, value: cell_getValue(cell), record })}
1553
- {:else if field?.type === 'boolean'}
1554
- <span class="svadmin-u-bb0c4bfc52bd svadmin-u-2f2a842e50fa svadmin-u-940924b6e2d9 svadmin-u-ac204c108886 {cell_getValue(cell) ? 'svadmin-u-3355648fe22b' : 'svadmin-u-2d56f1807e51'}"></span>
1555
- {:else if field?.type === 'date' && cell_getValue(cell)}
1556
- {new Date(cell_getValue(cell) as string).toLocaleDateString()}
1557
- {:else if field?.type === 'tags' && Array.isArray(cell_getValue(cell))}
1558
- <div class="svadmin-u-60fbb7713999 svadmin-u-1eb5c6df38c1 svadmin-u-44ee8ba0a421">
1559
- {#each (cell_getValue(cell) as string[]).slice(0, 3) as tag, _i (_i)}
1560
- <Badge variant="secondary">{tag}</Badge>
1561
- {/each}
1562
- </div>
1563
- {:else if field?.type === 'select' && field.options}
1564
- {@const opt = field.options.find(o => o.value === cell_getValue(cell))}
1565
- <Badge variant="outline">{opt?.label ?? cell_getValue(cell) ?? '—'}</Badge>
1566
- {:else if canEdit && field && ['text', 'number', 'email', 'url'].includes(field.type)}
1940
+ {@render defaultCellRenderer({ field, value: clonePlainValue(cell_getValue(cell)), record: copyTableRecord(record) })}
1941
+ {:else if canEdit && field && field.showInEdit !== false && writableFields.has(field.key) && field.key !== primaryKey && field.key !== 'id' && ['text', 'number', 'email', 'url'].includes(field.type)}
1567
1942
  <CanAccess resource={resourceName} action="edit" params={{ id }}>
1568
1943
  <InlineEdit
1569
1944
  {resourceName}
1570
1945
  recordId={id}
1571
1946
  {field}
1572
1947
  value={cell_getValue(cell)}
1573
- onSave={() => listResult.refetch()}
1574
1948
  />
1575
1949
  {#snippet fallback()}
1576
- <span class="svadmin-u-0214b4b355d1 svadmin-u-f283ea9bea0e" title={String(cell_getValue(cell) ?? '—')}>{cell_getValue(cell) ?? '—'}</span>
1950
+ <FieldDisplay type={field.type} value={cell_getValue(cell)} options={field.options} />
1577
1951
  {/snippet}
1578
1952
  </CanAccess>
1579
- {:else if field?.key === primaryKey}
1953
+ {:else if field?.key === 'id'}
1580
1954
  <span class="svadmin-u-0214b4b355d1 svadmin-u-f283ea9bea0e svadmin-u-0e65706bcccd svadmin-u-359090c2d529" title={String(cell_getValue(cell) ?? '—')}>{cell_getValue(cell) ?? '—'}</span>
1581
- {:else}
1582
- <span class="svadmin-u-0214b4b355d1 svadmin-u-f283ea9bea0e" title={String(cell_getValue(cell) ?? '—')}>{cell_getValue(cell) ?? '—'}</span>
1955
+ {:else if field}
1956
+ <FieldDisplay type={field.type} value={cell_getValue(cell)} options={field.options} resourceName={field.resource} />
1583
1957
  {/if}
1584
1958
  {/if}
1585
1959
  </Table.Cell>
@@ -1628,19 +2002,7 @@
1628
2002
  {#if emptyState}
1629
2003
  {@render emptyState()}
1630
2004
  {:else}
1631
- <div class="svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed svadmin-u-3960ffc248d9 svadmin-u-86843cf1e227 svadmin-u-a1f611f027dd">
1632
- <svg class="svadmin-u-acaee62117b1 svadmin-u-baceed3462fd svadmin-u-106b502aac96 svadmin-u-da019856f2cc" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor">
1633
- <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" />
1634
- </svg>
1635
- <p class="svadmin-u-fc7473ca09eb svadmin-u-2689f3958069 svadmin-u-bfa603190748 svadmin-u-65281709dacf">{i18n.t('common.noData')}</p>
1636
- <p class="svadmin-u-359090c2d529 svadmin-u-7be4d67a6256 svadmin-u-da019856f2cc">{i18n.t('common.noDataHint')}</p>
1637
- {#if canCreate}
1638
- <Button variant="outline" size="sm" class="svadmin-u-77a2a20e90d4" onclick={() => navigation.create(resourceName)}>
1639
- <Plus class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" />
1640
- {i18n.t('common.create')}
1641
- </Button>
1642
- {/if}
1643
- </div>
2005
+ {@render defaultEmptyState()}
1644
2006
  {/if}
1645
2007
  </Table.Cell>
1646
2008
  </Table.Row>
@@ -1648,7 +2010,7 @@
1648
2010
  </Table.Body>
1649
2011
  {#if summary}
1650
2012
  <Table.Footer class="svadmin-u-2859c861d7de svadmin-u-2689f3958069">
1651
- {@render summary({ data: (query.data?.data as BaseRecord[]) ?? [], total: query.data?.total ?? 0, visibleColumnsCount: columns.filter((c) => c.id != null && isColumnVisible(c.id)).length })}
2013
+ {@render summary({ data: pageRecords.data.map(copyTableRecord), total: query.data?.total ?? 0, visibleColumnsCount: columns.filter((c) => c.id != null && isColumnVisible(c.id)).length })}
1652
2014
  </Table.Footer>
1653
2015
  {/if}
1654
2016
  </Table.Root>
@@ -1657,8 +2019,8 @@
1657
2019
  <!-- Mobile Card View (visible only on small screens) -->
1658
2020
  <div class="svadmin-u-e477a6af4cb6 svadmin-u-6ed543e2fbbb svadmin-u-7660b450905a">
1659
2021
  {#each tableView.rows as row, _i (_i)}
1660
- {@const record = row.original}
1661
- {@const id = record[primaryKey] as string | number}
2022
+ {@const record = copyTableRecord(row.original)}
2023
+ {@const id = rowIdValue(row)}
1662
2024
  <div
1663
2025
  class="svadmin-u-f5c8cc114f47 svadmin-u-633ef3c47872 svadmin-u-3daca9af0861 svadmin-u-a3158643e114 svadmin-u-cd0ad9a56558 svadmin-u-c07e54fd1439 svadmin-u-0fe7d7d814d0 {rowIsSelected(row.id) ? 'svadmin-u-16b1efa5875e svadmin-u-f42e9fee68a1 svadmin-u-989c466fdbe7' : ''}"
1664
2026
  >
@@ -1688,27 +2050,7 @@
1688
2050
  {#if rowActions}
1689
2051
  {@render rowActions({ record, id })}
1690
2052
  {:else}
1691
- {#if canEdit}
1692
- <CanAccess resource={resourceName} action="edit" params={{ id }}>
1693
- <TooltipButton tooltip={i18n.t('common.edit')} variant="ghost" size="icon-sm" onclick={() => navigation.edit(resourceName, id)}>
1694
- <Pencil class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
1695
- </TooltipButton>
1696
- </CanAccess>
1697
- {/if}
1698
- {#if canShow}
1699
- <CanAccess resource={resourceName} action="show" params={{ id }}>
1700
- <TooltipButton tooltip={i18n.t('common.detail')} variant="ghost" size="icon-sm" onclick={() => openDetail(id)}>
1701
- <Eye class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
1702
- </TooltipButton>
1703
- </CanAccess>
1704
- {/if}
1705
- {#if canDelete}
1706
- <CanAccess resource={resourceName} action="delete" params={{ id }}>
1707
- <TooltipButton tooltip={i18n.t('common.delete')} variant="ghost" size="icon-sm" onclick={() => confirmDelete(id)} class="svadmin-u-51e95020d6f2">
1708
- <Trash2 class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
1709
- </TooltipButton>
1710
- </CanAccess>
1711
- {/if}
2053
+ {@render defaultRowActions(id)}
1712
2054
  {/if}
1713
2055
  </div>
1714
2056
  </div>
@@ -1716,28 +2058,18 @@
1716
2058
  <div class="svadmin-u-6f7e013d6499">
1717
2059
  {#each visibleFields.filter((field) => isColumnVisible(field.key)).slice(0, 6) as field (field.key)}
1718
2060
  {@const value = record[field.key]}
2061
+ {@const customColumn = customColumns?.[field.key]}
1719
2062
  <div class="svadmin-u-60fbb7713999 svadmin-u-60541e1e26f8 svadmin-u-8ef2268efbbc svadmin-u-0c3bc98565dd">
1720
2063
  <span class="svadmin-u-359090c2d529 svadmin-u-2689f3958069 svadmin-u-bfa603190748 svadmin-u-012fbd121f37">{field.label}</span>
1721
2064
  <span class="svadmin-u-fc7473ca09eb svadmin-u-308fc069e46e svadmin-u-f283ea9bea0e svadmin-u-897d497e57b6">
1722
- {#if customColumns && field && customColumns[field.key]}
1723
- {@render customColumns[field.key]({ value, record })}
2065
+ {#if customColumn}
2066
+ {@render customColumn({ value: clonePlainValue(value), record: copyTableRecord(record) })}
1724
2067
  {:else if defaultCellRenderer && field}
1725
- {@render defaultCellRenderer({ field, value, record })}
1726
- {:else if field.type === 'boolean'}
1727
- <span class="svadmin-u-bb0c4bfc52bd svadmin-u-2f2a842e50fa svadmin-u-940924b6e2d9 svadmin-u-ac204c108886 {value ? 'svadmin-u-3355648fe22b' : 'svadmin-u-2d56f1807e51'}"></span>
1728
- {:else if field.type === 'date' && value}
1729
- {new Date(value as string).toLocaleDateString()}
1730
- {:else if field.type === 'tags' && Array.isArray(value)}
1731
- <div class="svadmin-u-60fbb7713999 svadmin-u-1eb5c6df38c1 svadmin-u-44ee8ba0a421 svadmin-u-77c08e015d14">
1732
- {#each (value as string[]).slice(0, 2) as tag, _i (_i)}
1733
- <Badge variant="secondary" class="svadmin-u-1dc571a3609f">{tag}</Badge>
1734
- {/each}
1735
- </div>
1736
- {:else if field.type === 'select' && field.options}
1737
- {@const opt = field.options.find(o => o.value === value)}
1738
- <Badge variant="outline" class="svadmin-u-1dc571a3609f">{opt?.label ?? value ?? '—'}</Badge>
2068
+ {@render defaultCellRenderer({ field, value: clonePlainValue(value), record: copyTableRecord(record) })}
2069
+ {:else if field.key === 'id'}
2070
+ {id}
1739
2071
  {:else}
1740
- {value ?? '—'}
2072
+ <FieldDisplay type={field.type} {value} options={field.options} resourceName={field.resource} />
1741
2073
  {/if}
1742
2074
  </span>
1743
2075
  </div>
@@ -1749,7 +2081,7 @@
1749
2081
  {#if emptyState}
1750
2082
  {@render emptyState()}
1751
2083
  {:else}
1752
- <DataState state="empty" class="svadmin-u-119b2aa0b8f6 svadmin-u-7f19cdf4c5bb svadmin-u-d5eab218aa34 svadmin-u-cb11fec3bb46" />
2084
+ {@render defaultEmptyState()}
1753
2085
  {/if}
1754
2086
  </div>
1755
2087
  {/each}
@@ -1767,9 +2099,10 @@
1767
2099
  aria-label={i18n.t('common.perPage')}
1768
2100
  class="svadmin-u-ed8a5df7b2fb svadmin-u-d043cad8e3fa svadmin-u-012fbd121f37 svadmin-u-3032cae0badb"
1769
2101
  value={String(pagination.pageSize ?? 10)}
1770
- onchange={(e) => {
1771
- const size = Number((e.currentTarget as HTMLSelectElement).value);
1772
- if (!isNaN(size)) {
2102
+ onchange={(e: Event) => {
2103
+ if (!(e.currentTarget instanceof HTMLSelectElement)) return;
2104
+ const size = Number(e.currentTarget.value);
2105
+ if (Number.isSafeInteger(size) && size > 0) {
1773
2106
  markSavedViewDirty();
1774
2107
  pagination = { ...pagination, pageSize: size, current: 1 };
1775
2108
  }
@@ -1797,7 +2130,7 @@
1797
2130
  <PaginationUI.Link
1798
2131
  class="svadmin-u-3032cae0badb"
1799
2132
  isActive={page === currentPage}
1800
- onclick={() => goToPage(page as number)}
2133
+ onclick={() => { if (typeof page === 'number') goToPage(page); }}
1801
2134
  >
1802
2135
  {page}
1803
2136
  </PaginationUI.Link>
@@ -1820,16 +2153,23 @@
1820
2153
  open={confirmOpen}
1821
2154
  message={confirmMessage}
1822
2155
  confirmText={i18n.t('common.delete')}
1823
- confirming={confirmPending}
2156
+ confirming={confirmPending || deletePermission.isLoading}
1824
2157
  onconfirm={confirmAction}
1825
- oncancel={() => { confirmOpen = false; }}
2158
+ oncancel={() => { if (!confirmPending) { confirmOpen = false; deleteRequest = null; } }}
1826
2159
  />
1827
2160
 
1828
2161
  {#if detailRecordId != null}
1829
2162
  <RecordDetailDrawer
2163
+ rendering={activeRendering}
1830
2164
  resourceName={resourceName}
1831
2165
  open={detailOpen}
1832
2166
  recordId={detailRecordId}
1833
2167
  onClose={closeDetail}
1834
2168
  />
1835
2169
  {/if}
2170
+
2171
+ {#if quickEditId != null && canRead && canEdit}
2172
+ {#key quickEditId}
2173
+ <QuickEditDrawer rendering={activeRendering} {resourceName} recordId={quickEditId} onClose={() => quickEditId = undefined} />
2174
+ {/key}
2175
+ {/if}