flowcloudai-ui 0.1.0 → 0.1.2

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
@@ -16,16 +16,7 @@ function ThemeProvider({ children, defaultTheme = "system", target }) {
16
16
  const resolvedTheme = theme === "system" ? systemTheme : theme;
17
17
  useEffect(() => {
18
18
  const el = target ?? document.documentElement;
19
- el.classList.remove("theme-light", "theme-dark");
20
- el.classList.add(`theme-${resolvedTheme}`);
21
19
  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
20
  }, [resolvedTheme, target]);
30
21
  useEffect(() => {
31
22
  if (theme !== "system") return;
@@ -42,6 +33,7 @@ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
42
33
  function Button({
43
34
  variant = "primary",
44
35
  size = "md",
36
+ radius,
45
37
  disabled = false,
46
38
  loading = false,
47
39
  block = false,
@@ -49,14 +41,44 @@ function Button({
49
41
  iconOnly = false,
50
42
  iconLeft,
51
43
  iconRight,
44
+ background,
45
+ hoverBackground,
46
+ activeBackground,
47
+ color,
48
+ hoverColor,
49
+ activeColor,
50
+ borderColor,
51
+ hoverBorderColor,
52
52
  className,
53
+ style,
53
54
  children,
54
55
  ...props
55
56
  }) {
57
+ const colorVars = {
58
+ "--btn-bg": background,
59
+ "--btn-bg-hover": hoverBackground,
60
+ "--btn-bg-active": activeBackground,
61
+ "--btn-color": color,
62
+ "--btn-color-hover": hoverColor,
63
+ "--btn-color-active": activeColor,
64
+ "--btn-border": borderColor,
65
+ "--btn-border-hover": hoverBorderColor
66
+ };
67
+ const overrideStyle = {};
68
+ for (const [key, value] of Object.entries(colorVars)) {
69
+ if (value !== void 0) {
70
+ overrideStyle[key] = value;
71
+ }
72
+ }
73
+ const mergedStyle = {
74
+ ...overrideStyle,
75
+ ...style
76
+ };
56
77
  const classNames = [
57
78
  "fc-btn",
58
79
  `fc-btn--${variant}`,
59
80
  `fc-btn--${size}`,
81
+ radius && `fc-btn--radius-${radius}`,
60
82
  block && "fc-btn--block",
61
83
  circle && "fc-btn--circle",
62
84
  iconOnly && "fc-btn--icon-only",
@@ -69,6 +91,7 @@ function Button({
69
91
  {
70
92
  className: classNames,
71
93
  disabled: disabled || loading,
94
+ style: mergedStyle,
72
95
  ...props,
73
96
  children: [
74
97
  iconLeft && !loading && /* @__PURE__ */ jsx2("span", { className: "fc-btn__icon fc-btn__icon--left", children: iconLeft }),
@@ -105,8 +128,14 @@ function CheckButton({
105
128
  onChange,
106
129
  disabled = false,
107
130
  size = "md",
131
+ radius,
108
132
  labelLeft,
109
133
  labelRight,
134
+ trackBackground,
135
+ checkedTrackBackground,
136
+ thumbBackground,
137
+ thumbDotColor,
138
+ labelColor,
110
139
  className = "",
111
140
  style
112
141
  }) {
@@ -125,9 +154,27 @@ function CheckButton({
125
154
  toggle();
126
155
  }
127
156
  };
157
+ const colorVars = {
158
+ "--check-track-bg": trackBackground,
159
+ "--check-track-bg-checked": checkedTrackBackground,
160
+ "--check-thumb-bg": thumbBackground,
161
+ "--check-thumb-dot-color": thumbDotColor,
162
+ "--check-label-color": labelColor
163
+ };
164
+ const overrideStyle = {};
165
+ for (const [key, value] of Object.entries(colorVars)) {
166
+ if (value !== void 0) {
167
+ overrideStyle[key] = value;
168
+ }
169
+ }
170
+ const mergedStyle = {
171
+ ...overrideStyle,
172
+ ...style
173
+ };
128
174
  const cls = [
129
175
  "fc-check",
130
176
  `fc-check--${size}`,
177
+ radius && `fc-check--radius-${radius}`,
131
178
  checked && "fc-check--checked",
132
179
  disabled && "fc-check--disabled",
133
180
  className
@@ -136,7 +183,7 @@ function CheckButton({
136
183
  "div",
137
184
  {
138
185
  className: cls,
139
- style,
186
+ style: mergedStyle,
140
187
  role: "switch",
141
188
  "aria-checked": checked,
142
189
  tabIndex: disabled ? -1 : 0,
@@ -162,8 +209,26 @@ function RollingBox({
162
209
  showTrack = false,
163
210
  children,
164
211
  className,
212
+ thumbColor,
213
+ thumbHoverColor,
214
+ thumbActiveColor,
215
+ trackColor,
216
+ style,
165
217
  ...props
166
218
  }) {
219
+ const colorVars = {
220
+ "--roll-thumb": thumbColor,
221
+ "--roll-thumb-hover": thumbHoverColor,
222
+ "--roll-thumb-active": thumbActiveColor,
223
+ "--roll-track": trackColor
224
+ };
225
+ const overrideStyle = {};
226
+ for (const [key, value] of Object.entries(colorVars)) {
227
+ if (value !== void 0) {
228
+ overrideStyle[key] = value;
229
+ }
230
+ }
231
+ const mergedStyle = { ...overrideStyle, ...style };
167
232
  const containerRef = React2.useRef(null);
168
233
  const [isScrolling, setIsScrolling] = React2.useState(false);
169
234
  const scrollTimeoutRef = React2.useRef(null);
@@ -184,6 +249,17 @@ function RollingBox({
184
249
  }
185
250
  };
186
251
  }, []);
252
+ React2.useEffect(() => {
253
+ const el = containerRef.current;
254
+ if (!el || !horizontal) return;
255
+ const handler = (e) => {
256
+ if (e.deltaY === 0) return;
257
+ e.preventDefault();
258
+ el.scrollLeft += e.deltaY;
259
+ };
260
+ el.addEventListener("wheel", handler, { passive: false });
261
+ return () => el.removeEventListener("wheel", handler);
262
+ }, [horizontal]);
187
263
  const resolvedDirection = horizontal ? "horizontal" : "vertical";
188
264
  const classNames = [
189
265
  "fc-roll",
@@ -199,6 +275,7 @@ function RollingBox({
199
275
  {
200
276
  ref: containerRef,
201
277
  className: classNames,
278
+ style: mergedStyle,
202
279
  onScroll: handleScroll,
203
280
  ...props,
204
281
  children: /* @__PURE__ */ jsx4("div", { className: "fc-roll__content", children })
@@ -207,134 +284,110 @@ function RollingBox({
207
284
  }
208
285
 
209
286
  // src/components/Bar/SideBar.tsx
210
- import * as React3 from "react";
287
+ import { memo, useCallback as useCallback2 } from "react";
211
288
  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
- };
289
+ var SideBarItemView = memo(({ item, isSelected, onClick }) => {
290
+ const classes = [
291
+ "fc-sidebar__item",
292
+ isSelected && "fc-sidebar__item--selected",
293
+ item.disabled && "fc-sidebar__item--disabled"
294
+ ].filter(Boolean).join(" ");
295
+ const Tag = item.href ? "a" : "div";
296
+ const linkProps = item.href ? { href: item.href } : {};
311
297
  return /* @__PURE__ */ jsxs3(
312
- "aside",
298
+ Tag,
313
299
  {
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",
300
+ className: classes,
301
+ onClick: () => !item.disabled && onClick(item.key),
302
+ ...linkProps,
322
303
  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)) })
304
+ item.icon && /* @__PURE__ */ jsx5("span", { className: "fc-sidebar__icon", children: item.icon }),
305
+ /* @__PURE__ */ jsx5("span", { className: "fc-sidebar__label", children: item.label })
334
306
  ]
335
307
  }
336
308
  );
337
- }
309
+ });
310
+ SideBarItemView.displayName = "SideBarItemView";
311
+ var SideBar = memo(({
312
+ items,
313
+ bottomItems,
314
+ selectedKey,
315
+ collapsed,
316
+ width = 240,
317
+ collapsedWidth = 64,
318
+ onSelect,
319
+ onCollapse,
320
+ className = "",
321
+ style
322
+ }) => {
323
+ const handleClick = useCallback2(
324
+ (key) => onSelect(key),
325
+ [onSelect]
326
+ );
327
+ const toggleCollapse = useCallback2(
328
+ () => onCollapse(!collapsed),
329
+ [collapsed, onCollapse]
330
+ );
331
+ const rootClasses = [
332
+ "fc-sidebar",
333
+ collapsed && "fc-sidebar--collapsed",
334
+ className
335
+ ].filter(Boolean).join(" ");
336
+ const rootStyle = {
337
+ "--sidebar-width": `${collapsed ? collapsedWidth : width}px`,
338
+ "--sidebar-collapsed-width": `${collapsedWidth}px`,
339
+ ...style
340
+ };
341
+ return /* @__PURE__ */ jsxs3("aside", { className: rootClasses, style: rootStyle, children: [
342
+ /* @__PURE__ */ jsx5("div", { className: "fc-sidebar__header", children: /* @__PURE__ */ jsx5(
343
+ "button",
344
+ {
345
+ className: "fc-sidebar__collapse-btn",
346
+ onClick: toggleCollapse,
347
+ children: /* @__PURE__ */ jsx5(
348
+ "svg",
349
+ {
350
+ className: "fc-sidebar__collapse-icon",
351
+ width: "16",
352
+ height: "16",
353
+ viewBox: "0 0 16 16",
354
+ fill: "none",
355
+ xmlns: "http://www.w3.org/2000/svg",
356
+ children: /* @__PURE__ */ jsx5(
357
+ "path",
358
+ {
359
+ d: collapsed ? "M6 3L11 8L6 13" : "M10 3L5 8L10 13",
360
+ stroke: "currentColor",
361
+ strokeWidth: "1.5",
362
+ strokeLinecap: "round",
363
+ strokeLinejoin: "round"
364
+ }
365
+ )
366
+ }
367
+ )
368
+ }
369
+ ) }),
370
+ /* @__PURE__ */ jsx5("nav", { className: "fc-sidebar__menu", children: items.map((item) => /* @__PURE__ */ jsx5(
371
+ SideBarItemView,
372
+ {
373
+ item,
374
+ isSelected: selectedKey === item.key,
375
+ onClick: handleClick
376
+ },
377
+ item.key
378
+ )) }),
379
+ bottomItems && bottomItems.length > 0 && /* @__PURE__ */ jsx5("div", { className: "fc-sidebar__footer", children: bottomItems.map((item) => /* @__PURE__ */ jsx5(
380
+ SideBarItemView,
381
+ {
382
+ item,
383
+ isSelected: selectedKey === item.key,
384
+ onClick: handleClick
385
+ },
386
+ item.key
387
+ )) })
388
+ ] });
389
+ });
390
+ SideBar.displayName = "SideBar";
338
391
 
339
392
  // src/components/Input/Input.tsx
340
393
  import * as React4 from "react";
@@ -342,6 +395,7 @@ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
342
395
  var Input = React4.forwardRef(({
343
396
  size = "md",
344
397
  status = "default",
398
+ radius,
345
399
  prefix,
346
400
  suffix,
347
401
  allowClear = false,
@@ -364,13 +418,12 @@ var Input = React4.forwardRef(({
364
418
  const currentValue = isControlled ? value : internalValue;
365
419
  const handleChange = (e) => {
366
420
  if (!isControlled) setInternalValue(e.target.value);
367
- onChange?.(e);
421
+ onChange?.(e.target.value);
368
422
  };
369
423
  const handleClear = () => {
370
424
  if (!isControlled) setInternalValue("");
371
425
  onClear?.();
372
- const event = { target: { value: "" } };
373
- onChange?.(event);
426
+ onChange?.("");
374
427
  };
375
428
  const togglePassword = () => {
376
429
  setType((prev) => prev === "password" ? "text" : "password");
@@ -381,6 +434,7 @@ var Input = React4.forwardRef(({
381
434
  "fc-input",
382
435
  `fc-input--${size}`,
383
436
  `fc-input--${status}`,
437
+ radius && `fc-input--radius-${radius}`,
384
438
  (prefix || addonBefore) && "fc-input--has-prefix",
385
439
  (suffix || addonAfter || showClear || showPasswordToggle) && "fc-input--has-suffix",
386
440
  addonBefore && "fc-input--addon-before",
@@ -402,8 +456,8 @@ var Input = React4.forwardRef(({
402
456
  );
403
457
  return /* @__PURE__ */ jsxs4("div", { className: classNames, children: [
404
458
  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
459
  /* @__PURE__ */ jsxs4("div", { className: "fc-input__wrapper", children: [
460
+ prefix && /* @__PURE__ */ jsx6("span", { className: "fc-input__prefix", children: prefix }),
407
461
  input,
408
462
  /* @__PURE__ */ jsxs4("span", { className: "fc-input__actions", children: [
409
463
  showClear && /* @__PURE__ */ jsx6(
@@ -450,54 +504,112 @@ function Slider({
450
504
  disabled = false,
451
505
  marks,
452
506
  tooltip = false,
453
- className = ""
507
+ className = "",
508
+ style,
509
+ trackBackground,
510
+ fillBackground,
511
+ thumbBackground,
512
+ thumbBorderColor,
513
+ markDotColor,
514
+ markLabelColor,
515
+ tooltipBackground,
516
+ tooltipColor
454
517
  }) {
455
518
  const trackRef = React5.useRef(null);
456
- const draggingRef = React5.useRef(null);
457
519
  const [dragging, setDragging] = React5.useState(null);
520
+ const dragCleanupRef = React5.useRef(null);
521
+ React5.useEffect(() => {
522
+ return () => {
523
+ dragCleanupRef.current?.();
524
+ };
525
+ }, []);
458
526
  const initialValue = defaultValue ?? (range ? [min, max] : min);
459
527
  const [internalValue, setInternalValue] = React5.useState(initialValue);
460
528
  const isControlled = controlledValue !== void 0;
461
529
  const currentValue = isControlled ? controlledValue : internalValue;
530
+ const colorVars = {
531
+ "--slider-track-bg": trackBackground,
532
+ "--slider-fill-bg": fillBackground,
533
+ "--slider-thumb-bg": thumbBackground,
534
+ "--slider-thumb-border": thumbBorderColor,
535
+ "--slider-mark-dot-bg": markDotColor,
536
+ "--slider-mark-label-color": markLabelColor,
537
+ "--slider-tooltip-bg": tooltipBackground,
538
+ "--slider-tooltip-color": tooltipColor
539
+ };
540
+ const overrideStyle = {};
541
+ for (const [key, value] of Object.entries(colorVars)) {
542
+ if (value !== void 0) {
543
+ overrideStyle[key] = value;
544
+ }
545
+ }
546
+ const mergedStyle = { ...overrideStyle, ...style };
462
547
  const getPercent = (val) => Math.max(0, Math.min(100, (val - min) / (max - min) * 100));
463
548
  const getValueFromPercent = (percent) => {
464
549
  const raw = min + percent / 100 * (max - min);
465
550
  const stepped = Math.round(raw / step) * step;
466
551
  return Math.max(min, Math.min(max, stepped));
467
552
  };
468
- const handleMove = React5.useCallback((clientX, clientY) => {
469
- if (!trackRef.current || draggingRef.current === null || disabled) return;
553
+ const handleMove = React5.useCallback((clientX, clientY, activeIndex) => {
554
+ if (!trackRef.current || disabled) return;
470
555
  const rect = trackRef.current.getBoundingClientRect();
471
556
  const percent = orientation === "horizontal" ? (clientX - rect.left) / rect.width * 100 : (rect.bottom - clientY) / rect.height * 100;
472
557
  const newValue = getValueFromPercent(Math.max(0, Math.min(100, percent)));
473
558
  let nextValue;
474
559
  if (range) {
475
560
  const [start, end] = currentValue;
476
- nextValue = draggingRef.current === 0 ? [Math.min(newValue, end), end] : [start, Math.max(newValue, start)];
561
+ nextValue = activeIndex === 0 ? [Math.min(newValue, end), end] : [start, Math.max(newValue, start)];
477
562
  } else {
478
563
  nextValue = newValue;
479
564
  }
480
565
  if (!isControlled) setInternalValue(nextValue);
481
566
  onChange?.(nextValue);
482
567
  }, [disabled, orientation, range, currentValue, isControlled, onChange, min, max, step]);
483
- const handleMouseDown = (index) => (e) => {
568
+ const startDrag = (index, clientX, clientY) => {
484
569
  if (disabled) return;
485
- e.preventDefault();
486
- draggingRef.current = index;
487
570
  setDragging(index);
488
- const handleMouseMove = (e2) => handleMove(e2.clientX, e2.clientY);
489
- const handleMouseUp = () => {
490
- draggingRef.current = null;
491
- setDragging(null);
571
+ const handleMouseMove = (ev) => handleMove(ev.clientX, ev.clientY, index);
572
+ const handleTouchMove = (ev) => {
573
+ ev.preventDefault();
574
+ const t = ev.touches[0];
575
+ handleMove(t.clientX, t.clientY, index);
576
+ };
577
+ const cleanup = () => {
492
578
  document.removeEventListener("mousemove", handleMouseMove);
493
579
  document.removeEventListener("mouseup", handleMouseUp);
580
+ document.removeEventListener("touchmove", handleTouchMove);
581
+ document.removeEventListener("touchend", handleTouchEnd);
582
+ dragCleanupRef.current = null;
494
583
  };
584
+ const handleMouseUp = () => {
585
+ setDragging(null);
586
+ cleanup();
587
+ };
588
+ const handleTouchEnd = () => {
589
+ setDragging(null);
590
+ cleanup();
591
+ };
592
+ dragCleanupRef.current = cleanup;
495
593
  document.addEventListener("mousemove", handleMouseMove);
496
594
  document.addEventListener("mouseup", handleMouseUp);
595
+ document.addEventListener("touchmove", handleTouchMove, { passive: false });
596
+ document.addEventListener("touchend", handleTouchEnd);
597
+ handleMove(clientX, clientY, index);
598
+ };
599
+ const handleMouseDown = (index) => (e) => {
600
+ if (disabled) return;
601
+ e.preventDefault();
602
+ startDrag(index, e.clientX, e.clientY);
603
+ };
604
+ const handleTouchStart = (index) => (e) => {
605
+ if (disabled) return;
606
+ e.preventDefault();
607
+ const t = e.touches[0];
608
+ startDrag(index, t.clientX, t.clientY);
497
609
  };
498
610
  const handleTrackClick = (e) => {
499
- if (disabled || draggingRef.current !== null) return;
500
- handleMove(e.clientX, e.clientY);
611
+ if (disabled || dragging !== null) return;
612
+ handleMove(e.clientX, e.clientY, 0);
501
613
  };
502
614
  const [startVal, endVal] = range ? currentValue : [min, currentValue];
503
615
  const startPercent = getPercent(startVal);
@@ -512,7 +624,7 @@ function Slider({
512
624
  dragging !== null && "fc-slider--dragging",
513
625
  className
514
626
  ].filter(Boolean).join(" ");
515
- return /* @__PURE__ */ jsx7("div", { className: cls, children: /* @__PURE__ */ jsxs5(
627
+ return /* @__PURE__ */ jsx7("div", { className: cls, style: mergedStyle, children: /* @__PURE__ */ jsxs5(
516
628
  "div",
517
629
  {
518
630
  ref: trackRef,
@@ -532,6 +644,7 @@ function Slider({
532
644
  className: `fc-slider__thumb ${dragging === 0 ? "fc-slider__thumb--active" : ""}`,
533
645
  style: thumbStyle(startPercent),
534
646
  onMouseDown: handleMouseDown(0),
647
+ onTouchStart: handleTouchStart(0),
535
648
  children: tooltip && /* @__PURE__ */ jsx7("span", { className: "fc-slider__tooltip", children: startVal })
536
649
  }
537
650
  ),
@@ -541,6 +654,7 @@ function Slider({
541
654
  className: `fc-slider__thumb ${dragging === (range ? 1 : 0) ? "fc-slider__thumb--active" : ""}`,
542
655
  style: thumbStyle(endPercent),
543
656
  onMouseDown: handleMouseDown(range ? 1 : 0),
657
+ onTouchStart: handleTouchStart(range ? 1 : 0),
544
658
  children: tooltip && /* @__PURE__ */ jsx7("span", { className: "fc-slider__tooltip", children: endVal })
545
659
  }
546
660
  ),
@@ -563,6 +677,23 @@ function Slider({
563
677
 
564
678
  // src/components/Select/Select.tsx
565
679
  import * as React6 from "react";
680
+
681
+ // src/hooks/useClickOutside.ts
682
+ import { useEffect as useEffect4 } from "react";
683
+ function useClickOutside(ref, handler, enabled = true) {
684
+ useEffect4(() => {
685
+ if (!enabled) return;
686
+ const listener = (e) => {
687
+ if (ref.current && !ref.current.contains(e.target)) {
688
+ handler();
689
+ }
690
+ };
691
+ document.addEventListener("pointerdown", listener);
692
+ return () => document.removeEventListener("pointerdown", listener);
693
+ }, [ref, handler, enabled]);
694
+ }
695
+
696
+ // src/components/Select/Select.tsx
566
697
  import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
567
698
  function Select({
568
699
  options,
@@ -573,11 +704,32 @@ function Select({
573
704
  searchable = false,
574
705
  multiple = false,
575
706
  disabled = false,
707
+ radius,
576
708
  className = "",
709
+ style,
577
710
  virtualScroll = false,
578
711
  virtualItemHeight = 32,
579
- maxHeight = 256
712
+ maxHeight = 256,
713
+ triggerBackground,
714
+ triggerBorderColor,
715
+ selectedColor,
716
+ selectedBackground,
717
+ hoverBackground
580
718
  }) {
719
+ const colorVars = {
720
+ "--select-trigger-bg": triggerBackground,
721
+ "--select-trigger-border": triggerBorderColor,
722
+ "--select-option-selected-color": selectedColor,
723
+ "--select-option-selected-bg": selectedBackground,
724
+ "--select-option-hover-bg": hoverBackground
725
+ };
726
+ const overrideStyle = {};
727
+ for (const [key, value] of Object.entries(colorVars)) {
728
+ if (value !== void 0) {
729
+ overrideStyle[key] = value;
730
+ }
731
+ }
732
+ const mergedStyle = { ...overrideStyle, ...style };
581
733
  const [isOpen, setIsOpen] = React6.useState(false);
582
734
  const [searchValue, setSearchValue] = React6.useState("");
583
735
  const [highlightedIndex, setHighlightedIndex] = React6.useState(0);
@@ -635,30 +787,65 @@ function Select({
635
787
  const selected = options.find((o) => o.value === currentValue);
636
788
  return selected?.label || placeholder;
637
789
  };
638
- React6.useEffect(() => {
639
- const handleClick = (e) => {
640
- if (!containerRef.current?.contains(e.target)) {
790
+ useClickOutside(containerRef, () => setIsOpen(false));
791
+ const handleKeyDown = (e) => {
792
+ if (disabled) return;
793
+ switch (e.key) {
794
+ case "Enter":
795
+ case " ":
796
+ e.preventDefault();
797
+ if (!isOpen) {
798
+ setIsOpen(true);
799
+ break;
800
+ }
801
+ if (flatOptions[highlightedIndex]) handleSelect(flatOptions[highlightedIndex]);
802
+ break;
803
+ case "ArrowDown":
804
+ e.preventDefault();
805
+ if (!isOpen) {
806
+ setIsOpen(true);
807
+ break;
808
+ }
809
+ setHighlightedIndex((i) => Math.min(i + 1, flatOptions.length - 1));
810
+ break;
811
+ case "ArrowUp":
812
+ e.preventDefault();
813
+ setHighlightedIndex((i) => Math.max(i - 1, 0));
814
+ break;
815
+ case "Escape":
816
+ e.preventDefault();
641
817
  setIsOpen(false);
642
- }
643
- };
644
- document.addEventListener("mousedown", handleClick);
645
- return () => document.removeEventListener("mousedown", handleClick);
646
- }, []);
818
+ break;
819
+ case "Home":
820
+ e.preventDefault();
821
+ setHighlightedIndex(0);
822
+ break;
823
+ case "End":
824
+ e.preventDefault();
825
+ setHighlightedIndex(flatOptions.length - 1);
826
+ break;
827
+ }
828
+ };
647
829
  const classNames = [
648
830
  "fc-select",
649
831
  isOpen && "fc-select--open",
650
832
  multiple && "fc-select--multiple",
651
833
  disabled && "fc-select--disabled",
834
+ radius && `fc-select--radius-${radius}`,
652
835
  className
653
836
  ].filter(Boolean).join(" ");
654
- return /* @__PURE__ */ jsxs6("div", { ref: containerRef, className: classNames, children: [
837
+ return /* @__PURE__ */ jsxs6("div", { ref: containerRef, className: classNames, style: mergedStyle, onKeyDown: handleKeyDown, children: [
655
838
  /* @__PURE__ */ jsxs6(
656
839
  "div",
657
840
  {
658
841
  className: "fc-select__trigger",
659
842
  onClick: () => !disabled && setIsOpen(!isOpen),
843
+ tabIndex: disabled ? -1 : 0,
844
+ role: "combobox",
845
+ "aria-expanded": isOpen,
846
+ "aria-haspopup": "listbox",
660
847
  children: [
661
- /* @__PURE__ */ jsx8("span", { className: `fc-select__value ${!currentValue && "fc-select__value--placeholder"}`, children: displayLabel() }),
848
+ /* @__PURE__ */ jsx8("span", { className: `fc-select__value ${(currentValue === void 0 || currentValue === null || multiple && !currentValue.length) && "fc-select__value--placeholder"}`, children: displayLabel() }),
662
849
  /* @__PURE__ */ jsx8("span", { className: "fc-select__arrow", children: "\u25BC" })
663
850
  ]
664
851
  }
@@ -725,7 +912,8 @@ function Select({
725
912
  }
726
913
  function highlightText(text, highlight) {
727
914
  if (!highlight) return text;
728
- const parts = text.split(new RegExp(`(${highlight})`, "gi"));
915
+ const escaped = highlight.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
916
+ const parts = text.split(new RegExp(`(${escaped})`, "gi"));
729
917
  return parts.map(
730
918
  (part, i) => part.toLowerCase() === highlight.toLowerCase() ? /* @__PURE__ */ jsx8("mark", { className: "fc-select__highlight", children: part }, i) : part
731
919
  );
@@ -735,13 +923,13 @@ function Select({
735
923
  // src/components/Tree/Tree.tsx
736
924
  import {
737
925
  createContext as createContext2,
738
- memo,
926
+ memo as memo2,
739
927
  useCallback as useCallback4,
740
928
  useContext as useContext2,
741
- useEffect as useEffect4,
929
+ useEffect as useEffect5,
742
930
  useMemo as useMemo2,
743
931
  useRef as useRef4,
744
- useState as useState9
932
+ useState as useState8
745
933
  } from "react";
746
934
  import {
747
935
  DndContext,
@@ -753,11 +941,11 @@ import {
753
941
  } from "@dnd-kit/core";
754
942
 
755
943
  // src/components/Tree/DeleteDialog.tsx
756
- import { useState as useState8 } from "react";
944
+ import { useState as useState7 } from "react";
757
945
  import { Fragment, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
758
946
  function DeleteDialog({ node, onClose, onDelete }) {
759
- const [phase, setPhase] = useState8("choose");
760
- const [loading, setLoading] = useState8(false);
947
+ const [phase, setPhase] = useState7("choose");
948
+ const [loading, setLoading] = useState7(false);
761
949
  if (!node) return null;
762
950
  const reset = () => {
763
951
  setPhase("choose");
@@ -919,11 +1107,11 @@ import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-ru
919
1107
  var TreeActionsCtx = createContext2(null);
920
1108
  var TreeStateCtx = createContext2(null);
921
1109
  var DndStateCtx = createContext2(null);
922
- var CollapsePanel = memo(function CollapsePanel2({ open, children }) {
1110
+ var CollapsePanel = memo2(function CollapsePanel2({ open, children }) {
923
1111
  const innerRef = useRef4(null);
924
- const [height, setHeight] = useState9(0);
925
- const [ready, setReady] = useState9(false);
926
- useEffect4(() => {
1112
+ const [height, setHeight] = useState8(0);
1113
+ const [ready, setReady] = useState8(false);
1114
+ useEffect5(() => {
927
1115
  const el = innerRef.current;
928
1116
  if (!el) return;
929
1117
  const ro = new ResizeObserver(() => setHeight(el.offsetHeight));
@@ -938,7 +1126,7 @@ var CollapsePanel = memo(function CollapsePanel2({ open, children }) {
938
1126
  transition: ready ? "height 0.12s ease-out" : "none"
939
1127
  }, children: /* @__PURE__ */ jsx10("div", { ref: innerRef, children }) });
940
1128
  });
941
- var DndSlot = memo(function DndSlot2({
1129
+ var DndSlot = memo2(function DndSlot2({
942
1130
  nodeKey,
943
1131
  disabled,
944
1132
  children
@@ -959,15 +1147,15 @@ var DndSlot = memo(function DndSlot2({
959
1147
  );
960
1148
  return /* @__PURE__ */ jsx10(Fragment2, { children: children({ setRef, handleProps, isDragging, isDragSource, dropPosition }) });
961
1149
  });
962
- var TreeNodeItem = memo(function TreeNodeItem2({ node, level, hidden = false }) {
1150
+ var TreeNodeItem = memo2(function TreeNodeItem2({ node, level, hidden = false }) {
963
1151
  const actions = useContext2(TreeActionsCtx);
964
1152
  const state = useContext2(TreeStateCtx);
965
1153
  const isExpanded = state.expandedKeys.has(node.key);
966
1154
  const isEditing = state.editingKey === node.key;
967
1155
  const hasChildren = node.children.length > 0;
968
1156
  const indent = level * 20 + 12;
969
- const [localEdit, setLocalEdit] = useState9("");
970
- useEffect4(() => {
1157
+ const [localEdit, setLocalEdit] = useState8("");
1158
+ useEffect5(() => {
971
1159
  if (isEditing) setLocalEdit(node.title);
972
1160
  }, [isEditing, node.title]);
973
1161
  const handleEditKeyDown = useCallback4((e) => {
@@ -1093,18 +1281,18 @@ function Tree({
1093
1281
  scrollHeight = "400px",
1094
1282
  className = ""
1095
1283
  }) {
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({
1284
+ const [expandedKeys, setExpandedKeys] = useState8(/* @__PURE__ */ new Set());
1285
+ const [editingKey, setEditingKey] = useState8(null);
1286
+ const [deleteTarget, setDeleteTarget] = useState8(null);
1287
+ const [searchValue, setSearchValue] = useState8("");
1288
+ const [dndState, setDndState] = useState8({
1101
1289
  dropTargetKey: null,
1102
1290
  dropPosition: null,
1103
1291
  dragKey: null
1104
1292
  });
1105
1293
  const dropRef = useRef4({ key: null, pos: null });
1106
1294
  const pointerYRef = useRef4(0);
1107
- useEffect4(() => {
1295
+ useEffect5(() => {
1108
1296
  const handler = (e) => {
1109
1297
  pointerYRef.current = e.clientY;
1110
1298
  };
@@ -1263,10 +1451,10 @@ function Tree({
1263
1451
  }
1264
1452
 
1265
1453
  // src/components/Tree/OrphanDialog.tsx
1266
- import { useState as useState10 } from "react";
1454
+ import { useState as useState9 } from "react";
1267
1455
  import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
1268
1456
  function OrphanDialog({ orphans, onResolve, onClose }) {
1269
- const [resolutions, setResolutions] = useState10(
1457
+ const [resolutions, setResolutions] = useState9(
1270
1458
  () => Object.fromEntries(orphans.map((o) => [o.key, "lift"]))
1271
1459
  );
1272
1460
  if (orphans.length === 0) return null;
@@ -1330,50 +1518,16 @@ function OrphanDialog({ orphans, onResolve, onClose }) {
1330
1518
  }
1331
1519
 
1332
1520
  // src/components/Avatar/Avatar.tsx
1333
- import { useState as useState11, useMemo as useMemo3, useCallback as useCallback5, forwardRef as forwardRef2, useEffect as useEffect5 } from "react";
1521
+ import { useState as useState10, useCallback as useCallback5, forwardRef as forwardRef2, useEffect as useEffect6 } from "react";
1334
1522
  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
1523
  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(() => {
1524
+ var useImageLoader = (src, fallbackSrc, onImageLoad, onImageError) => {
1525
+ const [loadState, setLoadState] = useState10("idle");
1526
+ const [currentSrc, setCurrentSrc] = useState10(src);
1527
+ useEffect6(() => {
1371
1528
  setCurrentSrc(src);
1372
1529
  setLoadState(src ? "loading" : "idle");
1373
1530
  }, [src]);
1374
- useEffect5(() => {
1375
- onStateChange?.(loadState);
1376
- }, [loadState, onStateChange]);
1377
1531
  const handleLoad = useCallback5(() => {
1378
1532
  setLoadState("loaded");
1379
1533
  onImageLoad?.();
@@ -1394,18 +1548,8 @@ var useImageLoader = (src, fallbackSrc, onImageLoad, onImageError, onStateChange
1394
1548
  handleError
1395
1549
  };
1396
1550
  };
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) => {
1551
+ var renderContent = (currentSrc, fallbackSrc, loadState, alt, lazyLoad2, handleLoad, handleError) => {
1407
1552
  const showImage = currentSrc && loadState !== "error";
1408
- const showFallback = !showImage && children;
1409
1553
  const imageSrc = loadState === "error" && currentSrc !== fallbackSrc ? fallbackSrc : currentSrc;
1410
1554
  if (showImage && imageSrc) {
1411
1555
  return /* @__PURE__ */ jsx12(
@@ -1421,37 +1565,20 @@ var renderContent = (currentSrc, fallbackSrc, loadState, alt, lazyLoad2, childre
1421
1565
  }
1422
1566
  );
1423
1567
  }
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
- };
1568
+ return /* @__PURE__ */ jsx12("span", { className: "ui-avatar-placeholder", children: /* @__PURE__ */ jsx12("svg", { viewBox: "0 0 24 24", width: "40%", height: "40%", fill: "currentColor", children: /* @__PURE__ */ jsx12("path", { d: DEFAULT_ICON }) }) });
1437
1569
  };
1438
1570
  var Avatar = forwardRef2(
1439
1571
  ({
1440
- children,
1441
1572
  src,
1442
1573
  fallbackSrc,
1443
- color,
1444
- colorVariant,
1445
- size = "md",
1574
+ size = 40,
1446
1575
  shape = "circle",
1447
1576
  alt = "\u7528\u6237\u5934\u50CF",
1448
1577
  lazyLoad: lazyLoad2 = false,
1449
1578
  onImageLoad,
1450
1579
  onImageError,
1451
- onStateChange,
1452
1580
  bordered = false,
1453
1581
  className = "",
1454
- onClick,
1455
1582
  style: customStyle,
1456
1583
  ...restProps
1457
1584
  }, ref) => {
@@ -1459,31 +1586,28 @@ var Avatar = forwardRef2(
1459
1586
  src,
1460
1587
  fallbackSrc,
1461
1588
  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
1589
+ onImageError
1477
1590
  );
1478
- const { handleKeyDown } = useKeyboardInteraction(onClick);
1479
- const ariaAttributes = useAriaAttributes(onClick, loadState, alt);
1591
+ const sizeValue = `${size}px`;
1592
+ const classes = [
1593
+ "ui-avatar",
1594
+ `ui-avatar-${shape}`,
1595
+ bordered && "ui-avatar-bordered",
1596
+ loadState === "loading" && "ui-avatar-loading",
1597
+ className
1598
+ ].filter(Boolean).join(" ");
1599
+ const style = {
1600
+ width: sizeValue,
1601
+ height: sizeValue,
1602
+ fontSize: `calc(${sizeValue} * 0.4)`,
1603
+ ...customStyle
1604
+ };
1480
1605
  const content = renderContent(
1481
1606
  currentSrc,
1482
1607
  fallbackSrc,
1483
1608
  loadState,
1484
1609
  alt,
1485
1610
  lazyLoad2,
1486
- children,
1487
1611
  handleLoad,
1488
1612
  handleError
1489
1613
  );
@@ -1493,9 +1617,9 @@ var Avatar = forwardRef2(
1493
1617
  ref,
1494
1618
  className: classes,
1495
1619
  style,
1496
- onClick,
1497
- onKeyDown: handleKeyDown,
1498
- ...ariaAttributes,
1620
+ role: "img",
1621
+ "aria-label": alt,
1622
+ "data-load-state": loadState,
1499
1623
  ...restProps,
1500
1624
  children: content
1501
1625
  }
@@ -1505,7 +1629,7 @@ var Avatar = forwardRef2(
1505
1629
  Avatar.displayName = "Avatar";
1506
1630
 
1507
1631
  // src/components/ListGroup/ListGroup.tsx
1508
- import { forwardRef as forwardRef3, useMemo as useMemo4, useCallback as useCallback6 } from "react";
1632
+ import { forwardRef as forwardRef3, useMemo as useMemo3, useCallback as useCallback6 } from "react";
1509
1633
  import { jsx as jsx13 } from "react/jsx-runtime";
1510
1634
  var combineClassNames = (...classNames) => {
1511
1635
  return classNames.filter(Boolean).join(" ");
@@ -1523,7 +1647,7 @@ var ListGroupItem = forwardRef3(
1523
1647
  if (disabled) return;
1524
1648
  onClick?.(e);
1525
1649
  }, [disabled, onClick]);
1526
- const classNames = useMemo4(() => {
1650
+ const classNames = useMemo3(() => {
1527
1651
  return combineClassNames(
1528
1652
  "fc-list-group-item",
1529
1653
  active && "fc-list-group-item--active",
@@ -1556,7 +1680,7 @@ var ListGroup = forwardRef3(
1556
1680
  children,
1557
1681
  ...props
1558
1682
  }, ref) => {
1559
- const classNames = useMemo4(() => {
1683
+ const classNames = useMemo3(() => {
1560
1684
  return combineClassNames(
1561
1685
  "fc-list-group",
1562
1686
  bordered && "fc-list-group--bordered",
@@ -1579,8 +1703,10 @@ var ListGroup = forwardRef3(
1579
1703
  ListGroup.displayName = "ListGroup";
1580
1704
 
1581
1705
  // src/components/VirtualList/VirtualList.tsx
1582
- import { useState as useState12, useRef as useRef5, useCallback as useCallback7, useMemo as useMemo5, useEffect as useEffect6 } from "react";
1706
+ import { useState as useState11, useRef as useRef5, useCallback as useCallback7, useMemo as useMemo4, memo as memo3 } from "react";
1583
1707
  import { jsx as jsx14 } from "react/jsx-runtime";
1708
+ var VirtualItem = memo3(({ children, height }) => /* @__PURE__ */ jsx14("div", { className: "fc-virtual-list__item", style: { height: `${height}px` }, children }));
1709
+ VirtualItem.displayName = "VirtualItem";
1584
1710
  function VirtualList({
1585
1711
  data,
1586
1712
  height,
@@ -1593,7 +1719,7 @@ function VirtualList({
1593
1719
  style
1594
1720
  }) {
1595
1721
  const containerRef = useRef5(null);
1596
- const [scrollTop, setScrollTop] = useState12(0);
1722
+ const [scrollTop, setScrollTop] = useState11(0);
1597
1723
  const totalHeight = data.length * itemHeight;
1598
1724
  const handleScroll = useCallback7((e) => {
1599
1725
  const newScrollTop = e.currentTarget.scrollTop;
@@ -1606,7 +1732,7 @@ function VirtualList({
1606
1732
  }
1607
1733
  }
1608
1734
  }, [onScrollEnd]);
1609
- const visibleRange = useMemo5(() => {
1735
+ const visibleRange = useMemo4(() => {
1610
1736
  const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
1611
1737
  const endIndex = Math.min(
1612
1738
  data.length,
@@ -1614,15 +1740,10 @@ function VirtualList({
1614
1740
  );
1615
1741
  return { startIndex, endIndex };
1616
1742
  }, [scrollTop, height, itemHeight, data.length, overscan]);
1617
- const visibleData = useMemo5(() => {
1743
+ const visibleData = useMemo4(() => {
1618
1744
  return data.slice(visibleRange.startIndex, visibleRange.endIndex);
1619
1745
  }, [data, visibleRange]);
1620
1746
  const offsetY = visibleRange.startIndex * itemHeight;
1621
- useEffect6(() => {
1622
- if (!showScrollbar && containerRef.current) {
1623
- containerRef.current.style.scrollbarWidth = "none";
1624
- }
1625
- }, [showScrollbar]);
1626
1747
  const classNames = [
1627
1748
  "fc-virtual-list",
1628
1749
  !showScrollbar && "fc-virtual-list--hide-scrollbar",
@@ -1647,15 +1768,7 @@ function VirtualList({
1647
1768
  style: { transform: `translateY(${offsetY}px)` },
1648
1769
  children: visibleData.map((item, idx) => {
1649
1770
  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
- );
1771
+ return /* @__PURE__ */ jsx14(VirtualItem, { height: itemHeight, children: renderItem(item, actualIndex) }, actualIndex);
1659
1772
  })
1660
1773
  }
1661
1774
  )
@@ -1666,7 +1779,7 @@ function VirtualList({
1666
1779
  }
1667
1780
 
1668
1781
  // src/components/Alert/AlertContext.tsx
1669
- import { createContext as createContext3, useContext as useContext3, useState as useState13 } from "react";
1782
+ import { createContext as createContext3, useContext as useContext3, useEffect as useEffect7, useRef as useRef6, useState as useState12 } from "react";
1670
1783
  import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
1671
1784
  var AlertContext = createContext3(null);
1672
1785
  var ICONS = {
@@ -1742,55 +1855,84 @@ var ICONS = {
1742
1855
  }
1743
1856
  )
1744
1857
  };
1745
- function AlertProvider({ children }) {
1746
- const [props, setProps] = useState13({
1858
+ function AlertProvider({ children, background, borderColor }) {
1859
+ const [alert, setAlert] = useState12({
1747
1860
  msg: "",
1748
1861
  type: "info",
1862
+ mode: "alert",
1749
1863
  visible: false,
1750
1864
  choice: () => {
1751
1865
  }
1752
1866
  });
1753
- const showAlert = (msg, type, confirm = false) => new Promise((resolve) => {
1754
- setProps({
1867
+ const mountedRef = useRef6(true);
1868
+ useEffect7(() => {
1869
+ mountedRef.current = true;
1870
+ return () => {
1871
+ mountedRef.current = false;
1872
+ };
1873
+ }, []);
1874
+ const showAlert = (msg, type, mode = "alert", duration) => new Promise((resolve, reject) => {
1875
+ if (!mountedRef.current) {
1876
+ reject(new Error("AlertProvider unmounted"));
1877
+ return;
1878
+ }
1879
+ setAlert({
1755
1880
  msg,
1756
1881
  type,
1882
+ mode,
1757
1883
  visible: true,
1758
- confirm,
1884
+ duration,
1759
1885
  choice: (res) => {
1760
- setProps((p) => ({ ...p, visible: false }));
1886
+ if (!mountedRef.current) return;
1887
+ setAlert((p) => ({ ...p, visible: false }));
1761
1888
  resolve(res);
1762
1889
  }
1763
1890
  });
1764
1891
  });
1892
+ useEffect7(() => {
1893
+ if (!alert.visible || !alert.duration) return;
1894
+ const timer = setTimeout(() => alert.choice("auto"), alert.duration);
1895
+ return () => clearTimeout(timer);
1896
+ }, [alert.visible, alert.duration]);
1897
+ const overrideStyle = {};
1898
+ if (background !== void 0) overrideStyle["--alert-bg"] = background;
1899
+ if (borderColor !== void 0) overrideStyle["--alert-border"] = borderColor;
1765
1900
  return /* @__PURE__ */ jsxs10(AlertContext.Provider, { value: { showAlert }, children: [
1766
1901
  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
- ] }) })
1902
+ alert.visible && /* @__PURE__ */ jsx15("div", { className: "fc-alert-overlay", children: /* @__PURE__ */ jsxs10(
1903
+ "div",
1904
+ {
1905
+ className: `fc-alert fc-alert--${alert.type} fc-alert--${alert.mode}`,
1906
+ style: overrideStyle,
1907
+ children: [
1908
+ /* @__PURE__ */ jsxs10("div", { className: "fc-alert__header", children: [
1909
+ ICONS[alert.type],
1910
+ /* @__PURE__ */ jsx15("span", { className: "fc-alert__title", children: "\u63D0\u793A" })
1911
+ ] }),
1912
+ /* @__PURE__ */ jsx15(RollingBox, { className: "fc-alert__msg", children: alert.msg }),
1913
+ alert.mode !== "toast" && /* @__PURE__ */ jsxs10("div", { className: "fc-alert__footer", children: [
1914
+ alert.mode === "confirm" && /* @__PURE__ */ jsx15(
1915
+ Button,
1916
+ {
1917
+ variant: "secondary",
1918
+ size: "sm",
1919
+ onClick: () => alert.choice("no"),
1920
+ children: "\u53D6\u6D88"
1921
+ }
1922
+ ),
1923
+ /* @__PURE__ */ jsx15(
1924
+ Button,
1925
+ {
1926
+ variant: "primary",
1927
+ size: "sm",
1928
+ onClick: () => alert.choice("yes"),
1929
+ children: "\u786E\u5B9A"
1930
+ }
1931
+ )
1932
+ ] })
1933
+ ]
1934
+ }
1935
+ ) })
1794
1936
  ] });
1795
1937
  }
1796
1938
  var useAlert = () => useContext3(AlertContext);
@@ -1882,347 +2024,1217 @@ var Card = ({
1882
2024
  ] });
1883
2025
  };
1884
2026
 
1885
- // src/components/Tabs/Tabs.tsx
1886
- import { useState as useState14 } from "react";
2027
+ // src/components/Bar/TabBar.tsx
2028
+ import { useRef as useRef8, useCallback as useCallback9, memo as memo4, useEffect as useEffect9 } from "react";
2029
+
2030
+ // src/components/Bar/useAdaptiveTabLayout.ts
2031
+ import { useState as useState13, useRef as useRef7, useCallback as useCallback8, useEffect as useEffect8 } from "react";
2032
+ function useAdaptiveTabLayout(navRef, options) {
2033
+ const { itemsLength, addable, minWidthRatio, maxTabWidthRatio } = options;
2034
+ const itemsLengthRef = useRef7(itemsLength);
2035
+ itemsLengthRef.current = itemsLength;
2036
+ const addableRef = useRef7(addable);
2037
+ addableRef.current = addable;
2038
+ const minWidthRatioRef = useRef7(minWidthRatio);
2039
+ minWidthRatioRef.current = minWidthRatio;
2040
+ const maxTabWidthRatioRef = useRef7(maxTabWidthRatio);
2041
+ maxTabWidthRatioRef.current = maxTabWidthRatio;
2042
+ const [layout, setLayout] = useState13({ scrollMode: false, tabWidth: void 0 });
2043
+ const calculateTimeoutRef = useRef7(null);
2044
+ const lastLayoutRef = useRef7({ scrollMode: false, tabWidth: void 0 });
2045
+ const addBtnWidthRef = useRef7(0);
2046
+ const lastAddableRef = useRef7(void 0);
2047
+ const calculate = useCallback8(() => {
2048
+ const navOuter = navRef.current?.parentElement;
2049
+ const nav = navRef.current;
2050
+ const currentItemsLength = itemsLengthRef.current;
2051
+ const currentAddable = addableRef.current;
2052
+ const currentMinWidthRatio = minWidthRatioRef.current;
2053
+ const currentMaxTabWidthRatio = maxTabWidthRatioRef.current;
2054
+ if (!nav || !navOuter || currentItemsLength === 0) return;
2055
+ if (currentAddable !== lastAddableRef.current) {
2056
+ lastAddableRef.current = currentAddable;
2057
+ if (currentAddable) {
2058
+ const addBtnElement = navOuter.querySelector(".fc-tab-bar__add-btn");
2059
+ if (addBtnElement) {
2060
+ const btnStyle = getComputedStyle(addBtnElement);
2061
+ const marginL = parseFloat(btnStyle.marginLeft) || 0;
2062
+ const marginR = parseFloat(btnStyle.marginRight) || 0;
2063
+ addBtnWidthRef.current = addBtnElement.getBoundingClientRect().width + marginL + marginR;
2064
+ }
2065
+ } else {
2066
+ addBtnWidthRef.current = 0;
2067
+ }
2068
+ }
2069
+ const navOuterCS = getComputedStyle(navOuter);
2070
+ const navWrap = nav.querySelector(".fc-tab-bar__nav-wrap");
2071
+ const navOuterW = navOuter.clientWidth - parseFloat(navOuterCS.paddingLeft) - parseFloat(navOuterCS.paddingRight);
2072
+ const navW = navOuterW - addBtnWidthRef.current;
2073
+ const tabGap = navWrap ? parseFloat(getComputedStyle(navWrap).gap) : 0;
2074
+ const totalGapWidth = (currentItemsLength - 1) * tabGap;
2075
+ const idealW = (navW - totalGapWidth) / currentItemsLength;
2076
+ const threshold = window.innerWidth * currentMinWidthRatio;
2077
+ const maxAllowedWidth = window.innerWidth * currentMaxTabWidthRatio;
2078
+ const minAllowedWidth = 42;
2079
+ const prev = lastLayoutRef.current;
2080
+ const hysteresisFactor = 1.1;
2081
+ let newScrollMode;
2082
+ let newTabWidth;
2083
+ if (idealW < threshold) {
2084
+ newScrollMode = true;
2085
+ newTabWidth = Math.max(threshold, minAllowedWidth);
2086
+ } else if (prev.scrollMode && idealW < threshold * hysteresisFactor) {
2087
+ newScrollMode = true;
2088
+ newTabWidth = Math.max(threshold, minAllowedWidth);
2089
+ } else {
2090
+ newScrollMode = false;
2091
+ newTabWidth = Math.min(Math.max(idealW, minAllowedWidth), maxAllowedWidth);
2092
+ }
2093
+ if (prev.tabWidth !== newTabWidth || prev.scrollMode !== newScrollMode) {
2094
+ const newLayout = { scrollMode: newScrollMode, tabWidth: newTabWidth };
2095
+ lastLayoutRef.current = newLayout;
2096
+ setLayout(newLayout);
2097
+ }
2098
+ }, [navRef]);
2099
+ const calculateDebounced = useCallback8(() => {
2100
+ if (calculateTimeoutRef.current) clearTimeout(calculateTimeoutRef.current);
2101
+ calculateTimeoutRef.current = setTimeout(calculate, 50);
2102
+ }, [calculate]);
2103
+ const calculateDebouncedRef = useRef7(calculateDebounced);
2104
+ calculateDebouncedRef.current = calculateDebounced;
2105
+ useEffect8(() => {
2106
+ const rafId = requestAnimationFrame(calculate);
2107
+ const handleResize = () => calculateDebouncedRef.current();
2108
+ window.addEventListener("resize", handleResize);
2109
+ return () => {
2110
+ cancelAnimationFrame(rafId);
2111
+ window.removeEventListener("resize", handleResize);
2112
+ if (calculateTimeoutRef.current) clearTimeout(calculateTimeoutRef.current);
2113
+ };
2114
+ }, []);
2115
+ useEffect8(() => {
2116
+ calculate();
2117
+ }, [itemsLength, calculate]);
2118
+ return layout;
2119
+ }
2120
+
2121
+ // src/components/Bar/TabBar.tsx
1887
2122
  import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
1888
- var Tabs = ({
2123
+ var TabItemView = memo4(({
2124
+ item,
2125
+ isActive,
2126
+ closable,
2127
+ draggable,
2128
+ tabWidth,
2129
+ tabClassName,
2130
+ activeTabClassName,
2131
+ tabStyle,
2132
+ activeTabStyle,
2133
+ renderCloseIcon,
2134
+ onClick,
2135
+ onClose,
2136
+ onDragStart,
2137
+ onDragOver,
2138
+ onDrop,
2139
+ onDragEnd
2140
+ }) => {
2141
+ const showClose = item.closable !== void 0 ? item.closable : closable;
2142
+ const isCompact = tabWidth !== void 0 && tabWidth < 42;
2143
+ const classes = [
2144
+ "fc-tab-bar__tab",
2145
+ isActive && "fc-tab-bar__tab--active",
2146
+ item.disabled && "fc-tab-bar__tab--disabled",
2147
+ draggable && !item.disabled && "fc-tab-bar__tab--draggable",
2148
+ tabClassName,
2149
+ isActive && activeTabClassName
2150
+ ].filter(Boolean).join(" ");
2151
+ const mergedStyle = {
2152
+ ...tabWidth !== void 0 ? { width: tabWidth, boxSizing: "border-box", flexShrink: 0 } : void 0,
2153
+ ...tabStyle,
2154
+ ...isActive ? activeTabStyle : void 0
2155
+ };
2156
+ return /* @__PURE__ */ jsxs13(
2157
+ "div",
2158
+ {
2159
+ className: classes,
2160
+ style: mergedStyle,
2161
+ "data-compact": isCompact || void 0,
2162
+ onClick: () => !item.disabled && onClick(item.key),
2163
+ draggable: draggable && !item.disabled,
2164
+ onDragStart: (e) => onDragStart(e, item.key),
2165
+ onDragOver,
2166
+ onDrop: (e) => onDrop(e, item.key),
2167
+ onDragEnd,
2168
+ role: "tab",
2169
+ "aria-selected": isActive,
2170
+ "aria-disabled": item.disabled,
2171
+ tabIndex: item.disabled ? -1 : 0,
2172
+ children: [
2173
+ /* @__PURE__ */ jsx18("span", { className: "fc-tab-bar__tab-label", children: item.label }),
2174
+ showClose && !item.disabled && /* @__PURE__ */ jsx18(
2175
+ "span",
2176
+ {
2177
+ className: "fc-tab-bar__tab-close",
2178
+ onClick: (e) => onClose(e, item.key),
2179
+ role: "button",
2180
+ "aria-label": `\u5173\u95ED`,
2181
+ children: renderCloseIcon ? renderCloseIcon(item.key) : "\xD7"
2182
+ }
2183
+ )
2184
+ ]
2185
+ }
2186
+ );
2187
+ });
2188
+ TabItemView.displayName = "TabItemView";
2189
+ var TabBar = memo4(({
1889
2190
  items,
1890
- activeKey: controlledActiveKey,
1891
- defaultActiveKey,
2191
+ activeKey,
2192
+ variant = "attached",
1892
2193
  radius = "md",
2194
+ tabRadius,
1893
2195
  closable = false,
1894
2196
  addable = false,
2197
+ draggable = false,
2198
+ minWidthRatio = 0.07,
2199
+ maxTabWidthRatio = 0.15,
1895
2200
  onChange,
1896
2201
  onClose,
1897
2202
  onAdd,
2203
+ onReorder,
2204
+ tabClassName,
2205
+ activeTabClassName,
2206
+ tabStyle,
2207
+ activeTabStyle,
2208
+ renderCloseIcon,
2209
+ renderAddButton,
1898
2210
  className = "",
1899
- style
2211
+ style,
2212
+ background,
2213
+ tabColor,
2214
+ tabHoverColor,
2215
+ tabHoverBackground,
2216
+ tabActiveColor,
2217
+ tabActiveBackground,
2218
+ activeIndicatorColor
1900
2219
  }) => {
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);
2220
+ const colorVars = {
2221
+ "--tab-bar-bg": background,
2222
+ "--tab-color": tabColor,
2223
+ "--tab-hover-color": tabHoverColor,
2224
+ "--tab-hover-bg": tabHoverBackground,
2225
+ "--tab-active-color": tabActiveColor,
2226
+ "--tab-active-bg": tabActiveBackground,
2227
+ "--tab-active-indicator": activeIndicatorColor
1911
2228
  };
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
- }
2229
+ const overrideStyle = {};
2230
+ for (const [key, value] of Object.entries(colorVars)) {
2231
+ if (value !== void 0) {
2232
+ overrideStyle[key] = value;
1921
2233
  }
1922
- };
1923
- const classes = [
1924
- "fc-tabs",
1925
- `fc-tabs--radius-${radius}`,
2234
+ }
2235
+ const mergedStyle = { ...overrideStyle, ...style };
2236
+ const dragKeyRef = useRef8(null);
2237
+ const navRef = useRef8(null);
2238
+ const layout = useAdaptiveTabLayout(navRef, {
2239
+ itemsLength: items.length,
2240
+ addable,
2241
+ minWidthRatio,
2242
+ maxTabWidthRatio
2243
+ });
2244
+ const prevItemsLengthRef = useRef8(items.length);
2245
+ useEffect9(() => {
2246
+ const prev = prevItemsLengthRef.current;
2247
+ prevItemsLengthRef.current = items.length;
2248
+ if (items.length <= prev || !layout.scrollMode) return;
2249
+ const roll = navRef.current?.querySelector(".fc-roll");
2250
+ if (!roll) return;
2251
+ requestAnimationFrame(() => {
2252
+ roll.scrollLeft = roll.scrollWidth;
2253
+ });
2254
+ }, [items.length, layout.scrollMode]);
2255
+ const handleClick = useCallback9(
2256
+ (key) => onChange(key),
2257
+ [onChange]
2258
+ );
2259
+ const handleClose = useCallback9(
2260
+ (e, key) => {
2261
+ e.stopPropagation();
2262
+ onClose?.(key);
2263
+ },
2264
+ [onClose]
2265
+ );
2266
+ const handleDragStart = useCallback9(
2267
+ (e, key) => {
2268
+ dragKeyRef.current = key;
2269
+ e.dataTransfer.effectAllowed = "move";
2270
+ const target = e.currentTarget;
2271
+ requestAnimationFrame(() => target.classList.add("fc-tab-bar__tab--dragging"));
2272
+ },
2273
+ []
2274
+ );
2275
+ const handleDragOver = useCallback9((e) => {
2276
+ e.preventDefault();
2277
+ e.dataTransfer.dropEffect = "move";
2278
+ }, []);
2279
+ const handleDrop = useCallback9(
2280
+ (e, targetKey) => {
2281
+ e.preventDefault();
2282
+ const dragKey = dragKeyRef.current;
2283
+ if (!dragKey || dragKey === targetKey || !onReorder) return;
2284
+ const fromIndex = items.findIndex((i) => i.key === dragKey);
2285
+ const toIndex = items.findIndex((i) => i.key === targetKey);
2286
+ if (fromIndex === -1 || toIndex === -1) return;
2287
+ const reordered = [...items];
2288
+ const [moved] = reordered.splice(fromIndex, 1);
2289
+ reordered.splice(toIndex, 0, moved);
2290
+ onReorder(reordered);
2291
+ },
2292
+ [items, onReorder]
2293
+ );
2294
+ const handleDragEnd = useCallback9((e) => {
2295
+ dragKeyRef.current = null;
2296
+ e.currentTarget.classList.remove("fc-tab-bar__tab--dragging");
2297
+ }, []);
2298
+ const { scrollMode, tabWidth } = layout;
2299
+ const rootClasses = [
2300
+ "fc-tab-bar",
2301
+ `fc-tab-bar--${variant}`,
2302
+ `fc-tab-bar--radius-${radius}`,
2303
+ tabRadius && `fc-tab-bar--tab-radius-${tabRadius}`,
2304
+ !scrollMode && tabWidth !== void 0 && "fc-tab-bar--shrink",
1926
2305
  className
1927
2306
  ].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
- };
2307
+ const tabList = /* @__PURE__ */ jsx18("div", { className: "fc-tab-bar__nav-wrap", children: items.map((item) => /* @__PURE__ */ jsx18(
2308
+ TabItemView,
2309
+ {
2310
+ item,
2311
+ isActive: activeKey === item.key,
2312
+ closable,
2313
+ draggable,
2314
+ tabWidth,
2315
+ tabClassName,
2316
+ activeTabClassName,
2317
+ tabStyle,
2318
+ activeTabStyle,
2319
+ renderCloseIcon,
2320
+ onClick: handleClick,
2321
+ onClose: handleClose,
2322
+ onDragStart: handleDragStart,
2323
+ onDragOver: handleDragOver,
2324
+ onDrop: handleDrop,
2325
+ onDragEnd: handleDragEnd
2326
+ },
2327
+ item.key
2328
+ )) });
2329
+ const addBtn = addable && /* @__PURE__ */ jsx18(
2330
+ "div",
2331
+ {
2332
+ className: "fc-tab-bar__add-btn",
2333
+ onClick: onAdd,
2334
+ role: "button",
2335
+ "aria-label": "\u6DFB\u52A0\u6807\u7B7E",
2336
+ children: renderAddButton ? renderAddButton() : "+"
2337
+ }
2338
+ );
2339
+ return /* @__PURE__ */ jsx18("div", { className: rootClasses, style: mergedStyle, role: "tablist", children: /* @__PURE__ */ jsxs13("div", { className: `fc-tab-bar__nav-outer${scrollMode ? " fc-tab-bar__nav-outer--scroll" : ""}`, children: [
2340
+ /* @__PURE__ */ jsx18("div", { className: `fc-tab-bar__nav${scrollMode ? " fc-tab-bar__nav--scroll" : ""}`, ref: navRef, children: scrollMode ? /* @__PURE__ */ jsx18(RollingBox, { horizontal: true, showThumb: "hide", style: { flex: 1, minWidth: 0, height: "auto" }, children: tabList }) : tabList }),
2341
+ addBtn
2342
+ ] }) });
2343
+ });
2344
+ TabBar.displayName = "TabBar";
1957
2345
 
1958
- // src/components/Chat/Chat.tsx
1959
- import { useState as useState15, useRef as useRef6, useEffect as useEffect7, useCallback as useCallback8 } from "react";
2346
+ // src/components/Tag/TagItem.tsx
2347
+ import { useEffect as useEffect10, useRef as useRef9, useState as useState14 } from "react";
1960
2348
  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);
2349
+ function TagItem({
2350
+ schema,
2351
+ value,
2352
+ onChange,
2353
+ mode = "show",
2354
+ background,
2355
+ color,
2356
+ borderColor
2357
+ }) {
2358
+ const [editing, setEditing] = useState14(false);
2359
+ const [draft, setDraft] = useState14(() => value !== void 0 ? String(value) : "");
2360
+ const inputRef = useRef9(null);
2361
+ useEffect10(() => {
2362
+ if (!editing) setDraft(value !== void 0 ? String(value) : "");
2363
+ }, [value, editing]);
2364
+ const colorVars = {
2365
+ "--tag-bg": background,
2366
+ "--tag-color": color,
2367
+ "--tag-border": borderColor
2368
+ };
2369
+ const overrideStyle = {};
2370
+ for (const [k, v] of Object.entries(colorVars)) {
2371
+ if (v !== void 0) overrideStyle[k] = v;
2372
+ }
2373
+ if (schema.type === "boolean") {
2374
+ const boolVal = value === true || value === "true";
2375
+ return /* @__PURE__ */ jsxs14(
2376
+ "span",
2377
+ {
2378
+ className: "fc-tag-item fc-tag-item--boolean",
2379
+ style: overrideStyle,
2380
+ onClick: () => onChange?.(!boolVal),
2381
+ title: "\u70B9\u51FB\u5207\u6362",
2382
+ children: [
2383
+ /* @__PURE__ */ jsx19("span", { className: "fc-tag-item__name", children: schema.name }),
2384
+ /* @__PURE__ */ jsx19("span", { className: "fc-tag-item__sep", children: "\xB7" }),
2385
+ /* @__PURE__ */ jsx19("span", { className: boolVal ? "fc-tag-item__bool-true" : "fc-tag-item__bool-false", children: boolVal ? "\u2713" : "\u2717" })
2386
+ ]
2387
+ }
2388
+ );
2389
+ }
2390
+ const commit = () => {
2391
+ if (schema.type === "number") {
2392
+ const n = parseFloat(draft);
2393
+ if (!isNaN(n)) {
2394
+ const clamped = schema.range_min != null && n < schema.range_min ? schema.range_min : schema.range_max != null && n > schema.range_max ? schema.range_max : n;
2395
+ onChange?.(clamped);
2396
+ }
1990
2397
  } 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();
2398
+ onChange?.(draft);
2021
2399
  }
2022
- }, [autoFocus, disabled]);
2023
- useEffect7(() => {
2024
- if (copyError) {
2025
- const timer = setTimeout(() => {
2026
- setCopyError(null);
2027
- }, 2e3);
2028
- return () => clearTimeout(timer);
2400
+ if (mode === "show") setEditing(false);
2401
+ };
2402
+ const cancel = () => {
2403
+ setDraft(value !== void 0 ? String(value) : "");
2404
+ if (mode === "show") setEditing(false);
2405
+ };
2406
+ const isEditing = mode === "edit" || editing;
2407
+ if (isEditing) {
2408
+ return /* @__PURE__ */ jsxs14("span", { className: "fc-tag-item fc-tag-item--editing", style: overrideStyle, children: [
2409
+ /* @__PURE__ */ jsx19("span", { className: "fc-tag-item__name", children: schema.name }),
2410
+ /* @__PURE__ */ jsx19("span", { className: "fc-tag-item__sep", children: "\xB7" }),
2411
+ /* @__PURE__ */ jsx19(
2412
+ "input",
2413
+ {
2414
+ ref: inputRef,
2415
+ className: "fc-tag-item__input",
2416
+ type: schema.type === "number" ? "number" : "text",
2417
+ value: draft,
2418
+ min: schema.range_min ?? void 0,
2419
+ max: schema.range_max ?? void 0,
2420
+ autoFocus: true,
2421
+ onChange: (e) => setDraft(e.target.value),
2422
+ onBlur: commit,
2423
+ onKeyDown: (e) => {
2424
+ if (e.key === "Enter") {
2425
+ e.preventDefault();
2426
+ commit();
2427
+ }
2428
+ if (e.key === "Escape") {
2429
+ e.preventDefault();
2430
+ cancel();
2431
+ }
2432
+ }
2433
+ }
2434
+ )
2435
+ ] });
2436
+ }
2437
+ return /* @__PURE__ */ jsxs14(
2438
+ "span",
2439
+ {
2440
+ className: "fc-tag-item fc-tag-item--show",
2441
+ style: overrideStyle,
2442
+ onDoubleClick: () => {
2443
+ setDraft(value !== void 0 ? String(value) : "");
2444
+ setEditing(true);
2445
+ setTimeout(() => inputRef.current?.select(), 0);
2446
+ },
2447
+ title: "\u53CC\u51FB\u7F16\u8F91",
2448
+ children: [
2449
+ /* @__PURE__ */ jsx19("span", { className: "fc-tag-item__name", children: schema.name }),
2450
+ value !== void 0 && /* @__PURE__ */ jsxs14(Fragment3, { children: [
2451
+ /* @__PURE__ */ jsx19("span", { className: "fc-tag-item__sep", children: "\xB7" }),
2452
+ /* @__PURE__ */ jsx19("span", { className: "fc-tag-item__value", children: String(value) })
2453
+ ] })
2454
+ ]
2029
2455
  }
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;
2456
+ );
2457
+ }
2458
+
2459
+ // src/components/MarkdownEditor/MarkdownEditor.tsx
2460
+ import { useState as useState15 } from "react";
2461
+ import MDEditor, { commands } from "@uiw/react-md-editor";
2462
+ import { jsx as jsx20 } from "react/jsx-runtime";
2463
+ function withTitle(cmd, title) {
2464
+ return { ...cmd, buttonProps: { ...cmd.buttonProps ?? {}, title } };
2465
+ }
2466
+ var TOOLBAR_COMMANDS = [
2467
+ withTitle(commands.bold, "\u52A0\u7C97"),
2468
+ withTitle(commands.italic, "\u659C\u4F53"),
2469
+ withTitle(commands.strikethrough, "\u5220\u9664\u7EBF"),
2470
+ commands.divider,
2471
+ withTitle(commands.title1, "\u4E00\u7EA7\u6807\u9898"),
2472
+ withTitle(commands.title2, "\u4E8C\u7EA7\u6807\u9898"),
2473
+ withTitle(commands.title3, "\u4E09\u7EA7\u6807\u9898"),
2474
+ commands.divider,
2475
+ withTitle(commands.quote, "\u5F15\u7528"),
2476
+ withTitle(commands.code, "\u884C\u5185\u4EE3\u7801"),
2477
+ withTitle(commands.codeBlock, "\u4EE3\u7801\u5757"),
2478
+ commands.divider,
2479
+ withTitle(commands.link, "\u94FE\u63A5"),
2480
+ withTitle(commands.unorderedListCommand, "\u65E0\u5E8F\u5217\u8868"),
2481
+ withTitle(commands.orderedListCommand, "\u6709\u5E8F\u5217\u8868"),
2482
+ withTitle(commands.hr, "\u5206\u5272\u7EBF")
2483
+ ];
2484
+ function MarkdownEditor({
2485
+ value,
2486
+ onChange,
2487
+ onAiComplete,
2488
+ minHeight = 200,
2489
+ placeholder = "\u5728\u6B64\u8F93\u5165\u5185\u5BB9...",
2490
+ mode = "edit",
2491
+ background,
2492
+ toolbarBackground,
2493
+ borderColor
2494
+ }) {
2495
+ const { resolvedTheme } = useTheme();
2496
+ const [showSplit, setShowSplit] = useState15(false);
2497
+ const colorVars = {
2498
+ "--md-bg": background,
2499
+ "--md-toolbar-bg": toolbarBackground,
2500
+ "--md-border": borderColor
2501
+ };
2502
+ const overrideStyle = {};
2503
+ for (const [k, v] of Object.entries(colorVars)) {
2504
+ if (v !== void 0) overrideStyle[k] = v;
2505
+ }
2506
+ const aiCommand = {
2507
+ name: "ai-complete",
2508
+ keyCommand: "ai-complete",
2509
+ buttonProps: { "aria-label": "AI \u8865\u5168", title: "AI \u8865\u5168", className: "fc-md-ai-btn" },
2510
+ icon: /* @__PURE__ */ jsx20("span", { children: "AI" }),
2511
+ execute: () => onAiComplete?.()
2512
+ };
2513
+ const splitCommand = {
2514
+ name: "split-view",
2515
+ keyCommand: "split-view",
2516
+ buttonProps: {
2517
+ "aria-label": showSplit ? "\u7EAF\u7F16\u8F91" : "\u53CC\u680F\u9884\u89C8",
2518
+ title: showSplit ? "\u7EAF\u7F16\u8F91" : "\u53CC\u680F\u9884\u89C8",
2519
+ className: `fc-md-split-btn${showSplit ? " fc-md-split-btn--active" : ""}`
2520
+ },
2521
+ icon: /* @__PURE__ */ jsx20("span", { className: "fc-md-split-icon", children: "\u229F" }),
2522
+ execute: () => setShowSplit((p) => !p)
2523
+ };
2524
+ const extraCommands = [
2525
+ splitCommand,
2526
+ ...onAiComplete ? [commands.divider, aiCommand] : [],
2527
+ withTitle(commands.fullscreen, "\u5168\u5C4F")
2528
+ ];
2529
+ const editorPreview = mode === "preview" ? "preview" : showSplit ? "live" : "edit";
2530
+ return /* @__PURE__ */ jsx20(
2531
+ "div",
2532
+ {
2533
+ className: "fc-md-wrap",
2534
+ style: overrideStyle,
2535
+ "data-color-mode": resolvedTheme,
2536
+ children: /* @__PURE__ */ jsx20(
2537
+ MDEditor,
2538
+ {
2539
+ value,
2540
+ onChange: (v) => onChange(v ?? ""),
2541
+ commands: TOOLBAR_COMMANDS,
2542
+ extraCommands,
2543
+ height: minHeight,
2544
+ preview: editorPreview,
2545
+ hideToolbar: mode === "preview",
2546
+ visibleDragbar: false,
2547
+ textareaProps: { placeholder }
2548
+ }
2549
+ )
2035
2550
  }
2551
+ );
2552
+ }
2553
+
2554
+ // src/components/ContextMenu/ContextMenuContext.tsx
2555
+ import { createContext as createContext4, useContext as useContext4, useEffect as useEffect11, useRef as useRef10, useState as useState16 } from "react";
2556
+ import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
2557
+ var ContextMenuContext = createContext4(null);
2558
+ function ContextMenuProvider({
2559
+ children,
2560
+ background,
2561
+ borderColor,
2562
+ hoverBackground
2563
+ }) {
2564
+ const [menu, setMenu] = useState16({
2565
+ visible: false,
2566
+ x: 0,
2567
+ y: 0,
2568
+ items: []
2569
+ });
2570
+ const menuRef = useRef10(null);
2571
+ const showContextMenu = (e, items) => {
2572
+ e.preventDefault();
2573
+ e.stopPropagation();
2574
+ setMenu({ visible: true, x: e.clientX, y: e.clientY, items });
2575
+ };
2576
+ const hide = () => setMenu((s) => ({ ...s, visible: false }));
2577
+ useClickOutside(menuRef, hide, menu.visible);
2578
+ useEffect11(() => {
2579
+ if (!menu.visible) return;
2580
+ const onKeyDown = (e) => {
2581
+ if (e.key === "Escape") hide();
2582
+ };
2583
+ window.addEventListener("keydown", onKeyDown);
2584
+ return () => window.removeEventListener("keydown", onKeyDown);
2585
+ }, [menu.visible]);
2586
+ const getPosition = () => {
2587
+ const W = window.innerWidth;
2588
+ const H = window.innerHeight;
2589
+ const menuW = 180;
2590
+ const menuH = menu.items.length * 32 + 12;
2591
+ return {
2592
+ left: menu.x + menuW > W ? menu.x - menuW : menu.x,
2593
+ top: menu.y + menuH > H ? menu.y - menuH : menu.y
2594
+ };
2595
+ };
2596
+ const colorVars = {
2597
+ "--ctx-bg": background,
2598
+ "--ctx-border": borderColor,
2599
+ "--ctx-item-hover-bg": hoverBackground
2600
+ };
2601
+ const overrideStyle = { ...getPosition() };
2602
+ for (const [k, v] of Object.entries(colorVars)) {
2603
+ if (v !== void 0) overrideStyle[k] = v;
2604
+ }
2605
+ return /* @__PURE__ */ jsxs15(ContextMenuContext.Provider, { value: { showContextMenu }, children: [
2606
+ children,
2607
+ menu.visible && /* @__PURE__ */ jsx21(
2608
+ "ul",
2609
+ {
2610
+ ref: menuRef,
2611
+ className: "fc-context-menu",
2612
+ style: overrideStyle,
2613
+ children: menu.items.map((item, i) => {
2614
+ if ("type" in item && item.type === "divider") {
2615
+ return /* @__PURE__ */ jsx21("li", { className: "fc-context-menu__divider" }, i);
2616
+ }
2617
+ const action = item;
2618
+ return /* @__PURE__ */ jsxs15(
2619
+ "li",
2620
+ {
2621
+ className: [
2622
+ "fc-context-menu__item",
2623
+ action.danger ? "fc-context-menu__item--danger" : "",
2624
+ action.disabled ? "fc-context-menu__item--disabled" : ""
2625
+ ].filter(Boolean).join(" "),
2626
+ onPointerDown: (e) => e.stopPropagation(),
2627
+ onClick: () => {
2628
+ if (action.disabled) return;
2629
+ action.onClick();
2630
+ hide();
2631
+ },
2632
+ children: [
2633
+ action.icon && /* @__PURE__ */ jsx21("span", { className: "fc-context-menu__icon", children: action.icon }),
2634
+ /* @__PURE__ */ jsx21("span", { className: "fc-context-menu__label", children: action.label })
2635
+ ]
2636
+ },
2637
+ i
2638
+ );
2639
+ })
2640
+ }
2641
+ )
2642
+ ] });
2643
+ }
2644
+ var useContextMenu = () => useContext4(ContextMenuContext);
2645
+
2646
+ // src/components/Chat/Chat.tsx
2647
+ import { useState as useState18, useRef as useRef11, useEffect as useEffect12, useCallback as useCallback11, useMemo as useMemo5 } from "react";
2648
+
2649
+ // src/components/SmartMessage/SmartMessage.tsx
2650
+ import { useState as useState17, useCallback as useCallback10 } from "react";
2651
+ import { Fragment as Fragment4, jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
2652
+ var SmartMessage = ({
2653
+ id,
2654
+ content,
2655
+ role,
2656
+ status,
2657
+ toolName,
2658
+ toolResult,
2659
+ onCopy,
2660
+ className = "",
2661
+ style = {}
2662
+ }) => {
2663
+ const [copied, setCopied] = useState17(false);
2664
+ const handleCopy = useCallback10(async () => {
2036
2665
  try {
2037
2666
  await navigator.clipboard.writeText(content);
2038
- setCopiedMessageId(messageId);
2039
- setTimeout(() => {
2040
- setCopiedMessageId(null);
2041
- }, 2e3);
2667
+ setCopied(true);
2668
+ onCopy?.(content, role);
2669
+ setTimeout(() => setCopied(false), 2e3);
2042
2670
  } catch (err) {
2043
2671
  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
2672
  }
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;
2673
+ }, [content, role, onCopy]);
2674
+ const getMessageClass = () => {
2675
+ const baseClass = "fc-smart-message";
2676
+ const roleClass = `fc-smart-message--${role}`;
2677
+ const statusClass = status ? `fc-smart-message--${status}` : "";
2678
+ return `${baseClass} ${roleClass} ${statusClass} ${className}`.trim();
2679
+ };
2680
+ const shouldShowCopyButton = () => {
2681
+ return role === "user" || role === "assistant";
2682
+ };
2683
+ const renderContent2 = () => {
2684
+ switch (role) {
2685
+ case "tool":
2686
+ return /* @__PURE__ */ jsxs16(Fragment4, { children: [
2687
+ toolName && /* @__PURE__ */ jsxs16("div", { className: "fc-smart-message-tool-info", children: [
2688
+ /* @__PURE__ */ jsx22("span", { className: "fc-smart-message-tool-icon", children: "\u{1F527}" }),
2689
+ /* @__PURE__ */ jsx22("span", { className: "fc-smart-message-tool-name", children: toolName })
2690
+ ] }),
2691
+ /* @__PURE__ */ jsx22("div", { className: "fc-smart-message-content", children: /* @__PURE__ */ jsx22("pre", { className: "fc-smart-message-tool-result", children: (() => {
2692
+ try {
2693
+ return JSON.stringify(toolResult || content, null, 2);
2694
+ } catch {
2695
+ return "[\u5E8F\u5217\u5316\u5931\u8D25]";
2696
+ }
2697
+ })() }) })
2698
+ ] });
2699
+ case "system":
2700
+ return /* @__PURE__ */ jsx22("div", { className: "fc-smart-message-content", children: /* @__PURE__ */ jsx22("div", { className: "fc-smart-message-text fc-smart-message-system-text", children: content }) });
2701
+ default:
2702
+ return /* @__PURE__ */ jsx22("div", { className: "fc-smart-message-content", children: /* @__PURE__ */ jsx22("div", { className: "fc-smart-message-text", children: content }) });
2066
2703
  }
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);
2704
+ };
2705
+ return /* @__PURE__ */ jsxs16("div", { className: getMessageClass(), style, "data-message-id": id, children: [
2706
+ /* @__PURE__ */ jsx22("div", { className: "fc-smart-message-content-wrapper", children: renderContent2() }),
2707
+ shouldShowCopyButton() && /* @__PURE__ */ jsx22(
2708
+ "button",
2709
+ {
2710
+ className: "fc-smart-message-copy-btn",
2711
+ onClick: handleCopy,
2712
+ title: "\u590D\u5236\u5185\u5BB9",
2713
+ children: copied ? /* @__PURE__ */ jsx22("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx22("path", { d: "M20 6L9 17L4 12", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) : /* @__PURE__ */ jsx22("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx22("path", { d: "M16 1H4C2.9 1 2 1.9 2 3V17H4V3H16V1ZM19 5H8C6.9 5 6 5.9 6 7V21C6 22.1 6.9 23 8 23H19C20.1 23 21 22.1 21 21V7C21 5.9 20.1 5 19 5ZM19 21H8V7H19V21Z", fill: "currentColor" }) })
2092
2714
  }
2093
- setIsSending(false);
2715
+ )
2716
+ ] });
2717
+ };
2718
+ var SmartMessage_default = SmartMessage;
2719
+
2720
+ // src/components/Chat/Chat.tsx
2721
+ import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
2722
+ var Chat = ({
2723
+ messages = [],
2724
+ title = "\u6D41\u4E91AI",
2725
+ loading = false,
2726
+ conversations = [],
2727
+ currentConversationId,
2728
+ emptyText = "\u6682\u65E0\u5386\u53F2\u5BF9\u8BDD",
2729
+ newConversationText = "\u65B0\u5EFA\u5BF9\u8BDD",
2730
+ historyTitle = "\u5386\u53F2\u5BF9\u8BDD",
2731
+ showHistoryButton = true,
2732
+ showMinimizeButton = true,
2733
+ showHeader = true,
2734
+ showFooter = false,
2735
+ autoScroll = true,
2736
+ onSwitchConversation,
2737
+ onNewConversation,
2738
+ onDeleteConversation,
2739
+ onMinimize,
2740
+ onRestore,
2741
+ onMessageCopy,
2742
+ className = "",
2743
+ style = {},
2744
+ headerClassName = "",
2745
+ headerStyle = {},
2746
+ messagesClassName = "",
2747
+ messagesStyle = {},
2748
+ bubbleClassName = "",
2749
+ height = "600px",
2750
+ width
2751
+ }) => {
2752
+ const [showHistory, setShowHistory] = useState18(false);
2753
+ const [isMinimized, setIsMinimized] = useState18(false);
2754
+ const messagesContainerRef = useRef11(null);
2755
+ const messagesEndRef = useRef11(null);
2756
+ const historyPanelRef = useRef11(null);
2757
+ const currentConversation = useMemo5(
2758
+ () => conversations.find((c) => c.id === currentConversationId),
2759
+ [conversations, currentConversationId]
2760
+ );
2761
+ const currentTitle = currentConversation?.title || title;
2762
+ useEffect12(() => {
2763
+ if (autoScroll && messagesContainerRef.current && !showHistory && !isMinimized) {
2764
+ messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight;
2094
2765
  }
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();
2766
+ }, [messages, loading, showHistory, isMinimized, autoScroll]);
2767
+ useClickOutside(historyPanelRef, () => setShowHistory(false), showHistory);
2768
+ const handleDeleteConversation = useCallback11((conversationId) => {
2769
+ onDeleteConversation?.(conversationId);
2770
+ }, [onDeleteConversation]);
2771
+ const handleMinimize = useCallback11(() => {
2772
+ setIsMinimized(true);
2773
+ onMinimize?.();
2774
+ }, [onMinimize]);
2775
+ const handleRestore = useCallback11(() => {
2776
+ setIsMinimized(false);
2777
+ onRestore?.();
2778
+ }, [onRestore]);
2779
+ const handleCopy = useCallback11((content) => {
2780
+ const message = messages.find((m) => m.content === content);
2781
+ if (message) {
2782
+ onMessageCopy?.(message);
2100
2783
  }
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);
2784
+ }, [messages, onMessageCopy]);
2785
+ const containerStyle = useMemo5(() => ({
2786
+ height,
2787
+ width,
2788
+ ...style
2789
+ }), [height, width, style]);
2790
+ const renderSystemMessage = (message) => {
2791
+ return /* @__PURE__ */ jsx23("div", { className: "system-message-container", children: /* @__PURE__ */ jsx23(
2792
+ SmartMessage_default,
2793
+ {
2794
+ id: message.id,
2795
+ content: message.content,
2796
+ role: message.type,
2797
+ timestamp: message.timestamp,
2798
+ status: message.status,
2799
+ toolName: message.toolName,
2800
+ toolResult: message.toolResult,
2801
+ onCopy: handleCopy,
2802
+ className: bubbleClassName
2112
2803
  }
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) => {
2804
+ ) }, message.id);
2805
+ };
2806
+ const renderUserAssistantMessage = (message) => {
2122
2807
  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);
2808
+ const showCopyButton = message.type === "user" || message.type === "assistant";
2809
+ return /* @__PURE__ */ jsxs17(
2810
+ "div",
2811
+ {
2812
+ className: `message-wrapper message-wrapper--${isUser ? "user" : "assistant"}`,
2813
+ children: [
2814
+ /* @__PURE__ */ jsx23(
2815
+ SmartMessage_default,
2816
+ {
2817
+ id: message.id,
2818
+ content: message.content,
2819
+ role: message.type,
2820
+ timestamp: message.timestamp,
2821
+ status: message.status,
2822
+ toolName: message.toolName,
2823
+ toolResult: message.toolResult,
2824
+ onCopy: handleCopy,
2825
+ className: bubbleClassName
2826
+ }
2827
+ ),
2828
+ showCopyButton && /* @__PURE__ */ jsx23("div", { className: `message-copy-wrapper message-copy-wrapper--${isUser ? "user" : "assistant"}`, children: /* @__PURE__ */ jsx23(
2829
+ "button",
2830
+ {
2831
+ className: "message-copy-btn",
2832
+ onClick: () => handleCopy(message.content),
2833
+ title: "\u590D\u5236\u5185\u5BB9",
2834
+ children: /* @__PURE__ */ jsx23("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx23(
2835
+ "path",
2836
+ {
2837
+ d: "M16 1H4C2.9 1 2 1.9 2 3V17H4V3H16V1ZM19 5H8C6.9 5 6 5.9 6 7V21C6 22.1 6.9 23 8 23H19C20.1 23 21 22.1 21 21V7C21 5.9 20.1 5 19 5ZM19 21H8V7H19V21Z",
2838
+ fill: "currentColor"
2839
+ }
2840
+ ) })
2841
+ }
2842
+ ) })
2843
+ ]
2844
+ },
2845
+ message.id
2846
+ );
2847
+ };
2848
+ const renderToolMessage = (message) => {
2849
+ return /* @__PURE__ */ jsx23("div", { className: "tool-message-container", children: /* @__PURE__ */ jsx23(
2850
+ SmartMessage_default,
2851
+ {
2852
+ id: message.id,
2853
+ content: message.content,
2854
+ role: message.type,
2855
+ timestamp: message.timestamp,
2856
+ status: message.status,
2857
+ toolName: message.toolName,
2858
+ toolResult: message.toolResult,
2859
+ onCopy: handleCopy,
2860
+ className: bubbleClassName
2861
+ }
2862
+ ) }, message.id);
2863
+ };
2864
+ const renderMessage = (message) => {
2865
+ switch (message.type) {
2866
+ case "system":
2867
+ return renderSystemMessage(message);
2868
+ case "tool":
2869
+ return renderToolMessage(message);
2870
+ default:
2871
+ return renderUserAssistantMessage(message);
2127
2872
  }
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" })
2873
+ };
2874
+ if (isMinimized) {
2875
+ return /* @__PURE__ */ jsx23("div", { className: `chat-container-minimized ${className}`, style: containerStyle, children: /* @__PURE__ */ jsx23("button", { className: "restore-btn-only", onClick: handleRestore, title: "\u5C55\u5F00", children: /* @__PURE__ */ jsx23("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx23("path", { d: "M12 5V19M5 12H19", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }) }) });
2876
+ }
2877
+ return /* @__PURE__ */ jsxs17("div", { className: `chat-container ${className}`, style: containerStyle, children: [
2878
+ showHeader && /* @__PURE__ */ jsxs17("div", { className: `chat-header ${headerClassName}`, style: headerStyle, children: [
2879
+ /* @__PURE__ */ jsx23("div", { className: "chat-title", children: currentTitle }),
2880
+ /* @__PURE__ */ jsxs17("div", { className: "chat-header-actions", children: [
2881
+ showHistoryButton && /* @__PURE__ */ jsx23("button", { className: "history-btn", onClick: () => setShowHistory(!showHistory), title: "\u5386\u53F2\u5BF9\u8BDD", children: /* @__PURE__ */ jsxs17("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", children: [
2882
+ /* @__PURE__ */ jsx23("path", { d: "M12 8V12L15 15", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }),
2883
+ /* @__PURE__ */ jsx23("circle", { cx: "12", cy: "12", r: "9", stroke: "currentColor", strokeWidth: "2" })
2884
+ ] }) }),
2885
+ showMinimizeButton && /* @__PURE__ */ jsx23("button", { className: "minimize-btn", onClick: handleMinimize, title: "\u6700\u5C0F\u5316", children: /* @__PURE__ */ jsx23("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx23("path", { d: "M5 12H19", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }) })
2886
+ ] })
2887
+ ] }),
2888
+ /* @__PURE__ */ jsxs17("div", { className: `chat-messages ${messagesClassName}`, ref: messagesContainerRef, style: messagesStyle, children: [
2889
+ messages.map((message) => renderMessage(message)),
2890
+ loading && /* @__PURE__ */ jsx23("div", { className: "typing-wrapper", children: /* @__PURE__ */ jsxs17("div", { className: "typing-indicator", children: [
2891
+ /* @__PURE__ */ jsx23("span", {}),
2892
+ /* @__PURE__ */ jsx23("span", {}),
2893
+ /* @__PURE__ */ jsx23("span", {})
2894
+ ] }) }),
2895
+ /* @__PURE__ */ jsx23("div", { ref: messagesEndRef })
2896
+ ] }),
2897
+ showFooter && /* @__PURE__ */ jsx23("div", { className: "chat-footer" }),
2898
+ showHistory && /* @__PURE__ */ jsxs17("div", { className: "history-panel", ref: historyPanelRef, children: [
2899
+ /* @__PURE__ */ jsxs17("div", { className: "history-header", children: [
2900
+ /* @__PURE__ */ jsx23("h3", { children: historyTitle }),
2901
+ /* @__PURE__ */ jsx23("button", { className: "close-history-btn", onClick: () => setShowHistory(false), children: "\u2715" })
2902
+ ] }),
2903
+ /* @__PURE__ */ jsxs17("div", { className: "history-list", children: [
2904
+ /* @__PURE__ */ jsxs17("button", { className: "new-conversation-btn-large", onClick: () => {
2905
+ onNewConversation?.();
2906
+ setShowHistory(false);
2907
+ }, children: [
2908
+ /* @__PURE__ */ jsx23("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx23("path", { d: "M12 5V19M5 12H19", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }),
2909
+ /* @__PURE__ */ jsx23("span", { children: newConversationText })
2139
2910
  ] }),
2140
- enableCopy && /* @__PURE__ */ jsx19(
2141
- "button",
2911
+ conversations.length === 0 ? /* @__PURE__ */ jsx23("div", { className: "empty-history", children: /* @__PURE__ */ jsx23("p", { children: emptyText }) }) : conversations.map((conv) => /* @__PURE__ */ jsxs17(
2912
+ "div",
2142
2913
  {
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" })
2914
+ className: `history-item ${currentConversationId === conv.id ? "active" : ""}`,
2915
+ onClick: () => {
2916
+ onSwitchConversation?.(conv.id);
2917
+ setShowHistory(false);
2918
+ },
2919
+ children: [
2920
+ /* @__PURE__ */ jsxs17("div", { className: "history-item-content", children: [
2921
+ /* @__PURE__ */ jsx23("div", { className: "history-item-title", children: /* @__PURE__ */ jsx23("span", { children: conv.title }) }),
2922
+ /* @__PURE__ */ jsx23("div", { className: "history-item-preview", children: conv.lastMessage }),
2923
+ /* @__PURE__ */ jsxs17("div", { className: "history-item-meta", children: [
2924
+ /* @__PURE__ */ jsx23("span", { children: new Date(conv.timestamp).toLocaleDateString() }),
2925
+ /* @__PURE__ */ jsxs17("span", { children: [
2926
+ conv.messages.length,
2927
+ "\u6761\u6D88\u606F"
2928
+ ] })
2929
+ ] })
2154
2930
  ] }),
2155
- /* @__PURE__ */ jsx19("span", { children: "\u590D\u5236" })
2156
- ] })
2157
- }
2158
- )
2931
+ /* @__PURE__ */ jsx23("div", { className: "history-item-actions", children: /* @__PURE__ */ jsx23(
2932
+ "button",
2933
+ {
2934
+ className: "delete-conversation-btn",
2935
+ onClick: (e) => {
2936
+ e.stopPropagation();
2937
+ handleDeleteConversation(conv.id);
2938
+ },
2939
+ title: "\u5220\u9664\u5BF9\u8BDD",
2940
+ children: /* @__PURE__ */ jsxs17("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", children: [
2941
+ /* @__PURE__ */ jsx23("path", { d: "M3 6H21", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }),
2942
+ /* @__PURE__ */ jsx23("path", { d: "M19 6V20C19 21.1 18.1 22 17 22H7C5.9 22 5 21.1 5 20V6", stroke: "currentColor", strokeWidth: "2" }),
2943
+ /* @__PURE__ */ jsx23("path", { d: "M8 6V4C8 2.9 8.9 2 10 2H14C15.1 2 16 2.9 16 4V6", stroke: "currentColor", strokeWidth: "2" })
2944
+ ] })
2945
+ }
2946
+ ) })
2947
+ ]
2948
+ },
2949
+ conv.id
2950
+ ))
2159
2951
  ] })
2160
- ] }, message.id);
2952
+ ] })
2953
+ ] });
2954
+ };
2955
+
2956
+ // src/components/Relation/Relation.tsx
2957
+ import { useCallback as useCallback12, useEffect as useEffect13 } from "react";
2958
+ import {
2959
+ ReactFlow,
2960
+ useNodesState,
2961
+ useEdgesState,
2962
+ addEdge,
2963
+ Handle,
2964
+ Position,
2965
+ MarkerType,
2966
+ ConnectionLineType,
2967
+ useReactFlow,
2968
+ ReactFlowProvider
2969
+ } from "@xyflow/react";
2970
+ import "@xyflow/react/dist/style.css";
2971
+ import { jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
2972
+ var getIconEmoji = (iconType) => {
2973
+ const map = {
2974
+ war: "\u2694\uFE0F",
2975
+ target: "\u{1F3AF}",
2976
+ star: "\u2B50",
2977
+ award: "\u{1F3C6}",
2978
+ flag: "\u{1F6A9}",
2979
+ default: "\u{1F465}",
2980
+ user: "\u{1F464}",
2981
+ shield: "\u{1F6E1}\uFE0F"
2161
2982
  };
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
- ] })
2983
+ return map[iconType || "default"] || "\u{1F465}";
2984
+ };
2985
+ var getStatusColor = (status) => {
2986
+ const map = {
2987
+ active: "#10b981",
2988
+ inactive: "#6b7280",
2989
+ warning: "#f59e0b"
2990
+ };
2991
+ return map[status || "active"];
2992
+ };
2993
+ var CustomNode = ({ data, theme = "light" }) => {
2994
+ const isDark = theme === "dark";
2995
+ const nodeStyle = {
2996
+ background: isDark ? "linear-gradient(135deg, #1e293b 0%, #0f172a 100%)" : "linear-gradient(135deg, #ffffff 0%, #f8fafc 100%)",
2997
+ border: `2px solid ${isDark ? "rgba(255, 255, 255, 0.12)" : "rgba(0, 0, 0, 0.08)"}`,
2998
+ borderRadius: "14px",
2999
+ boxShadow: isDark ? "0 8px 20px rgba(0, 0, 0, 0.25)" : "0 4px 12px rgba(0, 0, 0, 0.08)",
3000
+ minWidth: "240px",
3001
+ cursor: "pointer",
3002
+ transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)"
3003
+ };
3004
+ const iconStyle = {
3005
+ width: "52px",
3006
+ height: "52px",
3007
+ borderRadius: "14px",
3008
+ display: "flex",
3009
+ alignItems: "center",
3010
+ justifyContent: "center",
3011
+ background: isDark ? "rgba(255, 255, 255, 0.08)" : "rgba(0, 0, 0, 0.04)",
3012
+ transition: "all 0.3s ease"
3013
+ };
3014
+ const titleStyle = {
3015
+ fontWeight: 700,
3016
+ fontSize: "15px",
3017
+ marginBottom: "4px",
3018
+ whiteSpace: "nowrap",
3019
+ overflow: "hidden",
3020
+ textOverflow: "ellipsis",
3021
+ color: isDark ? "#ffffff" : "#1e293b"
3022
+ };
3023
+ const subtitleStyle = {
3024
+ fontSize: "11px",
3025
+ fontWeight: 500,
3026
+ marginBottom: "2px",
3027
+ color: isDark ? "rgba(255, 255, 255, 0.7)" : "#64748b"
3028
+ };
3029
+ const descriptionStyle = {
3030
+ fontSize: "10px",
3031
+ marginTop: "6px",
3032
+ whiteSpace: "nowrap",
3033
+ overflow: "hidden",
3034
+ textOverflow: "ellipsis",
3035
+ color: isDark ? "rgba(255, 255, 255, 0.45)" : "#94a3b8"
3036
+ };
3037
+ return /* @__PURE__ */ jsxs18("div", { style: nodeStyle, children: [
3038
+ /* @__PURE__ */ jsx24(Handle, { type: "target", position: Position.Top, isConnectable: true }),
3039
+ /* @__PURE__ */ jsxs18("div", { style: { display: "flex", alignItems: "center", gap: "14px", padding: "14px 18px" }, children: [
3040
+ /* @__PURE__ */ jsxs18("div", { style: { position: "relative", flexShrink: 0 }, children: [
3041
+ /* @__PURE__ */ jsx24("div", { style: iconStyle, children: data.imageUrl ? /* @__PURE__ */ jsx24("img", { src: data.imageUrl, alt: data.title, style: { width: "100%", height: "100%", objectFit: "cover", borderRadius: "12px" } }) : /* @__PURE__ */ jsx24("span", { style: { fontSize: "28px" }, children: getIconEmoji(data.iconType) }) }),
3042
+ data.status && /* @__PURE__ */ jsx24("div", { style: {
3043
+ position: "absolute",
3044
+ bottom: "-2px",
3045
+ right: "-2px",
3046
+ width: "12px",
3047
+ height: "12px",
3048
+ borderRadius: "50%",
3049
+ border: `2px solid ${isDark ? "#1e293b" : "#ffffff"}`,
3050
+ backgroundColor: getStatusColor(data.status)
3051
+ } })
2182
3052
  ] }),
2183
- /* @__PURE__ */ jsx19("div", { ref: messagesEndRef })
3053
+ /* @__PURE__ */ jsxs18("div", { style: { flex: 1, minWidth: 0 }, children: [
3054
+ /* @__PURE__ */ jsx24("div", { style: titleStyle, children: data.title }),
3055
+ /* @__PURE__ */ jsx24("div", { style: subtitleStyle, children: data.subtitle }),
3056
+ data.description && /* @__PURE__ */ jsx24("div", { style: descriptionStyle, children: data.description })
3057
+ ] })
2184
3058
  ] }),
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",
3059
+ /* @__PURE__ */ jsx24(Handle, { type: "source", position: Position.Bottom, isConnectable: true })
3060
+ ] });
3061
+ };
3062
+ var nodeTypes = {
3063
+ custom: CustomNode
3064
+ };
3065
+ var RelationContent = ({
3066
+ nodes: propNodes,
3067
+ edges: propEdges,
3068
+ onNodeClick,
3069
+ onNodeDoubleClick,
3070
+ onEdgeClick,
3071
+ onConnect: onConnectProp,
3072
+ onNodesChange: onNodesChangeProp,
3073
+ onEdgesChange: onEdgesChangeProp,
3074
+ fitView = true,
3075
+ fitViewOptions,
3076
+ className = "",
3077
+ style = {},
3078
+ height = "70vh",
3079
+ width = "100%",
3080
+ defaultViewport = { x: 0, y: 0, zoom: 1 },
3081
+ minZoom = 0.5,
3082
+ maxZoom = 2,
3083
+ snapToGrid = false,
3084
+ snapGrid = [15, 15],
3085
+ enableEdgeCreation = true,
3086
+ enableNodeDrag = true,
3087
+ onNodeContextMenu,
3088
+ theme: propTheme = "light"
3089
+ }) => {
3090
+ const [nodes, , onNodesChange] = useNodesState(propNodes || []);
3091
+ const [edges, setEdges, onEdgesChange] = useEdgesState(propEdges || []);
3092
+ const { fitView: fitViewFn } = useReactFlow();
3093
+ const theme = propTheme;
3094
+ const isDark = theme === "dark";
3095
+ const bgColor = isDark ? "#0f172a" : "#f5f7fa";
3096
+ useEffect13(() => {
3097
+ if (fitView && fitViewFn && (propNodes?.length || 0) > 0) {
3098
+ setTimeout(() => {
3099
+ fitViewFn({ duration: 300, ...fitViewOptions }).catch((error) => {
3100
+ console.warn("Fit view failed:", error);
3101
+ });
3102
+ }, 100);
3103
+ }
3104
+ }, [fitView, fitViewFn, fitViewOptions, propNodes]);
3105
+ const handleNodesChange = useCallback12(
3106
+ (changes) => {
3107
+ onNodesChange(changes);
3108
+ if (onNodesChangeProp) {
3109
+ onNodesChangeProp(nodes);
3110
+ }
3111
+ },
3112
+ [onNodesChange, onNodesChangeProp, nodes]
3113
+ );
3114
+ const handleEdgesChange = useCallback12(
3115
+ (changes) => {
3116
+ onEdgesChange(changes);
3117
+ if (onEdgesChangeProp) {
3118
+ onEdgesChangeProp(edges);
3119
+ }
3120
+ },
3121
+ [onEdgesChange, onEdgesChangeProp, edges]
3122
+ );
3123
+ const onConnect = useCallback12(
3124
+ (params) => {
3125
+ const newEdge = {
3126
+ ...params,
3127
+ id: `edge-${Date.now()}-${Math.random()}`,
3128
+ type: "smoothstep",
3129
+ style: { stroke: "#ff6b6b", strokeWidth: 2 },
3130
+ markerEnd: { type: MarkerType.ArrowClosed, color: "#ff6b6b" },
3131
+ label: "\u65B0\u8FDE\u63A5"
3132
+ };
3133
+ setEdges((eds) => addEdge(newEdge, eds));
3134
+ if (onConnectProp) {
3135
+ onConnectProp(params);
3136
+ }
3137
+ },
3138
+ [setEdges, onConnectProp]
3139
+ );
3140
+ const handleNodeClick = useCallback12(
3141
+ (_event, node) => {
3142
+ if (onNodeClick && node.data) {
3143
+ onNodeClick(node.id, node.data, _event);
3144
+ }
3145
+ },
3146
+ [onNodeClick]
3147
+ );
3148
+ const handleNodeDoubleClick = useCallback12(
3149
+ (_event, node) => {
3150
+ if (onNodeDoubleClick && node.data) {
3151
+ onNodeDoubleClick(node.id, node.data);
3152
+ }
3153
+ },
3154
+ [onNodeDoubleClick]
3155
+ );
3156
+ const handleNodeContextMenu = useCallback12(
3157
+ (event, node) => {
3158
+ event.preventDefault();
3159
+ if (onNodeContextMenu && node.data) {
3160
+ onNodeContextMenu(node.id, node.data);
3161
+ }
3162
+ },
3163
+ [onNodeContextMenu]
3164
+ );
3165
+ const handleEdgeClick = useCallback12(
3166
+ (_event, edge) => {
3167
+ if (onEdgeClick) {
3168
+ onEdgeClick(edge.id, edge.data);
3169
+ }
3170
+ },
3171
+ [onEdgeClick]
3172
+ );
3173
+ const nodesWithTheme = nodes.map((node) => ({
3174
+ ...node,
3175
+ data: { ...node.data, theme }
3176
+ }));
3177
+ const globalStyle = `
3178
+ .react-flow__background,
3179
+ .react-flow__pane,
3180
+ .react-flow__renderer,
3181
+ .react-flow__viewport {
3182
+ background-color: ${bgColor} !important;
3183
+ }
3184
+ `;
3185
+ return /* @__PURE__ */ jsxs18(
3186
+ "div",
3187
+ {
3188
+ className: `relation-container ${className}`,
3189
+ style: {
3190
+ width,
3191
+ height,
3192
+ ...style,
3193
+ position: "relative",
3194
+ overflow: "hidden",
3195
+ backgroundColor: bgColor
3196
+ },
3197
+ children: [
3198
+ /* @__PURE__ */ jsx24("style", { children: globalStyle }),
3199
+ /* @__PURE__ */ jsx24(
3200
+ ReactFlow,
2208
3201
  {
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"
3202
+ nodes: nodesWithTheme,
3203
+ edges,
3204
+ onNodesChange: handleNodesChange,
3205
+ onEdgesChange: handleEdgesChange,
3206
+ onConnect: enableEdgeCreation ? onConnect : void 0,
3207
+ onNodeClick: handleNodeClick,
3208
+ onNodeDoubleClick: handleNodeDoubleClick,
3209
+ onNodeContextMenu: handleNodeContextMenu,
3210
+ onEdgeClick: handleEdgeClick,
3211
+ nodeTypes,
3212
+ fitView: false,
3213
+ fitViewOptions,
3214
+ defaultViewport,
3215
+ minZoom,
3216
+ maxZoom,
3217
+ snapToGrid,
3218
+ snapGrid,
3219
+ nodesDraggable: enableNodeDrag,
3220
+ nodesConnectable: enableEdgeCreation,
3221
+ connectionLineType: ConnectionLineType.SmoothStep,
3222
+ connectionLineStyle: { stroke: "#ff6b6b", strokeWidth: 2 },
3223
+ attributionPosition: "bottom-right",
3224
+ zoomOnScroll: true,
3225
+ zoomOnPinch: true,
3226
+ zoomOnDoubleClick: false,
3227
+ panOnScroll: false,
3228
+ panOnDrag: true,
3229
+ proOptions: { hideAttribution: true }
2213
3230
  }
2214
3231
  )
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
- ] });
3232
+ ]
3233
+ }
3234
+ );
3235
+ };
3236
+ var Relation = (props) => {
3237
+ return /* @__PURE__ */ jsx24(ReactFlowProvider, { children: /* @__PURE__ */ jsx24(RelationContent, { ...props }) });
2226
3238
  };
2227
3239
  export {
2228
3240
  AlertProvider,
@@ -2233,16 +3245,21 @@ export {
2233
3245
  Card,
2234
3246
  Chat,
2235
3247
  CheckButton,
3248
+ ContextMenuProvider,
2236
3249
  DeleteDialog,
2237
3250
  Input,
2238
3251
  ListGroup,
2239
3252
  ListGroupItem,
3253
+ MarkdownEditor,
2240
3254
  OrphanDialog,
3255
+ Relation,
2241
3256
  RollingBox,
2242
3257
  Select,
2243
3258
  SideBar,
2244
3259
  Slider,
2245
- Tabs,
3260
+ SmartMessage,
3261
+ TabBar,
3262
+ TagItem,
2246
3263
  ThemeProvider,
2247
3264
  Tree,
2248
3265
  VirtualList,
@@ -2251,5 +3268,6 @@ export {
2251
3268
  isDescendantOf,
2252
3269
  lazyLoad,
2253
3270
  useAlert,
3271
+ useContextMenu,
2254
3272
  useTheme
2255
3273
  };