@sia.soul/sia-react-ui 0.1.37 → 0.1.39

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.
@@ -15,1277 +15,1277 @@ import {
15
15
  withTitleTooltip
16
16
  } from "./chunk-H7IAG72O.js";
17
17
 
18
- // src/components/Checkbox.tsx
19
- import { createContext, forwardRef, useContext, useEffect, useId, useMemo, useRef } from "react";
20
- import { jsx, jsxs } from "react/jsx-runtime";
21
- var CheckboxGroupContext = createContext(null);
22
- var CheckboxRoot = forwardRef(function Checkbox({ value, size: sizeProp, indeterminate = false, children, disabled, checked, defaultChecked, className = "", onChange, ...props }, forwardedRef) {
23
- const size = useControlSize(sizeProp);
24
- const group = useContext(CheckboxGroupContext);
25
- const localRef = useRef(null);
26
- const fallbackId = useId();
27
- const grouped = group !== null && value !== void 0;
28
- const currentChecked = grouped ? group.values.includes(value) : checked;
18
+ // src/components/FloatingScrollbarProvider.tsx
19
+ import { useEffect as useEffect2 } from "react";
20
+
21
+ // src/components/useScrollbarProximity.ts
22
+ import { useEffect } from "react";
23
+ var users = 0;
24
+ var cleanup;
25
+ function useScrollbarProximity() {
29
26
  useEffect(() => {
30
- if (localRef.current) localRef.current.indeterminate = indeterminate;
31
- }, [indeterminate]);
32
- function setRef(node) {
33
- localRef.current = node;
34
- if (typeof forwardedRef === "function") forwardedRef(node);
35
- else if (forwardedRef) forwardedRef.current = node;
36
- }
37
- return /* @__PURE__ */ jsxs("label", { className: `sia-checkbox sia-checkbox--${size}${disabled || group?.disabled ? " is-disabled" : ""} ${className}`.trim(), children: [
38
- /* @__PURE__ */ jsx(
39
- "input",
40
- {
41
- ...withTitleTooltip(props),
42
- ref: setRef,
43
- id: props.id ?? fallbackId,
44
- className: "sia-checkbox__input",
45
- type: "checkbox",
46
- name: group?.name ?? props.name,
47
- disabled: disabled || group?.disabled,
48
- checked: currentChecked,
49
- defaultChecked: grouped ? void 0 : defaultChecked,
50
- "aria-checked": indeterminate ? "mixed" : currentChecked,
51
- onChange: (event) => {
52
- if (grouped) group.toggle(value, event.currentTarget.checked);
53
- onChange?.(event.currentTarget.checked, event);
54
- }
55
- }
56
- ),
57
- /* @__PURE__ */ jsx("span", { className: "sia-checkbox__control", "aria-hidden": "true", children: /* @__PURE__ */ jsx("span", {}) }),
58
- children !== void 0 ? /* @__PURE__ */ jsx("span", { className: "sia-checkbox__label", children }) : null
59
- ] });
60
- });
61
- function CheckboxGroup({
62
- size: sizeProp,
63
- columns,
64
- direction = "horizontal",
65
- gap,
66
- value,
67
- defaultValue = [],
68
- options,
69
- disabled,
70
- name,
71
- className = "",
72
- children,
73
- onChange
74
- }) {
75
- const size = useControlSize(sizeProp);
76
- const gridColumns = columns !== void 0 && Number.isInteger(columns) && columns > 0 ? columns : void 0;
77
- const layoutStyle = {
78
- gridTemplateColumns: gridColumns ? `repeat(${gridColumns}, minmax(0, 1fr))` : void 0,
79
- gap: gap === void 0 ? void 0 : typeof gap === "number" ? Math.max(0, gap) : `${Math.max(0, gap[0])}px ${Math.max(0, gap[1])}px`
80
- };
81
- const [values, setValues] = useControllableState({ value, defaultValue, onChange });
82
- const context = useMemo(() => ({
83
- values,
84
- disabled,
85
- name,
86
- toggle(optionValue, checked) {
87
- const next = checked ? values.includes(optionValue) ? values : [...values, optionValue] : values.filter((item) => item !== optionValue);
88
- setValues(next);
27
+ if (users++ === 0) {
28
+ let frame = 0;
29
+ let x = -Infinity;
30
+ let y = -Infinity;
31
+ const nearby = /* @__PURE__ */ new Set();
32
+ const clear = () => {
33
+ nearby.forEach((track) => track.removeAttribute("data-scrollbar-near"));
34
+ nearby.clear();
35
+ };
36
+ const update = () => {
37
+ frame = 0;
38
+ const next = /* @__PURE__ */ new Set();
39
+ document.querySelectorAll(".sia-scrollbar-track").forEach((track) => {
40
+ const rect = track.getBoundingClientRect();
41
+ const proximity = Number.parseFloat(getComputedStyle(track).getPropertyValue("--sia-scrollbar-proximity")) || 16;
42
+ if (rect.width && rect.height && x >= rect.left - proximity && x <= rect.right + proximity && y >= rect.top - proximity && y <= rect.bottom + proximity) {
43
+ next.add(track);
44
+ if (!nearby.has(track)) track.setAttribute("data-scrollbar-near", "true");
45
+ }
46
+ });
47
+ nearby.forEach((track) => {
48
+ if (!next.has(track)) track.removeAttribute("data-scrollbar-near");
49
+ });
50
+ nearby.clear();
51
+ next.forEach((track) => nearby.add(track));
52
+ };
53
+ const move = (event) => {
54
+ if (event.pointerType === "touch") return;
55
+ x = event.clientX;
56
+ y = event.clientY;
57
+ if (!frame) frame = requestAnimationFrame(update);
58
+ };
59
+ const leave = () => {
60
+ x = y = -Infinity;
61
+ clear();
62
+ };
63
+ document.addEventListener("pointermove", move, { passive: true });
64
+ document.documentElement.addEventListener("pointerleave", leave);
65
+ window.addEventListener("blur", leave);
66
+ cleanup = () => {
67
+ if (frame) cancelAnimationFrame(frame);
68
+ document.removeEventListener("pointermove", move);
69
+ document.documentElement.removeEventListener("pointerleave", leave);
70
+ window.removeEventListener("blur", leave);
71
+ clear();
72
+ };
89
73
  }
90
- }), [disabled, name, setValues, values]);
91
- return /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: size, children: /* @__PURE__ */ jsx(CheckboxGroupContext.Provider, { value: context, children: /* @__PURE__ */ jsxs("div", { className: `sia-checkbox-group${gridColumns ? " sia-checkbox-group--grid" : direction === "vertical" ? " sia-checkbox-group--vertical" : ""} ${className}`.trim(), style: layoutStyle, role: "group", children: [
92
- options?.map((option) => {
93
- const normalized = typeof option === "object" ? option : { label: String(option), value: option };
94
- return /* @__PURE__ */ jsx(CheckboxRoot, { value: normalized.value, disabled: normalized.disabled, children: normalized.label }, normalized.value);
95
- }),
96
- children
97
- ] }) }) });
74
+ return () => {
75
+ if (--users === 0) {
76
+ cleanup?.();
77
+ cleanup = void 0;
78
+ }
79
+ };
80
+ }, []);
98
81
  }
99
- var Checkbox2 = Object.assign(CheckboxRoot, {
100
- /** 组合多个子项并统一管理布局或选择状态。 */
101
- Group: CheckboxGroup
102
- });
103
82
 
