Files
fusion/plugins/fusion-plugin-dependency-graph/src/edges.tsx
Fusion 6f63ee3fcd feat(FN-3088): add dependency graph highlighting with hover and selection s
The merge adds visual highlighting to the dependency graph plugin. A new `useDependencyChain` hook tracks dependency relationships, while `GraphTaskNode` and `DependencyGraph` gained hover, selection, and highlight visual states backed by new CSS. Tests cover the hook logic, highlighting behavior, a

Fusion-Task-Id: FN-3088
2026-05-07 04:48:19 -07:00

71 lines
2.4 KiB
TypeScript

import type { GraphEdge, GraphPosition } from "./types";
import "./GraphHighlight.css";
interface GraphEdgesProps {
edges: GraphEdge[];
positions: Map<string, GraphPosition>;
nodeWidth?: number;
nodeHeight?: number;
highlightedEdgeIds?: Set<string>;
}
const DEFAULT_NODE_WIDTH = 280;
const DEFAULT_NODE_HEIGHT = 100;
export function GraphEdges({
edges,
positions,
nodeWidth = DEFAULT_NODE_WIDTH,
nodeHeight = DEFAULT_NODE_HEIGHT,
highlightedEdgeIds,
}: GraphEdgesProps) {
const hasHighlights = Boolean(highlightedEdgeIds && highlightedEdgeIds.size > 0);
return (
<svg className="dependency-graph-edges" aria-hidden="true">
<defs>
<marker
id="dependency-graph-arrowhead"
markerWidth="10"
markerHeight="7"
refX="10"
refY="3.5"
orient="auto"
markerUnits="strokeWidth"
>
<path d="M 0 0 L 10 3.5 L 0 7 z" fill="var(--border)" />
</marker>
</defs>
{edges.map((edge) => {
const source = positions.get(edge.source);
const target = positions.get(edge.target);
if (!source || !target) return null;
const edgeId = `${edge.source}->${edge.target}`;
const isActiveHighlight = hasHighlights && (highlightedEdgeIds?.has(edgeId) ?? false);
const x1 = source.x + nodeWidth / 2;
const y1 = source.y + nodeHeight;
const x2 = target.x + nodeWidth / 2;
const y2 = target.y;
const controlY = y1 + (y2 - y1) / 2;
return (
<path
key={edgeId}
data-testid="dependency-edge"
data-edge-id={edgeId}
className={`dependency-graph-edge${isActiveHighlight ? " graph-edge--highlighted" : ""}${hasHighlights && !isActiveHighlight ? " graph-edge--dimmed" : ""}`}
d={`M ${x1} ${y1} C ${x1} ${controlY}, ${x2} ${controlY}, ${x2} ${y2}`}
fill="none"
stroke={isActiveHighlight ? "var(--todo)" : "var(--border)"}
strokeWidth={isActiveHighlight ? "var(--space-xs)" : "var(--btn-border-width)"}
opacity={hasHighlights && !isActiveHighlight ? 0.15 : 1}
markerEnd="url(#dependency-graph-arrowhead)"
style={{ transition: "opacity var(--transition-fast), stroke var(--transition-fast), stroke-width var(--transition-fast)" }}
/>
);
})}
</svg>
);
}