@sia.soul/sia-react-ui 0.1.5 → 0.1.7

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.
@@ -0,0 +1,1493 @@
1
+ import {
2
+ Dropdown,
3
+ Icon,
4
+ Input,
5
+ PopupPortal
6
+ } from "./chunk-JJVD2ITI.js";
7
+ import {
8
+ Button
9
+ } from "./chunk-MBS2HXLZ.js";
10
+ import {
11
+ useControllableState,
12
+ useFloatingPlaceholder
13
+ } from "./chunk-EJDPRAU2.js";
14
+
15
+ // src/components/Checkbox.tsx
16
+ import { createContext, forwardRef, useContext, useEffect, useId, useMemo, useRef } from "react";
17
+ import { jsx, jsxs } from "react/jsx-runtime";
18
+ var CheckboxGroupContext = createContext(null);
19
+ var CheckboxRoot = forwardRef(function Checkbox({ value, indeterminate = false, children, disabled, checked, defaultChecked, className = "", onChange, ...props }, forwardedRef) {
20
+ const group = useContext(CheckboxGroupContext);
21
+ const localRef = useRef(null);
22
+ const fallbackId = useId();
23
+ const grouped = group !== null && value !== void 0;
24
+ const currentChecked = grouped ? group.values.includes(value) : checked;
25
+ useEffect(() => {
26
+ if (localRef.current) localRef.current.indeterminate = indeterminate;
27
+ }, [indeterminate]);
28
+ function setRef(node) {
29
+ localRef.current = node;
30
+ if (typeof forwardedRef === "function") forwardedRef(node);
31
+ else if (forwardedRef) forwardedRef.current = node;
32
+ }
33
+ return /* @__PURE__ */ jsxs("label", { className: `sia-checkbox${disabled || group?.disabled ? " is-disabled" : ""} ${className}`.trim(), children: [
34
+ /* @__PURE__ */ jsx(
35
+ "input",
36
+ {
37
+ ...props,
38
+ ref: setRef,
39
+ id: props.id ?? fallbackId,
40
+ className: "sia-checkbox__input",
41
+ type: "checkbox",
42
+ name: group?.name ?? props.name,
43
+ disabled: disabled || group?.disabled,
44
+ checked: currentChecked,
45
+ defaultChecked: grouped ? void 0 : defaultChecked,
46
+ "aria-checked": indeterminate ? "mixed" : currentChecked,
47
+ onChange: (event) => {
48
+ if (grouped) group.toggle(value, event.currentTarget.checked);
49
+ onChange?.(event.currentTarget.checked, event);
50
+ }
51
+ }
52
+ ),
53
+ /* @__PURE__ */ jsx("span", { className: "sia-checkbox__control", "aria-hidden": "true", children: /* @__PURE__ */ jsx("span", {}) }),
54
+ children !== void 0 ? /* @__PURE__ */ jsx("span", { className: "sia-checkbox__label", children }) : null
55
+ ] });
56
+ });
57
+ function CheckboxGroup({
58
+ value,
59
+ defaultValue = [],
60
+ options,
61
+ disabled,
62
+ name,
63
+ className = "",
64
+ children,
65
+ onChange
66
+ }) {
67
+ const [values, setValues] = useControllableState({ value, defaultValue, onChange });
68
+ const context = useMemo(() => ({
69
+ values,
70
+ disabled,
71
+ name,
72
+ toggle(optionValue, checked) {
73
+ const next = checked ? values.includes(optionValue) ? values : [...values, optionValue] : values.filter((item) => item !== optionValue);
74
+ setValues(next);
75
+ }
76
+ }), [disabled, name, setValues, values]);
77
+ return /* @__PURE__ */ jsx(CheckboxGroupContext.Provider, { value: context, children: /* @__PURE__ */ jsxs("div", { className: `sia-checkbox-group ${className}`.trim(), role: "group", children: [
78
+ options?.map((option) => {
79
+ const normalized = typeof option === "object" ? option : { label: String(option), value: option };
80
+ return /* @__PURE__ */ jsx(CheckboxRoot, { value: normalized.value, disabled: normalized.disabled, children: normalized.label }, normalized.value);
81
+ }),
82
+ children
83
+ ] }) });
84
+ }
85
+ var Checkbox2 = Object.assign(CheckboxRoot, { Group: CheckboxGroup });
86
+
87
+ // src/components/FloatingDisplay.tsx
88
+ import { cloneElement, useEffect as useEffect2, useId as useId2, useLayoutEffect, useMemo as useMemo2, useRef as useRef2, useState } from "react";
89
+ import { createPortal } from "react-dom";
90
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
91
+ var FLOATING_GAP = 9;
92
+ var FLOATING_VIEWPORT_PADDING = 8;
93
+ var FLOATING_ARROW_EDGE_OFFSET = 16;
94
+ var activeTooltipClose;
95
+ function clampFloating(value, min, max) {
96
+ return Math.min(Math.max(value, min), Math.max(min, max));
97
+ }
98
+ function resolveFloatingPosition(trigger, overlay, placement) {
99
+ const viewportWidth = document.documentElement.clientWidth;
100
+ const viewportHeight = document.documentElement.clientHeight;
101
+ let resolvedPlacement = placement;
102
+ if (placement.startsWith("top") && trigger.top - overlay.height - FLOATING_GAP < FLOATING_VIEWPORT_PADDING && trigger.bottom + overlay.height + FLOATING_GAP <= viewportHeight - FLOATING_VIEWPORT_PADDING) {
103
+ resolvedPlacement = placement.replace("top", "bottom");
104
+ } else if (placement.startsWith("bottom") && trigger.bottom + overlay.height + FLOATING_GAP > viewportHeight - FLOATING_VIEWPORT_PADDING && trigger.top - overlay.height - FLOATING_GAP >= FLOATING_VIEWPORT_PADDING) {
105
+ resolvedPlacement = placement.replace("bottom", "top");
106
+ } else if (placement === "left" && trigger.left - overlay.width - FLOATING_GAP < FLOATING_VIEWPORT_PADDING && trigger.right + overlay.width + FLOATING_GAP <= viewportWidth - FLOATING_VIEWPORT_PADDING) {
107
+ resolvedPlacement = "right";
108
+ } else if (placement === "right" && trigger.right + overlay.width + FLOATING_GAP > viewportWidth - FLOATING_VIEWPORT_PADDING && trigger.left - overlay.width - FLOATING_GAP >= FLOATING_VIEWPORT_PADDING) {
109
+ resolvedPlacement = "left";
110
+ }
111
+ let top = trigger.top;
112
+ let left = trigger.left;
113
+ if (resolvedPlacement.startsWith("top") || resolvedPlacement.startsWith("bottom")) {
114
+ top = resolvedPlacement.startsWith("top") ? trigger.top - overlay.height - FLOATING_GAP : trigger.bottom + FLOATING_GAP;
115
+ left = resolvedPlacement.endsWith("-start") ? trigger.left : resolvedPlacement.endsWith("-end") ? trigger.right - overlay.width : trigger.left + (trigger.width - overlay.width) / 2;
116
+ } else {
117
+ top = trigger.top + (trigger.height - overlay.height) / 2;
118
+ left = resolvedPlacement === "left" ? trigger.left - overlay.width - FLOATING_GAP : trigger.right + FLOATING_GAP;
119
+ }
120
+ top = clampFloating(top, FLOATING_VIEWPORT_PADDING, viewportHeight - overlay.height - FLOATING_VIEWPORT_PADDING);
121
+ left = clampFloating(left, FLOATING_VIEWPORT_PADDING, viewportWidth - overlay.width - FLOATING_VIEWPORT_PADDING);
122
+ const result = { top, left, placement: resolvedPlacement };
123
+ if (resolvedPlacement.startsWith("top") || resolvedPlacement.startsWith("bottom")) {
124
+ result.arrowX = resolvedPlacement.endsWith("-start") ? clampFloating(FLOATING_ARROW_EDGE_OFFSET, 8, overlay.width - 16) : resolvedPlacement.endsWith("-end") ? clampFloating(overlay.width - FLOATING_ARROW_EDGE_OFFSET - 8, 8, overlay.width - 16) : clampFloating(trigger.left + trigger.width / 2 - left - 4, 8, overlay.width - 16);
125
+ } else {
126
+ result.arrowY = clampFloating(trigger.top + trigger.height / 2 - top - 4, 8, overlay.height - 16);
127
+ }
128
+ return result;
129
+ }
130
+ function Floating({
131
+ children,
132
+ content,
133
+ placement = "top",
134
+ trigger = "hover",
135
+ open,
136
+ defaultOpen = false,
137
+ mouseEnterDelay = 0.1,
138
+ mouseLeaveDelay = 0.1,
139
+ arrow = true,
140
+ color,
141
+ className = "",
142
+ overlayClassName = "",
143
+ role = "tooltip",
144
+ onOpenChange
145
+ }) {
146
+ const rootRef = useRef2(null);
147
+ const overlayRef = useRef2(null);
148
+ const enterTimer = useRef2();
149
+ const leaveTimer = useRef2();
150
+ const contentId = useId2();
151
+ const triggers = Array.isArray(trigger) ? trigger : [trigger];
152
+ const [visible, setVisible] = useControllableState({ value: open, defaultValue: defaultOpen, onChange: onOpenChange });
153
+ const [position, setPosition] = useState();
154
+ const setVisibleRef = useRef2(setVisible);
155
+ setVisibleRef.current = setVisible;
156
+ const closeSelfRef = useRef2();
157
+ if (!closeSelfRef.current) closeSelfRef.current = () => setVisibleRef.current(false);
158
+ const exclusiveTooltip = role === "tooltip" && triggers.includes("hover");
159
+ function openNow() {
160
+ if (exclusiveTooltip && activeTooltipClose !== closeSelfRef.current) {
161
+ activeTooltipClose?.();
162
+ activeTooltipClose = closeSelfRef.current;
163
+ }
164
+ setVisible(true);
165
+ }
166
+ function closeNow() {
167
+ if (activeTooltipClose === closeSelfRef.current) activeTooltipClose = void 0;
168
+ setVisible(false);
169
+ }
170
+ function clearTimers() {
171
+ window.clearTimeout(enterTimer.current);
172
+ window.clearTimeout(leaveTimer.current);
173
+ }
174
+ function show(delay = 0) {
175
+ clearTimers();
176
+ if (delay <= 0) openNow();
177
+ else enterTimer.current = window.setTimeout(openNow, delay * 1e3);
178
+ }
179
+ function hide(delay = 0) {
180
+ clearTimers();
181
+ if (delay <= 0) closeNow();
182
+ else leaveTimer.current = window.setTimeout(closeNow, delay * 1e3);
183
+ }
184
+ useEffect2(() => {
185
+ if (!visible || !triggers.includes("click")) return;
186
+ const close = (event) => {
187
+ const target = event.target;
188
+ if (!rootRef.current?.contains(target) && !overlayRef.current?.contains(target)) closeNow();
189
+ };
190
+ document.addEventListener("pointerdown", close);
191
+ return () => document.removeEventListener("pointerdown", close);
192
+ }, [triggers.join("|"), visible]);
193
+ useLayoutEffect(() => {
194
+ if (!visible) {
195
+ setPosition(void 0);
196
+ return;
197
+ }
198
+ const update = () => {
199
+ if (!rootRef.current || !overlayRef.current) return;
200
+ setPosition(resolveFloatingPosition(rootRef.current.getBoundingClientRect(), overlayRef.current.getBoundingClientRect(), placement));
201
+ };
202
+ update();
203
+ window.addEventListener("resize", update);
204
+ window.addEventListener("scroll", update, true);
205
+ const observer = new ResizeObserver(update);
206
+ if (rootRef.current) observer.observe(rootRef.current);
207
+ if (overlayRef.current) observer.observe(overlayRef.current);
208
+ return () => {
209
+ window.removeEventListener("resize", update);
210
+ window.removeEventListener("scroll", update, true);
211
+ observer.disconnect();
212
+ };
213
+ }, [children, content, placement, visible]);
214
+ useEffect2(() => () => {
215
+ clearTimers();
216
+ if (activeTooltipClose === closeSelfRef.current) activeTooltipClose = void 0;
217
+ }, []);
218
+ const child = cloneElement(children, {
219
+ ...triggers.includes("hover") ? { onMouseEnter: (event) => {
220
+ children.props.onMouseEnter?.(event);
221
+ show(mouseEnterDelay);
222
+ }, onMouseLeave: (event) => {
223
+ children.props.onMouseLeave?.(event);
224
+ hide(mouseLeaveDelay);
225
+ } } : {},
226
+ ...triggers.includes("focus") ? { onFocus: (event) => {
227
+ children.props.onFocus?.(event);
228
+ show();
229
+ }, onBlur: (event) => {
230
+ children.props.onBlur?.(event);
231
+ hide();
232
+ } } : {},
233
+ ...triggers.includes("click") ? { onClick: (event) => {
234
+ children.props.onClick?.(event);
235
+ if (visible) closeNow();
236
+ else openNow();
237
+ } } : {},
238
+ "aria-describedby": visible ? contentId : void 0
239
+ });
240
+ const overlay = visible && content !== null && content !== void 0 && typeof document !== "undefined" ? createPortal(
241
+ /* @__PURE__ */ jsxs2(
242
+ "span",
243
+ {
244
+ ref: overlayRef,
245
+ id: contentId,
246
+ role,
247
+ "data-custom-color": color ? "" : void 0,
248
+ className: `sia-floating__overlay sia-floating__overlay--${position?.placement ?? placement} ${overlayClassName}`.trim(),
249
+ style: {
250
+ "--sia-floating-color": color,
251
+ "--sia-floating-arrow-x": position?.arrowX == null ? void 0 : `${position.arrowX}px`,
252
+ "--sia-floating-arrow-y": position?.arrowY == null ? void 0 : `${position.arrowY}px`,
253
+ top: position?.top ?? 0,
254
+ left: position?.left ?? 0,
255
+ visibility: position ? "visible" : "hidden"
256
+ },
257
+ onMouseEnter: triggers.includes("hover") ? () => show() : void 0,
258
+ onMouseLeave: triggers.includes("hover") ? () => hide(mouseLeaveDelay) : void 0,
259
+ children: [
260
+ content,
261
+ arrow ? /* @__PURE__ */ jsx2("span", { className: "sia-floating__arrow" }) : null
262
+ ]
263
+ }
264
+ ),
265
+ document.body
266
+ ) : null;
267
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
268
+ /* @__PURE__ */ jsx2("span", { ref: rootRef, className: `sia-floating ${className}`.trim(), children: child }),
269
+ overlay
270
+ ] });
271
+ }
272
+ function Tooltip(props) {
273
+ return /* @__PURE__ */ jsx2(Floating, { ...props, content: props.title });
274
+ }
275
+ function Popover({ title, content, trigger = "click", overlayClassName = "", ...props }) {
276
+ return /* @__PURE__ */ jsx2(Floating, { ...props, trigger, role: "dialog", overlayClassName: `sia-popover ${overlayClassName}`.trim(), content: /* @__PURE__ */ jsxs2(Fragment, { children: [
277
+ title ? /* @__PURE__ */ jsx2("strong", { className: "sia-popover__title", children: title }) : null,
278
+ /* @__PURE__ */ jsx2("span", { className: "sia-popover__content", children: content })
279
+ ] }) });
280
+ }
281
+ function Timeline({ items = [], mode = "left", pending = false, pendingDot, reverse = false, className = "", ...props }) {
282
+ const values = [...items, ...pending ? [{ children: pending === true ? "\u52A0\u8F7D\u4E2D..." : pending, dot: pendingDot ?? /* @__PURE__ */ jsx2(Icon, { name: "loader", className: "sia-spin-icon" }) }] : []];
283
+ if (reverse) values.reverse();
284
+ return /* @__PURE__ */ jsx2("ul", { className: `sia-timeline sia-timeline--${mode} ${className}`.trim(), ...props, children: values.map((item, index) => {
285
+ const position = item.position ?? (mode === "alternate" ? index % 2 ? "right" : "left" : mode);
286
+ return /* @__PURE__ */ jsxs2("li", { className: `sia-timeline__item sia-timeline__item--${position}`, children: [
287
+ item.label ? /* @__PURE__ */ jsx2("span", { className: "sia-timeline__label", children: item.label }) : null,
288
+ /* @__PURE__ */ jsx2("span", { className: "sia-timeline__rail", children: /* @__PURE__ */ jsx2("span", { className: "sia-timeline__dot", style: { color: item.color && !["blue", "red", "green", "gray"].includes(item.color) ? item.color : void 0 }, "data-color": item.color ?? "blue", children: item.dot }) }),
289
+ /* @__PURE__ */ jsx2("span", { className: "sia-timeline__content", children: item.children })
290
+ ] }, index);
291
+ }) });
292
+ }
293
+ function collectKeys(nodes, branchOnly = false, result = []) {
294
+ nodes.forEach((node) => {
295
+ if (!branchOnly || node.children?.length) result.push(node.key);
296
+ if (node.children) collectKeys(node.children, branchOnly, result);
297
+ });
298
+ return result;
299
+ }
300
+ function descendants(node) {
301
+ return node.children ? collectKeys(node.children) : [];
302
+ }
303
+ function Tree({
304
+ treeData,
305
+ selectedKeys,
306
+ defaultSelectedKeys = [],
307
+ expandedKeys,
308
+ defaultExpandedKeys = [],
309
+ defaultExpandAll = false,
310
+ checkedKeys,
311
+ defaultCheckedKeys = [],
312
+ loadingKeys = [],
313
+ checkable = false,
314
+ checkStrictly = false,
315
+ multiple = false,
316
+ selectable = true,
317
+ showLine = false,
318
+ showIcon = false,
319
+ blockNode = false,
320
+ indentSize = 20,
321
+ switcherWidth = 24,
322
+ stickyAncestors = false,
323
+ stickyRowHeight = 34,
324
+ draggable = false,
325
+ loadData,
326
+ onSelect,
327
+ onCheck,
328
+ onExpand,
329
+ onDrop,
330
+ className = "",
331
+ ...props
332
+ }) {
333
+ const [selected, setSelected] = useControllableState({ value: selectedKeys, defaultValue: defaultSelectedKeys });
334
+ const [expanded, setExpanded] = useControllableState({ value: expandedKeys, defaultValue: defaultExpandAll ? collectKeys(treeData, true) : defaultExpandedKeys });
335
+ const [checked, setChecked] = useControllableState({ value: checkedKeys, defaultValue: defaultCheckedKeys });
336
+ const [loading, setLoading] = useState(/* @__PURE__ */ new Set());
337
+ const controlledLoading = useMemo2(() => new Set(loadingKeys), [loadingKeys]);
338
+ const selectedKeySet = useMemo2(() => new Set(selected), [selected]);
339
+ const selectedAncestorKeys = useMemo2(() => {
340
+ const ancestors = /* @__PURE__ */ new Set();
341
+ const visit = (nodes, parentKeys) => {
342
+ nodes.forEach((node) => {
343
+ if (selectedKeySet.has(node.key)) parentKeys.forEach((key) => ancestors.add(key));
344
+ if (node.children?.length) visit(node.children, [...parentKeys, node.key]);
345
+ });
346
+ };
347
+ visit(treeData, []);
348
+ return ancestors;
349
+ }, [selectedKeySet, treeData]);
350
+ const dragNodeRef = useRef2();
351
+ const pointerDragRef = useRef2();
352
+ const suppressClickRef = useRef2(false);
353
+ const [dragOver, setDragOver] = useState();
354
+ const normalizedSwitcherWidth = Math.max(0, switcherWidth);
355
+ async function toggleExpand(node) {
356
+ const nextExpanded = !expanded.includes(node.key);
357
+ if (nextExpanded && node.loaded !== true && !node.children?.length && node.isLeaf === false && loadData) {
358
+ setLoading((keys) => new Set(keys).add(node.key));
359
+ try {
360
+ await loadData(node);
361
+ } finally {
362
+ setLoading((keys) => {
363
+ const next2 = new Set(keys);
364
+ next2.delete(node.key);
365
+ return next2;
366
+ });
367
+ }
368
+ }
369
+ const next = nextExpanded ? [...expanded, node.key] : expanded.filter((key) => key !== node.key);
370
+ setExpanded(next);
371
+ onExpand?.([...next], { expanded: nextExpanded, node });
372
+ }
373
+ function check(node) {
374
+ const nodeChecked = checked.includes(node.key);
375
+ const affected = checkStrictly ? [node.key] : [node.key, ...descendants(node)];
376
+ const next = nodeChecked ? checked.filter((key) => !affected.includes(key)) : [.../* @__PURE__ */ new Set([...checked, ...affected])];
377
+ setChecked(next);
378
+ onCheck?.([...next], { checked: !nodeChecked, node });
379
+ }
380
+ function select(node, event) {
381
+ if (!selectable || node.selectable === false || node.disabled) return;
382
+ const exists = selected.includes(node.key);
383
+ const next = multiple ? exists ? selected.filter((key) => key !== node.key) : [...selected, node.key] : exists ? [] : [node.key];
384
+ setSelected(next);
385
+ onSelect?.([...next], { selected: !exists, node, nativeEvent: event });
386
+ }
387
+ function selectForContextMenu(node, event) {
388
+ if (!selectable || node.selectable === false || node.disabled || selected.includes(node.key)) return;
389
+ const next = [node.key];
390
+ setSelected(next);
391
+ onSelect?.(next, { selected: true, node, nativeEvent: event });
392
+ }
393
+ function findNode(key, nodes = treeData) {
394
+ for (const node of nodes) {
395
+ if (String(node.key) === key) return node;
396
+ const child = node.children?.length ? findNode(key, node.children) : void 0;
397
+ if (child) return child;
398
+ }
399
+ return void 0;
400
+ }
401
+ function getPointerDropTarget(clientX, clientY, dragNode) {
402
+ const targetElement = document.elementFromPoint(clientX, clientY)?.closest("[data-sia-tree-drop-key]");
403
+ if (!targetElement) return void 0;
404
+ const targetNode = targetElement?.dataset.siaTreeDropKey ? findNode(targetElement.dataset.siaTreeDropKey) : void 0;
405
+ if (!targetNode || targetNode.key === dragNode.key || targetNode.disabled || targetNode.droppable === false) return void 0;
406
+ const bounds = targetElement.getBoundingClientRect();
407
+ const relativeY = bounds.height > 0 ? (clientY - bounds.top) / bounds.height : 0.5;
408
+ const dropPosition = relativeY < 0.25 ? "before" : relativeY > 0.75 ? "after" : "inside";
409
+ return { node: targetNode, dropPosition };
410
+ }
411
+ function beginPointerDrag(node, event) {
412
+ if (event.button !== 0) return;
413
+ pointerDragRef.current = {
414
+ node,
415
+ pointerId: event.pointerId,
416
+ startX: event.clientX,
417
+ startY: event.clientY,
418
+ active: false
419
+ };
420
+ event.currentTarget.setPointerCapture(event.pointerId);
421
+ }
422
+ function movePointerDrag(event) {
423
+ const pointerDrag = pointerDragRef.current;
424
+ if (!pointerDrag || pointerDrag.pointerId !== event.pointerId) return;
425
+ if (!pointerDrag.active && Math.hypot(event.clientX - pointerDrag.startX, event.clientY - pointerDrag.startY) < 5) return;
426
+ pointerDrag.active = true;
427
+ dragNodeRef.current = pointerDrag.node;
428
+ event.preventDefault();
429
+ const target = getPointerDropTarget(event.clientX, event.clientY, pointerDrag.node);
430
+ setDragOver(target ? { key: target.node.key, position: target.dropPosition } : void 0);
431
+ }
432
+ function finishPointerDrag(event) {
433
+ const pointerDrag = pointerDragRef.current;
434
+ if (!pointerDrag || pointerDrag.pointerId !== event.pointerId) return;
435
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
436
+ if (pointerDrag.active) {
437
+ event.preventDefault();
438
+ suppressClickRef.current = true;
439
+ const target = getPointerDropTarget(event.clientX, event.clientY, pointerDrag.node);
440
+ if (target) onDrop?.({ dragNode: pointerDrag.node, node: target.node, dropPosition: target.dropPosition });
441
+ window.setTimeout(() => {
442
+ suppressClickRef.current = false;
443
+ }, 0);
444
+ }
445
+ pointerDragRef.current = void 0;
446
+ dragNodeRef.current = void 0;
447
+ setDragOver(void 0);
448
+ }
449
+ function cancelPointerDrag(event) {
450
+ const pointerDrag = pointerDragRef.current;
451
+ if (!pointerDrag || pointerDrag.pointerId !== event.pointerId) return;
452
+ pointerDragRef.current = void 0;
453
+ dragNodeRef.current = void 0;
454
+ setDragOver(void 0);
455
+ }
456
+ function render(nodes, depth = 0) {
457
+ return nodes.map((node) => {
458
+ const open = expanded.includes(node.key);
459
+ const isSelected = selected.includes(node.key);
460
+ const isSelectedAncestor = selectedAncestorKeys.has(node.key);
461
+ const isChecked = checked.includes(node.key);
462
+ const hasChildren = Boolean(node.children?.length || node.isLeaf === false);
463
+ const nodeLoading = loading.has(node.key) || controlledLoading.has(node.key);
464
+ const sticky = stickyAncestors && hasChildren && open;
465
+ const canDrag = draggable && node.draggable !== false && !node.disabled;
466
+ const { className: rowClassName = "", style: rowStyle, onClick: onRowClick, ...rowProps } = node.rowProps ?? {};
467
+ const unloaded = hasChildren && node.loaded === false;
468
+ const dropPosition = dragOver?.key === node.key ? dragOver.position : void 0;
469
+ return /* @__PURE__ */ jsxs2("div", { className: `sia-tree__node${isSelected ? " is-selected" : ""}${isSelectedAncestor ? " is-selected-ancestor" : ""}${dropPosition ? ` is-drag-over-${dropPosition}` : ""}${node.disabled ? " is-disabled" : ""}${unloaded ? " is-unloaded" : ""}`, role: "treeitem", "aria-expanded": hasChildren ? open : void 0, "aria-selected": isSelected, children: [
470
+ /* @__PURE__ */ jsx2(
471
+ "div",
472
+ {
473
+ ...rowProps,
474
+ className: `sia-tree__row${blockNode ? " sia-tree__row--block" : ""}${sticky ? " sia-tree__row--sticky" : ""} ${rowClassName}`.trim(),
475
+ style: {
476
+ ...rowStyle,
477
+ paddingInlineStart: depth * Math.max(0, indentSize),
478
+ top: sticky ? depth * Math.max(1, stickyRowHeight) : rowStyle?.top,
479
+ zIndex: sticky ? Math.max(1, 100 - depth) : rowStyle?.zIndex
480
+ },
481
+ "aria-busy": nodeLoading || void 0,
482
+ "data-sia-tree-drop-key": String(node.key),
483
+ "data-sia-tree-draggable": canDrag || void 0,
484
+ onClick: (event) => {
485
+ onRowClick?.(event);
486
+ if (event.defaultPrevented || suppressClickRef.current || node.disabled) return;
487
+ select(node, event);
488
+ if (hasChildren) void toggleExpand(node);
489
+ },
490
+ children: (() => {
491
+ const content = /* @__PURE__ */ jsxs2("span", { className: "sia-tree__context-content", children: [
492
+ hasChildren ? /* @__PURE__ */ jsx2("button", { type: "button", className: "sia-tree__switcher", "aria-label": open ? "\u6536\u8D77\u8282\u70B9" : "\u5C55\u5F00\u8282\u70B9", "aria-expanded": open, style: { width: normalizedSwitcherWidth, flexBasis: normalizedSwitcherWidth }, disabled: node.disabled, onClick: (event) => {
493
+ event.stopPropagation();
494
+ void toggleExpand(node);
495
+ }, children: nodeLoading ? /* @__PURE__ */ jsx2(Icon, { name: "loader", className: "sia-spin-icon", size: 13 }) : /* @__PURE__ */ jsx2(Icon, { name: open ? "chevron-down" : "chevron-right", size: 13 }) }) : /* @__PURE__ */ jsx2("span", { className: "sia-tree__switcher", "aria-label": open ? "\u6536\u8D77\u8282\u70B9" : "\u5C55\u5F00\u8282\u70B9", "aria-expanded": open, style: { width: normalizedSwitcherWidth, flexBasis: normalizedSwitcherWidth }, "aria-hidden": "true" }),
496
+ checkable || node.checkable ? /* @__PURE__ */ jsx2("span", { onClick: (event) => event.stopPropagation(), children: /* @__PURE__ */ jsx2(Checkbox2, { checked: isChecked, disabled: node.disabled || node.disableCheckbox, onChange: () => check(node) }) }) : null,
497
+ node.prefix ? /* @__PURE__ */ jsx2("span", { className: "sia-tree__prefix", onClick: (event) => event.stopPropagation(), children: node.prefix }) : null,
498
+ showIcon ? /* @__PURE__ */ jsx2("span", { className: "sia-tree__icon", children: node.icon ?? /* @__PURE__ */ jsx2(Icon, { name: hasChildren ? "folder" : "file", size: 15 }) }) : null,
499
+ /* @__PURE__ */ jsx2(
500
+ "button",
501
+ {
502
+ type: "button",
503
+ className: "sia-tree__title",
504
+ disabled: node.disabled,
505
+ onPointerDown: canDrag ? (event) => beginPointerDrag(node, event) : void 0,
506
+ onPointerMove: canDrag ? movePointerDrag : void 0,
507
+ onPointerUp: canDrag ? finishPointerDrag : void 0,
508
+ onPointerCancel: canDrag ? cancelPointerDrag : void 0,
509
+ children: node.title
510
+ }
511
+ ),
512
+ nodeLoading ? /* @__PURE__ */ jsx2("span", { className: "sia-tree__loading-text", role: "status", children: node.loadingText ?? "\u6B63\u5728\u52A0\u8F7D\u2026" }) : null,
513
+ node.extra ? /* @__PURE__ */ jsx2("span", { className: "sia-tree__extra", onClick: (event) => event.stopPropagation(), children: node.extra }) : null
514
+ ] });
515
+ return node.contextMenu ? /* @__PURE__ */ jsx2(
516
+ Dropdown,
517
+ {
518
+ className: "sia-tree__context-trigger",
519
+ popupClassName: node.contextMenuPopupClassName,
520
+ trigger: ["contextMenu"],
521
+ menu: node.contextMenu,
522
+ onContextMenu: (event) => selectForContextMenu(node, event),
523
+ children: content
524
+ }
525
+ ) : content;
526
+ })()
527
+ }
528
+ ),
529
+ open && node.children?.length ? /* @__PURE__ */ jsx2("div", { className: "sia-tree__children", role: "group", children: render(node.children, depth + 1) }) : null
530
+ ] }, node.key);
531
+ });
532
+ }
533
+ return /* @__PURE__ */ jsx2("div", { className: `sia-tree${showLine ? " sia-tree--line" : ""}${stickyAncestors ? " sia-tree--sticky-ancestors" : ""} ${className}`.trim(), role: "tree", "aria-multiselectable": multiple || void 0, ...props, children: render(treeData) });
534
+ }
535
+
536
+ // src/components/Modal.tsx
537
+ import { forwardRef as forwardRef2, useEffect as useEffect4, useId as useId3, useRef as useRef4, useState as useState2 } from "react";
538
+ import { createRoot } from "react-dom/client";
539
+
540
+ // src/components/Overlay.tsx
541
+ import { useEffect as useEffect3, useRef as useRef3 } from "react";
542
+ import { createPortal as createPortal2 } from "react-dom";
543
+ var bodyLockCount = 0;
544
+ var previousBodyOverflow = "";
545
+ var overlayStack = [];
546
+ function lockBodyScroll() {
547
+ if (bodyLockCount === 0) {
548
+ previousBodyOverflow = document.body.style.overflow;
549
+ document.body.style.overflow = "hidden";
550
+ }
551
+ bodyLockCount += 1;
552
+ return () => {
553
+ bodyLockCount = Math.max(0, bodyLockCount - 1);
554
+ if (bodyLockCount === 0) document.body.style.overflow = previousBodyOverflow;
555
+ };
556
+ }
557
+ function focusableElements(container) {
558
+ return [...container.querySelectorAll(
559
+ 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
560
+ )].filter((element) => !element.hidden && element.getAttribute("aria-hidden") !== "true");
561
+ }
562
+ function useOverlayLifecycle(open, panelRef, onRequestClose, keyboard = true) {
563
+ const overlayIdRef = useRef3(/* @__PURE__ */ Symbol("sia-overlay"));
564
+ const closeRef = useRef3(onRequestClose);
565
+ closeRef.current = onRequestClose;
566
+ useEffect3(() => {
567
+ if (!open || typeof document === "undefined") return void 0;
568
+ const overlayId = overlayIdRef.current;
569
+ const previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
570
+ overlayStack.push(overlayId);
571
+ const unlockBody = lockBodyScroll();
572
+ const focusTimer = window.setTimeout(() => {
573
+ const panel = panelRef.current;
574
+ if (!panel) return;
575
+ const [firstFocusable] = focusableElements(panel);
576
+ (firstFocusable ?? panel).focus({ preventScroll: true });
577
+ }, 0);
578
+ function handleKeyDown(event) {
579
+ if (overlayStack.at(-1) !== overlayId) return;
580
+ if (event.key === "Escape" && keyboard) {
581
+ event.preventDefault();
582
+ closeRef.current();
583
+ return;
584
+ }
585
+ if (event.key !== "Tab" || !panelRef.current) return;
586
+ const focusable = focusableElements(panelRef.current);
587
+ if (focusable.length === 0) {
588
+ event.preventDefault();
589
+ panelRef.current.focus();
590
+ return;
591
+ }
592
+ const first = focusable[0];
593
+ const last = focusable.at(-1);
594
+ if (event.shiftKey && document.activeElement === first) {
595
+ event.preventDefault();
596
+ last.focus();
597
+ } else if (!event.shiftKey && document.activeElement === last) {
598
+ event.preventDefault();
599
+ first.focus();
600
+ }
601
+ }
602
+ document.addEventListener("keydown", handleKeyDown);
603
+ return () => {
604
+ window.clearTimeout(focusTimer);
605
+ document.removeEventListener("keydown", handleKeyDown);
606
+ const index = overlayStack.lastIndexOf(overlayId);
607
+ if (index >= 0) overlayStack.splice(index, 1);
608
+ unlockBody();
609
+ previouslyFocused?.focus({ preventScroll: true });
610
+ };
611
+ }, [keyboard, open, panelRef]);
612
+ }
613
+ function OverlayPortal({ children, container }) {
614
+ if (typeof document === "undefined") return null;
615
+ const target = typeof container === "function" ? container() : container ?? document.body;
616
+ return createPortal2(children, target);
617
+ }
618
+
619
+ // src/components/Modal.tsx
620
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
621
+ var modalHandles = /* @__PURE__ */ new Set();
622
+ var MODAL_MOTION_DURATION = 220;
623
+ var typeIcons = {
624
+ info: /* @__PURE__ */ jsx3(Icon, { name: "info", size: 23 }),
625
+ success: /* @__PURE__ */ jsx3(Icon, { name: "circle-check", size: 23 }),
626
+ warning: /* @__PURE__ */ jsx3(Icon, { name: "warning", size: 23 }),
627
+ error: /* @__PURE__ */ jsx3(Icon, { name: "circle-close", size: 23 }),
628
+ confirm: /* @__PURE__ */ jsx3(Icon, { name: "question", size: 23 })
629
+ };
630
+ var ModalRoot = forwardRef2(function Modal({
631
+ open,
632
+ title,
633
+ children,
634
+ footer,
635
+ type = "default",
636
+ okText = "\u786E\u5B9A",
637
+ cancelText = "\u53D6\u6D88",
638
+ showCancel = type === "default" || type === "confirm",
639
+ confirmLoading = false,
640
+ okButtonProps,
641
+ cancelButtonProps,
642
+ closable = true,
643
+ maskClosable = true,
644
+ keyboard = true,
645
+ centered = false,
646
+ width = 520,
647
+ zIndex = 1e3,
648
+ getContainer,
649
+ onOk,
650
+ onCancel,
651
+ onOpenChange,
652
+ afterOpenChange,
653
+ className = "",
654
+ style,
655
+ ...props
656
+ }, forwardedRef) {
657
+ const localRef = useRef4(null);
658
+ const titleId = useId3();
659
+ const [submitting, setSubmitting] = useState2(false);
660
+ const [rendered, setRendered] = useState2(open);
661
+ const [closing, setClosing] = useState2(false);
662
+ const afterOpenChangeRef = useRef4(afterOpenChange);
663
+ afterOpenChangeRef.current = afterOpenChange;
664
+ const submitLock = useRef4(false);
665
+ useEffect4(() => {
666
+ if (!open || !rendered) return void 0;
667
+ const timer = window.setTimeout(() => afterOpenChangeRef.current?.(true), MODAL_MOTION_DURATION);
668
+ return () => window.clearTimeout(timer);
669
+ }, [open, rendered]);
670
+ useEffect4(() => {
671
+ if (open) {
672
+ setRendered(true);
673
+ setClosing(false);
674
+ return void 0;
675
+ }
676
+ if (!rendered) return void 0;
677
+ setClosing(true);
678
+ const timer = window.setTimeout(() => {
679
+ setRendered(false);
680
+ setClosing(false);
681
+ afterOpenChangeRef.current?.(false);
682
+ }, MODAL_MOTION_DURATION);
683
+ return () => window.clearTimeout(timer);
684
+ }, [open, rendered]);
685
+ function setRef(node) {
686
+ localRef.current = node;
687
+ if (typeof forwardedRef === "function") forwardedRef(node);
688
+ else if (forwardedRef) forwardedRef.current = node;
689
+ }
690
+ function requestClose() {
691
+ if (submitting || confirmLoading) return;
692
+ onCancel?.();
693
+ onOpenChange?.(false);
694
+ }
695
+ async function handleOk() {
696
+ if (submitLock.current || confirmLoading) return;
697
+ submitLock.current = true;
698
+ if (!onOk) {
699
+ onOpenChange?.(false);
700
+ submitLock.current = false;
701
+ return;
702
+ }
703
+ try {
704
+ const result = onOk();
705
+ if (result instanceof Promise) {
706
+ setSubmitting(true);
707
+ const resolved = await result;
708
+ if (resolved !== false) onOpenChange?.(false);
709
+ } else if (result !== false) {
710
+ onOpenChange?.(false);
711
+ }
712
+ } catch {
713
+ return;
714
+ } finally {
715
+ submitLock.current = false;
716
+ setSubmitting(false);
717
+ }
718
+ }
719
+ useOverlayLifecycle(rendered, localRef, requestClose, keyboard);
720
+ if (!rendered) return null;
721
+ const mergedStyle = {
722
+ ...style,
723
+ "--sia-modal-width": typeof width === "number" ? `${width}px` : width
724
+ };
725
+ const loading = submitting || confirmLoading;
726
+ const defaultFooter = /* @__PURE__ */ jsxs3(Fragment2, { children: [
727
+ showCancel ? /* @__PURE__ */ jsx3(Button, { ...cancelButtonProps, onClick: requestClose, disabled: loading || cancelButtonProps?.disabled, children: cancelText }) : null,
728
+ /* @__PURE__ */ jsx3(Button, { variant: "primary", ...okButtonProps, onClick: handleOk, loading, children: okText })
729
+ ] });
730
+ return /* @__PURE__ */ jsx3(OverlayPortal, { container: getContainer, children: /* @__PURE__ */ jsx3(
731
+ "div",
732
+ {
733
+ className: `sia-modal-root${centered ? " sia-modal-root--centered" : ""}`,
734
+ "data-state": closing ? "closing" : "open",
735
+ style: { zIndex },
736
+ onMouseDown: (event) => {
737
+ if (event.target === event.currentTarget && maskClosable) requestClose();
738
+ },
739
+ children: /* @__PURE__ */ jsxs3(
740
+ "div",
741
+ {
742
+ ref: setRef,
743
+ className: `sia-modal sia-modal--${type} ${className}`.trim(),
744
+ style: mergedStyle,
745
+ role: "dialog",
746
+ "aria-modal": "true",
747
+ "aria-labelledby": title ? titleId : void 0,
748
+ tabIndex: -1,
749
+ ...props,
750
+ children: [
751
+ /* @__PURE__ */ jsxs3("div", { className: "sia-modal__header", children: [
752
+ /* @__PURE__ */ jsxs3("div", { className: "sia-modal__heading", children: [
753
+ typeIcons[type] ? /* @__PURE__ */ jsx3("span", { className: "sia-modal__type-icon", "aria-hidden": "true", children: typeIcons[type] }) : null,
754
+ title ? /* @__PURE__ */ jsx3("strong", { id: titleId, children: title }) : null
755
+ ] }),
756
+ closable ? /* @__PURE__ */ jsx3("button", { type: "button", className: "sia-modal__close", "aria-label": "\u5173\u95ED\u5BF9\u8BDD\u6846", onClick: requestClose, children: /* @__PURE__ */ jsx3(Icon, { name: "close", size: 17 }) }) : null
757
+ ] }),
758
+ children !== void 0 ? /* @__PURE__ */ jsx3("div", { className: "sia-modal__body", children }) : null,
759
+ footer !== null ? /* @__PURE__ */ jsx3("div", { className: "sia-modal__footer", children: footer ?? defaultFooter }) : null
760
+ ]
761
+ }
762
+ )
763
+ }
764
+ ) });
765
+ });
766
+ function openModal(config) {
767
+ if (typeof document === "undefined") return { destroy() {
768
+ }, update() {
769
+ } };
770
+ const container = document.createElement("div");
771
+ container.className = "sia-modal-api-host";
772
+ document.body.appendChild(container);
773
+ const root = createRoot(container);
774
+ let currentConfig = config;
775
+ let currentOpen = true;
776
+ let destroyed = false;
777
+ let cleanupTimer;
778
+ function cleanup() {
779
+ if (destroyed) return;
780
+ destroyed = true;
781
+ if (cleanupTimer !== void 0) window.clearTimeout(cleanupTimer);
782
+ root.unmount();
783
+ container.remove();
784
+ modalHandles.delete(handle);
785
+ }
786
+ function closeWithMotion() {
787
+ if (destroyed || !currentOpen) return;
788
+ currentOpen = false;
789
+ render();
790
+ cleanupTimer = window.setTimeout(cleanup, MODAL_MOTION_DURATION);
791
+ }
792
+ function render() {
793
+ root.render(/* @__PURE__ */ jsx3(ModalRoot, { ...currentConfig, open: currentOpen, onOpenChange: (nextOpen) => {
794
+ currentConfig.onOpenChange?.(nextOpen);
795
+ if (!nextOpen) closeWithMotion();
796
+ } }));
797
+ }
798
+ const handle = {
799
+ destroy: closeWithMotion,
800
+ update(next) {
801
+ if (destroyed) return;
802
+ currentConfig = { ...currentConfig, ...next };
803
+ render();
804
+ }
805
+ };
806
+ modalHandles.add(handle);
807
+ render();
808
+ return handle;
809
+ }
810
+ function preset(type, defaults = {}) {
811
+ return (config) => openModal({
812
+ okText: type === "confirm" ? "\u786E\u5B9A" : "\u77E5\u9053\u4E86",
813
+ showCancel: type === "confirm",
814
+ closable: type !== "confirm",
815
+ maskClosable: type !== "confirm",
816
+ ...defaults,
817
+ ...config,
818
+ type
819
+ });
820
+ }
821
+ var Modal2 = Object.assign(ModalRoot, {
822
+ open: openModal,
823
+ info: preset("info", { title: "\u63D0\u793A" }),
824
+ success: preset("success", { title: "\u64CD\u4F5C\u6210\u529F" }),
825
+ warning: preset("warning", { title: "\u8BF7\u6CE8\u610F" }),
826
+ error: preset("error", { title: "\u64CD\u4F5C\u5931\u8D25" }),
827
+ confirm: preset("confirm", { title: "\u786E\u8BA4\u64CD\u4F5C" }),
828
+ destroyAll() {
829
+ [...modalHandles].forEach((handle) => handle.destroy());
830
+ }
831
+ });
832
+
833
+ // src/components/SelectionPanel.tsx
834
+ import { useEffect as useEffect5, useId as useId4, useMemo as useMemo3, useRef as useRef5, useState as useState3 } from "react";
835
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
836
+ function SelectionPanel({
837
+ options,
838
+ value,
839
+ onChange,
840
+ multiple = false,
841
+ disabled = false,
842
+ loading = false,
843
+ showSearch = true,
844
+ showActions = true,
845
+ includeUnknownValues = true,
846
+ searchValue,
847
+ onSearch,
848
+ selectedOnly = false,
849
+ showValue = false,
850
+ displayField = "label",
851
+ columns = 1,
852
+ maxHeight = 250,
853
+ autoFocusSearch = false
854
+ }) {
855
+ const [localSearch, setLocalSearch] = useState3("");
856
+ const search = searchValue ?? localSearch;
857
+ const [scrollTop, setScrollTop] = useState3(0);
858
+ const [active, setActive] = useState3();
859
+ const listRef = useRef5(null);
860
+ const id = useId4();
861
+ const selected = useMemo3(() => new Set(value), [value]);
862
+ const count = Math.max(1, Math.floor(columns));
863
+ const allOptions = useMemo3(() => {
864
+ if (!includeUnknownValues) return [...options];
865
+ const known = new Set(options.filter((option) => option.optionType !== "divider").map((option) => option.value));
866
+ return [...value.filter((item) => !known.has(item)).map((item) => ({ value: item, label: String(item), group: "\u81EA\u5B9A\u4E49\u503C" })), ...options];
867
+ }, [options, value, includeUnknownValues]);
868
+ const filtered = useMemo3(
869
+ () => allOptions.filter((option) => (!selectedOnly || selected.has(option.value)) && (!search || option.optionType !== "divider" && [option.label, option.value].some((text) => String(text ?? "").toLowerCase().includes(search.toLowerCase())))),
870
+ [allOptions, selectedOnly, selected, search]
871
+ );
872
+ const rows = useMemo3(() => {
873
+ const groups = /* @__PURE__ */ new Map();
874
+ filtered.forEach((option) => {
875
+ const items = groups.get(option.group) ?? [];
876
+ items.push(option);
877
+ groups.set(option.group, items);
878
+ });
879
+ const rows2 = [];
880
+ let top = 0;
881
+ for (const [group, items] of groups) {
882
+ if (group) {
883
+ rows2.push({ key: `group-${rows2.length}`, title: group, top, height: 30 });
884
+ top += 30;
885
+ }
886
+ for (let index = 0; index < items.length; index += count) {
887
+ const height = showValue ? 48 : 34;
888
+ rows2.push({ key: `row-${rows2.length}`, items: items.slice(index, index + count), top, height });
889
+ top += height;
890
+ }
891
+ }
892
+ return rows2;
893
+ }, [filtered, count, showValue]);
894
+ const totalHeight = rows.length ? rows[rows.length - 1].top + rows[rows.length - 1].height : 0;
895
+ const virtual = filtered.length > 100;
896
+ const renderedRows = virtual ? rows.filter((row) => row.top + row.height >= scrollTop - 100 && row.top <= scrollTop + maxHeight + 100) : rows;
897
+ const enabled = filtered.filter((option) => !option.disabled && option.optionType !== "divider");
898
+ useEffect5(() => {
899
+ setScrollTop(0);
900
+ if (listRef.current) listRef.current.scrollTop = 0;
901
+ setActive(void 0);
902
+ }, [search, selectedOnly, count]);
903
+ function toggle(option) {
904
+ if (disabled || loading || option.disabled || option.optionType === "divider") return;
905
+ setActive(option.value);
906
+ onChange(multiple ? selected.has(option.value) ? value.filter((item) => item !== option.value) : [...value, option.value] : [option.value]);
907
+ }
908
+ function batch(operation) {
909
+ if (disabled || loading) return;
910
+ if (operation === "clear") {
911
+ onChange([]);
912
+ return;
913
+ }
914
+ const editable = new Set(enabled.map((option) => option.value));
915
+ const retained = value.filter((item) => !editable.has(item));
916
+ const add = enabled.filter((option) => operation === "all" || !selected.has(option.value)).map((option) => option.value);
917
+ onChange([...retained, ...add]);
918
+ }
919
+ return /* @__PURE__ */ jsxs4("div", { className: "sia-selection-panel", children: [
920
+ showSearch && /* @__PURE__ */ jsx4(
921
+ Input,
922
+ {
923
+ "aria-label": "\u641C\u7D22\u9009\u9879",
924
+ placeholder: "\u641C\u7D22",
925
+ autoFocus: autoFocusSearch,
926
+ value: search,
927
+ allowClear: true,
928
+ onChange: (event) => {
929
+ setLocalSearch(event.target.value);
930
+ onSearch?.(event.target.value);
931
+ }
932
+ }
933
+ ),
934
+ multiple && showActions && /* @__PURE__ */ jsxs4("div", { className: "sia-selection-panel__actions", children: [
935
+ /* @__PURE__ */ jsx4(Button, { variant: "link", size: "small", disabled: disabled || loading || !enabled.length, onClick: () => batch("all"), children: "\u5168\u9009" }),
936
+ /* @__PURE__ */ jsx4(Button, { variant: "link", size: "small", disabled: disabled || loading || !enabled.length, onClick: () => batch("invert"), children: "\u53CD\u9009" }),
937
+ /* @__PURE__ */ jsx4(Button, { variant: "link", size: "small", disabled: disabled || loading || !value.length, onClick: () => batch("clear"), children: "\u6E05\u7A7A" }),
938
+ /* @__PURE__ */ jsxs4("span", { children: [
939
+ "\u5DF2\u9009 ",
940
+ value.length,
941
+ " \u9879"
942
+ ] })
943
+ ] }),
944
+ /* @__PURE__ */ jsx4(
945
+ "div",
946
+ {
947
+ ref: listRef,
948
+ role: "listbox",
949
+ "aria-label": "\u9009\u9879\u5217\u8868",
950
+ "aria-multiselectable": multiple || void 0,
951
+ tabIndex: disabled ? -1 : 0,
952
+ className: "sia-selection-panel__list",
953
+ style: { maxHeight },
954
+ onScroll: (event) => setScrollTop(event.currentTarget.scrollTop),
955
+ "aria-activedescendant": active !== void 0 ? `${id}-${typeof active}-${active}` : void 0,
956
+ onKeyDown: (event) => {
957
+ if (event.target instanceof HTMLInputElement) return;
958
+ if (disabled || loading || !enabled.length) return;
959
+ const index = enabled.findIndex((option) => option.value === active);
960
+ if (["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
961
+ event.preventDefault();
962
+ const next = event.key === "Home" ? 0 : event.key === "End" ? enabled.length - 1 : Math.max(0, Math.min(enabled.length - 1, index + (event.key === "ArrowDown" ? 1 : -1)));
963
+ const option = enabled[next];
964
+ setActive(option.value);
965
+ const row = rows.find((row2) => row2.items?.includes(option));
966
+ if (row && listRef.current) {
967
+ listRef.current.scrollTop = Math.max(0, row.top - maxHeight / 2);
968
+ setScrollTop(listRef.current.scrollTop);
969
+ }
970
+ } else if ((event.key === "Enter" || event.key === " ") && index >= 0) {
971
+ event.preventDefault();
972
+ toggle(enabled[index]);
973
+ }
974
+ },
975
+ children: loading ? /* @__PURE__ */ jsx4("div", { role: "status", children: "\u52A0\u8F7D\u4E2D\u2026" }) : !rows.length ? /* @__PURE__ */ jsx4("div", { className: "sia-selection-panel__empty", children: "\u6682\u65E0\u5339\u914D\u9009\u9879" }) : /* @__PURE__ */ jsx4("div", { style: { height: totalHeight, position: "relative" }, children: renderedRows.map((row) => /* @__PURE__ */ jsx4(
976
+ "div",
977
+ {
978
+ className: row.title !== void 0 ? "sia-selection-panel__group" : "sia-selection-panel__row",
979
+ style: { position: "absolute", top: row.top, height: row.height, width: "100%", gridTemplateColumns: `repeat(${count}, minmax(0, 1fr))` },
980
+ children: row.title ?? row.items?.map((option, index) => option.optionType === "divider" ? /* @__PURE__ */ jsx4("div", { role: "separator", className: "sia-selection-panel__divider" }, `divider-${index}`) : /* @__PURE__ */ jsx4(Tooltip, { title: option.disabled ? option.disabledTip : void 0, placement: "right", children: /* @__PURE__ */ jsx4(
981
+ "div",
982
+ {
983
+ role: "option",
984
+ id: `${id}-${typeof option.value}-${option.value}`,
985
+ tabIndex: -1,
986
+ "aria-selected": selected.has(option.value),
987
+ "aria-disabled": disabled || option.disabled || void 0,
988
+ className: `sia-selection-panel__option${active === option.value ? " is-active" : ""}`,
989
+ onClick: (event) => {
990
+ if (!event.target.closest(".sia-checkbox")) toggle(option);
991
+ },
992
+ children: multiple ? /* @__PURE__ */ jsx4(Checkbox2, { checked: selected.has(option.value), disabled: disabled || loading || option.disabled, tabIndex: -1, onChange: () => toggle(option), children: /* @__PURE__ */ jsxs4("span", { className: "sia-selection-panel__text", children: [
993
+ /* @__PURE__ */ jsx4("span", { className: "sia-selection-panel__label", children: showValue || displayField === "label" ? option.label : String(option.value) }),
994
+ showValue && /* @__PURE__ */ jsx4("small", { children: String(option.value) })
995
+ ] }) }) : /* @__PURE__ */ jsxs4("span", { className: "sia-selection-panel__text", children: [
996
+ /* @__PURE__ */ jsx4("span", { className: "sia-selection-panel__label", children: showValue || displayField === "label" ? option.label : String(option.value) }),
997
+ showValue && /* @__PURE__ */ jsx4("small", { children: String(option.value) })
998
+ ] })
999
+ }
1000
+ ) }, `${typeof option.value}-${option.value}`))
1001
+ },
1002
+ row.key
1003
+ )) })
1004
+ }
1005
+ )
1006
+ ] });
1007
+ }
1008
+
1009
+ // src/components/Select.tsx
1010
+ import { forwardRef as forwardRef3, useDeferredValue, useEffect as useEffect6, useId as useId5, useMemo as useMemo4, useRef as useRef7, useState as useState5 } from "react";
1011
+
1012
+ // src/components/OverflowTags.tsx
1013
+ import { useLayoutEffect as useLayoutEffect2, useRef as useRef6, useState as useState4 } from "react";
1014
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1015
+ function OverflowTags({ items, maxCount, className = "", tagClassName, restClassName = "" }) {
1016
+ const rootRef = useRef6(null);
1017
+ const measureRef = useRef6(null);
1018
+ const limit = Math.min(items.length, Math.max(0, Math.floor(maxCount) || 0));
1019
+ const [visibleCount, setVisibleCount] = useState4(0);
1020
+ useLayoutEffect2(() => {
1021
+ const root = rootRef.current;
1022
+ const measure = measureRef.current;
1023
+ if (!root || !measure) return;
1024
+ const update = () => {
1025
+ const gap = Number.parseFloat(getComputedStyle(root).columnGap) || 0;
1026
+ const widths = Array.from(measure.children, (child) => child.getBoundingClientRect().width);
1027
+ let used = 0;
1028
+ let fitting = 0;
1029
+ for (let count2 = 1; count2 <= limit; count2 += 1) {
1030
+ used += widths[count2 - 1] + (count2 > 1 ? gap : 0);
1031
+ const rest = count2 < items.length ? gap + widths[limit + count2] : 0;
1032
+ if (used + rest <= root.clientWidth) fitting = count2;
1033
+ }
1034
+ setVisibleCount((previous) => previous === fitting ? previous : fitting);
1035
+ };
1036
+ update();
1037
+ const observer = new ResizeObserver(update);
1038
+ observer.observe(root);
1039
+ for (const child of measure.children) observer.observe(child);
1040
+ return () => observer.disconnect();
1041
+ }, [items, limit]);
1042
+ const count = Math.min(visibleCount, limit);
1043
+ return /* @__PURE__ */ jsxs5("span", { ref: rootRef, className: `sia-overflow-tags ${className}`.trim(), children: [
1044
+ items.slice(0, count).map((item) => /* @__PURE__ */ jsx5("span", { className: tagClassName, children: item.label }, item.key)),
1045
+ items.length > count ? /* @__PURE__ */ jsxs5("span", { className: `${tagClassName} sia-overflow-tags__rest ${restClassName}`.trim(), children: [
1046
+ "+",
1047
+ items.length - count
1048
+ ] }) : null,
1049
+ /* @__PURE__ */ jsxs5("span", { ref: measureRef, className: "sia-overflow-tags__measure", "aria-hidden": "true", children: [
1050
+ items.slice(0, limit).map((item) => /* @__PURE__ */ jsx5("span", { className: tagClassName, children: item.label }, item.key)),
1051
+ Array.from({ length: limit + 1 }, (_, index) => /* @__PURE__ */ jsxs5("span", { className: `${tagClassName} ${restClassName}`.trim(), children: [
1052
+ "+",
1053
+ items.length - index
1054
+ ] }, `rest-${index}`))
1055
+ ] })
1056
+ ] });
1057
+ }
1058
+
1059
+ // src/components/Select.tsx
1060
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1061
+ function defaultFilterOption(query, option) {
1062
+ const keyword = query.toLowerCase();
1063
+ return [option.label, option.value].some((value) => String(value).toLowerCase().includes(keyword));
1064
+ }
1065
+ var Select = forwardRef3(function Select2({
1066
+ options,
1067
+ mode = "single",
1068
+ popupMode = "dropdown",
1069
+ modalConfig,
1070
+ value,
1071
+ defaultValue,
1072
+ onChange,
1073
+ placeholder = "\u8BF7\u9009\u62E9",
1074
+ placeholderMode,
1075
+ size = "medium",
1076
+ status = "default",
1077
+ allowClear = false,
1078
+ loading = false,
1079
+ showSearch = false,
1080
+ showValue = false,
1081
+ allowInput = false,
1082
+ inputMaxLength,
1083
+ searchPlaceholder = "\u641C\u7D22\u9009\u9879",
1084
+ onSearch,
1085
+ filterOption = defaultFilterOption,
1086
+ showSelectAll = true,
1087
+ maxTagCount = 2,
1088
+ virtual = true,
1089
+ virtualThreshold = 100,
1090
+ listHeight = 256,
1091
+ optionHeight = showValue ? 54 : 34,
1092
+ disabled,
1093
+ className = "",
1094
+ id,
1095
+ notFoundContent = "\u6682\u65E0\u6570\u636E",
1096
+ onKeyDown,
1097
+ ...props
1098
+ }, forwardedRef) {
1099
+ const fallbackId = useId5();
1100
+ const selectId = id ?? fallbackId;
1101
+ const listboxId = `${selectId}-listbox`;
1102
+ const rootRef = useRef7(null);
1103
+ const popupRef = useRef7(null);
1104
+ const searchRef = useRef7(null);
1105
+ const listRef = useRef7(null);
1106
+ const [open, setOpen] = useState5(false);
1107
+ const [modalValues, setModalValues] = useState5([]);
1108
+ const [selectedOnly, setSelectedOnly] = useState5(false);
1109
+ const [query, setQuery] = useState5("");
1110
+ const [activeIndex, setActiveIndex] = useState5(-1);
1111
+ const [scrollTop, setScrollTop] = useState5(0);
1112
+ const deferredQuery = useDeferredValue(query.trim());
1113
+ const initialValue = defaultValue === void 0 ? mode === "multiple" ? [] : null : defaultValue;
1114
+ const [currentValue, setCurrentValue] = useControllableState({
1115
+ value,
1116
+ defaultValue: initialValue,
1117
+ onChange: (nextValue) => {
1118
+ if (Array.isArray(nextValue)) {
1119
+ const selectedOptions2 = nextValue.map((selectedValue) => options.find((option) => option.value === selectedValue) ?? (allowInput ? { label: String(selectedValue), value: selectedValue } : void 0)).filter((option) => option !== void 0);
1120
+ onChange?.(nextValue, selectedOptions2);
1121
+ } else {
1122
+ const selectedOption = nextValue === null ? null : options.find((option) => option.value === nextValue) ?? (allowInput ? { label: String(nextValue), value: nextValue } : null);
1123
+ onChange?.(nextValue, selectedOption);
1124
+ }
1125
+ }
1126
+ });
1127
+ const selectedValues = useMemo4(() => {
1128
+ if (mode === "multiple") return Array.isArray(currentValue) ? currentValue : currentValue === null ? [] : [currentValue];
1129
+ return Array.isArray(currentValue) ? currentValue.slice(0, 1) : currentValue === null ? [] : [currentValue];
1130
+ }, [currentValue, mode]);
1131
+ const selectedValueSet = useMemo4(() => new Set(selectedValues), [selectedValues]);
1132
+ const availableOptions = useMemo4(() => {
1133
+ if (!allowInput) return options;
1134
+ const optionValueSet = new Set(options.map((option) => option.value));
1135
+ const selectedCustomOptions = selectedValues.filter((selectedValue) => !optionValueSet.has(selectedValue)).map((selectedValue) => ({ label: String(selectedValue), value: selectedValue }));
1136
+ return selectedCustomOptions.length ? [...selectedCustomOptions, ...options] : options;
1137
+ }, [allowInput, options, selectedValues]);
1138
+ const selectedOptions = useMemo4(() => selectedValues.map((selectedValue) => availableOptions.find((option) => option.value === selectedValue)).filter((option) => option !== void 0), [availableOptions, selectedValues]);
1139
+ const filteredOptions = useMemo4(() => deferredQuery ? availableOptions.filter((option) => filterOption(deferredQuery, option)) : availableOptions, [availableOptions, deferredQuery, filterOption]);
1140
+ const inputValue = query.trim();
1141
+ const exactInputOption = inputValue ? availableOptions.find((option) => String(option.value).toLowerCase() === inputValue.toLowerCase() || typeof option.label === "string" && option.label.toLowerCase() === inputValue.toLowerCase()) : void 0;
1142
+ const canSubmitInput = allowInput && Boolean(inputValue) && !exactInputOption;
1143
+ const multiple = mode === "multiple";
1144
+ const hasValue = selectedOptions.length > 0;
1145
+ const virtualEnabled = virtual && filteredOptions.length >= virtualThreshold;
1146
+ const overscan = 3;
1147
+ const visibleCount = Math.ceil(listHeight / optionHeight) + overscan * 2;
1148
+ const virtualStart = virtualEnabled ? Math.max(0, Math.floor(scrollTop / optionHeight) - overscan) : 0;
1149
+ const virtualEnd = virtualEnabled ? Math.min(filteredOptions.length, virtualStart + visibleCount) : filteredOptions.length;
1150
+ const renderedOptions = virtualEnabled ? filteredOptions.slice(virtualStart, virtualEnd) : filteredOptions;
1151
+ const enabledFilteredOptions = filteredOptions.filter((option) => !option.disabled);
1152
+ const allFilteredSelected = enabledFilteredOptions.length > 0 && enabledFilteredOptions.every((option) => selectedValueSet.has(option.value));
1153
+ useEffect6(() => {
1154
+ if (!open || popupMode === "modal") return void 0;
1155
+ function closeFromOutside(event) {
1156
+ const target = event.target;
1157
+ if (!rootRef.current?.contains(target) && !popupRef.current?.contains(target)) closeDropdown();
1158
+ }
1159
+ document.addEventListener("mousedown", closeFromOutside);
1160
+ return () => document.removeEventListener("mousedown", closeFromOutside);
1161
+ }, [open, popupMode]);
1162
+ useEffect6(() => {
1163
+ if (open && (showSearch || allowInput)) searchRef.current?.focus();
1164
+ }, [allowInput, open, showSearch]);
1165
+ useEffect6(() => {
1166
+ if (disabled || loading) closeDropdown();
1167
+ }, [disabled, loading]);
1168
+ function firstEnabledIndex() {
1169
+ return filteredOptions.findIndex((option) => !option.disabled);
1170
+ }
1171
+ function closeDropdown() {
1172
+ setOpen(false);
1173
+ setQuery("");
1174
+ setScrollTop(0);
1175
+ }
1176
+ function openDropdown() {
1177
+ if (disabled || loading) return;
1178
+ const selectedIndex = filteredOptions.findIndex((option) => selectedValueSet.has(option.value) && !option.disabled);
1179
+ setActiveIndex(selectedIndex >= 0 ? selectedIndex : firstEnabledIndex());
1180
+ if (popupMode === "modal") {
1181
+ setModalValues([...selectedValues]);
1182
+ setSelectedOnly(false);
1183
+ }
1184
+ setOpen(true);
1185
+ }
1186
+ function keepActiveVisible(index) {
1187
+ if (!virtualEnabled || !listRef.current) return;
1188
+ const optionTop = index * optionHeight;
1189
+ const optionBottom = optionTop + optionHeight;
1190
+ const viewportTop = listRef.current.scrollTop;
1191
+ const viewportBottom = viewportTop + listHeight;
1192
+ const nextScrollTop = optionTop < viewportTop ? optionTop : optionBottom > viewportBottom ? optionBottom - listHeight : viewportTop;
1193
+ if (nextScrollTop !== viewportTop) {
1194
+ listRef.current.scrollTop = nextScrollTop;
1195
+ setScrollTop(nextScrollTop);
1196
+ }
1197
+ }
1198
+ function moveActive(direction) {
1199
+ if (filteredOptions.length === 0) return;
1200
+ let nextIndex = activeIndex;
1201
+ for (let index = 0; index < filteredOptions.length; index += 1) {
1202
+ nextIndex = (nextIndex + direction + filteredOptions.length) % filteredOptions.length;
1203
+ if (!filteredOptions[nextIndex]?.disabled) {
1204
+ setActiveIndex(nextIndex);
1205
+ keepActiveVisible(nextIndex);
1206
+ return;
1207
+ }
1208
+ }
1209
+ }
1210
+ function selectOption(option) {
1211
+ if (option.disabled) return;
1212
+ if (multiple) {
1213
+ const nextValues = selectedValueSet.has(option.value) ? selectedValues.filter((item) => item !== option.value) : [...selectedValues, option.value];
1214
+ setCurrentValue(nextValues);
1215
+ return;
1216
+ }
1217
+ setCurrentValue(option.value);
1218
+ closeDropdown();
1219
+ }
1220
+ function selectAllFiltered() {
1221
+ if (allFilteredSelected) return;
1222
+ const nextValues = [...selectedValues];
1223
+ const nextValueSet = new Set(nextValues);
1224
+ enabledFilteredOptions.forEach((option) => {
1225
+ if (!nextValueSet.has(option.value)) nextValues.push(option.value);
1226
+ });
1227
+ setCurrentValue(nextValues);
1228
+ }
1229
+ function submitInputValue() {
1230
+ if (!allowInput || !inputValue) return;
1231
+ selectOption(exactInputOption ?? { label: inputValue, value: inputValue });
1232
+ if (multiple) {
1233
+ setQuery("");
1234
+ setActiveIndex(-1);
1235
+ setScrollTop(0);
1236
+ }
1237
+ }
1238
+ function invertFiltered() {
1239
+ const enabled = new Set(enabledFilteredOptions.map((option) => option.value));
1240
+ setCurrentValue([...selectedValues.filter((value2) => !enabled.has(value2)), ...enabledFilteredOptions.filter((option) => !selectedValueSet.has(option.value)).map((option) => option.value)]);
1241
+ }
1242
+ function clearSelection() {
1243
+ setCurrentValue(multiple ? [] : null);
1244
+ }
1245
+ function handleInteractionKeyDown(event, fromSearch = false) {
1246
+ if (popupMode === "modal" && open) return;
1247
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
1248
+ event.preventDefault();
1249
+ if (!open) openDropdown();
1250
+ else moveActive(event.key === "ArrowDown" ? 1 : -1);
1251
+ } else if (event.key === "Enter" || event.key === " " && !fromSearch) {
1252
+ event.preventDefault();
1253
+ if (!open) openDropdown();
1254
+ else if (activeIndex >= 0 && filteredOptions[activeIndex]) selectOption(filteredOptions[activeIndex]);
1255
+ else if (fromSearch && allowInput) submitInputValue();
1256
+ } else if (event.key === "Escape") {
1257
+ closeDropdown();
1258
+ } else if (event.key === "Home" && open && !fromSearch) {
1259
+ event.preventDefault();
1260
+ const firstIndex = firstEnabledIndex();
1261
+ setActiveIndex(firstIndex);
1262
+ keepActiveVisible(firstIndex);
1263
+ } else if (event.key === "End" && open && !fromSearch) {
1264
+ event.preventDefault();
1265
+ const reversedIndex = [...filteredOptions].reverse().findIndex((option) => !option.disabled);
1266
+ const lastIndex = reversedIndex < 0 ? -1 : filteredOptions.length - 1 - reversedIndex;
1267
+ setActiveIndex(lastIndex);
1268
+ keepActiveVisible(lastIndex);
1269
+ }
1270
+ }
1271
+ function handleTriggerKeyDown(event) {
1272
+ handleInteractionKeyDown(event);
1273
+ onKeyDown?.(event);
1274
+ }
1275
+ function handleSearchChange(event) {
1276
+ const nextQuery = event.target.value;
1277
+ setQuery(nextQuery);
1278
+ setActiveIndex(-1);
1279
+ setScrollTop(0);
1280
+ if (listRef.current) listRef.current.scrollTop = 0;
1281
+ onSearch?.(nextQuery);
1282
+ }
1283
+ function clearValue(event) {
1284
+ event.stopPropagation();
1285
+ clearSelection();
1286
+ }
1287
+ function handleListScroll(event) {
1288
+ if (virtualEnabled) setScrollTop(event.currentTarget.scrollTop);
1289
+ }
1290
+ function renderOption(option, renderedIndex) {
1291
+ const optionIndex = virtualEnabled ? virtualStart + renderedIndex : renderedIndex;
1292
+ const selected = selectedValueSet.has(option.value);
1293
+ return /* @__PURE__ */ jsxs6(
1294
+ "div",
1295
+ {
1296
+ id: `${listboxId}-${optionIndex}`,
1297
+ className: `sia-select__option${selected ? " is-selected" : ""}${optionIndex === activeIndex ? " is-active" : ""}${option.disabled ? " is-disabled" : ""}`,
1298
+ role: "option",
1299
+ "aria-selected": selected,
1300
+ "aria-disabled": option.disabled || void 0,
1301
+ style: virtualEnabled ? { position: "absolute", top: optionIndex * optionHeight, right: 0, left: 0, height: optionHeight } : void 0,
1302
+ onMouseEnter: () => !option.disabled && setActiveIndex(optionIndex),
1303
+ onMouseDown: (event) => event.preventDefault(),
1304
+ onClick: () => selectOption(option),
1305
+ children: [
1306
+ /* @__PURE__ */ jsxs6("span", { className: "sia-select__option-content", children: [
1307
+ /* @__PURE__ */ jsx6("span", { className: "sia-select__option-label", children: option.label }),
1308
+ showValue ? /* @__PURE__ */ jsx6("small", { className: "sia-select__option-value", children: String(option.value) }) : null
1309
+ ] }),
1310
+ selected ? /* @__PURE__ */ jsx6(Icon, { name: "check", size: 15 }) : null
1311
+ ]
1312
+ },
1313
+ `${typeof option.value}-${option.value}`
1314
+ );
1315
+ }
1316
+ const modalAvailable = [...availableOptions, ...modalValues.filter((value2) => !availableOptions.some((option) => option.value === value2)).map((value2) => ({ value: value2, label: String(value2) }))];
1317
+ const modalFiltered = deferredQuery ? modalAvailable.filter((option) => filterOption(deferredQuery, option)) : modalAvailable;
1318
+ const modalEnabled = modalFiltered.filter((option) => !option.disabled && (!selectedOnly || modalValues.includes(option.value)));
1319
+ function modalBatch(operation) {
1320
+ const editable = new Set(modalEnabled.map((option) => option.value));
1321
+ setModalValues([...modalValues.filter((value2) => !editable.has(value2)), ...modalEnabled.filter((option) => operation === "all" || operation === "invert" && !modalValues.includes(option.value)).map((option) => option.value)]);
1322
+ }
1323
+ const floating = useFloatingPlaceholder({ mode: placeholderMode, placeholder, filled: hasValue, active: open });
1324
+ return /* @__PURE__ */ jsxs6("div", { ref: rootRef, className: `sia-select sia-select--${size} sia-select--${status}${open ? " is-open" : ""}${disabled ? " is-disabled" : ""}${multiple ? " sia-select--multiple" : ""} ${className}`.trim(), children: [
1325
+ /* @__PURE__ */ jsxs6(
1326
+ "button",
1327
+ {
1328
+ ...props,
1329
+ ref: forwardedRef,
1330
+ className: `sia-select__trigger${floating.className}`,
1331
+ id: selectId,
1332
+ type: "button",
1333
+ role: "combobox",
1334
+ "aria-controls": popupMode === "modal" ? void 0 : listboxId,
1335
+ "aria-expanded": open,
1336
+ "aria-haspopup": popupMode === "modal" ? "dialog" : "listbox",
1337
+ "aria-activedescendant": popupMode === "dropdown" && open && activeIndex >= 0 ? `${listboxId}-${activeIndex}` : void 0,
1338
+ "aria-invalid": status === "error" || void 0,
1339
+ disabled,
1340
+ onClick: () => open ? closeDropdown() : openDropdown(),
1341
+ onKeyDown: handleTriggerKeyDown,
1342
+ children: [
1343
+ multiple && hasValue ? /* @__PURE__ */ jsx6(OverflowTags, { className: "sia-select__tags", tagClassName: "sia-select__tag", restClassName: "sia-select__tag--rest", maxCount: maxTagCount, items: selectedOptions.map((option) => ({ key: `${typeof option.value}-${option.value}`, label: option.label })) }) : /* @__PURE__ */ jsx6("span", { className: `sia-select__value${hasValue ? "" : " is-placeholder"}`, children: selectedOptions[0]?.label ?? placeholder }),
1344
+ loading ? /* @__PURE__ */ jsx6("span", { className: "sia-select__spinner", "aria-hidden": "true" }) : null,
1345
+ floating.label,
1346
+ /* @__PURE__ */ jsx6(Icon, { className: "sia-select__arrow", name: "chevron-down", size: 15 })
1347
+ ]
1348
+ }
1349
+ ),
1350
+ allowClear && hasValue && !disabled && !loading ? /* @__PURE__ */ jsx6("button", { className: "sia-select__clear", type: "button", "aria-label": "\u6E05\u7A7A\u9009\u62E9", onClick: clearValue, children: /* @__PURE__ */ jsx6(Icon, { name: "circle-close", variant: "filled", size: 15 }) }) : null,
1351
+ /* @__PURE__ */ jsxs6(PopupPortal, { ref: popupRef, anchorRef: rootRef, open: open && popupMode === "dropdown", className: "sia-select__dropdown", children: [
1352
+ showSearch || allowInput ? /* @__PURE__ */ jsxs6("label", { className: "sia-select__search", children: [
1353
+ /* @__PURE__ */ jsx6(Icon, { name: "search", size: 15 }),
1354
+ /* @__PURE__ */ jsx6("input", { ref: searchRef, value: query, maxLength: inputMaxLength, onChange: handleSearchChange, onKeyDown: (event) => handleInteractionKeyDown(event, true), placeholder: searchPlaceholder, "aria-label": searchPlaceholder })
1355
+ ] }) : null,
1356
+ canSubmitInput ? /* @__PURE__ */ jsxs6("button", { type: "button", className: "sia-select__input-option", onMouseDown: (event) => event.preventDefault(), onClick: submitInputValue, children: [
1357
+ /* @__PURE__ */ jsx6(Icon, { name: "plus", size: 14 }),
1358
+ /* @__PURE__ */ jsxs6("span", { children: [
1359
+ "\u4F7F\u7528\u201C",
1360
+ inputValue,
1361
+ "\u201D"
1362
+ ] })
1363
+ ] }) : null,
1364
+ /* @__PURE__ */ jsx6(
1365
+ "div",
1366
+ {
1367
+ ref: listRef,
1368
+ id: listboxId,
1369
+ className: "sia-select__list",
1370
+ role: "listbox",
1371
+ "aria-labelledby": selectId,
1372
+ "aria-multiselectable": multiple || void 0,
1373
+ style: { maxHeight: listHeight },
1374
+ onScroll: handleListScroll,
1375
+ children: filteredOptions.length > 0 ? virtualEnabled ? /* @__PURE__ */ jsx6("div", { className: "sia-select__virtual-space", style: { height: filteredOptions.length * optionHeight }, children: renderedOptions.map(renderOption) }) : renderedOptions.map(renderOption) : canSubmitInput ? null : /* @__PURE__ */ jsx6("div", { className: "sia-select__empty", children: notFoundContent })
1376
+ }
1377
+ ),
1378
+ /* @__PURE__ */ jsxs6("div", { className: "sia-select__actions", children: [
1379
+ /* @__PURE__ */ jsxs6("span", { children: [
1380
+ "\u5171 ",
1381
+ filteredOptions.length,
1382
+ " \u6761"
1383
+ ] }),
1384
+ multiple && showSelectAll ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
1385
+ /* @__PURE__ */ jsx6(Button, { variant: "link", size: "small", disabled: allFilteredSelected || !enabledFilteredOptions.length, onClick: selectAllFiltered, children: "\u5168\u9009" }),
1386
+ /* @__PURE__ */ jsx6(Button, { variant: "link", size: "small", disabled: !enabledFilteredOptions.length, onClick: invertFiltered, children: "\u53CD\u9009" })
1387
+ ] }) : null,
1388
+ multiple || allowClear ? /* @__PURE__ */ jsx6(Button, { variant: "link", size: "small", danger: true, disabled: !hasValue, onClick: clearSelection, children: "\u6E05\u7A7A" }) : null
1389
+ ] })
1390
+ ] }),
1391
+ popupMode === "modal" ? /* @__PURE__ */ jsx6(
1392
+ Modal2,
1393
+ {
1394
+ className: "sia-select-modal",
1395
+ open,
1396
+ title: modalConfig?.title ?? placeholder,
1397
+ width: modalConfig?.width ?? 900,
1398
+ onOpenChange: (next) => {
1399
+ if (!next) closeDropdown();
1400
+ },
1401
+ onCancel: closeDropdown,
1402
+ footer: /* @__PURE__ */ jsxs6("div", { className: "sia-select-modal__footer", children: [
1403
+ /* @__PURE__ */ jsx6("div", { children: modalConfig?.footerExtra }),
1404
+ /* @__PURE__ */ jsxs6("div", { children: [
1405
+ /* @__PURE__ */ jsx6(Button, { onClick: closeDropdown, children: "\u53D6\u6D88" }),
1406
+ /* @__PURE__ */ jsx6(Button, { variant: "primary", disabled: disabled || loading, onClick: () => {
1407
+ setCurrentValue(multiple ? modalValues : modalValues[0] ?? null);
1408
+ closeDropdown();
1409
+ }, children: "\u786E\u5B9A" })
1410
+ ] })
1411
+ ] }),
1412
+ children: open ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
1413
+ /* @__PURE__ */ jsxs6("div", { className: "sia-select-modal__heading", children: [
1414
+ /* @__PURE__ */ jsxs6("div", { role: "tablist", "aria-label": "\u9009\u9879\u8303\u56F4", children: [
1415
+ /* @__PURE__ */ jsx6(Button, { role: "tab", "aria-selected": !selectedOnly, variant: "link", onClick: () => setSelectedOnly(false), children: "\u5168\u90E8" }),
1416
+ /* @__PURE__ */ jsxs6(Button, { role: "tab", "aria-selected": selectedOnly, variant: "link", onClick: () => setSelectedOnly(true), children: [
1417
+ "\u5DF2\u9009\u9879(",
1418
+ modalValues.length,
1419
+ ")"
1420
+ ] })
1421
+ ] }),
1422
+ /* @__PURE__ */ jsx6(
1423
+ Input,
1424
+ {
1425
+ "aria-label": searchPlaceholder,
1426
+ placeholder: searchPlaceholder,
1427
+ prefix: /* @__PURE__ */ jsx6(Icon, { name: "search", size: 14 }),
1428
+ value: query,
1429
+ allowClear: true,
1430
+ onChange: (event) => {
1431
+ setQuery(event.target.value);
1432
+ onSearch?.(event.target.value);
1433
+ }
1434
+ }
1435
+ )
1436
+ ] }),
1437
+ multiple && showSelectAll ? /* @__PURE__ */ jsxs6("div", { className: "sia-select-modal__actions", children: [
1438
+ /* @__PURE__ */ jsx6(
1439
+ Checkbox2,
1440
+ {
1441
+ checked: modalEnabled.length > 0 && modalEnabled.every((option) => modalValues.includes(option.value)),
1442
+ indeterminate: modalEnabled.some((option) => modalValues.includes(option.value)) && !modalEnabled.every((option) => modalValues.includes(option.value)),
1443
+ disabled: !modalEnabled.length,
1444
+ onChange: (checked) => modalBatch(checked ? "all" : "remove"),
1445
+ children: "\u5168\u9009"
1446
+ }
1447
+ ),
1448
+ /* @__PURE__ */ jsx6(Button, { variant: "link", size: "small", disabled: !modalEnabled.length, onClick: () => modalBatch("invert"), children: "\u53CD\u9009" }),
1449
+ /* @__PURE__ */ jsx6(Button, { variant: "link", size: "small", danger: true, disabled: !modalValues.length, onClick: () => setModalValues([]), children: "\u6E05\u7A7A" })
1450
+ ] }) : null,
1451
+ allowInput && canSubmitInput ? /* @__PURE__ */ jsxs6(Button, { variant: "link", onClick: () => {
1452
+ setModalValues(multiple ? [.../* @__PURE__ */ new Set([...modalValues, inputValue])] : [inputValue]);
1453
+ setQuery("");
1454
+ }, children: [
1455
+ "\u4F7F\u7528\u201C",
1456
+ inputValue,
1457
+ "\u201D"
1458
+ ] }) : null,
1459
+ /* @__PURE__ */ jsx6(
1460
+ SelectionPanel,
1461
+ {
1462
+ options: modalFiltered,
1463
+ value: modalValues,
1464
+ onChange: setModalValues,
1465
+ multiple,
1466
+ selectedOnly,
1467
+ showValue,
1468
+ showSearch: false,
1469
+ showActions: false,
1470
+ includeUnknownValues: false,
1471
+ columns: modalConfig?.columns ?? 3,
1472
+ maxHeight: modalConfig?.maxHeight ?? 360
1473
+ }
1474
+ )
1475
+ ] }) : null
1476
+ }
1477
+ ) : null
1478
+ ] });
1479
+ });
1480
+
1481
+ export {
1482
+ Checkbox2 as Checkbox,
1483
+ Tooltip,
1484
+ Popover,
1485
+ Timeline,
1486
+ Tree,
1487
+ useOverlayLifecycle,
1488
+ OverlayPortal,
1489
+ Modal2 as Modal,
1490
+ SelectionPanel,
1491
+ OverflowTags,
1492
+ Select
1493
+ };