@aiready/components 0.13.14 → 0.13.19

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
@@ -7,7 +7,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
7
  import { AnimatePresence, motion } from 'framer-motion';
8
8
  import { X, MessageSquare, Loader2, Send } from 'lucide-react';
9
9
  import { getRatingSlug, getRating as getRating$1 } from '@aiready/core/client';
10
- import * as d34 from 'd3';
10
+ import * as d32 from 'd3';
11
11
 
12
12
  // src/components/button.tsx
13
13
  function cn(...inputs) {
@@ -1954,27 +1954,58 @@ function formatDecimal(value, decimals = 2) {
1954
1954
  }
1955
1955
  function scoreColor(score) {
1956
1956
  if (score == null) return "text-slate-400";
1957
- if (score >= 75) return "text-emerald-400";
1958
- if (score >= 50) return "text-amber-400";
1959
- return "text-red-400";
1957
+ const rating = getRatingSlug(score);
1958
+ switch (rating) {
1959
+ case "excellent":
1960
+ case "good":
1961
+ return "text-emerald-400";
1962
+ case "fair":
1963
+ return "text-amber-400";
1964
+ default:
1965
+ return "text-red-400";
1966
+ }
1960
1967
  }
1961
1968
  function scoreBg(score) {
1962
1969
  if (score == null) return "bg-slate-800/50 border-slate-700";
1963
- if (score >= 75) return "bg-emerald-900/30 border-emerald-500/30";
1964
- if (score >= 50) return "bg-amber-900/30 border-amber-500/30";
1965
- return "bg-red-900/30 border-red-500/30";
1970
+ const rating = getRatingSlug(score);
1971
+ switch (rating) {
1972
+ case "excellent":
1973
+ case "good":
1974
+ return "bg-emerald-900/30 border-emerald-500/30";
1975
+ case "fair":
1976
+ return "bg-amber-900/30 border-amber-500/30";
1977
+ default:
1978
+ return "bg-red-900/30 border-red-500/30";
1979
+ }
1966
1980
  }
1967
1981
  function scoreLabel(score) {
1968
1982
  if (score == null) return "Not analyzed";
1969
- if (score >= 75) return "AI-Ready";
1970
- if (score >= 50) return "Needs Improvement";
1971
- return "Critical Issues";
1983
+ const rating = getRatingSlug(score);
1984
+ switch (rating) {
1985
+ case "excellent":
1986
+ return "Excellent";
1987
+ case "good":
1988
+ return "AI-Ready";
1989
+ case "fair":
1990
+ return "Fair";
1991
+ case "needs-work":
1992
+ return "Needs Improvement";
1993
+ default:
1994
+ return "Critical Issues";
1995
+ }
1972
1996
  }
1973
1997
  function scoreGlow(score) {
1974
1998
  if (score == null) return "";
1975
- if (score >= 75) return "shadow-emerald-500/20";
1976
- if (score >= 50) return "shadow-amber-500/20";
1977
- return "shadow-red-500/20";
1999
+ const rating = getRatingSlug(score);
2000
+ switch (rating) {
2001
+ case "excellent":
2002
+ case "good":
2003
+ return "shadow-emerald-500/20";
2004
+ case "fair":
2005
+ return "shadow-amber-500/20";
2006
+ default:
2007
+ return "shadow-red-500/20";
2008
+ }
1978
2009
  }
1979
2010
  function getScoreRating(score) {
1980
2011
  if (score == null) return "critical";
@@ -1996,7 +2027,7 @@ function useD3(renderFn, dependencies = []) {
1996
2027
  const ref = useRef(null);
1997
2028
  useEffect(() => {
1998
2029
  if (ref.current) {
1999
- const selection = d34.select(ref.current);
2030
+ const selection = d32.select(ref.current);
2000
2031
  renderFn(selection);
2001
2032
  }
2002
2033
  }, dependencies);
@@ -2006,7 +2037,7 @@ function useD3WithResize(renderFn, dependencies = []) {
2006
2037
  const ref = useRef(null);
2007
2038
  useEffect(() => {
2008
2039
  if (!ref.current) return;
2009
- const selection = d34.select(ref.current);
2040
+ const selection = d32.select(ref.current);
2010
2041
  const render = () => renderFn(selection);
2011
2042
  render();
2012
2043
  const resizeObserver = new ResizeObserver(() => {
@@ -2086,11 +2117,11 @@ function useForceSimulation(initialNodes, initialLinks, options) {
2086
2117
  } catch (e) {
2087
2118
  seedRandomPositions(nodesCopy, width, height);
2088
2119
  }
2089
- const simulation = d34.forceSimulation(
2120
+ const simulation = d32.forceSimulation(
2090
2121
  nodesCopy
2091
2122
  );
2092
2123
  try {
2093
- const linkForce = d34.forceLink(
2124
+ const linkForce = d32.forceLink(
2094
2125
  linksCopy
2095
2126
  );
2096
2127
  linkForce.id((d) => d.id).distance(
@@ -2099,31 +2130,31 @@ function useForceSimulation(initialNodes, initialLinks, options) {
2099
2130
  simulation.force("link", linkForce);
2100
2131
  } catch (e) {
2101
2132
  try {
2102
- simulation.force("link", d34.forceLink(linksCopy));
2133
+ simulation.force("link", d32.forceLink(linksCopy));
2103
2134
  } catch (e2) {
2104
2135
  }
2105
2136
  }
2106
2137
  try {
2107
2138
  simulation.force(
2108
2139
  "charge",
2109
- d34.forceManyBody().strength(chargeStrength)
2140
+ d32.forceManyBody().strength(chargeStrength)
2110
2141
  );
2111
2142
  simulation.force(
2112
2143
  "center",
2113
- d34.forceCenter(width / 2, height / 2).strength(centerStrength)
2144
+ d32.forceCenter(width / 2, height / 2).strength(centerStrength)
2114
2145
  );
2115
- const collide = d34.forceCollide().radius((d) => {
2146
+ const collide = d32.forceCollide().radius((d) => {
2116
2147
  const nodeSize = d && d.size ? d.size : 10;
2117
2148
  return nodeSize + collisionRadius;
2118
2149
  }).strength(collisionStrength);
2119
2150
  simulation.force("collision", collide);
2120
2151
  simulation.force(
2121
2152
  "x",
2122
- d34.forceX(width / 2).strength(Math.max(0.02, centerStrength * 0.5))
2153
+ d32.forceX(width / 2).strength(Math.max(0.02, centerStrength * 0.5))
2123
2154
  );
2124
2155
  simulation.force(
2125
2156
  "y",
2126
- d34.forceY(height / 2).strength(Math.max(0.02, centerStrength * 0.5))
2157
+ d32.forceY(height / 2).strength(Math.max(0.02, centerStrength * 0.5))
2127
2158
  );
2128
2159
  simulation.alphaDecay(alphaDecay);
2129
2160
  simulation.velocityDecay(velocityDecay);
@@ -2335,135 +2366,101 @@ function useDrag(simulation) {
2335
2366
  onDragEnd: dragEnded
2336
2367
  };
2337
2368
  }
2338
- var NodeItem = ({
2339
- node,
2340
- isSelected,
2341
- isHovered,
2342
- pinned,
2343
- defaultNodeSize,
2344
- defaultNodeColor,
2345
- showLabel = true,
2346
- onClick,
2347
- onDoubleClick,
2348
- onMouseEnter,
2349
- onMouseLeave,
2350
- onMouseDown
2351
- }) => {
2352
- const nodeSize = node.size || defaultNodeSize;
2353
- const nodeColor = node.color || defaultNodeColor;
2354
- const x = node.x ?? 0;
2355
- const y = node.y ?? 0;
2356
- return /* @__PURE__ */ jsxs(
2357
- "g",
2358
- {
2359
- className: "cursor-pointer node",
2360
- "data-id": node.id,
2361
- transform: `translate(${x},${y})`,
2362
- onClick: () => onClick?.(node),
2363
- onDoubleClick: (e) => onDoubleClick?.(e, node),
2364
- onMouseEnter: () => onMouseEnter?.(node),
2365
- onMouseLeave: () => onMouseLeave?.(),
2366
- onMouseDown: (e) => onMouseDown?.(e, node),
2367
- children: [
2368
- /* @__PURE__ */ jsx(
2369
- "circle",
2370
- {
2371
- r: nodeSize,
2372
- fill: nodeColor,
2373
- stroke: isSelected ? "#000" : isHovered ? "#666" : "none",
2374
- strokeWidth: pinned ? 3 : isSelected ? 2.5 : isHovered ? 2 : 1.5,
2375
- opacity: isHovered || isSelected ? 1 : 0.9
2376
- }
2377
- ),
2378
- pinned && /* @__PURE__ */ jsx(
2379
- "circle",
2380
- {
2381
- r: nodeSize + 4,
2382
- fill: "none",
2383
- stroke: "#ff6b6b",
2384
- strokeWidth: 1,
2385
- opacity: 0.5,
2386
- className: "pointer-events-none"
2387
- }
2388
- ),
2389
- showLabel && node.label && /* @__PURE__ */ jsx(
2390
- "text",
2391
- {
2392
- y: nodeSize + 15,
2393
- fill: "#333",
2394
- fontSize: "12",
2395
- textAnchor: "middle",
2396
- dominantBaseline: "middle",
2397
- pointerEvents: "none",
2398
- className: "select-none",
2399
- children: node.label
2400
- }
2401
- )
2402
- ]
2369
+ function pinNode(node) {
2370
+ node.fx = node.x;
2371
+ node.fy = node.y;
2372
+ }
2373
+ function unpinNode(node) {
2374
+ node.fx = null;
2375
+ node.fy = null;
2376
+ }
2377
+ function unpinAllNodes(nodes) {
2378
+ nodes.forEach(unpinNode);
2379
+ }
2380
+ function useGraphZoom(svgRef, gRef, enableZoom, setTransform, transformRef) {
2381
+ useEffect(() => {
2382
+ if (!enableZoom || !svgRef.current || !gRef.current) return;
2383
+ const svg = d32.select(svgRef.current);
2384
+ const g = d32.select(gRef.current);
2385
+ const zoom3 = d32.zoom().scaleExtent([0.1, 10]).on("zoom", (event) => {
2386
+ g.attr("transform", event.transform);
2387
+ transformRef.current = event.transform;
2388
+ setTransform(event.transform);
2389
+ });
2390
+ svg.call(zoom3);
2391
+ return () => {
2392
+ svg.on(".zoom", null);
2393
+ };
2394
+ }, [enableZoom, svgRef, gRef, setTransform, transformRef]);
2395
+ }
2396
+ function useWindowDrag(enableDrag, svgRef, transformRef, dragActiveRef, dragNodeRef, onDragEnd) {
2397
+ useEffect(() => {
2398
+ if (!enableDrag) return;
2399
+ const handleWindowMove = (event) => {
2400
+ if (!dragActiveRef.current || !dragNodeRef.current) return;
2401
+ const svg = svgRef.current;
2402
+ if (!svg) return;
2403
+ const rect = svg.getBoundingClientRect();
2404
+ const t = transformRef.current;
2405
+ const x = (event.clientX - rect.left - t.x) / t.k;
2406
+ const y = (event.clientY - rect.top - t.y) / t.k;
2407
+ dragNodeRef.current.fx = x;
2408
+ dragNodeRef.current.fy = y;
2409
+ };
2410
+ const handleWindowUp = () => {
2411
+ if (!dragActiveRef.current) return;
2412
+ onDragEnd();
2413
+ dragNodeRef.current = null;
2414
+ dragActiveRef.current = false;
2415
+ };
2416
+ const handleWindowLeave = (event) => {
2417
+ if (event.relatedTarget === null) handleWindowUp();
2418
+ };
2419
+ window.addEventListener("mousemove", handleWindowMove);
2420
+ window.addEventListener("mouseup", handleWindowUp);
2421
+ window.addEventListener("mouseout", handleWindowLeave);
2422
+ window.addEventListener("blur", handleWindowUp);
2423
+ return () => {
2424
+ window.removeEventListener("mousemove", handleWindowMove);
2425
+ window.removeEventListener("mouseup", handleWindowUp);
2426
+ window.removeEventListener("mouseout", handleWindowLeave);
2427
+ window.removeEventListener("blur", handleWindowUp);
2428
+ };
2429
+ }, [enableDrag, svgRef, transformRef, dragActiveRef, dragNodeRef, onDragEnd]);
2430
+ }
2431
+ function useNodeInteractions(enableDrag, _nodes, _pinnedNodes, setPinnedNodes, restart, stop) {
2432
+ const handleDragStart = useCallback(
2433
+ (event, node) => {
2434
+ if (!enableDrag) return;
2435
+ event.preventDefault();
2436
+ event.stopPropagation();
2437
+ pinNode(node);
2438
+ setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
2439
+ stop();
2403
2440
  },
2404
- node.id
2441
+ [enableDrag, stop, setPinnedNodes]
2405
2442
  );
2406
- };
2407
- var NodeItem_default = NodeItem;
2408
- var LinkItem = ({
2409
- link,
2410
- onClick,
2411
- defaultWidth,
2412
- showLabel = true,
2413
- nodes = []
2414
- }) => {
2415
- const src = link.source?.id ?? (typeof link.source === "string" ? link.source : void 0);
2416
- const tgt = link.target?.id ?? (typeof link.target === "string" ? link.target : void 0);
2417
- const getNodePosition = (nodeOrId) => {
2418
- if (typeof nodeOrId === "object" && nodeOrId !== null) {
2419
- const node = nodeOrId;
2420
- return { x: node.x ?? 0, y: node.y ?? 0 };
2421
- } else if (typeof nodeOrId === "string") {
2422
- const found = nodes.find((n) => n.id === nodeOrId);
2423
- if (found) return { x: found.x ?? 0, y: found.y ?? 0 };
2424
- }
2425
- return null;
2426
- };
2427
- const sourcePos = getNodePosition(link.source);
2428
- const targetPos = getNodePosition(link.target);
2429
- if (!sourcePos || !targetPos) {
2430
- return null;
2431
- }
2432
- const midX = (sourcePos.x + targetPos.x) / 2;
2433
- const midY = (sourcePos.y + targetPos.y) / 2;
2434
- return /* @__PURE__ */ jsxs("g", { children: [
2435
- /* @__PURE__ */ jsx(
2436
- "line",
2437
- {
2438
- x1: sourcePos.x,
2439
- y1: sourcePos.y,
2440
- x2: targetPos.x,
2441
- y2: targetPos.y,
2442
- "data-source": src,
2443
- "data-target": tgt,
2444
- stroke: link.color,
2445
- strokeWidth: link.width ?? defaultWidth ?? 1,
2446
- opacity: 0.6,
2447
- className: "cursor-pointer transition-opacity hover:opacity-100",
2448
- onClick: () => onClick?.(link)
2449
- }
2450
- ),
2451
- showLabel && link.label && /* @__PURE__ */ jsx(
2452
- "text",
2453
- {
2454
- x: midX,
2455
- y: midY,
2456
- fill: "#666",
2457
- fontSize: "10",
2458
- textAnchor: "middle",
2459
- dominantBaseline: "middle",
2460
- pointerEvents: "none",
2461
- children: link.label
2443
+ const handleNodeDoubleClick = useCallback(
2444
+ (event, node) => {
2445
+ event.stopPropagation();
2446
+ if (!enableDrag) return;
2447
+ if (node.fx === null || node.fx === void 0) {
2448
+ pinNode(node);
2449
+ setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
2450
+ } else {
2451
+ unpinNode(node);
2452
+ setPinnedNodes((prev) => {
2453
+ const next = new Set(prev);
2454
+ next.delete(node.id);
2455
+ return next;
2456
+ });
2462
2457
  }
2463
- )
2464
- ] });
2465
- };
2466
- var LinkItem_default = LinkItem;
2458
+ restart();
2459
+ },
2460
+ [enableDrag, restart, setPinnedNodes]
2461
+ );
2462
+ return { handleDragStart, handleNodeDoubleClick };
2463
+ }
2467
2464
 
2468
2465
  // src/charts/constants.ts
2469
2466
  var DEFAULT_NODE_COLOR = "#64748b";
@@ -2479,39 +2476,6 @@ var PACKAGE_BOUNDARY_STROKE_WIDTH = 2;
2479
2476
  var PACKAGE_BOUNDARY_DASH = "6 6";
2480
2477
  var PACKAGE_LABEL_FONT_SIZE = 11;
2481
2478
  var PACKAGE_LABEL_COLOR = "#475569";
2482
- var PackageBoundaries = ({
2483
- packageBounds
2484
- }) => {
2485
- if (!packageBounds || Object.keys(packageBounds).length === 0) return null;
2486
- return /* @__PURE__ */ jsx("g", { className: "package-boundaries", pointerEvents: "none", children: Object.entries(packageBounds).map(([pid, b]) => /* @__PURE__ */ jsxs("g", { children: [
2487
- /* @__PURE__ */ jsx(
2488
- "circle",
2489
- {
2490
- cx: b.x,
2491
- cy: b.y,
2492
- r: b.r,
2493
- fill: PACKAGE_BOUNDARY_FILL,
2494
- stroke: PACKAGE_BOUNDARY_STROKE,
2495
- strokeWidth: PACKAGE_BOUNDARY_STROKE_WIDTH,
2496
- strokeDasharray: PACKAGE_BOUNDARY_DASH,
2497
- opacity: 0.9
2498
- }
2499
- ),
2500
- /* @__PURE__ */ jsx(
2501
- "text",
2502
- {
2503
- x: b.x,
2504
- y: Math.max(12, b.y - b.r + 14),
2505
- fill: PACKAGE_LABEL_COLOR,
2506
- fontSize: PACKAGE_LABEL_FONT_SIZE,
2507
- textAnchor: "middle",
2508
- pointerEvents: "none",
2509
- children: pid.replace(/^pkg:/, "")
2510
- }
2511
- )
2512
- ] }, pid)) });
2513
- };
2514
- PackageBoundaries.displayName = "PackageBoundaries";
2515
2479
 
2516
2480
  // src/charts/layout-utils.ts
2517
2481
  function applyCircularLayout(nodes, width, height) {
@@ -2562,382 +2526,148 @@ function applyInitialForceLayout(nodes, width, height) {
2562
2526
  }
2563
2527
  });
2564
2528
  }
2565
- function useGraphZoom(svgRef, gRef, enableZoom, setTransform, transformRef) {
2529
+
2530
+ // src/charts/force-directed/useGraphLayout.ts
2531
+ function useGraphLayout(initialNodes, width, height, layout, restart) {
2532
+ const nodes = useMemo(() => {
2533
+ if (!initialNodes || !initialNodes.length) return initialNodes;
2534
+ const copy = initialNodes.map((n) => ({ ...n }));
2535
+ if (layout === "circular") applyCircularLayout(copy, width, height);
2536
+ else if (layout === "hierarchical")
2537
+ applyHierarchicalLayout(copy, width, height);
2538
+ else applyInitialForceLayout(copy, width, height);
2539
+ return copy;
2540
+ }, [initialNodes, width, height, layout]);
2566
2541
  useEffect(() => {
2567
- if (!enableZoom || !svgRef.current || !gRef.current) return;
2568
- const svg = d34.select(svgRef.current);
2569
- const g = d34.select(gRef.current);
2570
- const zoom3 = d34.zoom().scaleExtent([0.1, 10]).on("zoom", (event) => {
2571
- g.attr("transform", event.transform);
2572
- transformRef.current = event.transform;
2573
- setTransform(event.transform);
2542
+ if (!nodes || nodes.length === 0) return;
2543
+ if (layout === "circular") applyCircularLayout(nodes, width, height);
2544
+ else if (layout === "hierarchical")
2545
+ applyHierarchicalLayout(nodes, width, height);
2546
+ restart();
2547
+ }, [layout, nodes, width, height, restart]);
2548
+ return { nodes };
2549
+ }
2550
+ function useSimulationControls() {
2551
+ const restart = useCallback(() => {
2552
+ }, []);
2553
+ const stop = useCallback(() => {
2554
+ }, []);
2555
+ const setForcesEnabled = useCallback((enabled) => {
2556
+ }, []);
2557
+ return { restart, stop, setForcesEnabled };
2558
+ }
2559
+ function useImperativeHandleMethods({
2560
+ nodes,
2561
+ pinnedNodes,
2562
+ setPinnedNodes,
2563
+ restart,
2564
+ width,
2565
+ height,
2566
+ layout,
2567
+ handleLayoutChange,
2568
+ svgRef,
2569
+ gRef,
2570
+ setTransform,
2571
+ internalDragEnabledRef
2572
+ }) {
2573
+ const pinAll = useCallback(() => {
2574
+ const newPinned = /* @__PURE__ */ new Set();
2575
+ nodes.forEach((node) => {
2576
+ pinNode(node);
2577
+ newPinned.add(node.id);
2574
2578
  });
2575
- svg.call(zoom3);
2576
- return () => {
2577
- svg.on(".zoom", null);
2578
- };
2579
- }, [enableZoom, svgRef, gRef, setTransform, transformRef]);
2580
- }
2581
- function useWindowDrag(enableDrag, svgRef, transformRef, dragActiveRef, dragNodeRef, onDragEnd) {
2582
- useEffect(() => {
2583
- if (!enableDrag) return;
2584
- const handleWindowMove = (event) => {
2585
- if (!dragActiveRef.current || !dragNodeRef.current) return;
2586
- const svg = svgRef.current;
2587
- if (!svg) return;
2588
- const rect = svg.getBoundingClientRect();
2589
- const t = transformRef.current;
2590
- const x = (event.clientX - rect.left - t.x) / t.k;
2591
- const y = (event.clientY - rect.top - t.y) / t.k;
2592
- dragNodeRef.current.fx = x;
2593
- dragNodeRef.current.fy = y;
2594
- };
2595
- const handleWindowUp = () => {
2596
- if (!dragActiveRef.current) return;
2597
- onDragEnd();
2598
- dragNodeRef.current = null;
2599
- dragActiveRef.current = false;
2600
- };
2601
- const handleWindowLeave = (event) => {
2602
- if (event.relatedTarget === null) handleWindowUp();
2603
- };
2604
- window.addEventListener("mousemove", handleWindowMove);
2605
- window.addEventListener("mouseup", handleWindowUp);
2606
- window.addEventListener("mouseout", handleWindowLeave);
2607
- window.addEventListener("blur", handleWindowUp);
2608
- return () => {
2609
- window.removeEventListener("mousemove", handleWindowMove);
2610
- window.removeEventListener("mouseup", handleWindowUp);
2611
- window.removeEventListener("mouseout", handleWindowLeave);
2612
- window.removeEventListener("blur", handleWindowUp);
2613
- };
2614
- }, [enableDrag, svgRef, transformRef, dragActiveRef, dragNodeRef, onDragEnd]);
2615
- }
2616
- function pinNode(node) {
2617
- node.fx = node.x;
2618
- node.fy = node.y;
2619
- }
2620
- function unpinNode(node) {
2621
- node.fx = null;
2622
- node.fy = null;
2623
- }
2624
- function unpinAllNodes(nodes) {
2625
- nodes.forEach(unpinNode);
2626
- }
2627
- var ForceDirectedGraph = forwardRef(
2628
- ({
2629
- nodes: initialNodes,
2630
- links: initialLinks,
2631
- width,
2632
- height,
2633
- enableZoom = true,
2634
- enableDrag = true,
2635
- onNodeClick,
2636
- onNodeHover,
2637
- onLinkClick,
2638
- selectedNodeId,
2639
- hoveredNodeId,
2640
- defaultNodeColor = DEFAULT_NODE_COLOR,
2641
- defaultNodeSize = DEFAULT_NODE_SIZE,
2642
- defaultLinkColor = DEFAULT_LINK_COLOR,
2643
- defaultLinkWidth = DEFAULT_LINK_WIDTH,
2644
- showNodeLabels = true,
2645
- showLinkLabels = false,
2646
- className,
2647
- manualLayout = false,
2648
- onManualLayoutChange,
2649
- packageBounds,
2650
- layout: externalLayout,
2651
- onLayoutChange
2652
- }, ref) => {
2653
- const svgRef = useRef(null);
2654
- const gRef = useRef(null);
2655
- const [transform, setTransform] = useState({ k: 1, x: 0, y: 0 });
2656
- const transformRef = useRef(transform);
2657
- const dragNodeRef = useRef(null);
2658
- const dragActiveRef = useRef(false);
2659
- const [pinnedNodes, setPinnedNodes] = useState(/* @__PURE__ */ new Set());
2660
- const internalDragEnabledRef = useRef(enableDrag);
2661
- const [layout, setLayout] = useState(externalLayout || "force");
2662
- useEffect(() => {
2663
- if (externalLayout && externalLayout !== layout) {
2664
- setLayout(externalLayout);
2665
- }
2666
- }, [externalLayout, layout]);
2667
- const handleLayoutChange = useCallback(
2668
- (newLayout) => {
2669
- setLayout(newLayout);
2670
- onLayoutChange?.(newLayout);
2671
- },
2672
- [onLayoutChange]
2673
- );
2674
- useEffect(() => {
2675
- internalDragEnabledRef.current = enableDrag;
2676
- }, [enableDrag]);
2677
- const nodes = React2__default.useMemo(() => {
2678
- if (!initialNodes || !initialNodes.length) return initialNodes;
2679
- const copy = initialNodes.map((n) => ({ ...n }));
2680
- if (layout === "circular") applyCircularLayout(copy, width, height);
2681
- else if (layout === "hierarchical")
2682
- applyHierarchicalLayout(copy, width, height);
2683
- else applyInitialForceLayout(copy, width, height);
2684
- return copy;
2685
- }, [initialNodes, width, height, layout]);
2686
- const restart = React2__default.useCallback(() => {
2687
- }, []);
2688
- const stop = React2__default.useCallback(() => {
2689
- }, []);
2690
- const setForcesEnabled = React2__default.useCallback((enabled) => {
2691
- }, []);
2692
- useEffect(() => {
2693
- if (!nodes || nodes.length === 0) return;
2694
- if (layout === "circular") applyCircularLayout(nodes, width, height);
2695
- else if (layout === "hierarchical")
2696
- applyHierarchicalLayout(nodes, width, height);
2697
- restart();
2698
- }, [layout, nodes, width, height, restart]);
2699
- useEffect(() => {
2700
- if (manualLayout || pinnedNodes.size > 0) setForcesEnabled(false);
2701
- else setForcesEnabled(true);
2702
- }, [manualLayout, pinnedNodes, setForcesEnabled]);
2703
- useImperativeHandle(
2704
- ref,
2705
- () => ({
2706
- pinAll: () => {
2707
- const newPinned = /* @__PURE__ */ new Set();
2708
- nodes.forEach((node) => {
2709
- pinNode(node);
2710
- newPinned.add(node.id);
2711
- });
2712
- setPinnedNodes(newPinned);
2713
- restart();
2714
- },
2715
- unpinAll: () => {
2716
- unpinAllNodes(nodes);
2717
- setPinnedNodes(/* @__PURE__ */ new Set());
2718
- restart();
2719
- },
2720
- resetLayout: () => {
2721
- unpinAllNodes(nodes);
2722
- setPinnedNodes(/* @__PURE__ */ new Set());
2723
- restart();
2724
- },
2725
- fitView: () => {
2726
- if (!svgRef.current || !nodes.length) return;
2727
- let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
2728
- nodes.forEach((node) => {
2729
- if (node.x !== void 0 && node.y !== void 0) {
2730
- const size = node.size || DEFAULT_NODE_SIZE;
2731
- minX = Math.min(minX, node.x - size);
2732
- maxX = Math.max(maxX, node.x + size);
2733
- minY = Math.min(minY, node.y - size);
2734
- maxY = Math.max(maxY, node.y + size);
2735
- }
2736
- });
2737
- if (!isFinite(minX)) return;
2738
- const scale = Math.min(
2739
- (width - FIT_VIEW_PADDING * 2) / (maxX - minX),
2740
- (height - FIT_VIEW_PADDING * 2) / (maxY - minY),
2741
- 10
2742
- );
2743
- const x = width / 2 - (minX + maxX) / 2 * scale;
2744
- const y = height / 2 - (minY + maxY) / 2 * scale;
2745
- if (gRef.current && svgRef.current) {
2746
- const svg = d34.select(svgRef.current);
2747
- const newTransform = d34.zoomIdentity.translate(x, y).scale(scale);
2748
- svg.transition().duration(TRANSITION_DURATION_MS).call(d34.zoom().transform, newTransform);
2749
- setTransform(newTransform);
2750
- }
2751
- },
2752
- getPinnedNodes: () => Array.from(pinnedNodes),
2753
- setDragMode: (enabled) => {
2754
- internalDragEnabledRef.current = enabled;
2755
- },
2756
- setLayout: (newLayout) => handleLayoutChange(newLayout),
2757
- getLayout: () => layout
2758
- }),
2759
- [
2760
- nodes,
2761
- pinnedNodes,
2762
- restart,
2763
- width,
2764
- height,
2765
- layout,
2766
- handleLayoutChange,
2767
- setForcesEnabled
2768
- ]
2769
- );
2770
- useEffect(() => {
2771
- if (typeof onManualLayoutChange === "function")
2772
- onManualLayoutChange(manualLayout);
2773
- }, [manualLayout, onManualLayoutChange]);
2774
- useGraphZoom(svgRef, gRef, enableZoom, setTransform, transformRef);
2775
- useWindowDrag(
2776
- enableDrag,
2777
- svgRef,
2778
- transformRef,
2779
- dragActiveRef,
2780
- dragNodeRef,
2781
- () => {
2782
- setForcesEnabled(true);
2783
- restart();
2784
- }
2785
- );
2786
- useEffect(() => {
2787
- if (!gRef.current) return;
2788
- const g = d34.select(gRef.current);
2789
- g.selectAll("g.node").each(function() {
2790
- const datum = d34.select(this).datum();
2791
- if (!datum) return;
2792
- d34.select(this).attr(
2793
- "transform",
2794
- `translate(${datum.x || 0},${datum.y || 0})`
2795
- );
2796
- });
2797
- g.selectAll("line").each(function() {
2798
- const l = d34.select(this).datum();
2799
- if (!l) return;
2800
- const s = typeof l.source === "object" ? l.source : nodes.find((n) => n.id === l.source) || l.source;
2801
- const t = typeof l.target === "object" ? l.target : nodes.find((n) => n.id === l.target) || l.target;
2802
- if (!s || !t) return;
2803
- d34.select(this).attr("x1", s.x).attr("y1", s.y).attr("x2", t.x).attr("y2", t.y);
2804
- });
2805
- }, [nodes, initialLinks]);
2806
- const handleDragStart = useCallback(
2807
- (event, node) => {
2808
- if (!enableDrag) return;
2809
- event.preventDefault();
2810
- event.stopPropagation();
2811
- dragActiveRef.current = true;
2812
- dragNodeRef.current = node;
2813
- pinNode(node);
2814
- setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
2815
- stop();
2816
- },
2817
- [enableDrag, stop]
2818
- );
2819
- useEffect(() => {
2820
- if (!gRef.current || !enableDrag) return;
2821
- const g = d34.select(gRef.current);
2822
- const dragBehavior = d34.drag().on("start", (event) => {
2823
- const target = event.sourceEvent && event.sourceEvent.target || event.target;
2824
- const grp = target.closest?.("g.node");
2825
- const id = grp?.getAttribute("data-id");
2826
- if (!id || !internalDragEnabledRef.current) return;
2827
- const node = nodes.find((n) => n.id === id);
2828
- if (!node) return;
2829
- if (!event.active) restart();
2830
- dragActiveRef.current = true;
2831
- dragNodeRef.current = node;
2832
- pinNode(node);
2833
- setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
2834
- }).on("drag", (event) => {
2835
- if (!dragActiveRef.current || !dragNodeRef.current) return;
2836
- const svg = svgRef.current;
2837
- if (!svg) return;
2838
- const rect = svg.getBoundingClientRect();
2839
- dragNodeRef.current.fx = (event.sourceEvent.clientX - rect.left - transform.x) / transform.k;
2840
- dragNodeRef.current.fy = (event.sourceEvent.clientY - rect.top - transform.y) / transform.k;
2841
- }).on("end", () => {
2842
- setForcesEnabled(true);
2843
- restart();
2844
- });
2845
- g.selectAll("g.node").call(dragBehavior);
2846
- return () => {
2847
- g.selectAll("g.node").on(".drag", null);
2848
- };
2849
- }, [
2850
- gRef,
2851
- enableDrag,
2852
- nodes,
2853
- transform,
2854
- restart,
2855
- setForcesEnabled,
2856
- internalDragEnabledRef
2857
- ]);
2858
- const handleNodeDoubleClick = useCallback(
2859
- (event, node) => {
2860
- event.stopPropagation();
2861
- if (!enableDrag) return;
2862
- if (node.fx === null || node.fx === void 0) {
2863
- pinNode(node);
2864
- setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
2865
- } else {
2866
- unpinNode(node);
2867
- setPinnedNodes((prev) => {
2868
- const next = new Set(prev);
2869
- next.delete(node.id);
2870
- return next;
2871
- });
2872
- }
2873
- restart();
2874
- },
2875
- [enableDrag, restart]
2876
- );
2877
- return /* @__PURE__ */ jsxs(
2878
- "svg",
2879
- {
2880
- ref: svgRef,
2881
- width,
2882
- height,
2883
- className: cn("bg-white dark:bg-gray-900", className),
2884
- onDoubleClick: () => {
2885
- unpinAllNodes(nodes);
2886
- setPinnedNodes(/* @__PURE__ */ new Set());
2887
- restart();
2888
- },
2889
- children: [
2890
- /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx(
2891
- "marker",
2892
- {
2893
- id: "arrow",
2894
- viewBox: "0 0 10 10",
2895
- refX: "20",
2896
- refY: "5",
2897
- markerWidth: "6",
2898
- markerHeight: "6",
2899
- orient: "auto",
2900
- children: /* @__PURE__ */ jsx("path", { d: "M 0 0 L 10 5 L 0 10 z", fill: defaultLinkColor })
2901
- }
2902
- ) }),
2903
- /* @__PURE__ */ jsxs("g", { ref: gRef, children: [
2904
- initialLinks.map((link, i) => /* @__PURE__ */ jsx(
2905
- LinkItem_default,
2906
- {
2907
- link,
2908
- onClick: onLinkClick,
2909
- defaultWidth: defaultLinkWidth,
2910
- showLabel: showLinkLabels,
2911
- nodes
2912
- },
2913
- `link-${i}`
2914
- )),
2915
- nodes.map((node) => /* @__PURE__ */ jsx(
2916
- NodeItem_default,
2917
- {
2918
- node,
2919
- isSelected: selectedNodeId === node.id,
2920
- isHovered: hoveredNodeId === node.id,
2921
- pinned: pinnedNodes.has(node.id),
2922
- defaultNodeSize,
2923
- defaultNodeColor,
2924
- showLabel: showNodeLabels,
2925
- onClick: onNodeClick,
2926
- onDoubleClick: handleNodeDoubleClick,
2927
- onMouseEnter: (n) => onNodeHover?.(n),
2928
- onMouseLeave: () => onNodeHover?.(null),
2929
- onMouseDown: handleDragStart
2930
- },
2931
- node.id
2932
- )),
2933
- /* @__PURE__ */ jsx(PackageBoundaries, { packageBounds: packageBounds || {} })
2934
- ] })
2935
- ]
2579
+ setPinnedNodes(newPinned);
2580
+ restart();
2581
+ }, [nodes, setPinnedNodes, restart]);
2582
+ const unpinAll = useCallback(() => {
2583
+ unpinAllNodes(nodes);
2584
+ setPinnedNodes(/* @__PURE__ */ new Set());
2585
+ restart();
2586
+ }, [nodes, setPinnedNodes, restart]);
2587
+ const resetLayout = useCallback(() => {
2588
+ unpinAllNodes(nodes);
2589
+ setPinnedNodes(/* @__PURE__ */ new Set());
2590
+ restart();
2591
+ }, [nodes, setPinnedNodes, restart]);
2592
+ const fitView = useCallback(() => {
2593
+ if (!svgRef.current || !nodes.length) return;
2594
+ let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
2595
+ nodes.forEach((node) => {
2596
+ if (node.x !== void 0 && node.y !== void 0) {
2597
+ const size = node.size || DEFAULT_NODE_SIZE;
2598
+ minX = Math.min(minX, node.x - size);
2599
+ maxX = Math.max(maxX, node.x + size);
2600
+ minY = Math.min(minY, node.y - size);
2601
+ maxY = Math.max(maxY, node.y + size);
2936
2602
  }
2603
+ });
2604
+ if (!isFinite(minX)) return;
2605
+ const scale = Math.min(
2606
+ (width - FIT_VIEW_PADDING * 2) / (maxX - minX),
2607
+ (height - FIT_VIEW_PADDING * 2) / (maxY - minY),
2608
+ 10
2937
2609
  );
2938
- }
2939
- );
2940
- ForceDirectedGraph.displayName = "ForceDirectedGraph";
2610
+ const x = width / 2 - (minX + maxX) / 2 * scale;
2611
+ const y = height / 2 - (minY + maxY) / 2 * scale;
2612
+ if (gRef.current && svgRef.current) {
2613
+ const svg = d32.select(svgRef.current);
2614
+ const newTransform = d32.zoomIdentity.translate(x, y).scale(scale);
2615
+ svg.transition().duration(TRANSITION_DURATION_MS).call(d32.zoom().transform, newTransform);
2616
+ setTransform(newTransform);
2617
+ }
2618
+ }, [nodes, width, height, svgRef, gRef, setTransform]);
2619
+ const getPinnedNodes = useCallback(
2620
+ () => Array.from(pinnedNodes),
2621
+ [pinnedNodes]
2622
+ );
2623
+ const setDragMode = useCallback(
2624
+ (enabled) => {
2625
+ internalDragEnabledRef.current = enabled;
2626
+ },
2627
+ [internalDragEnabledRef]
2628
+ );
2629
+ const setLayoutMethod = useCallback(
2630
+ (newLayout) => {
2631
+ handleLayoutChange(newLayout);
2632
+ },
2633
+ [handleLayoutChange]
2634
+ );
2635
+ const getLayout = useCallback(() => layout, [layout]);
2636
+ return {
2637
+ pinAll,
2638
+ unpinAll,
2639
+ resetLayout,
2640
+ fitView,
2641
+ getPinnedNodes,
2642
+ setDragMode,
2643
+ setLayout: setLayoutMethod,
2644
+ getLayout
2645
+ };
2646
+ }
2647
+ var ControlButton = ({
2648
+ onClick,
2649
+ active = false,
2650
+ icon,
2651
+ label,
2652
+ disabled = false
2653
+ }) => /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
2654
+ /* @__PURE__ */ jsx(
2655
+ "button",
2656
+ {
2657
+ onClick,
2658
+ disabled,
2659
+ className: cn(
2660
+ "p-2 rounded-lg transition-all duration-200",
2661
+ active ? "bg-blue-500 text-white shadow-md hover:bg-blue-600" : "bg-gray-100 text-gray-700 hover:bg-gray-200",
2662
+ disabled && "opacity-50 cursor-not-allowed hover:bg-gray-100",
2663
+ "dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 dark:active:bg-blue-600"
2664
+ ),
2665
+ title: label,
2666
+ children: /* @__PURE__ */ jsx("span", { className: "text-lg", children: icon })
2667
+ }
2668
+ ),
2669
+ /* @__PURE__ */ jsx("div", { className: "absolute left-full ml-2 px-2 py-1 bg-gray-900 text-white text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-50", children: label })
2670
+ ] });
2941
2671
  var GraphControls = ({
2942
2672
  dragEnabled = true,
2943
2673
  onDragToggle,
@@ -2960,24 +2690,6 @@ var GraphControls = ({
2960
2690
  "bottom-left": "bottom-4 left-4",
2961
2691
  "bottom-right": "bottom-4 right-4"
2962
2692
  };
2963
- const ControlButton = ({ onClick, active = false, icon, label, disabled = false }) => /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
2964
- /* @__PURE__ */ jsx(
2965
- "button",
2966
- {
2967
- onClick,
2968
- disabled,
2969
- className: cn(
2970
- "p-2 rounded-lg transition-all duration-200",
2971
- active ? "bg-blue-500 text-white shadow-md hover:bg-blue-600" : "bg-gray-100 text-gray-700 hover:bg-gray-200",
2972
- disabled && "opacity-50 cursor-not-allowed hover:bg-gray-100",
2973
- "dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 dark:active:bg-blue-600"
2974
- ),
2975
- title: label,
2976
- children: /* @__PURE__ */ jsx("span", { className: "text-lg", children: icon })
2977
- }
2978
- ),
2979
- /* @__PURE__ */ jsx("div", { className: "absolute left-full ml-2 px-2 py-1 bg-gray-900 text-white text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-50", children: label })
2980
- ] });
2981
2693
  return /* @__PURE__ */ jsxs(
2982
2694
  "div",
2983
2695
  {
@@ -3003,7 +2715,7 @@ var GraphControls = ({
3003
2715
  onClick: () => onManualLayoutToggle?.(!manualLayout),
3004
2716
  active: manualLayout,
3005
2717
  icon: "\u{1F527}",
3006
- label: manualLayout ? "Manual layout: ON (drag freely)" : "Manual layout: OFF (forces active)"
2718
+ label: manualLayout ? "Manual layout: ON" : "Manual layout: OFF"
3007
2719
  }
3008
2720
  ),
3009
2721
  /* @__PURE__ */ jsx("div", { className: "w-8 h-px bg-gray-300 dark:bg-gray-600 mx-auto my-1" }),
@@ -3057,15 +2769,6 @@ var GraphControls = ({
3057
2769
  /* @__PURE__ */ jsx("strong", { children: "Pinned:" }),
3058
2770
  " ",
3059
2771
  pinnedCount
3060
- ] }),
3061
- /* @__PURE__ */ jsxs("div", { className: "mt-2 text-gray-500 dark:text-gray-500 leading-snug", children: [
3062
- /* @__PURE__ */ jsx("strong", { children: "Tips:" }),
3063
- /* @__PURE__ */ jsxs("ul", { className: "mt-1 ml-1 space-y-0.5", children: [
3064
- /* @__PURE__ */ jsx("li", { children: "\u2022 Drag nodes to reposition" }),
3065
- /* @__PURE__ */ jsx("li", { children: "\u2022 Double-click to pin/unpin" }),
3066
- /* @__PURE__ */ jsx("li", { children: "\u2022 Double-click canvas to unpin all" }),
3067
- /* @__PURE__ */ jsx("li", { children: "\u2022 Scroll to zoom" })
3068
- ] })
3069
2772
  ] })
3070
2773
  ] })
3071
2774
  ]
@@ -3073,6 +2776,445 @@ var GraphControls = ({
3073
2776
  );
3074
2777
  };
3075
2778
  GraphControls.displayName = "GraphControls";
2779
+ var NodeItem = ({
2780
+ node,
2781
+ isSelected,
2782
+ isHovered,
2783
+ pinned,
2784
+ defaultNodeSize,
2785
+ defaultNodeColor,
2786
+ showLabel = true,
2787
+ onClick,
2788
+ onDoubleClick,
2789
+ onMouseEnter,
2790
+ onMouseLeave,
2791
+ onMouseDown
2792
+ }) => {
2793
+ const nodeSize = node.size || defaultNodeSize;
2794
+ const nodeColor = node.color || defaultNodeColor;
2795
+ const x = node.x ?? 0;
2796
+ const y = node.y ?? 0;
2797
+ return /* @__PURE__ */ jsxs(
2798
+ "g",
2799
+ {
2800
+ className: "cursor-pointer node",
2801
+ "data-id": node.id,
2802
+ transform: `translate(${x},${y})`,
2803
+ onClick: () => onClick?.(node),
2804
+ onDoubleClick: (e) => onDoubleClick?.(e, node),
2805
+ onMouseEnter: () => onMouseEnter?.(node),
2806
+ onMouseLeave: () => onMouseLeave?.(),
2807
+ onMouseDown: (e) => onMouseDown?.(e, node),
2808
+ children: [
2809
+ /* @__PURE__ */ jsx(
2810
+ "circle",
2811
+ {
2812
+ r: nodeSize,
2813
+ fill: nodeColor,
2814
+ stroke: isSelected ? "#000" : isHovered ? "#666" : "none",
2815
+ strokeWidth: pinned ? 3 : isSelected ? 2.5 : isHovered ? 2 : 1.5,
2816
+ opacity: isHovered || isSelected ? 1 : 0.9
2817
+ }
2818
+ ),
2819
+ pinned && /* @__PURE__ */ jsx(
2820
+ "circle",
2821
+ {
2822
+ r: nodeSize + 4,
2823
+ fill: "none",
2824
+ stroke: "#ff6b6b",
2825
+ strokeWidth: 1,
2826
+ opacity: 0.5,
2827
+ className: "pointer-events-none"
2828
+ }
2829
+ ),
2830
+ showLabel && node.label && /* @__PURE__ */ jsx(
2831
+ "text",
2832
+ {
2833
+ y: nodeSize + 15,
2834
+ fill: "#333",
2835
+ fontSize: "12",
2836
+ textAnchor: "middle",
2837
+ dominantBaseline: "middle",
2838
+ pointerEvents: "none",
2839
+ className: "select-none",
2840
+ children: node.label
2841
+ }
2842
+ )
2843
+ ]
2844
+ },
2845
+ node.id
2846
+ );
2847
+ };
2848
+ var NodeItem_default = NodeItem;
2849
+ var LinkItem = ({
2850
+ link,
2851
+ onClick,
2852
+ defaultWidth,
2853
+ showLabel = true,
2854
+ nodes = []
2855
+ }) => {
2856
+ const src = link.source?.id ?? (typeof link.source === "string" ? link.source : void 0);
2857
+ const tgt = link.target?.id ?? (typeof link.target === "string" ? link.target : void 0);
2858
+ const getNodePosition = (nodeOrId) => {
2859
+ if (typeof nodeOrId === "object" && nodeOrId !== null) {
2860
+ const node = nodeOrId;
2861
+ return { x: node.x ?? 0, y: node.y ?? 0 };
2862
+ } else if (typeof nodeOrId === "string") {
2863
+ const found = nodes.find((n) => n.id === nodeOrId);
2864
+ if (found) return { x: found.x ?? 0, y: found.y ?? 0 };
2865
+ }
2866
+ return null;
2867
+ };
2868
+ const sourcePos = getNodePosition(link.source);
2869
+ const targetPos = getNodePosition(link.target);
2870
+ if (!sourcePos || !targetPos) {
2871
+ return null;
2872
+ }
2873
+ const midX = (sourcePos.x + targetPos.x) / 2;
2874
+ const midY = (sourcePos.y + targetPos.y) / 2;
2875
+ return /* @__PURE__ */ jsxs("g", { children: [
2876
+ /* @__PURE__ */ jsx(
2877
+ "line",
2878
+ {
2879
+ x1: sourcePos.x,
2880
+ y1: sourcePos.y,
2881
+ x2: targetPos.x,
2882
+ y2: targetPos.y,
2883
+ "data-source": src,
2884
+ "data-target": tgt,
2885
+ stroke: link.color,
2886
+ strokeWidth: link.width ?? defaultWidth ?? 1,
2887
+ opacity: 0.6,
2888
+ className: "cursor-pointer transition-opacity hover:opacity-100",
2889
+ onClick: () => onClick?.(link)
2890
+ }
2891
+ ),
2892
+ showLabel && link.label && /* @__PURE__ */ jsx(
2893
+ "text",
2894
+ {
2895
+ x: midX,
2896
+ y: midY,
2897
+ fill: "#666",
2898
+ fontSize: "10",
2899
+ textAnchor: "middle",
2900
+ dominantBaseline: "middle",
2901
+ pointerEvents: "none",
2902
+ children: link.label
2903
+ }
2904
+ )
2905
+ ] });
2906
+ };
2907
+ var LinkItem_default = LinkItem;
2908
+ var PackageBoundaries = ({
2909
+ packageBounds
2910
+ }) => {
2911
+ if (!packageBounds || Object.keys(packageBounds).length === 0) return null;
2912
+ return /* @__PURE__ */ jsx("g", { className: "package-boundaries", pointerEvents: "none", children: Object.entries(packageBounds).map(([pid, b]) => /* @__PURE__ */ jsxs("g", { children: [
2913
+ /* @__PURE__ */ jsx(
2914
+ "circle",
2915
+ {
2916
+ cx: b.x,
2917
+ cy: b.y,
2918
+ r: b.r,
2919
+ fill: PACKAGE_BOUNDARY_FILL,
2920
+ stroke: PACKAGE_BOUNDARY_STROKE,
2921
+ strokeWidth: PACKAGE_BOUNDARY_STROKE_WIDTH,
2922
+ strokeDasharray: PACKAGE_BOUNDARY_DASH,
2923
+ opacity: 0.9
2924
+ }
2925
+ ),
2926
+ /* @__PURE__ */ jsx(
2927
+ "text",
2928
+ {
2929
+ x: b.x,
2930
+ y: Math.max(12, b.y - b.r + 14),
2931
+ fill: PACKAGE_LABEL_COLOR,
2932
+ fontSize: PACKAGE_LABEL_FONT_SIZE,
2933
+ textAnchor: "middle",
2934
+ pointerEvents: "none",
2935
+ children: pid.replace(/^pkg:/, "")
2936
+ }
2937
+ )
2938
+ ] }, pid)) });
2939
+ };
2940
+ PackageBoundaries.displayName = "PackageBoundaries";
2941
+ var GraphCanvas = ({
2942
+ svgRef,
2943
+ gRef,
2944
+ width,
2945
+ height,
2946
+ className,
2947
+ nodes,
2948
+ links,
2949
+ pinnedNodes,
2950
+ selectedNodeId,
2951
+ hoveredNodeId,
2952
+ defaultNodeColor = DEFAULT_NODE_COLOR,
2953
+ defaultNodeSize = DEFAULT_NODE_SIZE,
2954
+ defaultLinkColor = DEFAULT_LINK_COLOR,
2955
+ defaultLinkWidth = DEFAULT_LINK_WIDTH,
2956
+ showNodeLabels = true,
2957
+ showLinkLabels = false,
2958
+ onNodeClick,
2959
+ onNodeHover,
2960
+ onLinkClick,
2961
+ packageBounds,
2962
+ handleNodeDoubleClick,
2963
+ handleDragStart,
2964
+ restart,
2965
+ setPinnedNodes
2966
+ }) => {
2967
+ return /* @__PURE__ */ jsxs(
2968
+ "svg",
2969
+ {
2970
+ ref: svgRef,
2971
+ width,
2972
+ height,
2973
+ className: cn("bg-white dark:bg-gray-900", className),
2974
+ onDoubleClick: () => {
2975
+ unpinAllNodes(nodes);
2976
+ setPinnedNodes(/* @__PURE__ */ new Set());
2977
+ restart();
2978
+ },
2979
+ children: [
2980
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx(
2981
+ "marker",
2982
+ {
2983
+ id: "arrow",
2984
+ viewBox: "0 0 10 10",
2985
+ refX: "20",
2986
+ refY: "5",
2987
+ markerWidth: "6",
2988
+ markerHeight: "6",
2989
+ orient: "auto",
2990
+ children: /* @__PURE__ */ jsx("path", { d: "M 0 0 L 10 5 L 0 10 z", fill: defaultLinkColor })
2991
+ }
2992
+ ) }),
2993
+ /* @__PURE__ */ jsxs("g", { ref: gRef, children: [
2994
+ links.map((link, i) => /* @__PURE__ */ jsx(
2995
+ LinkItem_default,
2996
+ {
2997
+ link,
2998
+ onClick: onLinkClick,
2999
+ defaultWidth: defaultLinkWidth,
3000
+ showLabel: showLinkLabels,
3001
+ nodes
3002
+ },
3003
+ `link-${i}`
3004
+ )),
3005
+ nodes.map((node) => /* @__PURE__ */ jsx(
3006
+ NodeItem_default,
3007
+ {
3008
+ node,
3009
+ isSelected: selectedNodeId === node.id,
3010
+ isHovered: hoveredNodeId === node.id,
3011
+ pinned: pinnedNodes.has(node.id),
3012
+ defaultNodeSize,
3013
+ defaultNodeColor,
3014
+ showLabel: showNodeLabels,
3015
+ onClick: onNodeClick,
3016
+ onDoubleClick: handleNodeDoubleClick,
3017
+ onMouseEnter: (n) => onNodeHover?.(n),
3018
+ onMouseLeave: () => onNodeHover?.(null),
3019
+ onMouseDown: handleDragStart
3020
+ },
3021
+ node.id
3022
+ )),
3023
+ /* @__PURE__ */ jsx(PackageBoundaries, { packageBounds: packageBounds || {} })
3024
+ ] })
3025
+ ]
3026
+ }
3027
+ );
3028
+ };
3029
+ var ForceDirectedGraph = forwardRef(
3030
+ ({
3031
+ nodes: initialNodes,
3032
+ links: initialLinks,
3033
+ width,
3034
+ height,
3035
+ enableZoom = true,
3036
+ enableDrag = true,
3037
+ onNodeClick,
3038
+ onNodeHover,
3039
+ onLinkClick,
3040
+ selectedNodeId,
3041
+ hoveredNodeId,
3042
+ defaultNodeColor,
3043
+ defaultNodeSize,
3044
+ defaultLinkColor,
3045
+ defaultLinkWidth,
3046
+ showNodeLabels,
3047
+ showLinkLabels,
3048
+ className,
3049
+ manualLayout = false,
3050
+ onManualLayoutChange,
3051
+ packageBounds,
3052
+ layout: externalLayout,
3053
+ onLayoutChange
3054
+ }, ref) => {
3055
+ const svgRef = useRef(null);
3056
+ const gRef = useRef(null);
3057
+ const [transform, setTransform] = useState({ k: 1, x: 0, y: 0 });
3058
+ const transformRef = useRef(transform);
3059
+ const dragNodeRef = useRef(null);
3060
+ const dragActiveRef = useRef(false);
3061
+ const [pinnedNodes, setPinnedNodes] = useState(/* @__PURE__ */ new Set());
3062
+ const internalDragEnabledRef = useRef(enableDrag);
3063
+ const [layout, setLayout] = useState(externalLayout || "force");
3064
+ useEffect(() => {
3065
+ if (externalLayout && externalLayout !== layout)
3066
+ setLayout(externalLayout);
3067
+ }, [externalLayout, layout]);
3068
+ const handleLayoutChange = useCallback(
3069
+ (newLayout) => {
3070
+ setLayout(newLayout);
3071
+ onLayoutChange?.(newLayout);
3072
+ },
3073
+ [onLayoutChange]
3074
+ );
3075
+ useEffect(() => {
3076
+ internalDragEnabledRef.current = enableDrag;
3077
+ }, [enableDrag]);
3078
+ const { restart, stop, setForcesEnabled } = useSimulationControls();
3079
+ const { nodes } = useGraphLayout(
3080
+ initialNodes,
3081
+ width,
3082
+ height,
3083
+ layout,
3084
+ restart
3085
+ );
3086
+ useEffect(() => {
3087
+ setForcesEnabled(!(manualLayout || pinnedNodes.size > 0));
3088
+ }, [manualLayout, pinnedNodes, setForcesEnabled]);
3089
+ useImperativeHandle(
3090
+ ref,
3091
+ () => useImperativeHandleMethods({
3092
+ nodes,
3093
+ pinnedNodes,
3094
+ setPinnedNodes,
3095
+ restart,
3096
+ width,
3097
+ height,
3098
+ layout,
3099
+ handleLayoutChange,
3100
+ svgRef,
3101
+ gRef,
3102
+ setTransform,
3103
+ internalDragEnabledRef
3104
+ }),
3105
+ [
3106
+ nodes,
3107
+ pinnedNodes,
3108
+ restart,
3109
+ width,
3110
+ height,
3111
+ layout,
3112
+ handleLayoutChange,
3113
+ setForcesEnabled
3114
+ ]
3115
+ );
3116
+ useEffect(() => {
3117
+ onManualLayoutChange?.(manualLayout);
3118
+ }, [manualLayout, onManualLayoutChange]);
3119
+ useGraphZoom(svgRef, gRef, enableZoom, setTransform, transformRef);
3120
+ useWindowDrag(
3121
+ enableDrag,
3122
+ svgRef,
3123
+ transformRef,
3124
+ dragActiveRef,
3125
+ dragNodeRef,
3126
+ () => {
3127
+ setForcesEnabled(true);
3128
+ restart();
3129
+ }
3130
+ );
3131
+ useEffect(() => {
3132
+ if (!gRef.current) return;
3133
+ const g = d32.select(gRef.current);
3134
+ g.selectAll("g.node").each(function() {
3135
+ const d = d32.select(this).datum();
3136
+ if (d)
3137
+ d32.select(this).attr(
3138
+ "transform",
3139
+ `translate(${d.x || 0},${d.y || 0})`
3140
+ );
3141
+ });
3142
+ g.selectAll("line").each(function() {
3143
+ const l = d32.select(this).datum();
3144
+ if (!l) return;
3145
+ const s = typeof l.source === "object" ? l.source : nodes.find((n) => n.id === l.source);
3146
+ const t = typeof l.target === "object" ? l.target : nodes.find((n) => n.id === l.target);
3147
+ if (s && t)
3148
+ d32.select(this).attr("x1", s.x).attr("y1", s.y).attr("x2", t.x).attr("y2", t.y);
3149
+ });
3150
+ }, [nodes, initialLinks]);
3151
+ const { handleDragStart, handleNodeDoubleClick } = useNodeInteractions(
3152
+ enableDrag,
3153
+ nodes,
3154
+ pinnedNodes,
3155
+ setPinnedNodes,
3156
+ restart,
3157
+ stop
3158
+ );
3159
+ useEffect(() => {
3160
+ if (!gRef.current || !enableDrag) return;
3161
+ const g = d32.select(gRef.current);
3162
+ const dragBehavior = d32.drag().on("start", (event) => {
3163
+ const target = event.sourceEvent?.target || event.target;
3164
+ const id = target.closest?.("g.node")?.getAttribute("data-id");
3165
+ if (!id || !internalDragEnabledRef.current) return;
3166
+ const node = nodes.find((n) => n.id === id);
3167
+ if (!node) return;
3168
+ if (!event.active) restart();
3169
+ dragActiveRef.current = true;
3170
+ dragNodeRef.current = node;
3171
+ }).on("drag", (event) => {
3172
+ if (!dragActiveRef.current || !dragNodeRef.current || !svgRef.current)
3173
+ return;
3174
+ const rect = svgRef.current.getBoundingClientRect();
3175
+ dragNodeRef.current.fx = (event.sourceEvent.clientX - rect.left - transform.x) / transform.k;
3176
+ dragNodeRef.current.fy = (event.sourceEvent.clientY - rect.top - transform.y) / transform.k;
3177
+ }).on("end", () => {
3178
+ setForcesEnabled(true);
3179
+ restart();
3180
+ });
3181
+ g.selectAll("g.node").call(dragBehavior);
3182
+ return () => {
3183
+ g.selectAll("g.node").on(".drag", null);
3184
+ };
3185
+ }, [gRef, enableDrag, nodes, transform, restart, setForcesEnabled]);
3186
+ return /* @__PURE__ */ jsx(
3187
+ GraphCanvas,
3188
+ {
3189
+ svgRef,
3190
+ gRef,
3191
+ width,
3192
+ height,
3193
+ className,
3194
+ nodes,
3195
+ links: initialLinks,
3196
+ pinnedNodes,
3197
+ selectedNodeId,
3198
+ hoveredNodeId,
3199
+ defaultNodeColor,
3200
+ defaultNodeSize,
3201
+ defaultLinkColor,
3202
+ defaultLinkWidth,
3203
+ showNodeLabels,
3204
+ showLinkLabels,
3205
+ onNodeClick,
3206
+ onNodeHover,
3207
+ onLinkClick,
3208
+ packageBounds,
3209
+ handleNodeDoubleClick,
3210
+ handleDragStart,
3211
+ restart,
3212
+ setPinnedNodes
3213
+ }
3214
+ );
3215
+ }
3216
+ );
3217
+ ForceDirectedGraph.displayName = "ForceDirectedGraph";
3076
3218
 
3077
3219
  export { AlertCircleIcon, AlertTriangleIcon, ArrowRightIcon, Badge, BrainIcon, Breadcrumb, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ChartIcon, Checkbox, ClockIcon, CodeBlock, Container, EmptyState, ErrorDisplay, FeedbackWidget, FileIcon, ForceDirectedGraph, GlassCard, GlassCardContent, GlassCardHeader, GraphControls, Grid, HammerIcon, InfoIcon, InlineCode, Input, Label, LoadingOverlay, LoadingSpinner, Modal, PlatformShell, PlayIcon, RadioGroup, RefreshCwIcon, RobotIcon, RocketIcon, SaveIcon, ScoreBar, ScoreCard, ScoreCircle, Select, Separator, SettingsIcon, ShieldCheckIcon, ShieldIcon, Stack, Switch, TargetIcon, TerminalIcon, Textarea, ThemeProvider, TrashIcon, TrendingUpIcon, UploadIcon, WalletIcon, ZapIcon, badgeVariants, buttonVariants, chartColors, cn, domainColors, formatCompactNumber, formatDate, formatDateTime, formatDecimal, formatDuration, formatFileSize, formatMetric, formatNumber, formatPercentage, formatRange, formatRelativeTime, getDomainColor, getScoreRating, getSeverityColor, hexToRgba, scoreBg, scoreColor, scoreGlow, scoreLabel, severityColors, useD3, useD3WithResize, useDebounce, useDrag, useForceSimulation, useTheme };
3078
3220
  //# sourceMappingURL=index.js.map