@svadmin/ui 0.88.1 → 0.91.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/dist/app.css +4 -0
- package/dist/app.theme.css +4 -0
- package/dist/component-registry.svelte.d.ts +2 -2
- package/dist/components/AboutSettings.svelte +23 -4
- package/dist/components/AdminApp.svelte +6 -4
- package/dist/components/AdminApp.svelte.d.ts +3 -3
- package/dist/components/ApiSettings.svelte +43 -27
- package/dist/components/ApprovalActionCard.svelte +8 -9
- package/dist/components/ApprovalCenter.svelte +24 -5
- package/dist/components/AuditLogViewer.svelte +2 -2
- package/dist/components/AutoComplete.svelte +610 -0
- package/dist/components/AutoComplete.svelte.d.ts +25 -0
- package/dist/components/AutoForm.svelte +263 -18
- package/dist/components/AutoForm.svelte.d.ts +19 -2
- package/dist/components/AutoTable.svelte +135 -35
- package/dist/components/AutoTable.svelte.d.ts +8 -0
- package/dist/components/BatchActionBar.svelte +42 -14
- package/dist/components/BatchActionBar.svelte.d.ts +5 -0
- package/dist/components/BoundRecordDetailDrawer.svelte +5 -3
- package/dist/components/CancelTaskButton.svelte +14 -1
- package/dist/components/Cascader.svelte +412 -53
- package/dist/components/Cascader.svelte.d.ts +7 -1
- package/dist/components/ColorPicker.svelte +1 -1
- package/dist/components/ColorPicker.svelte.d.ts +1 -1
- package/dist/components/ComboboxField.svelte +270 -34
- package/dist/components/ComboboxField.svelte.d.ts +4 -0
- package/dist/components/CommandPalette.svelte +4 -4
- package/dist/components/DashboardView.svelte +73 -7
- package/dist/components/DashboardView.svelte.d.ts +3 -1
- package/dist/components/DateRangeInput.svelte +8 -1
- package/dist/components/DetailWorkspaceAsyncSection.svelte +107 -0
- package/dist/components/DetailWorkspaceAsyncSection.svelte.d.ts +36 -0
- package/dist/components/DetailWorkspaceSections.svelte +233 -0
- package/dist/components/DetailWorkspaceSections.svelte.d.ts +9 -0
- package/dist/components/DrawerForm.svelte +78 -9
- package/dist/components/DrawerForm.svelte.d.ts +3 -3
- package/dist/components/ErrorBoundary.svelte +1 -1
- package/dist/components/ErrorComponent.svelte +19 -6
- package/dist/components/ErrorComponent.svelte.d.ts +1 -0
- package/dist/components/ErrorSummary.svelte +9 -4
- package/dist/components/FieldRenderer.svelte +203 -90
- package/dist/components/FieldRenderer.svelte.d.ts +16 -1
- package/dist/components/FileUpload.svelte +116 -51
- package/dist/components/FileUpload.svelte.d.ts +7 -27
- package/dist/components/FileUploadField.svelte +170 -0
- package/dist/components/FileUploadField.svelte.d.ts +21 -0
- package/dist/components/FilterBuilder.svelte +13 -7
- package/dist/components/FilterCollectionInput.svelte +9 -7
- package/dist/components/FilterCollectionInput.svelte.d.ts +3 -0
- package/dist/components/ForgotPasswordPage.svelte +1 -1
- package/dist/components/Header.svelte +1 -1
- package/dist/components/ImportWizard.svelte +197 -28
- package/dist/components/ImportWizard.svelte.d.ts +5 -1
- package/dist/components/InferencerPanel.svelte +4 -2
- package/dist/components/InfiniteList.svelte +52 -9
- package/dist/components/JsonSchemaForm.svelte +237 -42
- package/dist/components/JsonSchemaForm.svelte.d.ts +10 -3
- package/dist/components/KanbanBoard.svelte +43 -7
- package/dist/components/KanbanBoard.svelte.d.ts +2 -2
- package/dist/components/Layout.svelte +1 -1
- package/dist/components/LoginPage.svelte +1 -1
- package/dist/components/MetricStrip.svelte +8 -3
- package/dist/components/MetricStrip.svelte.d.ts +1 -0
- package/dist/components/ModalForm.svelte +85 -9
- package/dist/components/ModalForm.svelte.d.ts +3 -3
- package/dist/components/MoneyInput.svelte +5 -3
- package/dist/components/MoneyInput.svelte.d.ts +4 -2
- package/dist/components/MultiTabKeepAlive.svelte +502 -43
- package/dist/components/MultiTabKeepAlive.svelte.d.ts +40 -6
- package/dist/components/NotificationsSettings.svelte +10 -10
- package/dist/components/NumberInput.svelte +5 -3
- package/dist/components/NumberInput.svelte.d.ts +4 -2
- package/dist/components/NumericInput.svelte +277 -25
- package/dist/components/NumericInput.svelte.d.ts +3 -1
- package/dist/components/PercentInput.svelte +5 -3
- package/dist/components/PercentInput.svelte.d.ts +4 -2
- package/dist/components/PermissionMatrix.svelte +44 -8
- package/dist/components/PermissionMatrix.svelte.d.ts +1 -0
- package/dist/components/ProfilePage.svelte +38 -13
- package/dist/components/RadioGroup.svelte +203 -4
- package/dist/components/RadioGroup.svelte.d.ts +1 -1
- package/dist/components/Rate.svelte +3 -3
- package/dist/components/Rate.svelte.d.ts +2 -2
- package/dist/components/RecordDetailDrawer.svelte +1 -1
- package/dist/components/RecordDetailDrawer.svelte.d.ts +1 -1
- package/dist/components/RegisterPage.svelte +1 -1
- package/dist/components/RelationPicker.svelte +39 -11
- package/dist/components/ResourceAccessGuard.svelte +7 -2
- package/dist/components/RetryTaskButton.svelte +14 -1
- package/dist/components/RolesSettings.svelte +74 -28
- package/dist/components/SecuritySettings.svelte +66 -51
- package/dist/components/SegmentedControl.svelte +4 -2
- package/dist/components/SegmentedControl.svelte.d.ts +1 -1
- package/dist/components/Sidebar.svelte +1 -1
- package/dist/components/StepsForm.svelte +60 -10
- package/dist/components/StepsForm.svelte.d.ts +4 -2
- package/dist/components/SvadminLogo.svelte +1 -0
- package/dist/components/SvarDataGrid.svelte +42 -24
- package/dist/components/SvarDataGrid.svelte.d.ts +10 -10
- package/dist/components/SvarResourceRead.svelte +2 -0
- package/dist/components/SvarResourceTableView.svelte +4 -2
- package/dist/components/TagsInput.svelte +3 -3
- package/dist/components/TagsInput.svelte.d.ts +2 -2
- package/dist/components/TaskDetails.svelte +22 -16
- package/dist/components/TaskDetails.svelte.d.ts +1 -1
- package/dist/components/TaskList.svelte +57 -15
- package/dist/components/TaskQueueDrawer.svelte +54 -34
- package/dist/components/Transfer.svelte +67 -5
- package/dist/components/Transfer.svelte.d.ts +4 -0
- package/dist/components/TreeSelect.svelte +337 -77
- package/dist/components/TreeSelect.svelte.d.ts +4 -0
- package/dist/components/TreeTable.svelte +6 -1
- package/dist/components/TwoFactorAuthPage.svelte +8 -4
- package/dist/components/UpdatePasswordPage.svelte +1 -1
- package/dist/components/VersionDiffViewer.svelte +9 -7
- package/dist/components/account/CompanyProfilePage.svelte +13 -12
- package/dist/components/account/MemberDirectory.svelte +14 -10
- package/dist/components/account/SettingsEnterprisePage.svelte +18 -18
- package/dist/components/content/AuditTimeline.svelte +8 -6
- package/dist/components/content/DataState.svelte +1 -1
- package/dist/components/content/FileList.svelte +44 -13
- package/dist/components/content/FileList.svelte.d.ts +4 -2
- package/dist/components/content/FileListRow.svelte +87 -0
- package/dist/components/content/FileListRow.svelte.d.ts +11 -0
- package/dist/components/content/ImportDropzone.svelte +109 -12
- package/dist/components/content/ImportDropzone.svelte.d.ts +1 -0
- package/dist/components/content/IntegrationCard.svelte +3 -3
- package/dist/components/content/IntegrationCard.svelte.d.ts +2 -0
- package/dist/components/content/MediaThumbnail.svelte +6 -2
- package/dist/components/detail-workspace.d.ts +60 -0
- package/dist/components/detail-workspace.js +3 -0
- package/dist/components/enterprise/json-schema-form.d.ts +1 -1
- package/dist/components/enterprise/recursive-schema-form.d.ts +15 -1
- package/dist/components/enterprise/recursive-schema-form.js +288 -14
- package/dist/components/enterprise/schema-form-references.d.ts +14 -0
- package/dist/components/enterprise/schema-form-references.js +316 -0
- package/dist/components/fields/ArrayField.svelte +178 -14
- package/dist/components/fields/ArrayField.svelte.d.ts +17 -3
- package/dist/components/fields/array-field-metadata.d.ts +4 -0
- package/dist/components/fields/array-field-metadata.js +36 -0
- package/dist/components/file-upload-reference.d.ts +11 -0
- package/dist/components/file-upload-reference.js +50 -0
- package/dist/components/file-upload-types.d.ts +32 -0
- package/dist/components/file-upload-types.js +1 -0
- package/dist/components/form-contract.upload-type-fixture.svelte +108 -0
- package/dist/components/form-contract.upload-type-fixture.svelte.d.ts +18 -0
- package/dist/components/json-schema-form-state.d.ts +19 -0
- package/dist/components/list-deletion.svelte.js +6 -1
- package/dist/components/list-state.svelte.d.ts +12 -1
- package/dist/components/list-state.svelte.js +56 -16
- package/dist/components/numeric-format.d.ts +44 -0
- package/dist/components/numeric-format.js +198 -0
- package/dist/components/record-detail.svelte.js +2 -0
- package/dist/components/saved-list-views.d.ts +9 -2
- package/dist/components/saved-list-views.js +42 -11
- package/dist/components/saved-list-views.svelte.d.ts +3 -0
- package/dist/components/saved-list-views.svelte.js +56 -7
- package/dist/components/task-action.svelte.d.ts +1 -0
- package/dist/components/task-action.svelte.js +55 -3
- package/dist/components/task-utils.d.ts +1 -0
- package/dist/components/task-utils.js +18 -0
- package/dist/components/ui/input/input.svelte +1 -1
- package/dist/components/ui/input/input.svelte.d.ts +1 -1
- package/dist/components/ui/sheet/sheet.svelte +4 -1
- package/dist/components/version-history.d.ts +14 -0
- package/dist/components/version-history.js +32 -0
- package/dist/components/workspace-tab-state.d.ts +78 -0
- package/dist/components/workspace-tab-state.js +587 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.js +4 -0
- package/package.json +5 -5
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
const i18n = useTranslation();
|
|
23
23
|
|
|
24
24
|
interface Props {
|
|
25
|
+
id?: string;
|
|
26
|
+
ariaLabel?: string;
|
|
27
|
+
describedby?: string;
|
|
28
|
+
invalid?: boolean;
|
|
25
29
|
options?: TreeSelectOption[];
|
|
26
30
|
value?: string | number | (string | number)[];
|
|
27
31
|
multiple?: boolean;
|
|
@@ -39,6 +43,10 @@
|
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
let {
|
|
46
|
+
id,
|
|
47
|
+
ariaLabel,
|
|
48
|
+
describedby,
|
|
49
|
+
invalid = false,
|
|
42
50
|
options = $bindable([]),
|
|
43
51
|
value = $bindable(undefined),
|
|
44
52
|
multiple = false,
|
|
@@ -55,6 +63,10 @@
|
|
|
55
63
|
onchange,
|
|
56
64
|
}: Props = $props();
|
|
57
65
|
|
|
66
|
+
const instanceId = $props.id();
|
|
67
|
+
const triggerId = $derived(id ?? `${instanceId}-trigger`);
|
|
68
|
+
const panelId = $derived(`${triggerId}-panel`);
|
|
69
|
+
const treeId = `${instanceId}-tree`;
|
|
58
70
|
let open = $state(false);
|
|
59
71
|
let searchQuery = $state('');
|
|
60
72
|
let loadingValues = new SvelteSet<string | number>();
|
|
@@ -62,11 +74,21 @@
|
|
|
62
74
|
node: TreeSelectOption;
|
|
63
75
|
loader: NonNullable<Props['loadChildren']>;
|
|
64
76
|
treeEpoch: number;
|
|
77
|
+
selection: SelectionAttempt | undefined;
|
|
78
|
+
promise: Promise<boolean>;
|
|
79
|
+
retrying: boolean;
|
|
65
80
|
}
|
|
66
81
|
let loadErrors = new SvelteMap<string | number, ChildLoad>();
|
|
67
|
-
let
|
|
82
|
+
let expandedOverrides = new SvelteMap<string | number, boolean>();
|
|
68
83
|
let mounted = true;
|
|
69
84
|
let treeRoot: HTMLElement | undefined = $state();
|
|
85
|
+
let panelElement: HTMLDivElement | undefined = $state();
|
|
86
|
+
let triggerElement: HTMLButtonElement | undefined = $state();
|
|
87
|
+
let controlElement: HTMLDivElement | undefined = $state();
|
|
88
|
+
let searchElement: HTMLInputElement | null = $state(null);
|
|
89
|
+
let closedByOutsideInteraction = false;
|
|
90
|
+
let outsideFocusTarget: HTMLElement | undefined;
|
|
91
|
+
let activeTreeValue = $state<string | number>();
|
|
70
92
|
let treeScrollTop = $state(0);
|
|
71
93
|
let treeRevision = $state(0);
|
|
72
94
|
const loads = new Map<string | number, ChildLoad>();
|
|
@@ -80,6 +102,7 @@
|
|
|
80
102
|
}
|
|
81
103
|
let selectionAttempt = $state.raw<SelectionAttempt>();
|
|
82
104
|
const selectionErrors = new SvelteSet<string | number>();
|
|
105
|
+
const capabilityErrors = new SvelteSet<string | number>();
|
|
83
106
|
let treeEpoch = 0;
|
|
84
107
|
onDestroy(() => { mounted = false; loads.clear(); });
|
|
85
108
|
|
|
@@ -122,12 +145,15 @@
|
|
|
122
145
|
|
|
123
146
|
$effect.pre(() => {
|
|
124
147
|
// 外部替换根数组开启新数据版本;内部补充子节点保留当前版本。
|
|
148
|
+
void disabledValues;
|
|
125
149
|
if (options !== observedOptions) {
|
|
126
150
|
observedOptions = options;
|
|
127
151
|
treeEpoch += 1;
|
|
128
152
|
treeRevision += 1;
|
|
153
|
+
expandedOverrides.clear();
|
|
129
154
|
selectionAttempt = undefined;
|
|
130
155
|
selectionErrors.clear();
|
|
156
|
+
capabilityErrors.clear();
|
|
131
157
|
}
|
|
132
158
|
const loader = loadChildren;
|
|
133
159
|
const unavailable = disabled;
|
|
@@ -135,36 +161,61 @@
|
|
|
135
161
|
observedLoader = loader;
|
|
136
162
|
selectionAttempt = undefined;
|
|
137
163
|
selectionErrors.clear();
|
|
164
|
+
capabilityErrors.clear();
|
|
138
165
|
}
|
|
139
|
-
if (!multiple || onlyLeafSelectable || selectionAttempt?.value !== value
|
|
166
|
+
if (!multiple || onlyLeafSelectable || selectionAttempt?.value !== value
|
|
167
|
+
|| selectionAttempt && disabledValues.has(selectionAttempt.key)) selectionAttempt = undefined;
|
|
140
168
|
for (const [key, request] of loads) {
|
|
141
|
-
if (unavailable ||
|
|
169
|
+
if (unavailable || disabledValues.has(key)
|
|
170
|
+
|| request.treeEpoch !== treeEpoch || loader !== request.loader) {
|
|
171
|
+
if (request.selection === selectionAttempt) selectionAttempt = undefined;
|
|
142
172
|
loads.delete(key);
|
|
143
173
|
loadingValues.delete(key);
|
|
144
174
|
loadErrors.delete(key);
|
|
145
175
|
}
|
|
146
176
|
}
|
|
147
177
|
for (const [key, request] of loadErrors) {
|
|
148
|
-
if (unavailable ||
|
|
178
|
+
if (unavailable || disabledValues.has(key)
|
|
179
|
+
|| request.treeEpoch !== treeEpoch || loader !== request.loader) loadErrors.delete(key);
|
|
180
|
+
}
|
|
181
|
+
for (const key of [...capabilityErrors]) {
|
|
182
|
+
const current = optionMap.get(key);
|
|
183
|
+
if (unavailable || disabledValues.has(key) || !current
|
|
184
|
+
|| !subtreeLacksLoadCapability(current)) {
|
|
185
|
+
capabilityErrors.delete(key);
|
|
186
|
+
}
|
|
149
187
|
}
|
|
150
188
|
});
|
|
151
189
|
|
|
152
190
|
function toggleExpand(nodeValue: string | number, event?: MouseEvent) {
|
|
153
191
|
event?.stopPropagation();
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
expandedNodes.add(nodeValue);
|
|
158
|
-
}
|
|
192
|
+
const node = optionMap.get(nodeValue);
|
|
193
|
+
if (!mounted || !node || disabled || disabledValues.has(nodeValue) || searchQuery.trim()) return;
|
|
194
|
+
expandedOverrides.set(nodeValue, !isNodeExpanded(node));
|
|
159
195
|
}
|
|
160
196
|
|
|
161
197
|
function hasChildren(node: TreeSelectOption): boolean {
|
|
162
198
|
return Boolean(node.hasChildren ?? (node.children && node.children.length > 0));
|
|
163
199
|
}
|
|
164
200
|
|
|
201
|
+
function lacksLoadCapability(node: TreeSelectOption): boolean {
|
|
202
|
+
const current = optionMap.get(node.value) ?? node;
|
|
203
|
+
return hasChildren(current) && !current.children && !loadChildren;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 祖先反馈只有在整个可交互子树仍存在缺失加载能力的分支时保留。
|
|
207
|
+
function subtreeLacksLoadCapability(node: TreeSelectOption, depth = 0): boolean {
|
|
208
|
+
const current = optionMap.get(node.value) ?? node;
|
|
209
|
+
if (current.disabled || depth > 64) return false;
|
|
210
|
+
if (hasChildren(current) && !current.children) return !loadChildren;
|
|
211
|
+
return (current.children ?? []).some(child => subtreeLacksLoadCapability(child, depth + 1));
|
|
212
|
+
}
|
|
213
|
+
|
|
165
214
|
function isNodeExpanded(node: TreeSelectOption): boolean {
|
|
166
215
|
if (searchQuery.trim()) return true; // Auto expand all on search
|
|
167
|
-
return
|
|
216
|
+
return expandedOverrides.has(node.value)
|
|
217
|
+
? expandedOverrides.get(node.value) === true
|
|
218
|
+
: !!node.expanded;
|
|
168
219
|
}
|
|
169
220
|
|
|
170
221
|
function selectableLeaves(node: TreeSelectOption): TreeSelectOption[] {
|
|
@@ -215,46 +266,80 @@
|
|
|
215
266
|
return changed ? next : nodes;
|
|
216
267
|
}
|
|
217
268
|
|
|
218
|
-
async function ensureChildren(node: TreeSelectOption): Promise<boolean> {
|
|
219
|
-
if (!mounted || disabled || disabledValues.has(node.value)
|
|
269
|
+
async function ensureChildren(node: TreeSelectOption, selection?: SelectionAttempt): Promise<boolean> {
|
|
270
|
+
if (!mounted || disabled || disabledValues.has(node.value)) return false;
|
|
271
|
+
const existing = loads.get(node.value);
|
|
272
|
+
if (existing) {
|
|
273
|
+
if (selection) existing.selection = selection;
|
|
274
|
+
return existing.promise;
|
|
275
|
+
}
|
|
276
|
+
if (loadingValues.has(node.value)) return false;
|
|
220
277
|
if (!node.hasChildren || node.children) return true;
|
|
221
278
|
const currentNode = optionMap.get(node.value);
|
|
222
279
|
if (!loadChildren || !currentNode) return false;
|
|
223
|
-
const request = {
|
|
280
|
+
const request: ChildLoad = {
|
|
281
|
+
node: currentNode,
|
|
282
|
+
loader: loadChildren,
|
|
283
|
+
treeEpoch,
|
|
284
|
+
selection,
|
|
285
|
+
promise: Promise.resolve(false),
|
|
286
|
+
retrying: false,
|
|
287
|
+
};
|
|
224
288
|
loads.set(node.value, request);
|
|
225
289
|
const current = () => mounted && !disabled && loads.get(node.value) === request
|
|
226
290
|
&& options === observedOptions && treeEpoch === request.treeEpoch
|
|
227
291
|
&& optionMap.has(node.value) && !disabledValues.has(node.value) && loadChildren === request.loader;
|
|
228
292
|
loadingValues.add(node.value);
|
|
229
293
|
loadErrors.delete(node.value);
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
294
|
+
// 展开和级联选择共享包含归属校验、写入与清理的完整请求结果。
|
|
295
|
+
request.promise = (async () => {
|
|
296
|
+
try {
|
|
297
|
+
const children = await request.loader(request.node);
|
|
298
|
+
if (!current()) return false;
|
|
299
|
+
options = updateOptionsChildren(options, node.value, children);
|
|
300
|
+
observedOptions = options;
|
|
301
|
+
return true;
|
|
302
|
+
} catch {
|
|
303
|
+
if (current()) loadErrors.set(node.value, request);
|
|
304
|
+
return false;
|
|
305
|
+
} finally {
|
|
306
|
+
if (mounted && loads.get(node.value) === request) {
|
|
307
|
+
loads.delete(node.value);
|
|
308
|
+
loadingValues.delete(node.value);
|
|
309
|
+
}
|
|
243
310
|
}
|
|
244
|
-
}
|
|
311
|
+
})();
|
|
312
|
+
return request.promise;
|
|
245
313
|
}
|
|
246
314
|
|
|
247
315
|
async function toggleExpandAsync(node: TreeSelectOption, event?: MouseEvent): Promise<void> {
|
|
248
316
|
event?.stopPropagation();
|
|
317
|
+
if (!mounted || disabled || disabledValues.has(node.value)) return;
|
|
318
|
+
if (loadingValues.has(node.value)) return;
|
|
319
|
+
if (lacksLoadCapability(node)) {
|
|
320
|
+
capabilityErrors.add(node.value);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
capabilityErrors.delete(node.value);
|
|
249
324
|
if (!await ensureChildren(node)) return;
|
|
250
325
|
if (!mounted || disabled) return;
|
|
251
326
|
toggleExpand(node.value);
|
|
252
327
|
}
|
|
253
328
|
|
|
254
|
-
function
|
|
329
|
+
function ownerTreeItem(event: Event): HTMLElement | null {
|
|
330
|
+
return event.currentTarget instanceof HTMLElement
|
|
331
|
+
? event.currentTarget.closest<HTMLElement>('[role="treeitem"]')
|
|
332
|
+
: null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function retryChildren(node: TreeSelectOption, event: Event): void {
|
|
255
336
|
event.stopPropagation();
|
|
337
|
+
const owner = ownerTreeItem(event);
|
|
338
|
+
owner?.focus();
|
|
256
339
|
if (selectionErrors.has(node.value)) void handleSelect(node);
|
|
257
340
|
else void toggleExpandAsync(node);
|
|
341
|
+
const request = loads.get(node.value);
|
|
342
|
+
if (request) request.retrying = true;
|
|
258
343
|
}
|
|
259
344
|
|
|
260
345
|
function setMultipleSelection(node: TreeSelectOption): void {
|
|
@@ -272,6 +357,7 @@
|
|
|
272
357
|
function isCurrentSelection(attempt: SelectionAttempt): boolean {
|
|
273
358
|
return mounted && !disabled && multiple && !onlyLeafSelectable
|
|
274
359
|
&& selectionAttempt === attempt && attempt.epoch === treeEpoch
|
|
360
|
+
&& !disabledValues.has(attempt.key)
|
|
275
361
|
&& options === observedOptions && attempt.loader === loadChildren && attempt.value === value;
|
|
276
362
|
}
|
|
277
363
|
|
|
@@ -280,8 +366,10 @@
|
|
|
280
366
|
const attempt: SelectionAttempt = { key: node.value, epoch: treeEpoch, loader: loadChildren, value };
|
|
281
367
|
selectionAttempt = attempt;
|
|
282
368
|
selectionErrors.delete(node.value);
|
|
369
|
+
capabilityErrors.delete(node.value);
|
|
283
370
|
let visited = 0;
|
|
284
371
|
let requests = 0;
|
|
372
|
+
let missingCapability = false;
|
|
285
373
|
// 级联加载有界且只在全部成功后写入选择;搜索裁剪不改变选择范围。
|
|
286
374
|
async function loadSubtree(key: string | number, depth: number): Promise<boolean> {
|
|
287
375
|
if (!isCurrentSelection(attempt)) return false;
|
|
@@ -289,7 +377,11 @@
|
|
|
289
377
|
if (!currentNode || ++visited > 10000 || depth > 64) return false;
|
|
290
378
|
if (currentNode.disabled) return true;
|
|
291
379
|
if (currentNode.hasChildren && !currentNode.children) {
|
|
292
|
-
if (
|
|
380
|
+
if (lacksLoadCapability(currentNode)) {
|
|
381
|
+
missingCapability = true;
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
if (++requests > 100 || !await ensureChildren(currentNode, attempt)) return false;
|
|
293
385
|
}
|
|
294
386
|
if (!isCurrentSelection(attempt)) return false;
|
|
295
387
|
const loaded = optionMap.get(key);
|
|
@@ -301,7 +393,10 @@
|
|
|
301
393
|
}
|
|
302
394
|
try {
|
|
303
395
|
if (!await loadSubtree(node.value, 0)) {
|
|
304
|
-
if (isCurrentSelection(attempt))
|
|
396
|
+
if (isCurrentSelection(attempt)) {
|
|
397
|
+
if (missingCapability) capabilityErrors.add(node.value);
|
|
398
|
+
else selectionErrors.add(node.value);
|
|
399
|
+
}
|
|
305
400
|
return;
|
|
306
401
|
}
|
|
307
402
|
if (isCurrentSelection(attempt)) {
|
|
@@ -361,6 +456,10 @@
|
|
|
361
456
|
return `${typeof value}:${String(value)}`;
|
|
362
457
|
}
|
|
363
458
|
|
|
459
|
+
function nodeId(value: string | number): string {
|
|
460
|
+
return `${instanceId}-node-${treeKey(value).split('').map(unit => unit.charCodeAt(0).toString(16)).join('-')}`;
|
|
461
|
+
}
|
|
462
|
+
|
|
364
463
|
async function focusTreeNode(valueToFocus: string | number): Promise<void> {
|
|
365
464
|
const index = flatVisibleNodes.findIndex(entry => entry.node.value === valueToFocus);
|
|
366
465
|
if (index < 0) return;
|
|
@@ -372,7 +471,7 @@
|
|
|
372
471
|
await tick();
|
|
373
472
|
}
|
|
374
473
|
}
|
|
375
|
-
if (!mounted) return;
|
|
474
|
+
if (!mounted || !open) return;
|
|
376
475
|
const element = treeRoot?.querySelector<HTMLElement>(`[data-tree-key="${CSS.escape(treeKey(valueToFocus))}"]`);
|
|
377
476
|
element?.focus();
|
|
378
477
|
}
|
|
@@ -383,12 +482,73 @@
|
|
|
383
482
|
if (target) void focusTreeNode(target.node.value);
|
|
384
483
|
}
|
|
385
484
|
|
|
485
|
+
async function handleOpenAutoFocus(event: Event): Promise<void> {
|
|
486
|
+
event.preventDefault();
|
|
487
|
+
closedByOutsideInteraction = false;
|
|
488
|
+
outsideFocusTarget = undefined;
|
|
489
|
+
await tick();
|
|
490
|
+
const panel = panelElement;
|
|
491
|
+
if (!mounted || !open || !panel || !panel.isConnected || panel.contains(document.activeElement)) return;
|
|
492
|
+
if (searchable && searchElement) {
|
|
493
|
+
searchElement.focus();
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
const first = flatVisibleNodes[0];
|
|
497
|
+
if (first) await focusTreeNode(first.node.value);
|
|
498
|
+
if (!mounted || !open || panelElement !== panel || !panel.isConnected) return;
|
|
499
|
+
if (!panel.contains(document.activeElement)) panel.focus();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
async function handleCloseAutoFocus(event: Event): Promise<void> {
|
|
503
|
+
event.preventDefault();
|
|
504
|
+
const preserveOutsideFocus = closedByOutsideInteraction;
|
|
505
|
+
const focusTarget = outsideFocusTarget;
|
|
506
|
+
await tick();
|
|
507
|
+
if (!mounted || open) return;
|
|
508
|
+
if (preserveOutsideFocus) {
|
|
509
|
+
const active = document.activeElement;
|
|
510
|
+
if (focusTarget?.isConnected
|
|
511
|
+
&& (active === document.body || controlElement?.contains(active) || panelElement?.contains(active))) {
|
|
512
|
+
focusTarget.focus();
|
|
513
|
+
}
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
triggerElement?.focus();
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function handleInteractOutside(event: Event): void {
|
|
520
|
+
// 控制区内的触发/清空仍回焦本实例,真正的外部操作保留新焦点。
|
|
521
|
+
if (event.target instanceof Node && !controlElement?.contains(event.target)) {
|
|
522
|
+
closedByOutsideInteraction = true;
|
|
523
|
+
outsideFocusTarget = event.target instanceof HTMLElement ? event.target : undefined;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function handleWindowFocusIn(event: FocusEvent): void {
|
|
528
|
+
if (!open || !(event.target instanceof HTMLElement)) return;
|
|
529
|
+
if (panelElement?.contains(event.target) || controlElement?.contains(event.target)) return;
|
|
530
|
+
closedByOutsideInteraction = true;
|
|
531
|
+
outsideFocusTarget = event.target;
|
|
532
|
+
open = false;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function handleSearchKeydown(event: KeyboardEvent): void {
|
|
536
|
+
if (event.key !== 'ArrowDown' || event.isComposing || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
|
|
537
|
+
const first = flatVisibleNodes[0];
|
|
538
|
+
if (!first) return;
|
|
539
|
+
event.preventDefault();
|
|
540
|
+
void focusTreeNode(first.node.value);
|
|
541
|
+
}
|
|
542
|
+
|
|
386
543
|
function handleTreeKeydown(event: KeyboardEvent, node: TreeSelectOption, expanded: boolean): void {
|
|
387
|
-
if (event.target !== event.currentTarget) return;
|
|
388
|
-
const
|
|
544
|
+
if (event.target !== event.currentTarget || event.isComposing || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
|
|
545
|
+
const sourceNode = optionMap.get(node.value) ?? node;
|
|
546
|
+
const nodeHasChildren = hasChildren(sourceNode);
|
|
389
547
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
390
548
|
event.preventDefault();
|
|
391
|
-
|
|
549
|
+
if (loads.get(node.value)?.retrying) return;
|
|
550
|
+
if (loadErrors.has(node.value) || selectionErrors.has(node.value)) retryChildren(sourceNode, event);
|
|
551
|
+
else void handleSelect(node);
|
|
392
552
|
} else if (event.key === 'ArrowDown') {
|
|
393
553
|
event.preventDefault();
|
|
394
554
|
focusSibling(node.value, 1);
|
|
@@ -405,7 +565,12 @@
|
|
|
405
565
|
if (last) void focusTreeNode(last.node.value);
|
|
406
566
|
} else if (event.key === 'ArrowRight' && nodeHasChildren) {
|
|
407
567
|
event.preventDefault();
|
|
408
|
-
if (
|
|
568
|
+
if (disabled || disabledValues.has(sourceNode.value)) return;
|
|
569
|
+
if (searchQuery.trim() && !sourceNode.children) {
|
|
570
|
+
if (lacksLoadCapability(sourceNode)) capabilityErrors.add(sourceNode.value);
|
|
571
|
+
else void ensureChildren(sourceNode);
|
|
572
|
+
}
|
|
573
|
+
else if (!expanded) void toggleExpandAsync(sourceNode);
|
|
409
574
|
else {
|
|
410
575
|
const index = flatVisibleNodes.findIndex(entry => entry.node.value === node.value);
|
|
411
576
|
const current = flatVisibleNodes[index];
|
|
@@ -426,20 +591,24 @@
|
|
|
426
591
|
event?.stopPropagation();
|
|
427
592
|
if (disabled) return;
|
|
428
593
|
selectionAttempt = undefined;
|
|
594
|
+
open = false;
|
|
429
595
|
const next = multiple ? [] : undefined;
|
|
430
596
|
value = next;
|
|
431
597
|
onchange?.(next);
|
|
598
|
+
triggerElement?.focus();
|
|
432
599
|
}
|
|
433
600
|
|
|
434
601
|
function removeSingle(val: string | number, event?: MouseEvent | KeyboardEvent) {
|
|
435
602
|
event?.stopPropagation();
|
|
436
603
|
if (disabled) return;
|
|
437
604
|
selectionAttempt = undefined;
|
|
605
|
+
open = false;
|
|
438
606
|
if (multiple) {
|
|
439
607
|
const next = selectedValues.filter((v) => v !== val);
|
|
440
608
|
value = next;
|
|
441
609
|
onchange?.(next);
|
|
442
610
|
}
|
|
611
|
+
triggerElement?.focus();
|
|
443
612
|
}
|
|
444
613
|
|
|
445
614
|
function filterTree(nodes: TreeSelectOption[], query: string): TreeSelectOption[] {
|
|
@@ -488,6 +657,13 @@
|
|
|
488
657
|
const clampedScrollTop = $derived(Math.max(0, Math.min(treeScrollTop, totalHeight - windowHeight)));
|
|
489
658
|
const virtualStart = $derived(Math.max(0, Math.floor(clampedScrollTop / rowHeight) - 5));
|
|
490
659
|
const virtualEnd = $derived(Math.min(flatVisibleNodes.length, Math.ceil((clampedScrollTop + windowHeight) / rowHeight) + 5));
|
|
660
|
+
const renderedNodes = $derived(shouldVirtualize
|
|
661
|
+
? flatVisibleNodes.slice(virtualStart, virtualEnd)
|
|
662
|
+
: flatVisibleNodes);
|
|
663
|
+
// 当前节点被过滤、折叠或滚出渲染窗口时,仍保留一个有效的 Tab 入口。
|
|
664
|
+
const tabStopValue = $derived(renderedNodes.some(entry => entry.node.value === activeTreeValue)
|
|
665
|
+
? activeTreeValue
|
|
666
|
+
: renderedNodes[0]?.node.value);
|
|
491
667
|
|
|
492
668
|
$effect(() => {
|
|
493
669
|
// 搜索和外部数据替换从顶部开始;折叠或数据缩短只钳制窗口。
|
|
@@ -511,11 +687,17 @@
|
|
|
511
687
|
{@const partial = isPartiallySelected(node)}
|
|
512
688
|
{@const busy = selectionAttempt?.key === node.value || loadingValues.has(node.value)}
|
|
513
689
|
{@const unavailable = disabled || disabledValues.has(node.value)}
|
|
690
|
+
{@const currentNodeId = nodeId(node.value)}
|
|
691
|
+
{@const hasRenderedGroup = recursive && nodeHasChildren && expanded && !!node.children?.length}
|
|
514
692
|
|
|
515
693
|
<div class="svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed">
|
|
516
694
|
<div
|
|
517
695
|
role="treeitem"
|
|
518
|
-
|
|
696
|
+
id={currentNodeId}
|
|
697
|
+
tabindex={tabStopValue === node.value ? 0 : -1}
|
|
698
|
+
aria-label={node.label}
|
|
699
|
+
aria-owns={hasRenderedGroup ? `${currentNodeId}-group` : undefined}
|
|
700
|
+
aria-describedby={loadErrors.has(node.value) || selectionErrors.has(node.value) || capabilityErrors.has(node.value) ? `${currentNodeId}-error` : undefined}
|
|
519
701
|
aria-selected={selected}
|
|
520
702
|
aria-checked={multiple ? (partial ? 'mixed' : selected) : undefined}
|
|
521
703
|
aria-disabled={unavailable ? 'true' : undefined}
|
|
@@ -533,17 +715,22 @@
|
|
|
533
715
|
)}
|
|
534
716
|
style={`padding-left:${level * 16 + 8}px;${recursive ? '' : `height:${rowHeight}px;min-height:${rowHeight}px;box-sizing:border-box;overflow:hidden;`}`}
|
|
535
717
|
onclick={() => void handleSelect(node)}
|
|
718
|
+
onfocus={() => { activeTreeValue = node.value; }}
|
|
536
719
|
onkeydown={(e) => handleTreeKeydown(e, node, expanded)}
|
|
537
720
|
>
|
|
538
721
|
<div class="svadmin-u-60fbb7713999 svadmin-u-7e0b7cdf1a94 svadmin-u-3960ffc248d9 svadmin-u-58284b4ea568">
|
|
539
722
|
{#if nodeHasChildren}
|
|
540
723
|
<button
|
|
541
724
|
type="button"
|
|
725
|
+
tabindex="-1"
|
|
542
726
|
disabled={unavailable}
|
|
543
727
|
aria-label={`${node.label}: ${i18n.t('tree.toggleChildren')}`}
|
|
544
728
|
aria-busy={busy}
|
|
545
729
|
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"
|
|
546
|
-
onclick={(e) =>
|
|
730
|
+
onclick={(e) => {
|
|
731
|
+
ownerTreeItem(e)?.focus();
|
|
732
|
+
void toggleExpandAsync(node, e);
|
|
733
|
+
}}
|
|
547
734
|
>
|
|
548
735
|
{#if busy}
|
|
549
736
|
<Loader2 class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c svadmin-u-afbdd13a380e" />
|
|
@@ -560,13 +747,16 @@
|
|
|
560
747
|
<span class="svadmin-u-f283ea9bea0e">{node.label}</span>
|
|
561
748
|
</div>
|
|
562
749
|
|
|
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
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
750
|
+
{#if loadErrors.has(node.value) || selectionErrors.has(node.value) || capabilityErrors.has(node.value)}
|
|
751
|
+
<span id={`${currentNodeId}-error`} role="alert" class="svadmin-u-bfa603190748">{i18n.t(capabilityErrors.has(node.value) ? 'tree.childrenUnavailable' : selectionErrors.has(node.value) ? 'tree.selectionFailed' : 'tree.loadFailed')}</span>
|
|
752
|
+
{#if !capabilityErrors.has(node.value)}
|
|
753
|
+
<button type="button" aria-label={`${node.label}: ${i18n.t('common.retry')}`}
|
|
754
|
+
tabindex="-1"
|
|
755
|
+
disabled={unavailable}
|
|
756
|
+
title={i18n.t('common.retry')} onclick={(e) => retryChildren(node, e)}>
|
|
757
|
+
<RotateCw class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" />
|
|
758
|
+
</button>
|
|
759
|
+
{/if}
|
|
570
760
|
{/if}
|
|
571
761
|
|
|
572
762
|
{#if partial}
|
|
@@ -577,7 +767,7 @@
|
|
|
577
767
|
</div>
|
|
578
768
|
|
|
579
769
|
{#if recursive && nodeHasChildren && expanded && node.children}
|
|
580
|
-
<div role="group" class="svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed">
|
|
770
|
+
<div id={`${currentNodeId}-group`} role="group" class="svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed">
|
|
581
771
|
{#each node.children as child, index (treeKey(child.value))}
|
|
582
772
|
{@render treeNode(child, level + 1, index + 1, node.children.length)}
|
|
583
773
|
{/each}
|
|
@@ -586,20 +776,39 @@
|
|
|
586
776
|
</div>
|
|
587
777
|
{/snippet}
|
|
588
778
|
|
|
779
|
+
<svelte:window onfocusin={handleWindowFocusIn} />
|
|
780
|
+
|
|
589
781
|
<div class={cn('svadmin-u-d89972fe17d6 svadmin-u-6da6a3c3f741', className)} data-testid="tree-select">
|
|
590
782
|
<Popover.Root bind:open>
|
|
591
|
-
<Popover.Trigger class="svadmin-u-6da6a3c3f741">
|
|
783
|
+
<Popover.Trigger id={triggerId} {disabled} class="svadmin-u-6da6a3c3f741">
|
|
592
784
|
{#snippet child({ props })}
|
|
593
|
-
<
|
|
594
|
-
|
|
595
|
-
{
|
|
596
|
-
{disabled}
|
|
785
|
+
<div bind:this={controlElement}
|
|
786
|
+
data-disabled={disabled || undefined}
|
|
787
|
+
data-invalid={invalid || undefined}
|
|
597
788
|
class={cn(
|
|
789
|
+
'tree-select-control',
|
|
598
790
|
'svadmin-u-60fbb7713999 svadmin-u-968a1e649bad svadmin-u-6da6a3c3f741 svadmin-u-3960ffc248d9 svadmin-u-8ef2268efbbc svadmin-u-421ac2be5045 svadmin-u-ca6bcd4b6f3f svadmin-u-e5795dad4d22 svadmin-u-e6f9e383a762 svadmin-u-0e17f2bd9074 svadmin-u-ec0091ee009b svadmin-u-359090c2d529 svadmin-u-582e6ef4b245 svadmin-u-9c24ab70af61 svadmin-u-674ec2b09890 svadmin-u-608dd26cd5ba svadmin-u-80b9d0ae125f svadmin-u-6b22a22a9752 svadmin-u-5f533b3a7de7 svadmin-u-b29d8adbad2e svadmin-u-2eba0d65d059',
|
|
599
791
|
open && 'svadmin-u-16b1efa5875e svadmin-u-3e1868fc53e2 svadmin-u-0c15f6cff5a0'
|
|
600
792
|
)}
|
|
601
793
|
>
|
|
602
|
-
<
|
|
794
|
+
<button
|
|
795
|
+
{...props}
|
|
796
|
+
type="button"
|
|
797
|
+
bind:this={triggerElement}
|
|
798
|
+
aria-label={ariaLabel?.trim() ? ariaLabel : undefined}
|
|
799
|
+
aria-controls={open ? panelId : undefined}
|
|
800
|
+
aria-describedby={describedby}
|
|
801
|
+
data-invalid={invalid || undefined}
|
|
802
|
+
{disabled}
|
|
803
|
+
class="tree-select-trigger"
|
|
804
|
+
>
|
|
805
|
+
<span class="svadmin-u-2daa8e5e2f2e">
|
|
806
|
+
{selectedLabels.length ? selectedLabels.join(', ') : placeholder ?? i18n.t('field.selectPlaceholder')}
|
|
807
|
+
</span>
|
|
808
|
+
</button>
|
|
809
|
+
<div
|
|
810
|
+
class="tree-select-display svadmin-u-60fbb7713999 svadmin-u-1eb5c6df38c1 svadmin-u-3960ffc248d9 svadmin-u-44ee8ba0a421 svadmin-u-7e0b7cdf1a94 svadmin-u-36e579c0b41c"
|
|
811
|
+
>
|
|
603
812
|
{#if selectedValues.length === 0}
|
|
604
813
|
<span class="svadmin-u-bfa603190748">
|
|
605
814
|
{placeholder ?? (i18n.t('field.selectPlaceholder', undefined) ?? '请选择...')}
|
|
@@ -610,21 +819,14 @@
|
|
|
610
819
|
<Badge variant="secondary" class="svadmin-u-cd0d9c512cdc svadmin-u-45d828117213 svadmin-u-68ecb30dbec6 svadmin-u-d058ca6de60f svadmin-u-44ee8ba0a421 svadmin-u-012fbd121f37">
|
|
611
820
|
<span>{opt?.label ?? val}</span>
|
|
612
821
|
{#if !disabled}
|
|
613
|
-
<
|
|
614
|
-
role="button"
|
|
615
|
-
tabindex="0"
|
|
822
|
+
<button type="button" tabindex="0"
|
|
616
823
|
aria-label={i18n.t('tree.removeSelection', { label: opt?.label ?? String(val) })}
|
|
617
|
-
|
|
824
|
+
title={i18n.t('tree.removeSelection', { label: opt?.label ?? String(val) })}
|
|
825
|
+
class="tree-select-action svadmin-u-51e95020d6f2 svadmin-u-34516836730d"
|
|
618
826
|
onclick={(e) => removeSingle(val, e)}
|
|
619
|
-
onkeydown={(e) => {
|
|
620
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
621
|
-
e.preventDefault();
|
|
622
|
-
removeSingle(val, e);
|
|
623
|
-
}
|
|
624
|
-
}}
|
|
625
827
|
>
|
|
626
828
|
<X class="svadmin-u-6a60c09e6aaa svadmin-u-9cea05671a29" />
|
|
627
|
-
</
|
|
829
|
+
</button>
|
|
628
830
|
{/if}
|
|
629
831
|
</Badge>
|
|
630
832
|
{/each}
|
|
@@ -635,37 +837,42 @@
|
|
|
635
837
|
{/if}
|
|
636
838
|
</div>
|
|
637
839
|
|
|
638
|
-
<div
|
|
840
|
+
<div
|
|
841
|
+
class="tree-select-display svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-44ee8ba0a421 svadmin-u-012fbd121f37 svadmin-u-f58b02572ab2 svadmin-u-bfa603190748"
|
|
842
|
+
>
|
|
639
843
|
{#if allowClear && selectedValues.length > 0 && !disabled}
|
|
640
|
-
<
|
|
641
|
-
role="button"
|
|
642
|
-
tabindex="0"
|
|
844
|
+
<button type="button" tabindex="0"
|
|
643
845
|
aria-label={i18n.t('common.clearSelection')}
|
|
644
|
-
|
|
846
|
+
title={i18n.t('common.clearSelection')}
|
|
847
|
+
class="tree-select-action svadmin-u-ea7b2e9e070e svadmin-u-34516836730d svadmin-u-de8350a3bbad"
|
|
645
848
|
onclick={clearAll}
|
|
646
|
-
onkeydown={(e) => {
|
|
647
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
648
|
-
e.preventDefault();
|
|
649
|
-
clearAll(e);
|
|
650
|
-
}
|
|
651
|
-
}}
|
|
652
849
|
>
|
|
653
850
|
<X class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" />
|
|
654
|
-
</
|
|
851
|
+
</button>
|
|
655
852
|
{/if}
|
|
656
853
|
<ChevronsUpDown class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c svadmin-u-0b8c506a0596" />
|
|
657
854
|
</div>
|
|
658
|
-
</
|
|
855
|
+
</div>
|
|
659
856
|
{/snippet}
|
|
660
857
|
</Popover.Trigger>
|
|
661
858
|
|
|
662
|
-
<Popover.Content
|
|
859
|
+
<Popover.Content id={panelId} role="dialog" aria-labelledby={triggerId}
|
|
860
|
+
onOpenAutoFocus={handleOpenAutoFocus} onCloseAutoFocus={handleCloseAutoFocus}
|
|
861
|
+
onInteractOutside={handleInteractOutside}
|
|
862
|
+
class="svadmin-u-4827635fb99f svadmin-u-cbc20887874e svadmin-u-7b17becb34a1 svadmin-u-7660b450905a" align="start">
|
|
863
|
+
{#snippet child({ props, wrapperProps })}
|
|
864
|
+
<div {...wrapperProps}>
|
|
865
|
+
<div {...props} id={panelId} bind:this={panelElement}>
|
|
663
866
|
{#if searchable}
|
|
664
867
|
<div class="svadmin-u-d89972fe17d6 svadmin-u-a77ed4d908c0">
|
|
665
868
|
<Search class="svadmin-u-da4dbfbc4fdc svadmin-u-ecfeb742a3f3 svadmin-u-7a470f4c9b28 svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c svadmin-u-bfa603190748" />
|
|
666
869
|
<Input
|
|
667
870
|
type="text"
|
|
871
|
+
bind:ref={searchElement}
|
|
668
872
|
bind:value={searchQuery}
|
|
873
|
+
aria-label={i18n.t('common.search')}
|
|
874
|
+
aria-controls={treeId}
|
|
875
|
+
onkeydown={handleSearchKeydown}
|
|
669
876
|
placeholder={i18n.t('common.search', undefined) ?? '搜索...'}
|
|
670
877
|
class="svadmin-u-ed8a5df7b2fb svadmin-u-e4af885410fe svadmin-u-aa2c13a5e1b4 svadmin-u-359090c2d529"
|
|
671
878
|
/>
|
|
@@ -674,7 +881,10 @@
|
|
|
674
881
|
|
|
675
882
|
<div
|
|
676
883
|
bind:this={treeRoot}
|
|
884
|
+
id={treeId}
|
|
677
885
|
role="tree"
|
|
886
|
+
tabindex="-1"
|
|
887
|
+
aria-label={ariaLabel ?? placeholder ?? i18n.t('field.selectPlaceholder')}
|
|
678
888
|
aria-multiselectable={multiple ? 'true' : undefined}
|
|
679
889
|
class="svadmin-u-67d7e383dca6 svadmin-u-92bf82f493b1 svadmin-u-e2eedc5718f0 svadmin-u-eda955402ba6"
|
|
680
890
|
style={shouldVirtualize ? `height:${windowHeight}px;max-height:${windowHeight}px;overflow-y:auto;padding:0;` : undefined}
|
|
@@ -683,7 +893,7 @@
|
|
|
683
893
|
{#if shouldVirtualize}
|
|
684
894
|
<div style={`height:${totalHeight}px;position:relative;`}>
|
|
685
895
|
<div style={`position:absolute;top:${virtualStart * rowHeight}px;left:0;right:0;`}>
|
|
686
|
-
{#each
|
|
896
|
+
{#each renderedNodes as entry (treeKey(entry.node.value))}
|
|
687
897
|
{@render treeNode(entry.node, entry.level, entry.position, entry.size, false)}
|
|
688
898
|
{/each}
|
|
689
899
|
</div>
|
|
@@ -698,6 +908,56 @@
|
|
|
698
908
|
</div>
|
|
699
909
|
{/if}
|
|
700
910
|
</div>
|
|
911
|
+
</div>
|
|
912
|
+
</div>
|
|
913
|
+
{/snippet}
|
|
701
914
|
</Popover.Content>
|
|
702
915
|
</Popover.Root>
|
|
703
916
|
</div>
|
|
917
|
+
|
|
918
|
+
<style>
|
|
919
|
+
.tree-select-control {
|
|
920
|
+
position: relative;
|
|
921
|
+
isolation: isolate;
|
|
922
|
+
min-inline-size: 0;
|
|
923
|
+
}
|
|
924
|
+
.tree-select-trigger {
|
|
925
|
+
position: absolute;
|
|
926
|
+
inset: 0;
|
|
927
|
+
z-index: 0;
|
|
928
|
+
width: 100%;
|
|
929
|
+
height: 100%;
|
|
930
|
+
border: 0;
|
|
931
|
+
border-radius: inherit;
|
|
932
|
+
background: transparent;
|
|
933
|
+
cursor: pointer;
|
|
934
|
+
}
|
|
935
|
+
.tree-select-trigger:focus-visible,
|
|
936
|
+
.tree-select-action:focus-visible {
|
|
937
|
+
outline: 2px solid var(--ring);
|
|
938
|
+
outline-offset: 2px;
|
|
939
|
+
}
|
|
940
|
+
.tree-select-control[data-disabled] {
|
|
941
|
+
opacity: 0.5;
|
|
942
|
+
}
|
|
943
|
+
.tree-select-trigger:disabled {
|
|
944
|
+
cursor: not-allowed;
|
|
945
|
+
}
|
|
946
|
+
.tree-select-display {
|
|
947
|
+
position: relative;
|
|
948
|
+
z-index: 1;
|
|
949
|
+
pointer-events: none;
|
|
950
|
+
min-inline-size: 0;
|
|
951
|
+
}
|
|
952
|
+
.tree-select-action {
|
|
953
|
+
pointer-events: auto;
|
|
954
|
+
display: inline-flex;
|
|
955
|
+
align-items: center;
|
|
956
|
+
justify-content: center;
|
|
957
|
+
padding: 0;
|
|
958
|
+
border: 0;
|
|
959
|
+
background: transparent;
|
|
960
|
+
color: inherit;
|
|
961
|
+
cursor: pointer;
|
|
962
|
+
}
|
|
963
|
+
</style>
|