141 lines
3.1 KiB
TypeScript
141 lines
3.1 KiB
TypeScript
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];
|
|
}
|
|
|
|
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];
|
|
};
|
|
|
|
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,
|
|
};
|