@svadmin/ui 0.73.0 → 0.74.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 (295) hide show
  1. package/README.md +53 -28
  2. package/dist/app.css +3733 -4119
  3. package/dist/app.theme.css +3733 -4119
  4. package/dist/component-registry.d.ts +14977 -0
  5. package/dist/component-registry.js +216 -0
  6. package/dist/components/AboutSettings.svelte +7 -1
  7. package/dist/components/AboutSettings.svelte.d.ts +4 -1
  8. package/dist/components/ActivityFeed.svelte +9 -3
  9. package/dist/components/ActivityFeed.svelte.d.ts +3 -0
  10. package/dist/components/AdminApp.svelte +19 -10
  11. package/dist/components/AdminApp.svelte.d.ts +6 -0
  12. package/dist/components/ApiSettings.svelte +41 -5
  13. package/dist/components/ApprovalCenter.svelte +283 -0
  14. package/dist/components/ApprovalCenter.svelte.d.ts +10 -0
  15. package/dist/components/AuditLogViewer.svelte +3 -1
  16. package/dist/components/AutoForm.svelte +33 -3
  17. package/dist/components/AutoForm.svelte.d.ts +1 -0
  18. package/dist/components/AutoTable.svelte +610 -47
  19. package/dist/components/AutoTable.svelte.d.ts +17 -1
  20. package/dist/components/BoundRecordDetailDrawer.svelte +9 -0
  21. package/dist/components/BoundRecordDetailDrawer.svelte.d.ts +2 -0
  22. package/dist/components/Breadcrumbs.svelte +11 -1
  23. package/dist/components/CancelTaskButton.svelte +12 -42
  24. package/dist/components/ColorPicker.svelte +73 -0
  25. package/dist/components/ColorPicker.svelte.d.ts +15 -0
  26. package/dist/components/ComboboxField.svelte +114 -27
  27. package/dist/components/ComboboxField.svelte.d.ts +1 -0
  28. package/dist/components/DashboardView.svelte +99 -0
  29. package/dist/components/DashboardView.svelte.d.ts +14 -0
  30. package/dist/components/DatePicker.svelte +10 -0
  31. package/dist/components/DatePicker.svelte.d.ts +6 -0
  32. package/dist/components/DateRangeInput.svelte +85 -5
  33. package/dist/components/DateRangeInput.svelte.d.ts +10 -0
  34. package/dist/components/DateRangePicker.svelte +10 -0
  35. package/dist/components/DateRangePicker.svelte.d.ts +6 -0
  36. package/dist/components/DateTimeInput.svelte +74 -5
  37. package/dist/components/DateTimeInput.svelte.d.ts +8 -0
  38. package/dist/components/DateTimePicker.svelte +10 -0
  39. package/dist/components/DateTimePicker.svelte.d.ts +6 -0
  40. package/dist/components/DevTools.svelte +34 -26
  41. package/dist/components/DevframePanel.svelte +123 -0
  42. package/dist/components/DevframePanel.svelte.d.ts +3 -0
  43. package/dist/components/ErrorSummary.svelte +52 -0
  44. package/dist/components/ErrorSummary.svelte.d.ts +14 -0
  45. package/dist/components/FieldRenderer.svelte +82 -81
  46. package/dist/components/FileUpload.svelte +214 -52
  47. package/dist/components/FileUpload.svelte.d.ts +11 -0
  48. package/dist/components/FilterBuilder.svelte +88 -66
  49. package/dist/components/FilterCollectionInput.svelte +80 -0
  50. package/dist/components/FilterCollectionInput.svelte.d.ts +10 -0
  51. package/dist/components/GanttChart.svelte +66 -14
  52. package/dist/components/GanttChart.svelte.d.ts +6 -0
  53. package/dist/components/Header.svelte +18 -2
  54. package/dist/components/ImportWizard.svelte +339 -17
  55. package/dist/components/ImportWizard.svelte.d.ts +11 -0
  56. package/dist/components/JsonSchemaForm.svelte +120 -52
  57. package/dist/components/JsonSchemaForm.svelte.d.ts +7 -1
  58. package/dist/components/KanbanBoard.svelte +195 -40
  59. package/dist/components/KanbanBoard.svelte.d.ts +9 -2
  60. package/dist/components/Layout.svelte +4 -2
  61. package/dist/components/LazyAutoForm.svelte +28 -0
  62. package/dist/components/LazyAutoForm.svelte.d.ts +8 -0
  63. package/dist/components/LazyAutoTable.svelte +28 -0
  64. package/dist/components/LazyAutoTable.svelte.d.ts +8 -0
  65. package/dist/components/LazySettingsPage.svelte +28 -0
  66. package/dist/components/LazySettingsPage.svelte.d.ts +8 -0
  67. package/dist/components/LazyShowPage.svelte +28 -0
  68. package/dist/components/LazyShowPage.svelte.d.ts +8 -0
  69. package/dist/components/ListPage.svelte +5 -1
  70. package/dist/components/ListPage.svelte.d.ts +3 -0
  71. package/dist/components/LoginPage.svelte +1 -1
  72. package/dist/components/MultiTabKeepAlive.svelte +11 -6
  73. package/dist/components/NotificationsSettings.svelte +69 -2
  74. package/dist/components/NotificationsSettings.svelte.d.ts +25 -1
  75. package/dist/components/PermissionMatrix.svelte +20 -1
  76. package/dist/components/PivotExportButton.svelte +157 -0
  77. package/dist/components/PivotExportButton.svelte.d.ts +16 -0
  78. package/dist/components/PivotTable.svelte +229 -74
  79. package/dist/components/PivotTable.svelte.d.ts +19 -2
  80. package/dist/components/RadioGroup.svelte +121 -0
  81. package/dist/components/RadioGroup.svelte.d.ts +43 -0
  82. package/dist/components/RangeSlider.svelte +127 -0
  83. package/dist/components/RangeSlider.svelte.d.ts +22 -0
  84. package/dist/components/Rate.svelte +121 -0
  85. package/dist/components/Rate.svelte.d.ts +16 -0
  86. package/dist/components/RecordDetailDrawer.svelte +4 -2
  87. package/dist/components/RecordDetailDrawer.svelte.d.ts +2 -0
  88. package/dist/components/RelationPicker.svelte +341 -0
  89. package/dist/components/RelationPicker.svelte.d.ts +26 -0
  90. package/dist/components/ResourceOperationsPage.svelte +18 -18
  91. package/dist/components/ResourceOperationsPage.svelte.d.ts +2 -2
  92. package/dist/components/RetryTaskButton.svelte +12 -42
  93. package/dist/components/SecuritySettings.svelte +29 -5
  94. package/dist/components/SegmentedControl.svelte +145 -0
  95. package/dist/components/SegmentedControl.svelte.d.ts +38 -0
  96. package/dist/components/SettingsPage.svelte +3 -1
  97. package/dist/components/SettingsPage.svelte.d.ts +1 -0
  98. package/dist/components/ShowPage.svelte +18 -3
  99. package/dist/components/ShowPage.svelte.d.ts +3 -0
  100. package/dist/components/Sidebar.svelte +2 -2
  101. package/dist/components/Slider.svelte +100 -0
  102. package/dist/components/Slider.svelte.d.ts +19 -0
  103. package/dist/components/SpreadsheetView.svelte +1213 -95
  104. package/dist/components/SpreadsheetView.svelte.d.ts +19 -0
  105. package/dist/components/TagsInput.svelte +178 -0
  106. package/dist/components/TagsInput.svelte.d.ts +17 -0
  107. package/dist/components/TimePicker.svelte +10 -0
  108. package/dist/components/TimePicker.svelte.d.ts +6 -0
  109. package/dist/components/TreeSelect.svelte +267 -53
  110. package/dist/components/TreeSelect.svelte.d.ts +3 -0
  111. package/dist/components/TreeTable.svelte +484 -82
  112. package/dist/components/TreeTable.svelte.d.ts +24 -1
  113. package/dist/components/TwoFactorAuthPage.svelte +85 -48
  114. package/dist/components/TwoFactorAuthPage.svelte.d.ts +12 -1
  115. package/dist/components/VirtualTable.svelte +116 -20
  116. package/dist/components/VirtualTable.svelte.d.ts +5 -0
  117. package/dist/components/account/CompanyProfilePage.svelte +74 -38
  118. package/dist/components/account/GetStartedPage.svelte +2 -1
  119. package/dist/components/account/ImportMembersPage.svelte +22 -167
  120. package/dist/components/account/ImportMembersPage.svelte.d.ts +4 -1
  121. package/dist/components/account/MemberDirectory.svelte +156 -0
  122. package/dist/components/account/MemberDirectory.svelte.d.ts +19 -0
  123. package/dist/components/account/MembersStarterPage.svelte +9 -55
  124. package/dist/components/account/MembersStarterPage.svelte.d.ts +5 -1
  125. package/dist/components/account/SecurityLogPage.svelte +4 -1
  126. package/dist/components/account/SettingsPlainPage.svelte +7 -38
  127. package/dist/components/account/SettingsPlainPage.svelte.d.ts +4 -1
  128. package/dist/components/account/SettingsSidebarPage.svelte +7 -20
  129. package/dist/components/account/TeamMembersPage.svelte +4 -20
  130. package/dist/components/account/TeamMembersPage.svelte.d.ts +5 -1
  131. package/dist/components/account/UserProfilePage.svelte +3 -25
  132. package/dist/components/account/UserProfilePage.svelte.d.ts +17 -2
  133. package/dist/components/buttons/ExportButton.svelte +56 -12
  134. package/dist/components/buttons/ExportButton.svelte.d.ts +11 -0
  135. package/dist/components/content/ContentPageHeader.svelte +1 -1
  136. package/dist/components/content/ContentPageShell.svelte +1 -1
  137. package/dist/components/content/MetricBlock.svelte +1 -1
  138. package/dist/components/content/PageToolbar.svelte +1 -1
  139. package/dist/components/content/ReferenceNotice.svelte +5 -0
  140. package/dist/components/content/ReferenceNotice.svelte.d.ts +18 -0
  141. package/dist/components/content/SectionHeader.svelte +1 -1
  142. package/dist/components/content/SettingsFieldRow.svelte +1 -1
  143. package/dist/components/content/SettingsGroup.svelte +1 -1
  144. package/dist/components/content/StatusBadge.svelte +1 -1
  145. package/dist/components/content/WorkspaceLayout.svelte +1 -1
  146. package/dist/components/export-reference.d.ts +2 -0
  147. package/dist/components/export-reference.js +9 -0
  148. package/dist/components/fieldComponentMap.js +2 -5
  149. package/dist/components/fields/DateField.svelte +12 -13
  150. package/dist/components/fields/date-display.d.ts +6 -0
  151. package/dist/components/fields/date-display.js +24 -0
  152. package/dist/components/network/TeamCrewTablePage.svelte +6 -1
  153. package/dist/components/network/UserCardsNFTPage.svelte +2 -0
  154. package/dist/components/profile/PublicProfilePage.svelte +2 -0
  155. package/dist/components/saved-list-views.d.ts +74 -1
  156. package/dist/components/saved-list-views.js +191 -3
  157. package/dist/components/table-contract.d.ts +12 -1
  158. package/dist/components/task-action.svelte.d.ts +13 -0
  159. package/dist/components/task-action.svelte.js +73 -0
  160. package/dist/components/ui/DesignSystemShowcase.svelte +164 -0
  161. package/dist/components/ui/DesignSystemShowcase.svelte.d.ts +3 -0
  162. package/dist/components/ui/badge/badge-variants.d.ts +3 -2
  163. package/dist/components/ui/badge/badge-variants.js +2 -3
  164. package/dist/components/ui/badge/badge.svelte +3 -0
  165. package/dist/components/ui/badge/badge.svelte.d.ts +1 -0
  166. package/dist/components/ui/button/button-variants.d.ts +5 -4
  167. package/dist/components/ui/button/button-variants.js +7 -5
  168. package/dist/components/ui/checkbox/checkbox.svelte +5 -1
  169. package/dist/components/ui/checkbox/checkbox.svelte.d.ts +5 -1
  170. package/dist/components/ui/input/input.svelte +7 -2
  171. package/dist/components/ui/input/input.svelte.d.ts +4 -1
  172. package/dist/components/ui/select/select.svelte +4 -1
  173. package/dist/components/ui/select/select.svelte.d.ts +2 -1
  174. package/dist/components/ui/textarea/textarea.svelte +2 -2
  175. package/dist/components.css +33 -2
  176. package/dist/date-input-policy.d.ts +4 -0
  177. package/dist/date-input-policy.js +35 -0
  178. package/dist/design-system-components.d.ts +100 -0
  179. package/dist/design-system-components.js +101 -0
  180. package/dist/design-system.js +1 -1
  181. package/dist/devtools-bridge.d.ts +80 -0
  182. package/dist/devtools-bridge.js +92 -0
  183. package/dist/index.d.ts +37 -2
  184. package/dist/index.js +23 -2
  185. package/dist/recipes.d.ts +960 -4
  186. package/dist/recipes.js +264 -4
  187. package/dist/tree-table-model.d.ts +24 -0
  188. package/dist/tree-table-model.js +111 -0
  189. package/package.json +22 -10
  190. package/dist/styled-system/css/conditions.js +0 -36
  191. package/dist/styled-system/css/css.d.ts +0 -22
  192. package/dist/styled-system/css/css.js +0 -45
  193. package/dist/styled-system/css/cva.d.ts +0 -6
  194. package/dist/styled-system/css/cva.js +0 -87
  195. package/dist/styled-system/css/cx.d.ts +0 -5
  196. package/dist/styled-system/css/cx.js +0 -15
  197. package/dist/styled-system/css/index.d.ts +0 -5
  198. package/dist/styled-system/css/index.js +0 -4
  199. package/dist/styled-system/css/sva.d.ts +0 -4
  200. package/dist/styled-system/css/sva.js +0 -46
  201. package/dist/styled-system/helpers.js +0 -316
  202. package/dist/styled-system/patterns/aspect-ratio.d.ts +0 -20
  203. package/dist/styled-system/patterns/aspect-ratio.js +0 -38
  204. package/dist/styled-system/patterns/bleed.d.ts +0 -21
  205. package/dist/styled-system/patterns/bleed.js +0 -24
  206. package/dist/styled-system/patterns/box.d.ts +0 -20
  207. package/dist/styled-system/patterns/box.js +0 -15
  208. package/dist/styled-system/patterns/center.d.ts +0 -20
  209. package/dist/styled-system/patterns/center.js +0 -21
  210. package/dist/styled-system/patterns/circle.d.ts +0 -20
  211. package/dist/styled-system/patterns/circle.js +0 -25
  212. package/dist/styled-system/patterns/container.d.ts +0 -20
  213. package/dist/styled-system/patterns/container.js +0 -21
  214. package/dist/styled-system/patterns/cq.d.ts +0 -21
  215. package/dist/styled-system/patterns/cq.js +0 -21
  216. package/dist/styled-system/patterns/divider.d.ts +0 -22
  217. package/dist/styled-system/patterns/divider.js +0 -25
  218. package/dist/styled-system/patterns/flex.d.ts +0 -26
  219. package/dist/styled-system/patterns/flex.js +0 -26
  220. package/dist/styled-system/patterns/float.d.ts +0 -23
  221. package/dist/styled-system/patterns/float.js +0 -52
  222. package/dist/styled-system/patterns/grid-item.d.ts +0 -25
  223. package/dist/styled-system/patterns/grid-item.js +0 -25
  224. package/dist/styled-system/patterns/grid.d.ts +0 -24
  225. package/dist/styled-system/patterns/grid.js +0 -27
  226. package/dist/styled-system/patterns/hstack.d.ts +0 -21
  227. package/dist/styled-system/patterns/hstack.js +0 -24
  228. package/dist/styled-system/patterns/index.d.ts +0 -21
  229. package/dist/styled-system/patterns/index.js +0 -20
  230. package/dist/styled-system/patterns/link-overlay.d.ts +0 -20
  231. package/dist/styled-system/patterns/link-overlay.js +0 -24
  232. package/dist/styled-system/patterns/spacer.d.ts +0 -20
  233. package/dist/styled-system/patterns/spacer.js +0 -25
  234. package/dist/styled-system/patterns/square.d.ts +0 -20
  235. package/dist/styled-system/patterns/square.js +0 -24
  236. package/dist/styled-system/patterns/stack.d.ts +0 -23
  237. package/dist/styled-system/patterns/stack.js +0 -24
  238. package/dist/styled-system/patterns/visually-hidden.d.ts +0 -20
  239. package/dist/styled-system/patterns/visually-hidden.js +0 -18
  240. package/dist/styled-system/patterns/vstack.d.ts +0 -21
  241. package/dist/styled-system/patterns/vstack.js +0 -24
  242. package/dist/styled-system/patterns/wrap.d.ts +0 -24
  243. package/dist/styled-system/patterns/wrap.js +0 -25
  244. package/dist/styled-system/recipes/content-header.d.ts +0 -31
  245. package/dist/styled-system/recipes/content-header.js +0 -65
  246. package/dist/styled-system/recipes/content-page.d.ts +0 -34
  247. package/dist/styled-system/recipes/content-page.js +0 -43
  248. package/dist/styled-system/recipes/create-recipe.js +0 -82
  249. package/dist/styled-system/recipes/index.d.ts +0 -17
  250. package/dist/styled-system/recipes/index.js +0 -16
  251. package/dist/styled-system/recipes/metric-block.d.ts +0 -34
  252. package/dist/styled-system/recipes/metric-block.js +0 -76
  253. package/dist/styled-system/recipes/product-list.d.ts +0 -31
  254. package/dist/styled-system/recipes/product-list.js +0 -73
  255. package/dist/styled-system/recipes/product-section.d.ts +0 -31
  256. package/dist/styled-system/recipes/product-section.js +0 -49
  257. package/dist/styled-system/recipes/product-settings-row.d.ts +0 -34
  258. package/dist/styled-system/recipes/product-settings-row.js +0 -58
  259. package/dist/styled-system/recipes/product-settings.d.ts +0 -31
  260. package/dist/styled-system/recipes/product-settings.js +0 -57
  261. package/dist/styled-system/recipes/product-status.d.ts +0 -34
  262. package/dist/styled-system/recipes/product-status.js +0 -45
  263. package/dist/styled-system/recipes/product-toolbar.d.ts +0 -31
  264. package/dist/styled-system/recipes/product-toolbar.js +0 -41
  265. package/dist/styled-system/recipes/product-workspace.d.ts +0 -34
  266. package/dist/styled-system/recipes/product-workspace.js +0 -58
  267. package/dist/styled-system/recipes/surface-metric.d.ts +0 -38
  268. package/dist/styled-system/recipes/surface-metric.js +0 -63
  269. package/dist/styled-system/recipes/surface-table.d.ts +0 -34
  270. package/dist/styled-system/recipes/surface-table.js +0 -62
  271. package/dist/styled-system/recipes/ui-badge.d.ts +0 -31
  272. package/dist/styled-system/recipes/ui-badge.js +0 -38
  273. package/dist/styled-system/recipes/ui-button.d.ts +0 -32
  274. package/dist/styled-system/recipes/ui-button.js +0 -43
  275. package/dist/styled-system/recipes/ui-input.d.ts +0 -31
  276. package/dist/styled-system/recipes/ui-input.js +0 -49
  277. package/dist/styled-system/recipes/ui-textarea.d.ts +0 -31
  278. package/dist/styled-system/recipes/ui-textarea.js +0 -24
  279. package/dist/styled-system/styles.css +0 -1257
  280. package/dist/styled-system/tokens/index.d.ts +0 -9
  281. package/dist/styled-system/tokens/index.js +0 -268
  282. package/dist/styled-system/tokens/tokens.d.ts +0 -36
  283. package/dist/styled-system/types/composition.d.ts +0 -227
  284. package/dist/styled-system/types/conditions.d.ts +0 -236
  285. package/dist/styled-system/types/csstype.d.ts +0 -22570
  286. package/dist/styled-system/types/index.d.ts +0 -6
  287. package/dist/styled-system/types/parts.d.ts +0 -8
  288. package/dist/styled-system/types/pattern.d.ts +0 -78
  289. package/dist/styled-system/types/prop-type.d.ts +0 -223
  290. package/dist/styled-system/types/recipe.d.ts +0 -181
  291. package/dist/styled-system/types/selectors.d.ts +0 -59
  292. package/dist/styled-system/types/static-css.d.ts +0 -56
  293. package/dist/styled-system/types/style-props.d.ts +0 -8088
  294. package/dist/styled-system/types/system-types.d.ts +0 -151
  295. package/dist/styles/recipes.css +0 -1262
