@svadmin/ui 0.72.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.
- package/README.md +62 -22
- package/THIRD_PARTY_NOTICES.md +52 -0
- package/dist/app.css +8488 -6297
- package/dist/app.theme.css +8514 -6374
- package/dist/class-properties.generated.d.ts +1 -0
- package/dist/class-properties.generated.js +2 -0
- package/dist/classnames.d.ts +8 -0
- package/dist/classnames.js +26 -0
- package/dist/component-registry.d.ts +14977 -0
- package/dist/component-registry.js +216 -0
- package/dist/components/AboutSettings.svelte +7 -1
- package/dist/components/AboutSettings.svelte.d.ts +4 -1
- package/dist/components/ActivityFeed.svelte +9 -3
- package/dist/components/ActivityFeed.svelte.d.ts +3 -0
- package/dist/components/AdminApp.svelte +19 -10
- package/dist/components/AdminApp.svelte.d.ts +6 -0
- package/dist/components/ApiSettings.svelte +41 -5
- package/dist/components/ApprovalCenter.svelte +283 -0
- package/dist/components/ApprovalCenter.svelte.d.ts +10 -0
- package/dist/components/AuditLogViewer.svelte +3 -1
- package/dist/components/AutoForm.svelte +53 -9
- package/dist/components/AutoForm.svelte.d.ts +3 -0
- package/dist/components/AutoTable.svelte +846 -43
- package/dist/components/AutoTable.svelte.d.ts +22 -1
- package/dist/components/BoundRecordDetailDrawer.svelte +17 -2
- package/dist/components/BoundRecordDetailDrawer.svelte.d.ts +4 -0
- package/dist/components/Breadcrumbs.svelte +11 -1
- package/dist/components/CancelTaskButton.svelte +12 -42
- package/dist/components/ColorPicker.svelte +73 -0
- package/dist/components/ColorPicker.svelte.d.ts +15 -0
- package/dist/components/ComboboxField.svelte +143 -34
- package/dist/components/ComboboxField.svelte.d.ts +4 -2
- package/dist/components/CreatePage.svelte +4 -0
- package/dist/components/CreatePage.svelte.d.ts +2 -0
- package/dist/components/DashboardView.svelte +99 -0
- package/dist/components/DashboardView.svelte.d.ts +14 -0
- package/dist/components/DatePicker.svelte +10 -0
- package/dist/components/DatePicker.svelte.d.ts +6 -0
- package/dist/components/DateRangeInput.svelte +155 -0
- package/dist/components/DateRangeInput.svelte.d.ts +35 -0
- package/dist/components/DateRangePicker.svelte +10 -0
- package/dist/components/DateRangePicker.svelte.d.ts +6 -0
- package/dist/components/DateTimeInput.svelte +134 -0
- package/dist/components/DateTimeInput.svelte.d.ts +27 -0
- package/dist/components/DateTimePicker.svelte +10 -0
- package/dist/components/DateTimePicker.svelte.d.ts +6 -0
- package/dist/components/DevTools.svelte +36 -28
- package/dist/components/DevframePanel.svelte +123 -0
- package/dist/components/DevframePanel.svelte.d.ts +3 -0
- package/dist/components/EditPage.svelte +4 -0
- package/dist/components/EditPage.svelte.d.ts +2 -0
- package/dist/components/ErrorSummary.svelte +52 -0
- package/dist/components/ErrorSummary.svelte.d.ts +14 -0
- package/dist/components/FieldRenderer.svelte +172 -99
- package/dist/components/FileUpload.svelte +412 -0
- package/dist/components/FileUpload.svelte.d.ts +43 -0
- package/dist/components/FilterBuilder.svelte +244 -265
- package/dist/components/FilterBuilder.svelte.d.ts +3 -0
- package/dist/components/FilterCollectionInput.svelte +80 -0
- package/dist/components/FilterCollectionInput.svelte.d.ts +10 -0
- package/dist/components/GanttChart.svelte +66 -14
- package/dist/components/GanttChart.svelte.d.ts +6 -0
- package/dist/components/Header.svelte +18 -2
- package/dist/components/ImportWizard.svelte +509 -100
- package/dist/components/ImportWizard.svelte.d.ts +11 -0
- package/dist/components/JsonSchemaForm.svelte +322 -98
- package/dist/components/JsonSchemaForm.svelte.d.ts +18 -1
- package/dist/components/KanbanBoard.svelte +195 -40
- package/dist/components/KanbanBoard.svelte.d.ts +9 -2
- package/dist/components/Layout.svelte +4 -3
- package/dist/components/LazyAutoForm.svelte +28 -0
- package/dist/components/LazyAutoForm.svelte.d.ts +8 -0
- package/dist/components/LazyAutoTable.svelte +28 -0
- package/dist/components/LazyAutoTable.svelte.d.ts +8 -0
- package/dist/components/LazySettingsPage.svelte +28 -0
- package/dist/components/LazySettingsPage.svelte.d.ts +8 -0
- package/dist/components/LazyShowPage.svelte +28 -0
- package/dist/components/LazyShowPage.svelte.d.ts +8 -0
- package/dist/components/LazyTaskQueueDrawer.svelte.d.ts +1 -1
- package/dist/components/ListPage.svelte +9 -1
- package/dist/components/ListPage.svelte.d.ts +5 -0
- package/dist/components/LoginPage.svelte +2 -2
- package/dist/components/MoneyInput.svelte +25 -0
- package/dist/components/MoneyInput.svelte.d.ts +20 -0
- package/dist/components/MultiTabKeepAlive.svelte +11 -6
- package/dist/components/NotificationsSettings.svelte +69 -2
- package/dist/components/NotificationsSettings.svelte.d.ts +25 -1
- package/dist/components/NumberInput.svelte +25 -0
- package/dist/components/NumberInput.svelte.d.ts +19 -0
- package/dist/components/NumericInput.svelte +110 -0
- package/dist/components/NumericInput.svelte.d.ts +23 -0
- package/dist/components/PercentInput.svelte +25 -0
- package/dist/components/PercentInput.svelte.d.ts +20 -0
- package/dist/components/PermissionMatrix.svelte +20 -1
- package/dist/components/PivotExportButton.svelte +157 -0
- package/dist/components/PivotExportButton.svelte.d.ts +16 -0
- package/dist/components/PivotTable.svelte +229 -74
- package/dist/components/PivotTable.svelte.d.ts +19 -2
- package/dist/components/QuickEditDrawer.svelte +4 -1
- package/dist/components/QuickEditDrawer.svelte.d.ts +2 -0
- package/dist/components/RadioGroup.svelte +121 -0
- package/dist/components/RadioGroup.svelte.d.ts +43 -0
- package/dist/components/RangeSlider.svelte +127 -0
- package/dist/components/RangeSlider.svelte.d.ts +22 -0
- package/dist/components/Rate.svelte +121 -0
- package/dist/components/Rate.svelte.d.ts +16 -0
- package/dist/components/RecordDetailDrawer.svelte +6 -2
- package/dist/components/RecordDetailDrawer.svelte.d.ts +4 -0
- package/dist/components/RelationPicker.svelte +341 -0
- package/dist/components/RelationPicker.svelte.d.ts +26 -0
- package/dist/components/ResourceOperationsPage.svelte +25 -19
- package/dist/components/ResourceOperationsPage.svelte.d.ts +5 -1
- package/dist/components/RetryTaskButton.svelte +12 -42
- package/dist/components/SecuritySettings.svelte +29 -5
- package/dist/components/SegmentedControl.svelte +145 -0
- package/dist/components/SegmentedControl.svelte.d.ts +38 -0
- package/dist/components/SettingsPage.svelte +3 -1
- package/dist/components/SettingsPage.svelte.d.ts +1 -0
- package/dist/components/ShowInferencer.svelte +4 -2
- package/dist/components/ShowInferencer.svelte.d.ts +2 -0
- package/dist/components/ShowPage.svelte +25 -6
- package/dist/components/ShowPage.svelte.d.ts +5 -0
- package/dist/components/Sidebar.svelte +5 -5
- package/dist/components/Slider.svelte +100 -0
- package/dist/components/Slider.svelte.d.ts +19 -0
- package/dist/components/SpreadsheetView.svelte +1037 -124
- package/dist/components/SpreadsheetView.svelte.d.ts +19 -0
- package/dist/components/StepsForm.svelte +5 -5
- package/dist/components/SvarAutoTable.svelte +39 -0
- package/dist/components/SvarAutoTable.svelte.d.ts +13 -0
- package/dist/components/SvarDataGrid.svelte +362 -0
- package/dist/components/SvarDataGrid.svelte.d.ts +57 -0
- package/dist/components/SvarGridCell.svelte +10 -0
- package/dist/components/SvarGridCell.svelte.d.ts +7 -0
- package/dist/components/SvarResourceRead.svelte +50 -0
- package/dist/components/SvarResourceRead.svelte.d.ts +23 -0
- package/dist/components/SvarResourceTable.svelte +63 -0
- package/dist/components/SvarResourceTable.svelte.d.ts +42 -0
- package/dist/components/SvarResourceTableView.svelte +364 -0
- package/dist/components/SvarResourceTableView.svelte.d.ts +4 -0
- package/dist/components/TagsInput.svelte +178 -0
- package/dist/components/TagsInput.svelte.d.ts +17 -0
- package/dist/components/TaskQueueDrawer.svelte +72 -16
- package/dist/components/TimePicker.svelte +10 -0
- package/dist/components/TimePicker.svelte.d.ts +6 -0
- package/dist/components/TreeSelect.svelte +440 -46
- package/dist/components/TreeSelect.svelte.d.ts +6 -1
- package/dist/components/TreeTable.svelte +484 -82
- package/dist/components/TreeTable.svelte.d.ts +24 -1
- package/dist/components/TwoFactorAuthPage.svelte +85 -48
- package/dist/components/TwoFactorAuthPage.svelte.d.ts +12 -1
- package/dist/components/VirtualTable.svelte +116 -20
- package/dist/components/VirtualTable.svelte.d.ts +5 -0
- package/dist/components/account/CompanyProfilePage.svelte +74 -38
- package/dist/components/account/GetStartedPage.svelte +2 -1
- package/dist/components/account/ImportMembersPage.svelte +22 -167
- package/dist/components/account/ImportMembersPage.svelte.d.ts +4 -1
- package/dist/components/account/MemberDirectory.svelte +156 -0
- package/dist/components/account/MemberDirectory.svelte.d.ts +19 -0
- package/dist/components/account/MembersStarterPage.svelte +9 -55
- package/dist/components/account/MembersStarterPage.svelte.d.ts +5 -1
- package/dist/components/account/SecurityLogPage.svelte +4 -1
- package/dist/components/account/SettingsPlainPage.svelte +7 -38
- package/dist/components/account/SettingsPlainPage.svelte.d.ts +4 -1
- package/dist/components/account/SettingsSidebarPage.svelte +7 -20
- package/dist/components/account/TeamMembersPage.svelte +4 -20
- package/dist/components/account/TeamMembersPage.svelte.d.ts +5 -1
- package/dist/components/account/UserProfilePage.svelte +3 -25
- package/dist/components/account/UserProfilePage.svelte.d.ts +17 -2
- package/dist/components/auto-table-grid.d.ts +26 -0
- package/dist/components/auto-table-grid.js +1 -0
- package/dist/components/buttons/ExportButton.svelte +56 -12
- package/dist/components/buttons/ExportButton.svelte.d.ts +11 -0
- package/dist/components/content/ContentPageHeader.svelte +12 -9
- package/dist/components/content/ContentPageShell.svelte +6 -9
- package/dist/components/content/FileBrowser.svelte +35 -0
- package/dist/components/content/FileBrowser.svelte.d.ts +14 -0
- package/dist/components/content/MetricBlock.svelte +10 -13
- package/dist/components/content/NetworkTable.svelte +3 -3
- package/dist/components/content/NetworkTable.svelte.d.ts +1 -0
- package/dist/components/content/PageToolbar.svelte +6 -10
- package/dist/components/content/ReferenceNotice.svelte +5 -0
- package/dist/components/content/ReferenceNotice.svelte.d.ts +18 -0
- package/dist/components/content/SectionHeader.svelte +8 -13
- package/dist/components/content/SettingsFieldRow.svelte +9 -23
- package/dist/components/content/SettingsGroup.svelte +11 -27
- package/dist/components/content/StatusBadge.svelte +3 -8
- package/dist/components/content/WorkspaceLayout.svelte +8 -12
- package/dist/components/enterprise/filter-tree.d.ts +48 -0
- package/dist/components/enterprise/filter-tree.js +224 -0
- package/dist/components/enterprise/json-schema-form.d.ts +39 -0
- package/dist/components/enterprise/json-schema-form.js +195 -0
- package/dist/components/enterprise/recursive-schema-form.d.ts +30 -0
- package/dist/components/enterprise/recursive-schema-form.js +196 -0
- package/dist/components/enterprise/spreadsheet-formula.d.ts +12 -0
- package/dist/components/enterprise/spreadsheet-formula.js +245 -0
- package/dist/components/export-reference.d.ts +2 -0
- package/dist/components/export-reference.js +9 -0
- package/dist/components/fieldComponentMap.js +2 -0
- package/dist/components/fields/DateField.svelte +12 -12
- package/dist/components/fields/date-display.d.ts +6 -0
- package/dist/components/fields/date-display.js +24 -0
- package/dist/components/fields/date-field-value.d.ts +2 -0
- package/dist/components/fields/date-field-value.js +13 -0
- package/dist/components/json-schema-form-state.d.ts +37 -0
- package/dist/components/json-schema-form-state.js +168 -0
- package/dist/components/network/TeamCrewTablePage.svelte +6 -1
- package/dist/components/network/UserCardsNFTPage.svelte +2 -0
- package/dist/components/profile/PublicProfilePage.svelte +2 -0
- package/dist/components/record-detail.svelte.d.ts +3 -0
- package/dist/components/record-detail.svelte.js +10 -4
- package/dist/components/saved-list-views.d.ts +74 -1
- package/dist/components/saved-list-views.js +191 -3
- package/dist/components/svar-grid-cells.d.ts +14 -0
- package/dist/components/svar-grid-cells.js +2 -0
- package/dist/components/svar-grid-contract.d.ts +80 -0
- package/dist/components/svar-grid-contract.js +159 -0
- package/dist/components/svar-grid-interactions.d.ts +54 -0
- package/dist/components/svar-grid-interactions.js +174 -0
- package/dist/components/svar-grid-loading.d.ts +40 -0
- package/dist/components/svar-grid-loading.js +180 -0
- package/dist/components/svar-grid-operations.d.ts +37 -0
- package/dist/components/svar-grid-operations.js +186 -0
- package/dist/components/svar-grid-panes.d.ts +34 -0
- package/dist/components/svar-grid-panes.js +66 -0
- package/dist/components/svar-grid-resource-loading.d.ts +18 -0
- package/dist/components/svar-grid-resource-loading.js +68 -0
- package/dist/components/svar-grid-resource-session.svelte.d.ts +29 -0
- package/dist/components/svar-grid-resource-session.svelte.js +61 -0
- package/dist/components/table-contract.d.ts +12 -1
- package/dist/components/task-action.svelte.d.ts +13 -0
- package/dist/components/task-action.svelte.js +73 -0
- package/dist/components/ui/DesignSystemShowcase.svelte +164 -0
- package/dist/components/ui/DesignSystemShowcase.svelte.d.ts +3 -0
- package/dist/components/ui/badge/badge-variants.d.ts +3 -2
- package/dist/components/ui/badge/badge-variants.js +2 -1
- package/dist/components/ui/badge/badge.svelte +3 -0
- package/dist/components/ui/badge/badge.svelte.d.ts +1 -0
- package/dist/components/ui/button/button-variants.d.ts +5 -4
- package/dist/components/ui/button/button-variants.js +7 -2
- package/dist/components/ui/checkbox/checkbox.svelte +5 -1
- package/dist/components/ui/checkbox/checkbox.svelte.d.ts +5 -1
- package/dist/components/ui/input/input.svelte +8 -6
- package/dist/components/ui/input/input.svelte.d.ts +4 -1
- package/dist/components/ui/select/select.svelte +4 -1
- package/dist/components/ui/select/select.svelte.d.ts +2 -1
- package/dist/components/ui/textarea/textarea.svelte +2 -1
- package/dist/components.css +3110 -0
- package/dist/date-input-policy.d.ts +4 -0
- package/dist/date-input-policy.js +35 -0
- package/dist/design-contract.d.ts +13 -0
- package/dist/design-contract.js +11 -0
- package/dist/design-system-components.d.ts +100 -0
- package/dist/design-system-components.js +101 -0
- package/dist/design-system.d.ts +15 -0
- package/dist/design-system.js +7 -0
- package/dist/devtools-bridge.d.ts +80 -0
- package/dist/devtools-bridge.js +92 -0
- package/dist/index.d.ts +50 -1
- package/dist/index.js +32 -2
- package/dist/page-chrome.css +12 -49
- package/dist/recipes.d.ts +960 -0
- package/dist/recipes.js +264 -0
- package/dist/rendering/index.d.ts +87 -0
- package/dist/rendering/index.js +117 -0
- package/dist/styles/aliases.css +49 -0
- package/dist/styles/compatibility.epilogue.css +384 -0
- package/dist/styles/compatibility.prelude.css +5513 -0
- package/dist/styles/conditional-states.css +28 -0
- package/dist/styles/conditional-utilities.css +22 -0
- package/dist/tree-table-model.d.ts +24 -0
- package/dist/tree-table-model.js +111 -0
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +1 -1
- package/package.json +40 -10
- package/styles-compatibility.json +12 -0
|
@@ -1,16 +1,35 @@
|
|
|
1
|
+
import { type SpreadsheetCellFormat, type SpreadsheetWorkbook } from '@svadmin/core';
|
|
1
2
|
export interface SheetData {
|
|
2
3
|
id: string;
|
|
3
4
|
name: string;
|
|
4
5
|
rows: number;
|
|
5
6
|
cols: number;
|
|
6
7
|
cells: Record<string, string>;
|
|
8
|
+
formats?: Record<string, SpreadsheetCellFormat>;
|
|
9
|
+
}
|
|
10
|
+
export interface SpreadsheetLimits {
|
|
11
|
+
maxRows: number;
|
|
12
|
+
maxCols: number;
|
|
13
|
+
maxCells: number;
|
|
14
|
+
maxSheets: number;
|
|
15
|
+
maxCellLength: number;
|
|
16
|
+
maxFormulaLength: number;
|
|
17
|
+
maxFormulaDepth: number;
|
|
18
|
+
maxRangeCells: number;
|
|
19
|
+
maxFormulaSteps: number;
|
|
7
20
|
}
|
|
8
21
|
interface Props {
|
|
9
22
|
sheets?: SheetData[];
|
|
10
23
|
activeSheetId?: string;
|
|
24
|
+
scopeKey?: string;
|
|
11
25
|
readonly?: boolean;
|
|
12
26
|
onchange?: (sheets: SheetData[]) => void;
|
|
13
27
|
onexport?: (csv: string) => void;
|
|
28
|
+
onworkbookexport?: (json: string) => void;
|
|
29
|
+
onxlsxexport?: (bytes: Uint8Array) => void | Promise<void>;
|
|
30
|
+
xlsxexporter?: (workbook: SpreadsheetWorkbook) => Promise<Uint8Array | undefined>;
|
|
31
|
+
xlsximporter?: (bytes: Uint8Array, limits: SpreadsheetLimits, signal: AbortSignal) => Promise<unknown>;
|
|
32
|
+
limits?: Partial<SpreadsheetLimits>;
|
|
14
33
|
class?: string;
|
|
15
34
|
}
|
|
16
35
|
declare const SpreadsheetView: import("svelte").Component<Props, {}, "sheets" | "activeSheetId">;
|
|
@@ -229,10 +229,10 @@
|
|
|
229
229
|
>
|
|
230
230
|
<span
|
|
231
231
|
class="svadmin-u-60fbb7713999 svadmin-u-d0a52b312f7d svadmin-u-cbbf90f9a828 svadmin-u-3960ffc248d9 svadmin-u-86843cf1e227 svadmin-u-ac204c108886 svadmin-u-359090c2d529 svadmin-u-2689f3958069 svadmin-u-ceb69a6b0e5f"
|
|
232
|
-
class:
|
|
233
|
-
class:
|
|
234
|
-
class:
|
|
235
|
-
class:
|
|
232
|
+
class:svadmin-u-75b1bec3ea0e={i <= form.steps.currentStep}
|
|
233
|
+
class:svadmin-u-30ca335ae9c2={i <= form.steps.currentStep}
|
|
234
|
+
class:svadmin-u-2ef11f1cb219={i > form.steps.currentStep}
|
|
235
|
+
class:svadmin-u-bfa603190748={i > form.steps.currentStep}
|
|
236
236
|
>
|
|
237
237
|
{#if i < form.steps.currentStep}
|
|
238
238
|
<Check class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" />
|
|
@@ -243,7 +243,7 @@
|
|
|
243
243
|
<span class="svadmin-u-99d72c7fc3e2 svadmin-u-ee3c1259a368">{step.title}</span>
|
|
244
244
|
</Button>
|
|
245
245
|
{#if i < steps.length - 1}
|
|
246
|
-
<div class="svadmin-u-36e579c0b41c svadmin-u-9e10c04e17bb svadmin-u-aea6160836e7 svadmin-u-a59afa8d9b9d" class:
|
|
246
|
+
<div class="svadmin-u-36e579c0b41c svadmin-u-9e10c04e17bb svadmin-u-aea6160836e7 svadmin-u-a59afa8d9b9d" class:svadmin-u-75b1bec3ea0e={i < form.steps.currentStep}></div>
|
|
247
247
|
{/if}
|
|
248
248
|
{/each}
|
|
249
249
|
</div>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ComponentProps } from 'svelte';
|
|
3
|
+
import { captureAdminContext, captureAuthSession } from '@svadmin/core';
|
|
4
|
+
import AutoTable from './AutoTable.svelte';
|
|
5
|
+
import SvarDataGrid from './SvarDataGrid.svelte';
|
|
6
|
+
export type SvarAutoTableProps = Omit<ComponentProps<typeof AutoTable>, 'gridBody'> & {
|
|
7
|
+
Grid: ComponentProps<typeof SvarDataGrid>['Grid'];
|
|
8
|
+
Theme: ComponentProps<typeof SvarDataGrid>['Theme'];
|
|
9
|
+
height?: number;
|
|
10
|
+
freezeLeft?: number;
|
|
11
|
+
freezeRight?: number;
|
|
12
|
+
};
|
|
13
|
+
let { Grid, Theme, height = 420, freezeLeft = 0, freezeRight = 0, ...props }: SvarAutoTableProps = $props();
|
|
14
|
+
const context = captureAdminContext();
|
|
15
|
+
const owner = $derived.by(() => {
|
|
16
|
+
const auth = captureAuthSession(context.authProvider);
|
|
17
|
+
return { resource: props.resourceName, contract: context.getResource(props.resourceName).contract,
|
|
18
|
+
provider: context.getDataProviderForResource(props.resourceName), access: context.accessControlProvider,
|
|
19
|
+
tenant: context.tenantCacheKey?.__svadminTenant, authKey: auth.cacheKey, available: auth.available };
|
|
20
|
+
});
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
{#key owner}
|
|
24
|
+
{#if owner.available}
|
|
25
|
+
<AutoTable {...props}>
|
|
26
|
+
{#snippet gridBody(state)}
|
|
27
|
+
<details><summary>{state.locale.startsWith('zh') ? '表格选择与列顺序' : 'Selection and column order'}</summary>{@render state.controls()}</details>
|
|
28
|
+
<SvarDataGrid {Grid} {Theme} items={state.records} columns={state.columns} primaryKey={state.primaryKey}
|
|
29
|
+
{height} freezeLeft={Math.min(freezeLeft, state.columns.length - Math.min(freezeRight, state.columns.length - 1))}
|
|
30
|
+
freezeRight={Math.min(freezeRight, state.columns.length - 1)} density={state.density}
|
|
31
|
+
queryMode="server" sorters={state.sorters} onSortChange={state.onSortChange}
|
|
32
|
+
disabled={state.disabled} scopeKey={state.scopeKey} locale={state.locale} label={state.label}>
|
|
33
|
+
{#snippet cellContent({ id, field })}{@render state.cell({ id, field })}{/snippet}
|
|
34
|
+
</SvarDataGrid>
|
|
35
|
+
{@render state.after()}
|
|
36
|
+
{/snippet}
|
|
37
|
+
</AutoTable>
|
|
38
|
+
{/if}
|
|
39
|
+
{/key}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ComponentProps } from 'svelte';
|
|
2
|
+
import AutoTable from './AutoTable.svelte';
|
|
3
|
+
import SvarDataGrid from './SvarDataGrid.svelte';
|
|
4
|
+
export type SvarAutoTableProps = Omit<ComponentProps<typeof AutoTable>, 'gridBody'> & {
|
|
5
|
+
Grid: ComponentProps<typeof SvarDataGrid>['Grid'];
|
|
6
|
+
Theme: ComponentProps<typeof SvarDataGrid>['Theme'];
|
|
7
|
+
height?: number;
|
|
8
|
+
freezeLeft?: number;
|
|
9
|
+
freezeRight?: number;
|
|
10
|
+
};
|
|
11
|
+
declare const SvarAutoTable: import("svelte").Component<SvarAutoTableProps, {}, "">;
|
|
12
|
+
type SvarAutoTable = ReturnType<typeof SvarAutoTable>;
|
|
13
|
+
export default SvarAutoTable;
|
|
@@ -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,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;
|