flowcloudai-ui 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,24 +1,2255 @@
1
- // src/Button.tsx
1
+ // src/ThemeProvider.tsx
2
+ import { createContext, useContext, useEffect, useState } from "react";
2
3
  import { jsx } from "react/jsx-runtime";
3
- function Button({ children, style, ...props }) {
4
- return /* @__PURE__ */ jsx(
4
+ var ThemeContext = createContext(null);
5
+ function useTheme() {
6
+ const ctx = useContext(ThemeContext);
7
+ if (!ctx) throw new Error("useTheme must be used within <ThemeProvider>");
8
+ return ctx;
9
+ }
10
+ function getSystemTheme() {
11
+ return typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
12
+ }
13
+ function ThemeProvider({ children, defaultTheme = "system", target }) {
14
+ const [theme, setTheme] = useState(defaultTheme);
15
+ const [systemTheme, setSystemTheme] = useState(getSystemTheme);
16
+ const resolvedTheme = theme === "system" ? systemTheme : theme;
17
+ useEffect(() => {
18
+ const el = target ?? document.documentElement;
19
+ el.classList.remove("theme-light", "theme-dark");
20
+ el.classList.add(`theme-${resolvedTheme}`);
21
+ el.setAttribute("data-theme", resolvedTheme);
22
+ if (resolvedTheme === "dark") {
23
+ document.body.style.backgroundColor = "#0F0F0F";
24
+ document.body.style.color = "#E8E8E6";
25
+ } else {
26
+ document.body.style.backgroundColor = "";
27
+ document.body.style.color = "";
28
+ }
29
+ }, [resolvedTheme, target]);
30
+ useEffect(() => {
31
+ if (theme !== "system") return;
32
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
33
+ const handler = () => setSystemTheme(mq.matches ? "dark" : "light");
34
+ mq.addEventListener("change", handler);
35
+ return () => mq.removeEventListener("change", handler);
36
+ }, [theme]);
37
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: { theme, resolvedTheme, setTheme }, children });
38
+ }
39
+
40
+ // src/components/Button/Button.tsx
41
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
42
+ function Button({
43
+ variant = "primary",
44
+ size = "md",
45
+ disabled = false,
46
+ loading = false,
47
+ block = false,
48
+ circle = false,
49
+ iconOnly = false,
50
+ iconLeft,
51
+ iconRight,
52
+ className,
53
+ children,
54
+ ...props
55
+ }) {
56
+ const classNames = [
57
+ "fc-btn",
58
+ `fc-btn--${variant}`,
59
+ `fc-btn--${size}`,
60
+ block && "fc-btn--block",
61
+ circle && "fc-btn--circle",
62
+ iconOnly && "fc-btn--icon-only",
63
+ loading && "is-loading",
64
+ disabled && "is-disabled",
65
+ className
66
+ ].filter(Boolean).join(" ");
67
+ return /* @__PURE__ */ jsxs(
5
68
  "button",
6
69
  {
70
+ className: classNames,
71
+ disabled: disabled || loading,
72
+ ...props,
73
+ children: [
74
+ iconLeft && !loading && /* @__PURE__ */ jsx2("span", { className: "fc-btn__icon fc-btn__icon--left", children: iconLeft }),
75
+ children,
76
+ iconRight && !loading && /* @__PURE__ */ jsx2("span", { className: "fc-btn__icon fc-btn__icon--right", children: iconRight })
77
+ ]
78
+ }
79
+ );
80
+ }
81
+ function ButtonGroup({ children, className, ...props }) {
82
+ return /* @__PURE__ */ jsx2("div", { className: `fc-btn-group ${className ?? ""}`, ...props, children });
83
+ }
84
+ function ButtonToolbar({
85
+ children,
86
+ align = "left",
87
+ className,
88
+ ...props
89
+ }) {
90
+ const alignClass = {
91
+ left: "",
92
+ center: "fc-btn-toolbar--center",
93
+ right: "fc-btn-toolbar--right",
94
+ between: "fc-btn-toolbar--between"
95
+ }[align];
96
+ return /* @__PURE__ */ jsx2("div", { className: `fc-btn-toolbar ${alignClass} ${className ?? ""}`, ...props, children });
97
+ }
98
+
99
+ // src/components/Button/CheckButton.tsx
100
+ import * as React from "react";
101
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
102
+ function CheckButton({
103
+ checked: controlledChecked,
104
+ defaultChecked = false,
105
+ onChange,
106
+ disabled = false,
107
+ size = "md",
108
+ labelLeft,
109
+ labelRight,
110
+ className = "",
111
+ style
112
+ }) {
113
+ const isControlled = controlledChecked !== void 0;
114
+ const [internalChecked, setInternalChecked] = React.useState(defaultChecked);
115
+ const checked = isControlled ? controlledChecked : internalChecked;
116
+ const toggle = () => {
117
+ if (disabled) return;
118
+ const next = !checked;
119
+ if (!isControlled) setInternalChecked(next);
120
+ onChange?.(next);
121
+ };
122
+ const handleKeyDown = (e) => {
123
+ if (e.key === "Enter" || e.key === " ") {
124
+ e.preventDefault();
125
+ toggle();
126
+ }
127
+ };
128
+ const cls = [
129
+ "fc-check",
130
+ `fc-check--${size}`,
131
+ checked && "fc-check--checked",
132
+ disabled && "fc-check--disabled",
133
+ className
134
+ ].filter(Boolean).join(" ");
135
+ return /* @__PURE__ */ jsxs2(
136
+ "div",
137
+ {
138
+ className: cls,
139
+ style,
140
+ role: "switch",
141
+ "aria-checked": checked,
142
+ tabIndex: disabled ? -1 : 0,
143
+ onClick: toggle,
144
+ onKeyDown: handleKeyDown,
145
+ children: [
146
+ labelLeft && /* @__PURE__ */ jsx3("span", { className: "fc-check__label", children: labelLeft }),
147
+ /* @__PURE__ */ jsx3("span", { className: "fc-check__track", children: /* @__PURE__ */ jsx3("span", { className: "fc-check__thumb", children: /* @__PURE__ */ jsx3("span", { className: "fc-check__thumb-inner" }) }) }),
148
+ labelRight && /* @__PURE__ */ jsx3("span", { className: "fc-check__label", children: labelRight })
149
+ ]
150
+ }
151
+ );
152
+ }
153
+
154
+ // src/components/Box/RollingBox.tsx
155
+ import * as React2 from "react";
156
+ import { jsx as jsx4 } from "react/jsx-runtime";
157
+ function RollingBox({
158
+ showThumb = "auto",
159
+ horizontal = false,
160
+ vertical = true,
161
+ thumbSize = "normal",
162
+ showTrack = false,
163
+ children,
164
+ className,
165
+ ...props
166
+ }) {
167
+ const containerRef = React2.useRef(null);
168
+ const [isScrolling, setIsScrolling] = React2.useState(false);
169
+ const scrollTimeoutRef = React2.useRef(null);
170
+ const handleScroll = React2.useCallback(() => {
171
+ if (showThumb !== "auto") return;
172
+ setIsScrolling(true);
173
+ if (scrollTimeoutRef.current) {
174
+ clearTimeout(scrollTimeoutRef.current);
175
+ }
176
+ scrollTimeoutRef.current = setTimeout(() => {
177
+ setIsScrolling(false);
178
+ }, 1e3);
179
+ }, [showThumb]);
180
+ React2.useEffect(() => {
181
+ return () => {
182
+ if (scrollTimeoutRef.current) {
183
+ clearTimeout(scrollTimeoutRef.current);
184
+ }
185
+ };
186
+ }, []);
187
+ const resolvedDirection = horizontal ? "horizontal" : "vertical";
188
+ const classNames = [
189
+ "fc-roll",
190
+ `fc-roll--thumb-${showThumb}`,
191
+ `fc-roll--size-${thumbSize}`,
192
+ `fc-roll--${resolvedDirection}`,
193
+ showTrack && "fc-roll--track",
194
+ isScrolling && "fc-roll--scrolling",
195
+ className
196
+ ].filter(Boolean).join(" ");
197
+ return /* @__PURE__ */ jsx4(
198
+ "div",
199
+ {
200
+ ref: containerRef,
201
+ className: classNames,
202
+ onScroll: handleScroll,
7
203
  ...props,
8
- style: {
9
- padding: "10px 16px",
10
- border: "none",
11
- borderRadius: 10,
12
- background: "#863bff",
13
- color: "#fff",
14
- cursor: "pointer",
15
- fontSize: 14,
16
- ...style
17
- },
18
- children
204
+ children: /* @__PURE__ */ jsx4("div", { className: "fc-roll__content", children })
205
+ }
206
+ );
207
+ }
208
+
209
+ // src/components/Bar/SideBar.tsx
210
+ import * as React3 from "react";
211
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
212
+ function SideBar({
213
+ items,
214
+ selectedKeys: controlledSelected,
215
+ defaultSelectedKeys = [],
216
+ openKeys: controlledOpen,
217
+ defaultOpenKeys = [],
218
+ onSelect,
219
+ onOpenChange,
220
+ collapsed: controlledCollapsed,
221
+ defaultCollapsed = false,
222
+ onCollapse,
223
+ className,
224
+ width = 240,
225
+ collapsedWidth = 64
226
+ }) {
227
+ const [internalSelected, setInternalSelected] = React3.useState(defaultSelectedKeys);
228
+ const [internalOpen, setInternalOpen] = React3.useState(defaultOpenKeys);
229
+ const [internalCollapsed, setInternalCollapsed] = React3.useState(defaultCollapsed);
230
+ const currentSelected = controlledSelected ?? internalSelected;
231
+ const currentOpen = controlledOpen ?? internalOpen;
232
+ const currentCollapsed = controlledCollapsed ?? internalCollapsed;
233
+ const updateOpen = React3.useCallback((next) => {
234
+ if (controlledOpen === void 0) setInternalOpen(next);
235
+ onOpenChange?.(next);
236
+ }, [controlledOpen, onOpenChange]);
237
+ const toggleOpen = React3.useCallback((key) => {
238
+ const next = currentOpen.includes(key) ? currentOpen.filter((k) => k !== key) : [...currentOpen, key];
239
+ updateOpen(next);
240
+ }, [currentOpen, updateOpen]);
241
+ const handleSelect = React3.useCallback((key, item) => {
242
+ if (item.disabled) return;
243
+ const next = [key];
244
+ if (controlledSelected === void 0) setInternalSelected(next);
245
+ onSelect?.(next);
246
+ }, [controlledSelected, onSelect]);
247
+ const toggleCollapse = React3.useCallback(() => {
248
+ const next = !currentCollapsed;
249
+ if (controlledCollapsed === void 0) setInternalCollapsed(next);
250
+ onCollapse?.(next);
251
+ }, [currentCollapsed, controlledCollapsed, onCollapse]);
252
+ const handleItemClick = React3.useCallback((item) => {
253
+ if (item.children?.length) {
254
+ toggleOpen(item.key);
255
+ } else {
256
+ handleSelect(item.key, item);
257
+ }
258
+ }, [toggleOpen, handleSelect]);
259
+ const handleItemKeyDown = React3.useCallback((e, item) => {
260
+ if (e.key === "Enter" || e.key === " ") {
261
+ e.preventDefault();
262
+ handleItemClick(item);
263
+ }
264
+ }, [handleItemClick]);
265
+ const renderMenuItem = (item, level = 0) => {
266
+ const hasChildren = (item.children?.length ?? 0) > 0;
267
+ const isOpen = currentOpen.includes(item.key);
268
+ const isSelected = currentSelected.includes(item.key);
269
+ const itemClassName = [
270
+ "fc-sidebar__item",
271
+ `fc-sidebar__item--level-${level}`,
272
+ hasChildren && "fc-sidebar__item--parent",
273
+ isOpen && "fc-sidebar__item--open",
274
+ isSelected && "fc-sidebar__item--selected",
275
+ item.disabled && "fc-sidebar__item--disabled"
276
+ ].filter(Boolean).join(" ");
277
+ const Tag = item.href ? "a" : "div";
278
+ const interactiveProps = {
279
+ role: hasChildren ? "treeitem" : "menuitem",
280
+ tabIndex: item.disabled ? -1 : 0,
281
+ "aria-selected": isSelected || void 0,
282
+ "aria-expanded": hasChildren ? isOpen : void 0,
283
+ "aria-disabled": item.disabled || void 0,
284
+ onClick: () => handleItemClick(item),
285
+ onKeyDown: (e) => handleItemKeyDown(e, item),
286
+ ...item.href ? { href: item.href } : {}
287
+ };
288
+ return /* @__PURE__ */ jsxs3("div", { className: "fc-sidebar__item-wrapper", children: [
289
+ /* @__PURE__ */ jsxs3(Tag, { className: itemClassName, ...interactiveProps, children: [
290
+ item.icon && /* @__PURE__ */ jsx5("span", { className: "fc-sidebar__icon", "aria-hidden": "true", children: item.icon }),
291
+ /* @__PURE__ */ jsx5("span", { className: "fc-sidebar__label", children: item.label }),
292
+ hasChildren && /* @__PURE__ */ jsx5("span", { className: "fc-sidebar__arrow", "aria-hidden": "true", children: "\u25B6" })
293
+ ] }),
294
+ hasChildren && /* @__PURE__ */ jsx5(
295
+ "div",
296
+ {
297
+ className: [
298
+ "fc-sidebar__submenu",
299
+ isOpen && "fc-sidebar__submenu--open"
300
+ ].filter(Boolean).join(" "),
301
+ role: "group",
302
+ children: /* @__PURE__ */ jsx5("div", { className: "fc-sidebar__submenu-inner", children: item.children.map((child) => renderMenuItem(child, level + 1)) })
303
+ }
304
+ )
305
+ ] }, item.key);
306
+ };
307
+ const sidebarStyle = {
308
+ "--sidebar-width": `${currentCollapsed ? collapsedWidth : width}px`,
309
+ "--sidebar-collapsed-width": `${collapsedWidth}px`
310
+ };
311
+ return /* @__PURE__ */ jsxs3(
312
+ "aside",
313
+ {
314
+ className: [
315
+ "fc-sidebar",
316
+ currentCollapsed && "fc-sidebar--collapsed",
317
+ className
318
+ ].filter(Boolean).join(" "),
319
+ style: sidebarStyle,
320
+ role: "navigation",
321
+ "aria-label": "Sidebar",
322
+ children: [
323
+ /* @__PURE__ */ jsx5("div", { className: "fc-sidebar__header", children: /* @__PURE__ */ jsx5(
324
+ "button",
325
+ {
326
+ className: "fc-sidebar__collapse-btn",
327
+ onClick: toggleCollapse,
328
+ "aria-label": currentCollapsed ? "\u5C55\u5F00\u4FA7\u680F" : "\u6536\u8D77\u4FA7\u680F",
329
+ "aria-expanded": !currentCollapsed,
330
+ children: currentCollapsed ? "\u2192" : "\u2190"
331
+ }
332
+ ) }),
333
+ /* @__PURE__ */ jsx5("nav", { className: "fc-sidebar__menu", role: "tree", children: items.map((item) => renderMenuItem(item)) })
334
+ ]
335
+ }
336
+ );
337
+ }
338
+
339
+ // src/components/Input/Input.tsx
340
+ import * as React4 from "react";
341
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
342
+ var Input = React4.forwardRef(({
343
+ size = "md",
344
+ status = "default",
345
+ prefix,
346
+ suffix,
347
+ allowClear = false,
348
+ passwordToggle = false,
349
+ addonBefore,
350
+ addonAfter,
351
+ helperText,
352
+ className = "",
353
+ type: initialType = "text",
354
+ value,
355
+ defaultValue,
356
+ onChange,
357
+ onClear,
358
+ disabled,
359
+ ...props
360
+ }, ref) => {
361
+ const [type, setType] = React4.useState(initialType);
362
+ const [internalValue, setInternalValue] = React4.useState(defaultValue || "");
363
+ const isControlled = value !== void 0;
364
+ const currentValue = isControlled ? value : internalValue;
365
+ const handleChange = (e) => {
366
+ if (!isControlled) setInternalValue(e.target.value);
367
+ onChange?.(e);
368
+ };
369
+ const handleClear = () => {
370
+ if (!isControlled) setInternalValue("");
371
+ onClear?.();
372
+ const event = { target: { value: "" } };
373
+ onChange?.(event);
374
+ };
375
+ const togglePassword = () => {
376
+ setType((prev) => prev === "password" ? "text" : "password");
377
+ };
378
+ const showClear = allowClear && currentValue && !disabled;
379
+ const showPasswordToggle = passwordToggle && initialType === "password" && !disabled;
380
+ const classNames = [
381
+ "fc-input",
382
+ `fc-input--${size}`,
383
+ `fc-input--${status}`,
384
+ (prefix || addonBefore) && "fc-input--has-prefix",
385
+ (suffix || addonAfter || showClear || showPasswordToggle) && "fc-input--has-suffix",
386
+ addonBefore && "fc-input--addon-before",
387
+ addonAfter && "fc-input--addon-after",
388
+ disabled && "fc-input--disabled",
389
+ className
390
+ ].filter(Boolean).join(" ");
391
+ const input = /* @__PURE__ */ jsx6(
392
+ "input",
393
+ {
394
+ ref,
395
+ type,
396
+ value: currentValue,
397
+ onChange: handleChange,
398
+ disabled,
399
+ className: "fc-input__field",
400
+ ...props
401
+ }
402
+ );
403
+ return /* @__PURE__ */ jsxs4("div", { className: classNames, children: [
404
+ addonBefore && /* @__PURE__ */ jsx6("span", { className: "fc-input__addon fc-input__addon--before", children: addonBefore }),
405
+ prefix && /* @__PURE__ */ jsx6("span", { className: "fc-input__prefix", children: prefix }),
406
+ /* @__PURE__ */ jsxs4("div", { className: "fc-input__wrapper", children: [
407
+ input,
408
+ /* @__PURE__ */ jsxs4("span", { className: "fc-input__actions", children: [
409
+ showClear && /* @__PURE__ */ jsx6(
410
+ "button",
411
+ {
412
+ type: "button",
413
+ className: "fc-input__action fc-input__clear",
414
+ onClick: handleClear,
415
+ tabIndex: -1,
416
+ children: "\u2715"
417
+ }
418
+ ),
419
+ showPasswordToggle && /* @__PURE__ */ jsx6(
420
+ "button",
421
+ {
422
+ type: "button",
423
+ className: "fc-input__action fc-input__eye",
424
+ onClick: togglePassword,
425
+ tabIndex: -1,
426
+ children: type === "password" ? "\u{1F441}" : "\u{1F648}"
427
+ }
428
+ ),
429
+ suffix && /* @__PURE__ */ jsx6("span", { className: "fc-input__suffix", children: suffix })
430
+ ] })
431
+ ] }),
432
+ addonAfter && /* @__PURE__ */ jsx6("span", { className: "fc-input__addon fc-input__addon--after", children: addonAfter }),
433
+ helperText && /* @__PURE__ */ jsx6("span", { className: `fc-input__helper fc-input__helper--${status}`, children: helperText })
434
+ ] });
435
+ });
436
+ Input.displayName = "Input";
437
+
438
+ // src/components/Slider/Slider.tsx
439
+ import * as React5 from "react";
440
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
441
+ function Slider({
442
+ value: controlledValue,
443
+ defaultValue,
444
+ onChange,
445
+ min = 0,
446
+ max = 100,
447
+ step = 1,
448
+ range = false,
449
+ orientation = "horizontal",
450
+ disabled = false,
451
+ marks,
452
+ tooltip = false,
453
+ className = ""
454
+ }) {
455
+ const trackRef = React5.useRef(null);
456
+ const draggingRef = React5.useRef(null);
457
+ const [dragging, setDragging] = React5.useState(null);
458
+ const initialValue = defaultValue ?? (range ? [min, max] : min);
459
+ const [internalValue, setInternalValue] = React5.useState(initialValue);
460
+ const isControlled = controlledValue !== void 0;
461
+ const currentValue = isControlled ? controlledValue : internalValue;
462
+ const getPercent = (val) => Math.max(0, Math.min(100, (val - min) / (max - min) * 100));
463
+ const getValueFromPercent = (percent) => {
464
+ const raw = min + percent / 100 * (max - min);
465
+ const stepped = Math.round(raw / step) * step;
466
+ return Math.max(min, Math.min(max, stepped));
467
+ };
468
+ const handleMove = React5.useCallback((clientX, clientY) => {
469
+ if (!trackRef.current || draggingRef.current === null || disabled) return;
470
+ const rect = trackRef.current.getBoundingClientRect();
471
+ const percent = orientation === "horizontal" ? (clientX - rect.left) / rect.width * 100 : (rect.bottom - clientY) / rect.height * 100;
472
+ const newValue = getValueFromPercent(Math.max(0, Math.min(100, percent)));
473
+ let nextValue;
474
+ if (range) {
475
+ const [start, end] = currentValue;
476
+ nextValue = draggingRef.current === 0 ? [Math.min(newValue, end), end] : [start, Math.max(newValue, start)];
477
+ } else {
478
+ nextValue = newValue;
479
+ }
480
+ if (!isControlled) setInternalValue(nextValue);
481
+ onChange?.(nextValue);
482
+ }, [disabled, orientation, range, currentValue, isControlled, onChange, min, max, step]);
483
+ const handleMouseDown = (index) => (e) => {
484
+ if (disabled) return;
485
+ e.preventDefault();
486
+ draggingRef.current = index;
487
+ setDragging(index);
488
+ const handleMouseMove = (e2) => handleMove(e2.clientX, e2.clientY);
489
+ const handleMouseUp = () => {
490
+ draggingRef.current = null;
491
+ setDragging(null);
492
+ document.removeEventListener("mousemove", handleMouseMove);
493
+ document.removeEventListener("mouseup", handleMouseUp);
494
+ };
495
+ document.addEventListener("mousemove", handleMouseMove);
496
+ document.addEventListener("mouseup", handleMouseUp);
497
+ };
498
+ const handleTrackClick = (e) => {
499
+ if (disabled || draggingRef.current !== null) return;
500
+ handleMove(e.clientX, e.clientY);
501
+ };
502
+ const [startVal, endVal] = range ? currentValue : [min, currentValue];
503
+ const startPercent = getPercent(startVal);
504
+ const endPercent = getPercent(endVal);
505
+ const isHorizontal = orientation === "horizontal";
506
+ const thumbStyle = (percent) => isHorizontal ? { left: `${percent}%` } : { bottom: `${percent}%` };
507
+ const cls = [
508
+ "fc-slider",
509
+ `fc-slider--${orientation}`,
510
+ range && "fc-slider--range",
511
+ disabled && "fc-slider--disabled",
512
+ dragging !== null && "fc-slider--dragging",
513
+ className
514
+ ].filter(Boolean).join(" ");
515
+ return /* @__PURE__ */ jsx7("div", { className: cls, children: /* @__PURE__ */ jsxs5(
516
+ "div",
517
+ {
518
+ ref: trackRef,
519
+ className: "fc-slider__track",
520
+ onClick: handleTrackClick,
521
+ children: [
522
+ /* @__PURE__ */ jsx7(
523
+ "div",
524
+ {
525
+ className: "fc-slider__fill",
526
+ style: isHorizontal ? { left: `${startPercent}%`, width: `${endPercent - startPercent}%` } : { bottom: `${startPercent}%`, height: `${endPercent - startPercent}%` }
527
+ }
528
+ ),
529
+ range && /* @__PURE__ */ jsx7(
530
+ "div",
531
+ {
532
+ className: `fc-slider__thumb ${dragging === 0 ? "fc-slider__thumb--active" : ""}`,
533
+ style: thumbStyle(startPercent),
534
+ onMouseDown: handleMouseDown(0),
535
+ children: tooltip && /* @__PURE__ */ jsx7("span", { className: "fc-slider__tooltip", children: startVal })
536
+ }
537
+ ),
538
+ /* @__PURE__ */ jsx7(
539
+ "div",
540
+ {
541
+ className: `fc-slider__thumb ${dragging === (range ? 1 : 0) ? "fc-slider__thumb--active" : ""}`,
542
+ style: thumbStyle(endPercent),
543
+ onMouseDown: handleMouseDown(range ? 1 : 0),
544
+ children: tooltip && /* @__PURE__ */ jsx7("span", { className: "fc-slider__tooltip", children: endVal })
545
+ }
546
+ ),
547
+ marks && Object.entries(marks).map(([val, label]) => /* @__PURE__ */ jsxs5(
548
+ "div",
549
+ {
550
+ className: "fc-slider__mark",
551
+ style: thumbStyle(getPercent(Number(val))),
552
+ children: [
553
+ /* @__PURE__ */ jsx7("span", { className: "fc-slider__mark-dot" }),
554
+ /* @__PURE__ */ jsx7("span", { className: "fc-slider__mark-label", children: label })
555
+ ]
556
+ },
557
+ val
558
+ ))
559
+ ]
560
+ }
561
+ ) });
562
+ }
563
+
564
+ // src/components/Select/Select.tsx
565
+ import * as React6 from "react";
566
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
567
+ function Select({
568
+ options,
569
+ value: controlledValue,
570
+ defaultValue,
571
+ onChange,
572
+ placeholder = "\u8BF7\u9009\u62E9",
573
+ searchable = false,
574
+ multiple = false,
575
+ disabled = false,
576
+ className = "",
577
+ virtualScroll = false,
578
+ virtualItemHeight = 32,
579
+ maxHeight = 256
580
+ }) {
581
+ const [isOpen, setIsOpen] = React6.useState(false);
582
+ const [searchValue, setSearchValue] = React6.useState("");
583
+ const [highlightedIndex, setHighlightedIndex] = React6.useState(0);
584
+ const containerRef = React6.useRef(null);
585
+ const listRef = React6.useRef(null);
586
+ const isControlled = controlledValue !== void 0;
587
+ const [internalValue, setInternalValue] = React6.useState(
588
+ defaultValue ?? (multiple ? [] : void 0)
589
+ );
590
+ const currentValue = isControlled ? controlledValue : internalValue;
591
+ const groupedOptions = React6.useMemo(() => {
592
+ const filtered = searchable && searchValue ? options.filter((o) => o.label.toLowerCase().includes(searchValue.toLowerCase())) : options;
593
+ const groups = {};
594
+ filtered.forEach((opt) => {
595
+ const group = opt.group || "";
596
+ if (!groups[group]) groups[group] = [];
597
+ groups[group].push(opt);
598
+ });
599
+ return groups;
600
+ }, [options, searchValue, searchable]);
601
+ const flatOptions = React6.useMemo(() => {
602
+ return Object.values(groupedOptions).flat();
603
+ }, [groupedOptions]);
604
+ const [scrollTop, setScrollTop] = React6.useState(0);
605
+ const visibleCount = Math.ceil(maxHeight / virtualItemHeight);
606
+ const startIndex = Math.floor(scrollTop / virtualItemHeight);
607
+ const endIndex = Math.min(startIndex + visibleCount + 1, flatOptions.length);
608
+ const visibleOptions = virtualScroll ? flatOptions.slice(startIndex, endIndex) : flatOptions;
609
+ const totalHeight = flatOptions.length * virtualItemHeight;
610
+ const offsetY = startIndex * virtualItemHeight;
611
+ const handleSelect = (option) => {
612
+ if (option.disabled) return;
613
+ let nextValue;
614
+ if (multiple) {
615
+ const arr = currentValue || [];
616
+ const exists = arr.includes(option.value);
617
+ nextValue = exists ? arr.filter((v) => v !== option.value) : [...arr, option.value];
618
+ } else {
619
+ nextValue = option.value;
620
+ setIsOpen(false);
621
+ }
622
+ if (!isControlled) setInternalValue(nextValue);
623
+ onChange?.(nextValue);
624
+ if (!multiple) setSearchValue("");
625
+ };
626
+ const isSelected = (value) => {
627
+ if (multiple) return currentValue?.includes(value);
628
+ return currentValue === value;
629
+ };
630
+ const displayLabel = () => {
631
+ if (multiple) {
632
+ const count = currentValue?.length || 0;
633
+ return count > 0 ? `\u5DF2\u9009\u62E9 ${count} \u9879` : placeholder;
634
+ }
635
+ const selected = options.find((o) => o.value === currentValue);
636
+ return selected?.label || placeholder;
637
+ };
638
+ React6.useEffect(() => {
639
+ const handleClick = (e) => {
640
+ if (!containerRef.current?.contains(e.target)) {
641
+ setIsOpen(false);
642
+ }
643
+ };
644
+ document.addEventListener("mousedown", handleClick);
645
+ return () => document.removeEventListener("mousedown", handleClick);
646
+ }, []);
647
+ const classNames = [
648
+ "fc-select",
649
+ isOpen && "fc-select--open",
650
+ multiple && "fc-select--multiple",
651
+ disabled && "fc-select--disabled",
652
+ className
653
+ ].filter(Boolean).join(" ");
654
+ return /* @__PURE__ */ jsxs6("div", { ref: containerRef, className: classNames, children: [
655
+ /* @__PURE__ */ jsxs6(
656
+ "div",
657
+ {
658
+ className: "fc-select__trigger",
659
+ onClick: () => !disabled && setIsOpen(!isOpen),
660
+ children: [
661
+ /* @__PURE__ */ jsx8("span", { className: `fc-select__value ${!currentValue && "fc-select__value--placeholder"}`, children: displayLabel() }),
662
+ /* @__PURE__ */ jsx8("span", { className: "fc-select__arrow", children: "\u25BC" })
663
+ ]
664
+ }
665
+ ),
666
+ isOpen && /* @__PURE__ */ jsxs6("div", { className: "fc-select__dropdown", children: [
667
+ searchable && /* @__PURE__ */ jsx8("div", { className: "fc-select__search", children: /* @__PURE__ */ jsx8(
668
+ "input",
669
+ {
670
+ type: "text",
671
+ value: searchValue,
672
+ onChange: (e) => setSearchValue(e.target.value),
673
+ placeholder: "\u641C\u7D22...",
674
+ className: "fc-select__search-input",
675
+ autoFocus: true
676
+ }
677
+ ) }),
678
+ /* @__PURE__ */ jsxs6(
679
+ "div",
680
+ {
681
+ ref: listRef,
682
+ className: "fc-select__list",
683
+ style: { maxHeight },
684
+ onScroll: (e) => virtualScroll && setScrollTop(e.target.scrollTop),
685
+ children: [
686
+ virtualScroll && /* @__PURE__ */ jsx8("div", { style: { height: totalHeight, position: "relative" }, children: /* @__PURE__ */ jsx8("div", { style: { transform: `translateY(${offsetY}px)` }, children: renderOptions(visibleOptions, startIndex) }) }),
687
+ !virtualScroll && renderGroupedOptions()
688
+ ]
689
+ }
690
+ )
691
+ ] })
692
+ ] });
693
+ function renderOptions(opts, baseIndex = 0) {
694
+ return opts.map((option, idx) => {
695
+ const actualIndex = baseIndex + idx;
696
+ const selected = isSelected(option.value);
697
+ const highlighted = actualIndex === highlightedIndex;
698
+ return /* @__PURE__ */ jsxs6(
699
+ "div",
700
+ {
701
+ className: [
702
+ "fc-select__option",
703
+ selected && "fc-select__option--selected",
704
+ option.disabled && "fc-select__option--disabled",
705
+ highlighted && "fc-select__option--highlighted"
706
+ ].filter(Boolean).join(" "),
707
+ onClick: () => handleSelect(option),
708
+ onMouseEnter: () => setHighlightedIndex(actualIndex),
709
+ style: { height: virtualItemHeight },
710
+ children: [
711
+ multiple && /* @__PURE__ */ jsx8("span", { className: `fc-select__checkbox ${selected && "fc-select__checkbox--checked"}`, children: selected && "\u2713" }),
712
+ /* @__PURE__ */ jsx8("span", { className: "fc-select__option-label", children: highlightText(option.label, searchValue) }),
713
+ !multiple && selected && /* @__PURE__ */ jsx8("span", { className: "fc-select__check", children: "\u2713" })
714
+ ]
715
+ },
716
+ option.value
717
+ );
718
+ });
719
+ }
720
+ function renderGroupedOptions() {
721
+ return Object.entries(groupedOptions).map(([group, opts]) => /* @__PURE__ */ jsxs6("div", { className: "fc-select__group", children: [
722
+ group && /* @__PURE__ */ jsx8("div", { className: "fc-select__group-label", children: group }),
723
+ renderOptions(opts)
724
+ ] }, group));
725
+ }
726
+ function highlightText(text, highlight) {
727
+ if (!highlight) return text;
728
+ const parts = text.split(new RegExp(`(${highlight})`, "gi"));
729
+ return parts.map(
730
+ (part, i) => part.toLowerCase() === highlight.toLowerCase() ? /* @__PURE__ */ jsx8("mark", { className: "fc-select__highlight", children: part }, i) : part
731
+ );
732
+ }
733
+ }
734
+
735
+ // src/components/Tree/Tree.tsx
736
+ import {
737
+ createContext as createContext2,
738
+ memo,
739
+ useCallback as useCallback4,
740
+ useContext as useContext2,
741
+ useEffect as useEffect4,
742
+ useMemo as useMemo2,
743
+ useRef as useRef4,
744
+ useState as useState9
745
+ } from "react";
746
+ import {
747
+ DndContext,
748
+ PointerSensor,
749
+ useDraggable,
750
+ useDroppable,
751
+ useSensor,
752
+ useSensors
753
+ } from "@dnd-kit/core";
754
+
755
+ // src/components/Tree/DeleteDialog.tsx
756
+ import { useState as useState8 } from "react";
757
+ import { Fragment, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
758
+ function DeleteDialog({ node, onClose, onDelete }) {
759
+ const [phase, setPhase] = useState8("choose");
760
+ const [loading, setLoading] = useState8(false);
761
+ if (!node) return null;
762
+ const reset = () => {
763
+ setPhase("choose");
764
+ setLoading(false);
765
+ };
766
+ const handleClose = () => {
767
+ reset();
768
+ onClose();
769
+ };
770
+ const handleLift = async () => {
771
+ setLoading(true);
772
+ try {
773
+ await onDelete(node.key, "lift");
774
+ reset();
775
+ onClose();
776
+ } catch {
777
+ setLoading(false);
778
+ }
779
+ };
780
+ const handleCascade = async () => {
781
+ setLoading(true);
782
+ try {
783
+ await onDelete(node.key, "cascade");
784
+ reset();
785
+ onClose();
786
+ } catch {
787
+ setLoading(false);
788
+ }
789
+ };
790
+ return /* @__PURE__ */ jsx9("div", { className: "fc-dialog-overlay", onMouseDown: handleClose, children: /* @__PURE__ */ jsxs7("div", { className: "fc-dialog", onMouseDown: (e) => e.stopPropagation(), children: [
791
+ phase === "choose" && /* @__PURE__ */ jsxs7(Fragment, { children: [
792
+ /* @__PURE__ */ jsxs7("div", { className: "fc-dialog__header", children: [
793
+ /* @__PURE__ */ jsx9("span", { className: "fc-dialog__icon", children: "\u{1F5C2}" }),
794
+ /* @__PURE__ */ jsxs7("h3", { className: "fc-dialog__title", children: [
795
+ "\u5220\u9664\u300C",
796
+ node.title,
797
+ "\u300D"
798
+ ] })
799
+ ] }),
800
+ /* @__PURE__ */ jsx9("p", { className: "fc-dialog__desc", children: "\u8BE5\u5206\u7C7B\u4E0B\u53EF\u80FD\u5305\u542B\u5B50\u5206\u7C7B\u548C\u8BCD\u6761\uFF0C\u8BF7\u9009\u62E9\u5220\u9664\u65B9\u5F0F\uFF1A" }),
801
+ /* @__PURE__ */ jsxs7("div", { className: "fc-dialog__options", children: [
802
+ /* @__PURE__ */ jsxs7(
803
+ "button",
804
+ {
805
+ className: "fc-dialog__option",
806
+ onClick: handleLift,
807
+ disabled: loading,
808
+ children: [
809
+ /* @__PURE__ */ jsx9("span", { className: "fc-dialog__option-icon", children: "\u{1F4E4}" }),
810
+ /* @__PURE__ */ jsxs7("span", { className: "fc-dialog__option-body", children: [
811
+ /* @__PURE__ */ jsx9("span", { className: "fc-dialog__option-title", children: "\u79FB\u4EA4\u7ED9\u4E0A\u7EA7\u5206\u7C7B" }),
812
+ /* @__PURE__ */ jsx9("span", { className: "fc-dialog__option-desc", children: "\u6240\u6709\u5B50\u5206\u7C7B\u548C\u8BCD\u6761\u5C06\u79FB\u81F3\u4E0A\u7EA7\u5206\u7C7B\uFF1B\u82E5\u5DF2\u662F\u6839\u5206\u7C7B\uFF0C\u5219\u53D8\u4E3A\u65E0\u5206\u7C7B" })
813
+ ] })
814
+ ]
815
+ }
816
+ ),
817
+ /* @__PURE__ */ jsxs7(
818
+ "button",
819
+ {
820
+ className: "fc-dialog__option fc-dialog__option--danger",
821
+ onClick: () => setPhase("confirm-cascade"),
822
+ disabled: loading,
823
+ children: [
824
+ /* @__PURE__ */ jsx9("span", { className: "fc-dialog__option-icon", children: "\u{1F5D1}" }),
825
+ /* @__PURE__ */ jsxs7("span", { className: "fc-dialog__option-body", children: [
826
+ /* @__PURE__ */ jsx9("span", { className: "fc-dialog__option-title", children: "\u5F7B\u5E95\u5220\u9664" }),
827
+ /* @__PURE__ */ jsx9("span", { className: "fc-dialog__option-desc", children: "\u5220\u9664\u8BE5\u5206\u7C7B\u53CA\u6240\u6709\u5B50\u5206\u7C7B\uFF1B\u8BCD\u6761\u4E0D\u4F1A\u88AB\u5220\u9664\uFF0C\u5C06\u53D8\u4E3A\u65E0\u5206\u7C7B" })
828
+ ] })
829
+ ]
830
+ }
831
+ )
832
+ ] }),
833
+ /* @__PURE__ */ jsx9("div", { className: "fc-dialog__footer", children: /* @__PURE__ */ jsx9("button", { className: "fc-dialog__btn", onClick: handleClose, disabled: loading, children: "\u53D6\u6D88" }) })
834
+ ] }),
835
+ phase === "confirm-cascade" && /* @__PURE__ */ jsxs7(Fragment, { children: [
836
+ /* @__PURE__ */ jsxs7("div", { className: "fc-dialog__header", children: [
837
+ /* @__PURE__ */ jsx9("span", { className: "fc-dialog__icon", children: "\u26A0\uFE0F" }),
838
+ /* @__PURE__ */ jsx9("h3", { className: "fc-dialog__title fc-dialog__title--danger", children: "\u786E\u8BA4\u5F7B\u5E95\u5220\u9664\uFF1F" })
839
+ ] }),
840
+ /* @__PURE__ */ jsxs7("p", { className: "fc-dialog__desc", children: [
841
+ "\u6B64\u64CD\u4F5C",
842
+ /* @__PURE__ */ jsx9("strong", { children: "\u4E0D\u53EF\u9006" }),
843
+ "\u3002\u300C",
844
+ node.title,
845
+ "\u300D\u53CA\u5176\u6240\u6709\u5B50\u5206\u7C7B\u5C06\u88AB\u6C38\u4E45\u5220\u9664\uFF0C \u5176\u4E0B\u8BCD\u6761\u5C06\u53D8\u4E3A\u65E0\u5206\u7C7B\u3002"
846
+ ] }),
847
+ /* @__PURE__ */ jsxs7("div", { className: "fc-dialog__footer", children: [
848
+ /* @__PURE__ */ jsx9(
849
+ "button",
850
+ {
851
+ className: "fc-dialog__btn",
852
+ onClick: () => setPhase("choose"),
853
+ disabled: loading,
854
+ children: "\u8FD4\u56DE"
855
+ }
856
+ ),
857
+ /* @__PURE__ */ jsx9(
858
+ "button",
859
+ {
860
+ className: "fc-dialog__btn fc-dialog__btn--danger",
861
+ onClick: handleCascade,
862
+ disabled: loading,
863
+ children: loading ? "\u5220\u9664\u4E2D\u2026" : "\u786E\u8BA4\u5220\u9664"
864
+ }
865
+ )
866
+ ] })
867
+ ] })
868
+ ] }) });
869
+ }
870
+
871
+ // src/components/Tree/flatToTree.ts
872
+ function flatToTree(list) {
873
+ const nodeMap = /* @__PURE__ */ new Map();
874
+ for (const item of list) {
875
+ nodeMap.set(item.id, {
876
+ key: item.id,
877
+ title: item.name,
878
+ children: [],
879
+ raw: item
880
+ });
881
+ }
882
+ const roots = [];
883
+ const orphans = [];
884
+ for (const item of list) {
885
+ const node = nodeMap.get(item.id);
886
+ if (item.parent_id === null) {
887
+ roots.push(node);
888
+ } else if (nodeMap.has(item.parent_id)) {
889
+ nodeMap.get(item.parent_id).children.push(node);
890
+ } else {
891
+ orphans.push(node);
892
+ }
893
+ }
894
+ const sortLevel = (nodes) => {
895
+ nodes.sort((a, b) => a.raw.sort_order - b.raw.sort_order);
896
+ for (const node of nodes) sortLevel(node.children);
897
+ };
898
+ sortLevel(roots);
899
+ return { roots, orphans };
900
+ }
901
+ function findNodeInfo(nodes, key, parent = null) {
902
+ for (let i = 0; i < nodes.length; i++) {
903
+ if (nodes[i].key === key)
904
+ return { node: nodes[i], parent, siblings: nodes, index: i };
905
+ const found = findNodeInfo(nodes[i].children, key, nodes[i]);
906
+ if (found) return found;
907
+ }
908
+ return null;
909
+ }
910
+ function isDescendantOf(roots, ancestorKey, targetKey) {
911
+ const info = findNodeInfo(roots, ancestorKey);
912
+ if (!info) return false;
913
+ const check = (children) => children.some((n) => n.key === targetKey || check(n.children));
914
+ return check(info.node.children);
915
+ }
916
+
917
+ // src/components/Tree/Tree.tsx
918
+ import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
919
+ var TreeActionsCtx = createContext2(null);
920
+ var TreeStateCtx = createContext2(null);
921
+ var DndStateCtx = createContext2(null);
922
+ var CollapsePanel = memo(function CollapsePanel2({ open, children }) {
923
+ const innerRef = useRef4(null);
924
+ const [height, setHeight] = useState9(0);
925
+ const [ready, setReady] = useState9(false);
926
+ useEffect4(() => {
927
+ const el = innerRef.current;
928
+ if (!el) return;
929
+ const ro = new ResizeObserver(() => setHeight(el.offsetHeight));
930
+ ro.observe(el);
931
+ setHeight(el.offsetHeight);
932
+ requestAnimationFrame(() => requestAnimationFrame(() => setReady(true)));
933
+ return () => ro.disconnect();
934
+ }, []);
935
+ return /* @__PURE__ */ jsx10("div", { style: {
936
+ height: open ? height : 0,
937
+ overflow: "hidden",
938
+ transition: ready ? "height 0.12s ease-out" : "none"
939
+ }, children: /* @__PURE__ */ jsx10("div", { ref: innerRef, children }) });
940
+ });
941
+ var DndSlot = memo(function DndSlot2({
942
+ nodeKey,
943
+ disabled,
944
+ children
945
+ }) {
946
+ const dndState = useContext2(DndStateCtx);
947
+ const isDropTarget = dndState.dropTargetKey === nodeKey;
948
+ const dropPosition = isDropTarget ? dndState.dropPosition : null;
949
+ const isDragSource = dndState.dragKey === nodeKey;
950
+ const { attributes, listeners, setNodeRef: setDragRef, isDragging } = useDraggable({ id: nodeKey, disabled });
951
+ const { setNodeRef: setDropRef } = useDroppable({ id: nodeKey, disabled });
952
+ const setRef = useCallback4((el) => {
953
+ setDragRef(el);
954
+ setDropRef(el);
955
+ }, [setDragRef, setDropRef]);
956
+ const handleProps = useMemo2(
957
+ () => ({ ...attributes, ...listeners }),
958
+ [attributes, listeners]
959
+ );
960
+ return /* @__PURE__ */ jsx10(Fragment2, { children: children({ setRef, handleProps, isDragging, isDragSource, dropPosition }) });
961
+ });
962
+ var TreeNodeItem = memo(function TreeNodeItem2({ node, level, hidden = false }) {
963
+ const actions = useContext2(TreeActionsCtx);
964
+ const state = useContext2(TreeStateCtx);
965
+ const isExpanded = state.expandedKeys.has(node.key);
966
+ const isEditing = state.editingKey === node.key;
967
+ const hasChildren = node.children.length > 0;
968
+ const indent = level * 20 + 12;
969
+ const [localEdit, setLocalEdit] = useState9("");
970
+ useEffect4(() => {
971
+ if (isEditing) setLocalEdit(node.title);
972
+ }, [isEditing, node.title]);
973
+ const handleEditKeyDown = useCallback4((e) => {
974
+ e.stopPropagation();
975
+ if (e.key === "Enter") actions.commitEdit(node.key, localEdit).then();
976
+ if (e.key === "Escape") actions.cancelEdit();
977
+ }, [actions, node.key, localEdit]);
978
+ return /* @__PURE__ */ jsx10(DndSlot, { nodeKey: node.key, disabled: isEditing || hidden, children: ({ setRef, handleProps, isDragging, isDragSource, dropPosition }) => /* @__PURE__ */ jsxs8("div", { className: `fc-tree__node ${isDragging ? "is-dragging" : ""}`, children: [
979
+ /* @__PURE__ */ jsxs8(
980
+ "div",
981
+ {
982
+ ref: setRef,
983
+ className: [
984
+ "fc-tree__item",
985
+ isDragSource && "fc-tree__item--drag-source",
986
+ dropPosition === "into" && "fc-tree__item--drop-into",
987
+ dropPosition === "before" && "fc-tree__item--drop-before",
988
+ dropPosition === "after" && "fc-tree__item--drop-after"
989
+ ].filter(Boolean).join(" "),
990
+ style: {
991
+ paddingLeft: dropPosition === "into" ? indent + 8 : indent,
992
+ "--fc-indent": `${indent}px`
993
+ },
994
+ children: [
995
+ /* @__PURE__ */ jsx10(
996
+ "span",
997
+ {
998
+ className: "fc-tree__drag-handle",
999
+ title: "\u62D6\u62FD\u79FB\u52A8",
1000
+ ...handleProps,
1001
+ onMouseDown: (e) => e.stopPropagation(),
1002
+ children: "\u283F"
1003
+ }
1004
+ ),
1005
+ /* @__PURE__ */ jsx10(
1006
+ "span",
1007
+ {
1008
+ className: [
1009
+ "fc-tree__switcher",
1010
+ !hasChildren && "fc-tree__switcher--hidden",
1011
+ isExpanded && "fc-tree__switcher--open"
1012
+ ].filter(Boolean).join(" "),
1013
+ onClick: () => hasChildren && actions.toggleExpand(node.key),
1014
+ children: hasChildren ? "\u25B6" : ""
1015
+ }
1016
+ ),
1017
+ isEditing ? /* @__PURE__ */ jsx10(
1018
+ "input",
1019
+ {
1020
+ autoFocus: true,
1021
+ className: "fc-tree__edit-input",
1022
+ value: localEdit,
1023
+ onChange: (e) => setLocalEdit(e.target.value),
1024
+ onBlur: () => actions.commitEdit(node.key, localEdit),
1025
+ onKeyDown: handleEditKeyDown,
1026
+ onClick: (e) => e.stopPropagation()
1027
+ }
1028
+ ) : /* @__PURE__ */ jsx10(
1029
+ "span",
1030
+ {
1031
+ className: "fc-tree__title",
1032
+ onClick: () => {
1033
+ actions.select(node.key);
1034
+ if (hasChildren) actions.toggleExpand(node.key);
1035
+ },
1036
+ onDoubleClick: () => actions.startEdit(node.key),
1037
+ children: node.title
1038
+ }
1039
+ ),
1040
+ !isEditing && /* @__PURE__ */ jsxs8("span", { className: "fc-tree__actions", children: [
1041
+ /* @__PURE__ */ jsx10(
1042
+ "button",
1043
+ {
1044
+ className: "fc-tree__action",
1045
+ title: "\u65B0\u5EFA\u5B50\u5206\u7C7B",
1046
+ onClick: (e) => {
1047
+ e.stopPropagation();
1048
+ actions.requestCreate(node.key).then();
1049
+ },
1050
+ children: "+"
1051
+ }
1052
+ ),
1053
+ /* @__PURE__ */ jsx10(
1054
+ "button",
1055
+ {
1056
+ className: "fc-tree__action",
1057
+ title: "\u91CD\u547D\u540D\uFF08\u53CC\u51FB\u4E5F\u53EF\uFF09",
1058
+ onClick: (e) => {
1059
+ e.stopPropagation();
1060
+ actions.startEdit(node.key);
1061
+ },
1062
+ children: "\u270F"
1063
+ }
1064
+ ),
1065
+ /* @__PURE__ */ jsx10(
1066
+ "button",
1067
+ {
1068
+ className: "fc-tree__action fc-tree__action--danger",
1069
+ title: "\u5220\u9664",
1070
+ onClick: (e) => {
1071
+ e.stopPropagation();
1072
+ actions.requestDelete(node);
1073
+ },
1074
+ children: "\u{1F5D1}"
1075
+ }
1076
+ )
1077
+ ] })
1078
+ ]
1079
+ }
1080
+ ),
1081
+ hasChildren && /* @__PURE__ */ jsx10(CollapsePanel, { open: isExpanded, children: node.children.map((child) => /* @__PURE__ */ jsx10(TreeNodeItem2, { node: child, level: level + 1, hidden: hidden || !isExpanded }, child.key)) })
1082
+ ] }) });
1083
+ });
1084
+ function Tree({
1085
+ treeData,
1086
+ onRename,
1087
+ onCreate,
1088
+ onDelete,
1089
+ onMove,
1090
+ onSelect,
1091
+ selectedKey,
1092
+ searchable = false,
1093
+ scrollHeight = "400px",
1094
+ className = ""
1095
+ }) {
1096
+ const [expandedKeys, setExpandedKeys] = useState9(/* @__PURE__ */ new Set());
1097
+ const [editingKey, setEditingKey] = useState9(null);
1098
+ const [deleteTarget, setDeleteTarget] = useState9(null);
1099
+ const [searchValue, setSearchValue] = useState9("");
1100
+ const [dndState, setDndState] = useState9({
1101
+ dropTargetKey: null,
1102
+ dropPosition: null,
1103
+ dragKey: null
1104
+ });
1105
+ const dropRef = useRef4({ key: null, pos: null });
1106
+ const pointerYRef = useRef4(0);
1107
+ useEffect4(() => {
1108
+ const handler = (e) => {
1109
+ pointerYRef.current = e.clientY;
1110
+ };
1111
+ window.addEventListener("pointermove", handler);
1112
+ return () => window.removeEventListener("pointermove", handler);
1113
+ }, []);
1114
+ const sensors = useSensors(
1115
+ useSensor(PointerSensor, { activationConstraint: { distance: 8 } })
1116
+ );
1117
+ const toggleExpand = useCallback4((key) => {
1118
+ setExpandedKeys((prev) => {
1119
+ const next = new Set(prev);
1120
+ next.has(key) ? next.delete(key) : next.add(key);
1121
+ return next;
1122
+ });
1123
+ }, []);
1124
+ const select = useCallback4((key) => onSelect?.(key), [onSelect]);
1125
+ const startEdit = useCallback4((key) => setEditingKey(key), []);
1126
+ const cancelEdit = useCallback4(() => setEditingKey(null), []);
1127
+ const commitEdit = useCallback4(async (key, newTitle) => {
1128
+ setEditingKey(null);
1129
+ const trimmed = newTitle.trim();
1130
+ if (trimmed && onRename) await onRename(key, trimmed);
1131
+ }, [onRename]);
1132
+ const requestCreate = useCallback4(async (parentKey) => {
1133
+ if (!onCreate) return;
1134
+ const newKey = await onCreate(parentKey);
1135
+ if (parentKey) setExpandedKeys((prev) => /* @__PURE__ */ new Set([...prev, parentKey]));
1136
+ setEditingKey(newKey);
1137
+ }, [onCreate]);
1138
+ const requestDelete = useCallback4((node) => {
1139
+ setDeleteTarget(node);
1140
+ }, []);
1141
+ const treeDataRef = useRef4(treeData);
1142
+ const expandedRef = useRef4(expandedKeys);
1143
+ treeDataRef.current = treeData;
1144
+ expandedRef.current = expandedKeys;
1145
+ const handleDragStart = useCallback4(({ active }) => {
1146
+ dropRef.current = { key: null, pos: null };
1147
+ setDndState({ dropTargetKey: null, dropPosition: null, dragKey: active.id });
1148
+ }, []);
1149
+ const handleDragMove = useCallback4(({ over, active }) => {
1150
+ if (!over || over.id === active.id) {
1151
+ if (dropRef.current.key !== null) {
1152
+ dropRef.current = { key: null, pos: null };
1153
+ setDndState((prev) => ({ ...prev, dropTargetKey: null, dropPosition: null }));
1154
+ }
1155
+ return;
1156
+ }
1157
+ const targetKey = over.id;
1158
+ if (isDescendantOf(treeDataRef.current, active.id, targetKey)) {
1159
+ if (dropRef.current.key !== null) {
1160
+ dropRef.current = { key: null, pos: null };
1161
+ setDndState((prev) => ({ ...prev, dropTargetKey: null, dropPosition: null }));
1162
+ }
1163
+ return;
1164
+ }
1165
+ const rect = over.rect;
1166
+ const y = pointerYRef.current;
1167
+ const ratio = Math.max(0, Math.min(1, (y - rect.top) / rect.height));
1168
+ let position;
1169
+ if (ratio < 0.2) position = "before";
1170
+ else if (ratio > 0.8) position = "after";
1171
+ else position = "into";
1172
+ if (dropRef.current.key === targetKey && dropRef.current.pos === position) return;
1173
+ dropRef.current = { key: targetKey, pos: position };
1174
+ setDndState((prev) => ({ ...prev, dropTargetKey: targetKey, dropPosition: position }));
1175
+ }, []);
1176
+ const handleDragEnd = useCallback4(({ active }) => {
1177
+ const { key: target, pos: position } = dropRef.current;
1178
+ dropRef.current = { key: null, pos: null };
1179
+ setDndState({ dropTargetKey: null, dropPosition: null, dragKey: null });
1180
+ if (!target || !position || active.id === target) return;
1181
+ onMove?.(active.id, target, position);
1182
+ }, [onMove]);
1183
+ const handleDragCancel = useCallback4(() => {
1184
+ dropRef.current = { key: null, pos: null };
1185
+ setDndState({ dropTargetKey: null, dropPosition: null, dragKey: null });
1186
+ }, []);
1187
+ const displayData = useMemo2(() => {
1188
+ if (!searchValue) return treeData;
1189
+ const kw = searchValue.toLowerCase();
1190
+ const filter = (nodes) => nodes.reduce((acc, node) => {
1191
+ const match = node.title.toLowerCase().includes(kw);
1192
+ const filteredChildren = filter(node.children);
1193
+ if (match || filteredChildren.length > 0)
1194
+ acc.push({ ...node, children: filteredChildren });
1195
+ return acc;
1196
+ }, []);
1197
+ return filter(treeData);
1198
+ }, [treeData, searchValue]);
1199
+ const actionsValue = useMemo2(() => ({
1200
+ toggleExpand,
1201
+ select,
1202
+ startEdit,
1203
+ commitEdit,
1204
+ cancelEdit,
1205
+ requestCreate,
1206
+ requestDelete
1207
+ }), [toggleExpand, select, startEdit, commitEdit, cancelEdit, requestCreate, requestDelete]);
1208
+ const stateValue = useMemo2(() => ({
1209
+ expandedKeys,
1210
+ selectedKey: selectedKey ?? null,
1211
+ editingKey
1212
+ }), [expandedKeys, selectedKey, editingKey]);
1213
+ return /* @__PURE__ */ jsx10(TreeActionsCtx.Provider, { value: actionsValue, children: /* @__PURE__ */ jsx10(TreeStateCtx.Provider, { value: stateValue, children: /* @__PURE__ */ jsx10(DndStateCtx.Provider, { value: dndState, children: /* @__PURE__ */ jsxs8(
1214
+ DndContext,
1215
+ {
1216
+ sensors,
1217
+ onDragStart: handleDragStart,
1218
+ onDragMove: handleDragMove,
1219
+ onDragEnd: handleDragEnd,
1220
+ onDragCancel: handleDragCancel,
1221
+ children: [
1222
+ /* @__PURE__ */ jsxs8("div", { className: `fc-tree ${className}`, children: [
1223
+ searchable && /* @__PURE__ */ jsxs8("div", { className: "fc-tree__search", children: [
1224
+ /* @__PURE__ */ jsx10(
1225
+ "input",
1226
+ {
1227
+ type: "text",
1228
+ value: searchValue,
1229
+ onChange: (e) => setSearchValue(e.target.value),
1230
+ placeholder: "\u641C\u7D22\u5206\u7C7B\u2026",
1231
+ className: "fc-tree__search-input"
1232
+ }
1233
+ ),
1234
+ searchValue && /* @__PURE__ */ jsx10("button", { className: "fc-tree__search-clear", onClick: () => setSearchValue(""), children: "\u2715" })
1235
+ ] }),
1236
+ /* @__PURE__ */ jsx10(RollingBox, { showThumb: "show", style: { height: scrollHeight }, children: /* @__PURE__ */ jsxs8("div", { className: "fc-tree__list", children: [
1237
+ displayData.length === 0 && /* @__PURE__ */ jsx10("div", { className: "fc-tree__empty", children: searchValue ? "\u65E0\u5339\u914D\u5206\u7C7B" : "\u6682\u65E0\u5206\u7C7B" }),
1238
+ displayData.map((node) => /* @__PURE__ */ jsx10(TreeNodeItem, { node, level: 0 }, node.key))
1239
+ ] }) }),
1240
+ onCreate && /* @__PURE__ */ jsx10("div", { className: "fc-tree__add-root", children: /* @__PURE__ */ jsx10(
1241
+ "button",
1242
+ {
1243
+ className: "fc-tree__add-root-btn",
1244
+ onClick: () => requestCreate(null),
1245
+ children: "+ \u65B0\u5EFA\u9876\u7EA7\u5206\u7C7B"
1246
+ }
1247
+ ) })
1248
+ ] }),
1249
+ /* @__PURE__ */ jsx10(
1250
+ DeleteDialog,
1251
+ {
1252
+ node: deleteTarget,
1253
+ onClose: () => setDeleteTarget(null),
1254
+ onDelete: async (key, mode) => {
1255
+ await onDelete?.(key, mode);
1256
+ setDeleteTarget(null);
1257
+ }
1258
+ }
1259
+ )
1260
+ ]
1261
+ }
1262
+ ) }) }) });
1263
+ }
1264
+
1265
+ // src/components/Tree/OrphanDialog.tsx
1266
+ import { useState as useState10 } from "react";
1267
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
1268
+ function OrphanDialog({ orphans, onResolve, onClose }) {
1269
+ const [resolutions, setResolutions] = useState10(
1270
+ () => Object.fromEntries(orphans.map((o) => [o.key, "lift"]))
1271
+ );
1272
+ if (orphans.length === 0) return null;
1273
+ const set = (key, val) => setResolutions((prev) => ({ ...prev, [key]: val }));
1274
+ return /* @__PURE__ */ jsx11("div", { className: "fc-dialog-overlay", children: /* @__PURE__ */ jsxs9("div", { className: "fc-dialog fc-dialog--wide", children: [
1275
+ /* @__PURE__ */ jsxs9("div", { className: "fc-dialog__header", children: [
1276
+ /* @__PURE__ */ jsx11("span", { className: "fc-dialog__icon", children: "\u{1F50D}" }),
1277
+ /* @__PURE__ */ jsxs9("h3", { className: "fc-dialog__title fc-dialog__title--warning", children: [
1278
+ "\u68C0\u6D4B\u5230 ",
1279
+ orphans.length,
1280
+ " \u4E2A\u5B64\u7ACB\u5206\u7C7B"
1281
+ ] })
1282
+ ] }),
1283
+ /* @__PURE__ */ jsx11("p", { className: "fc-dialog__desc", children: "\u4EE5\u4E0B\u5206\u7C7B\u7684\u7236\u5206\u7C7B\u5DF2\u4E0D\u5B58\u5728\uFF0C\u53EF\u80FD\u662F\u6570\u636E\u8FC1\u79FB\u6216\u5F02\u5E38\u5220\u9664\u5BFC\u81F4\u3002 \u8FD9\u4E9B\u5206\u7C7B\u76EE\u524D\u4E0D\u4F1A\u663E\u793A\u5728\u6811\u4E2D\uFF0C\u8BF7\u9009\u62E9\u5904\u7406\u65B9\u5F0F\uFF1A" }),
1284
+ /* @__PURE__ */ jsx11("div", { className: "fc-orphan-list", children: orphans.map((node) => /* @__PURE__ */ jsxs9("div", { className: "fc-orphan-item", children: [
1285
+ /* @__PURE__ */ jsx11("span", { className: "fc-orphan-name", title: node.key, children: node.title }),
1286
+ /* @__PURE__ */ jsxs9("span", { className: "fc-orphan-id", children: [
1287
+ "id: ",
1288
+ node.key.slice(0, 8),
1289
+ "\u2026"
1290
+ ] }),
1291
+ /* @__PURE__ */ jsxs9("div", { className: "fc-orphan-radios", children: [
1292
+ /* @__PURE__ */ jsxs9("label", { className: `fc-orphan-radio ${resolutions[node.key] === "lift" ? "is-active" : ""}`, children: [
1293
+ /* @__PURE__ */ jsx11(
1294
+ "input",
1295
+ {
1296
+ type: "radio",
1297
+ name: node.key,
1298
+ checked: resolutions[node.key] === "lift",
1299
+ onChange: () => set(node.key, "lift")
1300
+ }
1301
+ ),
1302
+ "\u63D0\u5347\u4E3A\u6839\u5206\u7C7B"
1303
+ ] }),
1304
+ /* @__PURE__ */ jsxs9("label", { className: `fc-orphan-radio fc-orphan-radio--danger ${resolutions[node.key] === "remove" ? "is-active" : ""}`, children: [
1305
+ /* @__PURE__ */ jsx11(
1306
+ "input",
1307
+ {
1308
+ type: "radio",
1309
+ name: node.key,
1310
+ checked: resolutions[node.key] === "remove",
1311
+ onChange: () => set(node.key, "remove")
1312
+ }
1313
+ ),
1314
+ "\u5220\u9664"
1315
+ ] })
1316
+ ] })
1317
+ ] }, node.key)) }),
1318
+ /* @__PURE__ */ jsxs9("div", { className: "fc-dialog__footer", children: [
1319
+ /* @__PURE__ */ jsx11("button", { className: "fc-dialog__btn", onClick: onClose, children: "\u6682\u65F6\u5FFD\u7565" }),
1320
+ /* @__PURE__ */ jsx11(
1321
+ "button",
1322
+ {
1323
+ className: "fc-dialog__btn fc-dialog__btn--primary",
1324
+ onClick: () => onResolve(resolutions),
1325
+ children: "\u786E\u8BA4\u5904\u7406"
1326
+ }
1327
+ )
1328
+ ] })
1329
+ ] }) });
1330
+ }
1331
+
1332
+ // src/components/Avatar/Avatar.tsx
1333
+ import { useState as useState11, useMemo as useMemo3, useCallback as useCallback5, forwardRef as forwardRef2, useEffect as useEffect5 } from "react";
1334
+ import { jsx as jsx12 } from "react/jsx-runtime";
1335
+ var SIZE_MAP = {
1336
+ xs: 20,
1337
+ sm: 28,
1338
+ md: 40,
1339
+ lg: 56,
1340
+ xl: 72
1341
+ };
1342
+ var DEFAULT_ICON = "M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z";
1343
+ var useAvatarStyles = (size, shape, bordered, onClick, loadState, src, children, colorVariant, className, color, customStyle) => {
1344
+ const classes = useMemo3(() => {
1345
+ const classNames = [
1346
+ "ui-avatar",
1347
+ `ui-avatar-${size}`,
1348
+ `ui-avatar-${shape}`,
1349
+ bordered && "ui-avatar-bordered",
1350
+ onClick && "ui-avatar-clickable",
1351
+ loadState === "loading" && "ui-avatar-loading",
1352
+ !src && !children && "ui-avatar-empty",
1353
+ colorVariant && `ui-avatar--${colorVariant}`,
1354
+ className
1355
+ ];
1356
+ return classNames.filter(Boolean).join(" ");
1357
+ }, [size, shape, bordered, onClick, loadState, src, children, colorVariant, className]);
1358
+ const style = useMemo3(() => ({
1359
+ width: SIZE_MAP[size],
1360
+ height: SIZE_MAP[size],
1361
+ fontSize: `${SIZE_MAP[size] * 0.4}px`,
1362
+ ...color && { backgroundColor: color },
1363
+ ...customStyle
1364
+ }), [size, color, customStyle]);
1365
+ return { classes, style };
1366
+ };
1367
+ var useImageLoader = (src, fallbackSrc, onImageLoad, onImageError, onStateChange) => {
1368
+ const [loadState, setLoadState] = useState11("idle");
1369
+ const [currentSrc, setCurrentSrc] = useState11(src);
1370
+ useEffect5(() => {
1371
+ setCurrentSrc(src);
1372
+ setLoadState(src ? "loading" : "idle");
1373
+ }, [src]);
1374
+ useEffect5(() => {
1375
+ onStateChange?.(loadState);
1376
+ }, [loadState, onStateChange]);
1377
+ const handleLoad = useCallback5(() => {
1378
+ setLoadState("loaded");
1379
+ onImageLoad?.();
1380
+ }, [onImageLoad]);
1381
+ const handleError = useCallback5((e) => {
1382
+ if (fallbackSrc && currentSrc !== fallbackSrc) {
1383
+ setCurrentSrc(fallbackSrc);
1384
+ setLoadState("loading");
1385
+ } else {
1386
+ setLoadState("error");
1387
+ onImageError?.(e.nativeEvent);
1388
+ }
1389
+ }, [fallbackSrc, currentSrc, onImageError]);
1390
+ return {
1391
+ loadState,
1392
+ currentSrc,
1393
+ handleLoad,
1394
+ handleError
1395
+ };
1396
+ };
1397
+ var useKeyboardInteraction = (onClick) => {
1398
+ const handleKeyDown = useCallback5((e) => {
1399
+ if (onClick && (e.key === "Enter" || e.key === " ")) {
1400
+ e.preventDefault();
1401
+ onClick(e);
1402
+ }
1403
+ }, [onClick]);
1404
+ return { handleKeyDown };
1405
+ };
1406
+ var renderContent = (currentSrc, fallbackSrc, loadState, alt, lazyLoad2, children, handleLoad, handleError) => {
1407
+ const showImage = currentSrc && loadState !== "error";
1408
+ const showFallback = !showImage && children;
1409
+ const imageSrc = loadState === "error" && currentSrc !== fallbackSrc ? fallbackSrc : currentSrc;
1410
+ if (showImage && imageSrc) {
1411
+ return /* @__PURE__ */ jsx12(
1412
+ "img",
1413
+ {
1414
+ src: imageSrc,
1415
+ alt,
1416
+ className: "ui-avatar-img",
1417
+ loading: lazyLoad2 ? "lazy" : "eager",
1418
+ onLoad: handleLoad,
1419
+ onError: handleError,
1420
+ decoding: "async"
1421
+ }
1422
+ );
1423
+ }
1424
+ if (showFallback) {
1425
+ return /* @__PURE__ */ jsx12("span", { className: "ui-avatar-text", children });
1426
+ }
1427
+ return /* @__PURE__ */ jsx12("span", { className: "ui-avatar-placeholder", children: /* @__PURE__ */ jsx12("svg", { viewBox: "0 0 24 24", width: "40%", height: "40%", fill: "currentColor", opacity: 0.4, children: /* @__PURE__ */ jsx12("path", { d: DEFAULT_ICON }) }) });
1428
+ };
1429
+ var useAriaAttributes = (onClick, loadState, alt) => {
1430
+ return {
1431
+ role: onClick ? "button" : "img",
1432
+ tabIndex: onClick ? 0 : void 0,
1433
+ "aria-label": alt,
1434
+ "aria-busy": loadState === "loading",
1435
+ "data-load-state": loadState
1436
+ };
1437
+ };
1438
+ var Avatar = forwardRef2(
1439
+ ({
1440
+ children,
1441
+ src,
1442
+ fallbackSrc,
1443
+ color,
1444
+ colorVariant,
1445
+ size = "md",
1446
+ shape = "circle",
1447
+ alt = "\u7528\u6237\u5934\u50CF",
1448
+ lazyLoad: lazyLoad2 = false,
1449
+ onImageLoad,
1450
+ onImageError,
1451
+ onStateChange,
1452
+ bordered = false,
1453
+ className = "",
1454
+ onClick,
1455
+ style: customStyle,
1456
+ ...restProps
1457
+ }, ref) => {
1458
+ const { loadState, currentSrc, handleLoad, handleError } = useImageLoader(
1459
+ src,
1460
+ fallbackSrc,
1461
+ onImageLoad,
1462
+ onImageError,
1463
+ onStateChange
1464
+ );
1465
+ const { classes, style } = useAvatarStyles(
1466
+ size,
1467
+ shape,
1468
+ bordered,
1469
+ onClick,
1470
+ loadState,
1471
+ src,
1472
+ children,
1473
+ colorVariant,
1474
+ className,
1475
+ color,
1476
+ customStyle
1477
+ );
1478
+ const { handleKeyDown } = useKeyboardInteraction(onClick);
1479
+ const ariaAttributes = useAriaAttributes(onClick, loadState, alt);
1480
+ const content = renderContent(
1481
+ currentSrc,
1482
+ fallbackSrc,
1483
+ loadState,
1484
+ alt,
1485
+ lazyLoad2,
1486
+ children,
1487
+ handleLoad,
1488
+ handleError
1489
+ );
1490
+ return /* @__PURE__ */ jsx12(
1491
+ "div",
1492
+ {
1493
+ ref,
1494
+ className: classes,
1495
+ style,
1496
+ onClick,
1497
+ onKeyDown: handleKeyDown,
1498
+ ...ariaAttributes,
1499
+ ...restProps,
1500
+ children: content
1501
+ }
1502
+ );
1503
+ }
1504
+ );
1505
+ Avatar.displayName = "Avatar";
1506
+
1507
+ // src/components/ListGroup/ListGroup.tsx
1508
+ import { forwardRef as forwardRef3, useMemo as useMemo4, useCallback as useCallback6 } from "react";
1509
+ import { jsx as jsx13 } from "react/jsx-runtime";
1510
+ var combineClassNames = (...classNames) => {
1511
+ return classNames.filter(Boolean).join(" ");
1512
+ };
1513
+ var ListGroupItem = forwardRef3(
1514
+ ({
1515
+ active = false,
1516
+ disabled = false,
1517
+ onClick,
1518
+ className,
1519
+ children,
1520
+ ...props
1521
+ }, ref) => {
1522
+ const handleClick = useCallback6((e) => {
1523
+ if (disabled) return;
1524
+ onClick?.(e);
1525
+ }, [disabled, onClick]);
1526
+ const classNames = useMemo4(() => {
1527
+ return combineClassNames(
1528
+ "fc-list-group-item",
1529
+ active && "fc-list-group-item--active",
1530
+ disabled && "fc-list-group-item--disabled",
1531
+ className
1532
+ );
1533
+ }, [active, disabled, className]);
1534
+ return /* @__PURE__ */ jsx13(
1535
+ "li",
1536
+ {
1537
+ ref,
1538
+ className: classNames,
1539
+ onClick: handleClick,
1540
+ "aria-disabled": disabled,
1541
+ "aria-selected": active,
1542
+ role: onClick ? "button" : void 0,
1543
+ tabIndex: onClick && !disabled ? 0 : void 0,
1544
+ ...props,
1545
+ children
1546
+ }
1547
+ );
1548
+ }
1549
+ );
1550
+ ListGroupItem.displayName = "ListGroupItem";
1551
+ var ListGroup = forwardRef3(
1552
+ ({
1553
+ bordered = true,
1554
+ flush = false,
1555
+ className,
1556
+ children,
1557
+ ...props
1558
+ }, ref) => {
1559
+ const classNames = useMemo4(() => {
1560
+ return combineClassNames(
1561
+ "fc-list-group",
1562
+ bordered && "fc-list-group--bordered",
1563
+ flush && "fc-list-group--flush",
1564
+ className
1565
+ );
1566
+ }, [bordered, flush, className]);
1567
+ return /* @__PURE__ */ jsx13(
1568
+ "ul",
1569
+ {
1570
+ ref,
1571
+ className: classNames,
1572
+ role: "list",
1573
+ ...props,
1574
+ children
1575
+ }
1576
+ );
1577
+ }
1578
+ );
1579
+ ListGroup.displayName = "ListGroup";
1580
+
1581
+ // src/components/VirtualList/VirtualList.tsx
1582
+ import { useState as useState12, useRef as useRef5, useCallback as useCallback7, useMemo as useMemo5, useEffect as useEffect6 } from "react";
1583
+ import { jsx as jsx14 } from "react/jsx-runtime";
1584
+ function VirtualList({
1585
+ data,
1586
+ height,
1587
+ itemHeight,
1588
+ renderItem,
1589
+ className = "",
1590
+ overscan = 3,
1591
+ showScrollbar = true,
1592
+ onScrollEnd,
1593
+ style
1594
+ }) {
1595
+ const containerRef = useRef5(null);
1596
+ const [scrollTop, setScrollTop] = useState12(0);
1597
+ const totalHeight = data.length * itemHeight;
1598
+ const handleScroll = useCallback7((e) => {
1599
+ const newScrollTop = e.currentTarget.scrollTop;
1600
+ setScrollTop(newScrollTop);
1601
+ if (onScrollEnd) {
1602
+ const scrollHeight = e.currentTarget.scrollHeight;
1603
+ const clientHeight = e.currentTarget.clientHeight;
1604
+ if (newScrollTop + clientHeight >= scrollHeight - 5) {
1605
+ onScrollEnd();
1606
+ }
1607
+ }
1608
+ }, [onScrollEnd]);
1609
+ const visibleRange = useMemo5(() => {
1610
+ const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
1611
+ const endIndex = Math.min(
1612
+ data.length,
1613
+ Math.ceil((scrollTop + height) / itemHeight) + overscan
1614
+ );
1615
+ return { startIndex, endIndex };
1616
+ }, [scrollTop, height, itemHeight, data.length, overscan]);
1617
+ const visibleData = useMemo5(() => {
1618
+ return data.slice(visibleRange.startIndex, visibleRange.endIndex);
1619
+ }, [data, visibleRange]);
1620
+ const offsetY = visibleRange.startIndex * itemHeight;
1621
+ useEffect6(() => {
1622
+ if (!showScrollbar && containerRef.current) {
1623
+ containerRef.current.style.scrollbarWidth = "none";
1624
+ }
1625
+ }, [showScrollbar]);
1626
+ const classNames = [
1627
+ "fc-virtual-list",
1628
+ !showScrollbar && "fc-virtual-list--hide-scrollbar",
1629
+ className
1630
+ ].filter(Boolean).join(" ");
1631
+ return /* @__PURE__ */ jsx14(
1632
+ "div",
1633
+ {
1634
+ ref: containerRef,
1635
+ className: classNames,
1636
+ style: { height: `${height}px`, ...style },
1637
+ onScroll: handleScroll,
1638
+ children: /* @__PURE__ */ jsx14(
1639
+ "div",
1640
+ {
1641
+ className: "fc-virtual-list__container",
1642
+ style: { height: `${totalHeight}px` },
1643
+ children: /* @__PURE__ */ jsx14(
1644
+ "div",
1645
+ {
1646
+ className: "fc-virtual-list__content",
1647
+ style: { transform: `translateY(${offsetY}px)` },
1648
+ children: visibleData.map((item, idx) => {
1649
+ const actualIndex = visibleRange.startIndex + idx;
1650
+ return /* @__PURE__ */ jsx14(
1651
+ "div",
1652
+ {
1653
+ className: "fc-virtual-list__item",
1654
+ style: { height: `${itemHeight}px` },
1655
+ children: renderItem(item, actualIndex)
1656
+ },
1657
+ actualIndex
1658
+ );
1659
+ })
1660
+ }
1661
+ )
1662
+ }
1663
+ )
19
1664
  }
20
1665
  );
21
1666
  }
1667
+
1668
+ // src/components/Alert/AlertContext.tsx
1669
+ import { createContext as createContext3, useContext as useContext3, useState as useState13 } from "react";
1670
+ import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
1671
+ var AlertContext = createContext3(null);
1672
+ var ICONS = {
1673
+ success: /* @__PURE__ */ jsxs10(
1674
+ "svg",
1675
+ {
1676
+ className: "fc-alert__icon",
1677
+ xmlns: "http://www.w3.org/2000/svg",
1678
+ viewBox: "0 0 24 24",
1679
+ fill: "none",
1680
+ stroke: "currentColor",
1681
+ strokeWidth: "2",
1682
+ strokeLinecap: "round",
1683
+ strokeLinejoin: "round",
1684
+ children: [
1685
+ /* @__PURE__ */ jsx15("circle", { cx: "12", cy: "12", r: "10" }),
1686
+ /* @__PURE__ */ jsx15("polyline", { points: "17 9 11 17 6 12" })
1687
+ ]
1688
+ }
1689
+ ),
1690
+ error: /* @__PURE__ */ jsxs10(
1691
+ "svg",
1692
+ {
1693
+ className: "fc-alert__icon",
1694
+ xmlns: "http://www.w3.org/2000/svg",
1695
+ viewBox: "0 0 24 24",
1696
+ fill: "none",
1697
+ stroke: "currentColor",
1698
+ strokeWidth: "2",
1699
+ strokeLinecap: "round",
1700
+ strokeLinejoin: "round",
1701
+ children: [
1702
+ /* @__PURE__ */ jsx15("circle", { cx: "12", cy: "12", r: "10" }),
1703
+ /* @__PURE__ */ jsx15("line", { x1: "15", y1: "9", x2: "9", y2: "15" }),
1704
+ /* @__PURE__ */ jsx15("line", { x1: "9", y1: "9", x2: "15", y2: "15" })
1705
+ ]
1706
+ }
1707
+ ),
1708
+ warning: /* @__PURE__ */ jsxs10(
1709
+ "svg",
1710
+ {
1711
+ className: "fc-alert__icon",
1712
+ xmlns: "http://www.w3.org/2000/svg",
1713
+ viewBox: "0 0 24 24",
1714
+ fill: "none",
1715
+ stroke: "currentColor",
1716
+ strokeWidth: "2",
1717
+ strokeLinecap: "round",
1718
+ strokeLinejoin: "round",
1719
+ children: [
1720
+ /* @__PURE__ */ jsx15("path", { d: "M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" }),
1721
+ /* @__PURE__ */ jsx15("line", { x1: "12", y1: "9", x2: "12", y2: "13" }),
1722
+ /* @__PURE__ */ jsx15("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
1723
+ ]
1724
+ }
1725
+ ),
1726
+ info: /* @__PURE__ */ jsxs10(
1727
+ "svg",
1728
+ {
1729
+ className: "fc-alert__icon",
1730
+ xmlns: "http://www.w3.org/2000/svg",
1731
+ viewBox: "0 0 24 24",
1732
+ fill: "none",
1733
+ stroke: "currentColor",
1734
+ strokeWidth: "2",
1735
+ strokeLinecap: "round",
1736
+ strokeLinejoin: "round",
1737
+ children: [
1738
+ /* @__PURE__ */ jsx15("circle", { cx: "12", cy: "12", r: "10" }),
1739
+ /* @__PURE__ */ jsx15("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
1740
+ /* @__PURE__ */ jsx15("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
1741
+ ]
1742
+ }
1743
+ )
1744
+ };
1745
+ function AlertProvider({ children }) {
1746
+ const [props, setProps] = useState13({
1747
+ msg: "",
1748
+ type: "info",
1749
+ visible: false,
1750
+ choice: () => {
1751
+ }
1752
+ });
1753
+ const showAlert = (msg, type, confirm = false) => new Promise((resolve) => {
1754
+ setProps({
1755
+ msg,
1756
+ type,
1757
+ visible: true,
1758
+ confirm,
1759
+ choice: (res) => {
1760
+ setProps((p) => ({ ...p, visible: false }));
1761
+ resolve(res);
1762
+ }
1763
+ });
1764
+ });
1765
+ return /* @__PURE__ */ jsxs10(AlertContext.Provider, { value: { showAlert }, children: [
1766
+ children,
1767
+ props.visible && /* @__PURE__ */ jsx15("div", { className: "fc-alert-overlay", children: /* @__PURE__ */ jsxs10("div", { className: `fc-alert fc-alert--${props.type}`, children: [
1768
+ /* @__PURE__ */ jsxs10("div", { className: "fc-alert__header", children: [
1769
+ ICONS[props.type],
1770
+ /* @__PURE__ */ jsx15("span", { className: "fc-alert__title", children: "\u63D0\u793A" })
1771
+ ] }),
1772
+ /* @__PURE__ */ jsx15(RollingBox, { className: "fc-alert__msg", children: props.msg }),
1773
+ /* @__PURE__ */ jsxs10("div", { className: "fc-alert__footer", children: [
1774
+ props.confirm && /* @__PURE__ */ jsx15(
1775
+ Button,
1776
+ {
1777
+ variant: "secondary",
1778
+ size: "sm",
1779
+ onClick: () => props.choice("no"),
1780
+ children: "\u53D6\u6D88"
1781
+ }
1782
+ ),
1783
+ /* @__PURE__ */ jsx15(
1784
+ Button,
1785
+ {
1786
+ variant: "primary",
1787
+ size: "sm",
1788
+ onClick: () => props.choice("yes"),
1789
+ children: "\u786E\u5B9A"
1790
+ }
1791
+ )
1792
+ ] })
1793
+ ] }) })
1794
+ ] });
1795
+ }
1796
+ var useAlert = () => useContext3(AlertContext);
1797
+
1798
+ // src/components/LazyLoad/LazyLoad.tsx
1799
+ import { lazy, Suspense } from "react";
1800
+ import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
1801
+ function lazyLoad(importFn, options = {}) {
1802
+ const { fallback = /* @__PURE__ */ jsx16(LazySpinner, {}), timeout = 1e4 } = options;
1803
+ const LazyComponent = lazy(() => {
1804
+ let timeoutId;
1805
+ const loadPromise = Promise.race([
1806
+ importFn(),
1807
+ new Promise((_, reject) => {
1808
+ timeoutId = setTimeout(() => reject(new Error(`\u52A0\u8F7D\u8D85\u65F6`)), timeout);
1809
+ })
1810
+ ]).finally(() => clearTimeout(timeoutId));
1811
+ return loadPromise;
1812
+ });
1813
+ return (props) => /* @__PURE__ */ jsx16(Suspense, { fallback, children: /* @__PURE__ */ jsx16(LazyComponent, { ...props }) });
1814
+ }
1815
+ function LazySpinner() {
1816
+ return /* @__PURE__ */ jsxs11("div", { className: "fc-lazy-spinner", children: [
1817
+ /* @__PURE__ */ jsx16("div", { className: "fc-lazy-spinner-dot" }),
1818
+ /* @__PURE__ */ jsx16("div", { className: "fc-lazy-spinner-dot" }),
1819
+ /* @__PURE__ */ jsx16("div", { className: "fc-lazy-spinner-dot" })
1820
+ ] });
1821
+ }
1822
+
1823
+ // src/components/Card/Card.tsx
1824
+ import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
1825
+ var Card = ({
1826
+ image,
1827
+ imageSlot,
1828
+ imageHeight = 200,
1829
+ title,
1830
+ description,
1831
+ actions,
1832
+ extraInfo,
1833
+ variant = "default",
1834
+ hoverable = false,
1835
+ disabled = false,
1836
+ className = "",
1837
+ style,
1838
+ onClick
1839
+ }) => {
1840
+ const handleClick = () => {
1841
+ if (!disabled && onClick) {
1842
+ onClick();
1843
+ }
1844
+ };
1845
+ const classes = [
1846
+ "fc-card",
1847
+ `fc-card--${variant}`,
1848
+ hoverable && "fc-card--hoverable",
1849
+ disabled && "fc-card--disabled",
1850
+ onClick && "fc-card--clickable",
1851
+ className
1852
+ ].filter(Boolean).join(" ");
1853
+ const renderImage = () => {
1854
+ if (imageSlot) return imageSlot;
1855
+ if (image) {
1856
+ return /* @__PURE__ */ jsx17(
1857
+ "img",
1858
+ {
1859
+ className: "fc-card__image",
1860
+ src: image,
1861
+ alt: typeof title === "string" ? title : "card image"
1862
+ }
1863
+ );
1864
+ }
1865
+ return null;
1866
+ };
1867
+ return /* @__PURE__ */ jsxs12("div", { className: classes, style, onClick: handleClick, children: [
1868
+ renderImage() && /* @__PURE__ */ jsx17(
1869
+ "div",
1870
+ {
1871
+ className: "fc-card__image-wrapper",
1872
+ style: { height: imageHeight },
1873
+ children: renderImage()
1874
+ }
1875
+ ),
1876
+ /* @__PURE__ */ jsxs12("div", { className: "fc-card__content", children: [
1877
+ title && /* @__PURE__ */ jsx17("div", { className: "fc-card__title", children: title }),
1878
+ description && /* @__PURE__ */ jsx17("div", { className: "fc-card__description", children: description }),
1879
+ extraInfo && /* @__PURE__ */ jsx17("div", { className: "fc-card__extra-info", children: extraInfo }),
1880
+ actions && /* @__PURE__ */ jsx17("div", { className: "fc-card__actions", children: actions })
1881
+ ] })
1882
+ ] });
1883
+ };
1884
+
1885
+ // src/components/Tabs/Tabs.tsx
1886
+ import { useState as useState14 } from "react";
1887
+ import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
1888
+ var Tabs = ({
1889
+ items,
1890
+ activeKey: controlledActiveKey,
1891
+ defaultActiveKey,
1892
+ radius = "md",
1893
+ closable = false,
1894
+ addable = false,
1895
+ onChange,
1896
+ onClose,
1897
+ onAdd,
1898
+ className = "",
1899
+ style
1900
+ }) => {
1901
+ const [internalActiveKey, setInternalActiveKey] = useState14(
1902
+ defaultActiveKey || items[0]?.key || ""
1903
+ );
1904
+ const activeKey = controlledActiveKey !== void 0 ? controlledActiveKey : internalActiveKey;
1905
+ const handleTabClick = (key, disabled) => {
1906
+ if (disabled) return;
1907
+ if (controlledActiveKey === void 0) {
1908
+ setInternalActiveKey(key);
1909
+ }
1910
+ onChange?.(key);
1911
+ };
1912
+ const handleClose = (e, key) => {
1913
+ e.stopPropagation();
1914
+ onClose?.(key);
1915
+ if (key === activeKey) {
1916
+ const currentIndex = items.findIndex((item) => item.key === key);
1917
+ const nextItem = items[currentIndex + 1] || items[currentIndex - 1];
1918
+ if (nextItem && !nextItem.disabled) {
1919
+ handleTabClick(nextItem.key);
1920
+ }
1921
+ }
1922
+ };
1923
+ const classes = [
1924
+ "fc-tabs",
1925
+ `fc-tabs--radius-${radius}`,
1926
+ className
1927
+ ].filter(Boolean).join(" ");
1928
+ return /* @__PURE__ */ jsxs13("div", { className: classes, style, children: [
1929
+ /* @__PURE__ */ jsx18("div", { className: "fc-tabs__nav", children: /* @__PURE__ */ jsxs13("div", { className: "fc-tabs__nav-wrap", children: [
1930
+ items.map((item) => {
1931
+ const isActive = activeKey === item.key;
1932
+ return /* @__PURE__ */ jsxs13(
1933
+ "div",
1934
+ {
1935
+ className: `fc-tabs__tab ${isActive ? "fc-tabs__tab--active" : ""} ${item.disabled ? "fc-tabs__tab--disabled" : ""}`,
1936
+ onClick: () => handleTabClick(item.key, item.disabled),
1937
+ children: [
1938
+ /* @__PURE__ */ jsx18("span", { className: "fc-tabs__tab-label", children: item.label }),
1939
+ closable && /* @__PURE__ */ jsx18(
1940
+ "span",
1941
+ {
1942
+ className: "fc-tabs__tab-close",
1943
+ onClick: (e) => handleClose(e, item.key),
1944
+ children: "\xD7"
1945
+ }
1946
+ )
1947
+ ]
1948
+ },
1949
+ item.key
1950
+ );
1951
+ }),
1952
+ addable && /* @__PURE__ */ jsx18("div", { className: "fc-tabs__add-btn", onClick: onAdd, children: "+" })
1953
+ ] }) }),
1954
+ /* @__PURE__ */ jsx18("div", { className: "fc-tabs__content", children: items.find((item) => item.key === activeKey)?.content })
1955
+ ] });
1956
+ };
1957
+
1958
+ // src/components/Chat/Chat.tsx
1959
+ import { useState as useState15, useRef as useRef6, useEffect as useEffect7, useCallback as useCallback8 } from "react";
1960
+ import { Fragment as Fragment3, jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
1961
+ var Chat = ({
1962
+ messages = [],
1963
+ onSendMessage,
1964
+ placeholder = "\u8F93\u5165\u6D88\u606F...",
1965
+ title = "AI \u52A9\u624B",
1966
+ height = "600px",
1967
+ showHeader = true,
1968
+ showFooter = true,
1969
+ loading = false,
1970
+ disabled = false,
1971
+ theme: propTheme,
1972
+ userName = "\u6211",
1973
+ assistantName = "AI\u52A9\u624B",
1974
+ onTyping,
1975
+ maxInputLength = 2e3,
1976
+ autoFocus = true,
1977
+ enableCopy = true
1978
+ }) => {
1979
+ const [inputValue, setInputValue] = useState15("");
1980
+ const [isTyping, setIsTyping] = useState15(false);
1981
+ const [isSending, setIsSending] = useState15(false);
1982
+ const [currentTheme, setCurrentTheme] = useState15("light");
1983
+ const [copiedMessageId, setCopiedMessageId] = useState15(null);
1984
+ const [copyError, setCopyError] = useState15(null);
1985
+ const messagesEndRef = useRef6(null);
1986
+ const inputRef = useRef6(null);
1987
+ useEffect7(() => {
1988
+ if (propTheme) {
1989
+ setCurrentTheme(propTheme);
1990
+ } else {
1991
+ const checkTheme = () => {
1992
+ const dataTheme = document.documentElement.getAttribute("data-theme");
1993
+ if (dataTheme === "dark") {
1994
+ setCurrentTheme("dark");
1995
+ } else if (dataTheme === "light") {
1996
+ setCurrentTheme("light");
1997
+ } else {
1998
+ const hasDarkClass = document.documentElement.classList.contains("dark") || document.body.classList.contains("dark") || document.documentElement.classList.contains("theme-dark") || document.body.classList.contains("theme-dark");
1999
+ setCurrentTheme(hasDarkClass ? "dark" : "light");
2000
+ }
2001
+ };
2002
+ checkTheme();
2003
+ const observer = new MutationObserver(checkTheme);
2004
+ observer.observe(document.documentElement, {
2005
+ attributes: true,
2006
+ attributeFilter: ["data-theme", "class"]
2007
+ });
2008
+ observer.observe(document.body, {
2009
+ attributes: true,
2010
+ attributeFilter: ["class"]
2011
+ });
2012
+ return () => observer.disconnect();
2013
+ }
2014
+ }, [propTheme]);
2015
+ useEffect7(() => {
2016
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
2017
+ }, [messages, loading]);
2018
+ useEffect7(() => {
2019
+ if (autoFocus && !disabled && inputRef.current) {
2020
+ inputRef.current.focus();
2021
+ }
2022
+ }, [autoFocus, disabled]);
2023
+ useEffect7(() => {
2024
+ if (copyError) {
2025
+ const timer = setTimeout(() => {
2026
+ setCopyError(null);
2027
+ }, 2e3);
2028
+ return () => clearTimeout(timer);
2029
+ }
2030
+ }, [copyError]);
2031
+ const handleCopyMessage = useCallback8(async (messageId, content) => {
2032
+ if (!navigator.clipboard) {
2033
+ setCopyError("\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301\u590D\u5236\u529F\u80FD");
2034
+ return;
2035
+ }
2036
+ try {
2037
+ await navigator.clipboard.writeText(content);
2038
+ setCopiedMessageId(messageId);
2039
+ setTimeout(() => {
2040
+ setCopiedMessageId(null);
2041
+ }, 2e3);
2042
+ } catch (err) {
2043
+ console.error("\u590D\u5236\u5931\u8D25:", err);
2044
+ if (err instanceof Error) {
2045
+ if (err.name === "NotAllowedError") {
2046
+ setCopyError("\u9700\u8981\u526A\u8D34\u677F\u6743\u9650\uFF0C\u8BF7\u5141\u8BB8\u540E\u91CD\u8BD5");
2047
+ } else if (err.name === "SecurityError") {
2048
+ setCopyError("\u51FA\u4E8E\u5B89\u5168\u539F\u56E0\uFF0C\u65E0\u6CD5\u590D\u5236\u5185\u5BB9");
2049
+ } else {
2050
+ setCopyError("\u590D\u5236\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5");
2051
+ }
2052
+ } else {
2053
+ setCopyError("\u590D\u5236\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5");
2054
+ }
2055
+ setTimeout(() => {
2056
+ setCopyError(null);
2057
+ }, 3e3);
2058
+ }
2059
+ }, []);
2060
+ const handleSend = useCallback8(() => {
2061
+ const trimmedMessage = inputValue.trim();
2062
+ if (!trimmedMessage || disabled || loading || isSending) return;
2063
+ if (trimmedMessage.length > maxInputLength) {
2064
+ console.warn(`\u6D88\u606F\u8D85\u8FC7\u6700\u5927\u957F\u5EA6\u9650\u5236: ${maxInputLength}`);
2065
+ return;
2066
+ }
2067
+ setIsSending(true);
2068
+ const sendPromise = onSendMessage?.(trimmedMessage);
2069
+ if (sendPromise) {
2070
+ void sendPromise.then(() => {
2071
+ setInputValue("");
2072
+ if (inputRef.current) {
2073
+ inputRef.current.style.height = "auto";
2074
+ }
2075
+ if (isTyping) {
2076
+ setIsTyping(false);
2077
+ onTyping?.(false);
2078
+ }
2079
+ }).catch((error) => {
2080
+ console.error("\u53D1\u9001\u6D88\u606F\u5931\u8D25:", error);
2081
+ }).finally(() => {
2082
+ setIsSending(false);
2083
+ });
2084
+ } else {
2085
+ setInputValue("");
2086
+ if (inputRef.current) {
2087
+ inputRef.current.style.height = "auto";
2088
+ }
2089
+ if (isTyping) {
2090
+ setIsTyping(false);
2091
+ onTyping?.(false);
2092
+ }
2093
+ setIsSending(false);
2094
+ }
2095
+ }, [inputValue, disabled, loading, isSending, onSendMessage, maxInputLength, isTyping, onTyping]);
2096
+ const handleKeyDown = useCallback8((e) => {
2097
+ if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
2098
+ e.preventDefault();
2099
+ handleSend();
2100
+ }
2101
+ }, [handleSend]);
2102
+ const handleInputChange = useCallback8((e) => {
2103
+ const value = e.target.value;
2104
+ if (value.length <= maxInputLength) {
2105
+ setInputValue(value);
2106
+ e.target.style.height = "auto";
2107
+ e.target.style.height = `${Math.min(e.target.scrollHeight, 100)}px`;
2108
+ const hasContent = value.length > 0;
2109
+ if (hasContent !== isTyping) {
2110
+ setIsTyping(hasContent);
2111
+ onTyping?.(hasContent);
2112
+ }
2113
+ }
2114
+ }, [maxInputLength, isTyping, onTyping]);
2115
+ const formatTime = useCallback8((date) => {
2116
+ return new Date(date).toLocaleTimeString("zh-CN", {
2117
+ hour: "2-digit",
2118
+ minute: "2-digit"
2119
+ });
2120
+ }, []);
2121
+ const renderMessage = (message) => {
2122
+ const isUser = message.type === "user";
2123
+ const isSystem = message.type === "system";
2124
+ const isCopied = copiedMessageId === message.id;
2125
+ if (isSystem) {
2126
+ return /* @__PURE__ */ jsx19("div", { className: "chat-message-system", children: /* @__PURE__ */ jsx19("span", { className: "system-content", children: message.content }) }, message.id);
2127
+ }
2128
+ return /* @__PURE__ */ jsxs14("div", { className: `chat-message ${isUser ? "user" : "assistant"}`, children: [
2129
+ !isUser && /* @__PURE__ */ jsx19("div", { className: "message-avatar", children: assistantName[0] }),
2130
+ /* @__PURE__ */ jsxs14("div", { className: "message-content-wrapper", children: [
2131
+ /* @__PURE__ */ jsxs14("div", { className: "message-header", children: [
2132
+ /* @__PURE__ */ jsx19("span", { className: "message-sender", children: isUser ? userName : assistantName }),
2133
+ /* @__PURE__ */ jsx19("span", { className: "message-time", children: formatTime(message.timestamp) })
2134
+ ] }),
2135
+ /* @__PURE__ */ jsxs14("div", { className: "message-bubble", children: [
2136
+ /* @__PURE__ */ jsx19("div", { className: "message-text", children: message.content }),
2137
+ message.status === "sending" && /* @__PURE__ */ jsx19("span", { className: "message-status sending", children: "\u53D1\u9001\u4E2D..." }),
2138
+ message.status === "error" && /* @__PURE__ */ jsx19("span", { className: "message-status error", children: "\u53D1\u9001\u5931\u8D25" })
2139
+ ] }),
2140
+ enableCopy && /* @__PURE__ */ jsx19(
2141
+ "button",
2142
+ {
2143
+ className: `copy-btn ${isCopied ? "copied" : ""}`,
2144
+ onClick: () => handleCopyMessage(message.id, message.content),
2145
+ title: isCopied ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u5185\u5BB9",
2146
+ "aria-label": isCopied ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u5185\u5BB9",
2147
+ children: isCopied ? /* @__PURE__ */ jsxs14(Fragment3, { children: [
2148
+ /* @__PURE__ */ jsx19("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx19("path", { d: "M20 6L9 17L4 12", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }),
2149
+ /* @__PURE__ */ jsx19("span", { children: "\u5DF2\u590D\u5236" })
2150
+ ] }) : /* @__PURE__ */ jsxs14(Fragment3, { children: [
2151
+ /* @__PURE__ */ jsxs14("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
2152
+ /* @__PURE__ */ jsx19("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", stroke: "currentColor", strokeWidth: "2" }),
2153
+ /* @__PURE__ */ jsx19("path", { d: "M5 15H4C2.9 15 2 14.1 2 13V4C2 2.9 2.9 2 4 2H13C14.1 2 15 2.9 15 4V5", stroke: "currentColor", strokeWidth: "2" })
2154
+ ] }),
2155
+ /* @__PURE__ */ jsx19("span", { children: "\u590D\u5236" })
2156
+ ] })
2157
+ }
2158
+ )
2159
+ ] })
2160
+ ] }, message.id);
2161
+ };
2162
+ return /* @__PURE__ */ jsxs14("div", { className: `chat-container ${currentTheme}`, style: { height }, children: [
2163
+ showHeader && /* @__PURE__ */ jsxs14("div", { className: "chat-header", children: [
2164
+ /* @__PURE__ */ jsx19("div", { className: "chat-title", children: title }),
2165
+ /* @__PURE__ */ jsxs14("div", { className: "chat-status", children: [
2166
+ (loading || isSending) && /* @__PURE__ */ jsx19("span", { className: "status-dot" }),
2167
+ /* @__PURE__ */ jsx19("span", { children: loading || isSending ? "AI \u6B63\u5728\u601D\u8003..." : "\u5728\u7EBF" })
2168
+ ] })
2169
+ ] }),
2170
+ /* @__PURE__ */ jsxs14("div", { className: "chat-messages", children: [
2171
+ messages.map(renderMessage),
2172
+ (loading || isSending) && /* @__PURE__ */ jsxs14("div", { className: "chat-message assistant", children: [
2173
+ /* @__PURE__ */ jsx19("div", { className: "message-avatar", children: assistantName[0] }),
2174
+ /* @__PURE__ */ jsxs14("div", { className: "message-content-wrapper", children: [
2175
+ /* @__PURE__ */ jsx19("div", { className: "message-header", children: /* @__PURE__ */ jsx19("span", { className: "message-sender", children: assistantName }) }),
2176
+ /* @__PURE__ */ jsx19("div", { className: "message-bubble", children: /* @__PURE__ */ jsxs14("div", { className: "typing-indicator", children: [
2177
+ /* @__PURE__ */ jsx19("span", {}),
2178
+ /* @__PURE__ */ jsx19("span", {}),
2179
+ /* @__PURE__ */ jsx19("span", {})
2180
+ ] }) })
2181
+ ] })
2182
+ ] }),
2183
+ /* @__PURE__ */ jsx19("div", { ref: messagesEndRef })
2184
+ ] }),
2185
+ copyError && /* @__PURE__ */ jsx19("div", { className: "copy-error-toast", children: copyError }),
2186
+ showFooter && /* @__PURE__ */ jsxs14("div", { className: "chat-footer", children: [
2187
+ /* @__PURE__ */ jsxs14("div", { className: "chat-input-wrapper", children: [
2188
+ /* @__PURE__ */ jsx19(
2189
+ "textarea",
2190
+ {
2191
+ ref: inputRef,
2192
+ className: "chat-input",
2193
+ value: inputValue,
2194
+ onChange: handleInputChange,
2195
+ onKeyDown: handleKeyDown,
2196
+ placeholder,
2197
+ disabled: disabled || loading || isSending,
2198
+ rows: 1,
2199
+ maxLength: maxInputLength,
2200
+ spellCheck: false,
2201
+ autoCorrect: "off",
2202
+ autoCapitalize: "off",
2203
+ autoComplete: "off"
2204
+ }
2205
+ ),
2206
+ /* @__PURE__ */ jsx19(
2207
+ "button",
2208
+ {
2209
+ className: `chat-send-btn ${!inputValue.trim() || disabled || loading || isSending ? "disabled" : ""}`,
2210
+ onClick: handleSend,
2211
+ disabled: !inputValue.trim() || disabled || loading || isSending,
2212
+ children: "\u53D1\u9001"
2213
+ }
2214
+ )
2215
+ ] }),
2216
+ /* @__PURE__ */ jsxs14("div", { className: "chat-tips", children: [
2217
+ /* @__PURE__ */ jsx19("span", { children: "\u6309 Enter \u53D1\u9001\uFF0CShift + Enter \u6362\u884C" }),
2218
+ maxInputLength && /* @__PURE__ */ jsxs14("span", { className: "char-count", children: [
2219
+ inputValue.length,
2220
+ "/",
2221
+ maxInputLength
2222
+ ] })
2223
+ ] })
2224
+ ] })
2225
+ ] });
2226
+ };
22
2227
  export {
23
- Button
2228
+ AlertProvider,
2229
+ Avatar,
2230
+ Button,
2231
+ ButtonGroup,
2232
+ ButtonToolbar,
2233
+ Card,
2234
+ Chat,
2235
+ CheckButton,
2236
+ DeleteDialog,
2237
+ Input,
2238
+ ListGroup,
2239
+ ListGroupItem,
2240
+ OrphanDialog,
2241
+ RollingBox,
2242
+ Select,
2243
+ SideBar,
2244
+ Slider,
2245
+ Tabs,
2246
+ ThemeProvider,
2247
+ Tree,
2248
+ VirtualList,
2249
+ findNodeInfo,
2250
+ flatToTree,
2251
+ isDescendantOf,
2252
+ lazyLoad,
2253
+ useAlert,
2254
+ useTheme
24
2255
  };