@@ -40,9 +40,16 @@
40
40
  cell_getValue,
41
41
  } from '@tanstack/table-core/static-functions';
42
42
 
43
- import { captureAdminContext, DeleteManyPartialError, getAdminOptions, getContractFormFields, useNavigation, useParsed, useResourceContract, useList, useDeleteMany, downloadData } from '@svadmin/core';
43
+ import { captureAdminContext, captureAuthSession, useGetIdentity, DeleteManyPartialError, getAdminOptions, getContractFormFields, useNavigation, useParsed, useResourceContract, useList, useDeleteMany, downloadData, type ExportFormat, type TaskProvider } from '@svadmin/core';
44
44
  import { decodeBaseRecord, snapshotPlainData, parseContractRouteId, formatContractRouteId } from '@svadmin/core/schema';
45
- import { checkedTableRows, copyTableRecord, tableExportRows, tableRowKey, type TableRecord } from './table-contract';
45
+ import {
46
+ checkedTableRows,
47
+ copyTableRecord,
48
+ tableExportRows,
49
+ tableRowKey,
50
+ type BatchSelection,
51
+ type TableRecord,
52
+ } from './table-contract';
46
53
  import type {
47
54
  BaseRecord,
48
55
  FieldDefinition,
@@ -60,6 +67,11 @@
60
67
  cloneSavedListViewState,
61
68
  columnOrderStorageKey,
62
69
  columnVisibilityStorageKey,
70
+ decodeRemoteSavedListViews,
71
+ decodeSavedListViewMutationResult,
72
+ decodeSavedListViewRemoveResult,
73
+ decodeSavedListViewAccess,
74
+ decodeSavedListViewSubjects,
63
75
  legacyActiveSavedListViewStorageKey,
64
76
  legacyColumnOrderStorageKey,
65
77
  legacyColumnVisibilityStorageKey,
@@ -70,7 +82,10 @@
70
82
  serializeSavedListViews,
71
83
  type ListPreferenceScope,
72
84
  type SavedListView,
85
+ type SavedListViewAccess,
86
+ type SavedListViewSubject,
73
87
  type SavedListViewState,
88
+ type SavedListViewProvider,
74
89
  } from './saved-list-views.js';
