@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,8 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { definedOptions } from '@svadmin/core/options';
|
|
3
3
|
|
|
4
|
-
import { SvelteSet } from 'svelte/reactivity';
|
|
5
|
-
import {
|
|
4
|
+
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
|
5
|
+
import { onDestroy, tick, untrack } from 'svelte';
|
|
6
|
+
import { ChevronRight, ChevronDown, Check, X, Search, ChevronsUpDown, Loader2, Minus, RotateCw } from '@lucide/svelte';
|
|
6
7
|
import { cn } from '../utils.js';
|
|
7
8
|
import * as Popover from './ui/popover/index.js';
|
|
8
9
|
import { Input } from './ui/input/index.js';
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
value: string | number;
|
|
14
15
|
label: string;
|
|
15
16
|
children?: TreeSelectOption[];
|
|
17
|
+
hasChildren?: boolean;
|
|
16
18
|
disabled?: boolean;
|
|
17
19
|
expanded?: boolean;
|
|
18
20
|
}
|
|
@@ -28,12 +30,16 @@
|
|
|
28
30
|
onlyLeafSelectable?: boolean;
|
|
29
31
|
disabled?: boolean;
|
|
30
32
|
allowClear?: boolean;
|
|
33
|
+
loadChildren?: (node: TreeSelectOption) => Promise<TreeSelectOption[]>;
|
|
34
|
+
virtualized?: boolean;
|
|
35
|
+
itemHeight?: number;
|
|
36
|
+
viewportHeight?: number;
|
|
31
37
|
class?: string;
|
|
32
38
|
onchange?: (value: string | number | (string | number)[] | undefined) => void;
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
let {
|
|
36
|
-
options = [],
|
|
42
|
+
options = $bindable([]),
|
|
37
43
|
value = $bindable(undefined),
|
|
38
44
|
multiple = false,
|
|
39
45
|
placeholder,
|
|
@@ -41,13 +47,41 @@
|
|
|
41
47
|
onlyLeafSelectable = false,
|
|
42
48
|
disabled = false,
|
|
43
49
|
allowClear = true,
|
|
50
|
+
loadChildren,
|
|
51
|
+
virtualized = true,
|
|
52
|
+
itemHeight = 36,
|
|
53
|
+
viewportHeight = 280,
|
|
44
54
|
class: className,
|
|
45
55
|
onchange,
|
|
46
56
|
}: Props = $props();
|
|
47
57
|
|
|
48
58
|
let open = $state(false);
|
|
49
59
|
let searchQuery = $state('');
|
|
60
|
+
let loadingValues = new SvelteSet<string | number>();
|
|
61
|
+
interface ChildLoad {
|
|
62
|
+
node: TreeSelectOption;
|
|
63
|
+
loader: NonNullable<Props['loadChildren']>;
|
|
64
|
+
treeEpoch: number;
|
|
65
|
+
}
|
|
66
|
+
let loadErrors = new SvelteMap<string | number, ChildLoad>();
|
|
50
67
|
let expandedNodes = new SvelteSet<string | number>();
|
|
68
|
+
let mounted = true;
|
|
69
|
+
let treeRoot: HTMLElement | undefined = $state();
|
|
70
|
+
let treeScrollTop = $state(0);
|
|
71
|
+
let treeRevision = $state(0);
|
|
72
|
+
const loads = new Map<string | number, ChildLoad>();
|
|
73
|
+
let observedOptions = untrack(() => options);
|
|
74
|
+
let observedLoader = untrack(() => loadChildren);
|
|
75
|
+
interface SelectionAttempt {
|
|
76
|
+
key: string | number;
|
|
77
|
+
epoch: number;
|
|
78
|
+
loader: Props['loadChildren'];
|
|
79
|
+
value: Props['value'];
|
|
80
|
+
}
|
|
81
|
+
let selectionAttempt = $state.raw<SelectionAttempt>();
|
|
82
|
+
const selectionErrors = new SvelteSet<string | number>();
|
|
83
|
+
let treeEpoch = 0;
|
|
84
|
+
onDestroy(() => { mounted = false; loads.clear(); });
|
|
51
85
|
|
|
52
86
|
// Map to quickly look up option info by value
|
|
53
87
|
const optionMap = $derived.by(() => {
|
|
@@ -62,6 +96,20 @@
|
|
|
62
96
|
return map;
|
|
63
97
|
});
|
|
64
98
|
|
|
99
|
+
// 禁用状态沿完整数据树传播,搜索裁剪不能重新启用后代。
|
|
100
|
+
const disabledValues = $derived.by(() => {
|
|
101
|
+
const keys = new Set<string | number>();
|
|
102
|
+
function visit(nodes: TreeSelectOption[], inherited: boolean): void {
|
|
103
|
+
for (const node of nodes) {
|
|
104
|
+
const unavailable = inherited || node.disabled === true;
|
|
105
|
+
if (unavailable) keys.add(node.value);
|
|
106
|
+
if (node.children) visit(node.children, unavailable);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
visit(options, false);
|
|
110
|
+
return keys;
|
|
111
|
+
});
|
|
112
|
+
|
|
65
113
|
// Selected values array normalized
|
|
66
114
|
const selectedValues = $derived.by< (string | number)[]>(() => {
|
|
67
115
|
if (value === undefined || value === null || value === '') return [];
|
|
@@ -72,6 +120,35 @@
|
|
|
72
120
|
return selectedValues.map((v) => optionMap.get(v)?.label ?? String(v));
|
|
73
121
|
});
|
|
74
122
|
|
|
123
|
+
$effect.pre(() => {
|
|
124
|
+
// 外部替换根数组开启新数据版本;内部补充子节点保留当前版本。
|
|
125
|
+
if (options !== observedOptions) {
|
|
126
|
+
observedOptions = options;
|
|
127
|
+
treeEpoch += 1;
|
|
128
|
+
treeRevision += 1;
|
|
129
|
+
selectionAttempt = undefined;
|
|
130
|
+
selectionErrors.clear();
|
|
131
|
+
}
|
|
132
|
+
const loader = loadChildren;
|
|
133
|
+
const unavailable = disabled;
|
|
134
|
+
if (loader !== observedLoader || unavailable) {
|
|
135
|
+
observedLoader = loader;
|
|
136
|
+
selectionAttempt = undefined;
|
|
137
|
+
selectionErrors.clear();
|
|
138
|
+
}
|
|
139
|
+
if (!multiple || onlyLeafSelectable || selectionAttempt?.value !== value) selectionAttempt = undefined;
|
|
140
|
+
for (const [key, request] of loads) {
|
|
141
|
+
if (unavailable || request.treeEpoch !== treeEpoch || loader !== request.loader) {
|
|
142
|
+
loads.delete(key);
|
|
143
|
+
loadingValues.delete(key);
|
|
144
|
+
loadErrors.delete(key);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
for (const [key, request] of loadErrors) {
|
|
148
|
+
if (unavailable || request.treeEpoch !== treeEpoch || loader !== request.loader) loadErrors.delete(key);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
75
152
|
function toggleExpand(nodeValue: string | number, event?: MouseEvent) {
|
|
76
153
|
event?.stopPropagation();
|
|
77
154
|
if (expandedNodes.has(nodeValue)) {
|
|
@@ -81,32 +158,188 @@
|
|
|
81
158
|
}
|
|
82
159
|
}
|
|
83
160
|
|
|
161
|
+
function hasChildren(node: TreeSelectOption): boolean {
|
|
162
|
+
return Boolean(node.hasChildren ?? (node.children && node.children.length > 0));
|
|
163
|
+
}
|
|
164
|
+
|
|
84
165
|
function isNodeExpanded(node: TreeSelectOption): boolean {
|
|
85
166
|
if (searchQuery.trim()) return true; // Auto expand all on search
|
|
86
167
|
return expandedNodes.has(node.value) || !!node.expanded;
|
|
87
168
|
}
|
|
88
169
|
|
|
170
|
+
function selectableLeaves(node: TreeSelectOption): TreeSelectOption[] {
|
|
171
|
+
if (node.disabled) return [];
|
|
172
|
+
if (!hasChildren(node)) return [node];
|
|
173
|
+
if (!node.children) return [];
|
|
174
|
+
return (node.children ?? []).flatMap(selectableLeaves);
|
|
175
|
+
}
|
|
176
|
+
|
|
89
177
|
function isSelected(val: string | number): boolean {
|
|
90
178
|
return selectedValues.includes(val);
|
|
91
179
|
}
|
|
92
180
|
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
181
|
+
function isNodeSelected(node: TreeSelectOption): boolean {
|
|
182
|
+
node = optionMap.get(node.value) ?? node;
|
|
183
|
+
if (!multiple || !hasChildren(node)) return isSelected(node.value);
|
|
184
|
+
const leaves = selectableLeaves(node);
|
|
185
|
+
return leaves.length > 0 && !hasUnloadedDescendants(node) && leaves.every((leaf) => isSelected(leaf.value));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function hasUnloadedDescendants(node: TreeSelectOption): boolean {
|
|
189
|
+
if (node.disabled || !hasChildren(node)) return false;
|
|
190
|
+
if (!node.children) return true;
|
|
191
|
+
return node.children.some(hasUnloadedDescendants);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function isPartiallySelected(node: TreeSelectOption): boolean {
|
|
195
|
+
node = optionMap.get(node.value) ?? node;
|
|
196
|
+
if (!multiple || !hasChildren(node)) return false;
|
|
197
|
+
const leaves = selectableLeaves(node);
|
|
198
|
+
const count = leaves.filter((leaf) => isSelected(leaf.value)).length;
|
|
199
|
+
return count > 0 && (count < leaves.length || hasUnloadedDescendants(node));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function updateOptionsChildren(nodes: TreeSelectOption[], target: string | number, children: TreeSelectOption[]): TreeSelectOption[] {
|
|
203
|
+
let changed = false;
|
|
204
|
+
const next = nodes.map((node) => {
|
|
205
|
+
if (node.value === target) {
|
|
206
|
+
changed = true;
|
|
207
|
+
return { ...node, children, hasChildren: children.length > 0 };
|
|
208
|
+
}
|
|
209
|
+
if (!node.children) return node;
|
|
210
|
+
const updated = updateOptionsChildren(node.children, target, children);
|
|
211
|
+
if (updated === node.children) return node;
|
|
212
|
+
changed = true;
|
|
213
|
+
return { ...node, children: updated };
|
|
214
|
+
});
|
|
215
|
+
return changed ? next : nodes;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function ensureChildren(node: TreeSelectOption): Promise<boolean> {
|
|
219
|
+
if (!mounted || disabled || disabledValues.has(node.value) || loadingValues.has(node.value)) return false;
|
|
220
|
+
if (!node.hasChildren || node.children) return true;
|
|
221
|
+
const currentNode = optionMap.get(node.value);
|
|
222
|
+
if (!loadChildren || !currentNode) return false;
|
|
223
|
+
const request = { node: currentNode, loader: loadChildren, treeEpoch };
|
|
224
|
+
loads.set(node.value, request);
|
|
225
|
+
const current = () => mounted && !disabled && loads.get(node.value) === request
|
|
226
|
+
&& options === observedOptions && treeEpoch === request.treeEpoch
|
|
227
|
+
&& optionMap.has(node.value) && !disabledValues.has(node.value) && loadChildren === request.loader;
|
|
228
|
+
loadingValues.add(node.value);
|
|
229
|
+
loadErrors.delete(node.value);
|
|
230
|
+
try {
|
|
231
|
+
const children = await request.loader(request.node);
|
|
232
|
+
if (!current()) return false;
|
|
233
|
+
options = updateOptionsChildren(options, node.value, children);
|
|
234
|
+
observedOptions = options;
|
|
235
|
+
return true;
|
|
236
|
+
} catch {
|
|
237
|
+
if (current()) loadErrors.set(node.value, request);
|
|
238
|
+
return false;
|
|
239
|
+
} finally {
|
|
240
|
+
if (mounted && loads.get(node.value) === request) {
|
|
241
|
+
loads.delete(node.value);
|
|
242
|
+
loadingValues.delete(node.value);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function toggleExpandAsync(node: TreeSelectOption, event?: MouseEvent): Promise<void> {
|
|
248
|
+
event?.stopPropagation();
|
|
249
|
+
if (!await ensureChildren(node)) return;
|
|
250
|
+
if (!mounted || disabled) return;
|
|
251
|
+
toggleExpand(node.value);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function retryChildren(node: TreeSelectOption, event: MouseEvent): void {
|
|
255
|
+
event.stopPropagation();
|
|
256
|
+
if (selectionErrors.has(node.value)) void handleSelect(node);
|
|
257
|
+
else void toggleExpandAsync(node);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function setMultipleSelection(node: TreeSelectOption): void {
|
|
261
|
+
const leaves = selectableLeaves(node);
|
|
262
|
+
if (leaves.length === 0) return;
|
|
263
|
+
const leafValues = new Set(leaves.map((leaf) => leaf.value));
|
|
264
|
+
const shouldRemove = leaves.length > 0 && leaves.every((leaf) => isSelected(leaf.value));
|
|
265
|
+
const next = shouldRemove
|
|
266
|
+
? selectedValues.filter((value) => !leafValues.has(value))
|
|
267
|
+
: [...selectedValues, ...leaves.map((leaf) => leaf.value).filter((value) => !isSelected(value))];
|
|
268
|
+
value = next;
|
|
269
|
+
onchange?.(next);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function isCurrentSelection(attempt: SelectionAttempt): boolean {
|
|
273
|
+
return mounted && !disabled && multiple && !onlyLeafSelectable
|
|
274
|
+
&& selectionAttempt === attempt && attempt.epoch === treeEpoch
|
|
275
|
+
&& options === observedOptions && attempt.loader === loadChildren && attempt.value === value;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function selectSubtree(node: TreeSelectOption): Promise<void> {
|
|
279
|
+
if (selectionAttempt?.key === node.value && isCurrentSelection(selectionAttempt)) return;
|
|
280
|
+
const attempt: SelectionAttempt = { key: node.value, epoch: treeEpoch, loader: loadChildren, value };
|
|
281
|
+
selectionAttempt = attempt;
|
|
282
|
+
selectionErrors.delete(node.value);
|
|
283
|
+
let visited = 0;
|
|
284
|
+
let requests = 0;
|
|
285
|
+
// 级联加载有界且只在全部成功后写入选择;搜索裁剪不改变选择范围。
|
|
286
|
+
async function loadSubtree(key: string | number, depth: number): Promise<boolean> {
|
|
287
|
+
if (!isCurrentSelection(attempt)) return false;
|
|
288
|
+
const currentNode = optionMap.get(key);
|
|
289
|
+
if (!currentNode || ++visited > 10000 || depth > 64) return false;
|
|
290
|
+
if (currentNode.disabled) return true;
|
|
291
|
+
if (currentNode.hasChildren && !currentNode.children) {
|
|
292
|
+
if (++requests > 100 || !await ensureChildren(currentNode)) return false;
|
|
293
|
+
}
|
|
294
|
+
if (!isCurrentSelection(attempt)) return false;
|
|
295
|
+
const loaded = optionMap.get(key);
|
|
296
|
+
if (!loaded) return false;
|
|
297
|
+
for (const child of loaded.children ?? []) {
|
|
298
|
+
if (!await loadSubtree(child.value, depth + 1)) return false;
|
|
299
|
+
}
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
try {
|
|
303
|
+
if (!await loadSubtree(node.value, 0)) {
|
|
304
|
+
if (isCurrentSelection(attempt)) selectionErrors.add(node.value);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (isCurrentSelection(attempt)) {
|
|
308
|
+
const loaded = optionMap.get(node.value);
|
|
309
|
+
if (loaded) setMultipleSelection(loaded);
|
|
310
|
+
}
|
|
311
|
+
} finally {
|
|
312
|
+
if (selectionAttempt === attempt) selectionAttempt = undefined;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function handleSelect(node: TreeSelectOption): Promise<void> {
|
|
317
|
+
node = optionMap.get(node.value) ?? node;
|
|
318
|
+
if (disabled || disabledValues.has(node.value)) return;
|
|
319
|
+
if (selectionAttempt?.key === node.value && isCurrentSelection(selectionAttempt)) return;
|
|
320
|
+
const nodeHasChildren = hasChildren(node);
|
|
321
|
+
if (onlyLeafSelectable && nodeHasChildren) {
|
|
322
|
+
await toggleExpandAsync(node);
|
|
98
323
|
return;
|
|
99
324
|
}
|
|
100
325
|
|
|
101
326
|
if (multiple) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
327
|
+
if (nodeHasChildren) {
|
|
328
|
+
if (hasUnloadedDescendants(node)) await selectSubtree(node);
|
|
329
|
+
else {
|
|
330
|
+
selectionAttempt = undefined;
|
|
331
|
+
selectionErrors.delete(node.value);
|
|
332
|
+
setMultipleSelection(node);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
selectionAttempt = undefined;
|
|
337
|
+
const next = isSelected(node.value)
|
|
338
|
+
? selectedValues.filter((selected) => selected !== node.value)
|
|
339
|
+
: [...selectedValues, node.value];
|
|
340
|
+
value = next;
|
|
341
|
+
onchange?.(next);
|
|
107
342
|
}
|
|
108
|
-
value = next;
|
|
109
|
-
onchange?.(next);
|
|
110
343
|
} else {
|
|
111
344
|
const next = node.value;
|
|
112
345
|
value = next;
|
|
@@ -115,15 +348,93 @@
|
|
|
115
348
|
}
|
|
116
349
|
}
|
|
117
350
|
|
|
118
|
-
function
|
|
351
|
+
function findParent(nodes: TreeSelectOption[], target: string | number, parent?: TreeSelectOption): TreeSelectOption | undefined {
|
|
352
|
+
for (const node of nodes) {
|
|
353
|
+
if (node.value === target) return parent;
|
|
354
|
+
const match = node.children ? findParent(node.children, target, node) : undefined;
|
|
355
|
+
if (match) return match;
|
|
356
|
+
}
|
|
357
|
+
return undefined;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function treeKey(value: string | number): string {
|
|
361
|
+
return `${typeof value}:${String(value)}`;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async function focusTreeNode(valueToFocus: string | number): Promise<void> {
|
|
365
|
+
const index = flatVisibleNodes.findIndex(entry => entry.node.value === valueToFocus);
|
|
366
|
+
if (index < 0) return;
|
|
367
|
+
if (shouldVirtualize && treeRoot) {
|
|
368
|
+
const top = index * rowHeight;
|
|
369
|
+
if (top < treeScrollTop || top + rowHeight > treeScrollTop + windowHeight) {
|
|
370
|
+
treeScrollTop = Math.max(0, Math.min(top, totalHeight - windowHeight));
|
|
371
|
+
treeRoot.scrollTop = treeScrollTop;
|
|
372
|
+
await tick();
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (!mounted) return;
|
|
376
|
+
const element = treeRoot?.querySelector<HTMLElement>(`[data-tree-key="${CSS.escape(treeKey(valueToFocus))}"]`);
|
|
377
|
+
element?.focus();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function focusSibling(nodeValue: string | number, offset: number): void {
|
|
381
|
+
const index = flatVisibleNodes.findIndex(entry => entry.node.value === nodeValue);
|
|
382
|
+
const target = flatVisibleNodes[index + offset];
|
|
383
|
+
if (target) void focusTreeNode(target.node.value);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function handleTreeKeydown(event: KeyboardEvent, node: TreeSelectOption, expanded: boolean): void {
|
|
387
|
+
if (event.target !== event.currentTarget) return;
|
|
388
|
+
const nodeHasChildren = hasChildren(node);
|
|
389
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
390
|
+
event.preventDefault();
|
|
391
|
+
void handleSelect(node);
|
|
392
|
+
} else if (event.key === 'ArrowDown') {
|
|
393
|
+
event.preventDefault();
|
|
394
|
+
focusSibling(node.value, 1);
|
|
395
|
+
} else if (event.key === 'ArrowUp') {
|
|
396
|
+
event.preventDefault();
|
|
397
|
+
focusSibling(node.value, -1);
|
|
398
|
+
} else if (event.key === 'Home') {
|
|
399
|
+
event.preventDefault();
|
|
400
|
+
const first = flatVisibleNodes[0];
|
|
401
|
+
if (first) void focusTreeNode(first.node.value);
|
|
402
|
+
} else if (event.key === 'End') {
|
|
403
|
+
event.preventDefault();
|
|
404
|
+
const last = flatVisibleNodes[flatVisibleNodes.length - 1];
|
|
405
|
+
if (last) void focusTreeNode(last.node.value);
|
|
406
|
+
} else if (event.key === 'ArrowRight' && nodeHasChildren) {
|
|
407
|
+
event.preventDefault();
|
|
408
|
+
if (!expanded) void toggleExpandAsync(node);
|
|
409
|
+
else {
|
|
410
|
+
const index = flatVisibleNodes.findIndex(entry => entry.node.value === node.value);
|
|
411
|
+
const current = flatVisibleNodes[index];
|
|
412
|
+
const next = flatVisibleNodes[index + 1];
|
|
413
|
+
if (current && next && next.level > current.level) void focusTreeNode(next.node.value);
|
|
414
|
+
}
|
|
415
|
+
} else if (event.key === 'ArrowLeft') {
|
|
416
|
+
event.preventDefault();
|
|
417
|
+
if (nodeHasChildren && expanded) toggleExpand(node.value);
|
|
418
|
+
else {
|
|
419
|
+
const parent = findParent(options, node.value);
|
|
420
|
+
if (parent) void focusTreeNode(parent.value);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function clearAll(event?: MouseEvent | KeyboardEvent) {
|
|
119
426
|
event?.stopPropagation();
|
|
427
|
+
if (disabled) return;
|
|
428
|
+
selectionAttempt = undefined;
|
|
120
429
|
const next = multiple ? [] : undefined;
|
|
121
430
|
value = next;
|
|
122
431
|
onchange?.(next);
|
|
123
432
|
}
|
|
124
433
|
|
|
125
|
-
function removeSingle(val: string | number, event?: MouseEvent) {
|
|
434
|
+
function removeSingle(val: string | number, event?: MouseEvent | KeyboardEvent) {
|
|
126
435
|
event?.stopPropagation();
|
|
436
|
+
if (disabled) return;
|
|
437
|
+
selectionAttempt = undefined;
|
|
127
438
|
if (multiple) {
|
|
128
439
|
const next = selectedValues.filter((v) => v !== val);
|
|
129
440
|
value = next;
|
|
@@ -152,44 +463,91 @@
|
|
|
152
463
|
}
|
|
153
464
|
|
|
154
465
|
const visibleOptions = $derived(filterTree(options, searchQuery));
|
|
466
|
+
|
|
467
|
+
interface VisibleNode {
|
|
468
|
+
node: TreeSelectOption;
|
|
469
|
+
level: number;
|
|
470
|
+
position: number;
|
|
471
|
+
size: number;
|
|
472
|
+
}
|
|
473
|
+
const flatVisibleNodes = $derived.by(() => {
|
|
474
|
+
const result: VisibleNode[] = [];
|
|
475
|
+
function visit(nodes: TreeSelectOption[], level: number): void {
|
|
476
|
+
nodes.forEach((node, index) => {
|
|
477
|
+
result.push({ node, level, position: index + 1, size: nodes.length });
|
|
478
|
+
if (hasChildren(node) && isNodeExpanded(node) && node.children) visit(node.children, level + 1);
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
visit(visibleOptions, 0);
|
|
482
|
+
return result;
|
|
483
|
+
});
|
|
484
|
+
const rowHeight = $derived(Number.isSafeInteger(itemHeight) && itemHeight >= 32 && itemHeight <= 100 ? itemHeight : 36);
|
|
485
|
+
const windowHeight = $derived(Number.isSafeInteger(viewportHeight) && viewportHeight >= 120 && viewportHeight <= 800 ? viewportHeight : 280);
|
|
486
|
+
const shouldVirtualize = $derived(virtualized && flatVisibleNodes.length >= 200);
|
|
487
|
+
const totalHeight = $derived(flatVisibleNodes.length * rowHeight);
|
|
488
|
+
const clampedScrollTop = $derived(Math.max(0, Math.min(treeScrollTop, totalHeight - windowHeight)));
|
|
489
|
+
const virtualStart = $derived(Math.max(0, Math.floor(clampedScrollTop / rowHeight) - 5));
|
|
490
|
+
const virtualEnd = $derived(Math.min(flatVisibleNodes.length, Math.ceil((clampedScrollTop + windowHeight) / rowHeight) + 5));
|
|
491
|
+
|
|
492
|
+
$effect(() => {
|
|
493
|
+
// 搜索和外部数据替换从顶部开始;折叠或数据缩短只钳制窗口。
|
|
494
|
+
void searchQuery;
|
|
495
|
+
void treeRevision;
|
|
496
|
+
treeScrollTop = 0;
|
|
497
|
+
if (treeRoot) treeRoot.scrollTop = 0;
|
|
498
|
+
});
|
|
499
|
+
$effect(() => {
|
|
500
|
+
if (treeScrollTop !== clampedScrollTop) {
|
|
501
|
+
treeScrollTop = clampedScrollTop;
|
|
502
|
+
if (treeRoot) treeRoot.scrollTop = clampedScrollTop;
|
|
503
|
+
}
|
|
504
|
+
});
|
|
155
505
|
</script>
|
|
156
506
|
|
|
157
|
-
{#snippet treeNode(node: TreeSelectOption, level: number)}
|
|
158
|
-
{@const
|
|
507
|
+
{#snippet treeNode(node: TreeSelectOption, level: number, position: number, size: number, recursive = true)}
|
|
508
|
+
{@const nodeHasChildren = hasChildren(optionMap.get(node.value) ?? node)}
|
|
159
509
|
{@const expanded = isNodeExpanded(node)}
|
|
160
|
-
{@const selected =
|
|
161
|
-
{@const
|
|
510
|
+
{@const selected = isNodeSelected(node)}
|
|
511
|
+
{@const partial = isPartiallySelected(node)}
|
|
512
|
+
{@const busy = selectionAttempt?.key === node.value || loadingValues.has(node.value)}
|
|
513
|
+
{@const unavailable = disabled || disabledValues.has(node.value)}
|
|
162
514
|
|
|
163
515
|
<div class="svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed">
|
|
164
516
|
<div
|
|
165
517
|
role="treeitem"
|
|
166
518
|
tabindex="0"
|
|
167
519
|
aria-selected={selected}
|
|
168
|
-
aria-
|
|
520
|
+
aria-checked={multiple ? (partial ? 'mixed' : selected) : undefined}
|
|
521
|
+
aria-disabled={unavailable ? 'true' : undefined}
|
|
522
|
+
aria-busy={busy}
|
|
523
|
+
aria-level={level + 1}
|
|
524
|
+
aria-posinset={position}
|
|
525
|
+
aria-setsize={size}
|
|
526
|
+
aria-expanded={nodeHasChildren ? expanded : undefined}
|
|
527
|
+
data-tree-value={String(node.value)}
|
|
528
|
+
data-tree-key={treeKey(node.value)}
|
|
169
529
|
class={cn(
|
|
170
530
|
'group svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-8ef2268efbbc svadmin-u-58284b4ea568 svadmin-u-421ac2be5045 svadmin-u-d5eab218aa34 svadmin-u-ec0091ee009b svadmin-u-359090c2d529 svadmin-u-ceb69a6b0e5f svadmin-u-34516836730d svadmin-u-7f6912283f11',
|
|
171
531
|
selected ? 'svadmin-u-375dc44df6e9 svadmin-u-20aaf08a7ed1 svadmin-u-2689f3958069' : 'svadmin-u-68646cdcc246 svadmin-u-d4108abe6359',
|
|
172
|
-
|
|
532
|
+
unavailable && 'svadmin-u-0b8c506a0596 svadmin-u-29b733e4c162 svadmin-u-a4326536b8f5'
|
|
173
533
|
)}
|
|
174
|
-
style=
|
|
175
|
-
onclick={() =>
|
|
176
|
-
|
|
177
|
-
}}
|
|
178
|
-
onkeydown={(e) => {
|
|
179
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
180
|
-
e.preventDefault();
|
|
181
|
-
if (selectable) handleSelect(node);
|
|
182
|
-
}
|
|
183
|
-
}}
|
|
534
|
+
style={`padding-left:${level * 16 + 8}px;${recursive ? '' : `height:${rowHeight}px;min-height:${rowHeight}px;box-sizing:border-box;overflow:hidden;`}`}
|
|
535
|
+
onclick={() => void handleSelect(node)}
|
|
536
|
+
onkeydown={(e) => handleTreeKeydown(e, node, expanded)}
|
|
184
537
|
>
|
|
185
538
|
<div class="svadmin-u-60fbb7713999 svadmin-u-7e0b7cdf1a94 svadmin-u-3960ffc248d9 svadmin-u-58284b4ea568">
|
|
186
|
-
{#if
|
|
539
|
+
{#if nodeHasChildren}
|
|
187
540
|
<button
|
|
188
541
|
type="button"
|
|
542
|
+
disabled={unavailable}
|
|
543
|
+
aria-label={`${node.label}: ${i18n.t('tree.toggleChildren')}`}
|
|
544
|
+
aria-busy={busy}
|
|
189
545
|
class="svadmin-u-60fbb7713999 svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3 svadmin-u-012fbd121f37 svadmin-u-3960ffc248d9 svadmin-u-86843cf1e227 svadmin-u-07389a777c1f svadmin-u-bfa603190748 svadmin-u-8e551981c8d7"
|
|
190
|
-
onclick={(e) =>
|
|
546
|
+
onclick={(e) => void toggleExpandAsync(node, e)}
|
|
191
547
|
>
|
|
192
|
-
{#if
|
|
548
|
+
{#if busy}
|
|
549
|
+
<Loader2 class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c svadmin-u-afbdd13a380e" />
|
|
550
|
+
{:else if expanded}
|
|
193
551
|
<ChevronDown class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" />
|
|
194
552
|
{:else}
|
|
195
553
|
<ChevronRight class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" />
|
|
@@ -202,15 +560,26 @@
|
|
|
202
560
|
<span class="svadmin-u-f283ea9bea0e">{node.label}</span>
|
|
203
561
|
</div>
|
|
204
562
|
|
|
205
|
-
{#if
|
|
563
|
+
{#if loadErrors.has(node.value) || selectionErrors.has(node.value)}
|
|
564
|
+
<span role="alert" class="svadmin-u-bfa603190748">{i18n.t(selectionErrors.has(node.value) ? 'tree.selectionFailed' : 'tree.loadFailed')}</span>
|
|
565
|
+
<button type="button" aria-label={`${node.label}: ${i18n.t('common.retry')}`}
|
|
566
|
+
disabled={unavailable}
|
|
567
|
+
title={i18n.t('common.retry')} onclick={(e) => retryChildren(node, e)}>
|
|
568
|
+
<RotateCw class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" />
|
|
569
|
+
</button>
|
|
570
|
+
{/if}
|
|
571
|
+
|
|
572
|
+
{#if partial}
|
|
573
|
+
<Minus class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c svadmin-u-012fbd121f37 svadmin-u-20aaf08a7ed1" />
|
|
574
|
+
{:else if selected}
|
|
206
575
|
<Check class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c svadmin-u-012fbd121f37 svadmin-u-20aaf08a7ed1" />
|
|
207
576
|
{/if}
|
|
208
577
|
</div>
|
|
209
578
|
|
|
210
|
-
{#if
|
|
579
|
+
{#if recursive && nodeHasChildren && expanded && node.children}
|
|
211
580
|
<div role="group" class="svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed">
|
|
212
|
-
{#each node.children as child (child.value)}
|
|
213
|
-
{@render treeNode(child, level + 1)}
|
|
581
|
+
{#each node.children as child, index (treeKey(child.value))}
|
|
582
|
+
{@render treeNode(child, level + 1, index + 1, node.children.length)}
|
|
214
583
|
{/each}
|
|
215
584
|
</div>
|
|
216
585
|
{/if}
|
|
@@ -244,10 +613,14 @@
|
|
|
244
613
|
<span
|
|
245
614
|
role="button"
|
|
246
615
|
tabindex="0"
|
|
616
|
+
aria-label={i18n.t('tree.removeSelection', { label: opt?.label ?? String(val) })}
|
|
247
617
|
class="svadmin-u-51e95020d6f2 svadmin-u-34516836730d"
|
|
248
618
|
onclick={(e) => removeSingle(val, e)}
|
|
249
619
|
onkeydown={(e) => {
|
|
250
|
-
if (e.key === 'Enter'
|
|
620
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
621
|
+
e.preventDefault();
|
|
622
|
+
removeSingle(val, e);
|
|
623
|
+
}
|
|
251
624
|
}}
|
|
252
625
|
>
|
|
253
626
|
<X class="svadmin-u-6a60c09e6aaa svadmin-u-9cea05671a29" />
|
|
@@ -267,10 +640,14 @@
|
|
|
267
640
|
<span
|
|
268
641
|
role="button"
|
|
269
642
|
tabindex="0"
|
|
643
|
+
aria-label={i18n.t('common.clearSelection')}
|
|
270
644
|
class="svadmin-u-ea7b2e9e070e svadmin-u-34516836730d svadmin-u-de8350a3bbad"
|
|
271
645
|
onclick={clearAll}
|
|
272
646
|
onkeydown={(e) => {
|
|
273
|
-
if (e.key === 'Enter'
|
|
647
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
648
|
+
e.preventDefault();
|
|
649
|
+
clearAll(e);
|
|
650
|
+
}
|
|
274
651
|
}}
|
|
275
652
|
>
|
|
276
653
|
<X class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" />
|
|
@@ -295,14 +672,31 @@
|
|
|
295
672
|
</div>
|
|
296
673
|
{/if}
|
|
297
674
|
|
|
298
|
-
<div
|
|
299
|
-
{
|
|
300
|
-
|
|
675
|
+
<div
|
|
676
|
+
bind:this={treeRoot}
|
|
677
|
+
role="tree"
|
|
678
|
+
aria-multiselectable={multiple ? 'true' : undefined}
|
|
679
|
+
class="svadmin-u-67d7e383dca6 svadmin-u-92bf82f493b1 svadmin-u-e2eedc5718f0 svadmin-u-eda955402ba6"
|
|
680
|
+
style={shouldVirtualize ? `height:${windowHeight}px;max-height:${windowHeight}px;overflow-y:auto;padding:0;` : undefined}
|
|
681
|
+
onscroll={(event) => { treeScrollTop = event.currentTarget.scrollTop; }}
|
|
682
|
+
>
|
|
683
|
+
{#if shouldVirtualize}
|
|
684
|
+
<div style={`height:${totalHeight}px;position:relative;`}>
|
|
685
|
+
<div style={`position:absolute;top:${virtualStart * rowHeight}px;left:0;right:0;`}>
|
|
686
|
+
{#each flatVisibleNodes.slice(virtualStart, virtualEnd) as entry (treeKey(entry.node.value))}
|
|
687
|
+
{@render treeNode(entry.node, entry.level, entry.position, entry.size, false)}
|
|
688
|
+
{/each}
|
|
689
|
+
</div>
|
|
690
|
+
</div>
|
|
691
|
+
{:else if visibleOptions.length > 0}
|
|
692
|
+
{#each visibleOptions as rootNode, index (treeKey(rootNode.value))}
|
|
693
|
+
{@render treeNode(rootNode, 0, index + 1, visibleOptions.length)}
|
|
694
|
+
{/each}
|
|
301
695
|
{:else}
|
|
302
696
|
<div class="svadmin-u-940911bf310c svadmin-u-ca6bf63030aa svadmin-u-359090c2d529 svadmin-u-bfa603190748">
|
|
303
697
|
{i18n.t('common.noData', undefined) ?? '无匹配选项'}
|
|
304
698
|
</div>
|
|
305
|
-
{/
|
|
699
|
+
{/if}
|
|
306
700
|
</div>
|
|
307
701
|
</Popover.Content>
|
|
308
702
|
</Popover.Root>
|
|
@@ -2,6 +2,7 @@ export interface TreeSelectOption {
|
|
|
2
2
|
value: string | number;
|
|
3
3
|
label: string;
|
|
4
4
|
children?: TreeSelectOption[];
|
|
5
|
+
hasChildren?: boolean;
|
|
5
6
|
disabled?: boolean;
|
|
6
7
|
expanded?: boolean;
|
|
7
8
|
}
|
|
@@ -14,9 +15,13 @@ interface Props {
|
|
|
14
15
|
onlyLeafSelectable?: boolean;
|
|
15
16
|
disabled?: boolean;
|
|
16
17
|
allowClear?: boolean;
|
|
18
|
+
loadChildren?: (node: TreeSelectOption) => Promise<TreeSelectOption[]>;
|
|
19
|
+
virtualized?: boolean;
|
|
20
|
+
itemHeight?: number;
|
|
21
|
+
viewportHeight?: number;
|
|
17
22
|
class?: string;
|
|
18
23
|
onchange?: (value: string | number | (string | number)[] | undefined) => void;
|
|
19
24
|
}
|
|
20
|
-
declare const TreeSelect: import("svelte").Component<Props, {}, "value">;
|
|
25
|
+
declare const TreeSelect: import("svelte").Component<Props, {}, "value" | "options">;
|
|
21
26
|
type TreeSelect = ReturnType<typeof TreeSelect>;
|
|
22
27
|
export default TreeSelect;
|