| 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) => { |
| |
| 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; |
|
|
| |
| |
| |
| 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, |
| }; |
|
|