75
90
 
76
91
  import { fade } from 'svelte/transition';
@@ -86,7 +101,7 @@
86
101
  import * as ContextMenu from './ui/context-menu/index.js';
87
102
  import * as Select from './ui/select/index.js';
88
103
  import {
89
- Plus, Pencil, Trash2,
104
+ Plus, Pencil, Trash2, Star,
90
105
  Search, Download, ChevronDown, ChevronUp, SlidersHorizontal, Filter as FilterIcon,
91
106
  Eye, Copy, RefreshCw, Rows, X, Bookmark, Check
92
107
  } from '@lucide/svelte';
@@ -97,6 +112,7 @@
97
112
  import CanAccess from './CanAccess.svelte';
98
113
  import TooltipButton from './TooltipButton.svelte';
99
114
  import InlineEdit from './InlineEdit.svelte';
115
+ import ExportButton from './buttons/ExportButton.svelte';
100
116
  import FieldDisplay from './FieldDisplay.svelte';
101
117
  import DraggableHeader from './DraggableHeader.svelte';
102
118
  import DataState from './content/DataState.svelte';
@@ -111,6 +127,8 @@
111
127
  /** 可选主体渲染器:不替换资源、权限、工具栏和持久化逻辑。 */
112
128
  gridBody?: Snippet<[AutoTableGridState]>;
113
129
  selectable?: boolean;
130
+ /** 仅在业务回调支持按查询执行服务端命令时启用。 */
131
+ allowSelectAllMatching?: boolean;
114
132
  density?: 'compact' | 'comfortable';
115
133
  showHeader?: boolean;
116
134
  title?: string;
@@ -133,11 +151,21 @@
133
151
  pagination?: { current: number; pageSize: number };
134
152
  /** Externally controlled sorters */
135
153
  sorters?: Sort[];
136
- /** Custom batch actions to render when rows are selected */
137
- batchActions?: Snippet<[{ selectedIds: (string | number)[] }]>;
154
+ /** 批量操作必须按 selection.scope 区分明确 ID 与全部查询结果。 */
155
+ batchActions?: Snippet<[{ selectedIds: (string | number)[]; selection: BatchSelection }]>;
138
156
  /** Summary row rendered at table footer */
139
157
  summary?: Snippet<[{ data: BaseRecord[]; total: number; visibleColumnsCount: number }]>;
140
158
  deleteVariables?: unknown;
159
+ /** 团队/系统视图;写入需要 Provider 能力和显式可写标记。 */
160
+ savedViewProvider?: SavedListViewProvider;
161
+ /** 显式允许唯一的远程默认视图在无 URL/个人活动视图时初始化页面。 */
162
+ applyRemoteDefaultView?: boolean;
163
+ /** 提供任务名后按当前筛选/排序提交全量导出;未提供时仍导出当前已加载页。 */
164
+ exportTaskName?: string;
165
+ exportTaskProvider?: TaskProvider;
166
+ exportTaskIdempotencyKey?: string;
167
+ exportFormat?: ExportFormat;
168
+ exportMaxItemCount?: number;
141
169
  }
142
170
 
143
171
  let {
@@ -145,6 +173,7 @@
145
173
  rendering,
146
174
  gridBody,
147
175
  selectable = true,
176
+ allowSelectAllMatching = false,
148
177
  density = 'comfortable',
149
178
  showHeader = true,
150
179
  title,
@@ -162,9 +191,18 @@
162
191
  pagination: externalPagination,
163
192
  sorters: externalSorters,
164
193
  deleteVariables,
194
+ savedViewProvider,
195
+ applyRemoteDefaultView = false,
196
+ exportTaskName,
197
+ exportTaskProvider,
198
+ exportTaskIdempotencyKey,
199
+ exportFormat = 'csv',
200
+ exportMaxItemCount,
165
201
  }: Props = $props();
166
202
 
167
203
  let densityOverride = $state<'compact' | 'comfortable' | undefined>(undefined);
204
+ let allMatchingSelected = $state(false);
205
+ const excludedMatchingIds = new SvelteMap<string, string | number>();
168
206
  const currentDensity = $derived(densityOverride ?? density);
169
207
  const adminContext = captureAdminContext();
170
208
  const parsed = useParsed();
@@ -176,12 +214,20 @@
176
214
  const primaryKey = $derived(resource.primaryKey ?? 'id');
177
215
  const listPermission = useCan(() => ({ resource: resourceName, action: 'list' }));
178
216
  const canRead = $derived(listPermission.allowed);
217
+ const preferenceIdentity = useGetIdentity();
218
+ const preferenceSession = $derived(captureAuthSession(adminContext.authProvider));
219
+ const preferenceIdentityReady = $derived(!adminContext.authProvider || (
220
+ preferenceSession.available && !preferenceIdentity.isLoading && !preferenceIdentity.error
221
+ && typeof preferenceIdentity.data?.id === 'string' && !!preferenceIdentity.data.id.trim()
222
+ ));
179
223
  const listPreferenceScope = $derived.by(() => {
180
- const matcher = adminContext.queryKeyMatcher(resourceName);
181
224
  return definedOptions({
182
225
  resourceName,
183
- providerName: matcher.provider ?? 'default',
184
- tenantIdentity: matcher.tenant,
226
+ providerName: binding.dataProviderName,
227
+ tenantIdentity: adminContext.queryKeyMatcher(resourceName).tenant,
228
+ identityKey: adminContext.authProvider
229
+ ? preferenceIdentityReady ? `id:${preferenceIdentity.data?.id}` : `unresolved:${preferenceSession.cacheKey}`
230
+ : undefined,
185
231
  });
186
232
  });
187
233
 
@@ -195,6 +241,7 @@
195
241
  }
196
242
 
197
243
  function readScopedPreference(scope: ListPreferenceScope, scopedKey: string, legacyKey: string): string | null {
244
+ if (!preferenceIdentityReady) return null;
198
245
  const scoped = readLocalPreference(scopedKey);
199
246
  if (scoped !== null || !canMigrateLegacyListPreferences(scope)) return scoped;
200
247
  const legacy = readLocalPreference(legacyKey);
@@ -222,6 +269,19 @@
222
269
  savedViewColumnIds,
223
270
  ));
