@aiready/components 0.13.18 → 0.13.20
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/components/button.d.ts +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +728 -586
- 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 +28 -7
- package/src/utils/score.ts +67 -29
- 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) {
|
|
@@ -1952,29 +1952,60 @@ function formatRange(min, max) {
|
|
|
1952
1952
|
function formatDecimal(value, decimals = 2) {
|
|
1953
1953
|
return value.toFixed(decimals);
|
|
1954
1954
|
}
|
|
1955
|
+
var SCORE_METADATA = {
|
|
1956
|
+
excellent: {
|
|
1957
|
+
color: "text-emerald-400",
|
|
1958
|
+
bg: "bg-emerald-900/30 border-emerald-500/30",
|
|
1959
|
+
label: "Excellent",
|
|
1960
|
+
glow: "shadow-emerald-500/20"
|
|
1961
|
+
},
|
|
1962
|
+
good: {
|
|
1963
|
+
color: "text-emerald-400",
|
|
1964
|
+
bg: "bg-emerald-900/30 border-emerald-500/30",
|
|
1965
|
+
label: "AI-Ready",
|
|
1966
|
+
glow: "shadow-emerald-500/20"
|
|
1967
|
+
},
|
|
1968
|
+
fair: {
|
|
1969
|
+
color: "text-amber-400",
|
|
1970
|
+
bg: "bg-amber-900/30 border-amber-500/30",
|
|
1971
|
+
label: "Fair",
|
|
1972
|
+
glow: "shadow-amber-500/20"
|
|
1973
|
+
},
|
|
1974
|
+
"needs-work": {
|
|
1975
|
+
color: "text-red-400",
|
|
1976
|
+
bg: "bg-red-900/30 border-red-500/30",
|
|
1977
|
+
label: "Needs Improvement",
|
|
1978
|
+
glow: "shadow-red-500/20"
|
|
1979
|
+
},
|
|
1980
|
+
critical: {
|
|
1981
|
+
color: "text-red-400",
|
|
1982
|
+
bg: "bg-red-900/30 border-red-500/30",
|
|
1983
|
+
label: "Critical Issues",
|
|
1984
|
+
glow: "shadow-red-500/20"
|
|
1985
|
+
}
|
|
1986
|
+
};
|
|
1987
|
+
var DEFAULT_METADATA = {
|
|
1988
|
+
color: "text-slate-400",
|
|
1989
|
+
bg: "bg-slate-800/50 border-slate-700",
|
|
1990
|
+
label: "Not analyzed",
|
|
1991
|
+
glow: ""
|
|
1992
|
+
};
|
|
1993
|
+
function getMetadata(score) {
|
|
1994
|
+
if (score == null) return DEFAULT_METADATA;
|
|
1995
|
+
const rating = getRatingSlug(score);
|
|
1996
|
+
return SCORE_METADATA[rating] || SCORE_METADATA.critical;
|
|
1997
|
+
}
|
|
1955
1998
|
function scoreColor(score) {
|
|
1956
|
-
|
|
1957
|
-
if (score >= 75) return "text-emerald-400";
|
|
1958
|
-
if (score >= 50) return "text-amber-400";
|
|
1959
|
-
return "text-red-400";
|
|
1999
|
+
return getMetadata(score).color;
|
|
1960
2000
|
}
|
|
1961
2001
|
function scoreBg(score) {
|
|
1962
|
-
|
|
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";
|
|
2002
|
+
return getMetadata(score).bg;
|
|
1966
2003
|
}
|
|
1967
2004
|
function scoreLabel(score) {
|
|
1968
|
-
|
|
1969
|
-
if (score >= 75) return "AI-Ready";
|
|
1970
|
-
if (score >= 50) return "Needs Improvement";
|
|
1971
|
-
return "Critical Issues";
|
|
2005
|
+
return getMetadata(score).label;
|
|
1972
2006
|
}
|
|
1973
2007
|
function scoreGlow(score) {
|
|
1974
|
-
|
|
1975
|
-
if (score >= 75) return "shadow-emerald-500/20";
|
|
1976
|
-
if (score >= 50) return "shadow-amber-500/20";
|
|
1977
|
-
return "shadow-red-500/20";
|
|
2008
|
+
return getMetadata(score).glow;
|
|
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
|