import {
BaseEdge,
type EdgeProps,
getBezierPath,
getSimpleBezierPath,
type InternalNode,
type Node,
Position,
useInternalNode,
} from "@xyflow/react";
const Temporary = ({
id,
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
}: EdgeProps) => {
const [edgePath] = getSimpleBezierPath({
sourceX,
sourceY,
sourcePosition,
targetX,
targetY,
targetPosition,
});
return (
<BaseEdge
className="stroke-1 stroke-ring"
id={id}
path={edgePath}
style={{
strokeDasharray: "5, 5",
}}
/>
);
};
const getHandleCoordsByPosition = (
node: InternalNode<Node>,
handlePosition: Position
) => {
// Choose the handle type based on position - Left is for target, Right is for source
const handleType = handlePosition === Position.Left ? "target" : "source";
const handle = node.internals.handleBounds?.[handleType]?.find(
(h) => h.position === handlePosition
);
if (!handle) {
return [0, 0] as const;
}
let offsetX = handle.width / 2;
let offsetY = handle.height / 2;
// this is a tiny detail to make the markerEnd of an edge visible.
// The handle position that gets calculated has the origin top-left, so depending which side we are using, we add a little offset
// when the handlePosition is Position.Right for example, we need to add an offset as big as the handle itself in order to get the correct position
switch (handlePosition) {
case Position.Left:
offsetX = 0;
break;
case Position.Right:
offsetX = handle.width;
break;
case Position.Top:
offsetY = 0;
break;
case Position.Bottom:
offsetY = handle.height;
break;
default:
throw new Error(`Invalid handle position: ${handlePosition}`);
}
const x = node.internals.positionAbsolute.x + handle.x + offsetX;
const y = node.internals.positionAbsolute.y + handle.y + offsetY;
return [x, y] as const;
};
const getEdgeParams = (
source: InternalNode<Node>,
target: InternalNode<Node>
) => {
const sourcePos = Position.Right;
const [sx, sy] = getHandleCoordsByPosition(source, sourcePos);
const targetPos = Position.Left;
const [tx, ty] = getHandleCoordsByPosition(target, targetPos);
return {
sx,
sy,
tx,
ty,
sourcePos,
targetPos,
};
};
const Animated = ({ id, source, target, markerEnd, style }: EdgeProps) => {
const sourceNode = useInternalNode(source);
const targetNode = useInternalNode(target);
if (!(sourceNode && targetNode)) {
return null;
}
const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(
sourceNode,
targetNode
);
const [edgePath] = getBezierPath({
sourceX: sx,
sourceY: sy,
sourcePosition: sourcePos,
targetX: tx,
targetY: ty,
targetPosition: targetPos,
});
return (
<>
<BaseEdge id={id} markerEnd={markerEnd} path={edgePath} style={style} />
<circle fill="var(--primary)" r="4">
<animateMotion dur="2s" path={edgePath} repeatCount="indefinite" />
</circle>
</>
);
};
export const Edge = {
Temporary,
Animated,
};