@svadmin/ui 0.72.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 (252) hide show
  1. package/README.md +40 -25
  2. package/THIRD_PARTY_NOTICES.md +52 -0
  3. package/dist/app.css +7694 -5117
  4. package/dist/app.theme.css +7645 -5119
  5. package/dist/class-properties.generated.d.ts +1 -0
  6. package/dist/class-properties.generated.js +2 -0
  7. package/dist/classnames.d.ts +8 -0
  8. package/dist/classnames.js +26 -0
  9. package/dist/components/AutoForm.svelte +20 -6
  10. package/dist/components/AutoForm.svelte.d.ts +2 -0
  11. package/dist/components/AutoTable.svelte +249 -9
  12. package/dist/components/AutoTable.svelte.d.ts +5 -0
  13. package/dist/components/BoundRecordDetailDrawer.svelte +8 -2
  14. package/dist/components/BoundRecordDetailDrawer.svelte.d.ts +2 -0
  15. package/dist/components/ComboboxField.svelte +31 -9
  16. package/dist/components/ComboboxField.svelte.d.ts +3 -2
  17. package/dist/components/CreatePage.svelte +4 -0
  18. package/dist/components/CreatePage.svelte.d.ts +2 -0
  19. package/dist/components/DateRangeInput.svelte +75 -0
  20. package/dist/components/DateRangeInput.svelte.d.ts +25 -0
  21. package/dist/components/DateTimeInput.svelte +65 -0
  22. package/dist/components/DateTimeInput.svelte.d.ts +19 -0
  23. package/dist/components/DevTools.svelte +2 -2
  24. package/dist/components/EditPage.svelte +4 -0
  25. package/dist/components/EditPage.svelte.d.ts +2 -0
  26. package/dist/components/FieldRenderer.svelte +90 -18
  27. package/dist/components/FileUpload.svelte +250 -0
  28. package/dist/components/FileUpload.svelte.d.ts +32 -0
  29. package/dist/components/FilterBuilder.svelte +236 -279
  30. package/dist/components/FilterBuilder.svelte.d.ts +3 -0
  31. package/dist/components/ImportWizard.svelte +181 -94
  32. package/dist/components/JsonSchemaForm.svelte +255 -99
  33. package/dist/components/JsonSchemaForm.svelte.d.ts +12 -1
  34. package/dist/components/Layout.svelte +1 -2
  35. package/dist/components/LazyTaskQueueDrawer.svelte.d.ts +1 -1
  36. package/dist/components/ListPage.svelte +4 -0
  37. package/dist/components/ListPage.svelte.d.ts +2 -0
  38. package/dist/components/LoginPage.svelte +1 -1
  39. package/dist/components/MoneyInput.svelte +25 -0
  40. package/dist/components/MoneyInput.svelte.d.ts +20 -0
  41. package/dist/components/NumberInput.svelte +25 -0
  42. package/dist/components/NumberInput.svelte.d.ts +19 -0
  43. package/dist/components/NumericInput.svelte +110 -0
  44. package/dist/components/NumericInput.svelte.d.ts +23 -0
  45. package/dist/components/PercentInput.svelte +25 -0
  46. package/dist/components/PercentInput.svelte.d.ts +20 -0
  47. package/dist/components/QuickEditDrawer.svelte +4 -1
  48. package/dist/components/QuickEditDrawer.svelte.d.ts +2 -0
  49. package/dist/components/RecordDetailDrawer.svelte +4 -2
  50. package/dist/components/RecordDetailDrawer.svelte.d.ts +2 -0
  51. package/dist/components/ResourceOperationsPage.svelte +25 -19
  52. package/dist/components/ResourceOperationsPage.svelte.d.ts +5 -1
  53. package/dist/components/ShowInferencer.svelte +4 -2
  54. package/dist/components/ShowInferencer.svelte.d.ts +2 -0
  55. package/dist/components/ShowPage.svelte +7 -3
  56. package/dist/components/ShowPage.svelte.d.ts +2 -0
  57. package/dist/components/Sidebar.svelte +5 -5
  58. package/dist/components/SpreadsheetView.svelte +81 -286
  59. package/dist/components/StepsForm.svelte +5 -5
  60. package/dist/components/SvarAutoTable.svelte +39 -0
  61. package/dist/components/SvarAutoTable.svelte.d.ts +13 -0
  62. package/dist/components/SvarDataGrid.svelte +362 -0
  63. package/dist/components/SvarDataGrid.svelte.d.ts +57 -0
  64. package/dist/components/SvarGridCell.svelte +10 -0
  65. package/dist/components/SvarGridCell.svelte.d.ts +7 -0
  66. package/dist/components/SvarResourceRead.svelte +50 -0
  67. package/dist/components/SvarResourceRead.svelte.d.ts +23 -0
  68. package/dist/components/SvarResourceTable.svelte +63 -0
  69. package/dist/components/SvarResourceTable.svelte.d.ts +42 -0
  70. package/dist/components/SvarResourceTableView.svelte +364 -0
  71. package/dist/components/SvarResourceTableView.svelte.d.ts +4 -0
  72. package/dist/components/TaskQueueDrawer.svelte +72 -16
  73. package/dist/components/TreeSelect.svelte +208 -28
  74. package/dist/components/TreeSelect.svelte.d.ts +3 -1
  75. package/dist/components/auto-table-grid.d.ts +26 -0
  76. package/dist/components/auto-table-grid.js +1 -0
  77. package/dist/components/content/ContentPageHeader.svelte +12 -9
  78. package/dist/components/content/ContentPageShell.svelte +6 -9
  79. package/dist/components/content/FileBrowser.svelte +35 -0
  80. package/dist/components/content/FileBrowser.svelte.d.ts +14 -0
  81. package/dist/components/content/MetricBlock.svelte +10 -13
  82. package/dist/components/content/NetworkTable.svelte +3 -3
  83. package/dist/components/content/NetworkTable.svelte.d.ts +1 -0
  84. package/dist/components/content/PageToolbar.svelte +6 -10
  85. package/dist/components/content/SectionHeader.svelte +8 -13
  86. package/dist/components/content/SettingsFieldRow.svelte +9 -23
  87. package/dist/components/content/SettingsGroup.svelte +11 -27
  88. package/dist/components/content/StatusBadge.svelte +3 -8
  89. package/dist/components/content/WorkspaceLayout.svelte +8 -12
  90. package/dist/components/enterprise/filter-tree.d.ts +48 -0
  91. package/dist/components/enterprise/filter-tree.js +224 -0
  92. package/dist/components/enterprise/json-schema-form.d.ts +39 -0
  93. package/dist/components/enterprise/json-schema-form.js +195 -0
  94. package/dist/components/enterprise/recursive-schema-form.d.ts +30 -0
  95. package/dist/components/enterprise/recursive-schema-form.js +196 -0
  96. package/dist/components/enterprise/spreadsheet-formula.d.ts +12 -0
  97. package/dist/components/enterprise/spreadsheet-formula.js +245 -0
  98. package/dist/components/fieldComponentMap.js +5 -0
  99. package/dist/components/fields/DateField.svelte +4 -3
  100. package/dist/components/fields/date-field-value.d.ts +2 -0
  101. package/dist/components/fields/date-field-value.js +13 -0
  102. package/dist/components/json-schema-form-state.d.ts +37 -0
  103. package/dist/components/json-schema-form-state.js +168 -0
  104. package/dist/components/record-detail.svelte.d.ts +3 -0
  105. package/dist/components/record-detail.svelte.js +10 -4
  106. package/dist/components/svar-grid-cells.d.ts +14 -0
  107. package/dist/components/svar-grid-cells.js +2 -0
  108. package/dist/components/svar-grid-contract.d.ts +80 -0
  109. package/dist/components/svar-grid-contract.js +159 -0
  110. package/dist/components/svar-grid-interactions.d.ts +54 -0
  111. package/dist/components/svar-grid-interactions.js +174 -0
  112. package/dist/components/svar-grid-loading.d.ts +40 -0
  113. package/dist/components/svar-grid-loading.js +180 -0
  114. package/dist/components/svar-grid-operations.d.ts +37 -0
  115. package/dist/components/svar-grid-operations.js +186 -0
  116. package/dist/components/svar-grid-panes.d.ts +34 -0
  117. package/dist/components/svar-grid-panes.js +66 -0
  118. package/dist/components/svar-grid-resource-loading.d.ts +18 -0
  119. package/dist/components/svar-grid-resource-loading.js +68 -0
  120. package/dist/components/svar-grid-resource-session.svelte.d.ts +29 -0
  121. package/dist/components/svar-grid-resource-session.svelte.js +61 -0
  122. package/dist/components/ui/badge/badge-variants.js +3 -1
  123. package/dist/components/ui/button/button-variants.js +5 -2
  124. package/dist/components/ui/input/input.svelte +1 -4
  125. package/dist/components/ui/textarea/textarea.svelte +2 -1
  126. package/dist/components.css +3079 -0
  127. package/dist/design-contract.d.ts +13 -0
  128. package/dist/design-contract.js +11 -0
  129. package/dist/design-system.d.ts +15 -0
  130. package/dist/design-system.js +7 -0
  131. package/dist/index.d.ts +14 -0
  132. package/dist/index.js +9 -0
  133. package/dist/page-chrome.css +12 -49
  134. package/dist/recipes.d.ts +4 -0
  135. package/dist/recipes.js +4 -0
  136. package/dist/rendering/index.d.ts +87 -0
  137. package/dist/rendering/index.js +117 -0
  138. package/dist/styled-system/css/conditions.js +36 -0
  139. package/dist/styled-system/css/css.d.ts +22 -0
  140. package/dist/styled-system/css/css.js +45 -0
  141. package/dist/styled-system/css/cva.d.ts +6 -0
  142. package/dist/styled-system/css/cva.js +87 -0
  143. package/dist/styled-system/css/cx.d.ts +5 -0
  144. package/dist/styled-system/css/cx.js +15 -0
  145. package/dist/styled-system/css/index.d.ts +5 -0
  146. package/dist/styled-system/css/index.js +4 -0
  147. package/dist/styled-system/css/sva.d.ts +4 -0
  148. package/dist/styled-system/css/sva.js +46 -0
  149. package/dist/styled-system/helpers.js +316 -0
  150. package/dist/styled-system/patterns/aspect-ratio.d.ts +20 -0
  151. package/dist/styled-system/patterns/aspect-ratio.js +38 -0
  152. package/dist/styled-system/patterns/bleed.d.ts +21 -0
  153. package/dist/styled-system/patterns/bleed.js +24 -0
  154. package/dist/styled-system/patterns/box.d.ts +20 -0
  155. package/dist/styled-system/patterns/box.js +15 -0
  156. package/dist/styled-system/patterns/center.d.ts +20 -0
  157. package/dist/styled-system/patterns/center.js +21 -0
  158. package/dist/styled-system/patterns/circle.d.ts +20 -0
  159. package/dist/styled-system/patterns/circle.js +25 -0
  160. package/dist/styled-system/patterns/container.d.ts +20 -0
  161. package/dist/styled-system/patterns/container.js +21 -0
  162. package/dist/styled-system/patterns/cq.d.ts +21 -0
  163. package/dist/styled-system/patterns/cq.js +21 -0
  164. package/dist/styled-system/patterns/divider.d.ts +22 -0
  165. package/dist/styled-system/patterns/divider.js +25 -0
  166. package/dist/styled-system/patterns/flex.d.ts +26 -0
  167. package/dist/styled-system/patterns/flex.js +26 -0
  168. package/dist/styled-system/patterns/float.d.ts +23 -0
  169. package/dist/styled-system/patterns/float.js +52 -0
  170. package/dist/styled-system/patterns/grid-item.d.ts +25 -0
  171. package/dist/styled-system/patterns/grid-item.js +25 -0
  172. package/dist/styled-system/patterns/grid.d.ts +24 -0
  173. package/dist/styled-system/patterns/grid.js +27 -0
  174. package/dist/styled-system/patterns/hstack.d.ts +21 -0
  175. package/dist/styled-system/patterns/hstack.js +24 -0
  176. package/dist/styled-system/patterns/index.d.ts +21 -0
  177. package/dist/styled-system/patterns/index.js +20 -0
  178. package/dist/styled-system/patterns/link-overlay.d.ts +20 -0
  179. package/dist/styled-system/patterns/link-overlay.js +24 -0
  180. package/dist/styled-system/patterns/spacer.d.ts +20 -0
  181. package/dist/styled-system/patterns/spacer.js +25 -0
  182. package/dist/styled-system/patterns/square.d.ts +20 -0
  183. package/dist/styled-system/patterns/square.js +24 -0
  184. package/dist/styled-system/patterns/stack.d.ts +23 -0
  185. package/dist/styled-system/patterns/stack.js +24 -0
  186. package/dist/styled-system/patterns/visually-hidden.d.ts +20 -0
  187. package/dist/styled-system/patterns/visually-hidden.js +18 -0
  188. package/dist/styled-system/patterns/vstack.d.ts +21 -0
  189. package/dist/styled-system/patterns/vstack.js +24 -0
  190. package/dist/styled-system/patterns/wrap.d.ts +24 -0
  191. package/dist/styled-system/patterns/wrap.js +25 -0
  192. package/dist/styled-system/recipes/content-header.d.ts +31 -0
  193. package/dist/styled-system/recipes/content-header.js +65 -0
  194. package/dist/styled-system/recipes/content-page.d.ts +34 -0
  195. package/dist/styled-system/recipes/content-page.js +43 -0
  196. package/dist/styled-system/recipes/create-recipe.js +82 -0
  197. package/dist/styled-system/recipes/index.d.ts +17 -0
  198. package/dist/styled-system/recipes/index.js +16 -0
  199. package/dist/styled-system/recipes/metric-block.d.ts +34 -0
  200. package/dist/styled-system/recipes/metric-block.js +76 -0
  201. package/dist/styled-system/recipes/product-list.d.ts +31 -0
  202. package/dist/styled-system/recipes/product-list.js +73 -0
  203. package/dist/styled-system/recipes/product-section.d.ts +31 -0
  204. package/dist/styled-system/recipes/product-section.js +49 -0
  205. package/dist/styled-system/recipes/product-settings-row.d.ts +34 -0
  206. package/dist/styled-system/recipes/product-settings-row.js +58 -0
  207. package/dist/styled-system/recipes/product-settings.d.ts +31 -0
  208. package/dist/styled-system/recipes/product-settings.js +57 -0
  209. package/dist/styled-system/recipes/product-status.d.ts +34 -0
  210. package/dist/styled-system/recipes/product-status.js +45 -0
  211. package/dist/styled-system/recipes/product-toolbar.d.ts +31 -0
  212. package/dist/styled-system/recipes/product-toolbar.js +41 -0
  213. package/dist/styled-system/recipes/product-workspace.d.ts +34 -0
  214. package/dist/styled-system/recipes/product-workspace.js +58 -0
  215. package/dist/styled-system/recipes/surface-metric.d.ts +38 -0
  216. package/dist/styled-system/recipes/surface-metric.js +63 -0
  217. package/dist/styled-system/recipes/surface-table.d.ts +34 -0
  218. package/dist/styled-system/recipes/surface-table.js +62 -0
  219. package/dist/styled-system/recipes/ui-badge.d.ts +31 -0
  220. package/dist/styled-system/recipes/ui-badge.js +38 -0
  221. package/dist/styled-system/recipes/ui-button.d.ts +32 -0
  222. package/dist/styled-system/recipes/ui-button.js +43 -0
  223. package/dist/styled-system/recipes/ui-input.d.ts +31 -0
  224. package/dist/styled-system/recipes/ui-input.js +49 -0
  225. package/dist/styled-system/recipes/ui-textarea.d.ts +31 -0
  226. package/dist/styled-system/recipes/ui-textarea.js +24 -0
  227. package/dist/styled-system/styles.css +1257 -0
  228. package/dist/styled-system/tokens/index.d.ts +9 -0
  229. package/dist/styled-system/tokens/index.js +268 -0
  230. package/dist/styled-system/tokens/tokens.d.ts +36 -0
  231. package/dist/styled-system/types/composition.d.ts +227 -0
  232. package/dist/styled-system/types/conditions.d.ts +236 -0
  233. package/dist/styled-system/types/csstype.d.ts +22570 -0
  234. package/dist/styled-system/types/index.d.ts +6 -0
  235. package/dist/styled-system/types/parts.d.ts +8 -0
  236. package/dist/styled-system/types/pattern.d.ts +78 -0
  237. package/dist/styled-system/types/prop-type.d.ts +223 -0
  238. package/dist/styled-system/types/recipe.d.ts +181 -0
  239. package/dist/styled-system/types/selectors.d.ts +59 -0
  240. package/dist/styled-system/types/static-css.d.ts +56 -0
  241. package/dist/styled-system/types/style-props.d.ts +8088 -0
  242. package/dist/styled-system/types/system-types.d.ts +151 -0
  243. package/dist/styles/aliases.css +49 -0
  244. package/dist/styles/compatibility.epilogue.css +384 -0
  245. package/dist/styles/compatibility.prelude.css +5513 -0
  246. package/dist/styles/conditional-states.css +28 -0
  247. package/dist/styles/conditional-utilities.css +22 -0
  248. package/dist/styles/recipes.css +1262 -0
  249. package/dist/utils.d.ts +1 -1
  250. package/dist/utils.js +1 -1
  251. package/package.json +31 -13
  252. package/styles-compatibility.json +12 -0
