edge.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import {
  2. BaseEdge,
  3. type EdgeProps,
  4. getBezierPath,
  5. getSimpleBezierPath,
  6. type InternalNode,
  7. type Node,
  8. Position,
  9. useInternalNode,
  10. } from '@xyflow/react';
  11. const Temporary = ({
  12. id,
  13. sourceX,
  14. sourceY,
  15. targetX,
  16. targetY,
  17. sourcePosition,
  18. targetPosition,
  19. }: EdgeProps) => {
  20. const [edgePath] = getSimpleBezierPath({
  21. sourceX,
  22. sourceY,
  23. sourcePosition,
  24. targetX,
  25. targetY,
  26. targetPosition,
  27. });
  28. return (
  29. <BaseEdge
  30. className="stroke-1 stroke-ring"
  31. id={id}
  32. path={edgePath}
  33. style={{
  34. strokeDasharray: '5, 5',
  35. }}
  36. />
  37. );
  38. };
  39. const getHandleCoordsByPosition = (node: InternalNode<Node>, handlePosition: Position) => {
  40. // Choose the handle type based on position - Left is for target, Right is for source
  41. const handleType = handlePosition === Position.Left ? 'target' : 'source';
  42. const handle = node.internals.handleBounds?.[handleType]?.find(
  43. (h) => h.position === handlePosition,
  44. );
  45. if (!handle) {
  46. return [0, 0] as const;
  47. }
  48. let offsetX = handle.width / 2;
  49. let offsetY = handle.height / 2;
  50. // this is a tiny detail to make the markerEnd of an edge visible.
  51. // The handle position that gets calculated has the origin top-left, so depending which side we are using, we add a little offset
  52. // 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
  53. switch (handlePosition) {
  54. case Position.Left:
  55. offsetX = 0;
  56. break;
  57. case Position.Right:
  58. offsetX = handle.width;
  59. break;
  60. case Position.Top:
  61. offsetY = 0;
  62. break;
  63. case Position.Bottom:
  64. offsetY = handle.height;
  65. break;
  66. default:
  67. throw new Error(`Invalid handle position: ${handlePosition}`);
  68. }
  69. const x = node.internals.positionAbsolute.x + handle.x + offsetX;
  70. const y = node.internals.positionAbsolute.y + handle.y + offsetY;
  71. return [x, y] as const;
  72. };
  73. const getEdgeParams = (source: InternalNode<Node>, target: InternalNode<Node>) => {
  74. const sourcePos = Position.Right;
  75. const [sx, sy] = getHandleCoordsByPosition(source, sourcePos);
  76. const targetPos = Position.Left;
  77. const [tx, ty] = getHandleCoordsByPosition(target, targetPos);
  78. return {
  79. sx,
  80. sy,
  81. tx,
  82. ty,
  83. sourcePos,
  84. targetPos,
  85. };
  86. };
  87. const Animated = ({ id, source, target, markerEnd, style }: EdgeProps) => {
  88. const sourceNode = useInternalNode(source);
  89. const targetNode = useInternalNode(target);
  90. if (!(sourceNode && targetNode)) {
  91. return null;
  92. }
  93. const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode, targetNode);
  94. const [edgePath] = getBezierPath({
  95. sourceX: sx,
  96. sourceY: sy,
  97. sourcePosition: sourcePos,
  98. targetX: tx,
  99. targetY: ty,
  100. targetPosition: targetPos,
  101. });
  102. return (
  103. <>
  104. <BaseEdge id={id} markerEnd={markerEnd} path={edgePath} style={style} />
  105. <circle fill="var(--primary)" r="4">
  106. <animateMotion dur="2s" path={edgePath} repeatCount="indefinite" />
  107. </circle>
  108. </>
  109. );
  110. };
  111. export const Edge = {
  112. Temporary,
  113. Animated,
  114. };