224
271
  let savedViews = $state<SavedListView[]>(storedSavedViews);
272
+ let remoteSavedViews = $state<SavedListView[]>([]);
273
+ let remoteViewsLoading = $state(false);
274
+ let remoteViewsFailed = $state(false);
275
+ let remoteViewsReload = $state(0);
276
+ let remoteDefaultEligible = true;
277
+ const availableSavedViews = $derived.by(() => {
278
+ const ids = new Set<string>();
279
+ return [...remoteSavedViews, ...savedViews].filter(view => {
280
+ if (ids.has(view.id)) return false;
281
+ ids.add(view.id);
282
+ return true;
283
+ });
284
+ });
225
285
  const storedActiveSavedViewId = untrack(() => {
226
286
  const candidate = readScopedPreference(
227
287
  listPreferenceScope,
@@ -232,6 +292,16 @@
232
292
  });
233
293
  let savedViewName = $state('');
234
294
  let savedViewsOpen = $state(false);
295
+ let savedViewSource = $state<'local' | 'team' | 'system'>('local');
296
+ let savedViewMutationPending = $state(false);
297
+ let savedViewMutationError = $state<'failure' | 'conflict' | undefined>();
298
+ const accessDrafts = new SvelteMap<string, SavedListViewAccess>();
299
+ const accessSubjects = new SvelteMap<string, SavedListViewSubject[]>();
300
+ const accessSubjectsLoading = new SvelteMap<string, boolean>();
301
+ const accessSubjectsFailed = new SvelteMap<string, boolean>();
302
+ const accessSubjectQueries = new SvelteMap<string, string>();
303
+ const accessSubjectRequests = new Map<string, object>();
304
+ let savedViewMutationEpoch = 0;
235
305
 
236
306
  const hasExplicitURLState = untrack(() => (
237
307
  urlState.page !== undefined
@@ -241,7 +311,7 @@
241
311
  || urlState.filters !== undefined
242
312
  ));
243
313
  let activeSavedViewId = $state<string | undefined>(hasExplicitURLState ? undefined : storedActiveSavedViewId);
244
- const activeSavedViewName = $derived(savedViews.find((view) => view.id === activeSavedViewId)?.name);
314
+ const activeSavedViewName = $derived(availableSavedViews.find((view) => view.id === activeSavedViewId)?.name);
245
315
  const initialSavedView = untrack(() => (
246
316
  !hasExplicitURLState && storedActiveSavedViewId
247
317
  ? storedSavedViews.find((view) => view.id === storedActiveSavedViewId)
@@ -371,6 +441,7 @@
371
441
 
372
442
  function scheduleSearch(event: Event) {
373
443
  if (!(event.currentTarget instanceof HTMLInputElement)) return;
444
+ markSavedViewDirty();
374
445
  searchText = event.currentTarget.value;
375
446
  if (searchDebounceTimer) clearTimeout(searchDebounceTimer);
376
447
  searchDebounceTimer = setTimeout(() => {
@@ -622,7 +693,7 @@
622
693
  const selectedIdValueByKey = new SvelteMap<string, string | number>();
623
694
 
624
695
  function preferenceScopeIsLoaded(): boolean {
625
- return loadedPreferenceScopeId === listPreferenceScopeId(listPreferenceScope);
696
+ return preferenceIdentityReady && loadedPreferenceScopeId === listPreferenceScopeId(listPreferenceScope);
626
697
  }
627
698
 
628
699
  function rowIsExpanded(rowId: string): boolean {
@@ -631,7 +702,7 @@
631
702
  }
632
703
 
633
704
  function rowIsSelected(rowId: string): boolean {
634
- return rowSelection[rowId] === true;
705
+ return allMatchingSelected ? !excludedMatchingIds.has(rowId) : rowSelection[rowId] === true;
635
706
  }
636
707
 
637
708
  function rowIdValue(row: Row<TableFeatures, TableRecord>): string | number {
@@ -640,6 +711,11 @@
640
711
 
641
712
  function toggleRowSelection(row: Row<TableFeatures, TableRecord>): void {
642
713
  if (confirmPending || !canRead) return;
714
+ if (allMatchingSelected) {
715
+ if (excludedMatchingIds.has(row.id)) excludedMatchingIds.delete(row.id);
716
+ else excludedMatchingIds.set(row.id, rowIdValue(row));
717
+ return;
718
+ }
643
719
  const wasSelected = rowIsSelected(row.id);
644
720
  if (!wasSelected) {
645
721
  selectedIdValueByKey.set(row.id, rowIdValue(row));
@@ -694,6 +770,7 @@
694
770
  }
695
771
 
696
772
  function markSavedViewDirty(): void {
773
+ remoteDefaultEligible = false;
697
774
  if (!activeSavedViewId) return;
698
775
  activeSavedViewId = undefined;
699
776
  persistActiveSavedView();
@@ -714,6 +791,7 @@
714
791
  }
715
792
 
716
793
  function applySavedView(view: SavedListView): void {
794
+ remoteDefaultEligible = false;
717
795
  if (searchDebounceTimer) {
718
796
  clearTimeout(searchDebounceTimer);
719
797
  searchDebounceTimer = undefined;
@@ -748,6 +826,8 @@
748
826
  columnOrderAtom.set(state.columnOrder);
749
827
  persistColumnOrder(state.columnOrder);
750
828
  rowSelection = {};
829
+ allMatchingSelected = false;
830
+ excludedMatchingIds.clear();
751
831
  selectedIdValueByKey.clear();
752
832
  }
753
833
 
@@ -775,8 +855,12 @@
775
855
  const scopeId = listPreferenceScopeId(scope);
776
856
  if (scopeId === loadedPreferenceScopeId) return;
777
857
 
858
+ remoteDefaultEligible = true;
778
859
  const preferences = readScopedSavedViewPreferences(scope);
860
+ savedViewName = '';
861
+ savedViewsOpen = false;
779
862
  savedViews = preferences.savedViews;
863
+ remoteSavedViews = [];
780
864
  activeSavedViewId = preferences.activeSavedViewId;
781
865
 
782
866
  if (!hasExplicitURLState && preferences.activeSavedView) {
@@ -786,13 +870,68 @@
786
870
  columnVisibilityAtom.set(preferences.columnVisibility);
787
871
  columnOrderAtom.set(preferences.columnOrder);
788
872
  rowSelection = {};
873
+ allMatchingSelected = false;
874
+ excludedMatchingIds.clear();
789
875
  selectedIdValueByKey.clear();
790
876
  }
791
877
 
792
878
  loadedPreferenceScopeId = scopeId;
793
879
  });
794
880
 
881
+ let remoteViewEpoch = 0;
882
+ $effect(() => {
883
+ const provider = savedViewProvider;
884
+ const scope = listPreferenceScope;
885
+ const columns = savedViewColumnIds;
886
+ void remoteViewsReload;
887
+ remoteSavedViews = [];
888
+ remoteViewsLoading = false;
889
+ remoteViewsFailed = false;
890
+ accessDrafts.clear();
891
+ accessSubjects.clear();
892
+ accessSubjectsLoading.clear();
893
+ accessSubjectsFailed.clear();
894
+ accessSubjectQueries.clear();
895
+ accessSubjectRequests.clear();
896
+ savedViewMutationEpoch += 1;
897
+ savedViewMutationPending = false;
898
+ savedViewMutationError = undefined;
899
+ savedViewSource = 'local';
900
+ if (!provider || !preferenceScopeIsLoaded()) {
901
+ remoteSavedViews = [];
902
+ return;
903
+ }
904
+ const epoch = ++remoteViewEpoch;
905
+ remoteViewsLoading = true;
906
+ void Promise.resolve().then(() => provider.list({ ...scope })).then(value => {
907
+ if (epoch !== remoteViewEpoch || !preferenceScopeIsLoaded()) return;
908
+ remoteSavedViews = decodeRemoteSavedListViews(value, columns, !!(provider.save || provider.remove || provider.setDefault || provider.updateAccess));
909
+ if (applyRemoteDefaultView && remoteDefaultEligible) {
910
+ const defaults = remoteSavedViews.filter(view => view.default === true);
911
+ if (!hasExplicitURLState && !activeSavedViewId && !externalPagination && !externalSorters
912
+ && defaults.length === 1 && defaults[0]) {
913
+ applySavedView(defaults[0]);
914
+ remoteDefaultEligible = false;
915
+ }
916
+ }
917
+ }).catch(() => {
918
+ if (epoch === remoteViewEpoch) {
919
+ remoteSavedViews = [];
920
+ remoteViewsFailed = true;
921
+ }
922
+ }).finally(() => {
923
+ if (epoch === remoteViewEpoch) remoteViewsLoading = false;
924
+ });
925
+ return () => {
926
+ remoteViewEpoch += 1;
927
+ savedViewMutationEpoch += 1;
928
+ accessSubjectRequests.clear();
929
+ };
930
+ });
931
+
795
932
  function saveCurrentView(): void {
933
+ if (!preferenceScopeIsLoaded() || savedViewMutationPending) return;
934
+ remoteDefaultEligible = false;
796
935
  const name = savedViewName.trim().slice(0, 60);
797
936
  if (!name) return;
798
937
  if (searchDebounceTimer) {
@@ -800,6 +939,16 @@
800
939
  searchDebounceTimer = undefined;
801
940
  appliedSearchText = searchText;
802
941
  }
942
+ if (savedViewSource !== 'local') {
943
+ const existing = remoteSavedViews.find(view => view.source === savedViewSource
944
+ && view.name.toLocaleLowerCase() === name.toLocaleLowerCase());
945
+ if (!savedViewProvider?.save || (existing && (existing.readOnly || !existing.version))) return;
946
+ void mutateRemoteView({
947
+ id: existing?.id ?? crypto.randomUUID(), name, state: getCurrentSavedViewState(),
948
+ source: savedViewSource, expectedVersion: existing?.version ?? null,
949
+ });
950
+ return;
951
+ }
803
952
  const existing = savedViews.find((view) => view.name.toLocaleLowerCase() === name.toLocaleLowerCase());
804
953
  const view: SavedListView = {
805
954
  id: existing?.id ?? `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
@@ -816,12 +965,184 @@
816
965
  }
817
966
 
818
967
  function deleteSavedView(id: string): void {
968
+ if (!preferenceScopeIsLoaded() || savedViewMutationPending) return;
969
+ const remote = remoteSavedViews.find(view => view.id === id);
970
+ if (remote) {
971
+ if (!savedViewProvider?.remove || remote.readOnly || !remote.version) return;
972
+ void mutateRemoteView({
973
+ id, expectedVersion: remote.version, source: remote.source === 'system' ? 'system' : 'team',
974
+ name: remote.name, state: remote.state,
975
+ }, true);
976
+ return;
977
+ }
819
978
  savedViews = savedViews.filter((view) => view.id !== id);
820
979
  if (activeSavedViewId === id) activeSavedViewId = undefined;
821
980
  persistSavedViews();
822
981
  persistActiveSavedView();
823
982
  }
824
983
 
984
+ async function mutateRemoteView(
985
+ mutation: import('./saved-list-views').SavedListViewMutation, remove = false,
986
+ ): Promise<void> {
987
+ const provider = savedViewProvider;
988
+ if (!provider || savedViewMutationPending || !preferenceScopeIsLoaded()
989
+ || (remove && (mutation.expectedVersion === null || !provider.remove))
990
+ || (!remove && !provider.save)) return;
991
+ const scope = { ...listPreferenceScope };
992
+ const scopeId = listPreferenceScopeId(scope);
993
+ const columns = savedViewColumnIds;
994
+ const epoch = ++savedViewMutationEpoch;
995
+ const current = () => epoch === savedViewMutationEpoch && provider === savedViewProvider
996
+ && scopeId === listPreferenceScopeId(listPreferenceScope) && preferenceScopeIsLoaded();
997
+ savedViewMutationPending = true;
998
+ savedViewMutationError = undefined;
999
+ try {
1000
+ const result = remove
1001
+ ? await provider.remove?.(scope, { id: mutation.id, expectedVersion: mutation.expectedVersion ?? 0 })
1002
+ : await provider.save?.(scope, { ...mutation, state: cloneSavedListViewState(mutation.state) });
1003
+ if (!current()) return;
1004
+ if (remove && mutation.expectedVersion !== null && decodeSavedListViewRemoveResult(result, {
1005
+ id: mutation.id, expectedVersion: mutation.expectedVersion,
1006
+ })) {
1007
+ remoteSavedViews = remoteSavedViews.filter(view => view.id !== mutation.id);
1008
+ if (activeSavedViewId === mutation.id) activeSavedViewId = undefined;
1009
+ persistActiveSavedView();
1010
+ return;
1011
+ }
1012
+ const decoded = decodeSavedListViewMutationResult(result, columns, mutation);
1013
+ if (!decoded || (remove && decoded.ok)) {
1014
+ savedViewMutationError = 'failure';
1015
+ return;
1016
+ }
1017
+ if (!decoded.ok) {
1018
+ savedViewMutationError = 'conflict';
1019
+ // 保留本地查询;用户必须重新读取后再决定如何修改。
1020
+ return;
1021
+ }
1022
+ const view = { ...decoded.view, readOnly: decoded.view.readOnly !== false };
1023
+ remoteSavedViews = [view, ...remoteSavedViews.filter(candidate => candidate.id !== view.id)];
1024
+ savedViewName = '';
1025
+ } catch {
1026
+ if (current()) savedViewMutationError = 'failure';
1027
+ } finally {
1028
+ if (current()) savedViewMutationPending = false;
1029
+ }
1030
+ }
1031
+
1032
+ function setRemoteDefault(view: SavedListView): void {
1033
+ const provider = savedViewProvider;
1034
+ if (!provider?.setDefault || savedViewMutationPending || view.readOnly || !view.version
1035
+ || savedViewMutationError === 'conflict' || !preferenceScopeIsLoaded()
1036
+ || (view.source !== 'team' && view.source !== 'system')) return;
1037
+ const source = view.source;
1038
+ const expectedVersion = view.version;
1039
+ const defaultValue = view.default !== true;
1040
+ remoteDefaultEligible = false;
1041
+ const scope = { ...listPreferenceScope };
1042
+ const scopeId = listPreferenceScopeId(scope);
1043
+ const epoch = ++savedViewMutationEpoch;
1044
+ const current = () => epoch === savedViewMutationEpoch && provider === savedViewProvider
1045
+ && scopeId === listPreferenceScopeId(listPreferenceScope) && preferenceScopeIsLoaded();
1046
+ savedViewMutationPending = true;
1047
+ savedViewMutationError = undefined;
1048
+ void Promise.resolve().then(() => provider.setDefault?.(scope, {
1049
+ id: view.id, source, expectedVersion, default: defaultValue,
1050
+ })).then(result => {
1051
+ if (!current()) return;
1052
+ const decoded = decodeSavedListViewMutationResult(result, savedViewColumnIds, {
1053
+ id: view.id, source, expectedVersion,
1054
+ });
1055
+ if (!decoded) {
1056
+ savedViewMutationError = 'failure';
1057
+ } else if (!decoded.ok) {
1058
+ savedViewMutationError = 'conflict';
1059
+ } else if (decoded.view.default !== defaultValue) {
1060
+ savedViewMutationError = 'failure';
1061
+ } else {
1062
+ // 默认标记会同时改变同一作用域的其他视图,成功后重新读取完整集合。
1063
+ remoteViewsReload += 1;
1064
+ }
1065
+ }).catch(() => {
1066
+ if (current()) savedViewMutationError = 'failure';
1067
+ }).finally(() => {
1068
+ if (current()) savedViewMutationPending = false;
1069
+ });
1070
+ }
1071
+
1072
+ function saveRemoteAccess(view: SavedListView): void {
1073
+ const provider = savedViewProvider;
1074
+ const draft = accessDrafts.get(view.id);
1075
+ if (!provider?.updateAccess || !draft || view.readOnly !== false || !view.version
1076
+ || savedViewMutationPending || savedViewMutationError === 'conflict' || !preferenceScopeIsLoaded()
1077
+ || (view.source !== 'team' && view.source !== 'system')) return;
1078
+ const access = decodeSavedListViewAccess({
1079
+ mode: draft.mode,
1080
+ subjectIds: draft.mode === 'restricted' ? draft.subjectIds : [],
1081
+ });
1082
+ if (!access) { savedViewMutationError = 'failure'; return; }
1083
+ const source = view.source;
1084
+ const expectedVersion = view.version;
1085
+ const scope = { ...listPreferenceScope };
1086
+ const scopeId = listPreferenceScopeId(scope);
1087
+ const epoch = ++savedViewMutationEpoch;
1088
+ const current = () => epoch === savedViewMutationEpoch && provider === savedViewProvider
1089
+ && scopeId === listPreferenceScopeId(listPreferenceScope) && preferenceScopeIsLoaded();
1090
+ remoteDefaultEligible = false;
1091
+ savedViewMutationPending = true;
1092
+ savedViewMutationError = undefined;
1093
+ void Promise.resolve().then(() => {
1094
+ if (!current()) return;
1095
+ return provider.updateAccess?.(scope, {
1096
+ id: view.id, source, expectedVersion, access: { ...access, subjectIds: [...access.subjectIds] },
1097
+ });
1098
+ }).then(result => {
1099
+ if (!current()) return;
1100
+ const decoded = decodeSavedListViewMutationResult(result, savedViewColumnIds, {
1101
+ id: view.id, source, expectedVersion,
1102
+ });
1103
+ if (!decoded) savedViewMutationError = 'failure';
1104
+ else if (!decoded.ok) savedViewMutationError = 'conflict';
1105
+ else if (decoded.view.access?.mode !== access.mode
1106
+ || decoded.view.access.subjectIds.length !== access.subjectIds.length
1107
+ || !decoded.view.access.subjectIds.every(id => access.subjectIds.includes(id))) {
1108
+ savedViewMutationError = 'failure';
1109
+ } else remoteViewsReload += 1;
1110
+ }).catch(() => {
1111
+ if (current()) savedViewMutationError = 'failure';
1112
+ }).finally(() => {
1113
+ if (current()) savedViewMutationPending = false;
1114
+ });
1115
+ }
1116
+
1117
+ function loadAccessSubjects(view: SavedListView, query = ''): void {
1118
+ const provider = savedViewProvider;
1119
+ if (!provider?.listAccessSubjects || view.readOnly !== false
1120
+ || view.source === 'local' || !preferenceScopeIsLoaded()) return;
1121
+ const scope = { ...listPreferenceScope };
1122
+ const scopeId = listPreferenceScopeId(scope);
1123
+ const request = {};
1124
+ accessSubjectRequests.set(view.id, request);
1125
+ const current = () => accessSubjectRequests.get(view.id) === request
1126
+ && scopeId === listPreferenceScopeId(listPreferenceScope)
1127
+ && provider === savedViewProvider && preferenceScopeIsLoaded();
1128
+ accessSubjectsLoading.set(view.id, true);
1129
+ accessSubjectsFailed.delete(view.id);
1130
+ accessSubjects.delete(view.id);
1131
+ void Promise.resolve().then(() => {
1132
+ if (current()) return provider.listAccessSubjects?.(scope, { query: query.trim(), limit: 50 });
1133
+ })
1134
+ .then(result => {
1135
+ if (!current()) return;
1136
+ const subjects = decodeSavedListViewSubjects(result);
1137
+ if (!subjects) accessSubjectsFailed.set(view.id, true);
1138
+ else accessSubjects.set(view.id, subjects);
1139
+ })
1140
+ .catch(() => {
1141
+ if (current()) accessSubjectsFailed.set(view.id, true);
1142
+ })
1143
+ .finally(() => { if (current()) accessSubjectsLoading.delete(view.id); });
1144
+ }
1145
+
825
1146
  function setColumnVisibility(columnId: string, visible: boolean): void {
826
1147
  markSavedViewDirty();
827
1148
  columnVisibilityAtom.set({ ...tableColumnVisibility.current, [columnId]: visible });
@@ -939,6 +1260,10 @@
939
1260
 
940
1261
  function toggleAllRowsSelection(): void {
941
1262
  if (confirmPending || !canRead) return;
1263
+ if (allMatchingSelected) {
1264
+ clearSelection();
1265
+ return;
1266
+ }
942
1267
  const willSelect = !table_getIsAllRowsSelected(tbl);
943
1268
  for (const row of tableView.rows) {
944
1269
  if (willSelect) {
@@ -957,7 +1282,58 @@
957
1282
  return id === undefined ? [] : [id];
958
1283
  })
959
1284
  );
960
- const selectedCount = $derived(Object.keys(rowSelection).filter(key => rowSelection[key] === true).length);
1285
+ const matchingTotal = $derived(query.data?.total ?? 0);
1286
+ const selectedCount = $derived(allMatchingSelected
1287
+ ? Math.max(0, matchingTotal - excludedMatchingIds.size)
1288
+ : selectedIds.length);
1289
+ const selectionLabel = $derived(i18n.t(
1290
+ allMatchingSelected ? 'common.allMatchingSelected' : 'common.selectedCount',
1291
+ { count: selectedCount },
1292
+ ));
1293
+ const selectionReady = $derived(
1294
+ canRead && selectable && pageRecords.ok && !query.isError && !query.isFetching
1295
+ );
1296
+ const canSelectAllMatching = $derived(
1297
+ allowSelectAllMatching && !!batchActions && selectionReady
1298
+ && Number.isSafeInteger(matchingTotal) && matchingTotal > 0
1299
+ );
1300
+ function batchSelectionSnapshot(): BatchSelection {
1301
+ if (allMatchingSelected) return {
1302
+ scope: 'all', filters: queryFilters.map(cloneFilter),
1303
+ sorters: querySorters.map(sorter => ({ ...sorter })), total: matchingTotal,
1304
+ excludedIds: [...excludedMatchingIds.values()],
1305
+ };
1306
+ return {
1307
+ scope: 'selected', ids: [...selectedIds],
1308
+ currentPageIds: tableView.rows.filter(row => rowSelection[row.id] === true).map(rowIdValue),
1309
+ };
1310
+ }
1311
+ const selectionCriteria = $derived(JSON.stringify(queryFilters));
1312
+ let previousSelectionCriteria = untrack(() => selectionCriteria);
1313
+ $effect.pre(() => {
1314
+ const criteria = selectionCriteria;
1315
+ if (criteria !== previousSelectionCriteria) {
1316
+ allMatchingSelected = false;
1317
+ excludedMatchingIds.clear();
1318
+ rowSelection = {};
1319
+ selectedIdValueByKey.clear();
1320
+ if (deleteRequest?.batch && !confirmPending) {
1321
+ confirmOpen = false;
1322
+ deleteRequest = null;
1323
+ }
1324
+ }
1325
+ previousSelectionCriteria = criteria;
1326
+ });
1327
+ $effect.pre(() => {
1328
+ if (!selectable || !allowSelectAllMatching || !batchActions) {
1329
+ allMatchingSelected = false;
1330
+ excludedMatchingIds.clear();
1331
+ }
1332
+ if (!selectable) {
1333
+ rowSelection = {};
1334
+ selectedIdValueByKey.clear();
1335
+ }
1336
+ });
961
1337
  const batchDeletePerm = useCan(() => ({
962
1338
  resource: resourceName,
963
1339
  action: 'delete',
@@ -965,16 +1341,26 @@
965
1341
  queryOptions: { enabled: acEnabled && canDelete && selectedIds.length > 0 },
966
1342
  }));
967
1343
  const canBatchDelete = $derived(
968
- canDelete && (!acEnabled || batchDeletePerm.isLoading || batchDeletePerm.allowed)
1344
+ !allMatchingSelected && canDelete && (!acEnabled || batchDeletePerm.isLoading || batchDeletePerm.allowed)
969
1345
  );
970
1346
 
971
1347
  function clearSelection(): void {
972
1348
  if (!deleteManyMutation.isPending) {
1349
+ allMatchingSelected = false;
1350
+ excludedMatchingIds.clear();
973
1351
  selectedIdValueByKey.clear();
974
1352
  table_resetRowSelection(tbl, true);
975
1353
  }
976
1354
  }
977
1355
 
1356
+ function selectAllMatching(): void {
1357
+ if (!canSelectAllMatching || confirmPending || confirmOpen) return;
1358
+ selectedIdValueByKey.clear();
1359
+ excludedMatchingIds.clear();
1360
+ rowSelection = {};
1361
+ allMatchingSelected = true;
1362
+ }
1363
+
978
1364
  function isColumnVisible(columnId: string): boolean {
979
1365
  return tableColumnVisibility.current[columnId] !== false;
980
1366
  }
@@ -1049,6 +1435,19 @@
1049
1435
  variables: deleteVariables,
1050
1436
  });
1051
1437
  let previousTableScope: typeof tableScope | undefined;
1438
+ function tableScopesEqual(left: typeof tableScope, right: typeof tableScope): boolean {
1439
+ return left.contract === right.contract
1440
+ && left.resourceName === right.resourceName
1441
+ && left.provider === right.provider
1442
+ && left.meta === right.meta
1443
+ && left.tenant === right.tenant
1444
+ && left.auth === right.auth
1445
+ && left.router === right.router
1446
+ && left.permissionProvider === right.permissionProvider
1447
+ && left.canRead === right.canRead
1448
+ && left.canDelete === right.canDelete
1449
+ && left.variables === right.variables;
1450
+ }
1052
1451
  const detailState = $derived.by(() => {
1053
1452
  const route = parsed.params['detail'];
1054
1453
  if (route === undefined || !canRead) return { id: undefined, invalid: false };
@@ -1059,24 +1458,14 @@
1059
1458
  const detailOpen = $derived(detailRecordId != null);
1060
1459
  $effect.pre(() => {
1061
1460
  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
- )) {
1461
+ if (previousTableScope && !tableScopesEqual(previousTableScope, scope)) {
1075
1462
  activeDelete = undefined;
1076
1463
  confirmOpen = false;
1077
1464
  confirmPending = false;
1078
1465
  deleteRequest = null;
1079
1466
  operationError = null;
1467
+ allMatchingSelected = false;
1468
+ excludedMatchingIds.clear();
1080
1469
  selectedIdValueByKey.clear();
1081
1470
  rowSelection = {};
1082
1471
  expandedAtom.set({});
@@ -1450,9 +1839,21 @@
1450
1839
  <h1 class="svadmin-u-42536e69e639 svadmin-u-998e0b29fe9e svadmin-u-e83a7042bc91 svadmin-u-d4108abe6359">{title ?? resource.label}</h1>
1451
1840
  <div class="svadmin-u-60fbb7713999 svadmin-u-1eb5c6df38c1 svadmin-u-3960ffc248d9 svadmin-u-77a2a20e90d4">
1452
1841
  {#if canExport}
1453
- <Button variant="outline" size="sm" onclick={exportCSV}>
1454
- <Download class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" data-icon="inline-start" /> {i18n.t("common.export")}
1455
- </Button>
1842
+ {#if exportTaskName}
1843
+ <ExportButton
1844
+ resource={resourceName}
1845
+ taskName={exportTaskName}
1846
+ {...definedOptions({ taskProvider: exportTaskProvider, taskIdempotencyKey: exportTaskIdempotencyKey, maxItemCount: exportMaxItemCount })}
1847
+ format={exportFormat}
1848
+ filters={activeFilters}
1849
+ sorters={sorters}
1850
+ accessControl={{ enabled: acEnabled, hideIfUnauthorized: true }}
1851
+ />
1852
+ {:else}
1853
+ <Button variant="outline" size="sm" onclick={exportCSV}>
1854
+ <Download class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" data-icon="inline-start" /> {i18n.t("common.export")}
1855
+ </Button>
1856
+ {/if}
1456
1857
  {/if}
1457
1858
  {#if headerActions}
1458
1859
  {@render headerActions()}
@@ -1467,21 +1868,27 @@
1467
1868
  {/if}
1468
1869
 
1469
1870
  <!-- Selection Banner (Enterprise Batch Actions) -->
1470
- {#if selectedCount > 0}
1871
+ {#if selectedCount > 0 || allMatchingSelected}
1471
1872
  <div
1472
1873
  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"
1473
- aria-label={i18n.t("common.selectedCount", { count: selectedCount })}
1874
+ aria-label={selectionLabel}
1474
1875
  data-svadmin-batch-toolbar
1475
1876
  >
1476
1877
  <div class="svadmin-u-60fbb7713999 svadmin-u-7e0b7cdf1a94 svadmin-u-3960ffc248d9 svadmin-u-77a2a20e90d4 svadmin-u-fc7473ca09eb">
1477
- <span class="svadmin-u-2689f3958069 svadmin-u-d4108abe6359 svadmin-u-3032cae0badb">{i18n.t("common.selectedCount", { count: selectedCount })}</span>
1878
+ <span class="svadmin-u-2689f3958069 svadmin-u-d4108abe6359 svadmin-u-3032cae0badb">{selectionLabel}</span>
1478
1879
  {#if deleteManyMutation.isPending}
1479
1880
  <span class="svadmin-u-bfa603190748" role="status">{i18n.t("common.processing")}</span>
1480
1881
  {/if}
1481
1882
  </div>
1482
1883
  <div class="svadmin-u-60fbb7713999 svadmin-u-1eb5c6df38c1 svadmin-u-3960ffc248d9 svadmin-u-77a2a20e90d4">
1483
- {#if batchActions}
1484
- {@render batchActions({ selectedIds })}
1884
+ {#if batchActions && selectionReady && selectedCount > 0}
1885
+ {@render batchActions({ selectedIds: [...selectedIds], selection: batchSelectionSnapshot() })}
1886
+ {/if}
1887
+ {#if !allMatchingSelected && canSelectAllMatching && selectedCount < matchingTotal}
1888
+ <Button variant="outline" size="sm" disabled={confirmPending || confirmOpen} onclick={selectAllMatching}>
1889
+ <Rows class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" data-icon="inline-start" aria-hidden="true" />
1890
+ {i18n.t('common.selectAllMatching', { count: matchingTotal })}
1891
+ </Button>
1485
1892
  {/if}
1486
1893
  {#if canBatchDelete}
1487
1894
  <Button
@@ -1584,9 +1991,22 @@
1584
1991
  <!-- Right: Table Controls (Density, Columns, Saved Views, Refresh, Export if !showHeader) -->
1585
1992
  <div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-58284b4ea568 svadmin-u-012fbd121f37">
1586
1993
  {#if !showHeader && canExport}
1587
- <TooltipButton tooltip={i18n.t("common.export")} variant="outline" size="sm" class="svadmin-u-e7a768f922d2 svadmin-u-0b91436debbd" onclick={exportCSV}>
1588
- <Download class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" aria-hidden="true" />
1589
- </TooltipButton>
1994
+ {#if exportTaskName}
1995
+ <ExportButton
1996
+ resource={resourceName}
1997
+ taskName={exportTaskName}
1998
+ {...definedOptions({ taskProvider: exportTaskProvider, taskIdempotencyKey: exportTaskIdempotencyKey, maxItemCount: exportMaxItemCount })}
1999
+ format={exportFormat}
2000
+ filters={activeFilters}
2001
+ sorters={sorters}
2002
+ hideText
2003
+ accessControl={{ enabled: acEnabled, hideIfUnauthorized: true }}
2004
+ />
2005
+ {:else}
2006
+ <TooltipButton tooltip={i18n.t("common.export")} variant="outline" size="sm" class="svadmin-u-e7a768f922d2 svadmin-u-0b91436debbd" onclick={exportCSV}>
2007
+ <Download class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" aria-hidden="true" />
2008
+ </TooltipButton>
2009
+ {/if}
1590
2010
  {/if}
1591
2011
 
1592
2012
  {#if showDensitySwitcher}
@@ -1634,7 +2054,7 @@
1634
2054
  <Popover.Root bind:open={savedViewsOpen}>
1635
2055
  <Popover.Trigger>
1636
2056
  {#snippet child({ props })}
1637
- <Button variant="outline" size="sm" class="svadmin-u-e7a768f922d2 svadmin-u-0b91436debbd" {...props} aria-label={i18n.t("common.savedViews")}>
2057
+ <Button variant="outline" size="sm" class="svadmin-u-e7a768f922d2 svadmin-u-0b91436debbd" {...props} disabled={!preferenceIdentityReady} aria-label={i18n.t("common.savedViews")}>
1638
2058
  <Bookmark class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" data-icon="inline-start" /> {activeSavedViewName ?? i18n.t("common.savedViews")}
1639
2059
  </Button>
1640
2060
  {/snippet}
@@ -1645,6 +2065,34 @@
1645
2065
  <h4 class="svadmin-u-2689f3958069 svadmin-u-fc7473ca09eb">{i18n.t("common.savedViews")}</h4>
1646
2066
  <p class="svadmin-u-b6b02c0ebef6 svadmin-u-359090c2d529 svadmin-u-bfa603190748">{i18n.t("common.savedViewsHint")}</p>
1647
2067
  </div>
2068
+ {#if remoteViewsLoading}
2069
+ <p role="status">{i18n.t('common.loading')}</p>
2070
+ {:else if remoteViewsFailed}
2071
+ <p role="alert">{i18n.t('common.operationFailed')}</p>
2072
+ <Button type="button" variant="outline" size="sm" onclick={() => remoteViewsReload += 1}>
2073
+ {i18n.t('common.retry')}
2074
+ </Button>
2075
+ {/if}
2076
+ {#if savedViewMutationPending}
2077
+ <p role="status">{i18n.t('common.loading')}</p>
2078
+ {:else if savedViewMutationError}
2079
+ <p role="alert">{i18n.t(savedViewMutationError === 'conflict' ? 'common.viewConflict' : 'common.operationFailed')}</p>
2080
+ <Button type="button" variant="outline" size="sm" onclick={() => remoteViewsReload += 1}>
2081
+ {i18n.t('common.refresh')}
2082
+ </Button>
2083
+ {/if}
2084
+ {#if savedViewProvider?.save}
2085
+ <Select.Root aria-label={i18n.t('common.viewSource')} value={savedViewSource} disabled={savedViewMutationPending}
2086
+ onchange={(event: Event) => {
2087
+ if (!(event.currentTarget instanceof HTMLSelectElement)) return;
2088
+ const value = event.currentTarget.value;
2089
+ if (value === 'local' || value === 'team' || value === 'system') savedViewSource = value;
2090
+ }}>
2091
+ <option value="local">{i18n.t('common.personalView')}</option>
2092
+ <option value="team">{i18n.t('common.teamView')}</option>
2093
+ <option value="system">{i18n.t('common.systemView')}</option>
2094
+ </Select.Root>
2095
+ {/if}
1648
2096
  <div class="svadmin-u-da7c36cd8867">
1649
2097
  <label class="svadmin-u-359090c2d529 svadmin-u-bfa603190748" for="saved-list-view">{i18n.t("common.currentView")}</label>
1650
2098
  <Select.Root
@@ -1654,16 +2102,17 @@
1654
2102
  onchange={(event: Event) => {
1655
2103
  if (!(event.currentTarget instanceof HTMLSelectElement)) return;
1656
2104
  const id = event.currentTarget.value;
1657
- const view = savedViews.find((candidate) => candidate.id === id);
2105
+ const view = availableSavedViews.find((candidate) => candidate.id === id);
1658
2106
  if (view) applySavedView(view);
1659
2107
  else {
2108
+ remoteDefaultEligible = false;
1660
2109
  activeSavedViewId = undefined;
1661
2110
  persistActiveSavedView();
1662
2111
  }
1663
2112
  }}
1664
2113
  >
1665
2114
  <option value="">{i18n.t("common.currentView")}</option>
1666
- {#each savedViews as view (view.id)}
2115
+ {#each availableSavedViews as view (view.id)}
1667
2116
  <option value={view.id}>{view.name}</option>
1668
2117
  {/each}
1669
2118
  </Select.Root>
@@ -1674,22 +2123,132 @@
1674
2123
  placeholder={i18n.t("common.viewName")}
1675
2124
  maxlength={60}
1676
2125
  bind:value={savedViewName}
2126
+ disabled={savedViewMutationPending}
1677
2127
  class="svadmin-u-e7a768f922d2"
1678
2128
  />
1679
- <Button size="sm" class="svadmin-u-012fbd121f37" disabled={!savedViewName.trim()} onclick={saveCurrentView}>
2129
+ <Button size="sm" class="svadmin-u-012fbd121f37" disabled={!savedViewName.trim() || savedViewMutationPending || savedViewMutationError === 'conflict'} onclick={saveCurrentView}>
1680
2130
  <Check class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" data-icon="inline-start" /> {i18n.t("common.saveView")}
1681
2131
  </Button>
1682
2132
  </div>
1683
- {#if savedViews.length > 0}
2133
+ {#if availableSavedViews.length > 0}
1684
2134
  <div class="svadmin-u-da7c36cd8867 svadmin-u-b950dda299d3 svadmin-u-18049387f0af svadmin-u-f46b61a9b310">
1685
- {#each savedViews as view (view.id)}
2135
+ {#each availableSavedViews as view (view.id)}
1686
2136
  <div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-8ef2268efbbc svadmin-u-77a2a20e90d4 svadmin-u-fc7473ca09eb">
1687
2137
  <span class="svadmin-u-7e0b7cdf1a94 svadmin-u-f283ea9bea0e">{view.name}</span>
2138
+ {#if (view.source === 'team' || view.source === 'system') && savedViewProvider?.setDefault}
2139
+ <button
2140
+ type="button"
2141
+ class="svadmin-u-52083e7da442 svadmin-u-012fbd121f37 svadmin-u-bfa603190748"
2142
+ aria-label={`${i18n.t(view.default ? 'common.unsetDefaultView' : 'common.setDefaultView')} ${view.name}`}
2143
+ aria-pressed={view.default === true}
2144
+ title={i18n.t(view.default ? 'common.unsetDefaultView' : 'common.setDefaultView')}
2145
+ disabled={view.readOnly === true || !view.version || savedViewMutationPending || savedViewMutationError === 'conflict'}
2146
+ onclick={() => setRemoteDefault(view)}
2147
+ >
2148
+ <Star class={view.default ? "svadmin-u-e83a7042bc91" : ""} fill={view.default ? "currentColor" : "none"} />
2149
+ </button>
2150
+ {/if}
2151
+ {#if (view.source === 'team' || view.source === 'system') && savedViewProvider?.updateAccess}
2152
+ {@const draft = accessDrafts.get(view.id)}
2153
+ {@const accessMode = draft?.mode ?? view.access?.mode ?? ''}
2154
+ <Select.Root
2155
+ aria-label={`${i18n.t('common.viewAccess')} ${view.name}`}
2156
+ value={accessMode}
2157
+ disabled={view.readOnly !== false || !view.version || savedViewMutationPending || savedViewMutationError === 'conflict'}
2158
+ onchange={(event: Event) => {
2159
+ if (!(event.currentTarget instanceof HTMLSelectElement)) return;
2160
+ const mode = event.currentTarget.value;
2161
+ if (mode === 'team' || mode === 'organization' || mode === 'restricted') {
2162
+ accessDrafts.set(view.id, {
2163
+ mode,
2164
+ subjectIds: draft?.subjectIds ?? view.access?.subjectIds ?? [],
2165
+ });
2166
+ if (mode === 'restricted') loadAccessSubjects(view);
2167
+ }
2168
+ }}
2169
+ >
2170
+ <option value="" disabled>{i18n.t('common.selectOption')}</option>
2171
+ <option value="team">{i18n.t('common.teamView')}</option>
2172
+ <option value="organization">{i18n.t('common.accessOrganization')}</option>
2173
+ <option value="restricted">{i18n.t('common.accessRestricted')}</option>
2174
+ </Select.Root>
2175
+ {#if accessMode === 'restricted'}
2176
+ {#if savedViewProvider.listAccessSubjects}
2177
+ {@const selected = draft?.subjectIds ?? view.access?.subjectIds ?? []}
2178
+ <div>
2179
+ <Input
2180
+ aria-label={`${i18n.t('common.search')} ${view.name}`}
2181
+ value={accessSubjectQueries.get(view.id) ?? ''}
2182
+ disabled={view.readOnly !== false || savedViewMutationPending}
2183
+ oninput={(event: Event) => {
2184
+ if (!(event.currentTarget instanceof HTMLInputElement)) return;
2185
+ accessSubjectQueries.set(view.id, event.currentTarget.value);
2186
+ loadAccessSubjects(view, event.currentTarget.value);
2187
+ }}
2188
+ />
2189
+ <Button type="button" size="sm" variant="outline"
2190
+ aria-label={`${i18n.t('common.refresh')} ${view.name}`}
2191
+ disabled={view.readOnly !== false || savedViewMutationPending}
2192
+ onclick={() => loadAccessSubjects(view, accessSubjectQueries.get(view.id) ?? '')}>
2193
+ <RefreshCw />
2194
+ </Button>
2195
+ {#if accessSubjectsLoading.get(view.id)}
2196
+ <span role="status">{i18n.t('common.loading')}</span>
2197
+ {:else if accessSubjectsFailed.get(view.id)}
2198
+ <span role="alert">{i18n.t('common.operationFailed')}</span>
2199
+ {:else if accessSubjects.has(view.id) && !accessSubjects.get(view.id)?.length}
2200
+ <span role="status">{i18n.t('common.noData')}</span>
2201
+ {/if}
2202
+ {#each selected as id (id)}
2203
+ <label>
2204
+ <input type="checkbox" checked
2205
+ disabled={view.readOnly !== false || savedViewMutationPending || savedViewMutationError === 'conflict'}
2206
+ onchange={() => accessDrafts.set(view.id, {
2207
+ mode: 'restricted', subjectIds: selected.filter(candidate => candidate !== id),
2208
+ })} />
2209
+ {accessSubjects.get(view.id)?.find(subject => subject.id === id)?.label ?? id}
2210
+ </label>
2211
+ {/each}
2212
+ {#each (accessSubjects.get(view.id) ?? []).filter(subject => !selected.includes(subject.id)) as subject (subject.id)}
2213
+ <label>
2214
+ <input type="checkbox"
2215
+ disabled={view.readOnly !== false || savedViewMutationPending || savedViewMutationError === 'conflict' || selected.length >= 200}
2216
+ onchange={() => accessDrafts.set(view.id, {
2217
+ mode: 'restricted', subjectIds: [...selected, subject.id],
2218
+ })} />
2219
+ {subject.label}
2220
+ </label>
2221
+ {/each}
2222
+ </div>
2223
+ {:else}
2224
+ <Input
2225
+ aria-label={`${i18n.t('common.accessSubjects')} ${view.name}`}
2226
+ value={(draft?.subjectIds ?? view.access?.subjectIds ?? []).join(', ')}
2227
+ disabled={view.readOnly !== false || !view.version || savedViewMutationPending || savedViewMutationError === 'conflict'}
2228
+ oninput={(event: Event) => {
2229
+ if (event.currentTarget instanceof HTMLInputElement) {
2230
+ accessDrafts.set(view.id, { mode: 'restricted', subjectIds: event.currentTarget.value.split(',').map(id => id.trim()) });
2231
+ }
2232
+ }}
2233
+ />
2234
+ {/if}
2235
+ {/if}
2236
+ <Button type="button" size="sm"
2237
+ aria-label={`${i18n.t('common.saveAccess')} ${view.name}`}
2238
+ disabled={!draft || view.readOnly !== false || !view.version
2239
+ || savedViewMutationPending || savedViewMutationError === 'conflict'
2240
+ || (draft.mode === 'restricted' && !decodeSavedListViewAccess(draft))}
2241
+ onclick={() => saveRemoteAccess(view)}>
2242
+ {i18n.t('common.saveAccess')}
2243
+ </Button>
2244
+ {/if}
1688
2245
  <button
1689
2246
  type="button"
1690
2247
  class="svadmin-u-52083e7da442 svadmin-u-cc46d0fa277d svadmin-u-012fbd121f37 svadmin-u-3960ffc248d9 svadmin-u-86843cf1e227 svadmin-u-421ac2be5045 svadmin-u-bfa603190748 svadmin-u-8e551981c8d7 svadmin-u-ea7b2e9e070e svadmin-u-f10f771f87e9 svadmin-u-793c80e97ffb svadmin-u-9c1295a6914a"
1691
2248
  aria-label="{i18n.t("common.delete")} {view.name}"
1692
2249
  title="{i18n.t("common.delete")} {view.name}"
2250
+ disabled={view.readOnly === true || savedViewMutationPending || savedViewMutationError === 'conflict'
2251
+ || (!!view.source && !savedViewProvider?.remove)}
1693
2252
  onclick={() => deleteSavedView(view.id)}
1694
2253
  >
1695
2254
  <Trash2 class="svadmin-u-783b0d9d1e2c" />
@@ -1850,14 +2409,14 @@
1850
2409
  {#if header.id === '_select'}
1851
2410
  {#if batchActions}
1852
2411
  <Checkbox
1853
- aria-label={i18n.t('common.selectAll')}
1854
- checked={table_getIsAllRowsSelected(tbl)}
2412
+ aria-label={i18n.t(allMatchingSelected ? 'common.clearSelection' : 'common.selectAllOnPage')}
2413
+ checked={allMatchingSelected || table_getIsAllRowsSelected(tbl)}
1855
2414
  onCheckedChange={toggleAllRowsSelection}
1856
2415
  />
1857
2416
  {:else if !acEnabled || deleteResourcePermission.isLoading || deleteResourcePermission.allowed}
1858
2417
  <Checkbox
1859
- aria-label={i18n.t('common.selectAll')}
1860
- checked={table_getIsAllRowsSelected(tbl)}
2418
+ aria-label={i18n.t(allMatchingSelected ? 'common.clearSelection' : 'common.selectAllOnPage')}
2419
+ checked={allMatchingSelected || table_getIsAllRowsSelected(tbl)}
1861
2420
  onCheckedChange={toggleAllRowsSelection}
1862
2421
  />
1863
2422
  {/if}
@@ -1904,6 +2463,7 @@
1904
2463
  <Checkbox
1905
2464
  aria-label={i18n.t('common.selectRow', { id })}
1906
2465
  checked={rowIsSelected(row.id)}
2466
+ disabled={confirmPending}
1907
2467
  onCheckedChange={() => toggleRowSelection(row)}
1908
2468
  />
1909
2469
  {:else}
@@ -1911,6 +2471,7 @@
1911
2471
  <Checkbox
1912
2472
  aria-label={i18n.t('common.selectRow', { id })}
1913
2473
  checked={rowIsSelected(row.id)}
2474
+ disabled={confirmPending}
1914
2475
  onCheckedChange={() => toggleRowSelection(row)}
1915
2476
  />
1916
2477
  </CanAccess>
@@ -2032,6 +2593,7 @@
2032
2593
  <Checkbox
2033
2594
  aria-label={i18n.t('common.selectRow', { id })}
2034
2595
  checked={rowIsSelected(row.id)}
2596
+ disabled={confirmPending}
2035
2597
  onCheckedChange={() => toggleRowSelection(row)}
2036
2598
  />
2037
2599
  {:else}
@@ -2039,6 +2601,7 @@
2039
2601
  <Checkbox
2040
2602
  aria-label={i18n.t('common.selectRow', { id })}
2041
2603
  checked={rowIsSelected(row.id)}
2604
+ disabled={confirmPending}
2042
2605
  onCheckedChange={() => toggleRowSelection(row)}
2043
2606
  />
2044
2607
  </CanAccess>