@sia.soul/sia-react-ui 0.1.63 → 0.1.64

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