@svadmin/ui 0.73.0 → 0.74.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -28
- package/dist/app.css +3733 -4119
- package/dist/app.theme.css +3733 -4119
- 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 +33 -3
- package/dist/components/AutoForm.svelte.d.ts +1 -0
- package/dist/components/AutoTable.svelte +610 -47
- package/dist/components/AutoTable.svelte.d.ts +17 -1
- package/dist/components/BoundRecordDetailDrawer.svelte +9 -0
- package/dist/components/BoundRecordDetailDrawer.svelte.d.ts +2 -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 +114 -27
- package/dist/components/ComboboxField.svelte.d.ts +1 -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 +85 -5
- package/dist/components/DateRangeInput.svelte.d.ts +10 -0
- package/dist/components/DateRangePicker.svelte +10 -0
- package/dist/components/DateRangePicker.svelte.d.ts +6 -0
- package/dist/components/DateTimeInput.svelte +74 -5
- package/dist/components/DateTimeInput.svelte.d.ts +8 -0
- package/dist/components/DateTimePicker.svelte +10 -0
- package/dist/components/DateTimePicker.svelte.d.ts +6 -0
- package/dist/components/DevTools.svelte +34 -26
- package/dist/components/DevframePanel.svelte +123 -0
- package/dist/components/DevframePanel.svelte.d.ts +3 -0
- package/dist/components/ErrorSummary.svelte +52 -0
- package/dist/components/ErrorSummary.svelte.d.ts +14 -0
- package/dist/components/FieldRenderer.svelte +82 -81
- package/dist/components/FileUpload.svelte +214 -52
- package/dist/components/FileUpload.svelte.d.ts +11 -0
- package/dist/components/FilterBuilder.svelte +88 -66
- 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 +339 -17
- package/dist/components/ImportWizard.svelte.d.ts +11 -0
- package/dist/components/JsonSchemaForm.svelte +120 -52
- package/dist/components/JsonSchemaForm.svelte.d.ts +7 -1
- package/dist/components/KanbanBoard.svelte +195 -40
- package/dist/components/KanbanBoard.svelte.d.ts +9 -2
- package/dist/components/Layout.svelte +4 -2
- 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/ListPage.svelte +5 -1
- package/dist/components/ListPage.svelte.d.ts +3 -0
- package/dist/components/LoginPage.svelte +1 -1
- 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/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/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 +4 -2
- package/dist/components/RecordDetailDrawer.svelte.d.ts +2 -0
- package/dist/components/RelationPicker.svelte +341 -0
- package/dist/components/RelationPicker.svelte.d.ts +26 -0
- package/dist/components/ResourceOperationsPage.svelte +18 -18
- package/dist/components/ResourceOperationsPage.svelte.d.ts +2 -2
- 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/ShowPage.svelte +18 -3
- package/dist/components/ShowPage.svelte.d.ts +3 -0
- package/dist/components/Sidebar.svelte +2 -2
- package/dist/components/Slider.svelte +100 -0
- package/dist/components/Slider.svelte.d.ts +19 -0
- package/dist/components/SpreadsheetView.svelte +1213 -95
- package/dist/components/SpreadsheetView.svelte.d.ts +19 -0
- package/dist/components/TagsInput.svelte +178 -0
- package/dist/components/TagsInput.svelte.d.ts +17 -0
- package/dist/components/TimePicker.svelte +10 -0
- package/dist/components/TimePicker.svelte.d.ts +6 -0
- package/dist/components/TreeSelect.svelte +267 -53
- package/dist/components/TreeSelect.svelte.d.ts +3 -0
- 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/buttons/ExportButton.svelte +56 -12
- package/dist/components/buttons/ExportButton.svelte.d.ts +11 -0
- package/dist/components/content/ContentPageHeader.svelte +1 -1
- package/dist/components/content/ContentPageShell.svelte +1 -1
- package/dist/components/content/MetricBlock.svelte +1 -1
- package/dist/components/content/PageToolbar.svelte +1 -1
- package/dist/components/content/ReferenceNotice.svelte +5 -0
- package/dist/components/content/ReferenceNotice.svelte.d.ts +18 -0
- package/dist/components/content/SectionHeader.svelte +1 -1
- package/dist/components/content/SettingsFieldRow.svelte +1 -1
- package/dist/components/content/SettingsGroup.svelte +1 -1
- package/dist/components/content/StatusBadge.svelte +1 -1
- package/dist/components/content/WorkspaceLayout.svelte +1 -1
- package/dist/components/export-reference.d.ts +2 -0
- package/dist/components/export-reference.js +9 -0
- package/dist/components/fieldComponentMap.js +2 -5
- package/dist/components/fields/DateField.svelte +12 -13
- package/dist/components/fields/date-display.d.ts +6 -0
- package/dist/components/fields/date-display.js +24 -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/saved-list-views.d.ts +74 -1
- package/dist/components/saved-list-views.js +191 -3
- 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 -3
- 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 -5
- 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 +7 -2
- 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 -2
- package/dist/components.css +33 -2
- package/dist/date-input-policy.d.ts +4 -0
- package/dist/date-input-policy.js +35 -0
- package/dist/design-system-components.d.ts +100 -0
- package/dist/design-system-components.js +101 -0
- package/dist/design-system.js +1 -1
- package/dist/devtools-bridge.d.ts +80 -0
- package/dist/devtools-bridge.js +92 -0
- package/dist/index.d.ts +37 -2
- package/dist/index.js +23 -2
- package/dist/recipes.d.ts +960 -4
- package/dist/recipes.js +264 -4
- package/dist/tree-table-model.d.ts +24 -0
- package/dist/tree-table-model.js +111 -0
- package/package.json +22 -10
- package/dist/styled-system/css/conditions.js +0 -36
- package/dist/styled-system/css/css.d.ts +0 -22
- package/dist/styled-system/css/css.js +0 -45
- package/dist/styled-system/css/cva.d.ts +0 -6
- package/dist/styled-system/css/cva.js +0 -87
- package/dist/styled-system/css/cx.d.ts +0 -5
- package/dist/styled-system/css/cx.js +0 -15
- package/dist/styled-system/css/index.d.ts +0 -5
- package/dist/styled-system/css/index.js +0 -4
- package/dist/styled-system/css/sva.d.ts +0 -4
- package/dist/styled-system/css/sva.js +0 -46
- package/dist/styled-system/helpers.js +0 -316
- package/dist/styled-system/patterns/aspect-ratio.d.ts +0 -20
- package/dist/styled-system/patterns/aspect-ratio.js +0 -38
- package/dist/styled-system/patterns/bleed.d.ts +0 -21
- package/dist/styled-system/patterns/bleed.js +0 -24
- package/dist/styled-system/patterns/box.d.ts +0 -20
- package/dist/styled-system/patterns/box.js +0 -15
- package/dist/styled-system/patterns/center.d.ts +0 -20
- package/dist/styled-system/patterns/center.js +0 -21
- package/dist/styled-system/patterns/circle.d.ts +0 -20
- package/dist/styled-system/patterns/circle.js +0 -25
- package/dist/styled-system/patterns/container.d.ts +0 -20
- package/dist/styled-system/patterns/container.js +0 -21
- package/dist/styled-system/patterns/cq.d.ts +0 -21
- package/dist/styled-system/patterns/cq.js +0 -21
- package/dist/styled-system/patterns/divider.d.ts +0 -22
- package/dist/styled-system/patterns/divider.js +0 -25
- package/dist/styled-system/patterns/flex.d.ts +0 -26
- package/dist/styled-system/patterns/flex.js +0 -26
- package/dist/styled-system/patterns/float.d.ts +0 -23
- package/dist/styled-system/patterns/float.js +0 -52
- package/dist/styled-system/patterns/grid-item.d.ts +0 -25
- package/dist/styled-system/patterns/grid-item.js +0 -25
- package/dist/styled-system/patterns/grid.d.ts +0 -24
- package/dist/styled-system/patterns/grid.js +0 -27
- package/dist/styled-system/patterns/hstack.d.ts +0 -21
- package/dist/styled-system/patterns/hstack.js +0 -24
- package/dist/styled-system/patterns/index.d.ts +0 -21
- package/dist/styled-system/patterns/index.js +0 -20
- package/dist/styled-system/patterns/link-overlay.d.ts +0 -20
- package/dist/styled-system/patterns/link-overlay.js +0 -24
- package/dist/styled-system/patterns/spacer.d.ts +0 -20
- package/dist/styled-system/patterns/spacer.js +0 -25
- package/dist/styled-system/patterns/square.d.ts +0 -20
- package/dist/styled-system/patterns/square.js +0 -24
- package/dist/styled-system/patterns/stack.d.ts +0 -23
- package/dist/styled-system/patterns/stack.js +0 -24
- package/dist/styled-system/patterns/visually-hidden.d.ts +0 -20
- package/dist/styled-system/patterns/visually-hidden.js +0 -18
- package/dist/styled-system/patterns/vstack.d.ts +0 -21
- package/dist/styled-system/patterns/vstack.js +0 -24
- package/dist/styled-system/patterns/wrap.d.ts +0 -24
- package/dist/styled-system/patterns/wrap.js +0 -25
- package/dist/styled-system/recipes/content-header.d.ts +0 -31
- package/dist/styled-system/recipes/content-header.js +0 -65
- package/dist/styled-system/recipes/content-page.d.ts +0 -34
- package/dist/styled-system/recipes/content-page.js +0 -43
- package/dist/styled-system/recipes/create-recipe.js +0 -82
- package/dist/styled-system/recipes/index.d.ts +0 -17
- package/dist/styled-system/recipes/index.js +0 -16
- package/dist/styled-system/recipes/metric-block.d.ts +0 -34
- package/dist/styled-system/recipes/metric-block.js +0 -76
- package/dist/styled-system/recipes/product-list.d.ts +0 -31
- package/dist/styled-system/recipes/product-list.js +0 -73
- package/dist/styled-system/recipes/product-section.d.ts +0 -31
- package/dist/styled-system/recipes/product-section.js +0 -49
- package/dist/styled-system/recipes/product-settings-row.d.ts +0 -34
- package/dist/styled-system/recipes/product-settings-row.js +0 -58
- package/dist/styled-system/recipes/product-settings.d.ts +0 -31
- package/dist/styled-system/recipes/product-settings.js +0 -57
- package/dist/styled-system/recipes/product-status.d.ts +0 -34
- package/dist/styled-system/recipes/product-status.js +0 -45
- package/dist/styled-system/recipes/product-toolbar.d.ts +0 -31
- package/dist/styled-system/recipes/product-toolbar.js +0 -41
- package/dist/styled-system/recipes/product-workspace.d.ts +0 -34
- package/dist/styled-system/recipes/product-workspace.js +0 -58
- package/dist/styled-system/recipes/surface-metric.d.ts +0 -38
- package/dist/styled-system/recipes/surface-metric.js +0 -63
- package/dist/styled-system/recipes/surface-table.d.ts +0 -34
- package/dist/styled-system/recipes/surface-table.js +0 -62
- package/dist/styled-system/recipes/ui-badge.d.ts +0 -31
- package/dist/styled-system/recipes/ui-badge.js +0 -38
- package/dist/styled-system/recipes/ui-button.d.ts +0 -32
- package/dist/styled-system/recipes/ui-button.js +0 -43
- package/dist/styled-system/recipes/ui-input.d.ts +0 -31
- package/dist/styled-system/recipes/ui-input.js +0 -49
- package/dist/styled-system/recipes/ui-textarea.d.ts +0 -31
- package/dist/styled-system/recipes/ui-textarea.js +0 -24
- package/dist/styled-system/styles.css +0 -1257
- package/dist/styled-system/tokens/index.d.ts +0 -9
- package/dist/styled-system/tokens/index.js +0 -268
- package/dist/styled-system/tokens/tokens.d.ts +0 -36
- package/dist/styled-system/types/composition.d.ts +0 -227
- package/dist/styled-system/types/conditions.d.ts +0 -236
- package/dist/styled-system/types/csstype.d.ts +0 -22570
- package/dist/styled-system/types/index.d.ts +0 -6
- package/dist/styled-system/types/parts.d.ts +0 -8
- package/dist/styled-system/types/pattern.d.ts +0 -78
- package/dist/styled-system/types/prop-type.d.ts +0 -223
- package/dist/styled-system/types/recipe.d.ts +0 -181
- package/dist/styled-system/types/selectors.d.ts +0 -59
- package/dist/styled-system/types/static-css.d.ts +0 -56
- package/dist/styled-system/types/style-props.d.ts +0 -8088
- package/dist/styled-system/types/system-types.d.ts +0 -151
- package/dist/styles/recipes.css +0 -1262
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
-
import {
|
|
3
|
+
import { onDestroy, tick, untrack } from 'svelte';
|
|
4
|
+
import { snapshotPlainData } from '@svadmin/core/schema';
|
|
5
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
6
|
+
import { ArrowDown, ArrowUp, ArrowUpDown, ChevronLeft, ChevronRight, ChevronDown, Folder, File, RotateCw } from '@lucide/svelte';
|
|
7
|
+
import {
|
|
8
|
+
filterTreeTableData, indexTreeTable, sortTreeTableData, treeTableSelection,
|
|
9
|
+
type TreeTableId, type TreeTableNode, type TreeTableSort,
|
|
10
|
+
} from '../tree-table-model.js';
|
|
4
11
|
import { Button } from './ui/button/index.js';
|
|
5
12
|
import { Checkbox } from './ui/checkbox/index.js';
|
|
13
|
+
import { Input } from './ui/input/index.js';
|
|
6
14
|
import { cn } from '../utils.js';
|
|
7
15
|
|
|
8
16
|
export interface TreeTableColumn {
|
|
9
17
|
key: string;
|
|
10
18
|
label: string;
|
|
11
19
|
width?: string;
|
|
20
|
+
sortable?: boolean;
|
|
12
21
|
}
|
|
13
22
|
|
|
14
23
|
interface Props {
|
|
@@ -21,6 +30,27 @@
|
|
|
21
30
|
onselect?: (selectedKeys: (string | number)[]) => void;
|
|
22
31
|
rowActions?: Snippet<[{ record: Record<string, unknown>; id: string | number }]>;
|
|
23
32
|
customCell?: Snippet<[{ column: TreeTableColumn; record: Record<string, unknown>; value: unknown }]>;
|
|
33
|
+
scopeKey?: string | number;
|
|
34
|
+
ariaLabel?: string;
|
|
35
|
+
loading?: boolean;
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
error?: string;
|
|
38
|
+
onRetry?: () => void;
|
|
39
|
+
filterText?: string;
|
|
40
|
+
filterable?: boolean;
|
|
41
|
+
filterPlaceholder?: string;
|
|
42
|
+
filterKeys?: string[];
|
|
43
|
+
sort?: TreeTableSort | undefined;
|
|
44
|
+
onsortchange?: (sort: TreeTableSort | undefined) => void;
|
|
45
|
+
page?: number;
|
|
46
|
+
pageSize?: number;
|
|
47
|
+
onpagechange?: (page: number) => void;
|
|
48
|
+
loadChildren?: (record: Record<string, unknown>, id: TreeTableId) => Promise<Record<string, unknown>[]>;
|
|
49
|
+
canLoadChildren?: (record: Record<string, unknown>) => boolean;
|
|
50
|
+
virtualized?: boolean;
|
|
51
|
+
height?: number;
|
|
52
|
+
rowHeight?: number;
|
|
53
|
+
overscan?: number;
|
|
24
54
|
class?: string;
|
|
25
55
|
}
|
|
26
56
|
|
|
@@ -34,46 +64,221 @@
|
|
|
34
64
|
onselect,
|
|
35
65
|
rowActions,
|
|
36
66
|
customCell,
|
|
67
|
+
scopeKey,
|
|
68
|
+
ariaLabel,
|
|
69
|
+
loading = false,
|
|
70
|
+
disabled = false,
|
|
71
|
+
error,
|
|
72
|
+
onRetry,
|
|
73
|
+
filterText = $bindable(''),
|
|
74
|
+
filterable = false,
|
|
75
|
+
filterPlaceholder,
|
|
76
|
+
filterKeys,
|
|
77
|
+
sort = $bindable(),
|
|
78
|
+
onsortchange,
|
|
79
|
+
page = $bindable(1),
|
|
80
|
+
pageSize = 0,
|
|
81
|
+
onpagechange,
|
|
82
|
+
loadChildren,
|
|
83
|
+
canLoadChildren,
|
|
84
|
+
virtualized = false,
|
|
85
|
+
height = 360,
|
|
86
|
+
rowHeight = 40,
|
|
87
|
+
overscan = 4,
|
|
37
88
|
class: className = '',
|
|
38
89
|
}: Props = $props();
|
|
39
90
|
|
|
40
91
|
let expandedKeys = $state<Set<string | number>>(new Set());
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
92
|
+
let loadedChildren = $state<Map<TreeTableId, Record<string, unknown>[]>>(new Map());
|
|
93
|
+
let childLoadingKeys = $state<Set<TreeTableId>>(new Set());
|
|
94
|
+
let childErrors = $state<Map<TreeTableId, string>>(new Map());
|
|
95
|
+
let tableRoot: HTMLTableElement | undefined = $state();
|
|
96
|
+
let focusedKey = $state<TreeTableId>();
|
|
97
|
+
let scrollTop = $state(0);
|
|
98
|
+
let viewport: HTMLDivElement | undefined = $state();
|
|
99
|
+
let observedScope = untrack(() => scopeKey);
|
|
100
|
+
let observedPage = untrack(() => page);
|
|
101
|
+
let generation = 0;
|
|
102
|
+
let mounted = true;
|
|
103
|
+
const captureLoadScope = () => ({ data, scopeKey, loadChildren, canLoadChildren, primaryKey, childrenKey, disabled, loading, error });
|
|
104
|
+
let loadScope = $state.raw(untrack(captureLoadScope));
|
|
105
|
+
function sameLoadScope(scope: ReturnType<typeof captureLoadScope>): boolean {
|
|
106
|
+
return scope.data === data && scope.scopeKey === scopeKey && scope.loadChildren === loadChildren
|
|
107
|
+
&& scope.canLoadChildren === canLoadChildren && scope.primaryKey === primaryKey
|
|
108
|
+
&& scope.childrenKey === childrenKey && scope.disabled === disabled
|
|
109
|
+
&& scope.loading === loading && scope.error === error;
|
|
110
|
+
}
|
|
111
|
+
onDestroy(() => { mounted = false; generation++; });
|
|
112
|
+
const i18n = useTranslation();
|
|
113
|
+
const baseIndex = $derived(indexTreeTable(data, primaryKey, childrenKey));
|
|
114
|
+
const fullIndex = $derived(baseIndex.valid
|
|
115
|
+
? indexTreeTable(data, primaryKey, childrenKey, sameLoadScope(loadScope) ? loadedChildren : undefined)
|
|
116
|
+
: baseIndex);
|
|
117
|
+
const mergedData = $derived.by(() => {
|
|
118
|
+
if (!fullIndex.valid) return [];
|
|
119
|
+
// 完整索引先校验循环和数量;逆序重建后再进行筛选、排序和分页。
|
|
120
|
+
const records = new Map<TreeTableId, Record<string, unknown>>();
|
|
121
|
+
const roots: Record<string, unknown>[] = [];
|
|
122
|
+
for (const node of [...fullIndex.nodes.values()].reverse()) {
|
|
123
|
+
const children = node.children.map(id => records.get(id)).filter(
|
|
124
|
+
(record): record is Record<string, unknown> => record !== undefined,
|
|
125
|
+
);
|
|
126
|
+
records.set(node.id, { ...node.record, [childrenKey]: children });
|
|
127
|
+
}
|
|
128
|
+
for (const node of fullIndex.nodes.values()) {
|
|
129
|
+
const record = records.get(node.id);
|
|
130
|
+
if (node.parent === undefined && record) roots.push(record);
|
|
131
|
+
}
|
|
132
|
+
return roots;
|
|
133
|
+
});
|
|
134
|
+
const filteredData = $derived(filterTreeTableData(
|
|
135
|
+
mergedData, filterText, filterKeys ?? columns.map(column => column.key), childrenKey,
|
|
136
|
+
));
|
|
137
|
+
const sortedData = $derived(sortTreeTableData(filteredData, sort, childrenKey));
|
|
138
|
+
const normalizedPageSize = $derived(
|
|
139
|
+
Number.isSafeInteger(pageSize) && pageSize > 0 ? Math.min(pageSize, 1000) : 0,
|
|
140
|
+
);
|
|
141
|
+
const pageCount = $derived(normalizedPageSize > 0 ? Math.max(1, Math.ceil(sortedData.length / normalizedPageSize)) : 1);
|
|
142
|
+
const normalizedPage = $derived(Math.min(pageCount, Number.isSafeInteger(page) && page > 0 ? page : 1));
|
|
143
|
+
const pagedData = $derived(normalizedPageSize > 0
|
|
144
|
+
? sortedData.slice((normalizedPage - 1) * normalizedPageSize, normalizedPage * normalizedPageSize)
|
|
145
|
+
: sortedData);
|
|
146
|
+
const index = $derived(indexTreeTable(pagedData, primaryKey, childrenKey));
|
|
147
|
+
const columnsValid = $derived.by(() => {
|
|
148
|
+
const ordering = sort;
|
|
149
|
+
return columns.length > 0
|
|
150
|
+
&& columns.every(column => typeof column.key === 'string' && column.key.length > 0)
|
|
151
|
+
&& new Set(columns.map(column => column.key)).size === columns.length
|
|
152
|
+
&& (!ordering || (columns.some(column => column.key === ordering.key) && ['asc', 'desc'].includes(ordering.direction)));
|
|
153
|
+
});
|
|
154
|
+
const displayError = $derived(error || (!fullIndex.valid || !index.valid || !columnsValid ? i18n.t('treeTable.invalidData') : ''));
|
|
155
|
+
const available = $derived(!loading && !displayError);
|
|
156
|
+
const selection = $derived(treeTableSelection(fullIndex.nodes, new Set(selectedKeys)));
|
|
157
|
+
function isExpanded(id: TreeTableId): boolean {
|
|
158
|
+
return expandedKeys.has(id) || filterText.trim().length > 0;
|
|
159
|
+
}
|
|
160
|
+
function needsChildren(id: TreeTableId): boolean {
|
|
161
|
+
const node = fullIndex.nodes.get(id);
|
|
162
|
+
return !!node && !!loadChildren && !loadedChildren.has(id)
|
|
163
|
+
&& (canLoadChildren?.(node.record) ?? !Array.isArray(node.record[childrenKey]));
|
|
164
|
+
}
|
|
165
|
+
const flattenedRows = $derived.by(() => {
|
|
166
|
+
if (!available) return [];
|
|
167
|
+
const visible = new Set<TreeTableId>();
|
|
168
|
+
const rows: (TreeTableNode & { hasChildren: boolean; isExpanded: boolean })[] = [];
|
|
169
|
+
for (const node of index.nodes.values()) {
|
|
170
|
+
if (node.parent !== undefined && (!visible.has(node.parent) || !isExpanded(node.parent))) continue;
|
|
171
|
+
visible.add(node.id);
|
|
172
|
+
const canLoad = needsChildren(node.id);
|
|
173
|
+
rows.push({
|
|
174
|
+
...node,
|
|
175
|
+
hasChildren: node.children.length > 0 || canLoad,
|
|
176
|
+
isExpanded: isExpanded(node.id),
|
|
65
177
|
});
|
|
178
|
+
}
|
|
179
|
+
return rows;
|
|
180
|
+
});
|
|
181
|
+
const normalizedRowHeight = $derived(Number.isFinite(rowHeight) && rowHeight >= 24
|
|
182
|
+
? Math.min(Math.floor(rowHeight), 500) : 40);
|
|
183
|
+
const normalizedHeight = $derived(Number.isFinite(height) && height >= normalizedRowHeight * 2
|
|
184
|
+
? Math.min(Math.floor(height), 4000) : Math.max(360, normalizedRowHeight * 2));
|
|
185
|
+
const normalizedOverscan = $derived(Number.isSafeInteger(overscan) && overscan >= 0
|
|
186
|
+
? Math.min(overscan, 100) : 4);
|
|
187
|
+
const maxScroll = $derived(Math.max(0, (flattenedRows.length + 1) * normalizedRowHeight - normalizedHeight));
|
|
188
|
+
const boundedScrollTop = $derived(Math.max(0, Math.min(Number.isFinite(scrollTop) ? scrollTop : 0, maxScroll)));
|
|
189
|
+
const windowStart = $derived(virtualized
|
|
190
|
+
? Math.max(0, Math.floor(boundedScrollTop / normalizedRowHeight) - normalizedOverscan) : 0);
|
|
191
|
+
const windowEnd = $derived(virtualized
|
|
192
|
+
? Math.min(flattenedRows.length, Math.ceil((boundedScrollTop + normalizedHeight) / normalizedRowHeight) + normalizedOverscan)
|
|
193
|
+
: flattenedRows.length);
|
|
194
|
+
const renderedRows = $derived(flattenedRows.slice(windowStart, windowEnd));
|
|
195
|
+
const focusTarget = $derived(renderedRows.some(row => row.id === focusedKey) ? focusedKey : renderedRows[0]?.id);
|
|
196
|
+
const cellCount = $derived(columns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0));
|
|
197
|
+
|
|
198
|
+
function nodeKey(id: TreeTableId): string {
|
|
199
|
+
return `${typeof id}:${String(id)}`;
|
|
200
|
+
}
|
|
66
201
|
|
|
67
|
-
|
|
68
|
-
|
|
202
|
+
async function focusRow(id: TreeTableId | undefined): Promise<void> {
|
|
203
|
+
if (id === undefined) return;
|
|
204
|
+
const epoch = generation;
|
|
205
|
+
const position = flattenedRows.findIndex(item => item.id === id);
|
|
206
|
+
if (position < 0) return;
|
|
207
|
+
if (virtualized) {
|
|
208
|
+
const top = position * normalizedRowHeight;
|
|
209
|
+
const bottom = top + normalizedRowHeight;
|
|
210
|
+
if (top < boundedScrollTop) scrollTop = top;
|
|
211
|
+
else if (bottom > boundedScrollTop + normalizedHeight - normalizedRowHeight) {
|
|
212
|
+
scrollTop = Math.min(maxScroll, bottom - normalizedHeight + normalizedRowHeight);
|
|
69
213
|
}
|
|
214
|
+
await tick();
|
|
70
215
|
}
|
|
71
|
-
return
|
|
216
|
+
if (!mounted || epoch !== generation || !available) return;
|
|
217
|
+
tableRoot?.querySelector<HTMLElement>(`[data-tree-key="${CSS.escape(nodeKey(id))}"]`)?.focus();
|
|
72
218
|
}
|
|
73
219
|
|
|
74
|
-
|
|
220
|
+
function handleKeydown(event: KeyboardEvent, row: TreeTableNode): void {
|
|
221
|
+
if (disabled || event.target !== event.currentTarget || event.altKey || event.ctrlKey || event.metaKey) return;
|
|
222
|
+
const position = flattenedRows.findIndex(item => item.id === row.id);
|
|
223
|
+
switch (event.key) {
|
|
224
|
+
case 'ArrowDown': focusRow(flattenedRows[position + 1]?.id); break;
|
|
225
|
+
case 'ArrowUp': focusRow(flattenedRows[position - 1]?.id); break;
|
|
226
|
+
case 'Home': focusRow(flattenedRows[0]?.id); break;
|
|
227
|
+
case 'End': focusRow(flattenedRows.at(-1)?.id); break;
|
|
228
|
+
case 'ArrowRight':
|
|
229
|
+
if ((!isExpanded(row.id) && row.children.length) || needsChildren(row.id)) toggleExpand(row.id);
|
|
230
|
+
else focusRow(row.children[0]);
|
|
231
|
+
break;
|
|
232
|
+
case 'ArrowLeft':
|
|
233
|
+
if (expandedKeys.has(row.id)) toggleExpand(row.id);
|
|
234
|
+
else focusRow(row.parent);
|
|
235
|
+
break;
|
|
236
|
+
case ' ':
|
|
237
|
+
case 'Enter':
|
|
238
|
+
if (selectable) toggleSelect(row.id);
|
|
239
|
+
else return;
|
|
240
|
+
break;
|
|
241
|
+
default: return;
|
|
242
|
+
}
|
|
243
|
+
event.preventDefault();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function loadNodeChildren(row: TreeTableNode): Promise<void> {
|
|
247
|
+
const loader = loadChildren;
|
|
248
|
+
if (!loader || !available || disabled || childLoadingKeys.has(row.id) || !needsChildren(row.id)) return;
|
|
249
|
+
const scope = captureLoadScope();
|
|
250
|
+
const epoch = generation;
|
|
251
|
+
const current = () => mounted && generation === epoch && sameLoadScope(scope);
|
|
252
|
+
childLoadingKeys = new Set([...childLoadingKeys, row.id]);
|
|
253
|
+
const errors = new Map(childErrors);
|
|
254
|
+
errors.delete(row.id);
|
|
255
|
+
childErrors = errors;
|
|
256
|
+
try {
|
|
257
|
+
const rawChildren: unknown = snapshotPlainData(await loader(row.record, row.id));
|
|
258
|
+
if (!current()) return;
|
|
259
|
+
if (!Array.isArray(rawChildren) || !rawChildren.every((child): child is Record<string, unknown> =>
|
|
260
|
+
typeof child === 'object' && child !== null && !Array.isArray(child))) {
|
|
261
|
+
throw new Error('Invalid children');
|
|
262
|
+
}
|
|
263
|
+
const children: Record<string, unknown>[] = rawChildren;
|
|
264
|
+
const candidate = new Map(loadedChildren);
|
|
265
|
+
candidate.set(row.id, children);
|
|
266
|
+
if (!indexTreeTable(data, primaryKey, childrenKey, candidate).valid) throw new Error('Invalid children');
|
|
267
|
+
loadedChildren = candidate;
|
|
268
|
+
} catch {
|
|
269
|
+
if (current()) childErrors = new Map([...childErrors, [row.id, i18n.t('treeTable.loadFailed')]]);
|
|
270
|
+
} finally {
|
|
271
|
+
if (current()) {
|
|
272
|
+
const next = new Set(childLoadingKeys);
|
|
273
|
+
next.delete(row.id);
|
|
274
|
+
childLoadingKeys = next;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
75
278
|
|
|
76
279
|
function toggleExpand(id: string | number) {
|
|
280
|
+
if (!available || disabled || filterText.trim().length > 0) return;
|
|
281
|
+
const row = fullIndex.nodes.get(id);
|
|
77
282
|
const next = new Set(expandedKeys);
|
|
78
283
|
if (next.has(id)) {
|
|
79
284
|
next.delete(id);
|
|
@@ -81,120 +286,236 @@
|
|
|
81
286
|
next.add(id);
|
|
82
287
|
}
|
|
83
288
|
expandedKeys = next;
|
|
289
|
+
if (row && next.has(id)) void loadNodeChildren(row);
|
|
84
290
|
}
|
|
85
291
|
|
|
86
|
-
function
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
collect(children);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
292
|
+
function cycleSort(column: TreeTableColumn): void {
|
|
293
|
+
if (!available || disabled || !column.sortable) return;
|
|
294
|
+
if (!sort || sort.key !== column.key) {
|
|
295
|
+
sort = { key: column.key, direction: 'asc' };
|
|
296
|
+
} else if (sort.direction === 'asc') {
|
|
297
|
+
sort = { key: column.key, direction: 'desc' };
|
|
298
|
+
} else {
|
|
299
|
+
sort = undefined;
|
|
97
300
|
}
|
|
98
|
-
|
|
99
|
-
|
|
301
|
+
onsortchange?.(sort);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function expandAll() {
|
|
305
|
+
if (!available || disabled || filterText.trim().length > 0) return;
|
|
306
|
+
expandedKeys = new Set([...index.nodes.values()].filter(node => node.children.length).map(node => node.id));
|
|
100
307
|
}
|
|
101
308
|
|
|
102
309
|
function collapseAll() {
|
|
310
|
+
if (!available || disabled || filterText.trim().length > 0) return;
|
|
103
311
|
expandedKeys = new Set();
|
|
104
312
|
}
|
|
105
313
|
|
|
106
|
-
function
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
314
|
+
function toggleSelect(id: TreeTableId) {
|
|
315
|
+
if (!available || disabled || !selectable || fullIndex.nodes.get(id)?.disabled) return;
|
|
316
|
+
const ids = new Set<TreeTableId>();
|
|
317
|
+
const stack = [id];
|
|
318
|
+
while (stack.length) {
|
|
319
|
+
const key = stack.pop();
|
|
320
|
+
const node = key === undefined ? undefined : fullIndex.nodes.get(key);
|
|
321
|
+
if (!node) continue;
|
|
322
|
+
if (!node.disabled) ids.add(node.id);
|
|
323
|
+
for (let child = node.children.length - 1; child >= 0; child -= 1) {
|
|
324
|
+
const next = node.children[child];
|
|
325
|
+
if (next !== undefined) stack.push(next);
|
|
112
326
|
}
|
|
113
327
|
}
|
|
114
|
-
|
|
328
|
+
const count = selection.get(id);
|
|
329
|
+
const isSelected = count !== undefined && count.selected === count.total;
|
|
330
|
+
const next = isSelected ? selectedKeys.filter(key => !ids.has(key)) : [...new Set([...selectedKeys, ...ids])];
|
|
331
|
+
selectedKeys = next;
|
|
332
|
+
onselect?.(next);
|
|
115
333
|
}
|
|
116
334
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
} else {
|
|
125
|
-
next = Array.from(new Set([...selectedKeys, ...descendantIds]));
|
|
335
|
+
$effect.pre(() => {
|
|
336
|
+
if (!sameLoadScope(loadScope)) {
|
|
337
|
+
generation++;
|
|
338
|
+
loadScope = captureLoadScope();
|
|
339
|
+
loadedChildren = new Map();
|
|
340
|
+
childErrors = new Map();
|
|
341
|
+
childLoadingKeys = new Set();
|
|
126
342
|
}
|
|
343
|
+
});
|
|
127
344
|
|
|
128
|
-
|
|
129
|
-
|
|
345
|
+
$effect.pre(() => {
|
|
346
|
+
const changedScope = scopeKey !== observedScope;
|
|
347
|
+
const changedPage = page !== observedPage;
|
|
348
|
+
observedScope = scopeKey;
|
|
349
|
+
observedPage = page;
|
|
350
|
+
const nodes = fullIndex.nodes;
|
|
351
|
+
untrack(() => {
|
|
352
|
+
if (changedScope) focusedKey = undefined;
|
|
353
|
+
if (changedScope || changedPage) scrollTop = 0;
|
|
354
|
+
expandedKeys = new Set(changedScope ? [] : [...expandedKeys].filter(key => nodes.has(key)));
|
|
355
|
+
const next = changedScope ? [] : [...new Set(selectedKeys)].filter(key => nodes.get(key)?.disabled === false);
|
|
356
|
+
if (next.length !== selectedKeys.length || next.some((key, i) => key !== selectedKeys[i])) {
|
|
357
|
+
selectedKeys = next;
|
|
358
|
+
onselect?.([...next]);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
function setPage(next: number): void {
|
|
364
|
+
page = next;
|
|
365
|
+
scrollTop = 0;
|
|
366
|
+
onpagechange?.(next);
|
|
130
367
|
}
|
|
368
|
+
let queryKey = untrack(() => JSON.stringify([filterText, filterKeys, sort, pageSize, scopeKey]));
|
|
369
|
+
$effect.pre(() => {
|
|
370
|
+
const next = JSON.stringify([filterText, filterKeys, sort, pageSize, scopeKey]);
|
|
371
|
+
if (next !== queryKey) {
|
|
372
|
+
queryKey = next;
|
|
373
|
+
scrollTop = 0;
|
|
374
|
+
untrack(() => { if (page !== 1) setPage(1); });
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
$effect(() => {
|
|
378
|
+
if (normalizedPageSize > 0 && page !== normalizedPage) setPage(normalizedPage);
|
|
379
|
+
});
|
|
380
|
+
$effect(() => {
|
|
381
|
+
const top = virtualized ? boundedScrollTop : 0;
|
|
382
|
+
if (viewport && viewport.scrollTop !== top) viewport.scrollTop = top;
|
|
383
|
+
if (virtualized && scrollTop !== top) scrollTop = top;
|
|
384
|
+
});
|
|
131
385
|
</script>
|
|
132
386
|
|
|
133
387
|
<div class={cn('svadmin-u-6da6a3c3f741 svadmin-u-6f7e013d6499', className)}>
|
|
134
388
|
<div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-8ef2268efbbc svadmin-u-d8e0e382c67b svadmin-u-660d2effb880 svadmin-u-359090c2d529">
|
|
135
389
|
<div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-77a2a20e90d4">
|
|
136
|
-
|
|
137
|
-
|
|
390
|
+
{#if filterable}
|
|
391
|
+
<Input
|
|
392
|
+
bind:value={filterText}
|
|
393
|
+
disabled={disabled || loading}
|
|
394
|
+
placeholder={filterPlaceholder ?? i18n.t('common.search')}
|
|
395
|
+
aria-label={filterPlaceholder ?? i18n.t('common.search')}
|
|
396
|
+
/>
|
|
397
|
+
{/if}
|
|
398
|
+
<Button type="button" disabled={!available || disabled || filterText.trim().length > 0} variant="ghost" size="sm" class="svadmin-u-f6fe902450dc svadmin-u-d5eab218aa34 svadmin-u-359090c2d529" onclick={expandAll}>
|
|
399
|
+
{i18n.t('treeTable.expandAll')}
|
|
138
400
|
</Button>
|
|
139
|
-
|
|
140
|
-
|
|
401
|
+
{#if normalizedPageSize > 0}
|
|
402
|
+
<span role="status">{normalizedPage} / {pageCount}</span>
|
|
403
|
+
<Button type="button" disabled={!available || disabled || normalizedPage <= 1} variant="ghost" size="sm"
|
|
404
|
+
title={i18n.t('treeTable.previousPage')} aria-label={i18n.t('treeTable.previousPage')}
|
|
405
|
+
onclick={() => setPage(normalizedPage - 1)}><ChevronLeft aria-hidden="true" /></Button>
|
|
406
|
+
<Button type="button" disabled={!available || disabled || normalizedPage >= pageCount} variant="ghost" size="sm"
|
|
407
|
+
title={i18n.t('treeTable.nextPage')} aria-label={i18n.t('treeTable.nextPage')}
|
|
408
|
+
onclick={() => setPage(normalizedPage + 1)}><ChevronRight aria-hidden="true" /></Button>
|
|
409
|
+
{/if}
|
|
410
|
+
<Button type="button" disabled={!available || disabled || filterText.trim().length > 0} variant="ghost" size="sm" class="svadmin-u-f6fe902450dc svadmin-u-d5eab218aa34 svadmin-u-359090c2d529 svadmin-u-bfa603190748" onclick={collapseAll}>
|
|
411
|
+
{i18n.t('treeTable.collapseAll')}
|
|
141
412
|
</Button>
|
|
142
413
|
</div>
|
|
143
414
|
{#if selectable && selectedKeys.length > 0}
|
|
144
415
|
<span class="svadmin-u-359090c2d529 svadmin-u-bfa603190748">
|
|
145
|
-
|
|
416
|
+
{i18n.t('common.selectedCount', { count: String(selectedKeys.length) })}
|
|
146
417
|
</span>
|
|
147
418
|
{/if}
|
|
148
419
|
</div>
|
|
149
420
|
|
|
150
|
-
|
|
151
|
-
<
|
|
421
|
+
{#if loading}
|
|
422
|
+
<div role="status">{i18n.t('common.loading')}</div>
|
|
423
|
+
{:else if displayError}
|
|
424
|
+
<div role="alert">{displayError}</div>
|
|
425
|
+
{#if error && onRetry}
|
|
426
|
+
<Button type="button" variant="ghost" size="sm" onclick={onRetry}><RotateCw aria-hidden="true" />{i18n.t('common.retry')}</Button>
|
|
427
|
+
{/if}
|
|
428
|
+
{:else}
|
|
429
|
+
<div bind:this={viewport} data-testid="tree-table-viewport" class:tree-table-virtual={virtualized}
|
|
430
|
+
onscroll={event => { scrollTop = event.currentTarget.scrollTop; }}
|
|
431
|
+
style={virtualized ? `height: ${normalizedHeight}px; overflow: auto; --tree-row-height: ${normalizedRowHeight}px;` : 'overflow-x: auto;'}
|
|
432
|
+
class="svadmin-u-5f22e64f2282 svadmin-u-ca6bcd4b6f3f svadmin-u-c9ed8c5f79ae svadmin-u-2cd02d11d1af svadmin-u-cd0ad9a56558">
|
|
433
|
+
<!-- 原生表格保留列布局,treegrid 为可聚焦的层级行提供交互语义。 -->
|
|
434
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_to_interactive_role -->
|
|
435
|
+
<table bind:this={tableRoot} role="treegrid" aria-label={ariaLabel ?? i18n.t('treeTable.label')}
|
|
436
|
+
aria-rowcount={flattenedRows.length + 1} aria-colcount={cellCount}
|
|
437
|
+
aria-multiselectable={selectable ? 'true' : undefined} aria-disabled={disabled}
|
|
438
|
+
class="svadmin-u-6da6a3c3f741 svadmin-u-2eba0d65d059 svadmin-u-359090c2d529 svadmin-u-4583f90cd9bd">
|
|
152
439
|
<thead class="svadmin-u-358af0b65a31 svadmin-u-65fdbade2025 svadmin-u-05faf5c801ff svadmin-u-e83a7042bc91 svadmin-u-bfa603190748">
|
|
153
440
|
<tr>
|
|
154
441
|
{#if selectable}
|
|
155
442
|
<th class="svadmin-u-2bbcfc3b5179 svadmin-u-0e17f2bd9074 svadmin-u-03b4dd7f172b svadmin-u-ca6bf63030aa"></th>
|
|
156
443
|
{/if}
|
|
157
444
|
{#each columns as col (col.key)}
|
|
158
|
-
<th class="svadmin-u-0e17f2bd9074 svadmin-u-03b4dd7f172b" style={col.width ? `width: ${col.width}` : ''}
|
|
159
|
-
{col.
|
|
445
|
+
<th class="svadmin-u-0e17f2bd9074 svadmin-u-03b4dd7f172b" style={col.width ? `width: ${col.width}` : ''}
|
|
446
|
+
aria-sort={sort?.key === col.key ? (sort.direction === 'asc' ? 'ascending' : 'descending') : col.sortable ? 'none' : undefined}>
|
|
447
|
+
{#if col.sortable}
|
|
448
|
+
<button class="tree-sort" type="button" disabled={!available || disabled} onclick={() => cycleSort(col)}
|
|
449
|
+
title={i18n.t('treeTable.sortColumn', { column: col.label })}
|
|
450
|
+
aria-label={i18n.t('treeTable.sortColumn', { column: col.label })}>
|
|
451
|
+
{col.label}
|
|
452
|
+
{#if sort?.key !== col.key}<ArrowUpDown size={14} aria-hidden="true" />
|
|
453
|
+
{:else if sort.direction === 'asc'}<ArrowUp size={14} aria-hidden="true" />
|
|
454
|
+
{:else}<ArrowDown size={14} aria-hidden="true" />{/if}
|
|
455
|
+
</button>
|
|
456
|
+
{:else}
|
|
457
|
+
{col.label}
|
|
458
|
+
{/if}
|
|
160
459
|
</th>
|
|
161
460
|
{/each}
|
|
162
461
|
{#if rowActions}
|
|
163
|
-
<th class="svadmin-u-ed831a4dff32 svadmin-u-0e17f2bd9074 svadmin-u-03b4dd7f172b svadmin-u-308fc069e46e">
|
|
462
|
+
<th class="svadmin-u-ed831a4dff32 svadmin-u-0e17f2bd9074 svadmin-u-03b4dd7f172b svadmin-u-308fc069e46e">{i18n.t('common.actions')}</th>
|
|
164
463
|
{/if}
|
|
165
464
|
</tr>
|
|
166
465
|
</thead>
|
|
167
466
|
<tbody class="svadmin-u-fa6acbf81d74 svadmin-u-08b5607c7258">
|
|
168
|
-
{#
|
|
169
|
-
|
|
170
|
-
|
|
467
|
+
{#if virtualized && windowStart > 0}
|
|
468
|
+
<tr aria-hidden="true" class="tree-spacer"><td colspan={cellCount}
|
|
469
|
+
style={`height: ${windowStart * normalizedRowHeight}px;`}></td></tr>
|
|
470
|
+
{/if}
|
|
471
|
+
{#key scopeKey}
|
|
472
|
+
{#each renderedRows as row, renderedIndex (nodeKey(row.id))}
|
|
473
|
+
{@const count = selection.get(row.id)}
|
|
474
|
+
{@const isSelected = count !== undefined && count.total > 0 && count.selected === count.total}
|
|
475
|
+
{@const partial = count !== undefined && count.selected > 0 && count.selected < count.total}
|
|
476
|
+
<tr tabindex={focusTarget === row.id ? 0 : -1}
|
|
477
|
+
aria-rowindex={windowStart + renderedIndex + 2}
|
|
478
|
+
aria-level={row.level + 1} aria-expanded={row.hasChildren ? row.isExpanded : undefined}
|
|
479
|
+
aria-busy={childLoadingKeys.has(row.id)}
|
|
480
|
+
aria-disabled={disabled || row.disabled ? 'true' : undefined}
|
|
481
|
+
aria-selected={selectable ? isSelected : undefined} data-tree-key={nodeKey(row.id)}
|
|
482
|
+
onfocus={() => { focusedKey = row.id; }} onkeydown={event => handleKeydown(event, row)}
|
|
483
|
+
class="svadmin-u-12251b8f1749 svadmin-u-ceb69a6b0e5f {isSelected ? 'svadmin-u-989c466fdbe7' : ''}">
|
|
171
484
|
{#if selectable}
|
|
172
485
|
<td class="svadmin-u-2bbcfc3b5179 svadmin-u-0e17f2bd9074 svadmin-u-03b4dd7f172b svadmin-u-ca6bf63030aa">
|
|
486
|
+
<div class="tree-cell">
|
|
173
487
|
<Checkbox
|
|
174
488
|
checked={isSelected}
|
|
175
|
-
|
|
176
|
-
|
|
489
|
+
indeterminate={partial}
|
|
490
|
+
disabled={disabled || row.disabled}
|
|
491
|
+
onCheckedChange={() => toggleSelect(row.id)}
|
|
492
|
+
aria-label={i18n.t('common.selectRow', { id: String(row.id) })}
|
|
177
493
|
/>
|
|
494
|
+
</div>
|
|
178
495
|
</td>
|
|
179
496
|
{/if}
|
|
180
497
|
|
|
181
498
|
{#each columns as col, colIdx (col.key)}
|
|
182
499
|
{@const val = row.record[col.key]}
|
|
183
500
|
<td class="svadmin-u-0e17f2bd9074 svadmin-u-03b4dd7f172b">
|
|
501
|
+
<div class="tree-cell">
|
|
184
502
|
{#if colIdx === 0}
|
|
185
503
|
<!-- Primary tree node column with indent -->
|
|
186
504
|
<div
|
|
187
|
-
class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-58284b4ea568"
|
|
505
|
+
class="tree-node-content svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-58284b4ea568"
|
|
188
506
|
style="padding-left: {row.level * 20}px;"
|
|
189
507
|
>
|
|
190
508
|
{#if row.hasChildren}
|
|
191
509
|
<button
|
|
192
510
|
type="button"
|
|
511
|
+
disabled={disabled || filterText.trim().length > 0}
|
|
193
512
|
class="svadmin-u-cd0d9c512cdc svadmin-u-72470489ff4e svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-86843cf1e227 svadmin-u-07389a777c1f svadmin-u-8e551981c8d7 svadmin-u-bfa603190748 svadmin-u-ea7b2e9e070e svadmin-u-ceb69a6b0e5f"
|
|
194
513
|
onclick={() => toggleExpand(row.id)}
|
|
195
|
-
aria-label={row.isExpanded ? '
|
|
514
|
+
aria-label={`${String(val ?? row.id)}: ${i18n.t(row.isExpanded ? 'treeTable.collapse' : 'treeTable.expand')}`}
|
|
196
515
|
>
|
|
197
|
-
{#if row.
|
|
516
|
+
{#if childLoadingKeys.has(row.id)}
|
|
517
|
+
<RotateCw class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" aria-label={i18n.t('common.loading')} />
|
|
518
|
+
{:else if row.isExpanded}
|
|
198
519
|
<ChevronDown class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" />
|
|
199
520
|
{:else}
|
|
200
521
|
<ChevronRight class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" />
|
|
@@ -206,32 +527,113 @@
|
|
|
206
527
|
<File class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c svadmin-u-3a33f11548cd svadmin-u-012fbd121f37" />
|
|
207
528
|
{/if}
|
|
208
529
|
|
|
209
|
-
|
|
210
|
-
{val
|
|
211
|
-
|
|
530
|
+
{#if customCell}
|
|
531
|
+
{@render customCell({ column: col, record: row.record, value: val })}
|
|
532
|
+
{:else}
|
|
533
|
+
<span class="svadmin-u-2689f3958069 svadmin-u-d4108abe6359 svadmin-u-f283ea9bea0e">{val ?? '—'}</span>
|
|
534
|
+
{/if}
|
|
212
535
|
</div>
|
|
536
|
+
{#if childErrors.has(row.id)}
|
|
537
|
+
<span role="alert" title={childErrors.get(row.id)}>{childErrors.get(row.id)}</span>
|
|
538
|
+
<Button type="button" variant="ghost" size="sm" disabled={disabled || childLoadingKeys.has(row.id)}
|
|
539
|
+
onclick={() => {
|
|
540
|
+
const node = fullIndex.nodes.get(row.id);
|
|
541
|
+
if (node) {
|
|
542
|
+
expandedKeys = new Set([...expandedKeys, row.id]);
|
|
543
|
+
void loadNodeChildren(node);
|
|
544
|
+
}
|
|
545
|
+
}}><RotateCw aria-hidden="true" />{i18n.t('common.retry')}</Button>
|
|
546
|
+
{/if}
|
|
213
547
|
{:else if customCell}
|
|
214
548
|
{@render customCell({ column: col, record: row.record, value: val })}
|
|
215
549
|
{:else}
|
|
216
550
|
<span class="svadmin-u-d4108abe6359 svadmin-u-f283ea9bea0e">{val ?? '—'}</span>
|
|
217
551
|
{/if}
|
|
552
|
+
</div>
|
|
218
553
|
</td>
|
|
219
554
|
{/each}
|
|
220
555
|
|
|
221
556
|
{#if rowActions}
|
|
222
557
|
<td class="svadmin-u-ed831a4dff32 svadmin-u-0e17f2bd9074 svadmin-u-03b4dd7f172b svadmin-u-308fc069e46e">
|
|
558
|
+
<div class="tree-cell">
|
|
223
559
|
{@render rowActions({ record: row.record, id: row.id })}
|
|
560
|
+
</div>
|
|
224
561
|
</td>
|
|
225
562
|
{/if}
|
|
226
563
|
</tr>
|
|
227
564
|
{:else}
|
|
228
565
|
<tr>
|
|
229
566
|
<td colspan={columns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0)} class="svadmin-u-a1f611f027dd svadmin-u-ca6bf63030aa svadmin-u-bfa603190748">
|
|
230
|
-
|
|
567
|
+
{i18n.t('common.noData')}
|
|
231
568
|
</td>
|
|
232
569
|
</tr>
|
|
233
570
|
{/each}
|
|
571
|
+
{/key}
|
|
572
|
+
{#if virtualized && windowEnd < flattenedRows.length}
|
|
573
|
+
<tr aria-hidden="true" class="tree-spacer"><td colspan={cellCount}
|
|
574
|
+
style={`height: ${(flattenedRows.length - windowEnd) * normalizedRowHeight}px;`}></td></tr>
|
|
575
|
+
{/if}
|
|
234
576
|
</tbody>
|
|
235
577
|
</table>
|
|
236
578
|
</div>
|
|
579
|
+
{/if}
|
|
237
580
|
</div>
|
|
581
|
+
|
|
582
|
+
<style>
|
|
583
|
+
.tree-sort {
|
|
584
|
+
display: inline-flex;
|
|
585
|
+
align-items: center;
|
|
586
|
+
gap: 4px;
|
|
587
|
+
}
|
|
588
|
+
.tree-table-virtual .tree-node-content {
|
|
589
|
+
display: flex;
|
|
590
|
+
align-items: center;
|
|
591
|
+
min-width: 0;
|
|
592
|
+
overflow: hidden;
|
|
593
|
+
}
|
|
594
|
+
.tree-table-virtual .tree-cell > [role='alert'] {
|
|
595
|
+
min-width: 0;
|
|
596
|
+
overflow: hidden;
|
|
597
|
+
text-overflow: ellipsis;
|
|
598
|
+
}
|
|
599
|
+
.tree-table-virtual .tree-cell > :global(button) {
|
|
600
|
+
flex-shrink: 0;
|
|
601
|
+
max-height: var(--tree-row-height);
|
|
602
|
+
}
|
|
603
|
+
.tree-table-virtual table {
|
|
604
|
+
table-layout: fixed;
|
|
605
|
+
border-collapse: separate;
|
|
606
|
+
border-spacing: 0;
|
|
607
|
+
}
|
|
608
|
+
.tree-table-virtual thead {
|
|
609
|
+
position: sticky;
|
|
610
|
+
top: 0;
|
|
611
|
+
z-index: 1;
|
|
612
|
+
background: var(--background, white);
|
|
613
|
+
}
|
|
614
|
+
.tree-table-virtual th {
|
|
615
|
+
height: var(--tree-row-height);
|
|
616
|
+
box-sizing: border-box;
|
|
617
|
+
padding-block: 0;
|
|
618
|
+
overflow: hidden;
|
|
619
|
+
white-space: nowrap;
|
|
620
|
+
}
|
|
621
|
+
.tree-table-virtual td {
|
|
622
|
+
padding: 0;
|
|
623
|
+
border: 0;
|
|
624
|
+
}
|
|
625
|
+
.tree-table-virtual .tree-cell {
|
|
626
|
+
height: var(--tree-row-height);
|
|
627
|
+
box-sizing: border-box;
|
|
628
|
+
overflow: hidden;
|
|
629
|
+
display: flex;
|
|
630
|
+
align-items: center;
|
|
631
|
+
gap: 4px;
|
|
632
|
+
padding-inline: 12px;
|
|
633
|
+
white-space: nowrap;
|
|
634
|
+
}
|
|
635
|
+
.tree-spacer td {
|
|
636
|
+
padding: 0;
|
|
637
|
+
border: 0;
|
|
638
|
+
}
|
|
639
|
+
</style>
|