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

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