104
- // src/components/treeCheckState.ts
105
- function affectedKeys(node) {
106
- if (node.disabled || node.disableCheckbox) return [];
107
- return [...node.checkable === false ? [] : [node.key], ...node.children?.flatMap(affectedKeys) ?? []];
83
+ // src/components/FloatingScrollbarProvider.tsx
84
+ var scrollableOverflow = /* @__PURE__ */ new Set(["auto", "scroll", "overlay"]);
85
+ var clippingOverflow = /* @__PURE__ */ new Set(["auto", "scroll", "overlay", "hidden", "clip"]);
86
+ function isRootScroller(target) {
87
+ return target === document.scrollingElement || target === document.documentElement;
108
88
  }
109
- function getTreeCheckState(nodes, keys, strictly = false) {
110
- const checked = new Set(keys);
111
- const halfChecked = /* @__PURE__ */ new Set();
112
- if (strictly) return { checked, halfChecked };
113
- const explicit = new Set(keys);
114
- function expand(items) {
115
- for (const node of items) {
116
- if (explicit.has(node.key)) affectedKeys(node).forEach((key) => checked.add(key));
117
- if (node.children) expand(node.children);
118
- }
119
- }
120
- expand(nodes);
121
- function fold(node) {
122
- const children = node.children?.map(fold).filter((value) => value !== void 0) ?? [];
123
- if (node.disabled || node.disableCheckbox) return void 0;
124
- if (!children.length) return { checked: checked.has(node.key), partial: false };
125
- const all = children.every((child) => child.checked);
126
- const some = children.some((child) => child.checked || child.partial);
127
- if (node.checkable !== false) {
128
- if (all) checked.add(node.key);
129
- else checked.delete(node.key);
130
- if (!all && some) halfChecked.add(node.key);
131
- }
132
- return { checked: all, partial: !all && some };
89
+ function canScroll(target, axis) {
90
+ if (target.matches(".sia-table__viewport, [data-sia-native-scrollbar]")) return false;
91
+ if (isRootScroller(target)) {
92
+ return axis === "horizontal" ? target.scrollWidth > target.clientWidth + 1 : target.scrollHeight > target.clientHeight + 1;
133
93
  }
134
- nodes.forEach(fold);
135
- return { checked, halfChecked };
94
+ const style = getComputedStyle(target);
95
+ const overflow = axis === "horizontal" ? style.overflowX : style.overflowY;
96
+ const hasOverflow = axis === "horizontal" ? target.scrollWidth > target.clientWidth + 1 : target.scrollHeight > target.clientHeight + 1;
97
+ return hasOverflow && scrollableOverflow.has(overflow);
136
98
  }
137
- function toggleTreeCheck(nodes, keys, node, strictly = false) {
138
- const state = getTreeCheckState(nodes, keys, strictly);
139
- const next = new Set(state.checked);
140
- const checked = !next.has(node.key);
141
- if (node.disabled || node.disableCheckbox || node.checkable === false) return { ...state, checkedValue: !checked };
142
- (strictly ? [node.key] : affectedKeys(node)).forEach((key) => checked ? next.add(key) : next.delete(key));
143
- if (!strictly && !checked) {
144
- let removeParents2 = function(items) {
145
- let found = false;
146
- for (const item of items) {
147
- const childFound = item.children ? removeParents2(item.children) : false;
148
- if (childFound) next.delete(item.key);
149
- if (item.key === node.key || childFound) found = true;
99
+ function visibleRect(target) {
100
+ if (isRootScroller(target)) {
101
+ return { top: 0, right: window.innerWidth, bottom: window.innerHeight, left: 0, width: window.innerWidth, height: window.innerHeight };
102
+ }
103
+ const ownRect = target.getBoundingClientRect();
104
+ let top = Math.max(0, ownRect.top);
105
+ let right = Math.min(window.innerWidth, ownRect.right);
106
+ let bottom = Math.min(window.innerHeight, ownRect.bottom);
107
+ let left = Math.max(0, ownRect.left);
108
+ let parent = target.parentElement;
109
+ while (parent && parent !== document.body && parent !== document.documentElement) {
110
+ const style = getComputedStyle(parent);
111
+ const clipX = clippingOverflow.has(style.overflowX);
112
+ const clipY = clippingOverflow.has(style.overflowY);
113
+ if (clipX || clipY) {
114
+ const rect = parent.getBoundingClientRect();
115
+ if (clipX) {
116
+ left = Math.max(left, rect.left);
117
+ right = Math.min(right, rect.right);
150
118
  }
151
- return found;
152
- };
153
- var removeParents = removeParents2;
154
- removeParents2(nodes);
119
+ if (clipY) {
120
+ top = Math.max(top, rect.top);
121
+ bottom = Math.min(bottom, rect.bottom);
122
+ }
123
+ }
124
+ parent = parent.parentElement;
155
125
  }
156
- return { ...getTreeCheckState(nodes, [...next], strictly), checkedValue: checked };
126
+ return {
127
+ top,
128
+ right,
129
+ bottom,
130
+ left,
131
+ width: Math.max(0, right - left),
132
+ height: Math.max(0, bottom - top)
133
+ };
157
134
  }
158
-
159
- // src/components/FloatingDisplay.tsx
160
- import { cloneElement, useEffect as useEffect2, useId as useId2, useLayoutEffect, useMemo as useMemo2, useRef as useRef2, useState } from "react";
161
- import { createPortal } from "react-dom";
162
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
163
- var FLOATING_GAP = 9;
164
- var FLOATING_VIEWPORT_PADDING = 8;
165
- var FLOATING_ARROW_EDGE_OFFSET = 16;
166
- var activeTooltipClose;
167
- function clampFloating(value, min, max) {
168
- return Math.min(Math.max(value, min), Math.max(min, max));
135
+ function createTrack(axis) {
136
+ const track = document.createElement("div");
137
+ const thumb = document.createElement("div");
138
+ track.className = `sia-scrollbar-track sia-floating-scrollbar sia-floating-scrollbar--${axis}`;
139
+ thumb.className = "sia-floating-scrollbar__thumb";
140
+ track.dataset.axis = axis;
141
+ track.setAttribute("aria-hidden", "true");
142
+ track.append(thumb);
143
+ return { track, thumb };
169
144
  }
170
- function resolveFloatingPosition(trigger, overlay, placement) {
171
- const viewportWidth = document.documentElement.clientWidth;
172
- const viewportHeight = document.documentElement.clientHeight;
173
- let resolvedPlacement = placement;
174
- if (placement.startsWith("top") && trigger.top - overlay.height - FLOATING_GAP < FLOATING_VIEWPORT_PADDING && trigger.bottom + overlay.height + FLOATING_GAP <= viewportHeight - FLOATING_VIEWPORT_PADDING) {
175
- resolvedPlacement = placement.replace("top", "bottom");
176
- } else if (placement.startsWith("bottom") && trigger.bottom + overlay.height + FLOATING_GAP > viewportHeight - FLOATING_VIEWPORT_PADDING && trigger.top - overlay.height - FLOATING_GAP >= FLOATING_VIEWPORT_PADDING) {
177
- resolvedPlacement = placement.replace("bottom", "top");
178
- } else if (placement === "left" && trigger.left - overlay.width - FLOATING_GAP < FLOATING_VIEWPORT_PADDING && trigger.right + overlay.width + FLOATING_GAP <= viewportWidth - FLOATING_VIEWPORT_PADDING) {
179
- resolvedPlacement = "right";
180
- } else if (placement === "right" && trigger.right + overlay.width + FLOATING_GAP > viewportWidth - FLOATING_VIEWPORT_PADDING && trigger.left - overlay.width - FLOATING_GAP >= FLOATING_VIEWPORT_PADDING) {
181
- resolvedPlacement = "left";
182
- }
183
- let top = trigger.top;
184
- let left = trigger.left;
185
- if (resolvedPlacement.startsWith("top") || resolvedPlacement.startsWith("bottom")) {
186
- top = resolvedPlacement.startsWith("top") ? trigger.top - overlay.height - FLOATING_GAP : trigger.bottom + FLOATING_GAP;
187
- left = resolvedPlacement.endsWith("-start") ? trigger.left : resolvedPlacement.endsWith("-end") ? trigger.right - overlay.width : trigger.left + (trigger.width - overlay.width) / 2;
188
- } else {
189
- top = trigger.top + (trigger.height - overlay.height) / 2;
190
- left = resolvedPlacement === "left" ? trigger.left - overlay.width - FLOATING_GAP : trigger.right + FLOATING_GAP;
191
- }
192
- top = clampFloating(top, FLOATING_VIEWPORT_PADDING, viewportHeight - overlay.height - FLOATING_VIEWPORT_PADDING);
193
- left = clampFloating(left, FLOATING_VIEWPORT_PADDING, viewportWidth - overlay.width - FLOATING_VIEWPORT_PADDING);
194
- const result = { top, left, placement: resolvedPlacement };
195
- if (resolvedPlacement.startsWith("top") || resolvedPlacement.startsWith("bottom")) {
196
- 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);
197
- } else {
198
- result.arrowY = clampFloating(trigger.top + trigger.height / 2 - top - 4, 8, overlay.height - 16);
199
- }
200
- return result;
201
- }
202
- function Floating({
203
- children,
204
- content,
205
- placement = "top",
206
- trigger = "hover",
207
- open,
208
- defaultOpen = false,
209
- mouseEnterDelay = 0.1,
210
- mouseLeaveDelay = 0.1,
211
- arrow = true,
212
- color,
213
- className = "",
214
- overlayClassName = "",
215
- role = "tooltip",
216
- onOpenChange
217
- }) {
218
- const rootRef = useRef2(null);
219
- const overlayRef = useRef2(null);
220
- const enterTimer = useRef2();
221
- const leaveTimer = useRef2();
222
- const contentId = useId2();
223
- const triggers = Array.isArray(trigger) ? trigger : [trigger];
224
- const [visible, setVisible] = useControllableState({ value: open, defaultValue: defaultOpen, onChange: onOpenChange });
225
- const [position, setPosition] = useState();
226
- const setVisibleRef = useRef2(setVisible);
227
- setVisibleRef.current = setVisible;
228
- const closeSelfRef = useRef2();
229
- if (!closeSelfRef.current) closeSelfRef.current = () => setVisibleRef.current(false);
230
- const exclusiveTooltip = role === "tooltip" && triggers.includes("hover");
231
- function openNow() {
232
- if (exclusiveTooltip && activeTooltipClose !== closeSelfRef.current) {
233
- activeTooltipClose?.();
234
- activeTooltipClose = closeSelfRef.current;
235
- }
236
- setVisible(true);
237
- }
238
- function closeNow() {
239
- if (activeTooltipClose === closeSelfRef.current) activeTooltipClose = void 0;
240
- setVisible(false);
241
- }
242
- function clearTimers() {
243
- window.clearTimeout(enterTimer.current);
244
- window.clearTimeout(leaveTimer.current);
245
- }
246
- function show(delay = 0) {
247
- clearTimers();
248
- if (delay <= 0) openNow();
249
- else enterTimer.current = window.setTimeout(openNow, delay * 1e3);
250
- }
251
- function hide(delay = 0) {
252
- clearTimers();
253
- if (delay <= 0) closeNow();
254
- else leaveTimer.current = window.setTimeout(closeNow, delay * 1e3);
255
- }
145
+ function FloatingScrollbarProvider({ disabled = false, targetRef }) {
146
+ useScrollbarProximity();
256
147
  useEffect2(() => {
257
- if (!visible || !triggers.includes("click")) return;
258
- const close = (event) => {
259
- const target = event.target;
260
- if (!rootRef.current?.contains(target) && !overlayRef.current?.contains(target)) closeNow();
148
+ if (disabled) return;
149
+ const layer = document.createElement("div");
150
+ layer.className = "sia-floating-scrollbar-layer";
151
+ layer.setAttribute("aria-hidden", "true");
152
+ document.body.append(layer);
153
+ const entries = /* @__PURE__ */ new Map();
154
+ let scanFrame = 0;
155
+ let updateFrame = 0;
156
+ const setEntryVisible = (entry, visible) => {
157
+ entry.horizontalTrack.classList.toggle("sia-floating-scrollbar--visible", visible);
158
+ entry.verticalTrack.classList.toggle("sia-floating-scrollbar--visible", visible);
261
159
  };
262
- document.addEventListener("pointerdown", close);
263
- return () => document.removeEventListener("pointerdown", close);
264
- }, [triggers.join("|"), visible]);
265
- useLayoutEffect(() => {
266
- if (!visible) {
267
- setPosition(void 0);
268
- return;
269
- }
270
- const update = () => {
271
- if (!rootRef.current || !overlayRef.current) return;
272
- setPosition(resolveFloatingPosition(rootRef.current.getBoundingClientRect(), overlayRef.current.getBoundingClientRect(), placement));
160
+ const showEntry = (entry) => {
161
+ if (entry.hideTimer !== null) window.clearTimeout(entry.hideTimer);
162
+ entry.hideTimer = null;
163
+ setEntryVisible(entry, true);
273
164
  };
274
- update();
275
- window.addEventListener("resize", update);
276
- window.addEventListener("scroll", update, true);
277
- const observer = new ResizeObserver(update);
278
- if (rootRef.current) observer.observe(rootRef.current);
279
- if (overlayRef.current) observer.observe(overlayRef.current);
280
- return () => {
281
- window.removeEventListener("resize", update);
282
- window.removeEventListener("scroll", update, true);
283
- observer.disconnect();
165
+ const scheduleHide = (entry, delay = 500) => {
166
+ if (entry.hideTimer !== null) window.clearTimeout(entry.hideTimer);
167
+ entry.hideTimer = window.setTimeout(() => {
168
+ entry.hideTimer = null;
169
+ if (!entry.hoveringTarget && !entry.hoveringTrack && !entry.dragging) setEntryVisible(entry, false);
170
+ }, delay);
284
171
  };
285
- }, [children, content, placement, visible]);
286
- useEffect2(() => () => {
287
- clearTimers();
288
- if (activeTooltipClose === closeSelfRef.current) activeTooltipClose = void 0;
289
- }, []);
290
- const child = cloneElement(children, {
291
- ...triggers.includes("hover") ? { onMouseEnter: (event) => {
292
- children.props.onMouseEnter?.(event);
293
- show(mouseEnterDelay);
294
- }, onMouseLeave: (event) => {
295
- children.props.onMouseLeave?.(event);
296
- hide(mouseLeaveDelay);
297
- } } : {},
298
- ...triggers.includes("focus") ? { onFocus: (event) => {
299
- children.props.onFocus?.(event);
300
- show();
301
- }, onBlur: (event) => {
302
- children.props.onBlur?.(event);
303
- hide();
304
- } } : {},
305
- ...triggers.includes("click") ? { onClick: (event) => {
306
- children.props.onClick?.(event);
307
- if (visible) closeNow();
308
- else openNow();
309
- } } : {},
310
- "aria-describedby": visible ? contentId : void 0
311
- });
312
- const overlay = visible && content !== null && content !== void 0 && typeof document !== "undefined" ? createPortal(
313
- /* @__PURE__ */ jsxs2(
314
- "span",
315
- {
316
- ref: overlayRef,
317
- id: contentId,
318
- role,
319
- "data-custom-color": color ? "" : void 0,
320
- className: `sia-floating__overlay sia-floating__overlay--${position?.placement ?? placement} ${overlayClassName}`.trim(),
321
- style: {
322
- "--sia-floating-color": color,
323
- "--sia-floating-arrow-x": position?.arrowX == null ? void 0 : `${position.arrowX}px`,
324
- "--sia-floating-arrow-y": position?.arrowY == null ? void 0 : `${position.arrowY}px`,
325
- top: position?.top ?? 0,
326
- left: position?.left ?? 0,
327
- visibility: position ? "visible" : "hidden"
328
- },
329
- onMouseEnter: triggers.includes("hover") ? () => show() : void 0,
330
- onMouseLeave: triggers.includes("hover") ? () => hide(mouseLeaveDelay) : void 0,
331
- children: [
332
- content,
333
- arrow ? /* @__PURE__ */ jsx2("span", { className: "sia-floating__arrow" }) : null
334
- ]
172
+ const updateEntry = (entry) => {
173
+ const { target, horizontalTrack, horizontalThumb, verticalTrack, verticalThumb } = entry;
174
+ const hasHorizontal = canScroll(target, "horizontal");
175
+ const hasVertical = canScroll(target, "vertical");
176
+ const rect = visibleRect(target);
177
+ const thickness = 10;
178
+ horizontalTrack.hidden = !hasHorizontal || rect.width <= thickness || rect.height <= 0;
179
+ verticalTrack.hidden = !hasVertical || rect.height <= thickness || rect.width <= 0;
180
+ if (!horizontalTrack.hidden) {
181
+ const trackWidth = Math.max(0, rect.width - (hasVertical ? thickness : 0));
182
+ const thumbWidth = Math.max(24, Math.min(trackWidth, trackWidth * target.clientWidth / target.scrollWidth));
183
+ const travel = Math.max(0, trackWidth - thumbWidth);
184
+ const maxScroll = Math.max(1, target.scrollWidth - target.clientWidth);
185
+ horizontalTrack.style.left = `${rect.left}px`;
186
+ horizontalTrack.style.top = `${rect.bottom - thickness}px`;
187
+ horizontalTrack.style.width = `${trackWidth}px`;
188
+ horizontalTrack.style.height = `${thickness}px`;
189
+ horizontalThumb.style.width = `${thumbWidth}px`;
190
+ horizontalThumb.style.transform = `translate3d(${travel * target.scrollLeft / maxScroll}px, 0, 0)`;
335
191
  }
336
- ),
337
- document.body
338
- ) : null;
339
- return /* @__PURE__ */ jsxs2(Fragment, { children: [
340
- /* @__PURE__ */ jsx2("span", { ref: rootRef, className: `sia-floating ${className}`.trim(), children: child }),
341
- overlay
342
- ] });
343
- }
344
- function Tooltip(props) {
345
- return /* @__PURE__ */ jsx2(Floating, { ...props, content: props.title });
346
- }
347
- function Popover({ title, content, trigger = "click", overlayClassName = "", ...props }) {
348
- return /* @__PURE__ */ jsx2(Floating, { ...props, trigger, role: "dialog", overlayClassName: `sia-popover ${overlayClassName}`.trim(), content: /* @__PURE__ */ jsxs2(Fragment, { children: [
349
- title ? /* @__PURE__ */ jsx2("strong", { className: "sia-popover__title", children: title }) : null,
350
- /* @__PURE__ */ jsx2("span", { className: "sia-popover__content", children: content })
351
- ] }) });
352
- }
353
- function Timeline({ items = [], mode = "left", pending = false, pendingDot, reverse = false, className = "", ...props }) {
354
- const values = [...items, ...pending ? [{ children: pending === true ? "\u52A0\u8F7D\u4E2D..." : pending, dot: pendingDot ?? /* @__PURE__ */ jsx2(Icon, { name: "loader", className: "sia-spin-icon" }) }] : []];
355
- if (reverse) values.reverse();
356
- return /* @__PURE__ */ jsx2("ul", { className: `sia-timeline sia-timeline--${mode} ${className}`.trim(), ...withTitleTooltip(props), children: values.map((item, index) => {
357
- const position = item.position ?? (mode === "alternate" ? index % 2 ? "right" : "left" : mode);
358
- return /* @__PURE__ */ jsxs2("li", { className: `sia-timeline__item sia-timeline__item--${position}`, children: [
359
- item.label ? /* @__PURE__ */ jsx2("span", { className: "sia-timeline__label", children: item.label }) : null,
360
- /* @__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 }) }),
361
- /* @__PURE__ */ jsx2("span", { className: "sia-timeline__content", children: item.children })
362
- ] }, index);
363
- }) });
364
- }
365
- function collectKeys(nodes, branchOnly = false, result = []) {
366
- nodes.forEach((node) => {
367
- if (!branchOnly || node.children?.length) result.push(node.key);
368
- if (node.children) collectKeys(node.children, branchOnly, result);
369
- });
370
- return result;
371
- }
372
- function Tree({
373
- treeData,
374
- selectedKeys,
375
- defaultSelectedKeys = [],
376
- expandedKeys,
377
- defaultExpandedKeys = [],
378
- defaultExpandAll = false,
379
- checkedKeys,
380
- defaultCheckedKeys = [],
381
- loadingKeys = [],
382
- checkable = false,
383
- checkStrictly = false,
384
- multiple = false,
385
- selectable = true,
386
- showLine = false,
387
- showIcon = false,
388
- blockNode = false,
389
- indentSize = 20,
390
- switcherWidth = 24,
391
- stickyAncestors = false,
392
- stickyRowHeight = 34,
393
- draggable = false,
394
- loadData,
395
- onSelect,
396
- onCheck,
397
- onExpand,
398
- onDrop,
399
- className = "",
400
- ...props
401
- }) {
402
- const [selected, setSelected] = useControllableState({ value: selectedKeys, defaultValue: defaultSelectedKeys });
403
- const [expanded, setExpanded] = useControllableState({ value: expandedKeys, defaultValue: defaultExpandAll ? collectKeys(treeData, true) : defaultExpandedKeys });
404
- const [checked, setChecked] = useControllableState({ value: checkedKeys, defaultValue: defaultCheckedKeys });
405
- const [loading, setLoading] = useState(/* @__PURE__ */ new Set());
406
- const controlledLoading = useMemo2(() => new Set(loadingKeys), [loadingKeys]);
407
- const selectedKeySet = useMemo2(() => new Set(selected), [selected]);
408
- const selectedAncestorKeys = useMemo2(() => {
409
- const ancestors = /* @__PURE__ */ new Set();
410
- const visit = (nodes, parentKeys) => {
411
- nodes.forEach((node) => {
412
- if (selectedKeySet.has(node.key)) parentKeys.forEach((key) => ancestors.add(key));
413
- if (node.children?.length) visit(node.children, [...parentKeys, node.key]);
192
+ if (!verticalTrack.hidden) {
193
+ const trackHeight = Math.max(0, rect.height - (hasHorizontal ? thickness : 0));
194
+ const thumbHeight = Math.max(24, Math.min(trackHeight, trackHeight * target.clientHeight / target.scrollHeight));
195
+ const travel = Math.max(0, trackHeight - thumbHeight);
196
+ const maxScroll = Math.max(1, target.scrollHeight - target.clientHeight);
197
+ verticalTrack.style.left = `${rect.right - thickness}px`;
198
+ verticalTrack.style.top = `${rect.top}px`;
199
+ verticalTrack.style.width = `${thickness}px`;
200
+ verticalTrack.style.height = `${trackHeight}px`;
201
+ verticalThumb.style.height = `${thumbHeight}px`;
202
+ verticalThumb.style.transform = `translate3d(0, ${travel * target.scrollTop / maxScroll}px, 0)`;
203
+ }
204
+ };
205
+ const scheduleUpdateAll = () => {
206
+ if (updateFrame) return;
207
+ updateFrame = window.requestAnimationFrame(() => {
208
+ updateFrame = 0;
209
+ entries.forEach(updateEntry);
414
210
  });
415
211
  };
416
- visit(treeData, []);
417
- return ancestors;
418
- }, [selectedKeySet, treeData]);
419
- const dragNodeRef = useRef2();
420
- const pointerDragRef = useRef2();
421
- const suppressClickRef = useRef2(false);
422
- const [dragOver, setDragOver] = useState();
423
- const normalizedSwitcherWidth = Math.max(0, switcherWidth);
424
- async function toggleExpand(node) {
425
- const nextExpanded = !expanded.includes(node.key);
426
- if (nextExpanded && node.loaded !== true && !node.children?.length && node.isLeaf === false && loadData) {
427
- setLoading((keys) => new Set(keys).add(node.key));
428
- try {
429
- await loadData(node);
430
- } finally {
431
- setLoading((keys) => {
432
- const next2 = new Set(keys);
433
- next2.delete(node.key);
434
- return next2;
435
- });
436
- }
437
- }
438
- const next = nextExpanded ? [...expanded, node.key] : expanded.filter((key) => key !== node.key);
439
- setExpanded(next);
440
- onExpand?.([...next], { expanded: nextExpanded, node });
441
- }
442
- const checkState = useMemo2(() => getTreeCheckState(treeData, checked, checkStrictly), [treeData, checked, checkStrictly]);
443
- function check(node) {
444
- const next = toggleTreeCheck(treeData, checked, node, checkStrictly);
445
- setChecked([...next.checked]);
446
- onCheck?.([...next.checked], { checked: next.checkedValue, halfCheckedKeys: [...next.halfChecked], node });
447
- }
448
- function select(node, event) {
449
- if (!selectable || node.selectable === false || node.disabled) return;
450
- const exists = selected.includes(node.key);
451
- const next = multiple ? exists ? selected.filter((key) => key !== node.key) : [...selected, node.key] : exists ? [] : [node.key];
452
- setSelected(next);
453
- onSelect?.([...next], { selected: !exists, node, nativeEvent: event });
454
- }
455
- function selectForContextMenu(node, event) {
456
- if (!selectable || node.selectable === false || node.disabled || selected.includes(node.key)) return;
457
- const next = [node.key];
458
- setSelected(next);
459
- onSelect?.(next, { selected: true, node, nativeEvent: event });
460
- }
461
- function findNode(key, nodes = treeData) {
462
- for (const node of nodes) {
463
- if (String(node.key) === key) return node;
464
- const child = node.children?.length ? findNode(key, node.children) : void 0;
465
- if (child) return child;
466
- }
467
- return void 0;
468
- }
469
- function getPointerDropTarget(clientX, clientY, dragNode) {
470
- const targetElement = document.elementFromPoint(clientX, clientY)?.closest("[data-sia-tree-drop-key]");
471
- if (!targetElement) return void 0;
472
- const targetNode = targetElement?.dataset.siaTreeDropKey ? findNode(targetElement.dataset.siaTreeDropKey) : void 0;
473
- if (!targetNode || targetNode.key === dragNode.key || targetNode.disabled || targetNode.droppable === false) return void 0;
474
- const bounds = targetElement.getBoundingClientRect();
475
- const relativeY = bounds.height > 0 ? (clientY - bounds.top) / bounds.height : 0.5;
476
- const dropPosition = relativeY < 0.25 ? "before" : relativeY > 0.75 ? "after" : "inside";
477
- return { node: targetNode, dropPosition };
478
- }
479
- function beginPointerDrag(node, event) {
480
- if (event.button !== 0) return;
481
- pointerDragRef.current = {
482
- node,
483
- pointerId: event.pointerId,
484
- startX: event.clientX,
485
- startY: event.clientY,
486
- active: false
212
+ const bindTrackDrag = (entry, axis, track, thumb) => {
213
+ const target = entry.target;
214
+ const onTrackPointerEnter = () => {
215
+ entry.hoveringTrack = true;
216
+ showEntry(entry);
217
+ };
218
+ const onTrackPointerLeave = () => {
219
+ entry.hoveringTrack = false;
220
+ scheduleHide(entry);
221
+ };
222
+ const onTrackPointerDown = (event) => {
223
+ if (event.target === thumb || event.button !== 0) return;
224
+ event.preventDefault();
225
+ event.stopPropagation();
226
+ const trackRect = track.getBoundingClientRect();
227
+ const thumbRect = thumb.getBoundingClientRect();
228
+ const trackLength = axis === "vertical" ? trackRect.height : trackRect.width;
229
+ const thumbLength = axis === "vertical" ? thumbRect.height : thumbRect.width;
230
+ const pointer = axis === "vertical" ? event.clientY - trackRect.top : event.clientX - trackRect.left;
231
+ const ratio = Math.max(0, Math.min(1, (pointer - thumbLength / 2) / Math.max(1, trackLength - thumbLength)));
232
+ if (axis === "vertical") target.scrollTop = ratio * (target.scrollHeight - target.clientHeight);
233
+ else target.scrollLeft = ratio * (target.scrollWidth - target.clientWidth);
234
+ };
235
+ const onThumbPointerDown = (event) => {
236
+ if (event.button !== 0) return;
237
+ event.preventDefault();
238
+ event.stopPropagation();
239
+ entry.dragging = true;
240
+ showEntry(entry);
241
+ thumb.setPointerCapture(event.pointerId);
242
+ const startPointer = axis === "vertical" ? event.clientY : event.clientX;
243
+ const startScroll = axis === "vertical" ? target.scrollTop : target.scrollLeft;
244
+ const trackRect = track.getBoundingClientRect();
245
+ const thumbRect = thumb.getBoundingClientRect();
246
+ const trackLength = axis === "vertical" ? trackRect.height : trackRect.width;
247
+ const thumbLength = axis === "vertical" ? thumbRect.height : thumbRect.width;
248
+ const maxScroll = axis === "vertical" ? target.scrollHeight - target.clientHeight : target.scrollWidth - target.clientWidth;
249
+ const travel = Math.max(1, trackLength - thumbLength);
250
+ const onPointerMove = (moveEvent) => {
251
+ if (moveEvent.pointerId !== event.pointerId) return;
252
+ const pointer = axis === "vertical" ? moveEvent.clientY : moveEvent.clientX;
253
+ const nextScroll = startScroll + (pointer - startPointer) * maxScroll / travel;
254
+ if (axis === "vertical") target.scrollTop = nextScroll;
255
+ else target.scrollLeft = nextScroll;
256
+ };
257
+ const onPointerUp = (upEvent) => {
258
+ if (upEvent.pointerId !== event.pointerId) return;
259
+ entry.dragging = false;
260
+ thumb.releasePointerCapture(event.pointerId);
261
+ window.removeEventListener("pointermove", onPointerMove);
262
+ window.removeEventListener("pointerup", onPointerUp);
263
+ window.removeEventListener("pointercancel", onPointerUp);
264
+ scheduleHide(entry);
265
+ };
266
+ window.addEventListener("pointermove", onPointerMove);
267
+ window.addEventListener("pointerup", onPointerUp);
268
+ window.addEventListener("pointercancel", onPointerUp);
269
+ };
270
+ track.addEventListener("pointerenter", onTrackPointerEnter);
271
+ track.addEventListener("pointerleave", onTrackPointerLeave);
272
+ track.addEventListener("pointerdown", onTrackPointerDown);
273
+ thumb.addEventListener("pointerdown", onThumbPointerDown);
274
+ return () => {
275
+ track.removeEventListener("pointerenter", onTrackPointerEnter);
276
+ track.removeEventListener("pointerleave", onTrackPointerLeave);
277
+ track.removeEventListener("pointerdown", onTrackPointerDown);
278
+ thumb.removeEventListener("pointerdown", onThumbPointerDown);
279
+ };
487
280
  };
488
- event.currentTarget.setPointerCapture(event.pointerId);
489
- }
490
- function movePointerDrag(event) {
491
- const pointerDrag = pointerDragRef.current;
492
- if (!pointerDrag || pointerDrag.pointerId !== event.pointerId) return;
493
- if (!pointerDrag.active && Math.hypot(event.clientX - pointerDrag.startX, event.clientY - pointerDrag.startY) < 5) return;
494
- pointerDrag.active = true;
495
- dragNodeRef.current = pointerDrag.node;
496
- event.preventDefault();
497
- const target = getPointerDropTarget(event.clientX, event.clientY, pointerDrag.node);
498
- setDragOver(target ? { key: target.node.key, position: target.dropPosition } : void 0);
499
- }
500
- function finishPointerDrag(event) {
501
- const pointerDrag = pointerDragRef.current;
502
- if (!pointerDrag || pointerDrag.pointerId !== event.pointerId) return;
503
- if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
504
- if (pointerDrag.active) {
505
- event.preventDefault();
506
- suppressClickRef.current = true;
507
- const target = getPointerDropTarget(event.clientX, event.clientY, pointerDrag.node);
508
- if (target) onDrop?.({ dragNode: pointerDrag.node, node: target.node, dropPosition: target.dropPosition });
509
- window.setTimeout(() => {
510
- suppressClickRef.current = false;
511
- }, 0);
512
- }
513
- pointerDragRef.current = void 0;
514
- dragNodeRef.current = void 0;
515
- setDragOver(void 0);
516
- }
517
- function cancelPointerDrag(event) {
518
- const pointerDrag = pointerDragRef.current;
519
- if (!pointerDrag || pointerDrag.pointerId !== event.pointerId) return;
520
- pointerDragRef.current = void 0;
521
- dragNodeRef.current = void 0;
522
- setDragOver(void 0);
523
- }
524
- function render(nodes, depth = 0) {
525
- return nodes.map((node) => {
526
- const open = expanded.includes(node.key);
527
- const isSelected = selected.includes(node.key);
528
- const isSelectedAncestor = selectedAncestorKeys.has(node.key);
529
- const isChecked = checkState.checked.has(node.key);
530
- const isHalfChecked = checkState.halfChecked.has(node.key);
531
- const hasChildren = Boolean(node.children?.length || node.isLeaf === false);
532
- const nodeLoading = loading.has(node.key) || controlledLoading.has(node.key);
533
- const sticky = stickyAncestors && hasChildren && open;
534
- const canDrag = draggable && node.draggable !== false && !node.disabled;
535
- const { className: rowClassName = "", style: rowStyle, onClick: onRowClick, ...rowProps } = node.rowProps ?? {};
536
- const unloaded = hasChildren && node.loaded === false;
537
- const dropPosition = dragOver?.key === node.key ? dragOver.position : void 0;
538
- 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: [
539
- /* @__PURE__ */ jsx2(
540
- "div",
541
- {
542
- ...withTitleTooltip(rowProps),
543
- className: `sia-tree__row${blockNode ? " sia-tree__row--block" : ""}${sticky ? " sia-tree__row--sticky" : ""} ${rowClassName}`.trim(),
544
- style: {
545
- ...rowStyle,
546
- paddingInlineStart: depth * Math.max(0, indentSize),
547
- top: sticky ? depth * Math.max(1, stickyRowHeight) : rowStyle?.top,
548
- zIndex: sticky ? Math.max(1, 100 - depth) : rowStyle?.zIndex
549
- },
550
- "aria-busy": nodeLoading || void 0,
551
- "data-sia-tree-drop-key": String(node.key),
552
- "data-sia-tree-draggable": canDrag || void 0,
553
- onClick: (event) => {
554
- onRowClick?.(event);
555
- if (event.defaultPrevented || suppressClickRef.current || node.disabled) return;
556
- select(node, event);
557
- if (hasChildren) void toggleExpand(node);
558
- },
559
- children: (() => {
560
- const content = /* @__PURE__ */ jsxs2("span", { className: "sia-tree__context-content", children: [
561
- 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) => {
562
- event.stopPropagation();
563
- void toggleExpand(node);
564
- }, 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" }),
565
- checkable || node.checkable ? /* @__PURE__ */ jsx2("span", { onClick: (event) => event.stopPropagation(), children: /* @__PURE__ */ jsx2(Checkbox2, { checked: isChecked, indeterminate: isHalfChecked, disabled: node.disabled || node.disableCheckbox, onChange: () => check(node) }) }) : null,
566
- node.prefix ? /* @__PURE__ */ jsx2("span", { className: "sia-tree__prefix", onClick: (event) => event.stopPropagation(), children: node.prefix }) : null,
567
- showIcon ? /* @__PURE__ */ jsx2("span", { className: "sia-tree__icon", children: node.icon ?? /* @__PURE__ */ jsx2(Icon, { name: hasChildren ? "folder" : "file", size: 15 }) }) : null,
568
- /* @__PURE__ */ jsx2(
569
- "button",
570
- {
571
- type: "button",
572
- className: "sia-tree__title",
573
- disabled: node.disabled,
574
- onPointerDown: canDrag ? (event) => beginPointerDrag(node, event) : void 0,
575
- onPointerMove: canDrag ? movePointerDrag : void 0,
576
- onPointerUp: canDrag ? finishPointerDrag : void 0,
577
- onPointerCancel: canDrag ? cancelPointerDrag : void 0,
578
- children: node.title
579
- }
580
- ),
581
- nodeLoading ? /* @__PURE__ */ jsx2("span", { className: "sia-tree__loading-text", role: "status", children: node.loadingText ?? "\u6B63\u5728\u52A0\u8F7D\u2026" }) : null,
582
- node.extra ? /* @__PURE__ */ jsx2("span", { className: "sia-tree__extra", onClick: (event) => event.stopPropagation(), children: node.extra }) : null
583
- ] });
584
- return node.contextMenu ? /* @__PURE__ */ jsx2(
585
- Dropdown,
586
- {
587
- className: "sia-tree__context-trigger",
588
- popupClassName: node.contextMenuPopupClassName,
589
- trigger: ["contextMenu"],
590
- menu: node.contextMenu,
591
- onContextMenu: (event) => selectForContextMenu(node, event),
592
- children: content
593
- }
594
- ) : content;
595
- })()
596
- }
597
- ),
598
- open && node.children?.length ? /* @__PURE__ */ jsx2("div", { className: "sia-tree__children", role: "group", children: render(node.children, depth + 1) }) : null
599
- ] }, node.key);
281
+ const resizeObserver = new ResizeObserver((observedEntries) => {
282
+ observedEntries.forEach(({ target }) => {
283
+ const entry = entries.get(target);
284
+ if (entry) updateEntry(entry);
285
+ });
286
+ });
287
+ const addTarget = (target) => {
288
+ if (entries.has(target)) return;
289
+ const horizontal = createTrack("horizontal");
290
+ const vertical = createTrack("vertical");
291
+ layer.append(horizontal.track, vertical.track);
292
+ const onPointerEnter = () => {
293
+ const entry2 = entries.get(target);
294
+ if (!entry2) return;
295
+ entry2.hoveringTarget = true;
296
+ showEntry(entry2);
297
+ };
298
+ const onPointerLeave = () => {
299
+ const entry2 = entries.get(target);
300
+ if (!entry2) return;
301
+ entry2.hoveringTarget = false;
302
+ scheduleHide(entry2);
303
+ };
304
+ const onScroll = () => {
305
+ const entry2 = entries.get(target);
306
+ if (!entry2) return;
307
+ updateEntry(entry2);
308
+ showEntry(entry2);
309
+ scheduleHide(entry2, 700);
310
+ };
311
+ target.addEventListener("pointerenter", onPointerEnter);
312
+ target.addEventListener("pointerleave", onPointerLeave);
313
+ target.addEventListener("scroll", onScroll, { passive: true });
314
+ const entry = {
315
+ target,
316
+ horizontalTrack: horizontal.track,
317
+ horizontalThumb: horizontal.thumb,
318
+ verticalTrack: vertical.track,
319
+ verticalThumb: vertical.thumb,
320
+ hideTimer: null,
321
+ hoveringTarget: target.matches(":hover"),
322
+ hoveringTrack: false,
323
+ dragging: false,
324
+ cleanup: () => void 0
325
+ };
326
+ const cleanupHorizontal = bindTrackDrag(entry, "horizontal", horizontal.track, horizontal.thumb);
327
+ const cleanupVertical = bindTrackDrag(entry, "vertical", vertical.track, vertical.thumb);
328
+ entry.cleanup = () => {
329
+ if (entry.hideTimer !== null) window.clearTimeout(entry.hideTimer);
330
+ cleanupHorizontal();
331
+ cleanupVertical();
332
+ target.removeEventListener("pointerenter", onPointerEnter);
333
+ target.removeEventListener("pointerleave", onPointerLeave);
334
+ target.removeEventListener("scroll", onScroll);
335
+ resizeObserver.unobserve(target);
336
+ target.removeAttribute("data-sia-floating-scrollbar");
337
+ horizontal.track.remove();
338
+ vertical.track.remove();
339
+ };
340
+ entries.set(target, entry);
341
+ target.setAttribute("data-sia-floating-scrollbar", "");
342
+ resizeObserver.observe(target);
343
+ updateEntry(entry);
344
+ if (entry.hoveringTarget) showEntry(entry);
345
+ };
346
+ const removeTarget = (target) => {
347
+ const entry = entries.get(target);
348
+ if (!entry) return;
349
+ entry.cleanup();
350
+ entries.delete(target);
351
+ };
352
+ const scanTargets = () => {
353
+ scanFrame = 0;
354
+ const candidates = (targetRef ? [targetRef.current] : [document.scrollingElement, ...document.querySelectorAll("*")]).filter((target) => target instanceof HTMLElement);
355
+ const nextTargets = /* @__PURE__ */ new Set();
356
+ candidates.forEach((target) => {
357
+ if (canScroll(target, "horizontal") || canScroll(target, "vertical")) {
358
+ nextTargets.add(target);
359
+ if (entries.has(target) || !target.hasAttribute("data-sia-floating-scrollbar")) addTarget(target);
360
+ }
361
+ });
362
+ entries.forEach((_entry, target) => {
363
+ if (!target.isConnected || !nextTargets.has(target)) removeTarget(target);
364
+ else updateEntry(_entry);
365
+ });
366
+ };
367
+ const scheduleScan = () => {
368
+ if (scanFrame) return;
369
+ scanFrame = window.requestAnimationFrame(scanTargets);
370
+ };
371
+ const mutationObserver = new MutationObserver((mutations) => {
372
+ if (mutations.some((mutation) => !layer.contains(mutation.target))) scheduleScan();
600
373
  });
601
- }
602
- 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, ...withTitleTooltip(props), children: render(treeData) });
374
+ mutationObserver.observe(document.documentElement, { subtree: true, childList: true, attributes: true });
375
+ window.addEventListener("resize", scheduleScan);
376
+ window.addEventListener("scroll", scheduleUpdateAll, true);
377
+ scheduleScan();
378
+ return () => {
379
+ mutationObserver.disconnect();
380
+ resizeObserver.disconnect();
381
+ window.removeEventListener("resize", scheduleScan);
382
+ window.removeEventListener("scroll", scheduleUpdateAll, true);
383
+ if (scanFrame) window.cancelAnimationFrame(scanFrame);
384
+ if (updateFrame) window.cancelAnimationFrame(updateFrame);
385
+ entries.forEach((entry) => entry.cleanup());
386
+ entries.clear();
387
+ layer.remove();
388
+ };
389
+ }, [disabled, targetRef]);
390
+ return null;
603
391
  }
604
392
 
605
- // src/components/Modal.tsx
606
- import { forwardRef as forwardRef2, useEffect as useEffect4, useId as useId3, useRef as useRef4, useState as useState2 } from "react";
607
- import { createRoot } from "react-dom/client";
608
-
609
- // src/components/Overlay.tsx
610
- import { useEffect as useEffect3, useRef as useRef3 } from "react";
611
- import { createPortal as createPortal2 } from "react-dom";
612
- var bodyLockCount = 0;
613
- var previousBodyOverflow = "";
614
- var overlayStack = [];
615
- function lockBodyScroll() {
616
- if (bodyLockCount === 0) {
617
- previousBodyOverflow = document.body.style.overflow;
618
- document.body.style.overflow = "hidden";
393
+ // src/components/Checkbox.tsx
394
+ import { createContext, forwardRef, useContext, useEffect as useEffect3, useId, useMemo, useRef } from "react";
395
+ import { jsx, jsxs } from "react/jsx-runtime";
396
+ var CheckboxGroupContext = createContext(null);
397
+ var CheckboxRoot = forwardRef(function Checkbox({ value, size: sizeProp, indeterminate = false, children, disabled, checked, defaultChecked, className = "", onChange, ...props }, forwardedRef) {
398
+ const size = useControlSize(sizeProp);
399
+ const group = useContext(CheckboxGroupContext);
400
+ const localRef = useRef(null);
401
+ const fallbackId = useId();
402
+ const grouped = group !== null && value !== void 0;
403
+ const currentChecked = grouped ? group.values.includes(value) : checked;
404
+ useEffect3(() => {
405
+ if (localRef.current) localRef.current.indeterminate = indeterminate;
406
+ }, [indeterminate]);
407
+ function setRef(node) {
408
+ localRef.current = node;
409
+ if (typeof forwardedRef === "function") forwardedRef(node);
410
+ else if (forwardedRef) forwardedRef.current = node;
619
411
  }
620
- bodyLockCount += 1;
621
- return () => {
622
- bodyLockCount = Math.max(0, bodyLockCount - 1);
623
- if (bodyLockCount === 0) document.body.style.overflow = previousBodyOverflow;
412
+ return /* @__PURE__ */ jsxs("label", { className: `sia-checkbox sia-checkbox--${size}${disabled || group?.disabled ? " is-disabled" : ""} ${className}`.trim(), children: [
413
+ /* @__PURE__ */ jsx(
414
+ "input",
415
+ {
416
+ ...withTitleTooltip(props),
417
+ ref: setRef,
418
+ id: props.id ?? fallbackId,
419
+ className: "sia-checkbox__input",
420
+ type: "checkbox",
421
+ name: group?.name ?? props.name,
422
+ disabled: disabled || group?.disabled,
423
+ checked: currentChecked,
424
+ defaultChecked: grouped ? void 0 : defaultChecked,
425
+ "aria-checked": indeterminate ? "mixed" : currentChecked,
426
+ onChange: (event) => {
427
+ if (grouped) group.toggle(value, event.currentTarget.checked);
428
+ onChange?.(event.currentTarget.checked, event);
429
+ }
430
+ }
431
+ ),
432
+ /* @__PURE__ */ jsx("span", { className: "sia-checkbox__control", "aria-hidden": "true", children: /* @__PURE__ */ jsx("span", {}) }),
433
+ children !== void 0 ? /* @__PURE__ */ jsx("span", { className: "sia-checkbox__label", children }) : null
434
+ ] });
435
+ });
436
+ function CheckboxGroup({
437
+ size: sizeProp,
438
+ columns,
439
+ direction = "horizontal",
440
+ gap,
441
+ value,
442
+ defaultValue = [],
443
+ options,
444
+ disabled,
445
+ name,
446
+ className = "",
447
+ children,
448
+ onChange
449
+ }) {
450
+ const size = useControlSize(sizeProp);
451
+ const gridColumns = columns !== void 0 && Number.isInteger(columns) && columns > 0 ? columns : void 0;
452
+ const layoutStyle = {
453
+ gridTemplateColumns: gridColumns ? `repeat(${gridColumns}, minmax(0, 1fr))` : void 0,
454
+ gap: gap === void 0 ? void 0 : typeof gap === "number" ? Math.max(0, gap) : `${Math.max(0, gap[0])}px ${Math.max(0, gap[1])}px`
624
455
  };
456
+ const [values, setValues] = useControllableState({ value, defaultValue, onChange });
457
+ const context = useMemo(() => ({
458
+ values,
459
+ disabled,
460
+ name,
461
+ toggle(optionValue, checked) {
462
+ const next = checked ? values.includes(optionValue) ? values : [...values, optionValue] : values.filter((item) => item !== optionValue);
463
+ setValues(next);
464
+ }
465
+ }), [disabled, name, setValues, values]);
466
+ return /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: size, children: /* @__PURE__ */ jsx(CheckboxGroupContext.Provider, { value: context, children: /* @__PURE__ */ jsxs("div", { className: `sia-checkbox-group${gridColumns ? " sia-checkbox-group--grid" : direction === "vertical" ? " sia-checkbox-group--vertical" : ""} ${className}`.trim(), style: layoutStyle, role: "group", children: [
467
+ options?.map((option) => {
468
+ const normalized = typeof option === "object" ? option : { label: String(option), value: option };
469
+ return /* @__PURE__ */ jsx(CheckboxRoot, { value: normalized.value, disabled: normalized.disabled, children: normalized.label }, normalized.value);
470
+ }),
471
+ children
472
+ ] }) }) });
625
473
  }
626
- function focusableElements(container) {
627
- return [...container.querySelectorAll(
628
- 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
629
- )].filter((element) => !element.hidden && element.getAttribute("aria-hidden") !== "true");
474
+ var Checkbox2 = Object.assign(CheckboxRoot, {
475
+ /** 组合多个子项并统一管理布局或选择状态。 */
476
+ Group: CheckboxGroup
477
+ });
478
+
479
+ // src/components/treeCheckState.ts
480
+ function affectedKeys(node) {
481
+ if (node.disabled || node.disableCheckbox) return [];
482
+ return [...node.checkable === false ? [] : [node.key], ...node.children?.flatMap(affectedKeys) ?? []];
630
483
  }
631
- function useOverlayLifecycle(open, panelRef, onRequestClose, keyboard = true) {
632
- const overlayIdRef = useRef3(/* @__PURE__ */ Symbol("sia-overlay"));
633
- const closeRef = useRef3(onRequestClose);
634
- closeRef.current = onRequestClose;
635
- useEffect3(() => {
636
- if (!open || typeof document === "undefined") return void 0;
637
- const overlayId = overlayIdRef.current;
638
- const previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
639
- overlayStack.push(overlayId);
640
- const unlockBody = lockBodyScroll();
641
- const focusTimer = window.setTimeout(() => {
642
- const panel = panelRef.current;
643
- if (!panel) return;
644
- const [firstFocusable] = focusableElements(panel);
645
- (firstFocusable ?? panel).focus({ preventScroll: true });
646
- }, 0);
647
- function handleKeyDown(event) {
648
- if (overlayStack.at(-1) !== overlayId) return;
649
- if (event.key === "Escape" && keyboard) {
650
- event.preventDefault();
651
- closeRef.current();
652
- return;
653
- }
654
- if (event.key !== "Tab" || !panelRef.current) return;
655
- const focusable = focusableElements(panelRef.current);
656
- if (focusable.length === 0) {
657
- event.preventDefault();
658
- panelRef.current.focus();
659
- return;
660
- }
661
- const first = focusable[0];
662
- const last = focusable.at(-1);
663
- if (event.shiftKey && document.activeElement === first) {
664
- event.preventDefault();
665
- last.focus();
666
- } else if (!event.shiftKey && document.activeElement === last) {
667
- event.preventDefault();
668
- first.focus();
669
- }
484
+ function getTreeCheckState(nodes, keys, strictly = false) {
485
+ const checked = new Set(keys);
486
+ const halfChecked = /* @__PURE__ */ new Set();
487
+ if (strictly) return { checked, halfChecked };
488
+ const explicit = new Set(keys);
489
+ function expand(items) {
490
+ for (const node of items) {
491
+ if (explicit.has(node.key)) affectedKeys(node).forEach((key) => checked.add(key));
492
+ if (node.children) expand(node.children);
670
493
  }
671
- document.addEventListener("keydown", handleKeyDown);
672
- return () => {
673
- window.clearTimeout(focusTimer);
674
- document.removeEventListener("keydown", handleKeyDown);
675
- const index = overlayStack.lastIndexOf(overlayId);
676
- if (index >= 0) overlayStack.splice(index, 1);
677
- unlockBody();
678
- previouslyFocused?.focus({ preventScroll: true });
494
+ }
495
+ expand(nodes);
496
+ function fold(node) {
497
+ const children = node.children?.map(fold).filter((value) => value !== void 0) ?? [];
498
+ if (node.disabled || node.disableCheckbox) return void 0;
499
+ if (!children.length) return { checked: checked.has(node.key), partial: false };
500
+ const all = children.every((child) => child.checked);
501
+ const some = children.some((child) => child.checked || child.partial);
502
+ if (node.checkable !== false) {
503
+ if (all) checked.add(node.key);
504
+ else checked.delete(node.key);
505
+ if (!all && some) halfChecked.add(node.key);
506
+ }
507
+ return { checked: all, partial: !all && some };
508
+ }
509
+ nodes.forEach(fold);
510
+ return { checked, halfChecked };
511
+ }
512
+ function toggleTreeCheck(nodes, keys, node, strictly = false) {
513
+ const state = getTreeCheckState(nodes, keys, strictly);
514
+ const next = new Set(state.checked);
515
+ const checked = !next.has(node.key);
516
+ if (node.disabled || node.disableCheckbox || node.checkable === false) return { ...state, checkedValue: !checked };
517
+ (strictly ? [node.key] : affectedKeys(node)).forEach((key) => checked ? next.add(key) : next.delete(key));
518
+ if (!strictly && !checked) {
519
+ let removeParents2 = function(items) {
520
+ let found = false;
521
+ for (const item of items) {
522
+ const childFound = item.children ? removeParents2(item.children) : false;
523
+ if (childFound) next.delete(item.key);
524
+ if (item.key === node.key || childFound) found = true;
525
+ }
526
+ return found;
679
527
  };
680
- }, [keyboard, open, panelRef]);
528
+ var removeParents = removeParents2;
529
+ removeParents2(nodes);
530
+ }
531
+ return { ...getTreeCheckState(nodes, [...next], strictly), checkedValue: checked };
681
532
  }
682
- function OverlayPortal({ children, container }) {
683
- if (typeof document === "undefined") return null;
684
- const target = typeof container === "function" ? container() : container ?? document.body;
685
- return createPortal2(children, target);
533
+
534
+ // src/components/FloatingDisplay.tsx
535
+ import { cloneElement, useEffect as useEffect4, useId as useId2, useLayoutEffect, useMemo as useMemo2, useRef as useRef2, useState } from "react";
536
+ import { createPortal } from "react-dom";
537
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
538
+ var FLOATING_GAP = 9;
539
+ var FLOATING_VIEWPORT_PADDING = 8;
540
+ var FLOATING_ARROW_EDGE_OFFSET = 16;
541
+ var activeTooltipClose;
542
+ function clampFloating(value, min, max) {
543
+ return Math.min(Math.max(value, min), Math.max(min, max));
544
+ }
545
+ function resolveFloatingPosition(trigger, overlay, placement) {
546
+ const viewportWidth = document.documentElement.clientWidth;
547
+ const viewportHeight = document.documentElement.clientHeight;
548
+ let resolvedPlacement = placement;
549
+ if (placement.startsWith("top") && trigger.top - overlay.height - FLOATING_GAP < FLOATING_VIEWPORT_PADDING && trigger.bottom + overlay.height + FLOATING_GAP <= viewportHeight - FLOATING_VIEWPORT_PADDING) {
550
+ resolvedPlacement = placement.replace("top", "bottom");
551
+ } else if (placement.startsWith("bottom") && trigger.bottom + overlay.height + FLOATING_GAP > viewportHeight - FLOATING_VIEWPORT_PADDING && trigger.top - overlay.height - FLOATING_GAP >= FLOATING_VIEWPORT_PADDING) {
552
+ resolvedPlacement = placement.replace("bottom", "top");
553
+ } else if (placement === "left" && trigger.left - overlay.width - FLOATING_GAP < FLOATING_VIEWPORT_PADDING && trigger.right + overlay.width + FLOATING_GAP <= viewportWidth - FLOATING_VIEWPORT_PADDING) {
554
+ resolvedPlacement = "right";
555
+ } else if (placement === "right" && trigger.right + overlay.width + FLOATING_GAP > viewportWidth - FLOATING_VIEWPORT_PADDING && trigger.left - overlay.width - FLOATING_GAP >= FLOATING_VIEWPORT_PADDING) {
556
+ resolvedPlacement = "left";
557
+ }
558
+ let top = trigger.top;
559
+ let left = trigger.left;
560
+ if (resolvedPlacement.startsWith("top") || resolvedPlacement.startsWith("bottom")) {
561
+ top = resolvedPlacement.startsWith("top") ? trigger.top - overlay.height - FLOATING_GAP : trigger.bottom + FLOATING_GAP;
562
+ left = resolvedPlacement.endsWith("-start") ? trigger.left : resolvedPlacement.endsWith("-end") ? trigger.right - overlay.width : trigger.left + (trigger.width - overlay.width) / 2;
563
+ } else {
564
+ top = trigger.top + (trigger.height - overlay.height) / 2;
565
+ left = resolvedPlacement === "left" ? trigger.left - overlay.width - FLOATING_GAP : trigger.right + FLOATING_GAP;
566
+ }
567
+ top = clampFloating(top, FLOATING_VIEWPORT_PADDING, viewportHeight - overlay.height - FLOATING_VIEWPORT_PADDING);
568
+ left = clampFloating(left, FLOATING_VIEWPORT_PADDING, viewportWidth - overlay.width - FLOATING_VIEWPORT_PADDING);
569
+ const result = { top, left, placement: resolvedPlacement };
570
+ if (resolvedPlacement.startsWith("top") || resolvedPlacement.startsWith("bottom")) {
571
+ 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);
572
+ } else {
573
+ result.arrowY = clampFloating(trigger.top + trigger.height / 2 - top - 4, 8, overlay.height - 16);
574
+ }
575
+ return result;
686
576
  }
687
-
688
- // src/components/Modal.tsx
689
- import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
690
- var modalHandles = /* @__PURE__ */ new Set();
691
- var MODAL_MOTION_DURATION = 220;
692
- var typeIcons = {
693
- info: /* @__PURE__ */ jsx3(Icon, { name: "info", size: 23 }),
694
- success: /* @__PURE__ */ jsx3(Icon, { name: "circle-check", size: 23 }),
695
- warning: /* @__PURE__ */ jsx3(Icon, { name: "warning", size: 23 }),
696
- error: /* @__PURE__ */ jsx3(Icon, { name: "circle-close", size: 23 }),
697
- confirm: /* @__PURE__ */ jsx3(Icon, { name: "question", size: 23 })
698
- };
699
- var ModalRoot = forwardRef2(function Modal({
700
- open,
701
- title,
577
+ function Floating({
702
578
  children,
703
- footer,
704
- footerExtra,
705
- type = "default",
706
- okText = "\u786E\u5B9A",
707
- cancelText = "\u53D6\u6D88",
708
- showCancel = type === "default" || type === "confirm",
709
- confirmLoading = false,
710
- okButtonProps,
711
- cancelButtonProps,
712
- closable = true,
713
- maskClosable = true,
714
- keyboard = true,
715
- centered = false,
716
- width = 520,
717
- height,
718
- zIndex = 1e3,
719
- getContainer,
720
- onOk,
721
- onCancel,
722
- onOpenChange,
723
- afterOpenChange,
579
+ content,
580
+ placement = "top",
581
+ trigger = "hover",
582
+ open,
583
+ defaultOpen = false,
584
+ mouseEnterDelay = 0.1,
585
+ mouseLeaveDelay = 0.1,
586
+ arrow = true,
587
+ color,
724
588
  className = "",
725
- style,
726
- ...props
727
- }, forwardedRef) {
728
- const localRef = useRef4(null);
729
- const titleId = useId3();
730
- const [submitting, setSubmitting] = useState2(false);
731
- const [rendered, setRendered] = useState2(open);
732
- const [closing, setClosing] = useState2(false);
733
- const afterOpenChangeRef = useRef4(afterOpenChange);
734
- afterOpenChangeRef.current = afterOpenChange;
735
- const submitLock = useRef4(false);
736
- useEffect4(() => {
737
- if (!open || !rendered) return void 0;
738
- const timer = window.setTimeout(() => afterOpenChangeRef.current?.(true), MODAL_MOTION_DURATION);
739
- return () => window.clearTimeout(timer);
740
- }, [open, rendered]);
741
- useEffect4(() => {
742
- if (open) {
743
- setRendered(true);
744
- setClosing(false);
745
- return void 0;
589
+ overlayClassName = "",
590
+ role = "tooltip",
591
+ onOpenChange
592
+ }) {
593
+ const rootRef = useRef2(null);
594
+ const overlayRef = useRef2(null);
595
+ const enterTimer = useRef2();
596
+ const leaveTimer = useRef2();
597
+ const contentId = useId2();
598
+ const triggers = Array.isArray(trigger) ? trigger : [trigger];
599
+ const [visible, setVisible] = useControllableState({ value: open, defaultValue: defaultOpen, onChange: onOpenChange });
600
+ const [position, setPosition] = useState();
601
+ const setVisibleRef = useRef2(setVisible);
602
+ setVisibleRef.current = setVisible;
603
+ const closeSelfRef = useRef2();
604
+ if (!closeSelfRef.current) closeSelfRef.current = () => setVisibleRef.current(false);
605
+ const exclusiveTooltip = role === "tooltip" && triggers.includes("hover");
606
+ function openNow() {
607
+ if (exclusiveTooltip && activeTooltipClose !== closeSelfRef.current) {
608
+ activeTooltipClose?.();
609
+ activeTooltipClose = closeSelfRef.current;
746
610
  }
747
- if (!rendered) return void 0;
748
- setClosing(true);
749
- const timer = window.setTimeout(() => {
750
- setRendered(false);
751
- setClosing(false);
752
- afterOpenChangeRef.current?.(false);
753
- }, MODAL_MOTION_DURATION);
754
- return () => window.clearTimeout(timer);
755
- }, [open, rendered]);
756
- function setRef(node) {
757
- localRef.current = node;
758
- if (typeof forwardedRef === "function") forwardedRef(node);
759
- else if (forwardedRef) forwardedRef.current = node;
760
- }
761
- function requestClose() {
762
- if (submitting || confirmLoading) return;
763
- onCancel?.();
764
- onOpenChange?.(false);
611
+ setVisible(true);
765
612
  }
766
- async function handleOk() {
767
- if (submitLock.current || confirmLoading) return;
768
- submitLock.current = true;
769
- if (!onOk) {
770
- onOpenChange?.(false);
771
- submitLock.current = false;
772
- return;
773
- }
774
- try {
775
- const result = onOk();
776
- if (result instanceof Promise) {
777
- setSubmitting(true);
778
- const resolved = await result;
779
- if (resolved !== false) onOpenChange?.(false);
780
- } else if (result !== false) {
781
- onOpenChange?.(false);
782
- }
783
- } catch {
784
- return;
785
- } finally {
786
- submitLock.current = false;
787
- setSubmitting(false);
788
- }
613
+ function closeNow() {
614
+ if (activeTooltipClose === closeSelfRef.current) activeTooltipClose = void 0;
615
+ setVisible(false);
789
616
  }
790
- useOverlayLifecycle(rendered, localRef, requestClose, keyboard);
791
- if (!rendered) return null;
792
- const mergedStyle = {
793
- ...height !== void 0 ? { display: "flex", flexDirection: "column", height, maxHeight: "calc(100dvh - 48px)" } : {},
794
- ...style,
795
- "--sia-modal-width": typeof width === "number" ? `${width}px` : width
796
- };
797
- const loading = submitting || confirmLoading;
798
- const defaultFooter = /* @__PURE__ */ jsxs3(Fragment2, { children: [
799
- showCancel ? /* @__PURE__ */ jsx3(Button, { ...cancelButtonProps, onClick: requestClose, disabled: loading || cancelButtonProps?.disabled, children: cancelText }) : null,
800
- /* @__PURE__ */ jsx3(Button, { variant: "primary", ...okButtonProps, onClick: handleOk, loading, children: okText })
801
- ] });
802
- return /* @__PURE__ */ jsx3(OverlayPortal, { container: getContainer, children: /* @__PURE__ */ jsx3(
803
- "div",
804
- {
805
- className: `sia-modal-root${centered ? " sia-modal-root--centered" : ""}`,
806
- "data-state": closing ? "closing" : "open",
807
- style: { zIndex },
808
- onMouseDown: (event) => {
809
- if (event.target === event.currentTarget && maskClosable) requestClose();
810
- },
811
- children: /* @__PURE__ */ jsxs3(
812
- "div",
813
- {
814
- ref: setRef,
815
- className: `sia-modal sia-modal--${type} ${className}`.trim(),
816
- style: mergedStyle,
817
- role: "dialog",
818
- "aria-modal": "true",
819
- "aria-labelledby": title ? titleId : void 0,
820
- tabIndex: -1,
821
- ...withTitleTooltip(props),
822
- children: [
823
- /* @__PURE__ */ jsxs3("div", { className: "sia-modal__header", style: height !== void 0 ? { flexShrink: 0 } : void 0, children: [
824
- /* @__PURE__ */ jsxs3("div", { className: "sia-modal__heading", children: [
825
- typeIcons[type] ? /* @__PURE__ */ jsx3("span", { className: "sia-modal__type-icon", "aria-hidden": "true", children: typeIcons[type] }) : null,
826
- title ? /* @__PURE__ */ jsx3("strong", { id: titleId, children: title }) : null
827
- ] }),
828
- 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
829
- ] }),
830
- children !== void 0 ? /* @__PURE__ */ jsx3("div", { className: "sia-modal__body", style: height !== void 0 ? { flex: 1, minHeight: 0, overflow: "auto" } : void 0, children }) : null,
831
- footer !== null ? /* @__PURE__ */ jsxs3("div", { className: "sia-modal__footer", style: height !== void 0 ? { flexShrink: 0 } : void 0, children: [
832
- footer === void 0 && footerExtra != null ? /* @__PURE__ */ jsx3("div", { style: { marginRight: "auto" }, children: footerExtra }) : null,
833
- footer ?? defaultFooter
834
- ] }) : null
835
- ]
836
- }
837
- )
838
- }
839
- ) });
840
- });
841
- function openModal(config) {
842
- if (typeof document === "undefined") return { destroy() {
843
- }, update() {
844
- } };
845
- const container = document.createElement("div");
846
- container.className = "sia-modal-api-host";
847
- document.body.appendChild(container);
848
- const root = createRoot(container);
849
- let currentConfig = config;
850
- let currentOpen = true;
851
- let destroyed = false;
852
- let cleanupTimer;
853
- function cleanup2() {
854
- if (destroyed) return;
855
- destroyed = true;
856
- if (cleanupTimer !== void 0) window.clearTimeout(cleanupTimer);
857
- root.unmount();
858
- container.remove();
859
- modalHandles.delete(handle);
617
+ function clearTimers() {
618
+ window.clearTimeout(enterTimer.current);
619
+ window.clearTimeout(leaveTimer.current);
860
620
  }
861
- function closeWithMotion() {
862
- if (destroyed || !currentOpen) return;
863
- currentOpen = false;
864
- render();
865
- cleanupTimer = window.setTimeout(cleanup2, MODAL_MOTION_DURATION);
621
+ function show(delay = 0) {
622
+ clearTimers();
623
+ if (delay <= 0) openNow();
624
+ else enterTimer.current = window.setTimeout(openNow, delay * 1e3);
866
625
  }
867
- function render() {
868
- root.render(/* @__PURE__ */ jsx3(ModalRoot, { ...currentConfig, open: currentOpen, onOpenChange: (nextOpen) => {
869
- currentConfig.onOpenChange?.(nextOpen);
870
- if (!nextOpen) closeWithMotion();
871
- } }));
626
+ function hide(delay = 0) {
627
+ clearTimers();
628
+ if (delay <= 0) closeNow();
629
+ else leaveTimer.current = window.setTimeout(closeNow, delay * 1e3);
872
630
  }
873
- const handle = {
874
- destroy: closeWithMotion,
875
- update(next) {
876
- if (destroyed) return;
877
- currentConfig = { ...currentConfig, ...next };
878
- render();
631
+ useEffect4(() => {
632
+ if (!visible || !triggers.includes("click")) return;
633
+ const close = (event) => {
634
+ const target = event.target;
635
+ if (!rootRef.current?.contains(target) && !overlayRef.current?.contains(target)) closeNow();
636
+ };
637
+ document.addEventListener("pointerdown", close);
638
+ return () => document.removeEventListener("pointerdown", close);
639
+ }, [triggers.join("|"), visible]);
640
+ useLayoutEffect(() => {
641
+ if (!visible) {
642
+ setPosition(void 0);
643
+ return;
879
644
  }
880
- };
881
- modalHandles.add(handle);
882
- render();
883
- return handle;
645
+ const update = () => {
646
+ if (!rootRef.current || !overlayRef.current) return;
647
+ setPosition(resolveFloatingPosition(rootRef.current.getBoundingClientRect(), overlayRef.current.getBoundingClientRect(), placement));
648
+ };
649
+ update();
650
+ window.addEventListener("resize", update);
651
+ window.addEventListener("scroll", update, true);
652
+ const observer = new ResizeObserver(update);
653
+ if (rootRef.current) observer.observe(rootRef.current);
654
+ if (overlayRef.current) observer.observe(overlayRef.current);
655
+ return () => {
656
+ window.removeEventListener("resize", update);
657
+ window.removeEventListener("scroll", update, true);
658
+ observer.disconnect();
659
+ };
660
+ }, [children, content, placement, visible]);
661
+ useEffect4(() => () => {
662
+ clearTimers();
663
+ if (activeTooltipClose === closeSelfRef.current) activeTooltipClose = void 0;
664
+ }, []);
665
+ const child = cloneElement(children, {
666
+ ...triggers.includes("hover") ? { onMouseEnter: (event) => {
667
+ children.props.onMouseEnter?.(event);
668
+ show(mouseEnterDelay);
669
+ }, onMouseLeave: (event) => {
670
+ children.props.onMouseLeave?.(event);
671
+ hide(mouseLeaveDelay);
672
+ } } : {},
673
+ ...triggers.includes("focus") ? { onFocus: (event) => {
674
+ children.props.onFocus?.(event);
675
+ show();
676
+ }, onBlur: (event) => {
677
+ children.props.onBlur?.(event);
678
+ hide();
679
+ } } : {},
680
+ ...triggers.includes("click") ? { onClick: (event) => {
681
+ children.props.onClick?.(event);
682
+ if (visible) closeNow();
683
+ else openNow();
684
+ } } : {},
685
+ "aria-describedby": visible ? contentId : void 0
686
+ });
687
+ const overlay = visible && content !== null && content !== void 0 && typeof document !== "undefined" ? createPortal(
688
+ /* @__PURE__ */ jsxs2(
689
+ "span",
690
+ {
691
+ ref: overlayRef,
692
+ id: contentId,
693
+ role,
694
+ "data-custom-color": color ? "" : void 0,
695
+ className: `sia-floating__overlay sia-floating__overlay--${position?.placement ?? placement} ${overlayClassName}`.trim(),
696
+ style: {
697
+ "--sia-floating-color": color,
698
+ "--sia-floating-arrow-x": position?.arrowX == null ? void 0 : `${position.arrowX}px`,
699
+ "--sia-floating-arrow-y": position?.arrowY == null ? void 0 : `${position.arrowY}px`,
700
+ top: position?.top ?? 0,
701
+ left: position?.left ?? 0,
702
+ visibility: position ? "visible" : "hidden"
703
+ },
704
+ onMouseEnter: triggers.includes("hover") ? () => show() : void 0,
705
+ onMouseLeave: triggers.includes("hover") ? () => hide(mouseLeaveDelay) : void 0,
706
+ children: [
707
+ content,
708
+ arrow ? /* @__PURE__ */ jsx2("span", { className: "sia-floating__arrow" }) : null
709
+ ]
710
+ }
711
+ ),
712
+ document.body
713
+ ) : null;
714
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
715
+ /* @__PURE__ */ jsx2("span", { ref: rootRef, className: `sia-floating ${className}`.trim(), children: child }),
716
+ overlay
717
+ ] });
884
718
  }
885
- function preset(type, defaults = {}) {
886
- return (config) => openModal({
887
- okText: type === "confirm" ? "\u786E\u5B9A" : "\u77E5\u9053\u4E86",
888
- showCancel: type === "confirm",
889
- closable: type !== "confirm",
890
- maskClosable: type !== "confirm",
891
- ...defaults,
892
- ...config,
893
- type
719
+ function Tooltip(props) {
720
+ return /* @__PURE__ */ jsx2(Floating, { ...props, content: props.title });
721
+ }
722
+ function Popover({ title, content, trigger = "click", overlayClassName = "", ...props }) {
723
+ return /* @__PURE__ */ jsx2(Floating, { ...props, trigger, role: "dialog", overlayClassName: `sia-popover ${overlayClassName}`.trim(), content: /* @__PURE__ */ jsxs2(Fragment, { children: [
724
+ title ? /* @__PURE__ */ jsx2("strong", { className: "sia-popover__title", children: title }) : null,
725
+ /* @__PURE__ */ jsx2("span", { className: "sia-popover__content", children: content })
726
+ ] }) });
727
+ }
728
+ function Timeline({ items = [], mode = "left", pending = false, pendingDot, reverse = false, className = "", ...props }) {
729
+ const values = [...items, ...pending ? [{ children: pending === true ? "\u52A0\u8F7D\u4E2D..." : pending, dot: pendingDot ?? /* @__PURE__ */ jsx2(Icon, { name: "loader", className: "sia-spin-icon" }) }] : []];
730
+ if (reverse) values.reverse();
731
+ return /* @__PURE__ */ jsx2("ul", { className: `sia-timeline sia-timeline--${mode} ${className}`.trim(), ...withTitleTooltip(props), children: values.map((item, index) => {
732
+ const position = item.position ?? (mode === "alternate" ? index % 2 ? "right" : "left" : mode);
733
+ return /* @__PURE__ */ jsxs2("li", { className: `sia-timeline__item sia-timeline__item--${position}`, children: [
734
+ item.label ? /* @__PURE__ */ jsx2("span", { className: "sia-timeline__label", children: item.label }) : null,
735
+ /* @__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 }) }),
736
+ /* @__PURE__ */ jsx2("span", { className: "sia-timeline__content", children: item.children })
737
+ ] }, index);
738
+ }) });
739
+ }
740
+ function collectKeys(nodes, branchOnly = false, result = []) {
741
+ nodes.forEach((node) => {
742
+ if (!branchOnly || node.children?.length) result.push(node.key);
743
+ if (node.children) collectKeys(node.children, branchOnly, result);
894
744
  });
745
+ return result;
895
746
  }
896
- var Modal2 = Object.assign(ModalRoot, {
897
- /** 直接打开实例,返回可用于更新或关闭的操作句柄。 */
898
- open: openModal,
899
- /** 显示普通信息提示。 */
900
- info: preset("info", { title: "\u63D0\u793A" }),
901
- /** 显示成功提示。 */
902
- success: preset("success", { title: "\u64CD\u4F5C\u6210\u529F" }),
903
- /** 显示警告提示。 */
904
- warning: preset("warning", { title: "\u8BF7\u6CE8\u610F" }),
905
- /** 显示错误提示。 */
906
- error: preset("error", { title: "\u64CD\u4F5C\u5931\u8D25" }),
907
- /** 打开确认弹框,支持异步确认;返回 false 可阻止关闭。 */
908
- confirm: preset("confirm", { title: "\u786E\u8BA4\u64CD\u4F5C" }),
909
- /** 关闭由此 API 打开的所有弹层。 */
910
- destroyAll() {
911
- [...modalHandles].forEach((handle) => handle.destroy());
912
- }
913
- });
914
-
915
- // src/components/FloatingScrollbarProvider.tsx
916
- import { useEffect as useEffect6 } from "react";
917
-
918
- // src/components/useScrollbarProximity.ts
919
- import { useEffect as useEffect5 } from "react";
920
- var users = 0;
921
- var cleanup;
922
- function useScrollbarProximity() {
923
- useEffect5(() => {
924
- if (users++ === 0) {
925
- let frame = 0;
926
- let x = -Infinity;
927
- let y = -Infinity;
928
- const nearby = /* @__PURE__ */ new Set();
929
- const clear = () => {
930
- nearby.forEach((track) => track.removeAttribute("data-scrollbar-near"));
931
- nearby.clear();
932
- };
933
- const update = () => {
934
- frame = 0;
935
- const next = /* @__PURE__ */ new Set();
936
- document.querySelectorAll(".sia-scrollbar-track").forEach((track) => {
937
- const rect = track.getBoundingClientRect();
938
- const proximity = Number.parseFloat(getComputedStyle(track).getPropertyValue("--sia-scrollbar-proximity")) || 16;
939
- if (rect.width && rect.height && x >= rect.left - proximity && x <= rect.right + proximity && y >= rect.top - proximity && y <= rect.bottom + proximity) {
940
- next.add(track);
941
- if (!nearby.has(track)) track.setAttribute("data-scrollbar-near", "true");
942
- }
943
- });
944
- nearby.forEach((track) => {
945
- if (!next.has(track)) track.removeAttribute("data-scrollbar-near");
747
+ function Tree({
748
+ treeData,
749
+ selectedKeys,
750
+ defaultSelectedKeys = [],
751
+ expandedKeys,
752
+ defaultExpandedKeys = [],
753
+ defaultExpandAll = false,
754
+ checkedKeys,
755
+ defaultCheckedKeys = [],
756
+ loadingKeys = [],
757
+ checkable = false,
758
+ checkStrictly = false,
759
+ multiple = false,
760
+ selectable = true,
761
+ showLine = false,
762
+ showIcon = false,
763
+ blockNode = false,
764
+ indentSize = 20,
765
+ switcherWidth = 24,
766
+ stickyAncestors = false,
767
+ stickyRowHeight = 34,
768
+ draggable = false,
769
+ loadData,
770
+ onSelect,
771
+ onCheck,
772
+ onExpand,
773
+ onDrop,
774
+ className = "",
775
+ ...props
776
+ }) {
777
+ const [selected, setSelected] = useControllableState({ value: selectedKeys, defaultValue: defaultSelectedKeys });
778
+ const [expanded, setExpanded] = useControllableState({ value: expandedKeys, defaultValue: defaultExpandAll ? collectKeys(treeData, true) : defaultExpandedKeys });
779
+ const [checked, setChecked] = useControllableState({ value: checkedKeys, defaultValue: defaultCheckedKeys });
780
+ const [loading, setLoading] = useState(/* @__PURE__ */ new Set());
781
+ const controlledLoading = useMemo2(() => new Set(loadingKeys), [loadingKeys]);
782
+ const selectedKeySet = useMemo2(() => new Set(selected), [selected]);
783
+ const selectedAncestorKeys = useMemo2(() => {
784
+ const ancestors = /* @__PURE__ */ new Set();
785
+ const visit = (nodes, parentKeys) => {
786
+ nodes.forEach((node) => {
787
+ if (selectedKeySet.has(node.key)) parentKeys.forEach((key) => ancestors.add(key));
788
+ if (node.children?.length) visit(node.children, [...parentKeys, node.key]);
789
+ });
790
+ };
791
+ visit(treeData, []);
792
+ return ancestors;
793
+ }, [selectedKeySet, treeData]);
794
+ const dragNodeRef = useRef2();
795
+ const pointerDragRef = useRef2();
796
+ const suppressClickRef = useRef2(false);
797
+ const [dragOver, setDragOver] = useState();
798
+ const normalizedSwitcherWidth = Math.max(0, switcherWidth);
799
+ async function toggleExpand(node) {
800
+ const nextExpanded = !expanded.includes(node.key);
801
+ if (nextExpanded && node.loaded !== true && !node.children?.length && node.isLeaf === false && loadData) {
802
+ setLoading((keys) => new Set(keys).add(node.key));
803
+ try {
804
+ await loadData(node);
805
+ } finally {
806
+ setLoading((keys) => {
807
+ const next2 = new Set(keys);
808
+ next2.delete(node.key);
809
+ return next2;
946
810
  });
947
- nearby.clear();
948
- next.forEach((track) => nearby.add(track));
949
- };
950
- const move = (event) => {
951
- if (event.pointerType === "touch") return;
952
- x = event.clientX;
953
- y = event.clientY;
954
- if (!frame) frame = requestAnimationFrame(update);
955
- };
956
- const leave = () => {
957
- x = y = -Infinity;
958
- clear();
959
- };
960
- document.addEventListener("pointermove", move, { passive: true });
961
- document.documentElement.addEventListener("pointerleave", leave);
962
- window.addEventListener("blur", leave);
963
- cleanup = () => {
964
- if (frame) cancelAnimationFrame(frame);
965
- document.removeEventListener("pointermove", move);
966
- document.documentElement.removeEventListener("pointerleave", leave);
967
- window.removeEventListener("blur", leave);
968
- clear();
969
- };
970
- }
971
- return () => {
972
- if (--users === 0) {
973
- cleanup?.();
974
- cleanup = void 0;
975
811
  }
976
- };
977
- }, []);
978
- }
979
-
980
- // src/components/FloatingScrollbarProvider.tsx
981
- var scrollableOverflow = /* @__PURE__ */ new Set(["auto", "scroll", "overlay"]);
982
- var clippingOverflow = /* @__PURE__ */ new Set(["auto", "scroll", "overlay", "hidden", "clip"]);
983
- function isRootScroller(target) {
984
- return target === document.scrollingElement || target === document.documentElement;
985
- }
986
- function canScroll(target, axis) {
987
- if (target.matches(".sia-table__viewport, [data-sia-native-scrollbar]")) return false;
988
- if (isRootScroller(target)) {
989
- return axis === "horizontal" ? target.scrollWidth > target.clientWidth + 1 : target.scrollHeight > target.clientHeight + 1;
812
+ }
813
+ const next = nextExpanded ? [...expanded, node.key] : expanded.filter((key) => key !== node.key);
814
+ setExpanded(next);
815
+ onExpand?.([...next], { expanded: nextExpanded, node });
990
816
  }
991
- const style = getComputedStyle(target);
992
- const overflow = axis === "horizontal" ? style.overflowX : style.overflowY;
993
- const hasOverflow = axis === "horizontal" ? target.scrollWidth > target.clientWidth + 1 : target.scrollHeight > target.clientHeight + 1;
994
- return hasOverflow && scrollableOverflow.has(overflow);
995
- }
996
- function visibleRect(target) {
997
- if (isRootScroller(target)) {
998
- return { top: 0, right: window.innerWidth, bottom: window.innerHeight, left: 0, width: window.innerWidth, height: window.innerHeight };
817
+ const checkState = useMemo2(() => getTreeCheckState(treeData, checked, checkStrictly), [treeData, checked, checkStrictly]);
818
+ function check(node) {
819
+ const next = toggleTreeCheck(treeData, checked, node, checkStrictly);
820
+ setChecked([...next.checked]);
821
+ onCheck?.([...next.checked], { checked: next.checkedValue, halfCheckedKeys: [...next.halfChecked], node });
999
822
  }
1000
- const ownRect = target.getBoundingClientRect();
1001
- let top = Math.max(0, ownRect.top);
1002
- let right = Math.min(window.innerWidth, ownRect.right);
1003
- let bottom = Math.min(window.innerHeight, ownRect.bottom);
1004
- let left = Math.max(0, ownRect.left);
1005
- let parent = target.parentElement;
1006
- while (parent && parent !== document.body && parent !== document.documentElement) {
1007
- const style = getComputedStyle(parent);
1008
- const clipX = clippingOverflow.has(style.overflowX);
1009
- const clipY = clippingOverflow.has(style.overflowY);
1010
- if (clipX || clipY) {
1011
- const rect = parent.getBoundingClientRect();
1012
- if (clipX) {
1013
- left = Math.max(left, rect.left);
1014
- right = Math.min(right, rect.right);
1015
- }
1016
- if (clipY) {
1017
- top = Math.max(top, rect.top);
1018
- bottom = Math.min(bottom, rect.bottom);
1019
- }
823
+ function select(node, event) {
824
+ if (!selectable || node.selectable === false || node.disabled) return;
825
+ const exists = selected.includes(node.key);
826
+ const next = multiple ? exists ? selected.filter((key) => key !== node.key) : [...selected, node.key] : exists ? [] : [node.key];
827
+ setSelected(next);
828
+ onSelect?.([...next], { selected: !exists, node, nativeEvent: event });
829
+ }
830
+ function selectForContextMenu(node, event) {
831
+ if (!selectable || node.selectable === false || node.disabled || selected.includes(node.key)) return;
832
+ const next = [node.key];
833
+ setSelected(next);
834
+ onSelect?.(next, { selected: true, node, nativeEvent: event });
835
+ }
836
+ function findNode(key, nodes = treeData) {
837
+ for (const node of nodes) {
838
+ if (String(node.key) === key) return node;
839
+ const child = node.children?.length ? findNode(key, node.children) : void 0;
840
+ if (child) return child;
1020
841
  }
1021
- parent = parent.parentElement;
842
+ return void 0;
1022
843
  }
1023
- return {
1024
- top,
1025
- right,
1026
- bottom,
1027
- left,
1028
- width: Math.max(0, right - left),
1029
- height: Math.max(0, bottom - top)
1030
- };
1031
- }
1032
- function createTrack(axis) {
1033
- const track = document.createElement("div");
1034
- const thumb = document.createElement("div");
1035
- track.className = `sia-scrollbar-track sia-floating-scrollbar sia-floating-scrollbar--${axis}`;
1036
- thumb.className = "sia-floating-scrollbar__thumb";
1037
- track.dataset.axis = axis;
1038
- track.setAttribute("aria-hidden", "true");
1039
- track.append(thumb);
1040
- return { track, thumb };
1041
- }
1042
- function FloatingScrollbarProvider({ disabled = false, targetRef }) {
1043
- useScrollbarProximity();
1044
- useEffect6(() => {
1045
- if (disabled) return;
1046
- const layer = document.createElement("div");
1047
- layer.className = "sia-floating-scrollbar-layer";
1048
- layer.setAttribute("aria-hidden", "true");
1049
- document.body.append(layer);
1050
- const entries = /* @__PURE__ */ new Map();
1051
- let scanFrame = 0;
1052
- let updateFrame = 0;
1053
- const setEntryVisible = (entry, visible) => {
1054
- entry.horizontalTrack.classList.toggle("sia-floating-scrollbar--visible", visible);
1055
- entry.verticalTrack.classList.toggle("sia-floating-scrollbar--visible", visible);
1056
- };
1057
- const showEntry = (entry) => {
1058
- if (entry.hideTimer !== null) window.clearTimeout(entry.hideTimer);
1059
- entry.hideTimer = null;
1060
- setEntryVisible(entry, true);
1061
- };
1062
- const scheduleHide = (entry, delay = 500) => {
1063
- if (entry.hideTimer !== null) window.clearTimeout(entry.hideTimer);
1064
- entry.hideTimer = window.setTimeout(() => {
1065
- entry.hideTimer = null;
1066
- if (!entry.hoveringTarget && !entry.hoveringTrack && !entry.dragging) setEntryVisible(entry, false);
1067
- }, delay);
844
+ function getPointerDropTarget(clientX, clientY, dragNode) {
845
+ const targetElement = document.elementFromPoint(clientX, clientY)?.closest("[data-sia-tree-drop-key]");
846
+ if (!targetElement) return void 0;
847
+ const targetNode = targetElement?.dataset.siaTreeDropKey ? findNode(targetElement.dataset.siaTreeDropKey) : void 0;
848
+ if (!targetNode || targetNode.key === dragNode.key || targetNode.disabled || targetNode.droppable === false) return void 0;
849
+ const bounds = targetElement.getBoundingClientRect();
850
+ const relativeY = bounds.height > 0 ? (clientY - bounds.top) / bounds.height : 0.5;
851
+ const dropPosition = relativeY < 0.25 ? "before" : relativeY > 0.75 ? "after" : "inside";
852
+ return { node: targetNode, dropPosition };
853
+ }
854
+ function beginPointerDrag(node, event) {
855
+ if (event.button !== 0) return;
856
+ pointerDragRef.current = {
857
+ node,
858
+ pointerId: event.pointerId,
859
+ startX: event.clientX,
860
+ startY: event.clientY,
861
+ active: false
1068
862
  };
1069
- const updateEntry = (entry) => {
1070
- const { target, horizontalTrack, horizontalThumb, verticalTrack, verticalThumb } = entry;
1071
- const hasHorizontal = canScroll(target, "horizontal");
1072
- const hasVertical = canScroll(target, "vertical");
1073
- const rect = visibleRect(target);
1074
- const thickness = 10;
1075
- horizontalTrack.hidden = !hasHorizontal || rect.width <= thickness || rect.height <= 0;
1076
- verticalTrack.hidden = !hasVertical || rect.height <= thickness || rect.width <= 0;
1077
- if (!horizontalTrack.hidden) {
1078
- const trackWidth = Math.max(0, rect.width - (hasVertical ? thickness : 0));
1079
- const thumbWidth = Math.max(24, Math.min(trackWidth, trackWidth * target.clientWidth / target.scrollWidth));
1080
- const travel = Math.max(0, trackWidth - thumbWidth);
1081
- const maxScroll = Math.max(1, target.scrollWidth - target.clientWidth);
1082
- horizontalTrack.style.left = `${rect.left}px`;
1083
- horizontalTrack.style.top = `${rect.bottom - thickness}px`;
1084
- horizontalTrack.style.width = `${trackWidth}px`;
1085
- horizontalTrack.style.height = `${thickness}px`;
1086
- horizontalThumb.style.width = `${thumbWidth}px`;
1087
- horizontalThumb.style.transform = `translate3d(${travel * target.scrollLeft / maxScroll}px, 0, 0)`;
863
+ event.currentTarget.setPointerCapture(event.pointerId);
864
+ }
865
+ function movePointerDrag(event) {
866
+ const pointerDrag = pointerDragRef.current;
867
+ if (!pointerDrag || pointerDrag.pointerId !== event.pointerId) return;
868
+ if (!pointerDrag.active && Math.hypot(event.clientX - pointerDrag.startX, event.clientY - pointerDrag.startY) < 5) return;
869
+ pointerDrag.active = true;
870
+ dragNodeRef.current = pointerDrag.node;
871
+ event.preventDefault();
872
+ const target = getPointerDropTarget(event.clientX, event.clientY, pointerDrag.node);
873
+ setDragOver(target ? { key: target.node.key, position: target.dropPosition } : void 0);
874
+ }
875
+ function finishPointerDrag(event) {
876
+ const pointerDrag = pointerDragRef.current;
877
+ if (!pointerDrag || pointerDrag.pointerId !== event.pointerId) return;
878
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
879
+ if (pointerDrag.active) {
880
+ event.preventDefault();
881
+ suppressClickRef.current = true;
882
+ const target = getPointerDropTarget(event.clientX, event.clientY, pointerDrag.node);
883
+ if (target) onDrop?.({ dragNode: pointerDrag.node, node: target.node, dropPosition: target.dropPosition });
884
+ window.setTimeout(() => {
885
+ suppressClickRef.current = false;
886
+ }, 0);
887
+ }
888
+ pointerDragRef.current = void 0;
889
+ dragNodeRef.current = void 0;
890
+ setDragOver(void 0);
891
+ }
892
+ function cancelPointerDrag(event) {
893
+ const pointerDrag = pointerDragRef.current;
894
+ if (!pointerDrag || pointerDrag.pointerId !== event.pointerId) return;
895
+ pointerDragRef.current = void 0;
896
+ dragNodeRef.current = void 0;
897
+ setDragOver(void 0);
898
+ }
899
+ function render(nodes, depth = 0) {
900
+ return nodes.map((node) => {
901
+ const open = expanded.includes(node.key);
902
+ const isSelected = selected.includes(node.key);
903
+ const isSelectedAncestor = selectedAncestorKeys.has(node.key);
904
+ const isChecked = checkState.checked.has(node.key);
905
+ const isHalfChecked = checkState.halfChecked.has(node.key);
906
+ const hasChildren = Boolean(node.children?.length || node.isLeaf === false);
907
+ const nodeLoading = loading.has(node.key) || controlledLoading.has(node.key);
908
+ const sticky = stickyAncestors && hasChildren && open;
909
+ const canDrag = draggable && node.draggable !== false && !node.disabled;
910
+ const { className: rowClassName = "", style: rowStyle, onClick: onRowClick, ...rowProps } = node.rowProps ?? {};
911
+ const unloaded = hasChildren && node.loaded === false;
912
+ const dropPosition = dragOver?.key === node.key ? dragOver.position : void 0;
913
+ 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: [
914
+ /* @__PURE__ */ jsx2(
915
+ "div",
916
+ {
917
+ ...withTitleTooltip(rowProps),
918
+ className: `sia-tree__row${blockNode ? " sia-tree__row--block" : ""}${sticky ? " sia-tree__row--sticky" : ""} ${rowClassName}`.trim(),
919
+ style: {
920
+ ...rowStyle,
921
+ paddingInlineStart: depth * Math.max(0, indentSize),
922
+ top: sticky ? depth * Math.max(1, stickyRowHeight) : rowStyle?.top,
923
+ zIndex: sticky ? Math.max(1, 100 - depth) : rowStyle?.zIndex
924
+ },
925
+ "aria-busy": nodeLoading || void 0,
926
+ "data-sia-tree-drop-key": String(node.key),
927
+ "data-sia-tree-draggable": canDrag || void 0,
928
+ onClick: (event) => {
929
+ onRowClick?.(event);
930
+ if (event.defaultPrevented || suppressClickRef.current || node.disabled) return;
931
+ select(node, event);
932
+ if (hasChildren) void toggleExpand(node);
933
+ },
934
+ children: (() => {
935
+ const content = /* @__PURE__ */ jsxs2("span", { className: "sia-tree__context-content", children: [
936
+ 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) => {
937
+ event.stopPropagation();
938
+ void toggleExpand(node);
939
+ }, 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" }),
940
+ checkable || node.checkable ? /* @__PURE__ */ jsx2("span", { onClick: (event) => event.stopPropagation(), children: /* @__PURE__ */ jsx2(Checkbox2, { checked: isChecked, indeterminate: isHalfChecked, disabled: node.disabled || node.disableCheckbox, onChange: () => check(node) }) }) : null,
941
+ node.prefix ? /* @__PURE__ */ jsx2("span", { className: "sia-tree__prefix", onClick: (event) => event.stopPropagation(), children: node.prefix }) : null,
942
+ showIcon ? /* @__PURE__ */ jsx2("span", { className: "sia-tree__icon", children: node.icon ?? /* @__PURE__ */ jsx2(Icon, { name: hasChildren ? "folder" : "file", size: 15 }) }) : null,
943
+ /* @__PURE__ */ jsx2(
944
+ "button",
945
+ {
946
+ type: "button",
947
+ className: "sia-tree__title",
948
+ disabled: node.disabled,
949
+ onPointerDown: canDrag ? (event) => beginPointerDrag(node, event) : void 0,
950
+ onPointerMove: canDrag ? movePointerDrag : void 0,
951
+ onPointerUp: canDrag ? finishPointerDrag : void 0,
952
+ onPointerCancel: canDrag ? cancelPointerDrag : void 0,
953
+ children: node.title
954
+ }
955
+ ),
956
+ nodeLoading ? /* @__PURE__ */ jsx2("span", { className: "sia-tree__loading-text", role: "status", children: node.loadingText ?? "\u6B63\u5728\u52A0\u8F7D\u2026" }) : null,
957
+ node.extra ? /* @__PURE__ */ jsx2("span", { className: "sia-tree__extra", onClick: (event) => event.stopPropagation(), children: node.extra }) : null
958
+ ] });
959
+ return node.contextMenu ? /* @__PURE__ */ jsx2(
960
+ Dropdown,
961
+ {
962
+ className: "sia-tree__context-trigger",
963
+ popupClassName: node.contextMenuPopupClassName,
964
+ trigger: ["contextMenu"],
965
+ menu: node.contextMenu,
966
+ onContextMenu: (event) => selectForContextMenu(node, event),
967
+ children: content
968
+ }
969
+ ) : content;
970
+ })()
971
+ }
972
+ ),
973
+ open && node.children?.length ? /* @__PURE__ */ jsx2("div", { className: "sia-tree__children", role: "group", children: render(node.children, depth + 1) }) : null
974
+ ] }, node.key);
975
+ });
976
+ }
977
+ 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, ...withTitleTooltip(props), children: render(treeData) });
978
+ }
979
+
980
+ // src/components/Modal.tsx
981
+ import { forwardRef as forwardRef2, useEffect as useEffect6, useId as useId3, useRef as useRef4, useState as useState2 } from "react";
982
+ import { createRoot } from "react-dom/client";
983
+
984
+ // src/components/Overlay.tsx
985
+ import { useEffect as useEffect5, useRef as useRef3 } from "react";
986
+ import { createPortal as createPortal2 } from "react-dom";
987
+ var bodyLockCount = 0;
988
+ var previousBodyOverflow = "";
989
+ var overlayStack = [];
990
+ function lockBodyScroll() {
991
+ if (bodyLockCount === 0) {
992
+ previousBodyOverflow = document.body.style.overflow;
993
+ document.body.style.overflow = "hidden";
994
+ }
995
+ bodyLockCount += 1;
996
+ return () => {
997
+ bodyLockCount = Math.max(0, bodyLockCount - 1);
998
+ if (bodyLockCount === 0) document.body.style.overflow = previousBodyOverflow;
999
+ };
1000
+ }
1001
+ function focusableElements(container) {
1002
+ return [...container.querySelectorAll(
1003
+ 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
1004
+ )].filter((element) => !element.hidden && element.getAttribute("aria-hidden") !== "true");
1005
+ }
1006
+ function useOverlayLifecycle(open, panelRef, onRequestClose, keyboard = true) {
1007
+ const overlayIdRef = useRef3(/* @__PURE__ */ Symbol("sia-overlay"));
1008
+ const closeRef = useRef3(onRequestClose);
1009
+ closeRef.current = onRequestClose;
1010
+ useEffect5(() => {
1011
+ if (!open || typeof document === "undefined") return void 0;
1012
+ const overlayId = overlayIdRef.current;
1013
+ const previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
1014
+ overlayStack.push(overlayId);
1015
+ const unlockBody = lockBodyScroll();
1016
+ const focusTimer = window.setTimeout(() => {
1017
+ const panel = panelRef.current;
1018
+ if (!panel) return;
1019
+ const [firstFocusable] = focusableElements(panel);
1020
+ (firstFocusable ?? panel).focus({ preventScroll: true });
1021
+ }, 0);
1022
+ function handleKeyDown(event) {
1023
+ if (overlayStack.at(-1) !== overlayId) return;
1024
+ if (event.key === "Escape" && keyboard) {
1025
+ event.preventDefault();
1026
+ closeRef.current();
1027
+ return;
1088
1028
  }
1089
- if (!verticalTrack.hidden) {
1090
- const trackHeight = Math.max(0, rect.height - (hasHorizontal ? thickness : 0));
1091
- const thumbHeight = Math.max(24, Math.min(trackHeight, trackHeight * target.clientHeight / target.scrollHeight));
1092
- const travel = Math.max(0, trackHeight - thumbHeight);
1093
- const maxScroll = Math.max(1, target.scrollHeight - target.clientHeight);
1094
- verticalTrack.style.left = `${rect.right - thickness}px`;
1095
- verticalTrack.style.top = `${rect.top}px`;
1096
- verticalTrack.style.width = `${thickness}px`;
1097
- verticalTrack.style.height = `${trackHeight}px`;
1098
- verticalThumb.style.height = `${thumbHeight}px`;
1099
- verticalThumb.style.transform = `translate3d(0, ${travel * target.scrollTop / maxScroll}px, 0)`;
1029
+ if (event.key !== "Tab" || !panelRef.current) return;
1030
+ const focusable = focusableElements(panelRef.current);
1031
+ if (focusable.length === 0) {
1032
+ event.preventDefault();
1033
+ panelRef.current.focus();
1034
+ return;
1100
1035
  }
1101
- };
1102
- const scheduleUpdateAll = () => {
1103
- if (updateFrame) return;
1104
- updateFrame = window.requestAnimationFrame(() => {
1105
- updateFrame = 0;
1106
- entries.forEach(updateEntry);
1107
- });
1108
- };
1109
- const bindTrackDrag = (entry, axis, track, thumb) => {
1110
- const target = entry.target;
1111
- const onTrackPointerEnter = () => {
1112
- entry.hoveringTrack = true;
1113
- showEntry(entry);
1114
- };
1115
- const onTrackPointerLeave = () => {
1116
- entry.hoveringTrack = false;
1117
- scheduleHide(entry);
1118
- };
1119
- const onTrackPointerDown = (event) => {
1120
- if (event.target === thumb || event.button !== 0) return;
1036
+ const first = focusable[0];
1037
+ const last = focusable.at(-1);
1038
+ if (event.shiftKey && document.activeElement === first) {
1121
1039
  event.preventDefault();
1122
- event.stopPropagation();
1123
- const trackRect = track.getBoundingClientRect();
1124
- const thumbRect = thumb.getBoundingClientRect();
1125
- const trackLength = axis === "vertical" ? trackRect.height : trackRect.width;
1126
- const thumbLength = axis === "vertical" ? thumbRect.height : thumbRect.width;
1127
- const pointer = axis === "vertical" ? event.clientY - trackRect.top : event.clientX - trackRect.left;
1128
- const ratio = Math.max(0, Math.min(1, (pointer - thumbLength / 2) / Math.max(1, trackLength - thumbLength)));
1129
- if (axis === "vertical") target.scrollTop = ratio * (target.scrollHeight - target.clientHeight);
1130
- else target.scrollLeft = ratio * (target.scrollWidth - target.clientWidth);
1131
- };
1132
- const onThumbPointerDown = (event) => {
1133
- if (event.button !== 0) return;
1040
+ last.focus();
1041
+ } else if (!event.shiftKey && document.activeElement === last) {
1134
1042
  event.preventDefault();
1135
- event.stopPropagation();
1136
- entry.dragging = true;
1137
- showEntry(entry);
1138
- thumb.setPointerCapture(event.pointerId);
1139
- const startPointer = axis === "vertical" ? event.clientY : event.clientX;
1140
- const startScroll = axis === "vertical" ? target.scrollTop : target.scrollLeft;
1141
- const trackRect = track.getBoundingClientRect();
1142
- const thumbRect = thumb.getBoundingClientRect();
1143
- const trackLength = axis === "vertical" ? trackRect.height : trackRect.width;
1144
- const thumbLength = axis === "vertical" ? thumbRect.height : thumbRect.width;
1145
- const maxScroll = axis === "vertical" ? target.scrollHeight - target.clientHeight : target.scrollWidth - target.clientWidth;
1146
- const travel = Math.max(1, trackLength - thumbLength);
1147
- const onPointerMove = (moveEvent) => {
1148
- if (moveEvent.pointerId !== event.pointerId) return;
1149
- const pointer = axis === "vertical" ? moveEvent.clientY : moveEvent.clientX;
1150
- const nextScroll = startScroll + (pointer - startPointer) * maxScroll / travel;
1151
- if (axis === "vertical") target.scrollTop = nextScroll;
1152
- else target.scrollLeft = nextScroll;
1153
- };
1154
- const onPointerUp = (upEvent) => {
1155
- if (upEvent.pointerId !== event.pointerId) return;
1156
- entry.dragging = false;
1157
- thumb.releasePointerCapture(event.pointerId);
1158
- window.removeEventListener("pointermove", onPointerMove);
1159
- window.removeEventListener("pointerup", onPointerUp);
1160
- window.removeEventListener("pointercancel", onPointerUp);
1161
- scheduleHide(entry);
1162
- };
1163
- window.addEventListener("pointermove", onPointerMove);
1164
- window.addEventListener("pointerup", onPointerUp);
1165
- window.addEventListener("pointercancel", onPointerUp);
1166
- };
1167
- track.addEventListener("pointerenter", onTrackPointerEnter);
1168
- track.addEventListener("pointerleave", onTrackPointerLeave);
1169
- track.addEventListener("pointerdown", onTrackPointerDown);
1170
- thumb.addEventListener("pointerdown", onThumbPointerDown);
1171
- return () => {
1172
- track.removeEventListener("pointerenter", onTrackPointerEnter);
1173
- track.removeEventListener("pointerleave", onTrackPointerLeave);
1174
- track.removeEventListener("pointerdown", onTrackPointerDown);
1175
- thumb.removeEventListener("pointerdown", onThumbPointerDown);
1176
- };
1177
- };
1178
- const resizeObserver = new ResizeObserver((observedEntries) => {
1179
- observedEntries.forEach(({ target }) => {
1180
- const entry = entries.get(target);
1181
- if (entry) updateEntry(entry);
1182
- });
1183
- });
1184
- const addTarget = (target) => {
1185
- if (entries.has(target)) return;
1186
- const horizontal = createTrack("horizontal");
1187
- const vertical = createTrack("vertical");
1188
- layer.append(horizontal.track, vertical.track);
1189
- const onPointerEnter = () => {
1190
- const entry2 = entries.get(target);
1191
- if (!entry2) return;
1192
- entry2.hoveringTarget = true;
1193
- showEntry(entry2);
1194
- };
1195
- const onPointerLeave = () => {
1196
- const entry2 = entries.get(target);
1197
- if (!entry2) return;
1198
- entry2.hoveringTarget = false;
1199
- scheduleHide(entry2);
1200
- };
1201
- const onScroll = () => {
1202
- const entry2 = entries.get(target);
1203
- if (!entry2) return;
1204
- updateEntry(entry2);
1205
- showEntry(entry2);
1206
- scheduleHide(entry2, 700);
1207
- };
1208
- target.addEventListener("pointerenter", onPointerEnter);
1209
- target.addEventListener("pointerleave", onPointerLeave);
1210
- target.addEventListener("scroll", onScroll, { passive: true });
1211
- const entry = {
1212
- target,
1213
- horizontalTrack: horizontal.track,
1214
- horizontalThumb: horizontal.thumb,
1215
- verticalTrack: vertical.track,
1216
- verticalThumb: vertical.thumb,
1217
- hideTimer: null,
1218
- hoveringTarget: target.matches(":hover"),
1219
- hoveringTrack: false,
1220
- dragging: false,
1221
- cleanup: () => void 0
1222
- };
1223
- const cleanupHorizontal = bindTrackDrag(entry, "horizontal", horizontal.track, horizontal.thumb);
1224
- const cleanupVertical = bindTrackDrag(entry, "vertical", vertical.track, vertical.thumb);
1225
- entry.cleanup = () => {
1226
- if (entry.hideTimer !== null) window.clearTimeout(entry.hideTimer);
1227
- cleanupHorizontal();
1228
- cleanupVertical();
1229
- target.removeEventListener("pointerenter", onPointerEnter);
1230
- target.removeEventListener("pointerleave", onPointerLeave);
1231
- target.removeEventListener("scroll", onScroll);
1232
- resizeObserver.unobserve(target);
1233
- target.removeAttribute("data-sia-floating-scrollbar");
1234
- horizontal.track.remove();
1235
- vertical.track.remove();
1236
- };
1237
- entries.set(target, entry);
1238
- target.setAttribute("data-sia-floating-scrollbar", "");
1239
- resizeObserver.observe(target);
1240
- updateEntry(entry);
1241
- if (entry.hoveringTarget) showEntry(entry);
1242
- };
1243
- const removeTarget = (target) => {
1244
- const entry = entries.get(target);
1245
- if (!entry) return;
1246
- entry.cleanup();
1247
- entries.delete(target);
1248
- };
1249
- const scanTargets = () => {
1250
- scanFrame = 0;
1251
- const candidates = (targetRef ? [targetRef.current] : [document.scrollingElement, ...document.querySelectorAll("*")]).filter((target) => target instanceof HTMLElement);
1252
- const nextTargets = /* @__PURE__ */ new Set();
1253
- candidates.forEach((target) => {
1254
- if (canScroll(target, "horizontal") || canScroll(target, "vertical")) {
1255
- nextTargets.add(target);
1256
- if (entries.has(target) || !target.hasAttribute("data-sia-floating-scrollbar")) addTarget(target);
1257
- }
1258
- });
1259
- entries.forEach((_entry, target) => {
1260
- if (!target.isConnected || !nextTargets.has(target)) removeTarget(target);
1261
- else updateEntry(_entry);
1262
- });
1263
- };
1264
- const scheduleScan = () => {
1265
- if (scanFrame) return;
1266
- scanFrame = window.requestAnimationFrame(scanTargets);
1267
- };
1268
- const mutationObserver = new MutationObserver((mutations) => {
1269
- if (mutations.some((mutation) => !layer.contains(mutation.target))) scheduleScan();
1270
- });
1271
- mutationObserver.observe(document.documentElement, { subtree: true, childList: true, attributes: true });
1272
- window.addEventListener("resize", scheduleScan);
1273
- window.addEventListener("scroll", scheduleUpdateAll, true);
1274
- scheduleScan();
1043
+ first.focus();
1044
+ }
1045
+ }
1046
+ document.addEventListener("keydown", handleKeyDown);
1275
1047
  return () => {
1276
- mutationObserver.disconnect();
1277
- resizeObserver.disconnect();
1278
- window.removeEventListener("resize", scheduleScan);
1279
- window.removeEventListener("scroll", scheduleUpdateAll, true);
1280
- if (scanFrame) window.cancelAnimationFrame(scanFrame);
1281
- if (updateFrame) window.cancelAnimationFrame(updateFrame);
1282
- entries.forEach((entry) => entry.cleanup());
1283
- entries.clear();
1284
- layer.remove();
1048
+ window.clearTimeout(focusTimer);
1049
+ document.removeEventListener("keydown", handleKeyDown);
1050
+ const index = overlayStack.lastIndexOf(overlayId);
1051
+ if (index >= 0) overlayStack.splice(index, 1);
1052
+ unlockBody();
1053
+ previouslyFocused?.focus({ preventScroll: true });
1285
1054
  };
1286
- }, [disabled, targetRef]);
1287
- return null;
1055
+ }, [keyboard, open, panelRef]);
1056
+ }
1057
+ function OverlayPortal({ children, container }) {
1058
+ if (typeof document === "undefined") return null;
1059
+ const target = typeof container === "function" ? container() : container ?? document.body;
1060
+ return createPortal2(children, target);
1061
+ }
1062
+
1063
+ // src/components/Modal.tsx
1064
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1065
+ var modalHandles = /* @__PURE__ */ new Set();
1066
+ var MODAL_MOTION_DURATION = 220;
1067
+ var typeIcons = {
1068
+ info: /* @__PURE__ */ jsx3(Icon, { name: "info", size: 23 }),
1069
+ success: /* @__PURE__ */ jsx3(Icon, { name: "circle-check", size: 23 }),
1070
+ warning: /* @__PURE__ */ jsx3(Icon, { name: "warning", size: 23 }),
1071
+ error: /* @__PURE__ */ jsx3(Icon, { name: "circle-close", size: 23 }),
1072
+ confirm: /* @__PURE__ */ jsx3(Icon, { name: "question", size: 23 })
1073
+ };
1074
+ var ModalRoot = forwardRef2(function Modal({
1075
+ open,
1076
+ title,
1077
+ children,
1078
+ footer,
1079
+ footerExtra,
1080
+ type = "default",
1081
+ okText = "\u786E\u5B9A",
1082
+ cancelText = "\u53D6\u6D88",
1083
+ showCancel = type === "default" || type === "confirm",
1084
+ confirmLoading = false,
1085
+ okButtonProps,
1086
+ cancelButtonProps,
1087
+ closable = true,
1088
+ maskClosable = true,
1089
+ keyboard = true,
1090
+ centered = false,
1091
+ width = 520,
1092
+ height,
1093
+ zIndex = 1e3,
1094
+ getContainer,
1095
+ onOk,
1096
+ onCancel,
1097
+ onOpenChange,
1098
+ afterOpenChange,
1099
+ className = "",
1100
+ style,
1101
+ ...props
1102
+ }, forwardedRef) {
1103
+ const localRef = useRef4(null);
1104
+ const titleId = useId3();
1105
+ const [submitting, setSubmitting] = useState2(false);
1106
+ const [rendered, setRendered] = useState2(open);
1107
+ const [closing, setClosing] = useState2(false);
1108
+ const afterOpenChangeRef = useRef4(afterOpenChange);
1109
+ afterOpenChangeRef.current = afterOpenChange;
1110
+ const submitLock = useRef4(false);
1111
+ useEffect6(() => {
1112
+ if (!open || !rendered) return void 0;
1113
+ const timer = window.setTimeout(() => afterOpenChangeRef.current?.(true), MODAL_MOTION_DURATION);
1114
+ return () => window.clearTimeout(timer);
1115
+ }, [open, rendered]);
1116
+ useEffect6(() => {
1117
+ if (open) {
1118
+ setRendered(true);
1119
+ setClosing(false);
1120
+ return void 0;
1121
+ }
1122
+ if (!rendered) return void 0;
1123
+ setClosing(true);
1124
+ const timer = window.setTimeout(() => {
1125
+ setRendered(false);
1126
+ setClosing(false);
1127
+ afterOpenChangeRef.current?.(false);
1128
+ }, MODAL_MOTION_DURATION);
1129
+ return () => window.clearTimeout(timer);
1130
+ }, [open, rendered]);
1131
+ function setRef(node) {
1132
+ localRef.current = node;
1133
+ if (typeof forwardedRef === "function") forwardedRef(node);
1134
+ else if (forwardedRef) forwardedRef.current = node;
1135
+ }
1136
+ function requestClose() {
1137
+ if (submitting || confirmLoading) return;
1138
+ onCancel?.();
1139
+ onOpenChange?.(false);
1140
+ }
1141
+ async function handleOk() {
1142
+ if (submitLock.current || confirmLoading) return;
1143
+ submitLock.current = true;
1144
+ if (!onOk) {
1145
+ onOpenChange?.(false);
1146
+ submitLock.current = false;
1147
+ return;
1148
+ }
1149
+ try {
1150
+ const result = onOk();
1151
+ if (result instanceof Promise) {
1152
+ setSubmitting(true);
1153
+ const resolved = await result;
1154
+ if (resolved !== false) onOpenChange?.(false);
1155
+ } else if (result !== false) {
1156
+ onOpenChange?.(false);
1157
+ }
1158
+ } catch {
1159
+ return;
1160
+ } finally {
1161
+ submitLock.current = false;
1162
+ setSubmitting(false);
1163
+ }
1164
+ }
1165
+ useOverlayLifecycle(rendered, localRef, requestClose, keyboard);
1166
+ if (!rendered) return null;
1167
+ const mergedStyle = {
1168
+ ...height !== void 0 ? { display: "flex", flexDirection: "column", height, maxHeight: "calc(100dvh - 48px)" } : {},
1169
+ ...style,
1170
+ "--sia-modal-width": typeof width === "number" ? `${width}px` : width
1171
+ };
1172
+ const loading = submitting || confirmLoading;
1173
+ const defaultFooter = /* @__PURE__ */ jsxs3(Fragment2, { children: [
1174
+ showCancel ? /* @__PURE__ */ jsx3(Button, { ...cancelButtonProps, onClick: requestClose, disabled: loading || cancelButtonProps?.disabled, children: cancelText }) : null,
1175
+ /* @__PURE__ */ jsx3(Button, { variant: "primary", ...okButtonProps, onClick: handleOk, loading, children: okText })
1176
+ ] });
1177
+ return /* @__PURE__ */ jsx3(OverlayPortal, { container: getContainer, children: /* @__PURE__ */ jsx3(
1178
+ "div",
1179
+ {
1180
+ className: `sia-modal-root${centered ? " sia-modal-root--centered" : ""}`,
1181
+ "data-state": closing ? "closing" : "open",
1182
+ style: { zIndex },
1183
+ onMouseDown: (event) => {
1184
+ if (event.target === event.currentTarget && maskClosable) requestClose();
1185
+ },
1186
+ children: /* @__PURE__ */ jsxs3(
1187
+ "div",
1188
+ {
1189
+ ref: setRef,
1190
+ className: `sia-modal sia-modal--${type} ${className}`.trim(),
1191
+ style: mergedStyle,
1192
+ role: "dialog",
1193
+ "aria-modal": "true",
1194
+ "aria-labelledby": title ? titleId : void 0,
1195
+ tabIndex: -1,
1196
+ ...withTitleTooltip(props),
1197
+ children: [
1198
+ /* @__PURE__ */ jsxs3("div", { className: "sia-modal__header", style: height !== void 0 ? { flexShrink: 0 } : void 0, children: [
1199
+ /* @__PURE__ */ jsxs3("div", { className: "sia-modal__heading", children: [
1200
+ typeIcons[type] ? /* @__PURE__ */ jsx3("span", { className: "sia-modal__type-icon", "aria-hidden": "true", children: typeIcons[type] }) : null,
1201
+ title ? /* @__PURE__ */ jsx3("strong", { id: titleId, children: title }) : null
1202
+ ] }),
1203
+ 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
1204
+ ] }),
1205
+ children !== void 0 ? /* @__PURE__ */ jsx3("div", { className: "sia-modal__body", style: height !== void 0 ? { flex: 1, minHeight: 0, overflow: "auto" } : void 0, children }) : null,
1206
+ footer !== null ? /* @__PURE__ */ jsxs3("div", { className: "sia-modal__footer", style: height !== void 0 ? { flexShrink: 0 } : void 0, children: [
1207
+ footer === void 0 && footerExtra != null ? /* @__PURE__ */ jsx3("div", { style: { marginRight: "auto" }, children: footerExtra }) : null,
1208
+ footer ?? defaultFooter
1209
+ ] }) : null
1210
+ ]
1211
+ }
1212
+ )
1213
+ }
1214
+ ) });
1215
+ });
1216
+ function openModal(config) {
1217
+ if (typeof document === "undefined") return { destroy() {
1218
+ }, update() {
1219
+ } };
1220
+ const container = document.createElement("div");
1221
+ container.className = "sia-modal-api-host";
1222
+ document.body.appendChild(container);
1223
+ const root = createRoot(container);
1224
+ let currentConfig = config;
1225
+ let currentOpen = true;
1226
+ let destroyed = false;
1227
+ let cleanupTimer;
1228
+ function cleanup2() {
1229
+ if (destroyed) return;
1230
+ destroyed = true;
1231
+ if (cleanupTimer !== void 0) window.clearTimeout(cleanupTimer);
1232
+ root.unmount();
1233
+ container.remove();
1234
+ modalHandles.delete(handle);
1235
+ }
1236
+ function closeWithMotion() {
1237
+ if (destroyed || !currentOpen) return;
1238
+ currentOpen = false;
1239
+ render();
1240
+ cleanupTimer = window.setTimeout(cleanup2, MODAL_MOTION_DURATION);
1241
+ }
1242
+ function render() {
1243
+ root.render(/* @__PURE__ */ jsx3(ModalRoot, { ...currentConfig, open: currentOpen, onOpenChange: (nextOpen) => {
1244
+ currentConfig.onOpenChange?.(nextOpen);
1245
+ if (!nextOpen) closeWithMotion();
1246
+ } }));
1247
+ }
1248
+ const handle = {
1249
+ destroy: closeWithMotion,
1250
+ update(next) {
1251
+ if (destroyed) return;
1252
+ currentConfig = { ...currentConfig, ...next };
1253
+ render();
1254
+ }
1255
+ };
1256
+ modalHandles.add(handle);
1257
+ render();
1258
+ return handle;
1259
+ }
1260
+ function preset(type, defaults = {}) {
1261
+ return (config) => openModal({
1262
+ okText: type === "confirm" ? "\u786E\u5B9A" : "\u77E5\u9053\u4E86",
1263
+ showCancel: type === "confirm",
1264
+ closable: type !== "confirm",
1265
+ maskClosable: type !== "confirm",
1266
+ ...defaults,
1267
+ ...config,
1268
+ type
1269
+ });
1288
1270
  }
1271
+ var Modal2 = Object.assign(ModalRoot, {
1272
+ /** 直接打开实例,返回可用于更新或关闭的操作句柄。 */
1273
+ open: openModal,
1274
+ /** 显示普通信息提示。 */
1275
+ info: preset("info", { title: "\u63D0\u793A" }),
1276
+ /** 显示成功提示。 */
1277
+ success: preset("success", { title: "\u64CD\u4F5C\u6210\u529F" }),
1278
+ /** 显示警告提示。 */
1279
+ warning: preset("warning", { title: "\u8BF7\u6CE8\u610F" }),
1280
+ /** 显示错误提示。 */
1281
+ error: preset("error", { title: "\u64CD\u4F5C\u5931\u8D25" }),
1282
+ /** 打开确认弹框,支持异步确认;返回 false 可阻止关闭。 */
1283
+ confirm: preset("confirm", { title: "\u786E\u8BA4\u64CD\u4F5C" }),
1284
+ /** 关闭由此 API 打开的所有弹层。 */
1285
+ destroyAll() {
1286
+ [...modalHandles].forEach((handle) => handle.destroy());
1287
+ }
1288
+ });
1289
1289
 
1290
1290
  // src/components/SelectionPanel.tsx
1291
1291
  import { useEffect as useEffect7, useId as useId4, useMemo as useMemo3, useRef as useRef5, useState as useState3 } from "react";
@@ -2006,6 +2006,8 @@ var Select = forwardRef3(function Select2({
2006
2006
  });
2007
2007
 
2008
2008
  export {
2009
+ useScrollbarProximity,
2010
+ FloatingScrollbarProvider,
2009
2011
  Checkbox2 as Checkbox,
2010
2012
  Tooltip,
2011
2013
  Popover,
@@ -2014,8 +2016,6 @@ export {
2014
2016
  useOverlayLifecycle,
2015
2017
  OverlayPortal,
2016
2018
  Modal2 as Modal,
2017
- useScrollbarProximity,
2018
- FloatingScrollbarProvider,
2019
2019
  SelectionPanel,
2020
2020
  OverflowTags,
2021
2021
  Select