@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/charts/ForceDirectedGraph.d.ts +7 -13
- package/dist/charts/ForceDirectedGraph.js +451 -337
- package/dist/charts/ForceDirectedGraph.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +724 -582
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/charts/ForceDirectedGraph.tsx +12 -509
- package/src/charts/LinkItem.tsx +1 -1
- package/src/charts/NodeItem.tsx +1 -1
- package/src/charts/force-directed/ControlButton.tsx +39 -0
- package/src/charts/force-directed/ForceDirectedGraph.tsx +250 -0
- package/src/charts/force-directed/GraphCanvas.tsx +129 -0
- package/src/charts/{GraphControls.tsx → force-directed/GraphControls.tsx} +3 -110
- package/src/charts/force-directed/index.ts +31 -0
- package/src/charts/force-directed/types.ts +102 -0
- package/src/charts/{hooks.ts → force-directed/useGraphInteractions.ts} +64 -1
- package/src/charts/force-directed/useGraphLayout.ts +54 -0
- package/src/charts/force-directed/useImperativeHandle.ts +131 -0
- package/src/charts/layout-utils.ts +1 -1
- package/src/components/feedback/__tests__/badge.test.tsx +92 -0
- package/src/components/ui/__tests__/button.test.tsx +203 -0
- package/src/data-display/__tests__/ScoreBar.test.tsx +215 -0
- package/src/index.ts +4 -1
- package/src/utils/__tests__/score.test.ts +1 -1
- package/src/utils/score.ts +48 -23
- package/src/charts/types.ts +0 -24
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
|
|
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
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
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
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
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
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
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
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
2120
|
+
const simulation = d32.forceSimulation(
|
|
2090
2121
|
nodesCopy
|
|
2091
2122
|
);
|
|
2092
2123
|
try {
|
|
2093
|
-
const linkForce =
|
|
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",
|
|
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
|
-
|
|
2140
|
+
d32.forceManyBody().strength(chargeStrength)
|
|
2110
2141
|
);
|
|
2111
2142
|
simulation.force(
|
|
2112
2143
|
"center",
|
|
2113
|
-
|
|
2144
|
+
d32.forceCenter(width / 2, height / 2).strength(centerStrength)
|
|
2114
2145
|
);
|
|
2115
|
-
const collide =
|
|
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
|
-
|
|
2153
|
+
d32.forceX(width / 2).strength(Math.max(0.02, centerStrength * 0.5))
|
|
2123
2154
|
);
|
|
2124
2155
|
simulation.force(
|
|
2125
2156
|
"y",
|
|
2126
|
-
|
|
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
|
-
|
|
2339
|
-
node
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
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
|
-
|
|
2441
|
+
[enableDrag, stop, setPinnedNodes]
|
|
2405
2442
|
);
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
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
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
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
|
-
|
|
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
|
|
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
|