@stn-uiux/pub-ui-react 0.1.90 → 0.1.91

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
@@ -57,7 +57,7 @@ function buildPaginationItems(page, count, siblingCount = 1, boundaryCount = 1)
57
57
  }
58
58
 
59
59
  // src/StnProvider.tsx
60
- import { useCallback, useEffect, useState } from "react";
60
+ import { useCallback, useContext as useContext2, useEffect, useState } from "react";
61
61
 
62
62
  // src/StnContext.tsx
63
63
  import { createContext, useContext } from "react";
@@ -94,10 +94,13 @@ function StnProvider({
94
94
  applyThemeToDocument,
95
95
  className
96
96
  }) {
97
- const [internalMode, setInternalMode] = useState(defaultMode);
98
- const mode = modeProp ?? internalMode;
99
- const [internalTheme, setInternalTheme] = useState(defaultTheme);
100
- const theme = themeProp ?? internalTheme;
97
+ const parentCtx = useContext2(StnContext);
98
+ const resolvedModeProp = modeProp ?? (themeProp === "light" || themeProp === "dark" ? themeProp : void 0);
99
+ const fallbackDefaultMode = parentCtx?.mode ?? defaultMode;
100
+ const [internalMode, setInternalMode] = useState(fallbackDefaultMode);
101
+ const mode = resolvedModeProp ?? (parentCtx ? parentCtx.mode : internalMode);
102
+ const [internalTheme, setInternalTheme] = useState(defaultTheme ?? parentCtx?.theme);
103
+ const theme = (themeProp === "light" || themeProp === "dark" ? void 0 : themeProp) ?? internalTheme;
101
104
  const setMode = useCallback(
102
105
  (next) => {
103
106
  if (modeProp === void 0) setInternalMode(next);
@@ -892,6 +895,7 @@ var StnPopover = forwardRef5(function StnPopover2({
892
895
  anchorPosition,
893
896
  anchorOrigin = DEFAULT_ANCHOR_ORIGIN,
894
897
  transformOrigin = DEFAULT_TRANSFORM_ORIGIN,
898
+ offset = 0,
895
899
  marginThreshold = 16,
896
900
  className,
897
901
  style,
@@ -959,8 +963,24 @@ var StnPopover = forwardRef5(function StnPopover2({
959
963
  } else if (typeof transformOrigin.horizontal === "number") {
960
964
  transformLeft = transformOrigin.horizontal;
961
965
  }
962
- let top = anchorTop - transformTop;
963
- let left = anchorLeft - transformLeft;
966
+ let offsetX = 0;
967
+ let offsetY = 0;
968
+ if (typeof offset === "number") {
969
+ if (anchorReference === "anchorPosition") {
970
+ offsetX = offset;
971
+ offsetY = offset;
972
+ } else {
973
+ if (anchorOrigin.vertical === "bottom") offsetY += offset;
974
+ else if (anchorOrigin.vertical === "top") offsetY -= offset;
975
+ if (anchorOrigin.horizontal === "right") offsetX += offset;
976
+ else if (anchorOrigin.horizontal === "left") offsetX -= offset;
977
+ }
978
+ } else if (offset && typeof offset === "object") {
979
+ offsetX = offset.x ?? 0;
980
+ offsetY = offset.y ?? 0;
981
+ }
982
+ let top = anchorTop - transformTop + offsetY;
983
+ let left = anchorLeft - transformLeft + offsetX;
964
984
  const viewportWidth = window.innerWidth;
965
985
  const viewportHeight = window.innerHeight;
966
986
  const maxTop = viewportHeight - paperRect.height - marginThreshold;
@@ -980,7 +1000,8 @@ var StnPopover = forwardRef5(function StnPopover2({
980
1000
  anchorOrigin.horizontal,
981
1001
  transformOrigin.vertical,
982
1002
  transformOrigin.horizontal,
983
- marginThreshold
1003
+ marginThreshold,
1004
+ offset
984
1005
  ]);
985
1006
  useEffect2(() => {
986
1007
  if (!open) return;
@@ -1253,7 +1274,7 @@ var StnModal = forwardRef7(function StnModal2({
1253
1274
  var StnPopup = StnModal;
1254
1275
 
1255
1276
  // src/components/StnDrawer.tsx
1256
- import { forwardRef as forwardRef8, useEffect as useEffect4, useId as useId2 } from "react";
1277
+ import { forwardRef as forwardRef8, useEffect as useEffect4, useId as useId2, useState as useState5, useRef as useRef2 } from "react";
1257
1278
  import { createPortal as createPortal3 } from "react-dom";
1258
1279
  import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
1259
1280
  var SIZE_CLASS2 = {
@@ -1274,29 +1295,48 @@ function StnDrawerHeader({
1274
1295
  headerExtra,
1275
1296
  onClose,
1276
1297
  showClose = true,
1298
+ padding,
1277
1299
  className,
1300
+ style,
1278
1301
  children,
1279
1302
  ...rest
1280
1303
  }) {
1281
- return /* @__PURE__ */ jsxs7("div", { className: cn("stn-drawer__head", className), ...rest, children: [
1282
- /* @__PURE__ */ jsxs7("div", { className: "stn-drawer__head-text", children: [
1283
- title ? /* @__PURE__ */ jsx11("h4", { className: "stn-drawer__head-title", children: title }) : null,
1284
- subtitle ? /* @__PURE__ */ jsx11("p", { className: "stn-drawer__subtitle", children: subtitle }) : null,
1285
- children
1286
- ] }),
1287
- headerExtra ? /* @__PURE__ */ jsx11("div", { className: "stn-drawer__head-extra", children: headerExtra }) : null,
1288
- showClose && onClose ? /* @__PURE__ */ jsx11(
1289
- StnButton,
1290
- {
1291
- icon: "line/x",
1292
- variant: "ghost",
1293
- size: "sm",
1294
- className: "stn-drawer__close",
1295
- "aria-label": "\uB2EB\uAE30",
1296
- onClick: onClose
1297
- }
1298
- ) : null
1299
- ] });
1304
+ const isScalePadding = typeof padding === "string" && ["none", "xs", "sm", "md", "lg", "xl"].includes(padding);
1305
+ const computedPadding = isScalePadding ? void 0 : typeof padding === "number" ? `var(--stn-space-${padding}, ${padding}px)` : padding;
1306
+ return /* @__PURE__ */ jsxs7(
1307
+ "div",
1308
+ {
1309
+ className: cn(
1310
+ "stn-drawer__head",
1311
+ isScalePadding && `padding-${padding}`,
1312
+ className
1313
+ ),
1314
+ style: {
1315
+ ...computedPadding ? { padding: computedPadding } : {},
1316
+ ...style
1317
+ },
1318
+ ...rest,
1319
+ children: [
1320
+ /* @__PURE__ */ jsxs7("div", { className: "stn-drawer__head-text", children: [
1321
+ title ? /* @__PURE__ */ jsx11("h4", { className: "stn-drawer__head-title", children: title }) : null,
1322
+ subtitle ? /* @__PURE__ */ jsx11("p", { className: "stn-drawer__subtitle", children: subtitle }) : null,
1323
+ children
1324
+ ] }),
1325
+ headerExtra ? /* @__PURE__ */ jsx11("div", { className: "stn-drawer__head-extra", children: headerExtra }) : null,
1326
+ showClose && onClose ? /* @__PURE__ */ jsx11(
1327
+ StnButton,
1328
+ {
1329
+ icon: "line/x",
1330
+ variant: "ghost",
1331
+ size: "sm",
1332
+ className: "stn-drawer__close",
1333
+ "aria-label": "\uB2EB\uAE30",
1334
+ onClick: onClose
1335
+ }
1336
+ ) : null
1337
+ ]
1338
+ }
1339
+ );
1300
1340
  }
1301
1341
  function resolveScaleGap(gap, spacing) {
1302
1342
  if (gap && ["none", "xs", "sm", "md", "lg", "xl"].includes(gap)) return gap;
@@ -1345,7 +1385,16 @@ function StnDrawerBody({
1345
1385
  }
1346
1386
  );
1347
1387
  }
1348
- function StnDrawerFooter({ align = "right", className, children, ...rest }) {
1388
+ function StnDrawerFooter({
1389
+ align = "right",
1390
+ padding,
1391
+ className,
1392
+ style,
1393
+ children,
1394
+ ...rest
1395
+ }) {
1396
+ const isScalePadding = typeof padding === "string" && ["none", "xs", "sm", "md", "lg", "xl"].includes(padding);
1397
+ const computedPadding = isScalePadding ? void 0 : typeof padding === "number" ? `var(--stn-space-${padding}, ${padding}px)` : padding;
1349
1398
  return /* @__PURE__ */ jsx11(
1350
1399
  "div",
1351
1400
  {
@@ -1354,8 +1403,13 @@ function StnDrawerFooter({ align = "right", className, children, ...rest }) {
1354
1403
  align === "right" && "stn-drawer__footer--right",
1355
1404
  align === "center" && "stn-drawer__footer--center",
1356
1405
  align === "between" && "stn-drawer__footer--between",
1406
+ isScalePadding && `padding-${padding}`,
1357
1407
  className
1358
1408
  ),
1409
+ style: {
1410
+ ...computedPadding ? { padding: computedPadding } : {},
1411
+ ...style
1412
+ },
1359
1413
  ...rest,
1360
1414
  children
1361
1415
  }
@@ -1370,32 +1424,67 @@ var StnDrawerRoot = forwardRef8(function StnDrawer({
1370
1424
  placement = "right",
1371
1425
  size = "md",
1372
1426
  padding,
1427
+ headerPadding,
1428
+ footerPadding,
1373
1429
  gap,
1374
1430
  surface,
1375
1431
  dim = false,
1376
1432
  closeOnScrimClick = false,
1377
1433
  closeOnEsc = true,
1378
1434
  portal = true,
1435
+ motionDuration,
1379
1436
  actions,
1380
1437
  actionAlign = "right",
1381
1438
  className,
1382
1439
  children,
1383
1440
  role = "dialog",
1441
+ style,
1384
1442
  ...rest
1385
1443
  }, ref) {
1386
1444
  const generatedId = useId2();
1387
1445
  const drawerId = rest.id ?? generatedId;
1388
1446
  const titleId = `${drawerId}-title`;
1447
+ const motionDurationMs = motionDuration ?? 400;
1448
+ const [isMounted, setIsMounted] = useState5(open);
1449
+ const [isOpenState, setIsOpenState] = useState5(false);
1450
+ const closeTimerRef = useRef2(null);
1389
1451
  useEffect4(() => {
1390
- if (!open || !portal || !dim) return;
1452
+ if (open) {
1453
+ if (closeTimerRef.current !== null) {
1454
+ window.clearTimeout(closeTimerRef.current);
1455
+ closeTimerRef.current = null;
1456
+ }
1457
+ setIsMounted(true);
1458
+ const raf = requestAnimationFrame(() => {
1459
+ requestAnimationFrame(() => {
1460
+ setIsOpenState(true);
1461
+ });
1462
+ });
1463
+ return () => cancelAnimationFrame(raf);
1464
+ } else {
1465
+ setIsOpenState(false);
1466
+ closeTimerRef.current = window.setTimeout(() => {
1467
+ setIsMounted(false);
1468
+ closeTimerRef.current = null;
1469
+ }, motionDurationMs + 30);
1470
+ return () => {
1471
+ if (closeTimerRef.current !== null) {
1472
+ window.clearTimeout(closeTimerRef.current);
1473
+ closeTimerRef.current = null;
1474
+ }
1475
+ };
1476
+ }
1477
+ }, [open, motionDurationMs]);
1478
+ useEffect4(() => {
1479
+ if (!isOpenState || !portal || !dim) return;
1391
1480
  const prev = document.body.style.overflow;
1392
1481
  document.body.style.overflow = "hidden";
1393
1482
  return () => {
1394
1483
  document.body.style.overflow = prev;
1395
1484
  };
1396
- }, [open, portal, dim]);
1485
+ }, [isOpenState, portal, dim]);
1397
1486
  useEffect4(() => {
1398
- if (!open || !closeOnEsc) return;
1487
+ if (!isOpenState || !closeOnEsc) return;
1399
1488
  const handleKeyDown = (e) => {
1400
1489
  if (e.key === "Escape") {
1401
1490
  onClose();
@@ -1403,9 +1492,10 @@ var StnDrawerRoot = forwardRef8(function StnDrawer({
1403
1492
  };
1404
1493
  window.addEventListener("keydown", handleKeyDown);
1405
1494
  return () => window.removeEventListener("keydown", handleKeyDown);
1406
- }, [open, closeOnEsc, onClose]);
1407
- if (!open) return null;
1495
+ }, [isOpenState, closeOnEsc, onClose]);
1496
+ if (!isMounted) return null;
1408
1497
  const hasShorthandHeader = Boolean(title || subtitle || headerExtra);
1498
+ const motionStyle = motionDuration ? { "--stn-drawer-motion-duration": `${motionDuration}ms` } : void 0;
1409
1499
  const drawerPanel = /* @__PURE__ */ jsxs7(
1410
1500
  "div",
1411
1501
  {
@@ -1421,6 +1511,10 @@ var StnDrawerRoot = forwardRef8(function StnDrawer({
1421
1511
  gap && `gap-${gap}`,
1422
1512
  className
1423
1513
  ),
1514
+ style: {
1515
+ ...motionStyle,
1516
+ ...style
1517
+ },
1424
1518
  ...rest,
1425
1519
  children: [
1426
1520
  hasShorthandHeader ? /* @__PURE__ */ jsx11(
@@ -1430,11 +1524,12 @@ var StnDrawerRoot = forwardRef8(function StnDrawer({
1430
1524
  title,
1431
1525
  subtitle,
1432
1526
  headerExtra,
1527
+ padding: headerPadding,
1433
1528
  onClose
1434
1529
  }
1435
1530
  ) : null,
1436
1531
  hasShorthandHeader || actions ? /* @__PURE__ */ jsx11(StnDrawerBody, { surface, children }) : children,
1437
- actions ? /* @__PURE__ */ jsx11(StnDrawerFooter, { align: actionAlign, children: actions }) : null
1532
+ actions ? /* @__PURE__ */ jsx11(StnDrawerFooter, { align: actionAlign, padding: footerPadding, children: actions }) : null
1438
1533
  ]
1439
1534
  }
1440
1535
  );
@@ -1443,9 +1538,10 @@ var StnDrawerRoot = forwardRef8(function StnDrawer({
1443
1538
  {
1444
1539
  className: cn(
1445
1540
  "stn-drawer-stack",
1446
- open && "is-open",
1541
+ isOpenState && "is-open",
1447
1542
  dim ? "stn-drawer-stack--dim" : "stn-drawer-stack--no-dim"
1448
1543
  ),
1544
+ style: motionStyle,
1449
1545
  children: [
1450
1546
  dim ? /* @__PURE__ */ jsx11(
1451
1547
  "div",
@@ -1537,7 +1633,7 @@ function StnToastIcon({
1537
1633
  import {
1538
1634
  forwardRef as forwardRef9,
1539
1635
  useEffect as useEffect5,
1540
- useRef as useRef2
1636
+ useRef as useRef3
1541
1637
  } from "react";
1542
1638
  import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
1543
1639
  var StnProgressBar = forwardRef9(function StnProgressBar2({
@@ -1559,7 +1655,7 @@ var StnProgressBar = forwardRef9(function StnProgressBar2({
1559
1655
  className,
1560
1656
  ...rest
1561
1657
  }, ref) {
1562
- const fillRef = useRef2(null);
1658
+ const fillRef = useRef3(null);
1563
1659
  const isCountdown = duration != null && duration > 0;
1564
1660
  const pct = max <= 0 ? 0 : Math.min(100, Math.max(0, value / max * 100));
1565
1661
  const pctNum = pct / 100;
@@ -1811,10 +1907,10 @@ import {
1811
1907
  Children,
1812
1908
  forwardRef as forwardRef14,
1813
1909
  createContext as createContext2,
1814
- useContext as useContext2,
1910
+ useContext as useContext3,
1815
1911
  useEffect as useEffect8,
1816
- useRef as useRef5,
1817
- useState as useState7
1912
+ useRef as useRef6,
1913
+ useState as useState8
1818
1914
  } from "react";
1819
1915
 
1820
1916
  // src/components/StnCheckbox.tsx
@@ -1823,7 +1919,7 @@ import {
1823
1919
  useEffect as useEffect6,
1824
1920
  useId as useId4,
1825
1921
  useImperativeHandle as useImperativeHandle2,
1826
- useRef as useRef3
1922
+ useRef as useRef4
1827
1923
  } from "react";
1828
1924
  import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
1829
1925
  var StnCheckbox = forwardRef11(function StnCheckbox2({
@@ -1846,7 +1942,7 @@ var StnCheckbox = forwardRef11(function StnCheckbox2({
1846
1942
  }, forwardedRef) {
1847
1943
  const autoId = useId4();
1848
1944
  const id = idProp ?? autoId;
1849
- const innerRef = useRef3(null);
1945
+ const innerRef = useRef4(null);
1850
1946
  useImperativeHandle2(forwardedRef, () => innerRef.current);
1851
1947
  useEffect6(() => {
1852
1948
  if (innerRef.current) {
@@ -1925,8 +2021,8 @@ import {
1925
2021
  useCallback as useCallback4,
1926
2022
  useEffect as useEffect7,
1927
2023
  useId as useId5,
1928
- useRef as useRef4,
1929
- useState as useState5
2024
+ useRef as useRef5,
2025
+ useState as useState6
1930
2026
  } from "react";
1931
2027
  import { Fragment, jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
1932
2028
  function assignRef(ref, value) {
@@ -1957,8 +2053,8 @@ var StnScroll = forwardRef12(function StnScroll2({
1957
2053
  }, ref) {
1958
2054
  const rawId = useId5();
1959
2055
  const customId = `stn-scr-${rawId.replace(/[^a-zA-Z0-9_-]/g, "")}`;
1960
- const innerRef = useRef4(null);
1961
- const [edges, setEdges] = useState5({ top: true, bottom: false, left: true, right: false });
2056
+ const innerRef = useRef5(null);
2057
+ const [edges, setEdges] = useState6({ top: true, bottom: false, left: true, right: false });
1962
2058
  const hasArrows = variant === "arrows" || showArrows;
1963
2059
  const hasCustomStyling = Boolean(thumbColor || trackColor || scrollWidth != null);
1964
2060
  const showY = orientation !== "horizontal";
@@ -2127,7 +2223,7 @@ function ScrollArrow({
2127
2223
  // src/components/StnStatus.tsx
2128
2224
  import {
2129
2225
  forwardRef as forwardRef13,
2130
- useState as useState6
2226
+ useState as useState7
2131
2227
  } from "react";
2132
2228
  import { Fragment as Fragment2, jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
2133
2229
  var StnStatusDot = forwardRef13(function StnStatusDot2({
@@ -2211,7 +2307,7 @@ var StnStatusSwitch = forwardRef13(function StnStatusSwitch2({
2211
2307
  style,
2212
2308
  ...rest
2213
2309
  }, ref) {
2214
- const [internalChecked, setInternalChecked] = useState6(defaultChecked);
2310
+ const [internalChecked, setInternalChecked] = useState7(defaultChecked);
2215
2311
  const isChecked = checkedProp !== void 0 ? checkedProp : internalChecked;
2216
2312
  const widthProps = resolveButtonWidthProps(fullWidth, width, 80);
2217
2313
  const handleClick = () => {
@@ -2444,8 +2540,8 @@ function StnTableBase({
2444
2540
  onColumnReorder,
2445
2541
  className
2446
2542
  }) {
2447
- const [visitedRowId, setVisitedRowId] = useState7(null);
2448
- const wrapperRef = useRef5(null);
2543
+ const [visitedRowId, setVisitedRowId] = useState8(null);
2544
+ const wrapperRef = useRef6(null);
2449
2545
  useEffect8(() => {
2450
2546
  const wrapper = wrapperRef.current;
2451
2547
  if (!wrapper) return;
@@ -2648,7 +2744,7 @@ var StnTableBody = forwardRef14(
2648
2744
  }
2649
2745
  );
2650
2746
  var StnTableRow = forwardRef14(function StnTableRow2({ className, hoverable = false, selected = false, visited = false, highlighted = false, clickable = false, pressed = false, rowId, children, onClick, ...rest }, ref) {
2651
- const ctx = useContext2(StnTableContext);
2747
+ const ctx = useContext3(StnTableContext);
2652
2748
  const isTrackedVisited = ctx?.trackVisited && ctx.visitedRowId != null && ctx.visitedRowId === rowId;
2653
2749
  const finalVisited = visited || isTrackedVisited;
2654
2750
  const handleClick = (e) => {
@@ -2684,7 +2780,7 @@ function tableColWidthClass(width) {
2684
2780
  return `stn-table__col--${width}`;
2685
2781
  }
2686
2782
  var StnTableTh = forwardRef14(function StnTableTh2({ className, check = false, align, pressed = false, width, style, children, scope = "col", resizable, draggable, name, groupCols, ...rest }, ref) {
2687
- const ctx = useContext2(StnTableContext);
2783
+ const ctx = useContext3(StnTableContext);
2688
2784
  const colWidthClass = tableColWidthClass(width);
2689
2785
  const finalResizable = resizable ?? ctx?.resizable;
2690
2786
  const finalDraggable = draggable ?? ctx?.draggable;
@@ -2812,10 +2908,10 @@ StnTable.Flow = StnStatusFlow;
2812
2908
  import {
2813
2909
  createContext as createContext3,
2814
2910
  forwardRef as forwardRef16,
2815
- useContext as useContext3,
2911
+ useContext as useContext4,
2816
2912
  useEffect as useEffect9,
2817
- useRef as useRef6,
2818
- useState as useState8
2913
+ useRef as useRef7,
2914
+ useState as useState9
2819
2915
  } from "react";
2820
2916
 
2821
2917
  // src/components/StnDivider.tsx
@@ -2874,7 +2970,7 @@ var StnListContext = createContext3({
2874
2970
  disablePadding: false,
2875
2971
  indent: 0
2876
2972
  });
2877
- var useStnList = () => useContext3(StnListContext);
2973
+ var useStnList = () => useContext4(StnListContext);
2878
2974
  var StnListBase = forwardRef16(function StnList({
2879
2975
  size = "md",
2880
2976
  padding,
@@ -3054,8 +3150,8 @@ var StnListItemAction = forwardRef16(
3054
3150
  );
3055
3151
  var StnListCollapse = forwardRef16(
3056
3152
  function StnListCollapse2({ in: inProp, component: Component = "li", className, children, ...rest }, ref) {
3057
- const innerRef = useRef6(null);
3058
- const [maxHeight, setMaxHeight] = useState8(inProp ? void 0 : 0);
3153
+ const innerRef = useRef7(null);
3154
+ const [maxHeight, setMaxHeight] = useState9(inProp ? void 0 : 0);
3059
3155
  useEffect9(() => {
3060
3156
  if (inProp) {
3061
3157
  const h = innerRef.current?.scrollHeight ?? 500;
@@ -3097,7 +3193,7 @@ var StnListVirtual = forwardRef16(
3097
3193
  className,
3098
3194
  ...rest
3099
3195
  }, ref) {
3100
- const [scrollTop, setScrollTop] = useState8(0);
3196
+ const [scrollTop, setScrollTop] = useState9(0);
3101
3197
  const handleScroll = (e) => {
3102
3198
  setScrollTop(e.currentTarget.scrollTop);
3103
3199
  };
@@ -3175,7 +3271,7 @@ import {
3175
3271
  createContext as createContext4,
3176
3272
  forwardRef as forwardRef18,
3177
3273
  isValidElement as isValidElement3,
3178
- useContext as useContext4
3274
+ useContext as useContext5
3179
3275
  } from "react";
3180
3276
 
3181
3277
  // src/components/StnRadio.tsx
@@ -3242,7 +3338,7 @@ import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
3242
3338
  var StnMenuContext = createContext4({
3243
3339
  size: "md"
3244
3340
  });
3245
- var useStnMenu = () => useContext4(StnMenuContext);
3341
+ var useStnMenu = () => useContext5(StnMenuContext);
3246
3342
  var DEFAULT_MENU_ANCHOR_ORIGIN = {
3247
3343
  vertical: "bottom",
3248
3344
  horizontal: "left"
@@ -3564,7 +3660,7 @@ import {
3564
3660
  forwardRef as forwardRef20,
3565
3661
  useCallback as useCallback5,
3566
3662
  useMemo as useMemo2,
3567
- useState as useState9
3663
+ useState as useState10
3568
3664
  } from "react";
3569
3665
 
3570
3666
  // src/components/StnTreeNode.tsx
@@ -4037,7 +4133,7 @@ var StnTreeItem = StnTreeNode;
4037
4133
  // src/components/StnTree.tsx
4038
4134
  import { jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
4039
4135
  function useControllableSet(controlled, defaultIds, defaultExpandAll, allExpandable) {
4040
- const [internal, setInternal] = useState9(() => {
4136
+ const [internal, setInternal] = useState10(() => {
4041
4137
  if (defaultIds != null) return defaultIds;
4042
4138
  if (defaultExpandAll) return allExpandable;
4043
4139
  return [];
@@ -4096,12 +4192,12 @@ var StnTree = forwardRef20(function StnTree2({
4096
4192
  defaultExpandAll,
4097
4193
  expandableIds
4098
4194
  );
4099
- const [internalSelected, setInternalSelected] = useState9(defaultSelectedId);
4195
+ const [internalSelected, setInternalSelected] = useState10(defaultSelectedId);
4100
4196
  const selected = selectedId ?? internalSelected;
4101
- const [internalHidden, setInternalHidden] = useState9(() => defaultHiddenIds ?? []);
4197
+ const [internalHidden, setInternalHidden] = useState10(() => defaultHiddenIds ?? []);
4102
4198
  const hidden = controlledHiddenIds ?? internalHidden;
4103
4199
  const isCheckboxEnabled = showCheckbox ?? checkable ?? false;
4104
- const [internalChecked, setInternalChecked] = useState9(() => {
4200
+ const [internalChecked, setInternalChecked] = useState10(() => {
4105
4201
  if (defaultCheckedIds != null) {
4106
4202
  if (cascadeCheck) {
4107
4203
  return computeInitialCascade(nodes, defaultCheckedIds).checkedIds;
@@ -4111,7 +4207,7 @@ var StnTree = forwardRef20(function StnTree2({
4111
4207
  return [];
4112
4208
  });
4113
4209
  const checked = controlledCheckedIds ?? internalChecked;
4114
- const [internalIndeterminate, setInternalIndeterminate] = useState9(() => {
4210
+ const [internalIndeterminate, setInternalIndeterminate] = useState10(() => {
4115
4211
  if (defaultIndeterminateIds != null) return defaultIndeterminateIds;
4116
4212
  if (cascadeCheck && defaultCheckedIds != null) {
4117
4213
  return computeInitialCascade(nodes, defaultCheckedIds).indeterminateIds;
@@ -5370,7 +5466,7 @@ function StnChip(props) {
5370
5466
  // src/components/StnToggleButton.tsx
5371
5467
  import {
5372
5468
  forwardRef as forwardRef30,
5373
- useState as useState10
5469
+ useState as useState11
5374
5470
  } from "react";
5375
5471
  import { Fragment as Fragment3, jsx as jsx36, jsxs as jsxs29 } from "react/jsx-runtime";
5376
5472
  var StnToggleButton = forwardRef30(
@@ -5395,7 +5491,7 @@ var StnToggleButton = forwardRef30(
5395
5491
  ...rest
5396
5492
  }, ref) {
5397
5493
  const isControlled = pressedProp !== void 0;
5398
- const [internalPressed, setInternalPressed] = useState10(defaultPressed);
5494
+ const [internalPressed, setInternalPressed] = useState11(defaultPressed);
5399
5495
  const isPressed = isControlled ? Boolean(pressedProp) : internalPressed;
5400
5496
  const hasGradient = gradient && gradient !== "none";
5401
5497
  const isKnownTone = [
@@ -5718,19 +5814,19 @@ function StnSegmented(props) {
5718
5814
  // src/components/tabs/StnTabs.tsx
5719
5815
  import {
5720
5816
  forwardRef as forwardRef34,
5721
- useState as useState11,
5722
- useRef as useRef7,
5817
+ useState as useState12,
5818
+ useRef as useRef8,
5723
5819
  useEffect as useEffect10,
5724
5820
  useCallback as useCallback6,
5725
5821
  useLayoutEffect
5726
5822
  } from "react";
5727
5823
 
5728
5824
  // src/components/tabs/StnTabContext.tsx
5729
- import { createContext as createContext5, useContext as useContext5 } from "react";
5825
+ import { createContext as createContext5, useContext as useContext6 } from "react";
5730
5826
  import { jsx as jsx40 } from "react/jsx-runtime";
5731
5827
  var StnTabsContext = createContext5(null);
5732
5828
  function useStnTabsContext() {
5733
- return useContext5(StnTabsContext);
5829
+ return useContext6(StnTabsContext);
5734
5830
  }
5735
5831
  function StnTabContextProvider({
5736
5832
  value,
@@ -5871,7 +5967,7 @@ var StnTabsRoot = forwardRef34(function StnTabs({
5871
5967
  ...rest
5872
5968
  }, ref) {
5873
5969
  const context = useStnTabsContext();
5874
- const [internalValue, setInternalValue] = useState11(() => {
5970
+ const [internalValue, setInternalValue] = useState12(() => {
5875
5971
  return controlledValue ?? context?.value ?? defaultValue ?? (items && items.length > 0 ? items[0].value : "");
5876
5972
  });
5877
5973
  const activeValue = controlledValue ?? context?.value ?? internalValue;
@@ -5887,12 +5983,12 @@ var StnTabsRoot = forwardRef34(function StnTabs({
5887
5983
  );
5888
5984
  const resolvedSize = propSize ?? context?.size ?? "md";
5889
5985
  const resolvedVariant = propVariant ?? context?.variant ?? "line";
5890
- const scrollerRef = useRef7(null);
5891
- const listRef = useRef7(null);
5892
- const [indicatorStyle, setIndicatorStyle] = useState11({ left: 0, width: 0, opacity: 0 });
5893
- const [canScrollStart, setCanScrollStart] = useState11(false);
5894
- const [canScrollEnd, setCanScrollEnd] = useState11(false);
5895
- const [isScrollable, setIsScrollable] = useState11(false);
5986
+ const scrollerRef = useRef8(null);
5987
+ const listRef = useRef8(null);
5988
+ const [indicatorStyle, setIndicatorStyle] = useState12({ left: 0, width: 0, opacity: 0 });
5989
+ const [canScrollStart, setCanScrollStart] = useState12(false);
5990
+ const [canScrollEnd, setCanScrollEnd] = useState12(false);
5991
+ const [isScrollable, setIsScrollable] = useState12(false);
5896
5992
  const updateIndicator = useCallback6(() => {
5897
5993
  const list = listRef.current;
5898
5994
  if (!list) return;
@@ -6089,8 +6185,8 @@ import {
6089
6185
  useCallback as useCallback7,
6090
6186
  useEffect as useEffect11,
6091
6187
  useId as useId10,
6092
- useRef as useRef8,
6093
- useState as useState12
6188
+ useRef as useRef9,
6189
+ useState as useState13
6094
6190
  } from "react";
6095
6191
  import { jsx as jsx44, jsxs as jsxs34 } from "react/jsx-runtime";
6096
6192
  function resolveGlyph(node) {
@@ -6122,7 +6218,7 @@ function variantClass(variant = "outline") {
6122
6218
  return "outline";
6123
6219
  }
6124
6220
  function useControllable(controlled, defaultValue, onChange) {
6125
- const [internal, setInternal] = useState12(defaultValue);
6221
+ const [internal, setInternal] = useState13(defaultValue);
6126
6222
  const value = controlled ?? internal;
6127
6223
  const setValue = useCallback7(
6128
6224
  (next) => {
@@ -6164,8 +6260,8 @@ function StnDropdown({
6164
6260
  const autoId = useId10();
6165
6261
  const rootId = idProp ?? `stn-dropdown-${autoId.replace(/:/g, "")}`;
6166
6262
  const [value, setValue] = useControllable(valueProp, defaultValue, onChange);
6167
- const [open, setOpen] = useState12(false);
6168
- const rootRef = useRef8(null);
6263
+ const [open, setOpen] = useState13(false);
6264
+ const rootRef = useRef9(null);
6169
6265
  const selected = options.find((o) => o.value === value);
6170
6266
  const hasValue = selected !== void 0 && value !== "";
6171
6267
  const displayLabel = hasValue ? selected.label : placeholder ?? (options[0]?.label ?? "");
@@ -6505,7 +6601,7 @@ var StnTextField = forwardRef35(function StnTextField2({
6505
6601
  import {
6506
6602
  forwardRef as forwardRef36,
6507
6603
  useId as useId12,
6508
- useState as useState13
6604
+ useState as useState14
6509
6605
  } from "react";
6510
6606
  import { jsx as jsx47, jsxs as jsxs36 } from "react/jsx-runtime";
6511
6607
  function normalizeSize2(size) {
@@ -6560,7 +6656,7 @@ var StnTextArea = forwardRef36(function StnTextArea2({
6560
6656
  const autoId = useId12();
6561
6657
  const id = idProp ?? autoId;
6562
6658
  const isControlled = value !== void 0;
6563
- const [uncontrolledLength, setUncontrolledLength] = useState13(() => {
6659
+ const [uncontrolledLength, setUncontrolledLength] = useState14(() => {
6564
6660
  if (defaultValue != null) return String(defaultValue).length;
6565
6661
  return 0;
6566
6662
  });
@@ -6802,7 +6898,7 @@ var StnDateRangeField = forwardRef37(function StnDateRangeField2({
6802
6898
  // src/components/StnLabelPrefix.tsx
6803
6899
  import {
6804
6900
  useId as useId14,
6805
- useState as useState14,
6901
+ useState as useState15,
6806
6902
  isValidElement as isValidElement6,
6807
6903
  cloneElement as cloneElement4
6808
6904
  } from "react";
@@ -6846,7 +6942,7 @@ function StnLabelPrefix({
6846
6942
  const isError = error || invalid || color === "error";
6847
6943
  const hasHelper = helperText != null && helperText !== "";
6848
6944
  const helperId = hasHelper ? `${id}-helper` : void 0;
6849
- const [internalChecked, setInternalChecked] = useState14(defaultChecked ?? false);
6945
+ const [internalChecked, setInternalChecked] = useState15(defaultChecked ?? false);
6850
6946
  const isChecked = checked !== void 0 ? checked : internalChecked;
6851
6947
  const handleCheckboxChange = (nextChecked) => {
6852
6948
  if (checked === void 0) {
@@ -7104,14 +7200,14 @@ import {
7104
7200
  useCallback as useCallback9,
7105
7201
  useEffect as useEffect13,
7106
7202
  useLayoutEffect as useLayoutEffect3,
7107
- useRef as useRef10,
7108
- useState as useState16
7203
+ useRef as useRef11,
7204
+ useState as useState17
7109
7205
  } from "react";
7110
7206
  import { createPortal as createPortal4 } from "react-dom";
7111
7207
  import { formatStnDateRangeFieldValue as formatStnDateRangeFieldValue2 } from "@stn-uiux/pub-ui/data/stnDateRangeField";
7112
7208
 
7113
7209
  // src/components/datePicker/StnDateRangePicker.tsx
7114
- import { useCallback as useCallback8, useEffect as useEffect12, useId as useId15, useLayoutEffect as useLayoutEffect2, useMemo as useMemo3, useRef as useRef9, useState as useState15 } from "react";
7210
+ import { useCallback as useCallback8, useEffect as useEffect12, useId as useId15, useLayoutEffect as useLayoutEffect2, useMemo as useMemo3, useRef as useRef10, useState as useState16 } from "react";
7115
7211
  import {
7116
7212
  STN_DATE_PICKER_WEEKDAYS
7117
7213
  } from "@stn-uiux/pub-ui/data/stnDatePicker";
@@ -7187,7 +7283,7 @@ function sameStamp(a, b, withTime) {
7187
7283
  return a.getHours() === b.getHours() && a.getMinutes() === b.getMinutes() && a.getSeconds() === b.getSeconds();
7188
7284
  }
7189
7285
  function useDraftRange(value, open) {
7190
- const [draft, setDraft] = useState15(value);
7286
+ const [draft, setDraft] = useState16(value);
7191
7287
  useEffect12(() => {
7192
7288
  if (open) setDraft(value);
7193
7289
  }, [open, value]);
@@ -7263,7 +7359,7 @@ function TimeList({
7263
7359
  stacked,
7264
7360
  onSelect
7265
7361
  }) {
7266
- const listRef = useRef9(null);
7362
+ const listRef = useRef10(null);
7267
7363
  useLayoutEffect2(() => {
7268
7364
  const list = listRef.current;
7269
7365
  const scroll = list?.closest(".stn-scroll");
@@ -7338,8 +7434,8 @@ function StnDateRangePicker({
7338
7434
  const titleId = useId15();
7339
7435
  const pickerSize = normalizePickerSize(size);
7340
7436
  const [draft, setDraft] = useDraftRange(value, open);
7341
- const [hoverDay, setHoverDay] = useState15(null);
7342
- const [leftMonth, setLeftMonth] = useState15(() => startOfMonth(draft.start ?? draft.end ?? /* @__PURE__ */ new Date()));
7437
+ const [hoverDay, setHoverDay] = useState16(null);
7438
+ const [leftMonth, setLeftMonth] = useState16(() => startOfMonth(draft.start ?? draft.end ?? /* @__PURE__ */ new Date()));
7343
7439
  useEffect12(() => {
7344
7440
  if (!open) return;
7345
7441
  const anchor = value.start ?? value.end ?? /* @__PURE__ */ new Date();
@@ -7589,7 +7685,7 @@ function StnDateRangePicker({
7589
7685
  // src/components/datePicker/StnDateRangePickerField.tsx
7590
7686
  import { jsx as jsx52, jsxs as jsxs41 } from "react/jsx-runtime";
7591
7687
  function useControllableRange(controlled, defaultValue, onChange) {
7592
- const [internal, setInternal] = useState16(defaultValue);
7688
+ const [internal, setInternal] = useState17(defaultValue);
7593
7689
  const value = controlled ?? internal;
7594
7690
  const setValue = useCallback9(
7595
7691
  (next) => {
@@ -7630,11 +7726,11 @@ function StnDateRangePickerField({
7630
7726
  const format = formatProp ?? (showTime ? formatStnDateRangeFieldValue2 : formatDateOnly);
7631
7727
  const [value, setValue] = useControllableRange(valueProp, defaultValue, onChange);
7632
7728
  const oneDay = mode === "single" && isOneCalendarDay(value.start, value.end);
7633
- const [open, setOpen] = useState16(false);
7634
- const [anchorRect, setAnchorRect] = useState16(null);
7635
- const rootRef = useRef10(null);
7636
- const triggerRef = useRef10(null);
7637
- const panelRef = useRef10(null);
7729
+ const [open, setOpen] = useState17(false);
7730
+ const [anchorRect, setAnchorRect] = useState17(null);
7731
+ const rootRef = useRef11(null);
7732
+ const triggerRef = useRef11(null);
7733
+ const panelRef = useRef11(null);
7638
7734
  const pickerSize = size === "sm" || size === "s" ? "sm" : size === "lg" || size === "l" || size === "xl" ? "lg" : "md";
7639
7735
  const updateAnchor = useCallback9(() => {
7640
7736
  setAnchorRect(triggerRef.current?.getBoundingClientRect() ?? null);
@@ -8608,11 +8704,11 @@ function topologyShapePoints(shape, cx, cy, r) {
8608
8704
  return polygonPoints(cx, cy, r, 32, 0);
8609
8705
  }
8610
8706
  var TOPOLOGY_LINK_LABEL_SPEC = {
8611
- baseFontSize: 4.6,
8612
- minFontSize: 4,
8613
- maxFontSize: 5.2,
8614
- offsetY: 3.2,
8615
- haloStrokeWidth: 1.2
8707
+ baseFontSize: 10,
8708
+ minFontSize: 8.5,
8709
+ maxFontSize: 11.5,
8710
+ offsetY: 5.5,
8711
+ haloStrokeWidth: 2.2
8616
8712
  };
8617
8713
  var TOPOLOGY_NODE_CURRENT_RING_SPEC = {
8618
8714
  circleOffset: 5.5,
@@ -9143,13 +9239,13 @@ StnTopologyRing.Node = StnTopologyRingNode;
9143
9239
 
9144
9240
  // src/components/topology/StnTopologyGraph.tsx
9145
9241
  import {
9146
- useState as useState17,
9147
- useRef as useRef11,
9242
+ useState as useState18,
9243
+ useRef as useRef12,
9148
9244
  useCallback as useCallback10,
9149
9245
  useEffect as useEffect14,
9150
9246
  useMemo as useMemo4,
9151
9247
  createContext as createContext6,
9152
- useContext as useContext6,
9248
+ useContext as useContext7,
9153
9249
  Children as Children6,
9154
9250
  isValidElement as isValidElement9,
9155
9251
  forwardRef as forwardRef39,
@@ -9391,6 +9487,7 @@ function StnTopologyLinkGlyph({
9391
9487
  width,
9392
9488
  height,
9393
9489
  strokeWidth = 2.5,
9490
+ dashed = false,
9394
9491
  className,
9395
9492
  style
9396
9493
  }) {
@@ -9399,7 +9496,7 @@ function StnTopologyLinkGlyph({
9399
9496
  const h = height ?? px;
9400
9497
  const halfW = w / 2;
9401
9498
  const halfH = h / 2;
9402
- const dashArray = variant === "dot" ? "2 2" : void 0;
9499
+ const dashArray = dashed ? "3 3" : variant === "dot" ? "2 2" : void 0;
9403
9500
  return /* @__PURE__ */ jsx62(
9404
9501
  "svg",
9405
9502
  {
@@ -9517,7 +9614,7 @@ function project3DCoordinate(x, y, z, yaw, pitch, zoom) {
9517
9614
  }
9518
9615
  var StnTopologyGraphContext = createContext6(null);
9519
9616
  function useStnTopologyGraph() {
9520
- const ctx = useContext6(StnTopologyGraphContext);
9617
+ const ctx = useContext7(StnTopologyGraphContext);
9521
9618
  if (!ctx) {
9522
9619
  throw new Error("useStnTopologyGraph must be used within <StnTopologyGraph>");
9523
9620
  }
@@ -9529,7 +9626,7 @@ function StnTopologyGraphModeToggle({
9529
9626
  className,
9530
9627
  style
9531
9628
  }) {
9532
- const ctx = useContext6(StnTopologyGraphContext);
9629
+ const ctx = useContext7(StnTopologyGraphContext);
9533
9630
  const viewMode = ctx?.viewMode ?? "3D";
9534
9631
  const handleSwitchMode = ctx?.handleSwitchMode ?? (() => {
9535
9632
  });
@@ -9903,7 +10000,6 @@ function StnTopologyGraphViewport({ className, style, viewBox: customViewBox })
9903
10000
  zoom,
9904
10001
  fixedNodeSize,
9905
10002
  calibrateScreenPixels,
9906
- screenPixelsRatio = 1,
9907
10003
  dynamicViewBox,
9908
10004
  selectedId,
9909
10005
  onNodeClick,
@@ -9911,6 +10007,7 @@ function StnTopologyGraphViewport({ className, style, viewBox: customViewBox })
9911
10007
  onLinkClick,
9912
10008
  onLinkHover,
9913
10009
  linkLabelMode,
10010
+ multiLinkMode,
9914
10011
  onCanvasClick,
9915
10012
  onBackgroundClick,
9916
10013
  handleNodeMouseDown,
@@ -9921,11 +10018,12 @@ function StnTopologyGraphViewport({ className, style, viewBox: customViewBox })
9921
10018
  renderNodeLabel,
9922
10019
  hoveredNodeId,
9923
10020
  setHoveredNodeId,
9924
- hasDraggedRef
10021
+ hasDraggedRef,
10022
+ pan
9925
10023
  } = useStnTopologyGraph();
9926
- const calibration = calibrateScreenPixels && screenPixelsRatio > 0 ? 1 / screenPixelsRatio : 1;
9927
- const activeViewBox = customViewBox ?? dynamicViewBox ?? "-250 -250 500 500";
9928
- const [hoveredLinkKey, setHoveredLinkKey] = useState17(null);
10024
+ const calibration = 1;
10025
+ const activeViewBox = customViewBox ?? dynamicViewBox ?? "-400 -300 800 600";
10026
+ const [hoveredLinkKey, setHoveredLinkKey] = useState18(null);
9929
10027
  const isAnyLinkHovered = hoveredLinkKey !== null;
9930
10028
  return /* @__PURE__ */ jsxs50(
9931
10029
  "svg",
@@ -9944,430 +10042,472 @@ function StnTopologyGraphViewport({ className, style, viewBox: customViewBox })
9944
10042
  /* @__PURE__ */ jsx63("stop", { offset: "0%", stopColor: "color-mix(in srgb, var(--stn-primary) 70%, var(--stn-static-white))" }),
9945
10043
  /* @__PURE__ */ jsx63("stop", { offset: "100%", stopColor: "var(--stn-primary)" })
9946
10044
  ] }) }),
9947
- renderGridLines(),
9948
- /* @__PURE__ */ jsx63(
9949
- "g",
9950
- {
9951
- className: cn(
9952
- "stn-topology-links-group",
9953
- isAnyLinkHovered && "is-link-hovered",
9954
- `stn-topology-links-group--label-${linkLabelMode}`
9955
- ),
9956
- children: projectedLinks.map((link, idx) => {
9957
- if (!link.n1 || !link.n2) return null;
9958
- const { n1, n2 } = link;
9959
- const depthScale = 550 / (550 + link.avgDepth);
9960
- const baseOpacity = Math.max(0.65, Math.min(1, depthScale));
9961
- const opacity = resolveTopologyOpacity(link.opacity, baseOpacity);
9962
- const linkKey = String(link.id ?? `${link.source}-${link.target}-${idx}`);
9963
- const isThisLinkHovered = hoveredLinkKey === linkKey;
9964
- const effectiveLineOpacity = isThisLinkHovered ? 1 : opacity;
9965
- const isLabelVisible = linkLabelMode === "always" || linkLabelMode === "hover" && isAnyLinkHovered;
9966
- const isDot = link.variant === "dot";
9967
- const effectiveStatus = link.status ?? (isDot ? n1.status === "critical" || n2.status === "critical" ? "critical" : n1.status === "major" || n2.status === "major" ? "major" : n1.status === "minor" || n2.status === "minor" ? "minor" : n1.status === "warning" || n2.status === "warning" ? "warning" : void 0 : void 0);
9968
- const statusColor = effectiveStatus === "critical" ? "var(--stn-grade-critical)" : effectiveStatus === "major" ? "var(--stn-grade-major)" : effectiveStatus === "minor" ? "var(--stn-grade-minor)" : effectiveStatus === "warning" ? "var(--stn-grade-warning)" : effectiveStatus === "primary" || effectiveStatus === "point" ? "var(--stn-primary)" : null;
9969
- let dotElement = null;
9970
- if (isDot) {
9971
- const midSX = (n1.sx + n2.sx) / 2;
9972
- const midSY = (n1.sy + n2.sy) / 2;
9973
- const avgScale = n1 && n2 ? (n1.scale + n2.scale) / 2 : 1;
9974
- const dotRadius = fixedNodeSize ? 3 * calibration : Math.max(1.5, 3 * avgScale * calibration);
9975
- const dotFill = link.color ?? statusColor ?? "var(--stn-topology-node-normal)";
9976
- dotElement = /* @__PURE__ */ jsx63("circle", { cx: midSX, cy: midSY, r: dotRadius, fill: dotFill, opacity: effectiveLineOpacity });
9977
- }
9978
- const lineColor = link.color ?? (!isDot && statusColor ? statusColor : "var(--stn-topology-link-line)");
9979
- const effectiveStrokeWidth = resolveTopologyStrokeWidth(
9980
- link.strokeWidth,
9981
- depthScale,
9982
- zoom,
9983
- viewMode === "2D"
9984
- );
9985
- let labelElement = null;
9986
- if (link.label) {
9987
- const midX = (n1.sx + n2.sx) / 2;
9988
- const midY = (n1.sy + n2.sy) / 2;
9989
- const dx = n2.sx - n1.sx;
9990
- const dy = n2.sy - n1.sy;
9991
- const angleDeg = Math.atan2(dy, dx) * 180 / Math.PI;
9992
- let rotation = angleDeg;
9993
- if (rotation > 90) {
9994
- rotation -= 180;
9995
- } else if (rotation < -90) {
9996
- rotation += 180;
10045
+ /* @__PURE__ */ jsxs50("g", { className: "stn-topology-viewport-pan", transform: `translate(${pan.x.toFixed(1)}, ${pan.y.toFixed(1)})`, children: [
10046
+ renderGridLines(),
10047
+ /* @__PURE__ */ jsx63(
10048
+ "g",
10049
+ {
10050
+ className: cn(
10051
+ "stn-topology-links-group",
10052
+ isAnyLinkHovered && "is-link-hovered",
10053
+ `stn-topology-links-group--label-${linkLabelMode}`
10054
+ ),
10055
+ children: projectedLinks.map((link, idx) => {
10056
+ if (!link.n1 || !link.n2) return null;
10057
+ const { n1, n2, multiLinkIndex = 0, multiLinkTotal = 1 } = link;
10058
+ const depthScale = 550 / (550 + link.avgDepth);
10059
+ const baseOpacity = Math.max(0.65, Math.min(1, depthScale));
10060
+ const opacity = resolveTopologyOpacity(link.opacity, baseOpacity);
10061
+ const linkKey = String(link.id ?? `${link.source}-${link.target}-${idx}`);
10062
+ const isThisLinkHovered = hoveredLinkKey === linkKey;
10063
+ const effectiveLineOpacity = isThisLinkHovered ? 1 : opacity;
10064
+ const isLabelVisible = linkLabelMode === "always" || linkLabelMode === "hover" && isAnyLinkHovered;
10065
+ const x1 = n1.sx;
10066
+ const y1 = n1.sy;
10067
+ const x2 = n2.sx;
10068
+ const y2 = n2.sy;
10069
+ const midX = (x1 + x2) / 2;
10070
+ const midY = (y1 + y2) / 2;
10071
+ const dx = x2 - x1;
10072
+ const dy = y2 - y1;
10073
+ const lineLength = Math.hypot(dx, dy);
10074
+ let cx = midX;
10075
+ let cy = midY;
10076
+ let peakX = midX;
10077
+ let peakY = midY;
10078
+ let isCurved = false;
10079
+ let lx1 = x1;
10080
+ let ly1 = y1;
10081
+ let lx2 = x2;
10082
+ let ly2 = y2;
10083
+ if (multiLinkTotal > 1 && lineLength > 0) {
10084
+ const nx = -dy / lineLength;
10085
+ const ny = dx / lineLength;
10086
+ const isCanonical = String(link.source) < String(link.target);
10087
+ const orientedNx = isCanonical ? nx : -nx;
10088
+ const orientedNy = isCanonical ? ny : -ny;
10089
+ if (multiLinkMode !== "parallel") {
10090
+ const baseArch = Math.min(36, Math.max(18, lineLength * 0.16));
10091
+ const step = multiLinkIndex - (multiLinkTotal - 1) / 2;
10092
+ const curveDistance = step * (multiLinkTotal === 2 ? baseArch * 2 : baseArch);
10093
+ if (Math.abs(curveDistance) > 0.5) {
10094
+ cx = midX + orientedNx * curveDistance;
10095
+ cy = midY + orientedNy * curveDistance;
10096
+ peakX = 0.5 * midX + 0.5 * cx;
10097
+ peakY = 0.5 * midY + 0.5 * cy;
10098
+ isCurved = true;
10099
+ }
10100
+ } else {
10101
+ const spacing = multiLinkTotal === 2 ? 8 : 7;
10102
+ const offsetDistance = (multiLinkIndex - (multiLinkTotal - 1) / 2) * spacing;
10103
+ const offsetX = orientedNx * offsetDistance;
10104
+ const offsetY = orientedNy * offsetDistance;
10105
+ lx1 = x1 + offsetX;
10106
+ ly1 = y1 + offsetY;
10107
+ lx2 = x2 + offsetX;
10108
+ ly2 = y2 + offsetY;
10109
+ peakX = (lx1 + lx2) / 2;
10110
+ peakY = (ly1 + ly2) / 2;
10111
+ }
10112
+ }
10113
+ const isDot = link.variant === "dot";
10114
+ const effectiveStatus = link.status ?? (isDot ? n1.status === "critical" || n2.status === "critical" ? "critical" : n1.status === "major" || n2.status === "major" ? "major" : n1.status === "minor" || n2.status === "minor" ? "minor" : n1.status === "warning" || n2.status === "warning" ? "warning" : void 0 : void 0);
10115
+ const statusColor = effectiveStatus === "critical" ? "var(--stn-grade-critical)" : effectiveStatus === "major" ? "var(--stn-grade-major)" : effectiveStatus === "minor" ? "var(--stn-grade-minor)" : effectiveStatus === "warning" ? "var(--stn-grade-warning)" : effectiveStatus === "primary" || effectiveStatus === "point" ? "var(--stn-primary)" : null;
10116
+ let dotElement = null;
10117
+ if (isDot) {
10118
+ const avgScale = n1 && n2 ? (n1.scale + n2.scale) / 2 : 1;
10119
+ const dotRadius = fixedNodeSize ? 3 * calibration : Math.max(1.5, 3 * avgScale * calibration);
10120
+ const dotFill = link.color ?? statusColor ?? "var(--stn-topology-node-normal)";
10121
+ dotElement = /* @__PURE__ */ jsx63("circle", { cx: peakX, cy: peakY, r: dotRadius, fill: dotFill, opacity: effectiveLineOpacity });
9997
10122
  }
9998
- const offsetY = -(TOPOLOGY_LINK_LABEL_SPEC.offsetY + effectiveStrokeWidth / 2);
9999
- const fontSize = Math.max(
10000
- TOPOLOGY_LINK_LABEL_SPEC.minFontSize,
10001
- Math.min(TOPOLOGY_LINK_LABEL_SPEC.maxFontSize, TOPOLOGY_LINK_LABEL_SPEC.baseFontSize * depthScale)
10123
+ const lineColor = link.color ?? (!isDot && statusColor ? statusColor : "var(--stn-topology-link-line)");
10124
+ const effectiveStrokeWidth = resolveTopologyStrokeWidth(
10125
+ link.strokeWidth,
10126
+ depthScale,
10127
+ zoom,
10128
+ viewMode === "2D"
10002
10129
  );
10003
- const labelColor = link.labelColor ?? "var(--stn-topology-link-label-fg, var(--stn-text-secondary))";
10004
- labelElement = /* @__PURE__ */ jsx63(
10005
- "g",
10006
- {
10007
- className: "stn-topology-link__label",
10008
- transform: `translate(${midX}, ${midY}) rotate(${rotation})`,
10009
- opacity: isLabelVisible ? 1 : 0,
10010
- children: /* @__PURE__ */ jsx63(
10011
- "text",
10012
- {
10013
- x: 0,
10014
- y: offsetY,
10015
- textAnchor: "middle",
10016
- dominantBaseline: "alphabetic",
10017
- fill: labelColor,
10018
- stroke: "var(--stn-topology-link-label-halo, var(--stn-elevation-surface-0))",
10019
- strokeWidth: TOPOLOGY_LINK_LABEL_SPEC.haloStrokeWidth,
10020
- paintOrder: "stroke",
10021
- strokeLinejoin: "round",
10022
- fontSize,
10023
- fontWeight: 600,
10024
- letterSpacing: "-0.2px",
10025
- children: link.label
10026
- }
10027
- )
10130
+ let labelElement = null;
10131
+ if (link.label) {
10132
+ const angleDeg = Math.atan2(dy, dx) * 180 / Math.PI;
10133
+ let rotation = angleDeg;
10134
+ if (rotation > 90) {
10135
+ rotation -= 180;
10136
+ } else if (rotation < -90) {
10137
+ rotation += 180;
10028
10138
  }
10029
- );
10030
- }
10031
- const isClickable = Boolean(onLinkClick);
10032
- return /* @__PURE__ */ jsxs50(
10033
- "g",
10034
- {
10035
- className: cn(
10036
- "stn-topology-link",
10037
- isClickable && "stn-topology-link--clickable",
10038
- isThisLinkHovered && "is-hovered"
10039
- ),
10040
- style: { cursor: isClickable ? "pointer" : void 0 },
10041
- onMouseEnter: (e) => {
10042
- setHoveredLinkKey(linkKey);
10043
- onLinkHover?.(link, e);
10044
- },
10045
- onMouseLeave: (e) => {
10046
- setHoveredLinkKey((prev) => prev === linkKey ? null : prev);
10047
- onLinkHover?.(null, e);
10048
- },
10049
- onClick: (e) => {
10050
- if (onLinkClick) {
10051
- e.stopPropagation();
10052
- onLinkClick(link, e);
10053
- }
10054
- },
10055
- children: [
10056
- /* @__PURE__ */ jsx63(
10057
- "line",
10058
- {
10059
- x1: n1.sx,
10060
- y1: n1.sy,
10061
- x2: n2.sx,
10062
- y2: n2.sy,
10063
- stroke: "transparent",
10064
- strokeWidth: Math.max(20, effectiveStrokeWidth + 16),
10065
- pointerEvents: "stroke"
10066
- }
10067
- ),
10068
- /* @__PURE__ */ jsx63(
10069
- "line",
10070
- {
10071
- x1: n1.sx,
10072
- y1: n1.sy,
10073
- x2: n2.sx,
10074
- y2: n2.sy,
10075
- stroke: lineColor,
10076
- strokeWidth: effectiveStrokeWidth,
10077
- strokeDasharray: link.dashed ? "4 4" : void 0,
10078
- opacity: effectiveLineOpacity,
10079
- pointerEvents: "none"
10080
- }
10081
- ),
10082
- dotElement,
10083
- labelElement
10084
- ]
10085
- },
10086
- `link-${idx}`
10087
- );
10088
- })
10089
- }
10090
- ),
10091
- /* @__PURE__ */ jsx63("g", { className: "stn-topology-nodes-group", children: sortedNodes.map((n) => {
10092
- const depthScale = 550 / (550 + n.depth);
10093
- const scale = fixedNodeSize ? calibration : Math.max(0.3, n.scale * calibration);
10094
- const baseOpacity = Math.max(0.75, Math.min(1, depthScale));
10095
- const opacity = resolveTopologyOpacity(n.opacity, baseOpacity);
10096
- const isSelected = (Array.isArray(selectedId) ? selectedId.some((sid) => String(sid) === String(n.id)) : selectedId !== void 0 && selectedId !== null && String(selectedId) === String(n.id)) || Boolean(n.isCurrent) || Boolean(n.isSelected);
10097
- const isCore = Boolean(n.isCore);
10098
- const isPrimary = n.status === "primary" || n.status === "point";
10099
- const isCritical = n.status === "critical";
10100
- const isWarning = n.status === "warning";
10101
- const isMajor = n.status === "major";
10102
- const isMinor = n.status === "minor";
10103
- const isNeutral = n.status === "neutral";
10104
- const isNormal = n.status === "normal";
10105
- const hasPulse = isCritical || Boolean(n.pulse);
10106
- const shape = n.shape ?? (isCore ? "diamond" : "circle");
10107
- const variant = n.variant ?? (isCore ? "combined" : "outline");
10108
- const statusColor = n.color ? n.color : isCritical ? "var(--stn-grade-critical)" : isMajor ? "var(--stn-grade-major)" : isMinor ? "var(--stn-grade-minor)" : isWarning ? "var(--stn-grade-warning)" : isPrimary || isCore ? "var(--stn-primary)" : isNormal || isNeutral ? "var(--stn-topology-node-normal)" : "var(--stn-topology-node-normal)";
10109
- const baseRadius = typeof n.size === "number" ? n.size : n.size && TOPOLOGY_NODE_SIZE_MAP[n.size] ? TOPOLOGY_NODE_SIZE_MAP[n.size] : isCore ? TOPOLOGY_NODE_SIZE_MAP.lg : TOPOLOGY_NODE_SIZE_MAP.md;
10110
- const r = baseRadius * scale;
10111
- const strokeWidth = calibrateScreenPixels ? Number((1.2 * calibration).toFixed(2)) : "var(--stn-topology-node-stroke, 1.2px)";
10112
- const rOuter = Math.max(baseRadius, (baseRadius + 2) * scale);
10113
- const pulseRing = hasPulse && /* @__PURE__ */ jsxs50("g", { children: [
10114
- /* @__PURE__ */ jsx63(
10115
- "circle",
10116
- {
10117
- cx: "0",
10118
- cy: "0",
10119
- r: rOuter,
10120
- fill: "none",
10121
- stroke: statusColor,
10122
- className: "stn-topology-pulse-ring-1"
10123
- }
10124
- ),
10125
- /* @__PURE__ */ jsx63(
10126
- "circle",
10127
- {
10128
- cx: "0",
10129
- cy: "0",
10130
- r: rOuter,
10131
- fill: "none",
10132
- stroke: statusColor,
10133
- className: "stn-topology-pulse-ring-2"
10134
- }
10135
- )
10136
- ] });
10137
- const currentRing = isSelected && renderCurrentNodeRing({ shape, r });
10138
- const glyph = render3DNodeGlyph({
10139
- shape,
10140
- variant,
10141
- r,
10142
- statusColor,
10143
- strokeWidth
10144
- });
10145
- let topBadgeElement = null;
10146
- const isTopBadge = Boolean(n.badge) && (n.badgePlacement ?? "top") === "top";
10147
- if (isTopBadge && n.badge) {
10148
- const badgeText = String(n.badge);
10149
- if (renderBadge) {
10150
- topBadgeElement = renderBadge(n, { r, position: "top", badgeText });
10151
- } else {
10152
- const badgeText2 = String(n.badge);
10153
- const bW = Math.max(140, badgeText2.length * 14 + 36);
10154
- const badgeScale = calibrateScreenPixels ? Number((0.95 * calibration).toFixed(2)) : fixedNodeSize ? 0.6 : 0.6 * Math.max(0.65, Math.min(1.4, depthScale));
10155
- const bH = Math.max(30, Math.round(26 * badgeScale));
10156
- const badgeHalfHeight = 18 * badgeScale / 2;
10157
- const posY = -r - 2.5 - badgeHalfHeight - bH / 2;
10158
- topBadgeElement = /* @__PURE__ */ jsx63(
10159
- "foreignObject",
10160
- {
10161
- x: -bW / 2,
10162
- y: posY,
10163
- width: bW,
10164
- height: bH,
10165
- className: "stn-topology-node__badge-fo",
10166
- children: /* @__PURE__ */ jsx63("div", { className: "stn-topology-node__badge-fo-inner", children: /* @__PURE__ */ jsx63(
10167
- "div",
10139
+ const offsetY = -(TOPOLOGY_LINK_LABEL_SPEC.offsetY + effectiveStrokeWidth / 2);
10140
+ const baseFontSize = link.labelFontSize ?? TOPOLOGY_LINK_LABEL_SPEC.baseFontSize;
10141
+ const fontSize = Math.max(
10142
+ TOPOLOGY_LINK_LABEL_SPEC.minFontSize,
10143
+ Math.min(TOPOLOGY_LINK_LABEL_SPEC.maxFontSize, baseFontSize * depthScale)
10144
+ );
10145
+ const labelColor = link.labelColor ?? "var(--stn-topology-link-label-fg, var(--stn-text-secondary))";
10146
+ labelElement = /* @__PURE__ */ jsx63(
10147
+ "g",
10168
10148
  {
10169
- className: "stn-topology-node__badge-fo-scale",
10170
- style: { transform: `scale(${badgeScale})` },
10149
+ className: "stn-topology-link__label",
10150
+ transform: `translate(${peakX}, ${peakY}) rotate(${rotation})`,
10151
+ opacity: isLabelVisible ? 1 : 0,
10171
10152
  children: /* @__PURE__ */ jsx63(
10172
- StnBadge,
10153
+ "text",
10173
10154
  {
10174
- color: n.badgeColor ?? "primary",
10175
- appearance: n.badgeAppearance ?? "solid",
10176
- badgeContent: badgeText2,
10177
- ...n.badgeProps
10155
+ x: 0,
10156
+ y: offsetY,
10157
+ textAnchor: "middle",
10158
+ dominantBaseline: "alphabetic",
10159
+ fill: labelColor,
10160
+ stroke: "var(--stn-topology-link-label-halo, var(--stn-elevation-surface-0))",
10161
+ strokeWidth: TOPOLOGY_LINK_LABEL_SPEC.haloStrokeWidth,
10162
+ paintOrder: "stroke",
10163
+ strokeLinejoin: "round",
10164
+ fontSize,
10165
+ fontWeight: 600,
10166
+ letterSpacing: "-0.2px",
10167
+ children: link.label
10178
10168
  }
10179
10169
  )
10180
10170
  }
10181
- ) })
10171
+ );
10182
10172
  }
10183
- );
10184
- }
10185
- }
10186
- let bottomLabelElement = null;
10187
- const isBottomVisible = showNodeLabels !== false && n.showLabel !== false && Boolean(n.label);
10188
- if (isBottomVisible && n.label) {
10189
- const labelText = String(n.label);
10190
- const mode = n.labelMode ?? nodeLabelMode ?? "badge";
10191
- const bottomY = r + 2.8;
10192
- if (renderNodeLabel) {
10193
- bottomLabelElement = renderNodeLabel(n, { r, label: labelText });
10194
- } else if (renderBadge && n.badgePlacement === "bottom") {
10195
- bottomLabelElement = renderBadge(n, { r, position: "bottom", badgeText: labelText });
10196
- } else if (mode === "badge") {
10197
- const bW = Math.max(160, Math.min(360, labelText.length * 12 + 40));
10198
- const badgeScale = calibrateScreenPixels ? Number((0.95 * calibration).toFixed(2)) : fixedNodeSize ? 0.6 : 0.6 * Math.max(0.65, Math.min(1.4, depthScale));
10199
- const bH = Math.max(30, Math.round(24 * badgeScale));
10200
- const badgeHalfHeight = 16 * badgeScale / 2;
10201
- const posY = r + 2.5 + badgeHalfHeight - bH / 2;
10202
- bottomLabelElement = /* @__PURE__ */ jsx63(
10203
- "foreignObject",
10204
- {
10205
- x: -bW / 2,
10206
- y: posY,
10207
- width: bW,
10208
- height: bH,
10209
- className: "stn-topology-node__badge-fo",
10210
- children: /* @__PURE__ */ jsx63("div", { className: "stn-topology-node__badge-fo-inner", children: /* @__PURE__ */ jsx63(
10211
- "div",
10212
- {
10213
- className: "stn-topology-node__badge-fo-scale",
10214
- style: { transform: `scale(${badgeScale})` },
10215
- children: /* @__PURE__ */ jsx63(
10216
- StnBadge,
10173
+ const pathD = isCurved ? `M ${x1.toFixed(1)} ${y1.toFixed(1)} Q ${cx.toFixed(1)} ${cy.toFixed(1)} ${x2.toFixed(1)} ${y2.toFixed(1)}` : `M ${lx1.toFixed(1)} ${ly1.toFixed(1)} L ${lx2.toFixed(1)} ${ly2.toFixed(1)}`;
10174
+ const isClickable = Boolean(onLinkClick);
10175
+ return /* @__PURE__ */ jsxs50(
10176
+ "g",
10177
+ {
10178
+ className: cn(
10179
+ "stn-topology-link",
10180
+ isClickable && "stn-topology-link--clickable",
10181
+ isThisLinkHovered && "is-hovered"
10182
+ ),
10183
+ style: { cursor: isClickable ? "pointer" : void 0 },
10184
+ onMouseEnter: (e) => {
10185
+ setHoveredLinkKey(linkKey);
10186
+ onLinkHover?.(link, e);
10187
+ },
10188
+ onMouseLeave: (e) => {
10189
+ setHoveredLinkKey((prev) => prev === linkKey ? null : prev);
10190
+ onLinkHover?.(null, e);
10191
+ },
10192
+ onClick: (e) => {
10193
+ if (onLinkClick) {
10194
+ e.stopPropagation();
10195
+ onLinkClick(link, e);
10196
+ }
10197
+ },
10198
+ children: [
10199
+ /* @__PURE__ */ jsx63(
10200
+ "path",
10217
10201
  {
10218
- color: n.labelBadgeColor ?? "neutral",
10219
- appearance: n.labelBadgeAppearance ?? "subtle",
10220
- badgeContent: labelText,
10221
- className: cn("stn-topology-node__label-badge", n.labelBadgeProps?.className),
10222
- ...n.labelBadgeProps
10202
+ d: pathD,
10203
+ fill: "none",
10204
+ stroke: "transparent",
10205
+ strokeWidth: Math.max(20, effectiveStrokeWidth + 16),
10206
+ pointerEvents: "stroke"
10223
10207
  }
10224
- )
10225
- }
10226
- ) })
10227
- }
10228
- );
10229
- } else {
10230
- const plainFontSize = calibrateScreenPixels ? Number((9.5 * calibration).toFixed(1)) : fixedNodeSize ? TOPOLOGY_LINK_LABEL_SPEC.baseFontSize : Number((TOPOLOGY_LINK_LABEL_SPEC.baseFontSize * Math.max(0.65, Math.min(1.4, depthScale))).toFixed(1));
10231
- bottomLabelElement = /* @__PURE__ */ jsx63("g", { className: "stn-topology-node__bottom-label stn-topology-node__bottom-label--plain", transform: `translate(0, ${bottomY})`, children: /* @__PURE__ */ jsx63(
10232
- "text",
10233
- {
10234
- x: 0,
10235
- y: 4.5,
10236
- textAnchor: "middle",
10237
- dominantBaseline: "middle",
10238
- fill: "var(--stn-topology-node-label-fg, var(--stn-text-primary))",
10239
- stroke: "var(--stn-topology-node-label-halo, var(--stn-elevation-surface-0))",
10240
- strokeWidth: TOPOLOGY_LINK_LABEL_SPEC.haloStrokeWidth,
10241
- paintOrder: "stroke",
10242
- strokeLinejoin: "round",
10243
- fontSize: plainFontSize,
10244
- fontWeight: 600,
10245
- letterSpacing: "-0.2px",
10246
- children: labelText
10247
- }
10248
- ) });
10208
+ ),
10209
+ /* @__PURE__ */ jsx63(
10210
+ "path",
10211
+ {
10212
+ d: pathD,
10213
+ fill: "none",
10214
+ stroke: lineColor,
10215
+ strokeWidth: effectiveStrokeWidth,
10216
+ strokeDasharray: link.dashed ? "4 4" : void 0,
10217
+ opacity: effectiveLineOpacity,
10218
+ pointerEvents: "none"
10219
+ }
10220
+ ),
10221
+ dotElement,
10222
+ labelElement
10223
+ ]
10224
+ },
10225
+ `link-${idx}`
10226
+ );
10227
+ })
10249
10228
  }
10250
- }
10251
- const nodeContent = /* @__PURE__ */ jsxs50("g", { children: [
10252
- currentRing,
10253
- pulseRing,
10254
- glyph,
10255
- topBadgeElement,
10256
- bottomLabelElement
10257
- ] });
10258
- return /* @__PURE__ */ jsx63(
10259
- "g",
10260
- {
10261
- className: cn("stn-topology-node", isSelected && "selected", isSelected && "is-current"),
10262
- transform: `translate(${n.sx}, ${n.sy})`,
10263
- opacity,
10264
- style: { cursor: "pointer" },
10265
- onMouseDown: (e) => handleNodeMouseDown(e, n.id),
10266
- onClick: (e) => {
10267
- e.stopPropagation();
10268
- if (hasDraggedRef.current) {
10269
- return;
10270
- }
10271
- onNodeClick?.(n, e);
10272
- },
10273
- onContextMenu: (e) => {
10274
- if (onNodeContextMenu) {
10275
- e.preventDefault();
10276
- e.stopPropagation();
10277
- onNodeContextMenu(n, e);
10229
+ ),
10230
+ /* @__PURE__ */ jsx63("g", { className: "stn-topology-nodes-group", children: sortedNodes.map((n) => {
10231
+ const depthScale = 550 / (550 + n.depth);
10232
+ const scale = fixedNodeSize ? calibration : Math.max(0.3, n.scale * calibration);
10233
+ const baseOpacity = Math.max(0.75, Math.min(1, depthScale));
10234
+ const opacity = resolveTopologyOpacity(n.opacity, baseOpacity);
10235
+ const isSelected = (Array.isArray(selectedId) ? selectedId.some((sid) => String(sid) === String(n.id)) : selectedId !== void 0 && selectedId !== null && String(selectedId) === String(n.id)) || Boolean(n.isCurrent) || Boolean(n.isSelected);
10236
+ const isCore = Boolean(n.isCore);
10237
+ const isPrimary = n.status === "primary" || n.status === "point";
10238
+ const isCritical = n.status === "critical";
10239
+ const isWarning = n.status === "warning";
10240
+ const isMajor = n.status === "major";
10241
+ const isMinor = n.status === "minor";
10242
+ const isNeutral = n.status === "neutral";
10243
+ const isNormal = n.status === "normal";
10244
+ const hasPulse = isCritical || Boolean(n.pulse);
10245
+ const shape = n.shape ?? (isCore ? "diamond" : "circle");
10246
+ const variant = n.variant ?? (isCore ? "combined" : "outline");
10247
+ const statusColor = n.color ? n.color : isCritical ? "var(--stn-grade-critical)" : isMajor ? "var(--stn-grade-major)" : isMinor ? "var(--stn-grade-minor)" : isWarning ? "var(--stn-grade-warning)" : isPrimary || isCore ? "var(--stn-primary)" : isNormal || isNeutral ? "var(--stn-topology-node-normal)" : "var(--stn-topology-node-normal)";
10248
+ const baseRadius = typeof n.size === "number" ? n.size : n.size && TOPOLOGY_NODE_SIZE_MAP[n.size] ? TOPOLOGY_NODE_SIZE_MAP[n.size] : isCore ? TOPOLOGY_NODE_SIZE_MAP.lg : TOPOLOGY_NODE_SIZE_MAP.md;
10249
+ const r = baseRadius * scale;
10250
+ const strokeWidth = calibrateScreenPixels ? Number((1.2 * calibration).toFixed(2)) : "var(--stn-topology-node-stroke, 1.2px)";
10251
+ const rOuter = Math.max(baseRadius, (baseRadius + 2) * scale);
10252
+ const pulseRing = hasPulse && /* @__PURE__ */ jsxs50("g", { children: [
10253
+ /* @__PURE__ */ jsx63(
10254
+ "circle",
10255
+ {
10256
+ cx: "0",
10257
+ cy: "0",
10258
+ r: rOuter,
10259
+ fill: "none",
10260
+ stroke: statusColor,
10261
+ className: "stn-topology-pulse-ring-1"
10278
10262
  }
10279
- },
10280
- onMouseEnter: () => {
10281
- setHoveredNodeId(n.id);
10282
- },
10283
- onMouseLeave: () => {
10284
- setHoveredNodeId(null);
10285
- },
10286
- children: nodeContent
10287
- },
10288
- n.id
10289
- );
10290
- }) }),
10291
- /* @__PURE__ */ jsx63("g", { className: "stn-topology-tooltips-group", style: { pointerEvents: "none", userSelect: "none" }, children: sortedNodes.map((n) => {
10292
- const isTooltipVisible = Boolean(showTooltips) || hoveredNodeId === n.id;
10293
- if (!isTooltipVisible || !n.label) return null;
10294
- const depthScale = 550 / (550 + n.depth);
10295
- const scale = fixedNodeSize ? calibration : Math.max(0.4, n.scale * calibration);
10296
- const baseRadius = typeof n.size === "number" ? n.size : n.size && TOPOLOGY_NODE_SIZE_MAP[n.size] ? TOPOLOGY_NODE_SIZE_MAP[n.size] : Boolean(n.isCore) ? TOPOLOGY_NODE_SIZE_MAP.lg : TOPOLOGY_NODE_SIZE_MAP.md;
10297
- const r = baseRadius * scale;
10298
- const isCritical = n.status === "critical";
10299
- const isWarning = n.status === "warning";
10300
- const isMajor = n.status === "major";
10301
- const isMinor = n.status === "minor";
10302
- const isCore = Boolean(n.isCore);
10303
- const tone = isCore ? "primary" : isCritical || Boolean(n.pulse) ? "critical" : isWarning ? "warning" : isMajor ? "major" : isMinor ? "minor" : "neutral";
10304
- const tooltipBg = tone === "primary" ? "var(--stn-primary)" : tone === "critical" ? "var(--stn-grade-critical)" : tone === "warning" ? "var(--stn-grade-warning)" : tone === "major" ? "var(--stn-grade-major)" : tone === "minor" ? "var(--stn-grade-minor)" : "var(--stn-tooltip-bg-neutral)";
10305
- const tooltipFg = "var(--stn-tooltip-fg-neutral)";
10306
- const hasBadge = Boolean(n.badge) && (n.badgePlacement ?? "top") === "top";
10307
- const topBadgeScale = calibrateScreenPixels ? Number((0.95 * calibration).toFixed(2)) : fixedNodeSize ? 0.6 : 0.6 * Math.max(0.65, Math.min(1.4, depthScale));
10308
- const badgeHeight = hasBadge ? 18 * topBadgeScale : 0;
10309
- const tooltipScale = calibrateScreenPixels ? calibration : fixedNodeSize ? 1 : Math.max(0.65, Math.min(1.4, depthScale));
10310
- const tooltipH = 18 * tooltipScale;
10311
- const arrowH = 3.5 * tooltipScale;
10312
- const arrowW = 7 * tooltipScale;
10313
- const gap = 3.5 * tooltipScale;
10314
- const tooltipBoxY = -r - badgeHeight - gap - arrowH - tooltipH;
10315
- const charCount = n.label.length;
10316
- const tooltipFontSize = Number((10 * tooltipScale).toFixed(1));
10317
- const tooltipW = Math.max(32 * tooltipScale, charCount * 6.8 * tooltipScale + 16 * tooltipScale);
10318
- const rx = 3 * tooltipScale;
10319
- const nodeOpacity = resolveTopologyOpacity(n.opacity, Math.max(0.75, Math.min(1, depthScale)));
10320
- return /* @__PURE__ */ jsx63(
10321
- "g",
10322
- {
10323
- className: cn("stn-topology-node-tooltip", hoveredNodeId === n.id && "is-hovered"),
10324
- transform: `translate(${n.sx}, ${n.sy})`,
10325
- opacity: nodeOpacity,
10326
- children: /* @__PURE__ */ jsxs50(
10327
- "g",
10263
+ ),
10264
+ /* @__PURE__ */ jsx63(
10265
+ "circle",
10328
10266
  {
10329
- className: "stn-topology-node-tooltip__box",
10330
- transform: `translate(0, ${tooltipBoxY})`,
10331
- children: [
10332
- /* @__PURE__ */ jsx63(
10333
- "rect",
10334
- {
10335
- x: -tooltipW / 2,
10336
- y: 0,
10337
- width: tooltipW,
10338
- height: tooltipH,
10339
- rx,
10340
- fill: tooltipBg
10341
- }
10342
- ),
10343
- /* @__PURE__ */ jsx63(
10344
- "polygon",
10267
+ cx: "0",
10268
+ cy: "0",
10269
+ r: rOuter,
10270
+ fill: "none",
10271
+ stroke: statusColor,
10272
+ className: "stn-topology-pulse-ring-2"
10273
+ }
10274
+ )
10275
+ ] });
10276
+ const currentRing = isSelected && renderCurrentNodeRing({ shape, r });
10277
+ const glyph = render3DNodeGlyph({
10278
+ shape,
10279
+ variant,
10280
+ r,
10281
+ statusColor,
10282
+ strokeWidth
10283
+ });
10284
+ let topBadgeElement = null;
10285
+ const isTopBadge = Boolean(n.badge) && (n.badgePlacement ?? "top") === "top";
10286
+ if (isTopBadge && n.badge) {
10287
+ const badgeText = String(n.badge);
10288
+ if (renderBadge) {
10289
+ topBadgeElement = renderBadge(n, { r, position: "top", badgeText });
10290
+ } else {
10291
+ const badgeText2 = String(n.badge);
10292
+ const bW = Math.max(140, badgeText2.length * 14 + 36);
10293
+ const badgeScale = calibrateScreenPixels ? Number((0.95 * calibration).toFixed(2)) : fixedNodeSize ? 0.6 : 0.6 * Math.max(0.65, Math.min(1.4, depthScale));
10294
+ const bH = Math.max(30, Math.round(26 * badgeScale));
10295
+ const badgeHalfHeight = 18 * badgeScale / 2;
10296
+ const posY = -r - 2.5 - badgeHalfHeight - bH / 2;
10297
+ topBadgeElement = /* @__PURE__ */ jsx63(
10298
+ "foreignObject",
10299
+ {
10300
+ x: -bW / 2,
10301
+ y: posY,
10302
+ width: bW,
10303
+ height: bH,
10304
+ className: "stn-topology-node__badge-fo",
10305
+ children: /* @__PURE__ */ jsx63("div", { className: "stn-topology-node__badge-fo-inner", children: /* @__PURE__ */ jsx63(
10306
+ "div",
10345
10307
  {
10346
- points: `-${arrowW / 2},${tooltipH} ${arrowW / 2},${tooltipH} 0,${tooltipH + arrowH}`,
10347
- fill: tooltipBg
10308
+ className: "stn-topology-node__badge-fo-scale",
10309
+ style: { transform: `scale(${badgeScale})` },
10310
+ children: /* @__PURE__ */ jsx63(
10311
+ StnBadge,
10312
+ {
10313
+ color: n.badgeColor ?? "primary",
10314
+ appearance: n.badgeAppearance ?? "solid",
10315
+ badgeContent: badgeText2,
10316
+ ...n.badgeProps
10317
+ }
10318
+ )
10348
10319
  }
10349
- ),
10350
- /* @__PURE__ */ jsx63(
10351
- "text",
10320
+ ) })
10321
+ }
10322
+ );
10323
+ }
10324
+ }
10325
+ let bottomLabelElement = null;
10326
+ const isBottomVisible = showNodeLabels !== false && n.showLabel !== false && Boolean(n.label);
10327
+ if (isBottomVisible && n.label) {
10328
+ const labelText = String(n.label);
10329
+ const mode = n.labelMode ?? nodeLabelMode ?? "badge";
10330
+ const bottomY = r + 2.8;
10331
+ if (renderNodeLabel) {
10332
+ bottomLabelElement = renderNodeLabel(n, { r, label: labelText });
10333
+ } else if (renderBadge && n.badgePlacement === "bottom") {
10334
+ bottomLabelElement = renderBadge(n, { r, position: "bottom", badgeText: labelText });
10335
+ } else if (mode === "badge") {
10336
+ const bW = Math.max(160, Math.min(360, labelText.length * 12 + 40));
10337
+ const badgeScale = calibrateScreenPixels ? Number((0.95 * calibration).toFixed(2)) : fixedNodeSize ? 0.6 : 0.6 * Math.max(0.65, Math.min(1.4, depthScale));
10338
+ const bH = Math.max(30, Math.round(24 * badgeScale));
10339
+ const badgeHalfHeight = 16 * badgeScale / 2;
10340
+ const posY = r + 2.5 + badgeHalfHeight - bH / 2;
10341
+ bottomLabelElement = /* @__PURE__ */ jsx63(
10342
+ "foreignObject",
10343
+ {
10344
+ x: -bW / 2,
10345
+ y: posY,
10346
+ width: bW,
10347
+ height: bH,
10348
+ className: "stn-topology-node__badge-fo",
10349
+ children: /* @__PURE__ */ jsx63("div", { className: "stn-topology-node__badge-fo-inner", children: /* @__PURE__ */ jsx63(
10350
+ "div",
10352
10351
  {
10353
- x: 0,
10354
- y: tooltipH / 2 + 0.3,
10355
- textAnchor: "middle",
10356
- dominantBaseline: "central",
10357
- fill: tooltipFg,
10358
- fontSize: tooltipFontSize,
10359
- fontWeight: 600,
10360
- letterSpacing: "-0.1px",
10361
- children: n.label
10352
+ className: "stn-topology-node__badge-fo-scale",
10353
+ style: { transform: `scale(${badgeScale})` },
10354
+ children: /* @__PURE__ */ jsx63(
10355
+ StnBadge,
10356
+ {
10357
+ color: n.labelBadgeColor ?? "neutral",
10358
+ appearance: n.labelBadgeAppearance ?? "subtle",
10359
+ badgeContent: labelText,
10360
+ className: cn("stn-topology-node__label-badge", n.labelBadgeProps?.className),
10361
+ ...n.labelBadgeProps
10362
+ }
10363
+ )
10362
10364
  }
10363
- )
10364
- ]
10365
- }
10366
- )
10367
- },
10368
- `tooltip-${n.id}`
10369
- );
10370
- }) })
10365
+ ) })
10366
+ }
10367
+ );
10368
+ } else {
10369
+ const plainFontSize = calibrateScreenPixels ? Number((9.5 * calibration).toFixed(1)) : fixedNodeSize ? TOPOLOGY_LINK_LABEL_SPEC.baseFontSize : Number((TOPOLOGY_LINK_LABEL_SPEC.baseFontSize * Math.max(0.65, Math.min(1.4, depthScale))).toFixed(1));
10370
+ bottomLabelElement = /* @__PURE__ */ jsx63("g", { className: "stn-topology-node__bottom-label stn-topology-node__bottom-label--plain", transform: `translate(0, ${bottomY})`, children: /* @__PURE__ */ jsx63(
10371
+ "text",
10372
+ {
10373
+ x: 0,
10374
+ y: 4.5,
10375
+ textAnchor: "middle",
10376
+ dominantBaseline: "middle",
10377
+ fill: "var(--stn-topology-node-label-fg, var(--stn-text-primary))",
10378
+ stroke: "var(--stn-topology-node-label-halo, var(--stn-elevation-surface-0))",
10379
+ strokeWidth: TOPOLOGY_LINK_LABEL_SPEC.haloStrokeWidth,
10380
+ paintOrder: "stroke",
10381
+ strokeLinejoin: "round",
10382
+ fontSize: plainFontSize,
10383
+ fontWeight: 600,
10384
+ letterSpacing: "-0.2px",
10385
+ children: labelText
10386
+ }
10387
+ ) });
10388
+ }
10389
+ }
10390
+ const nodeContent = /* @__PURE__ */ jsxs50("g", { children: [
10391
+ currentRing,
10392
+ pulseRing,
10393
+ glyph,
10394
+ topBadgeElement,
10395
+ bottomLabelElement
10396
+ ] });
10397
+ return /* @__PURE__ */ jsx63(
10398
+ "g",
10399
+ {
10400
+ className: cn("stn-topology-node", isSelected && "selected", isSelected && "is-current"),
10401
+ transform: `translate(${n.sx}, ${n.sy})`,
10402
+ opacity,
10403
+ style: { cursor: "pointer" },
10404
+ onMouseDown: (e) => handleNodeMouseDown(e, n.id),
10405
+ onClick: (e) => {
10406
+ e.stopPropagation();
10407
+ if (hasDraggedRef.current) {
10408
+ return;
10409
+ }
10410
+ onNodeClick?.(n, e);
10411
+ },
10412
+ onContextMenu: (e) => {
10413
+ if (onNodeContextMenu) {
10414
+ e.preventDefault();
10415
+ e.stopPropagation();
10416
+ onNodeContextMenu(n, e);
10417
+ }
10418
+ },
10419
+ onMouseEnter: () => {
10420
+ setHoveredNodeId(n.id);
10421
+ },
10422
+ onMouseLeave: () => {
10423
+ setHoveredNodeId(null);
10424
+ },
10425
+ children: nodeContent
10426
+ },
10427
+ n.id
10428
+ );
10429
+ }) }),
10430
+ /* @__PURE__ */ jsx63("g", { className: "stn-topology-tooltips-group", style: { pointerEvents: "none", userSelect: "none" }, children: sortedNodes.map((n) => {
10431
+ const isTooltipVisible = Boolean(showTooltips) || hoveredNodeId === n.id;
10432
+ if (!isTooltipVisible || !n.label) return null;
10433
+ const depthScale = 550 / (550 + n.depth);
10434
+ const scale = fixedNodeSize ? calibration : Math.max(0.4, n.scale * calibration);
10435
+ const baseRadius = typeof n.size === "number" ? n.size : n.size && TOPOLOGY_NODE_SIZE_MAP[n.size] ? TOPOLOGY_NODE_SIZE_MAP[n.size] : Boolean(n.isCore) ? TOPOLOGY_NODE_SIZE_MAP.lg : TOPOLOGY_NODE_SIZE_MAP.md;
10436
+ const r = baseRadius * scale;
10437
+ const isCritical = n.status === "critical";
10438
+ const isWarning = n.status === "warning";
10439
+ const isMajor = n.status === "major";
10440
+ const isMinor = n.status === "minor";
10441
+ const isCore = Boolean(n.isCore);
10442
+ const tone = isCore ? "primary" : isCritical || Boolean(n.pulse) ? "critical" : isWarning ? "warning" : isMajor ? "major" : isMinor ? "minor" : "neutral";
10443
+ const tooltipBg = tone === "primary" ? "var(--stn-primary)" : tone === "critical" ? "var(--stn-grade-critical)" : tone === "warning" ? "var(--stn-grade-warning)" : tone === "major" ? "var(--stn-grade-major)" : tone === "minor" ? "var(--stn-grade-minor)" : "var(--stn-tooltip-bg-neutral)";
10444
+ const tooltipFg = "var(--stn-tooltip-fg-neutral)";
10445
+ const hasBadge = Boolean(n.badge) && (n.badgePlacement ?? "top") === "top";
10446
+ const topBadgeScale = calibrateScreenPixels ? Number((0.95 * calibration).toFixed(2)) : fixedNodeSize ? 0.6 : 0.6 * Math.max(0.65, Math.min(1.4, depthScale));
10447
+ const badgeHeight = hasBadge ? 18 * topBadgeScale : 0;
10448
+ const tooltipScale = calibrateScreenPixels ? calibration : fixedNodeSize ? 1 : Math.max(0.65, Math.min(1.4, depthScale));
10449
+ const tooltipH = 18 * tooltipScale;
10450
+ const arrowH = 3.5 * tooltipScale;
10451
+ const arrowW = 7 * tooltipScale;
10452
+ const gap = 3.5 * tooltipScale;
10453
+ const tooltipBoxY = -r - badgeHeight - gap - arrowH - tooltipH;
10454
+ const charCount = n.label.length;
10455
+ const tooltipFontSize = Number((10 * tooltipScale).toFixed(1));
10456
+ const tooltipW = Math.max(32 * tooltipScale, charCount * 6.8 * tooltipScale + 16 * tooltipScale);
10457
+ const rx = 3 * tooltipScale;
10458
+ const nodeOpacity = resolveTopologyOpacity(n.opacity, Math.max(0.75, Math.min(1, depthScale)));
10459
+ return /* @__PURE__ */ jsx63(
10460
+ "g",
10461
+ {
10462
+ className: cn("stn-topology-node-tooltip", hoveredNodeId === n.id && "is-hovered"),
10463
+ transform: `translate(${n.sx}, ${n.sy})`,
10464
+ opacity: nodeOpacity,
10465
+ children: /* @__PURE__ */ jsxs50(
10466
+ "g",
10467
+ {
10468
+ className: "stn-topology-node-tooltip__box",
10469
+ transform: `translate(0, ${tooltipBoxY})`,
10470
+ children: [
10471
+ /* @__PURE__ */ jsx63(
10472
+ "rect",
10473
+ {
10474
+ x: -tooltipW / 2,
10475
+ y: 0,
10476
+ width: tooltipW,
10477
+ height: tooltipH,
10478
+ rx,
10479
+ fill: tooltipBg
10480
+ }
10481
+ ),
10482
+ /* @__PURE__ */ jsx63(
10483
+ "polygon",
10484
+ {
10485
+ points: `-${arrowW / 2},${tooltipH} ${arrowW / 2},${tooltipH} 0,${tooltipH + arrowH}`,
10486
+ fill: tooltipBg
10487
+ }
10488
+ ),
10489
+ /* @__PURE__ */ jsx63(
10490
+ "text",
10491
+ {
10492
+ x: 0,
10493
+ y: tooltipH / 2 + 0.3,
10494
+ textAnchor: "middle",
10495
+ dominantBaseline: "central",
10496
+ fill: tooltipFg,
10497
+ fontSize: tooltipFontSize,
10498
+ fontWeight: 600,
10499
+ letterSpacing: "-0.1px",
10500
+ children: n.label
10501
+ }
10502
+ )
10503
+ ]
10504
+ }
10505
+ )
10506
+ },
10507
+ `tooltip-${n.id}`
10508
+ );
10509
+ }) })
10510
+ ] })
10371
10511
  ]
10372
10512
  }
10373
10513
  );
@@ -10385,6 +10525,9 @@ var StnTopologyGraphInternal = forwardRef39(
10385
10525
  mode: modeProp,
10386
10526
  defaultMode = "2D",
10387
10527
  onModeChange,
10528
+ panOnDrag = true,
10529
+ initialPan = { x: 0, y: 0 },
10530
+ onPanChange,
10388
10531
  showModeToggle = true,
10389
10532
  showToolbar = true,
10390
10533
  showMinimap = false,
@@ -10395,6 +10538,7 @@ var StnTopologyGraphInternal = forwardRef39(
10395
10538
  showNodeLabels = true,
10396
10539
  nodeLabelMode = "badge",
10397
10540
  linkLabelMode = "hover",
10541
+ multiLinkMode = "curved",
10398
10542
  onLinkHover,
10399
10543
  useHtmlBadge = true,
10400
10544
  renderBadge,
@@ -10443,86 +10587,78 @@ var StnTopologyGraphInternal = forwardRef39(
10443
10587
  [declarativeLinks, links]
10444
10588
  );
10445
10589
  const isModeControlled = modeProp !== void 0;
10446
- const [internalMode, setInternalMode] = useState17(defaultMode);
10590
+ const [internalMode, setInternalMode] = useState18(defaultMode);
10447
10591
  const viewMode = isModeControlled ? modeProp : internalMode;
10448
- const [pitch, setPitch] = useState17(viewMode === "2D" ? 0 : initialPitch);
10449
- const [yaw, setYaw] = useState17(viewMode === "2D" ? 0 : initialYaw);
10450
- const [zoom, setZoom] = useState17(initialZoom);
10451
- const [projectionType, setProjectionType] = useState17("\uAD6C\uBA74");
10452
- const [isMinimapOpen, setIsMinimapOpen] = useState17(true);
10453
- const [nodeList, setNodeList] = useState17(effectiveNodes);
10454
- const [hoveredNodeId, setHoveredNodeId] = useState17(null);
10455
- const [tooltip, setTooltip] = useState17({
10592
+ const [pitch, setPitch] = useState18(viewMode === "2D" ? 0 : initialPitch);
10593
+ const [yaw, setYaw] = useState18(viewMode === "2D" ? 0 : initialYaw);
10594
+ const [zoom, setZoom] = useState18(initialZoom);
10595
+ const [projectionType, setProjectionType] = useState18("\uAD6C\uBA74");
10596
+ const [isMinimapOpen, setIsMinimapOpen] = useState18(true);
10597
+ const [nodeList, setNodeList] = useState18(effectiveNodes);
10598
+ const [hoveredNodeId, setHoveredNodeId] = useState18(null);
10599
+ const [tooltip, setTooltip] = useState18({
10456
10600
  visible: false,
10457
10601
  x: 0,
10458
10602
  y: 0,
10459
10603
  text: "",
10460
10604
  tone: "neutral"
10461
10605
  });
10462
- const containerRef = useRef11(null);
10463
- const isOrbitingRef = useRef11(false);
10464
- const isDraggingNodeRef = useRef11(false);
10465
- const draggedNodeIdRef = useRef11(null);
10466
- const lastMousePosRef = useRef11({ x: 0, y: 0 });
10467
- const viewModeRef = useRef11(viewMode);
10606
+ const containerRef = useRef12(null);
10607
+ const isOrbitingRef = useRef12(false);
10608
+ const isDraggingNodeRef = useRef12(false);
10609
+ const draggedNodeIdRef = useRef12(null);
10610
+ const lastMousePosRef = useRef12({ x: 0, y: 0 });
10611
+ const [pan, setPan] = useState18(initialPan ?? { x: 0, y: 0 });
10612
+ const panRef = useRef12(pan);
10613
+ panRef.current = pan;
10614
+ const panOnDragRef = useRef12(panOnDrag);
10615
+ panOnDragRef.current = panOnDrag;
10616
+ const [isPanning, setIsPanning] = useState18(false);
10617
+ const isPanningRef = useRef12(false);
10618
+ const viewModeRef = useRef12(viewMode);
10468
10619
  viewModeRef.current = viewMode;
10469
- const pitchRef = useRef11(pitch);
10620
+ const pitchRef = useRef12(pitch);
10470
10621
  pitchRef.current = pitch;
10471
- const yawRef = useRef11(yaw);
10622
+ const yawRef = useRef12(yaw);
10472
10623
  yawRef.current = yaw;
10473
- const zoomRef = useRef11(zoom);
10624
+ const zoomRef = useRef12(zoom);
10474
10625
  zoomRef.current = zoom;
10475
- const [containerDimensions, setContainerDimensions] = useState17({
10476
- width: 500,
10477
- height: 500
10626
+ const [containerDimensions, setContainerDimensions] = useState18({
10627
+ width: 800,
10628
+ height: 600
10478
10629
  });
10479
- const [screenPixelsRatio, setScreenPixelsRatio] = useState17(1);
10630
+ const [screenPixelsRatio] = useState18(1);
10480
10631
  useEffect14(() => {
10481
10632
  const container = containerRef.current;
10482
10633
  if (!container) return;
10483
- const updateDimensionsAndScale = () => {
10634
+ const updateDimensions = () => {
10484
10635
  const rect = container.getBoundingClientRect();
10485
- if (rect.width > 0 && rect.height > 0) {
10486
- setContainerDimensions({ width: rect.width, height: rect.height });
10487
- }
10488
- if (calibrateScreenPixels) {
10489
- const svg = container.querySelector("svg.stn-topology-graph__svg");
10490
- const ctm = svg?.getScreenCTM();
10491
- if (ctm && ctm.a > 0) {
10492
- setScreenPixelsRatio(ctm.a);
10493
- } else if (rect.width > 0 && rect.height > 0) {
10494
- const minDim = Math.min(rect.width, rect.height);
10495
- setScreenPixelsRatio(minDim / 500);
10496
- }
10636
+ if (rect.width >= 50 && rect.height >= 50) {
10637
+ setContainerDimensions((prev) => {
10638
+ if (Math.abs(prev.width - rect.width) < 1 && Math.abs(prev.height - rect.height) < 1) {
10639
+ return prev;
10640
+ }
10641
+ return { width: Math.round(rect.width), height: Math.round(rect.height) };
10642
+ });
10497
10643
  }
10498
10644
  };
10499
- updateDimensionsAndScale();
10645
+ updateDimensions();
10500
10646
  const ro = new ResizeObserver(() => {
10501
- updateDimensionsAndScale();
10647
+ updateDimensions();
10502
10648
  });
10503
10649
  ro.observe(container);
10504
- window.addEventListener("resize", updateDimensionsAndScale);
10650
+ window.addEventListener("resize", updateDimensions);
10505
10651
  return () => {
10506
10652
  ro.disconnect();
10507
- window.removeEventListener("resize", updateDimensionsAndScale);
10653
+ window.removeEventListener("resize", updateDimensions);
10508
10654
  };
10509
- }, [calibrateScreenPixels]);
10655
+ }, []);
10510
10656
  const dynamicViewBox = useMemo4(() => {
10511
- const { width, height: height2 } = containerDimensions;
10512
- if (width <= 0 || height2 <= 0) return "-250 -250 500 500";
10513
- const aspect = width / height2;
10514
- let vWidth = 500;
10515
- let vHeight = 500;
10516
- if (aspect >= 1) {
10517
- vWidth = 500 * aspect;
10518
- vHeight = 500;
10519
- } else {
10520
- vWidth = 500;
10521
- vHeight = 500 / aspect;
10522
- }
10523
- const minX = -vWidth / 2;
10524
- const minY = -vHeight / 2;
10525
- return `${minX.toFixed(1)} ${minY.toFixed(1)} ${vWidth.toFixed(1)} ${vHeight.toFixed(1)}`;
10657
+ const width = containerDimensions.width > 0 ? containerDimensions.width : 800;
10658
+ const height2 = containerDimensions.height > 0 ? containerDimensions.height : 600;
10659
+ const minX = -width / 2;
10660
+ const minY = -height2 / 2;
10661
+ return `${minX.toFixed(1)} ${minY.toFixed(1)} ${width.toFixed(1)} ${height2.toFixed(1)}`;
10526
10662
  }, [containerDimensions]);
10527
10663
  useEffect14(() => {
10528
10664
  setNodeList((prev) => {
@@ -10574,12 +10710,16 @@ var StnTopologyGraphInternal = forwardRef39(
10574
10710
  }
10575
10711
  }, [isModeControlled, modeProp, initialPitch, initialYaw]);
10576
10712
  const handleResetWorld = useCallback10(() => {
10577
- handleSwitchMode("3D");
10578
- setPitch(initialPitch);
10579
- setYaw(initialYaw);
10713
+ if (!isModeControlled) {
10714
+ handleSwitchMode(defaultMode);
10715
+ }
10716
+ setPitch(viewMode === "2D" ? 0 : initialPitch);
10717
+ setYaw(viewMode === "2D" ? 0 : initialYaw);
10580
10718
  setZoom(initialZoom);
10719
+ setPan({ x: 0, y: 0 });
10720
+ onPanChange?.({ x: 0, y: 0 });
10581
10721
  setNodeList(effectiveNodes);
10582
- }, [handleSwitchMode, initialPitch, initialYaw, initialZoom, effectiveNodes]);
10722
+ }, [isModeControlled, handleSwitchMode, defaultMode, viewMode, initialPitch, initialYaw, initialZoom, effectiveNodes, onPanChange]);
10583
10723
  const handleZoom = useCallback10((factor) => {
10584
10724
  setZoom((prev) => Math.max(0.3, Math.min(3.5, prev * factor)));
10585
10725
  }, []);
@@ -10604,18 +10744,32 @@ var StnTopologyGraphInternal = forwardRef39(
10604
10744
  reset: handleResetWorld,
10605
10745
  toggleFullscreen: handleToggleFullscreen,
10606
10746
  switchMode: handleSwitchMode,
10747
+ pan,
10748
+ setPan,
10749
+ resetPan: () => {
10750
+ setPan({ x: 0, y: 0 });
10751
+ onPanChange?.({ x: 0, y: 0 });
10752
+ },
10607
10753
  container: containerRef.current
10608
10754
  }),
10609
- [handleZoom, setZoom, initialZoom, handleResetWorld, handleToggleFullscreen, handleSwitchMode]
10755
+ [handleZoom, setZoom, initialZoom, handleResetWorld, handleToggleFullscreen, handleSwitchMode, pan, onPanChange]
10610
10756
  );
10611
- const hasDraggedRef = useRef11(false);
10612
- const mouseDownPosRef = useRef11({ x: 0, y: 0 });
10757
+ const hasDraggedRef = useRef12(false);
10758
+ const mouseDownPosRef = useRef12({ x: 0, y: 0 });
10613
10759
  const handleMouseDown = (e) => {
10614
10760
  if (isDraggingNodeRef.current) return;
10615
- isOrbitingRef.current = true;
10616
- hasDraggedRef.current = false;
10761
+ if (e.button !== 0) return;
10617
10762
  mouseDownPosRef.current = { x: e.clientX, y: e.clientY };
10618
10763
  lastMousePosRef.current = { x: e.clientX, y: e.clientY };
10764
+ hasDraggedRef.current = false;
10765
+ if (viewModeRef.current === "2D") {
10766
+ if (panOnDragRef.current) {
10767
+ isPanningRef.current = true;
10768
+ setIsPanning(true);
10769
+ }
10770
+ } else {
10771
+ isOrbitingRef.current = true;
10772
+ }
10619
10773
  };
10620
10774
  const handleNodeMouseDown = (e, id) => {
10621
10775
  e.stopPropagation();
@@ -10635,12 +10789,8 @@ var StnTopologyGraphInternal = forwardRef39(
10635
10789
  const dy = e.clientY - lastMousePosRef.current.y;
10636
10790
  lastMousePosRef.current = { x: e.clientX, y: e.clientY };
10637
10791
  const currentZoom = Math.max(0.1, zoomRef.current);
10638
- const svg = containerRef.current?.querySelector("svg.stn-topology-graph__svg");
10639
- const ctm = svg?.getScreenCTM();
10640
- const rect = containerRef.current?.getBoundingClientRect();
10641
- const screenPixelsPerSvgUnit = ctm && ctm.a > 0 ? ctm.a : rect && rect.height > 0 && rect.width > 0 ? Math.min(rect.width, rect.height) / 500 : 1;
10642
10792
  if (viewModeRef.current === "2D") {
10643
- const moveFactor = 1 / (currentZoom * screenPixelsPerSvgUnit);
10793
+ const moveFactor = 1 / currentZoom;
10644
10794
  setNodeList(
10645
10795
  (prev) => prev.map(
10646
10796
  (n) => n.id === draggedNodeIdRef.current ? { ...n, x: n.x + dx * moveFactor, y: n.y + dy * moveFactor } : n
@@ -10666,7 +10816,7 @@ var StnTopologyGraphInternal = forwardRef39(
10666
10816
  currentPitch,
10667
10817
  currentZoom
10668
10818
  );
10669
- const effectiveScale = Math.max(0.05, proj.scale * screenPixelsPerSvgUnit);
10819
+ const effectiveScale = Math.max(0.05, proj.scale);
10670
10820
  const dx_cam = dx / effectiveScale;
10671
10821
  const dy_cam = dy / effectiveScale;
10672
10822
  const dx_world = dx_cam * cosY + dy_cam * sinP * sinY;
@@ -10682,6 +10832,15 @@ var StnTopologyGraphInternal = forwardRef39(
10682
10832
  );
10683
10833
  });
10684
10834
  }
10835
+ } else if (isPanningRef.current) {
10836
+ const dx = e.clientX - lastMousePosRef.current.x;
10837
+ const dy = e.clientY - lastMousePosRef.current.y;
10838
+ lastMousePosRef.current = { x: e.clientX, y: e.clientY };
10839
+ setPan((prev) => {
10840
+ const next = { x: prev.x + dx, y: prev.y + dy };
10841
+ onPanChange?.(next);
10842
+ return next;
10843
+ });
10685
10844
  } else if (isOrbitingRef.current) {
10686
10845
  if (viewModeRef.current === "2D") return;
10687
10846
  const dx = e.clientX - lastMousePosRef.current.x;
@@ -10690,7 +10849,7 @@ var StnTopologyGraphInternal = forwardRef39(
10690
10849
  setYaw((prev) => (prev + dx * 0.6) % 360);
10691
10850
  setPitch((prev) => Math.max(-80, Math.min(80, prev + dy * 0.6)));
10692
10851
  }
10693
- }, []);
10852
+ }, [onPanChange]);
10694
10853
  const handleContainerClick = (e) => {
10695
10854
  if (hasDraggedRef.current) {
10696
10855
  hasDraggedRef.current = false;
@@ -10701,6 +10860,8 @@ var StnTopologyGraphInternal = forwardRef39(
10701
10860
  };
10702
10861
  const handleMouseUp = useCallback10(() => {
10703
10862
  isOrbitingRef.current = false;
10863
+ isPanningRef.current = false;
10864
+ setIsPanning(false);
10704
10865
  isDraggingNodeRef.current = false;
10705
10866
  draggedNodeIdRef.current = null;
10706
10867
  setTimeout(() => {
@@ -10739,11 +10900,21 @@ var StnTopologyGraphInternal = forwardRef39(
10739
10900
  const nodeMap = new Map(
10740
10901
  projectedNodes.map((n) => [n.id, n])
10741
10902
  );
10903
+ const pairCounts = /* @__PURE__ */ new Map();
10904
+ for (const l of effectiveLinks) {
10905
+ const key = [String(l.source), String(l.target)].sort().join(":::");
10906
+ pairCounts.set(key, (pairCounts.get(key) ?? 0) + 1);
10907
+ }
10908
+ const pairIndices = /* @__PURE__ */ new Map();
10742
10909
  const projectedLinks = effectiveLinks.map((link) => {
10743
10910
  const n1 = nodeMap.get(link.source);
10744
10911
  const n2 = nodeMap.get(link.target);
10745
10912
  const avgDepth = n1 && n2 ? (n1.depth + n2.depth) / 2 : 0;
10746
- return { ...link, n1, n2, avgDepth };
10913
+ const key = [String(link.source), String(link.target)].sort().join(":::");
10914
+ const multiLinkTotal = pairCounts.get(key) ?? 1;
10915
+ const multiLinkIndex = pairIndices.get(key) ?? 0;
10916
+ pairIndices.set(key, multiLinkIndex + 1);
10917
+ return { ...link, n1, n2, avgDepth, multiLinkIndex, multiLinkTotal };
10747
10918
  }).sort((a, b) => b.avgDepth - a.avgDepth);
10748
10919
  const sortedNodes = [...projectedNodes].sort((a, b) => b.depth - a.depth);
10749
10920
  const renderGridLines = useCallback10(() => {
@@ -10832,6 +11003,7 @@ var StnTopologyGraphInternal = forwardRef39(
10832
11003
  onLinkClick,
10833
11004
  onLinkHover,
10834
11005
  linkLabelMode,
11006
+ multiLinkMode,
10835
11007
  onCanvasClick,
10836
11008
  onBackgroundClick,
10837
11009
  handleNodeMouseDown,
@@ -10845,14 +11017,19 @@ var StnTopologyGraphInternal = forwardRef39(
10845
11017
  renderNodeLabel,
10846
11018
  hoveredNodeId,
10847
11019
  setHoveredNodeId,
10848
- hasDraggedRef
11020
+ hasDraggedRef,
11021
+ pan,
11022
+ setPan,
11023
+ panOnDrag,
11024
+ isPanning
10849
11025
  };
11026
+ const containerCursor = viewMode === "2D" ? panOnDrag ? isPanning ? "grabbing" : "grab" : "default" : isOrbitingRef.current ? "grabbing" : "grab";
10850
11027
  return /* @__PURE__ */ jsx63(StnTopologyGraphContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs50(
10851
11028
  "div",
10852
11029
  {
10853
11030
  ref: containerRef,
10854
11031
  className: cn("stn-topology-graph", className),
10855
- style: { height, cursor: viewMode === "2D" ? "default" : isOrbitingRef.current ? "grabbing" : "grab", ...style },
11032
+ style: { height, cursor: containerCursor, ...style },
10856
11033
  "data-stn-topology-graph": true,
10857
11034
  "data-view-mode": viewMode,
10858
11035
  onMouseDown: handleMouseDown,
@@ -10909,7 +11086,7 @@ StnTopologyGraph2.Viewport = StnTopologyGraphViewport;
10909
11086
  StnTopologyGraph2.displayName = "StnTopologyGraph";
10910
11087
 
10911
11088
  // src/components/topology/StnTopologyNodeContextMenu.tsx
10912
- import { forwardRef as forwardRef40, useState as useState18, useRef as useRef12, useEffect as useEffect15 } from "react";
11089
+ import { forwardRef as forwardRef40, useState as useState19, useRef as useRef13, useEffect as useEffect15 } from "react";
10913
11090
  import { Fragment as Fragment9, jsx as jsx64, jsxs as jsxs51 } from "react/jsx-runtime";
10914
11091
  var CIRCLED_TO_PLAIN = {
10915
11092
  "\u2460": "1",
@@ -10931,8 +11108,8 @@ var StepBadge = ({ num }) => {
10931
11108
  };
10932
11109
  var StnTopologyNodeContextMenu = forwardRef40(
10933
11110
  function StnTopologyNodeContextMenu2({ target, onClose, onAction }, ref) {
10934
- const [submenuAnchorEl, setSubmenuAnchorEl] = useState18(null);
10935
- const closeTimerRef = useRef12(null);
11111
+ const [submenuAnchorEl, setSubmenuAnchorEl] = useState19(null);
11112
+ const closeTimerRef = useRef13(null);
10936
11113
  useEffect15(() => {
10937
11114
  if (!target) {
10938
11115
  if (closeTimerRef.current) {
@@ -11133,7 +11310,7 @@ var DEFAULT_TUNNELS = [
11133
11310
  { id: "t3", source: "NODE-6", target: "NODE-3", count: 2, bandwidthText: "100M / 1G", utilization: 10 },
11134
11311
  { id: "t4", source: "NODE-7", target: "NODE-3", count: 3, bandwidthText: "100M / 1G", utilization: 10 }
11135
11312
  ];
11136
- var StnTopologyNodeDetailPopover = forwardRef41(function StnTopologyNodeDetailPopover2({ target, onClose, onConnectDevice, data: propData, className, ...rest }, ref) {
11313
+ var StnTopologyNodeDetailPopover = forwardRef41(function StnTopologyNodeDetailPopover2({ target, onClose, onConnectDevice, data: propData, offset = 12, className, ...rest }, ref) {
11137
11314
  if (!target) return null;
11138
11315
  const { node, mouseX, mouseY, data: targetData } = target;
11139
11316
  const data = propData ?? targetData;
@@ -11158,6 +11335,7 @@ var StnTopologyNodeDetailPopover = forwardRef41(function StnTopologyNodeDetailPo
11158
11335
  anchorPosition: { top: mouseY, left: mouseX },
11159
11336
  anchorOrigin: { vertical: "top", horizontal: "left" },
11160
11337
  transformOrigin: { vertical: "top", horizontal: "left" },
11338
+ offset,
11161
11339
  onClose,
11162
11340
  hideBackdrop: false,
11163
11341
  paperClassName: "stn-topology-popover__paper",
@@ -11276,7 +11454,7 @@ var DEFAULT_LSP_LIST = [
11276
11454
  { id: "2", name: "test1_NODE-5_TO_NODE-8", bandwidth: "100M" },
11277
11455
  { id: "3", name: "test1_NODE-6_TO_NODE-1", bandwidth: "0M" }
11278
11456
  ];
11279
- var StnTopologyLinkDetailPopover = forwardRef42(function StnTopologyLinkDetailPopover2({ target, onClose, onOpenDetail, data: propData, className, ...rest }, ref) {
11457
+ var StnTopologyLinkDetailPopover = forwardRef42(function StnTopologyLinkDetailPopover2({ target, onClose, onOpenDetail, data: propData, offset = 12, className, ...rest }, ref) {
11280
11458
  if (!target) return null;
11281
11459
  const { link, mouseX, mouseY, data: targetData } = target;
11282
11460
  const data = propData ?? targetData;
@@ -11316,6 +11494,7 @@ var StnTopologyLinkDetailPopover = forwardRef42(function StnTopologyLinkDetailPo
11316
11494
  anchorPosition: { top: mouseY, left: mouseX },
11317
11495
  anchorOrigin: { vertical: "top", horizontal: "left" },
11318
11496
  transformOrigin: { vertical: "top", horizontal: "left" },
11497
+ offset,
11319
11498
  onClose,
11320
11499
  hideBackdrop: false,
11321
11500
  paperClassName: "stn-topology-popover__paper",
@@ -11444,7 +11623,7 @@ var StnTopologyLinkDetailPopover = forwardRef42(function StnTopologyLinkDetailPo
11444
11623
  });
11445
11624
 
11446
11625
  // src/components/topology/StnTopologyLinkDetailModal.tsx
11447
- import { useState as useState19, useMemo as useMemo5, forwardRef as forwardRef43 } from "react";
11626
+ import { useState as useState20, useMemo as useMemo5, forwardRef as forwardRef43 } from "react";
11448
11627
  import { jsx as jsx67, jsxs as jsxs54 } from "react/jsx-runtime";
11449
11628
  var DEFAULT_FORWARD_LSPS = [
11450
11629
  {
@@ -11513,13 +11692,13 @@ var INITIAL_COLUMNS = [
11513
11692
  { key: "status", label: "\uC0C1\uD0DC", width: 80 }
11514
11693
  ];
11515
11694
  var StnTopologyLinkDetailModal = forwardRef43(function StnTopologyLinkDetailModal2({ open, link, onClose, onExportExcel, exportExcelIcon = "download", ...rest }, ref) {
11516
- const [tab, setTab] = useState19("lsp");
11517
- const [cols1] = useState19(INITIAL_COLUMNS);
11518
- const [cols2] = useState19(INITIAL_COLUMNS);
11519
- const [sortKey1, setSortKey1] = useState19("lspName");
11520
- const [sortOrder1, setSortOrder1] = useState19("none");
11521
- const [sortKey2, setSortKey2] = useState19("lspName");
11522
- const [sortOrder2, setSortOrder2] = useState19("none");
11695
+ const [tab, setTab] = useState20("lsp");
11696
+ const [cols1] = useState20(INITIAL_COLUMNS);
11697
+ const [cols2] = useState20(INITIAL_COLUMNS);
11698
+ const [sortKey1, setSortKey1] = useState20("lspName");
11699
+ const [sortOrder1, setSortOrder1] = useState20("none");
11700
+ const [sortKey2, setSortKey2] = useState20("lspName");
11701
+ const [sortOrder2, setSortOrder2] = useState20("none");
11523
11702
  const sourceName = link ? String(link.source) : "NODE-3";
11524
11703
  const targetName = link ? String(link.target) : "NODE-4";
11525
11704
  const handleToggleSort1 = (key) => {
@@ -12336,7 +12515,7 @@ function PubChartSample({
12336
12515
  }
12337
12516
 
12338
12517
  // src/patterns/alertCenter/StnAlertCenterPanel.tsx
12339
- import { useState as useState20 } from "react";
12518
+ import { useState as useState21 } from "react";
12340
12519
  import {
12341
12520
  STN_ALERT_CENTER_TABS
12342
12521
  } from "@stn-uiux/pub-ui/data/stnAlertCenter";
@@ -12468,7 +12647,7 @@ function StnAlertCenterPanel({
12468
12647
  closeLabel = "\uB2EB\uAE30",
12469
12648
  className
12470
12649
  }) {
12471
- const [activeTab, setActiveTab] = useState20(defaultTab);
12650
+ const [activeTab, setActiveTab] = useState21(defaultTab);
12472
12651
  const alertCount = alerts.length;
12473
12652
  const resolvedTitleOverflow = titleOverflow ?? textOverflow;
12474
12653
  const resolvedDescOverflow = descOverflow ?? textOverflow;
@@ -12610,12 +12789,12 @@ import {
12610
12789
  } from "@stn-uiux/pub-ui/data/stnAlertCenter";
12611
12790
 
12612
12791
  // src/components/sideNav/SideNavContext.ts
12613
- import { createContext as createContext7, useContext as useContext7 } from "react";
12792
+ import { createContext as createContext7, useContext as useContext8 } from "react";
12614
12793
  var SideNavContext = createContext7({
12615
12794
  collapsed: false,
12616
12795
  mobileOpen: false
12617
12796
  });
12618
- var useSideNav = () => useContext7(SideNavContext);
12797
+ var useSideNav = () => useContext8(SideNavContext);
12619
12798
 
12620
12799
  // src/components/sideNav/StnSideNavItem.tsx
12621
12800
  import { forwardRef as forwardRef44 } from "react";
@@ -12865,8 +13044,8 @@ var StnSideNavMenu = forwardRef47(function StnSideNavMenu2({
12865
13044
  import {
12866
13045
  forwardRef as forwardRef48,
12867
13046
  useEffect as useEffect16,
12868
- useRef as useRef13,
12869
- useState as useState21
13047
+ useRef as useRef14,
13048
+ useState as useState22
12870
13049
  } from "react";
12871
13050
  import { jsx as jsx80, jsxs as jsxs64 } from "react/jsx-runtime";
12872
13051
  var StnSideNavGroup = forwardRef48(function StnSideNavGroup2({
@@ -12885,12 +13064,12 @@ var StnSideNavGroup = forwardRef48(function StnSideNavGroup2({
12885
13064
  ...rest
12886
13065
  }, ref) {
12887
13066
  const { collapsed } = useSideNav();
12888
- const [internalOpen, setInternalOpen] = useState21(defaultOpen);
13067
+ const [internalOpen, setInternalOpen] = useState22(defaultOpen);
12889
13068
  const isOpen = openProp ?? internalOpen;
12890
13069
  const isDropdown = Boolean(dropdown || variant === "dropdown");
12891
13070
  const isTransparent = dropdownVariant === "transparent" || dropdownBackground === false;
12892
13071
  const isUp = direction === "up";
12893
- const groupRef = useRef13(null);
13072
+ const groupRef = useRef14(null);
12894
13073
  useEffect16(() => {
12895
13074
  if (!isDropdown || !isOpen) return;
12896
13075
  const handlePointerDown = (e) => {
@@ -13047,12 +13226,12 @@ var StnSideNavWatermark = forwardRef52(
13047
13226
  );
13048
13227
 
13049
13228
  // src/components/sideNav/StnSideNavMobileTrigger.tsx
13050
- import { forwardRef as forwardRef53, useEffect as useEffect17, useState as useState22 } from "react";
13229
+ import { forwardRef as forwardRef53, useEffect as useEffect17, useState as useState23 } from "react";
13051
13230
  import { jsx as jsx85, jsxs as jsxs65 } from "react/jsx-runtime";
13052
13231
  var StnSideNavMobileTrigger = forwardRef53(
13053
13232
  function StnSideNavMobileTrigger2({ floating = false, className, children, onClick, ...rest }, ref) {
13054
13233
  const { onToggleMobileOpen, mobileOpen: contextMobileOpen } = useSideNav();
13055
- const [fallbackMobileOpen, setFallbackMobileOpen] = useState22(false);
13234
+ const [fallbackMobileOpen, setFallbackMobileOpen] = useState23(false);
13056
13235
  useEffect17(() => {
13057
13236
  if (onToggleMobileOpen) return;
13058
13237
  const handleStateChange = (e) => {
@@ -13202,11 +13381,11 @@ import {
13202
13381
  useCallback as useCallback11,
13203
13382
  useEffect as useEffect18,
13204
13383
  useMemo as useMemo6,
13205
- useState as useState23
13384
+ useState as useState24
13206
13385
  } from "react";
13207
13386
  import { jsx as jsx88, jsxs as jsxs66 } from "react/jsx-runtime";
13208
13387
  function useControllableState(controlledValue, defaultValue, onChange) {
13209
- const [internalState, setInternalState] = useState23(defaultValue);
13388
+ const [internalState, setInternalState] = useState24(defaultValue);
13210
13389
  const isControlled = controlledValue !== void 0;
13211
13390
  const value = isControlled ? controlledValue : internalState;
13212
13391
  const setValue = useCallback11(
@@ -13350,8 +13529,8 @@ import {
13350
13529
  Fragment as Fragment13,
13351
13530
  useEffect as useEffect19,
13352
13531
  useId as useId17,
13353
- useRef as useRef14,
13354
- useState as useState24
13532
+ useRef as useRef15,
13533
+ useState as useState25
13355
13534
  } from "react";
13356
13535
  import {
13357
13536
  STN_BREADCRUMB_ICON_SRC
@@ -13427,7 +13606,7 @@ function BreadcrumbMenuList({
13427
13606
  ) }, option.id)) });
13428
13607
  }
13429
13608
  function useDismissibleMenu(open, onClose) {
13430
- const rootRef = useRef14(null);
13609
+ const rootRef = useRef15(null);
13431
13610
  useEffect19(() => {
13432
13611
  if (!open) return;
13433
13612
  const onPointerDown = (event) => {
@@ -13447,15 +13626,16 @@ function useDismissibleMenu(open, onClose) {
13447
13626
  }
13448
13627
  function BreadcrumbSegment({
13449
13628
  item,
13450
- isCurrent
13629
+ isCurrent,
13630
+ showItemIcons = true
13451
13631
  }) {
13452
13632
  const menuId = useId17();
13453
- const [open, setOpen] = useState24(false);
13633
+ const [open, setOpen] = useState25(false);
13454
13634
  const menuItems = item.menuItems ?? [];
13455
13635
  const showMenu = hasSegmentMenu(item) && !isCurrent;
13456
13636
  const canOpenMenu = showMenu && menuItems.length > 0;
13457
13637
  const rootRef = useDismissibleMenu(open, () => setOpen(false));
13458
- const iconSrc = resolveIconSrc(item);
13638
+ const iconSrc = showItemIcons ? resolveIconSrc(item) : void 0;
13459
13639
  const content = /* @__PURE__ */ jsx89(SegmentContent, { iconSrc, label: item.label, menu: showMenu });
13460
13640
  if (isCurrent) {
13461
13641
  return /* @__PURE__ */ jsx89("span", { className: "stn-breadcrumb__segment", "aria-current": "page", "aria-label": item.label, children: content });
@@ -13544,7 +13724,7 @@ function BreadcrumbCollapsed({
13544
13724
  items,
13545
13725
  menuId
13546
13726
  }) {
13547
- const [open, setOpen] = useState24(false);
13727
+ const [open, setOpen] = useState25(false);
13548
13728
  const rootRef = useDismissibleMenu(open, () => setOpen(false));
13549
13729
  const menuItems = items.map((item) => ({
13550
13730
  id: item.id,
@@ -13584,7 +13764,8 @@ function StnBreadcrumb({
13584
13764
  homeLabel = "\uD648",
13585
13765
  renderItem,
13586
13766
  reverse = false,
13587
- leadSeparator = false
13767
+ leadSeparator = false,
13768
+ showItemIcons = true
13588
13769
  }) {
13589
13770
  const menuId = useId17();
13590
13771
  const currentIndex = items.findIndex((item, index) => item.current ?? index === items.length - 1);
@@ -13603,7 +13784,14 @@ function StnBreadcrumb({
13603
13784
  );
13604
13785
  const renderTrailItem = (item) => {
13605
13786
  const isCurrent = Boolean(item.current) || item.id === targetCurrentId;
13606
- const segment = /* @__PURE__ */ jsx89(BreadcrumbSegment, { item, isCurrent });
13787
+ const segment = /* @__PURE__ */ jsx89(
13788
+ BreadcrumbSegment,
13789
+ {
13790
+ item,
13791
+ isCurrent,
13792
+ showItemIcons
13793
+ }
13794
+ );
13607
13795
  return /* @__PURE__ */ jsx89(
13608
13796
  "li",
13609
13797
  {
@@ -13657,7 +13845,7 @@ function StnBreadcrumb({
13657
13845
  }
13658
13846
 
13659
13847
  // src/components/StnHeaderTitle.tsx
13660
- import { useEffect as useEffect20, useState as useState25 } from "react";
13848
+ import { useEffect as useEffect20, useState as useState26 } from "react";
13661
13849
  import { STN_HEADER_TITLE_FRAME } from "@stn-uiux/pub-ui/data/stnHeaderTitle";
13662
13850
  import { jsx as jsx90, jsxs as jsxs68 } from "react/jsx-runtime";
13663
13851
  var KO_WEEKDAY_SHORT = ["\uC77C", "\uC6D4", "\uD654", "\uC218", "\uBAA9", "\uAE08", "\uD1A0"];
@@ -13734,7 +13922,7 @@ function StnHeaderTitleFramePreview({ className }) {
13734
13922
  return /* @__PURE__ */ jsx90("div", { className: cn("stn-header-title__frame-only", className), children: /* @__PURE__ */ jsx90(HeaderTitleFrame, { preview: true }) });
13735
13923
  }
13736
13924
  function useLiveClock(enabled) {
13737
- const [now, setNow] = useState25(() => /* @__PURE__ */ new Date());
13925
+ const [now, setNow] = useState26(() => /* @__PURE__ */ new Date());
13738
13926
  useEffect20(() => {
13739
13927
  if (!enabled) return;
13740
13928
  const id = window.setInterval(() => setNow(/* @__PURE__ */ new Date()), 1e3);
@@ -14620,7 +14808,7 @@ function DashboardPageScaffold({
14620
14808
  }
14621
14809
 
14622
14810
  // src/patterns/scaffold/AlertCenterPageScaffold.tsx
14623
- import { useMemo as useMemo7, useState as useState26 } from "react";
14811
+ import { useMemo as useMemo7, useState as useState27 } from "react";
14624
14812
  import {
14625
14813
  STN_ALERT_CENTER_DEMO_ALERTS,
14626
14814
  STN_ALERT_CENTER_DEMO_DEPLOY,
@@ -14646,7 +14834,7 @@ function AlertCenterPageScaffold({
14646
14834
  defaultGnbOpen = true,
14647
14835
  ...shellProps
14648
14836
  }) {
14649
- const [gnbOpen, setGnbOpen] = useState26(defaultGnbOpen);
14837
+ const [gnbOpen, setGnbOpen] = useState27(defaultGnbOpen);
14650
14838
  const panel = useMemo7(
14651
14839
  () => gnbAlertPanel ?? /* @__PURE__ */ jsx98(
14652
14840
  StnAlertCenterPanel,
@@ -14714,7 +14902,7 @@ function AlertCenterPageScaffold({
14714
14902
  }
14715
14903
 
14716
14904
  // src/patterns/scaffold/LoginPageScaffold.tsx
14717
- import { useState as useState27 } from "react";
14905
+ import { useState as useState28 } from "react";
14718
14906
  import { jsx as jsx99, jsxs as jsxs73 } from "react/jsx-runtime";
14719
14907
  var LOGIN_TABS = [
14720
14908
  { value: "normal", label: "\uC77C\uBC18 \uB85C\uADF8\uC778" },
@@ -14726,9 +14914,9 @@ function DefaultLoginForm({
14726
14914
  signUpHref = "#",
14727
14915
  recoverHref = "#"
14728
14916
  }) {
14729
- const [mode, setMode] = useState27("normal");
14730
- const [userId, setUserId] = useState27("");
14731
- const [password, setPassword] = useState27("");
14917
+ const [mode, setMode] = useState28("normal");
14918
+ const [userId, setUserId] = useState28("");
14919
+ const [password, setPassword] = useState28("");
14732
14920
  function handleSubmit(e) {
14733
14921
  e.preventDefault();
14734
14922
  onLogin?.(userId, password);
@@ -15131,7 +15319,7 @@ function renderChartState(loading, hasData, emptyText = "\uC870\uD68C\uB41C \uB3
15131
15319
 
15132
15320
  // src/components/StnBarChart.tsx
15133
15321
  import {
15134
- useState as useState28
15322
+ useState as useState29
15135
15323
  } from "react";
15136
15324
  import { Fragment as Fragment17, jsx as jsx102, jsxs as jsxs76 } from "react/jsx-runtime";
15137
15325
  var DEFAULT_BAR_COLORS = STN_CHART_COLORS;
@@ -15164,8 +15352,8 @@ function StnBarChart({
15164
15352
  style,
15165
15353
  ...rest
15166
15354
  }) {
15167
- const [internalHighlight, setInternalHighlight] = useState28(null);
15168
- const [hoveredIndex, setHoveredIndex] = useState28(null);
15355
+ const [internalHighlight, setInternalHighlight] = useState29(null);
15356
+ const [hoveredIndex, setHoveredIndex] = useState29(null);
15169
15357
  const isHorizontal = direction === "horizontal" || orientation === "horizontal";
15170
15358
  if (loading || !data || data.length === 0) {
15171
15359
  const emptyContainerStyle = {
@@ -15470,7 +15658,7 @@ function StnBarChart({
15470
15658
  // src/components/StnPieChart.tsx
15471
15659
  import {
15472
15660
  useId as useId18,
15473
- useState as useState29
15661
+ useState as useState30
15474
15662
  } from "react";
15475
15663
  import { Fragment as Fragment18, jsx as jsx103, jsxs as jsxs77 } from "react/jsx-runtime";
15476
15664
  var DEFAULT_PIE_COLORS = STN_CHART_COLORS;
@@ -15500,7 +15688,7 @@ function StnPieChart({
15500
15688
  ...rest
15501
15689
  }) {
15502
15690
  const filterId = useId18().replace(/:/g, "");
15503
- const [hoveredIndex, setHoveredIndex] = useState29(null);
15691
+ const [hoveredIndex, setHoveredIndex] = useState30(null);
15504
15692
  const hasData = Boolean(data && data.length > 0);
15505
15693
  if (loading || !hasData) {
15506
15694
  return /* @__PURE__ */ jsx103(
@@ -15685,8 +15873,8 @@ var StnGaugeChart = StnPieChart;
15685
15873
  // src/components/StnLineChart.tsx
15686
15874
  import {
15687
15875
  useId as useId19,
15688
- useRef as useRef15,
15689
- useState as useState30
15876
+ useRef as useRef16,
15877
+ useState as useState31
15690
15878
  } from "react";
15691
15879
  import { jsx as jsx104, jsxs as jsxs78 } from "react/jsx-runtime";
15692
15880
  var DEFAULT_LINE_COLORS = STN_CHART_COLORS;
@@ -15727,8 +15915,8 @@ function StnLineChart({
15727
15915
  ...rest
15728
15916
  }) {
15729
15917
  const chartId = useId19().replace(/:/g, "");
15730
- const svgRef = useRef15(null);
15731
- const [hoverIndex, setHoverIndex] = useState30(null);
15918
+ const svgRef = useRef16(null);
15919
+ const [hoverIndex, setHoverIndex] = useState31(null);
15732
15920
  const valueFormatter = format ?? ((v) => formatChartValue(v, decimals));
15733
15921
  const hasDots = Boolean(showDots ?? showPoints ?? dots ?? false);
15734
15922
  const dotRadius = DOT_RADIUS_MAP[size] || 3;
@@ -16046,7 +16234,7 @@ function StnLineChart({
16046
16234
  // src/components/StnComboChart.tsx
16047
16235
  import {
16048
16236
  useId as useId20,
16049
- useState as useState31
16237
+ useState as useState32
16050
16238
  } from "react";
16051
16239
  import { jsx as jsx105, jsxs as jsxs79 } from "react/jsx-runtime";
16052
16240
  var BAR_WIDTH_MAP = {
@@ -16087,8 +16275,8 @@ function StnComboChart({
16087
16275
  ...rest
16088
16276
  }) {
16089
16277
  const chartId = useId20().replace(/:/g, "");
16090
- const [internalHighlight, setInternalHighlight] = useState31(null);
16091
- const [hoverIndex, setHoverIndex] = useState31(null);
16278
+ const [internalHighlight, setInternalHighlight] = useState32(null);
16279
+ const [hoverIndex, setHoverIndex] = useState32(null);
16092
16280
  const hasData = Boolean(data && data.length > 0);
16093
16281
  if (loading || !hasData) {
16094
16282
  return /* @__PURE__ */ jsx105(