@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.
Files changed (276) hide show
  1. package/README.md +62 -22
  2. package/THIRD_PARTY_NOTICES.md +52 -0
  3. package/dist/app.css +8488 -6297
  4. package/dist/app.theme.css +8514 -6374
  5. package/dist/class-properties.generated.d.ts +1 -0
  6. package/dist/class-properties.generated.js +2 -0
  7. package/dist/classnames.d.ts +8 -0
  8. package/dist/classnames.js +26 -0
  9. package/dist/component-registry.d.ts +14977 -0
  10. package/dist/component-registry.js +216 -0
  11. package/dist/components/AboutSettings.svelte +7 -1
  12. package/dist/components/AboutSettings.svelte.d.ts +4 -1
  13. package/dist/components/ActivityFeed.svelte +9 -3
  14. package/dist/components/ActivityFeed.svelte.d.ts +3 -0
  15. package/dist/components/AdminApp.svelte +19 -10
  16. package/dist/components/AdminApp.svelte.d.ts +6 -0
  17. package/dist/components/ApiSettings.svelte +41 -5
  18. package/dist/components/ApprovalCenter.svelte +283 -0
  19. package/dist/components/ApprovalCenter.svelte.d.ts +10 -0
  20. package/dist/components/AuditLogViewer.svelte +3 -1
  21. package/dist/components/AutoForm.svelte +53 -9
  22. package/dist/components/AutoForm.svelte.d.ts +3 -0
  23. package/dist/components/AutoTable.svelte +846 -43
  24. package/dist/components/AutoTable.svelte.d.ts +22 -1
  25. package/dist/components/BoundRecordDetailDrawer.svelte +17 -2
  26. package/dist/components/BoundRecordDetailDrawer.svelte.d.ts +4 -0
  27. package/dist/components/Breadcrumbs.svelte +11 -1
  28. package/dist/components/CancelTaskButton.svelte +12 -42
  29. package/dist/components/ColorPicker.svelte +73 -0
  30. package/dist/components/ColorPicker.svelte.d.ts +15 -0
  31. package/dist/components/ComboboxField.svelte +143 -34
  32. package/dist/components/ComboboxField.svelte.d.ts +4 -2
  33. package/dist/components/CreatePage.svelte +4 -0
  34. package/dist/components/CreatePage.svelte.d.ts +2 -0
  35. package/dist/components/DashboardView.svelte +99 -0
  36. package/dist/components/DashboardView.svelte.d.ts +14 -0
  37. package/dist/components/DatePicker.svelte +10 -0
  38. package/dist/components/DatePicker.svelte.d.ts +6 -0
  39. package/dist/components/DateRangeInput.svelte +155 -0
  40. package/dist/components/DateRangeInput.svelte.d.ts +35 -0
  41. package/dist/components/DateRangePicker.svelte +10 -0
  42. package/dist/components/DateRangePicker.svelte.d.ts +6 -0
  43. package/dist/components/DateTimeInput.svelte +134 -0
  44. package/dist/components/DateTimeInput.svelte.d.ts +27 -0
  45. package/dist/components/DateTimePicker.svelte +10 -0
  46. package/dist/components/DateTimePicker.svelte.d.ts +6 -0
  47. package/dist/components/DevTools.svelte +36 -28
  48. package/dist/components/DevframePanel.svelte +123 -0
  49. package/dist/components/DevframePanel.svelte.d.ts +3 -0
  50. package/dist/components/EditPage.svelte +4 -0
  51. package/dist/components/EditPage.svelte.d.ts +2 -0
  52. package/dist/components/ErrorSummary.svelte +52 -0
  53. package/dist/components/ErrorSummary.svelte.d.ts +14 -0
  54. package/dist/components/FieldRenderer.svelte +172 -99
  55. package/dist/components/FileUpload.svelte +412 -0
  56. package/dist/components/FileUpload.svelte.d.ts +43 -0
  57. package/dist/components/FilterBuilder.svelte +244 -265
  58. package/dist/components/FilterBuilder.svelte.d.ts +3 -0
  59. package/dist/components/FilterCollectionInput.svelte +80 -0
  60. package/dist/components/FilterCollectionInput.svelte.d.ts +10 -0
  61. package/dist/components/GanttChart.svelte +66 -14
  62. package/dist/components/GanttChart.svelte.d.ts +6 -0
  63. package/dist/components/Header.svelte +18 -2
  64. package/dist/components/ImportWizard.svelte +509 -100
  65. package/dist/components/ImportWizard.svelte.d.ts +11 -0
  66. package/dist/components/JsonSchemaForm.svelte +322 -98
  67. package/dist/components/JsonSchemaForm.svelte.d.ts +18 -1
  68. package/dist/components/KanbanBoard.svelte +195 -40
  69. package/dist/components/KanbanBoard.svelte.d.ts +9 -2
  70. package/dist/components/Layout.svelte +4 -3
  71. package/dist/components/LazyAutoForm.svelte +28 -0
  72. package/dist/components/LazyAutoForm.svelte.d.ts +8 -0
  73. package/dist/components/LazyAutoTable.svelte +28 -0
  74. package/dist/components/LazyAutoTable.svelte.d.ts +8 -0
  75. package/dist/components/LazySettingsPage.svelte +28 -0
  76. package/dist/components/LazySettingsPage.svelte.d.ts +8 -0
  77. package/dist/components/LazyShowPage.svelte +28 -0
  78. package/dist/components/LazyShowPage.svelte.d.ts +8 -0
  79. package/dist/components/LazyTaskQueueDrawer.svelte.d.ts +1 -1
  80. package/dist/components/ListPage.svelte +9 -1
  81. package/dist/components/ListPage.svelte.d.ts +5 -0
  82. package/dist/components/LoginPage.svelte +2 -2
  83. package/dist/components/MoneyInput.svelte +25 -0
  84. package/dist/components/MoneyInput.svelte.d.ts +20 -0
  85. package/dist/components/MultiTabKeepAlive.svelte +11 -6
  86. package/dist/components/NotificationsSettings.svelte +69 -2
  87. package/dist/components/NotificationsSettings.svelte.d.ts +25 -1
  88. package/dist/components/NumberInput.svelte +25 -0
  89. package/dist/components/NumberInput.svelte.d.ts +19 -0
  90. package/dist/components/NumericInput.svelte +110 -0
  91. package/dist/components/NumericInput.svelte.d.ts +23 -0
  92. package/dist/components/PercentInput.svelte +25 -0
  93. package/dist/components/PercentInput.svelte.d.ts +20 -0
  94. package/dist/components/PermissionMatrix.svelte +20 -1
  95. package/dist/components/PivotExportButton.svelte +157 -0
  96. package/dist/components/PivotExportButton.svelte.d.ts +16 -0
  97. package/dist/components/PivotTable.svelte +229 -74
  98. package/dist/components/PivotTable.svelte.d.ts +19 -2
  99. package/dist/components/QuickEditDrawer.svelte +4 -1
  100. package/dist/components/QuickEditDrawer.svelte.d.ts +2 -0
  101. package/dist/components/RadioGroup.svelte +121 -0
  102. package/dist/components/RadioGroup.svelte.d.ts +43 -0
  103. package/dist/components/RangeSlider.svelte +127 -0
  104. package/dist/components/RangeSlider.svelte.d.ts +22 -0
  105. package/dist/components/Rate.svelte +121 -0
  106. package/dist/components/Rate.svelte.d.ts +16 -0
  107. package/dist/components/RecordDetailDrawer.svelte +6 -2
  108. package/dist/components/RecordDetailDrawer.svelte.d.ts +4 -0
  109. package/dist/components/RelationPicker.svelte +341 -0
  110. package/dist/components/RelationPicker.svelte.d.ts +26 -0
  111. package/dist/components/ResourceOperationsPage.svelte +25 -19
  112. package/dist/components/ResourceOperationsPage.svelte.d.ts +5 -1
  113. package/dist/components/RetryTaskButton.svelte +12 -42
  114. package/dist/components/SecuritySettings.svelte +29 -5
  115. package/dist/components/SegmentedControl.svelte +145 -0
  116. package/dist/components/SegmentedControl.svelte.d.ts +38 -0
  117. package/dist/components/SettingsPage.svelte +3 -1
  118. package/dist/components/SettingsPage.svelte.d.ts +1 -0
  119. package/dist/components/ShowInferencer.svelte +4 -2
  120. package/dist/components/ShowInferencer.svelte.d.ts +2 -0
  121. package/dist/components/ShowPage.svelte +25 -6
  122. package/dist/components/ShowPage.svelte.d.ts +5 -0
  123. package/dist/components/Sidebar.svelte +5 -5
  124. package/dist/components/Slider.svelte +100 -0
  125. package/dist/components/Slider.svelte.d.ts +19 -0
  126. package/dist/components/SpreadsheetView.svelte +1037 -124
  127. package/dist/components/SpreadsheetView.svelte.d.ts +19 -0
  128. package/dist/components/StepsForm.svelte +5 -5
  129. package/dist/components/SvarAutoTable.svelte +39 -0
  130. package/dist/components/SvarAutoTable.svelte.d.ts +13 -0
  131. package/dist/components/SvarDataGrid.svelte +362 -0
  132. package/dist/components/SvarDataGrid.svelte.d.ts +57 -0
  133. package/dist/components/SvarGridCell.svelte +10 -0
  134. package/dist/components/SvarGridCell.svelte.d.ts +7 -0
  135. package/dist/components/SvarResourceRead.svelte +50 -0
  136. package/dist/components/SvarResourceRead.svelte.d.ts +23 -0
  137. package/dist/components/SvarResourceTable.svelte +63 -0
  138. package/dist/components/SvarResourceTable.svelte.d.ts +42 -0
  139. package/dist/components/SvarResourceTableView.svelte +364 -0
  140. package/dist/components/SvarResourceTableView.svelte.d.ts +4 -0
  141. package/dist/components/TagsInput.svelte +178 -0
  142. package/dist/components/TagsInput.svelte.d.ts +17 -0
  143. package/dist/components/TaskQueueDrawer.svelte +72 -16
  144. package/dist/components/TimePicker.svelte +10 -0
  145. package/dist/components/TimePicker.svelte.d.ts +6 -0
  146. package/dist/components/TreeSelect.svelte +440 -46
  147. package/dist/components/TreeSelect.svelte.d.ts +6 -1
  148. package/dist/components/TreeTable.svelte +484 -82
  149. package/dist/components/TreeTable.svelte.d.ts +24 -1
  150. package/dist/components/TwoFactorAuthPage.svelte +85 -48
  151. package/dist/components/TwoFactorAuthPage.svelte.d.ts +12 -1
  152. package/dist/components/VirtualTable.svelte +116 -20
  153. package/dist/components/VirtualTable.svelte.d.ts +5 -0
  154. package/dist/components/account/CompanyProfilePage.svelte +74 -38
  155. package/dist/components/account/GetStartedPage.svelte +2 -1
  156. package/dist/components/account/ImportMembersPage.svelte +22 -167
  157. package/dist/components/account/ImportMembersPage.svelte.d.ts +4 -1
  158. package/dist/components/account/MemberDirectory.svelte +156 -0
  159. package/dist/components/account/MemberDirectory.svelte.d.ts +19 -0
  160. package/dist/components/account/MembersStarterPage.svelte +9 -55
  161. package/dist/components/account/MembersStarterPage.svelte.d.ts +5 -1
  162. package/dist/components/account/SecurityLogPage.svelte +4 -1
  163. package/dist/components/account/SettingsPlainPage.svelte +7 -38
  164. package/dist/components/account/SettingsPlainPage.svelte.d.ts +4 -1
  165. package/dist/components/account/SettingsSidebarPage.svelte +7 -20
  166. package/dist/components/account/TeamMembersPage.svelte +4 -20
  167. package/dist/components/account/TeamMembersPage.svelte.d.ts +5 -1
  168. package/dist/components/account/UserProfilePage.svelte +3 -25
  169. package/dist/components/account/UserProfilePage.svelte.d.ts +17 -2
  170. package/dist/components/auto-table-grid.d.ts +26 -0
  171. package/dist/components/auto-table-grid.js +1 -0
  172. package/dist/components/buttons/ExportButton.svelte +56 -12
  173. package/dist/components/buttons/ExportButton.svelte.d.ts +11 -0
  174. package/dist/components/content/ContentPageHeader.svelte +12 -9
  175. package/dist/components/content/ContentPageShell.svelte +6 -9
  176. package/dist/components/content/FileBrowser.svelte +35 -0
  177. package/dist/components/content/FileBrowser.svelte.d.ts +14 -0
  178. package/dist/components/content/MetricBlock.svelte +10 -13
  179. package/dist/components/content/NetworkTable.svelte +3 -3
  180. package/dist/components/content/NetworkTable.svelte.d.ts +1 -0
  181. package/dist/components/content/PageToolbar.svelte +6 -10
  182. package/dist/components/content/ReferenceNotice.svelte +5 -0
  183. package/dist/components/content/ReferenceNotice.svelte.d.ts +18 -0
  184. package/dist/components/content/SectionHeader.svelte +8 -13
  185. package/dist/components/content/SettingsFieldRow.svelte +9 -23
  186. package/dist/components/content/SettingsGroup.svelte +11 -27
  187. package/dist/components/content/StatusBadge.svelte +3 -8
  188. package/dist/components/content/WorkspaceLayout.svelte +8 -12
  189. package/dist/components/enterprise/filter-tree.d.ts +48 -0
  190. package/dist/components/enterprise/filter-tree.js +224 -0
  191. package/dist/components/enterprise/json-schema-form.d.ts +39 -0
  192. package/dist/components/enterprise/json-schema-form.js +195 -0
  193. package/dist/components/enterprise/recursive-schema-form.d.ts +30 -0
  194. package/dist/components/enterprise/recursive-schema-form.js +196 -0
  195. package/dist/components/enterprise/spreadsheet-formula.d.ts +12 -0
  196. package/dist/components/enterprise/spreadsheet-formula.js +245 -0
  197. package/dist/components/export-reference.d.ts +2 -0
  198. package/dist/components/export-reference.js +9 -0
  199. package/dist/components/fieldComponentMap.js +2 -0
  200. package/dist/components/fields/DateField.svelte +12 -12
  201. package/dist/components/fields/date-display.d.ts +6 -0
  202. package/dist/components/fields/date-display.js +24 -0
  203. package/dist/components/fields/date-field-value.d.ts +2 -0
  204. package/dist/components/fields/date-field-value.js +13 -0
  205. package/dist/components/json-schema-form-state.d.ts +37 -0
  206. package/dist/components/json-schema-form-state.js +168 -0
  207. package/dist/components/network/TeamCrewTablePage.svelte +6 -1
  208. package/dist/components/network/UserCardsNFTPage.svelte +2 -0
  209. package/dist/components/profile/PublicProfilePage.svelte +2 -0
  210. package/dist/components/record-detail.svelte.d.ts +3 -0
  211. package/dist/components/record-detail.svelte.js +10 -4
  212. package/dist/components/saved-list-views.d.ts +74 -1
  213. package/dist/components/saved-list-views.js +191 -3
  214. package/dist/components/svar-grid-cells.d.ts +14 -0
  215. package/dist/components/svar-grid-cells.js +2 -0
  216. package/dist/components/svar-grid-contract.d.ts +80 -0
  217. package/dist/components/svar-grid-contract.js +159 -0
  218. package/dist/components/svar-grid-interactions.d.ts +54 -0
  219. package/dist/components/svar-grid-interactions.js +174 -0
  220. package/dist/components/svar-grid-loading.d.ts +40 -0
  221. package/dist/components/svar-grid-loading.js +180 -0
  222. package/dist/components/svar-grid-operations.d.ts +37 -0
  223. package/dist/components/svar-grid-operations.js +186 -0
  224. package/dist/components/svar-grid-panes.d.ts +34 -0
  225. package/dist/components/svar-grid-panes.js +66 -0
  226. package/dist/components/svar-grid-resource-loading.d.ts +18 -0
  227. package/dist/components/svar-grid-resource-loading.js +68 -0
  228. package/dist/components/svar-grid-resource-session.svelte.d.ts +29 -0
  229. package/dist/components/svar-grid-resource-session.svelte.js +61 -0
  230. package/dist/components/table-contract.d.ts +12 -1
  231. package/dist/components/task-action.svelte.d.ts +13 -0
  232. package/dist/components/task-action.svelte.js +73 -0
  233. package/dist/components/ui/DesignSystemShowcase.svelte +164 -0
  234. package/dist/components/ui/DesignSystemShowcase.svelte.d.ts +3 -0
  235. package/dist/components/ui/badge/badge-variants.d.ts +3 -2
  236. package/dist/components/ui/badge/badge-variants.js +2 -1
  237. package/dist/components/ui/badge/badge.svelte +3 -0
  238. package/dist/components/ui/badge/badge.svelte.d.ts +1 -0
  239. package/dist/components/ui/button/button-variants.d.ts +5 -4
  240. package/dist/components/ui/button/button-variants.js +7 -2
  241. package/dist/components/ui/checkbox/checkbox.svelte +5 -1
  242. package/dist/components/ui/checkbox/checkbox.svelte.d.ts +5 -1
  243. package/dist/components/ui/input/input.svelte +8 -6
  244. package/dist/components/ui/input/input.svelte.d.ts +4 -1
  245. package/dist/components/ui/select/select.svelte +4 -1
  246. package/dist/components/ui/select/select.svelte.d.ts +2 -1
  247. package/dist/components/ui/textarea/textarea.svelte +2 -1
  248. package/dist/components.css +3110 -0
  249. package/dist/date-input-policy.d.ts +4 -0
  250. package/dist/date-input-policy.js +35 -0
  251. package/dist/design-contract.d.ts +13 -0
  252. package/dist/design-contract.js +11 -0
  253. package/dist/design-system-components.d.ts +100 -0
  254. package/dist/design-system-components.js +101 -0
  255. package/dist/design-system.d.ts +15 -0
  256. package/dist/design-system.js +7 -0
  257. package/dist/devtools-bridge.d.ts +80 -0
  258. package/dist/devtools-bridge.js +92 -0
  259. package/dist/index.d.ts +50 -1
  260. package/dist/index.js +32 -2
  261. package/dist/page-chrome.css +12 -49
  262. package/dist/recipes.d.ts +960 -0
  263. package/dist/recipes.js +264 -0
  264. package/dist/rendering/index.d.ts +87 -0
  265. package/dist/rendering/index.js +117 -0
  266. package/dist/styles/aliases.css +49 -0
  267. package/dist/styles/compatibility.epilogue.css +384 -0
  268. package/dist/styles/compatibility.prelude.css +5513 -0
  269. package/dist/styles/conditional-states.css +28 -0
  270. package/dist/styles/conditional-utilities.css +22 -0
  271. package/dist/tree-table-model.d.ts +24 -0
  272. package/dist/tree-table-model.js +111 -0
  273. package/dist/utils.d.ts +1 -1
  274. package/dist/utils.js +1 -1
  275. package/package.json +40 -10
  276. 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 { ChevronRight, ChevronDown, Check, X, Search, ChevronsUpDown } from '@lucide/svelte';
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 handleSelect(node: TreeSelectOption) {
94
- if (node.disabled) return;
95
- const hasChildren = node.children && node.children.length > 0;
96
- if (onlyLeafSelectable && hasChildren) {
97
- toggleExpand(node.value);
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
- let next: (string | number)[];
103
- if (isSelected(node.value)) {
104
- next = selectedValues.filter((v) => v !== node.value);
105
- } else {
106
- next = [...selectedValues, node.value];
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 clearAll(event?: MouseEvent) {
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 hasChildren = Boolean(node.children && node.children.length > 0)}
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 = isSelected(node.value)}
161
- {@const selectable = !node.disabled && (!onlyLeafSelectable || !hasChildren)}
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-expanded={hasChildren ? expanded : undefined}
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
- node.disabled && 'svadmin-u-0b8c506a0596 svadmin-u-29b733e4c162 svadmin-u-a4326536b8f5'
532
+ unavailable && 'svadmin-u-0b8c506a0596 svadmin-u-29b733e4c162 svadmin-u-a4326536b8f5'
173
533
  )}
174
- style="padding-left: {level * 16 + 8}px;"
175
- onclick={() => {
176
- if (selectable) handleSelect(node);
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 hasChildren}
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) => toggleExpand(node.value, e)}
546
+ onclick={(e) => void toggleExpandAsync(node, e)}
191
547
  >
192
- {#if expanded}
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 selected}
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 hasChildren && expanded && node.children}
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') removeSingle(val, e as never);
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') clearAll(e as never);
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 role="tree" class="svadmin-u-67d7e383dca6 svadmin-u-92bf82f493b1 svadmin-u-e2eedc5718f0 svadmin-u-eda955402ba6">
299
- {#each visibleOptions as rootNode (rootNode.value)}
300
- {@render treeNode(rootNode, 0)}
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
- {/each}
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;