@@ -0,0 +1,362 @@
1
+ <script lang="ts">
2
+ import { onMount, onDestroy, untrack, setContext, type Component, type Snippet } from 'svelte';
3
+ import { SvelteMap } from 'svelte/reactivity';
4
+ import SvarGridCell from './SvarGridCell.svelte';
5
+ import { SVAR_CELL_CONTEXT, type SvarCellContext, type SvarCellContent } from './svar-grid-cells.js';
6
+ import { projectSvarRows, svarColumnId, svarSortMarks, svarFilterValues, type SvarSort, type SvarTextFilter } from './svar-grid-contract.js';
7
+ import { checkedSvarInteractiveColumns, buildSvarInteractiveColumns, installSvarInteractions, type SvarInteractiveColumn, type SvarCellEdit } from './svar-grid-interactions.js';
8
+ import { svarRecordKey, svarRecordIndex, type SvarRecordId } from './svar-grid-operations.js';
9
+ import { createSvarPanePair, type SvarManagedApi, type SvarManagedEngineProps } from './svar-grid-panes.js';
10
+ import { createSvarLoadCache, checkedSvarRange, snapshotSvarRecords, snapshotSvarWindow, mergeSvarBranches, projectSvarLazyRows, svarRowId, SvarLoadCancelled,
11
+ type SvarRange, type SvarWindowPage, type SvarWindowSource, type SvarChildrenLoader } from './svar-grid-loading.js';
12
+
13
+ export interface SvarDataGridProps {
14
+ Grid: Component<SvarManagedEngineProps>;
15
+ Theme: Component<{ fonts?: boolean; children?: Snippet }>;
16
+ items?: readonly Record<string, unknown>[];
17
+ columns: readonly SvarInteractiveColumn[];
18
+ primaryKey?: string;
19
+ childrenKey?: string;
20
+ height?: number;
21
+ density?: 'compact' | 'comfortable';
22
+ freezeLeft?: number;
23
+ /** 独立右侧面板,不使用上游 split.right。 */
24
+ freezeRight?: number;
25
+ queryMode?: 'local' | 'server';
26
+ sorters?: readonly SvarSort[];
27
+ filters?: readonly SvarTextFilter[];
28
+ onSortChange?: (sorters: SvarSort[]) => void;
29
+ onFilterChange?: (filters: SvarTextFilter[]) => void;
30
+ onCellEdit?: (edit: SvarCellEdit) => Promise<void>;
31
+ selectable?: boolean;
32
+ selectedIds?: readonly SvarRecordId[];
33
+ onSelectionChange?: (ids: SvarRecordId[]) => void;
34
+ /** 仅可信宿主可注入的原生单元格内容。 */
35
+ cellContent?: Snippet<[SvarCellContent]>;
36
+ /** 已加载记录的独立快照,含已加载后代,不包含尚未请求的行。 */
37
+ onRecordsChange?: (records: readonly Record<string, unknown>[]) => void;
38
+ windowSource?: SvarWindowSource;
39
+ loadChildren?: SvarChildrenLoader;
40
+ hasChildrenKey?: string;
41
+ onLoadMore?: (request: { signal: AbortSignal }) => Promise<void>;
42
+ hasMore?: boolean;
43
+ /** 查询、租户或会话切换必须改变此宿主版本;追加同一数据集则无需改变。 */
44
+ scopeKey?: string | number;
45
+ loading?: boolean;
46
+ error?: string;
47
+ disabled?: boolean;
48
+ locale?: string;
49
+ label?: string;
50
+ emptyLabel?: string;
51
+ loadingLabel?: string;
52
+ disabledLabel?: string;
53
+ fallbackLabel?: string;
54
+ }
55
+ let { Grid, Theme, items = [], columns, primaryKey = 'id', childrenKey, height = 420, density = 'comfortable', freezeLeft = 0, freezeRight = 0, queryMode = 'local',
56
+ sorters = [], filters = [], onSortChange, onFilterChange, onCellEdit, selectable = false, selectedIds = [], onSelectionChange, onRecordsChange, cellContent,
57
+ windowSource, loadChildren, hasChildrenKey = 'hasChildren', onLoadMore, hasMore = false, scopeKey = 0,
58
+ loading = false, error, disabled = false, locale = 'en-US', label = 'Data grid', emptyLabel = 'No records', loadingLabel = 'Loading',
59
+ disabledLabel = 'Grid is disabled', fallbackLabel = 'Static preview (up to 20 rows)',
60
+ }: SvarDataGridProps = $props();
61
+ const treeKey = $derived(loadChildren ? childrenKey ?? 'children' : childrenKey);
62
+ const rowHeight = $derived(density === 'compact' ? 32 : 44);
63
+ // 普通追加不重建引擎,否则无限加载会把滚动位置重置。懒树根替换必须清理分支缓存。
64
+ const dataOwner = $derived({ scopeKey, items: loadChildren ? items : undefined, source: windowSource,
65
+ loadChildren, onLoadMore, primaryKey, treeKey, hasChildrenKey, freezeRight, Grid });
66
+ let resetOwner = untrack(() => dataOwner);
67
+ let mounted = $state(false);
68
+ let alive = true;
69
+ let pending = $state(false);
70
+ let editFailure = $state('');
71
+ const branches = new SvelteMap<string, readonly Record<string, unknown>[]>();
72
+ const branchPending = new SvelteMap<string, SvarRecordId>();
73
+ const branchErrors = new SvelteMap<string, { id: SvarRecordId; message: string }>();
74
+ const opened = new Set<string>();
75
+ const childCache = createSvarLoadCache<readonly Record<string, unknown>[]>(value => snapshotSvarRecords(value, primaryKey, treeKey));
76
+ const windowCache = createSvarLoadCache<SvarWindowPage>(value => ({ data: snapshotSvarRecords(value.data, primaryKey), total: value.total }), 3);
77
+ let windowRows = $state.raw<readonly Record<string, unknown>[]>([]);
78
+ let windowTotal = $state<number | undefined>();
79
+ let windowLoading = $state(false);
80
+ let windowError = $state('');
81
+ let desiredWindow = '';
82
+ let lastRange: SvarRange | undefined;
83
+ let morePending = $state(false);
84
+ let moreError = $state('');
85
+ let moreController: AbortController | undefined;
86
+ let pairOwner: object | undefined;
87
+ let pair = createSvarPanePair(() => false);
88
+ onMount(() => { mounted = true; });
89
+ onDestroy(() => { alive = false; pair.dispose(); childCache.dispose(); windowCache.dispose(); moreController?.abort(); });
90
+ $effect.pre(() => {
91
+ const owner = dataOwner;
92
+ if (owner === resetOwner) return;
93
+ untrack(() => {
94
+ resetOwner = owner; childCache.clear(); windowCache.clear(); branches.clear(); branchPending.clear(); branchErrors.clear(); opened.clear();
95
+ windowRows = []; windowTotal = undefined; windowLoading = false; windowError = ''; desiredWindow = ''; lastRange = undefined;
96
+ moreController?.abort(); moreController = undefined; morePending = false; moreError = ''; pending = false; editFailure = '';
97
+ });
98
+ });
99
+
100
+ const model = $derived.by(() => {
101
+ try {
102
+ const checked = checkedSvarInteractiveColumns(columns);
103
+ if (!Number.isFinite(height) || height < 160 || height > 4096 || !Number.isInteger(freezeLeft) || freezeLeft < 0
104
+ || !Number.isInteger(freezeRight) || freezeRight < 0 || freezeLeft + freezeRight > checked.length || freezeRight >= checked.length) throw new Error('Invalid grid height or frozen-column count');
105
+ if (queryMode === 'server' && ((checked.some(c => c.sortable) && !onSortChange) || (checked.some(c => c.filterable) && !onFilterChange))) throw new Error('Server grids require query callbacks');
106
+ if (windowSource && (queryMode !== 'server' || treeKey !== undefined || onLoadMore)) throw new Error('Window loading requires a flat server grid');
107
+ if (onLoadMore && queryMode !== 'server') throw new Error('Infinite loading requires server query mode');
108
+ if (windowSource) checkedSvarRange({ row: { start: 0, end: 0 } }, windowSource.total);
109
+ const engineColumns = buildSvarInteractiveColumns(checked, treeKey !== undefined, onCellEdit !== undefined, locale)
110
+ .map(column => cellContent ? { ...column, cell: SvarGridCell } : column);
111
+ const rightIds = new Set(engineColumns.slice(engineColumns.length - freezeRight).map(column => column.id));
112
+ return { ok: true as const, columns: checked, engineColumns, rightIds,
113
+ center: engineColumns.map(column => ({ ...column, hidden: freezeRight > 0 && rightIds.has(column.id) })),
114
+ right: engineColumns.map(column => ({ ...column, hidden: !rightIds.has(column.id), width: column.width ?? 160, flexgrow: 0, resize: false })),
115
+ rightWidth: engineColumns.slice(engineColumns.length - freezeRight).reduce((sum, column) => sum + (column.width ?? 160), 0) + 2 };
116
+ } catch (failure) { return { ok: false as const, message: failure instanceof Error ? failure.message : 'Invalid grid configuration' }; }
117
+ });
118
+ const records = $derived.by(() => {
119
+ if (!model.ok) return { ok: false as const, message: model.message };
120
+ try {
121
+ const value = windowSource ? windowRows : loadChildren && treeKey ? mergeSvarBranches(items, branches, primaryKey, treeKey) : items;
122
+ const rows = loadChildren && treeKey ? projectSvarLazyRows(value, model.columns, primaryKey, treeKey, hasChildrenKey, branches, opened)
123
+ : projectSvarRows(value, model.columns, primaryKey, treeKey);
124
+ return { ok: true as const, value, rows };
125
+ } catch (failure) { return { ok: false as const, message: failure instanceof Error ? failure.message : 'Invalid grid data' }; }
126
+ });
127
+ const cellRecords = $derived.by(() => cellContent && records.ok
128
+ ? svarRecordIndex(snapshotSvarRecords(records.value, primaryKey, treeKey), primaryKey, treeKey) : new Map<string, Record<string, unknown>>());
129
+ setContext<SvarCellContext>(SVAR_CELL_CONTEXT, {
130
+ render: () => cellContent,
131
+ resolve(row, column) {
132
+ if (!model.ok || loading || error || windowLoading || windowError) return;
133
+ const rowId: unknown = row && typeof row === 'object' ? Object.getOwnPropertyDescriptor(row, 'id')?.value : undefined;
134
+ const columnId: unknown = column && typeof column === 'object' ? Object.getOwnPropertyDescriptor(column, 'id')?.value : undefined;
135
+ const source = typeof rowId === 'string' ? cellRecords.get(rowId) : undefined;
136
+ const field = model.columns.find(item => svarColumnId(item.key) === columnId);
137
+ if (!source || !field) return;
138
+ // 每个调用取得独立副本,snippet 不能修改 Provider 或其他单元格的记录。
139
+ const record = snapshotSvarRecords([source], primaryKey, treeKey)[0];
140
+ if (!record) return;
141
+ return { id: svarRowId(record, primaryKey), field: field.key, value: record[field.key], record };
142
+ },
143
+ });
144
+ const rightRows = $derived.by(() => {
145
+ if (!records.ok || !model.ok || !freezeRight) return [];
146
+ return loadChildren && treeKey ? projectSvarLazyRows(records.value, model.columns, primaryKey, treeKey, hasChildrenKey, branches, opened)
147
+ : projectSvarRows(records.value, model.columns, primaryKey, treeKey);
148
+ });
149
+ const queryProps = $derived(queryMode === 'server' ? { sortMarks: svarSortMarks(sorters), filterValues: svarFilterValues(filters) } : {});
150
+ const dynamicProps = $derived(windowSource ? { dynamic: { rowCount: windowTotal ?? windowSource.total } } : {});
151
+ const selectedRows = $derived(selectedIds.map(svarRecordKey));
152
+ const isLoading = $derived(loading || windowLoading);
153
+ const activeError = $derived(error || windowError);
154
+ $effect(() => {
155
+ const callback = onRecordsChange, value = records;
156
+ const unavailable = isLoading || activeError || !value.ok;
157
+ untrack(() => {
158
+ if (callback) callback(unavailable || !value.ok ? [] : snapshotSvarRecords(value.value, primaryKey, treeKey));
159
+ });
160
+ });
161
+
162
+ function current(owner: typeof dataOwner): boolean { return alive && dataOwner === owner && !disabled && !loading && !error && model.ok && records.ok; }
163
+ function requestWindow(event: unknown, force = false): void {
164
+ const source = windowSource, owner = dataOwner;
165
+ if (!source || !current(owner)) return;
166
+ let range: SvarRange;
167
+ try { range = checkedSvarRange(event, windowTotal ?? source.total); }
168
+ catch { windowError = 'Invalid grid window'; return; }
169
+ // 布局测量也会发出新窗口,不能因此自动重试失败或移除用户的重试按钮。
170
+ if (windowError && !force) { lastRange = range; return; }
171
+ const key = `${range.start}:${range.end}`;
172
+ if (!force && desiredWindow === key) return;
173
+ desiredWindow = key; lastRange = range;
174
+ if (force) windowCache.invalidate(key);
175
+ windowCache.abortPendingExcept(key); windowLoading = true; windowError = ''; windowRows = [];
176
+ void windowCache.request(key, async signal => snapshotSvarWindow(await source.load({ ...range, signal }), range, primaryKey)).then(page => {
177
+ if (!alive || dataOwner !== owner || desiredWindow !== key) return;
178
+ if (!current(owner)) { windowLoading = false; desiredWindow = ''; return; }
179
+ windowRows = page.data; windowTotal = page.total; windowLoading = false;
180
+ if (range.start >= page.total && range.start > 0) void pair.get(0)?.exec('scroll-to', { top: 0 }).catch(() => {});
181
+ }).catch((failure: unknown) => {
182
+ if (!alive || dataOwner !== owner || desiredWindow !== key || failure instanceof SvarLoadCancelled) return;
183
+ windowRows = []; windowLoading = false;
184
+ if (!current(owner)) { desiredWindow = ''; return; }
185
+ windowError = locale.startsWith('zh') ? '窗口加载失败,请重试' : 'Window load failed; retry';
186
+ });
187
+ }
188
+ $effect(() => {
189
+ const available = mounted && !disabled && !loading && !error;
190
+ const source = windowSource;
191
+ if (available && source) untrack(() => { if (!desiredWindow && lastRange) requestWindow({ row: lastRange }); });
192
+ });
193
+ function retryWindow(): void { if (lastRange) requestWindow({ row: lastRange }, true); }
194
+ function openBranch(key: string, force = false): boolean {
195
+ const loader = loadChildren, owner = dataOwner;
196
+ if (!loader || !treeKey || !current(owner) || !records.ok) return false;
197
+ const raw = svarRecordIndex(records.value, primaryKey, treeKey).get(key);
198
+ if (!raw) return false;
199
+ if (branches.has(key) && !force) return true;
200
+ const native: unknown = Object.getOwnPropertyDescriptor(raw, treeKey)?.value;
201
+ const marker: unknown = Object.getOwnPropertyDescriptor(raw, hasChildrenKey)?.value;
202
+ if (!force && ((Array.isArray(native) && native.length > 0) || marker !== true)) return true;
203
+ opened.add(key);
204
+ if (branchPending.has(key)) return false;
205
+ const id = svarRowId(raw, primaryKey);
206
+ if (force) childCache.invalidate(key);
207
+ branchPending.set(key, id); branchErrors.delete(key);
208
+ void childCache.request(key, signal => loader({ id, signal })).then(children => {
209
+ if (!alive || dataOwner !== owner) return;
210
+ branchPending.delete(key);
211
+ if (!current(owner) || !treeKey) return;
212
+ const candidate = new Map(branches); candidate.set(key, children);
213
+ mergeSvarBranches(items, candidate, primaryKey, treeKey); branches.set(key, children);
214
+ }).catch((failure: unknown) => {
215
+ if (!alive || dataOwner !== owner) return;
216
+ branchPending.delete(key);
217
+ if (!current(owner) || failure instanceof SvarLoadCancelled) return;
218
+ branchErrors.set(key, { id, message: locale.startsWith('zh') ? '子节点加载失败' : 'Child loading failed' });
219
+ });
220
+ return false;
221
+ }
222
+ function loadMore(): void {
223
+ const load = onLoadMore, owner = dataOwner;
224
+ if (!load || !hasMore || morePending || !current(owner)) return;
225
+ const controller = new AbortController(); moreController = controller; morePending = true; moreError = '';
226
+ void Promise.resolve().then(() => {
227
+ if (!current(owner) || controller.signal.aborted) throw new SvarLoadCancelled(); return load({ signal: controller.signal });
228
+ }).catch((failure: unknown) => {
229
+ if (current(owner) && !(failure instanceof SvarLoadCancelled)) moreError = locale.startsWith('zh') ? '追加加载失败,请重试' : 'Loading more rows failed; retry';
230
+ }).finally(() => { if (alive && moreController === controller) { morePending = false; moreController = undefined; } });
231
+ }
232
+ function init(api: SvarManagedApi, side: 0 | 1): void {
233
+ const owner = dataOwner;
234
+ if (pairOwner !== owner) { pair.dispose(); pairOwner = owner; pair = createSvarPanePair(() => current(owner)); }
235
+ pair.attach(side, api);
236
+ const active = () => current(owner) && pair.get(side) === api && !windowLoading && !windowError;
237
+ installSvarInteractions(api, {
238
+ current: active, columns: () => model.ok ? model.columns : [], items: () => records.ok ? records.value : [],
239
+ primaryKey: () => primaryKey, childrenKey: () => treeKey,
240
+ server: () => queryMode === 'server', sorters: () => sorters, filters: () => filters,
241
+ sort: next => onSortChange?.(next), filter: next => onFilterChange?.(next),
242
+ editable: () => onCellEdit !== undefined && !pending, selectable: () => selectable && !pending,
243
+ selection: next => onSelectionChange?.(next), error: message => { if (active()) editFailure = message; },
244
+ edit(edit) {
245
+ const write = onCellEdit;
246
+ if (!write || pending || !active()) return;
247
+ pending = true; editFailure = '';
248
+ void Promise.resolve().then(() => { if (!active()) throw new Error('Scope changed'); return write(edit); }).then(() => {
249
+ if (active() && windowSource && lastRange) requestWindow({ row: lastRange }, true);
250
+ }).catch(() => { if (current(owner)) editFailure = locale.startsWith('zh') ? '保存失败,请刷新确认后重试' : 'Save failed; refresh before retrying';
251
+ }).finally(() => { if (alive && dataOwner === owner) pending = false; });
252
+ },
253
+ });
254
+ api.intercept('open-row', event => {
255
+ if (!active()) return false;
256
+ const key: unknown = event && typeof event === 'object' ? Object.getOwnPropertyDescriptor(event, 'id')?.value : undefined;
257
+ if (typeof key !== 'string') return false;
258
+ try { if (loadChildren && !openBranch(key)) return false; } catch { return false; }
259
+ });
260
+ api.on('open-row', event => {
261
+ const key: unknown = event && typeof event === 'object' ? Object.getOwnPropertyDescriptor(event, 'id')?.value : undefined;
262
+ if (active() && typeof key === 'string') opened.add(key);
263
+ });
264
+ api.on('close-row', event => {
265
+ const key: unknown = event && typeof event === 'object' ? Object.getOwnPropertyDescriptor(event, 'id')?.value : undefined;
266
+ if (active() && typeof key === 'string') opened.delete(key);
267
+ });
268
+ if (side === 0) {
269
+ api.on('request-data', event => { if (pair.get(side) === api && current(owner)) requestWindow(event); });
270
+ api.on('scroll-to', () => {
271
+ if (!active() || !onLoadMore || !hasMore) return;
272
+ const state = api.getState();
273
+ if ((state.scrollTop ?? 0) + height * 2 >= (state.flatData?.length ?? 0) * rowHeight) loadMore();
274
+ });
275
+ }
276
+ }
277
+ function navigatePane(event: KeyboardEvent, side: 0 | 1): void {
278
+ if (!freezeRight || !model.ok || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
279
+ if (event.target instanceof HTMLElement && event.target.closest('input,textarea,select,[contenteditable="true"]')) return;
280
+ const focus = pair.get(side)?.getState().focusCell;
281
+ if (focus?.row === undefined || typeof focus.column !== 'string') return;
282
+ const center = model.center.filter(column => !column.hidden), right = model.right.filter(column => !column.hidden);
283
+ const target = side === 0 && event.key === 'ArrowRight' && center.at(-1)?.id === focus.column ? right[0]
284
+ : side === 1 && event.key === 'ArrowLeft' && right[0]?.id === focus.column ? center.at(-1) : undefined;
285
+ if (!target) return;
286
+ event.preventDefault(); event.stopPropagation(); pair.focus(side === 0 ? 1 : 0, focus.row, target.id);
287
+ }
288
+ </script>
289
+
290
+ <Theme fonts={false}>
291
+ <div class="svadmin-svar-grid" data-svadmin-svar-grid data-density={density} role="group" aria-label={label} aria-busy={isLoading || pending || morePending} aria-disabled={disabled}>
292
+ {#if !model.ok || !records.ok}<p role="alert">{!model.ok ? model.message : !records.ok ? records.message : ''}</p>
293
+ {:else}
294
+ {#if activeError}<p role="alert">{activeError}</p>
295
+ {:else if isLoading}<p role="status">{loadingLabel}</p>
296
+ {:else if disabled}<p role="status">{disabledLabel}</p>
297
+ {:else if records.rows.length === 0 && (!windowSource || (windowTotal ?? windowSource.total) === 0)}<p role="status">{emptyLabel}</p>{/if}
298
+ {#if windowError}<button type="button" disabled={disabled} onclick={retryWindow}>{locale.startsWith('zh') ? '重试窗口' : 'Retry window'}</button>{/if}
299
+ {#if editFailure}<p role="alert">{editFailure}</p>{/if}
300
+ {#if pending}<p role="status">{locale.startsWith('zh') ? '正在保存' : 'Saving'}</p>{/if}
301
+ {#if branchPending.size}<p role="status">{locale.startsWith('zh') ? '正在加载子节点' : 'Loading children'}</p>{/if}
302
+ {#each [...branchErrors] as [key, failure] (key)}<p role="alert">{failure.message}: {failure.id} <button type="button" disabled={disabled} onclick={() => openBranch(key, true)}>{locale.startsWith('zh') ? '重试' : 'Retry children'}</button></p>{/each}
303
+ {#if mounted}
304
+ {#key dataOwner}<div class="grid-panes" inert={disabled}>
305
+ <div class="grid-viewport center-pane" data-svar-pane="center" style:height="{height}px" role="region" aria-label="Scrollable columns" onkeydowncapture={event => navigatePane(event, 0)}>
306
+ <Grid columns={model.center} data={activeError || isLoading ? [] : records.rows} tree={treeKey !== undefined}
307
+ split={{ left: freezeLeft }} sizes={{ rowHeight, headerHeight: 36, columnWidth: 160 }} select={selectable} multiselect={selectable} {selectedRows}
308
+ reorder={false} draggableRows={false} undo={false} {...queryProps} {...dynamicProps} init={api => init(api, 0)} />
309
+ </div>
310
+ {#if freezeRight > 0}<div class="grid-viewport right-pane" data-svar-pane="right" style:height="{height}px" style:width="{model.rightWidth}px" role="region" aria-label="Frozen right columns" onkeydowncapture={event => navigatePane(event, 1)}>
311
+ <Grid columns={model.right} data={activeError || isLoading ? [] : rightRows} tree={treeKey !== undefined}
312
+ split={{ left: 0 }} sizes={{ rowHeight, headerHeight: 36, columnWidth: 160 }} select={selectable} multiselect={selectable} {selectedRows}
313
+ reorder={false} draggableRows={false} undo={false} {...queryProps} {...dynamicProps} init={api => init(api, 1)} />
314
+ </div>{/if}
315
+ </div>{/key}
316
+ {:else if !loading && !error}
317
+ <div class="static-preview"><table><caption>{fallbackLabel}</caption>
318
+ <thead><tr>{#each model.engineColumns as column, index (column.id)}<th scope="col">{model.columns[index]?.label}</th>{/each}</tr></thead>
319
+ <tbody>{#each records.rows.slice(0, 20) as row (row.id)}<tr>{#each model.engineColumns as column (column.id)}<td>{row[column.id] ?? ''}</td>{/each}</tr>{/each}</tbody>
320
+ </table></div>
321
+ {/if}
322
+ {#if moreError}<p role="alert">{moreError}</p>{/if}
323
+ {#if onLoadMore && hasMore}<button type="button" disabled={disabled || isLoading || morePending} onclick={loadMore}>{morePending ? (locale.startsWith('zh') ? '正在加载' : 'Loading more') : (locale.startsWith('zh') ? '加载更多' : 'Load more')}</button>{/if}
324
+ {/if}
325
+ </div>
326
+ </Theme>
327
+
328
+ <style>
329
+ .svadmin-svar-grid {
330
+ min-width: 0; color: var(--foreground, CanvasText); background: var(--card, Canvas); font-family: var(--font-sans, system-ui);
331
+ --wx-font-family: var(--font-sans, system-ui); --wx-font-size: 14px; --wx-font-size-md: 14px;
332
+ --wx-color-font: var(--foreground, CanvasText); --wx-color-font-alt: var(--muted-foreground, GrayText);
333
+ --wx-color-primary: var(--primary, Highlight); --wx-color-primary-font: var(--primary-foreground, HighlightText);
334
+ --wx-background: var(--card, Canvas); --wx-background-alt: var(--muted, Canvas); --wx-background-hover: var(--accent, Canvas);
335
+ --wx-border: 1px solid var(--border, GrayText); --wx-border-medium: 1px solid var(--border, GrayText); --wx-border-radius: var(--radius, 6px);
336
+ --wx-table-border: 1px solid var(--border, GrayText); --wx-table-cell-border: var(--wx-table-border);
337
+ --wx-table-header-border: var(--wx-table-border); --wx-table-header-cell-border: var(--wx-table-border);
338
+ --wx-table-header-background: var(--muted, Canvas); --wx-table-select-background: var(--accent, Highlight);
339
+ --wx-table-select-focus-background: var(--accent, Highlight); --wx-table-select-color: var(--accent-foreground, CanvasText);
340
+ --wx-table-fixed-column-border: var(--wx-table-border); --wx-table-fixed-column-right-border: var(--wx-table-border);
341
+ --wx-input-background: var(--background, Canvas); --wx-input-font-color: var(--foreground, CanvasText); --wx-input-border: var(--wx-border);
342
+ }
343
+ .grid-panes { display: flex; min-width: 0; width: 100%; }
344
+ .grid-viewport { min-width: 0; }
345
+ .center-pane { flex: 1; }
346
+ .right-pane { flex: none; max-width: 55%; border-left: 1px solid var(--border, GrayText); box-shadow: -3px 0 6px rgb(0 0 0 / 8%); }
347
+ .grid-panes[inert] { opacity: 0.6; }
348
+ p { margin: 0; padding: 0.75rem; }
349
+ button { font: inherit; color: inherit; background: var(--card, Canvas); border: var(--wx-border); border-radius: var(--radius, 6px); padding: 0.375rem 0.75rem; cursor: pointer; }
350
+ button:disabled { cursor: not-allowed; opacity: 0.5; }
351
+ button:focus-visible { outline: 2px solid var(--ring, Highlight); outline-offset: 2px; }
352
+ [role='alert'] { color: var(--destructive, CanvasText); }
353
+ .static-preview { overflow-x: auto; }
354
+ table { border-collapse: collapse; width: 100%; }
355
+ th, td { padding: 0.5rem; text-align: start; border-bottom: var(--wx-border); }
356
+ caption { text-align: start; color: var(--muted-foreground, GrayText); }
357
+ .svadmin-svar-grid :global(.wx-table-tree-toggle), .svadmin-svar-grid :global(.wx-sort i) { font-family: system-ui; font-style: normal; }
358
+ .svadmin-svar-grid :global(.wx-sort .wxi-arrow-up)::before { content: '↑'; }
359
+ .svadmin-svar-grid :global(.wx-sort .wxi-arrow-down)::before { content: '↓'; }
360
+ .svadmin-svar-grid :global(.wx-table-tree-toggle.wxi-menu-right)::before { content: '▸'; }
361
+ .svadmin-svar-grid :global(.wx-table-tree-toggle.wxi-menu-down)::before { content: '▾'; }
362
+ </style>
@@ -0,0 +1,57 @@
1
+ import { type Component, type Snippet } from 'svelte';
2
+ import { type SvarCellContent } from './svar-grid-cells.js';
3
+ import { type SvarSort, type SvarTextFilter } from './svar-grid-contract.js';
4
+ import { type SvarInteractiveColumn, type SvarCellEdit } from './svar-grid-interactions.js';
5
+ import { type SvarRecordId } from './svar-grid-operations.js';
6
+ import { type SvarManagedEngineProps } from './svar-grid-panes.js';
7
+ import { type SvarWindowSource, type SvarChildrenLoader } from './svar-grid-loading.js';
8
+ export interface SvarDataGridProps {
9
+ Grid: Component<SvarManagedEngineProps>;
10
+ Theme: Component<{
11
+ fonts?: boolean;
12
+ children?: Snippet;
13
+ }>;
14
+ items?: readonly Record<string, unknown>[];
15
+ columns: readonly SvarInteractiveColumn[];
16
+ primaryKey?: string;
17
+ childrenKey?: string;
18
+ height?: number;
19
+ density?: 'compact' | 'comfortable';
20
+ freezeLeft?: number;
21
+ /** 独立右侧面板,不使用上游 split.right。 */
22
+ freezeRight?: number;
23
+ queryMode?: 'local' | 'server';
24
+ sorters?: readonly SvarSort[];
25
+ filters?: readonly SvarTextFilter[];
26
+ onSortChange?: (sorters: SvarSort[]) => void;
27
+ onFilterChange?: (filters: SvarTextFilter[]) => void;
28
+ onCellEdit?: (edit: SvarCellEdit) => Promise<void>;
29
+ selectable?: boolean;
30
+ selectedIds?: readonly SvarRecordId[];
31
+ onSelectionChange?: (ids: SvarRecordId[]) => void;
32
+ /** 仅可信宿主可注入的原生单元格内容。 */
33
+ cellContent?: Snippet<[SvarCellContent]>;
34
+ /** 已加载记录的独立快照,含已加载后代,不包含尚未请求的行。 */
35
+ onRecordsChange?: (records: readonly Record<string, unknown>[]) => void;
36
+ windowSource?: SvarWindowSource;
37
+ loadChildren?: SvarChildrenLoader;
38
+ hasChildrenKey?: string;
39
+ onLoadMore?: (request: {
40
+ signal: AbortSignal;
41
+ }) => Promise<void>;
42
+ hasMore?: boolean;
43
+ /** 查询、租户或会话切换必须改变此宿主版本;追加同一数据集则无需改变。 */
44
+ scopeKey?: string | number;
45
+ loading?: boolean;
46
+ error?: string;
47
+ disabled?: boolean;
48
+ locale?: string;
49
+ label?: string;
50
+ emptyLabel?: string;
51
+ loadingLabel?: string;
52
+ disabledLabel?: string;
53
+ fallbackLabel?: string;
54
+ }
55
+ declare const SvarDataGrid: Component<SvarDataGridProps, {}, "">;
56
+ type SvarDataGrid = ReturnType<typeof SvarDataGrid>;
57
+ export default SvarDataGrid;
@@ -0,0 +1,10 @@
1
+ <script lang="ts">
2
+ import { getContext } from 'svelte';
3
+ import { SVAR_CELL_CONTEXT, type SvarCellContext } from './svar-grid-cells.js';
4
+ let { row, column }: { row: unknown; column: unknown } = $props();
5
+ const host = getContext<SvarCellContext | undefined>(SVAR_CELL_CONTEXT);
6
+ const content = $derived(host?.resolve(row, column));
7
+ const render = $derived(host?.render());
8
+ </script>
9
+
10
+ {#if render && content}{@render render(content)}{/if}
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ row: unknown;
3
+ column: unknown;
4
+ };
5
+ declare const SvarGridCell: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type SvarGridCell = ReturnType<typeof SvarGridCell>;
7
+ export default SvarGridCell;
@@ -0,0 +1,50 @@
1
+ <script lang="ts">
2
+ import { onDestroy, untrack } from 'svelte';
3
+ import { useResourceContract, useList, type Filter, type Sort } from '@svadmin/core';
4
+ import { snapshotSvarRecords, SvarLoadCancelled, type SvarWindowPage } from './svar-grid-loading.js';
5
+
6
+ /** Internal worker: requests use the same contract, Provider, tenant and query cache as the resource table. */
7
+ export interface SvarResourceReadRequest {
8
+ readonly resourceName: string;
9
+ readonly dataScopeKey: string | number;
10
+ readonly primaryKey: string;
11
+ readonly childrenKey?: string;
12
+ readonly pagination: { current: number; pageSize: number };
13
+ readonly sorters: Sort[];
14
+ readonly filters: Filter[];
15
+ readonly resolve: (page: SvarWindowPage) => void;
16
+ readonly reject: (error: unknown) => void;
17
+ }
18
+
19
+ let { request }: { request: SvarResourceReadRequest } = $props();
20
+ const binding = useResourceContract(() => request.resourceName);
21
+ const query = useList({
22
+ get resource() { return binding.resource; },
23
+ get dataProviderName() { return binding.dataProviderName; },
24
+ get pagination() { return request.pagination; },
25
+ get sorters() { return request.sorters; },
26
+ get filters() { return request.filters; },
27
+ get meta() { return { svadminSvarScope: request.dataScopeKey }; },
28
+ queryOptions: { refetchOnWindowFocus: false },
29
+ });
30
+ let settled = false;
31
+ onDestroy(() => {
32
+ if (!settled) { settled = true; request.reject(new SvarLoadCancelled()); }
33
+ });
34
+ $effect(() => {
35
+ const failed = query.isError;
36
+ const failure = query.error;
37
+ const fetching = query.isFetching;
38
+ const data = query.data;
39
+ untrack(() => {
40
+ if (settled || fetching) return;
41
+ if (failed) { settled = true; request.reject(failure); return; }
42
+ if (!data) return;
43
+ try {
44
+ if (!Number.isSafeInteger(data.total) || data.total < 0 || data.total > 10_000_000) throw new Error('Invalid resource total');
45
+ const page = { data: snapshotSvarRecords(data.data, request.primaryKey, request.childrenKey), total: data.total };
46
+ settled = true; request.resolve(page);
47
+ } catch (error) { settled = true; request.reject(error); }
48
+ });
49
+ });
50
+ </script>
@@ -0,0 +1,23 @@
1
+ import { type Filter, type Sort } from '@svadmin/core';
2
+ import { type SvarWindowPage } from './svar-grid-loading.js';
3
+ /** Internal worker: requests use the same contract, Provider, tenant and query cache as the resource table. */
4
+ export interface SvarResourceReadRequest {
5
+ readonly resourceName: string;
6
+ readonly dataScopeKey: string | number;
7
+ readonly primaryKey: string;
8
+ readonly childrenKey?: string;
9
+ readonly pagination: {
10
+ current: number;
11
+ pageSize: number;
12
+ };
13
+ readonly sorters: Sort[];
14
+ readonly filters: Filter[];
15
+ readonly resolve: (page: SvarWindowPage) => void;
16
+ readonly reject: (error: unknown) => void;
17
+ }
18
+ type $$ComponentProps = {
19
+ request: SvarResourceReadRequest;
20
+ };
21
+ declare const SvarResourceRead: import("svelte").Component<$$ComponentProps, {}, "">;
22
+ type SvarResourceRead = ReturnType<typeof SvarResourceRead>;
23
+ export default SvarResourceRead;
@@ -0,0 +1,63 @@
1
+ <script lang="ts">
2
+ import { captureAdminContext, captureAuthSession, type Filter, type Sort } from '@svadmin/core';
3
+ import { useTranslation } from '@svadmin/core/i18n';
4
+ import type { ComponentProps } from 'svelte';
5
+ import SvarDataGrid from './SvarDataGrid.svelte';
6
+ import SvarResourceTableView from './SvarResourceTableView.svelte';
7
+ import type { SvarLazyTree } from './svar-grid-resource-loading.js';
8
+ import type { SvarValueFormat } from './svar-grid-interactions.js';
9
+
10
+ export interface SvarResourceTableProps {
11
+ Grid: ComponentProps<typeof SvarDataGrid>['Grid'];
12
+ Theme: ComponentProps<typeof SvarDataGrid>['Theme'];
13
+ resourceName: string;
14
+ pageSize?: number;
15
+ filters?: readonly Filter[];
16
+ initialSorters?: readonly Sort[];
17
+ height?: number;
18
+ freezeLeft?: number;
19
+ freezeRight?: number;
20
+ /** 高级读取仍使用资源契约、Provider 和核心查询缓存。 */
21
+ loadingMode?: 'page' | 'window' | 'infinite';
22
+ lazyTree?: SvarLazyTree;
23
+ density?: 'compact' | 'comfortable';
24
+ childrenKey?: string;
25
+ dataScopeKey?: string | number;
26
+ disabled?: boolean;
27
+ /** 所有写入能力默认关闭,必须由可信宿主开启。 */
28
+ editable?: boolean;
29
+ selectable?: boolean;
30
+ batchUpdate?: boolean;
31
+ batchDelete?: boolean;
32
+ exportable?: boolean;
33
+ savedViews?: boolean;
34
+ /** 非敏感的用户/租户偏好分区;省略时只保存在本次挂载的内存。 */
35
+ preferenceScopeKey?: string;
36
+ /** 显式导入 AutoTable 的作用域视图,不修改原视图。 */
37
+ migrateAutoTableViews?: boolean;
38
+ deleteVariables?: unknown;
39
+ formats?: Readonly<Record<string, { format: SvarValueFormat; currency?: string }>>;
40
+ }
41
+ let props: SvarResourceTableProps = $props();
42
+ const context = captureAdminContext();
43
+ const i18n = useTranslation();
44
+ const owner = $derived.by(() => {
45
+ const auth = captureAuthSession(context.authProvider);
46
+ return {
47
+ provider: context.getDataProviderForResource(props.resourceName),
48
+ resourceName: props.resourceName, contract: context.getResource(props.resourceName).contract,
49
+ tenant: context.tenantCacheKey?.__svadminTenant,
50
+ authKey: auth.cacheKey, available: auth.available,
51
+ access: context.accessControlProvider, dataScopeKey: props.dataScopeKey,
52
+ preferenceScopeKey: props.preferenceScopeKey,
53
+ };
54
+ });
55
+ </script>
56
+
57
+ {#key owner}
58
+ {#if owner.available}
59
+ <SvarResourceTableView {...props} />
60
+ {:else}
61
+ <p role="status">{i18n.locale.startsWith('zh') ? '当前会话不可读取数据' : 'Data is unavailable for the current session'}</p>
62
+ {/if}
63
+ {/key}
@@ -0,0 +1,42 @@
1
+ import { type Filter, type Sort } from '@svadmin/core';
2
+ import type { ComponentProps } from 'svelte';
3
+ import SvarDataGrid from './SvarDataGrid.svelte';
4
+ import type { SvarLazyTree } from './svar-grid-resource-loading.js';
5
+ import type { SvarValueFormat } from './svar-grid-interactions.js';
6
+ export interface SvarResourceTableProps {
7
+ Grid: ComponentProps<typeof SvarDataGrid>['Grid'];
8
+ Theme: ComponentProps<typeof SvarDataGrid>['Theme'];
9
+ resourceName: string;
10
+ pageSize?: number;
11
+ filters?: readonly Filter[];
12
+ initialSorters?: readonly Sort[];
13
+ height?: number;
14
+ freezeLeft?: number;
15
+ freezeRight?: number;
16
+ /** 高级读取仍使用资源契约、Provider 和核心查询缓存。 */
17
+ loadingMode?: 'page' | 'window' | 'infinite';
18
+ lazyTree?: SvarLazyTree;
19
+ density?: 'compact' | 'comfortable';
20
+ childrenKey?: string;
21
+ dataScopeKey?: string | number;
22
+ disabled?: boolean;
23
+ /** 所有写入能力默认关闭,必须由可信宿主开启。 */
24
+ editable?: boolean;
25
+ selectable?: boolean;
26
+ batchUpdate?: boolean;
27
+ batchDelete?: boolean;
28
+ exportable?: boolean;
29
+ savedViews?: boolean;
30
+ /** 非敏感的用户/租户偏好分区;省略时只保存在本次挂载的内存。 */
31
+ preferenceScopeKey?: string;
32
+ /** 显式导入 AutoTable 的作用域视图,不修改原视图。 */
33
+ migrateAutoTableViews?: boolean;
34
+ deleteVariables?: unknown;
35
+ formats?: Readonly<Record<string, {
36
+ format: SvarValueFormat;
37
+ currency?: string;
38
+ }>>;
39
+ }
40
+ declare const SvarResourceTable: import("svelte").Component<SvarResourceTableProps, {}, "">;
41
+ type SvarResourceTable = ReturnType<typeof SvarResourceTable>;
42
+ export default SvarResourceTable;