chore: setup monorepo using preconstruct
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
|
||||
import EdgeText from './EdgeText';
|
||||
import { BaseEdgeProps } from '../../types';
|
||||
|
||||
export default ({
|
||||
path,
|
||||
centerX,
|
||||
centerY,
|
||||
label,
|
||||
labelStyle,
|
||||
labelShowBg,
|
||||
labelBgStyle,
|
||||
labelBgPadding,
|
||||
labelBgBorderRadius,
|
||||
style,
|
||||
markerEnd,
|
||||
markerStart,
|
||||
}: BaseEdgeProps) => {
|
||||
const text = label ? (
|
||||
<EdgeText
|
||||
x={centerX}
|
||||
y={centerY}
|
||||
label={label}
|
||||
labelStyle={labelStyle}
|
||||
labelShowBg={labelShowBg}
|
||||
labelBgStyle={labelBgStyle}
|
||||
labelBgPadding={labelBgPadding}
|
||||
labelBgBorderRadius={labelBgBorderRadius}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<path style={style} d={path} className="react-flow__edge-path" markerEnd={markerEnd} markerStart={markerStart} />
|
||||
{text}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,177 @@
|
||||
import React, { memo } from 'react';
|
||||
import { EdgeProps, Position } from '../../types';
|
||||
import BaseEdge from './BaseEdge';
|
||||
|
||||
export interface GetBezierPathParams {
|
||||
sourceX: number;
|
||||
sourceY: number;
|
||||
sourcePosition?: Position;
|
||||
targetX: number;
|
||||
targetY: number;
|
||||
targetPosition?: Position;
|
||||
curvature?: number;
|
||||
}
|
||||
|
||||
interface GetControlWithCurvatureParams {
|
||||
pos: Position;
|
||||
x1: number;
|
||||
y1: number;
|
||||
x2: number;
|
||||
y2: number;
|
||||
c: number;
|
||||
}
|
||||
|
||||
function calculateControlOffset(distance: number, curvature: number): number {
|
||||
if (distance >= 0) {
|
||||
return 0.5 * distance;
|
||||
} else {
|
||||
return curvature * 25 * Math.sqrt(-distance);
|
||||
}
|
||||
}
|
||||
|
||||
function getControlWithCurvature({ pos, x1, y1, x2, y2, c }: GetControlWithCurvatureParams): [number, number] {
|
||||
let ctX: number, ctY: number;
|
||||
switch (pos) {
|
||||
case Position.Left:
|
||||
{
|
||||
ctX = x1 - calculateControlOffset(x1 - x2, c);
|
||||
ctY = y1;
|
||||
}
|
||||
break;
|
||||
case Position.Right:
|
||||
{
|
||||
ctX = x1 + calculateControlOffset(x2 - x1, c);
|
||||
ctY = y1;
|
||||
}
|
||||
break;
|
||||
case Position.Top:
|
||||
{
|
||||
ctX = x1;
|
||||
ctY = y1 - calculateControlOffset(y1 - y2, c);
|
||||
}
|
||||
break;
|
||||
case Position.Bottom:
|
||||
{
|
||||
ctX = x1;
|
||||
ctY = y1 + calculateControlOffset(y2 - y1, c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return [ctX, ctY];
|
||||
}
|
||||
|
||||
export function getBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition = Position.Bottom,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition = Position.Top,
|
||||
curvature = 0.25,
|
||||
}: GetBezierPathParams): string {
|
||||
const [sourceControlX, sourceControlY] = getControlWithCurvature({
|
||||
pos: sourcePosition,
|
||||
x1: sourceX,
|
||||
y1: sourceY,
|
||||
x2: targetX,
|
||||
y2: targetY,
|
||||
c: curvature,
|
||||
});
|
||||
const [targetControlX, targetControlY] = getControlWithCurvature({
|
||||
pos: targetPosition,
|
||||
x1: targetX,
|
||||
y1: targetY,
|
||||
x2: sourceX,
|
||||
y2: sourceY,
|
||||
c: curvature,
|
||||
});
|
||||
return `M${sourceX},${sourceY} C${sourceControlX},${sourceControlY} ${targetControlX},${targetControlY} ${targetX},${targetY}`;
|
||||
}
|
||||
|
||||
// @TODO: this function will recalculate the control points
|
||||
// one option is to let getXXXPath() return center points
|
||||
// but will introduce breaking changes
|
||||
// the getCenter() of other types of edges might need to change, too
|
||||
export function getBezierCenter({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition = Position.Bottom,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition = Position.Top,
|
||||
curvature = 0.25,
|
||||
}: GetBezierPathParams): [number, number, number, number] {
|
||||
const [sourceControlX, sourceControlY] = getControlWithCurvature({
|
||||
pos: sourcePosition,
|
||||
x1: sourceX,
|
||||
y1: sourceY,
|
||||
x2: targetX,
|
||||
y2: targetY,
|
||||
c: curvature,
|
||||
});
|
||||
const [targetControlX, targetControlY] = getControlWithCurvature({
|
||||
pos: targetPosition,
|
||||
x1: targetX,
|
||||
y1: targetY,
|
||||
x2: sourceX,
|
||||
y2: sourceY,
|
||||
c: curvature,
|
||||
});
|
||||
// cubic bezier t=0.5 mid point, not the actual mid point, but easy to calculate
|
||||
// https://stackoverflow.com/questions/67516101/how-to-find-distance-mid-point-of-bezier-curve
|
||||
const centerX = sourceX * 0.125 + sourceControlX * 0.375 + targetControlX * 0.375 + targetX * 0.125;
|
||||
const centerY = sourceY * 0.125 + sourceControlY * 0.375 + targetControlY * 0.375 + targetY * 0.125;
|
||||
const xOffset = Math.abs(centerX - sourceX);
|
||||
const yOffset = Math.abs(centerY - sourceY);
|
||||
return [centerX, centerY, xOffset, yOffset];
|
||||
}
|
||||
|
||||
export default memo(
|
||||
({
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
sourcePosition = Position.Bottom,
|
||||
targetPosition = Position.Top,
|
||||
label,
|
||||
labelStyle,
|
||||
labelShowBg,
|
||||
labelBgStyle,
|
||||
labelBgPadding,
|
||||
labelBgBorderRadius,
|
||||
style,
|
||||
markerEnd,
|
||||
markerStart,
|
||||
curvature,
|
||||
}: EdgeProps) => {
|
||||
const params = {
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition,
|
||||
curvature,
|
||||
};
|
||||
const path = getBezierPath(params);
|
||||
const [centerX, centerY] = getBezierCenter(params);
|
||||
|
||||
return (
|
||||
<BaseEdge
|
||||
path={path}
|
||||
centerX={centerX}
|
||||
centerY={centerY}
|
||||
label={label}
|
||||
labelStyle={labelStyle}
|
||||
labelShowBg={labelShowBg}
|
||||
labelBgStyle={labelBgStyle}
|
||||
labelBgPadding={labelBgPadding}
|
||||
labelBgBorderRadius={labelBgBorderRadius}
|
||||
style={style}
|
||||
markerEnd={markerEnd}
|
||||
markerStart={markerStart}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,40 @@
|
||||
import React, { FC, HTMLAttributes } from 'react';
|
||||
import cc from 'classcat';
|
||||
|
||||
import { Position } from '../../types';
|
||||
|
||||
const shiftX = (x: number, shift: number, position: Position): number => {
|
||||
if (position === Position.Left) return x - shift;
|
||||
if (position === Position.Right) return x + shift;
|
||||
return x;
|
||||
};
|
||||
|
||||
const shiftY = (y: number, shift: number, position: Position): number => {
|
||||
if (position === Position.Top) return y - shift;
|
||||
if (position === Position.Bottom) return y + shift;
|
||||
return y;
|
||||
};
|
||||
|
||||
export interface EdgeAnchorProps extends HTMLAttributes<HTMLDivElement> {
|
||||
position: Position;
|
||||
centerX: number;
|
||||
centerY: number;
|
||||
radius?: number;
|
||||
}
|
||||
|
||||
export const EdgeAnchor: FC<EdgeAnchorProps> = ({
|
||||
className,
|
||||
position,
|
||||
centerX,
|
||||
centerY,
|
||||
radius = 10,
|
||||
}: EdgeAnchorProps) => (
|
||||
<circle
|
||||
className={cc(['react-flow__edgeupdater', className])}
|
||||
cx={shiftX(centerX, radius, position)}
|
||||
cy={shiftY(centerY, radius, position)}
|
||||
r={radius}
|
||||
stroke="transparent"
|
||||
fill="transparent"
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,65 @@
|
||||
import React, { memo, useRef, useState, useEffect, FC, PropsWithChildren } from 'react';
|
||||
import cc from 'classcat';
|
||||
|
||||
import { EdgeTextProps, Rect } from '../../types';
|
||||
|
||||
const EdgeText: FC<PropsWithChildren<EdgeTextProps>> = ({
|
||||
x,
|
||||
y,
|
||||
label,
|
||||
labelStyle = {},
|
||||
labelShowBg = true,
|
||||
labelBgStyle = {},
|
||||
labelBgPadding = [2, 4],
|
||||
labelBgBorderRadius = 2,
|
||||
children,
|
||||
className,
|
||||
...rest
|
||||
}) => {
|
||||
const edgeRef = useRef<SVGTextElement>(null);
|
||||
const [edgeTextBbox, setEdgeTextBbox] = useState<Rect>({ x: 0, y: 0, width: 0, height: 0 });
|
||||
const edgeTextClasses = cc(['react-flow__edge-textwrapper', className]);
|
||||
|
||||
useEffect(() => {
|
||||
if (edgeRef.current) {
|
||||
const textBbox = edgeRef.current.getBBox();
|
||||
|
||||
setEdgeTextBbox({
|
||||
x: textBbox.x,
|
||||
y: textBbox.y,
|
||||
width: textBbox.width,
|
||||
height: textBbox.height,
|
||||
});
|
||||
}
|
||||
}, [label]);
|
||||
|
||||
if (typeof label === 'undefined' || !label) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<g
|
||||
transform={`translate(${x - edgeTextBbox.width / 2} ${y - edgeTextBbox.height / 2})`}
|
||||
className={edgeTextClasses}
|
||||
{...rest}
|
||||
>
|
||||
{labelShowBg && (
|
||||
<rect
|
||||
width={edgeTextBbox.width + 2 * labelBgPadding[0]}
|
||||
x={-labelBgPadding[0]}
|
||||
y={-labelBgPadding[1]}
|
||||
height={edgeTextBbox.height + 2 * labelBgPadding[1]}
|
||||
className="react-flow__edge-textbg"
|
||||
style={labelBgStyle}
|
||||
rx={labelBgBorderRadius}
|
||||
ry={labelBgBorderRadius}
|
||||
/>
|
||||
)}
|
||||
<text className="react-flow__edge-text" y={edgeTextBbox.height / 2} dy="0.3em" ref={edgeRef} style={labelStyle}>
|
||||
{label}
|
||||
</text>
|
||||
{children}
|
||||
</g>
|
||||
);
|
||||
};
|
||||
export default memo(EdgeText);
|
||||
@@ -0,0 +1,149 @@
|
||||
import React, { memo } from 'react';
|
||||
import { EdgeProps, Position } from '../../types';
|
||||
import BaseEdge from './BaseEdge';
|
||||
|
||||
export interface GetSimpleBezierPathParams {
|
||||
sourceX: number;
|
||||
sourceY: number;
|
||||
sourcePosition?: Position;
|
||||
targetX: number;
|
||||
targetY: number;
|
||||
targetPosition?: Position;
|
||||
}
|
||||
|
||||
interface GetControlParams {
|
||||
pos: Position;
|
||||
x1: number;
|
||||
y1: number;
|
||||
x2: number;
|
||||
y2: number;
|
||||
}
|
||||
|
||||
function getControl({ pos, x1, y1, x2, y2 }: GetControlParams): [number, number] {
|
||||
let ctX: number, ctY: number;
|
||||
switch (pos) {
|
||||
case Position.Left:
|
||||
case Position.Right:
|
||||
{
|
||||
ctX = 0.5 * (x1 + x2);
|
||||
ctY = y1;
|
||||
}
|
||||
break;
|
||||
case Position.Top:
|
||||
case Position.Bottom:
|
||||
{
|
||||
ctX = x1;
|
||||
ctY = 0.5 * (y1 + y2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return [ctX, ctY];
|
||||
}
|
||||
|
||||
export function getSimpleBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition = Position.Bottom,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition = Position.Top,
|
||||
}: GetSimpleBezierPathParams): string {
|
||||
const [sourceControlX, sourceControlY] = getControl({
|
||||
pos: sourcePosition,
|
||||
x1: sourceX,
|
||||
y1: sourceY,
|
||||
x2: targetX,
|
||||
y2: targetY,
|
||||
});
|
||||
const [targetControlX, targetControlY] = getControl({
|
||||
pos: targetPosition,
|
||||
x1: targetX,
|
||||
y1: targetY,
|
||||
x2: sourceX,
|
||||
y2: sourceY,
|
||||
});
|
||||
return `M${sourceX},${sourceY} C${sourceControlX},${sourceControlY} ${targetControlX},${targetControlY} ${targetX},${targetY}`;
|
||||
}
|
||||
|
||||
// @TODO: this function will recalculate the control points
|
||||
// one option is to let getXXXPath() return center points
|
||||
// but will introduce breaking changes
|
||||
// the getCenter() of other types of edges might need to change, too
|
||||
export function getSimpleBezierCenter({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition = Position.Bottom,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition = Position.Top,
|
||||
}: GetSimpleBezierPathParams): [number, number, number, number] {
|
||||
const [sourceControlX, sourceControlY] = getControl({
|
||||
pos: sourcePosition,
|
||||
x1: sourceX,
|
||||
y1: sourceY,
|
||||
x2: targetX,
|
||||
y2: targetY,
|
||||
});
|
||||
const [targetControlX, targetControlY] = getControl({
|
||||
pos: targetPosition,
|
||||
x1: targetX,
|
||||
y1: targetY,
|
||||
x2: sourceX,
|
||||
y2: sourceY,
|
||||
});
|
||||
// cubic bezier t=0.5 mid point, not the actual mid point, but easy to calculate
|
||||
// https://stackoverflow.com/questions/67516101/how-to-find-distance-mid-point-of-bezier-curve
|
||||
const centerX = sourceX * 0.125 + sourceControlX * 0.375 + targetControlX * 0.375 + targetX * 0.125;
|
||||
const centerY = sourceY * 0.125 + sourceControlY * 0.375 + targetControlY * 0.375 + targetY * 0.125;
|
||||
const xOffset = Math.abs(centerX - sourceX);
|
||||
const yOffset = Math.abs(centerY - sourceY);
|
||||
return [centerX, centerY, xOffset, yOffset];
|
||||
}
|
||||
|
||||
export default memo(
|
||||
({
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
sourcePosition = Position.Bottom,
|
||||
targetPosition = Position.Top,
|
||||
label,
|
||||
labelStyle,
|
||||
labelShowBg,
|
||||
labelBgStyle,
|
||||
labelBgPadding,
|
||||
labelBgBorderRadius,
|
||||
style,
|
||||
markerEnd,
|
||||
markerStart,
|
||||
}: EdgeProps) => {
|
||||
const params = {
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition,
|
||||
};
|
||||
const path = getSimpleBezierPath(params);
|
||||
const [centerX, centerY] = getSimpleBezierCenter(params);
|
||||
|
||||
return (
|
||||
<BaseEdge
|
||||
path={path}
|
||||
centerX={centerX}
|
||||
centerY={centerY}
|
||||
label={label}
|
||||
labelStyle={labelStyle}
|
||||
labelShowBg={labelShowBg}
|
||||
labelBgStyle={labelBgStyle}
|
||||
labelBgPadding={labelBgPadding}
|
||||
labelBgBorderRadius={labelBgBorderRadius}
|
||||
style={style}
|
||||
markerEnd={markerEnd}
|
||||
markerStart={markerStart}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,166 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { getCenter } from './utils';
|
||||
import { EdgeSmoothStepProps, Position } from '../../types';
|
||||
import BaseEdge from './BaseEdge';
|
||||
|
||||
// These are some helper methods for drawing the round corners
|
||||
// The name indicates the direction of the path. "bottomLeftCorner" goes
|
||||
// from bottom to the left and "leftBottomCorner" goes from left to the bottom.
|
||||
// We have to consider the direction of the paths because of the animated lines.
|
||||
const bottomLeftCorner = (x: number, y: number, size: number): string =>
|
||||
`L ${x},${y - size}Q ${x},${y} ${x + size},${y}`;
|
||||
const leftBottomCorner = (x: number, y: number, size: number): string =>
|
||||
`L ${x + size},${y}Q ${x},${y} ${x},${y - size}`;
|
||||
const bottomRightCorner = (x: number, y: number, size: number): string =>
|
||||
`L ${x},${y - size}Q ${x},${y} ${x - size},${y}`;
|
||||
const rightBottomCorner = (x: number, y: number, size: number): string =>
|
||||
`L ${x - size},${y}Q ${x},${y} ${x},${y - size}`;
|
||||
const leftTopCorner = (x: number, y: number, size: number): string => `L ${x + size},${y}Q ${x},${y} ${x},${y + size}`;
|
||||
const topLeftCorner = (x: number, y: number, size: number): string => `L ${x},${y + size}Q ${x},${y} ${x + size},${y}`;
|
||||
const topRightCorner = (x: number, y: number, size: number): string => `L ${x},${y + size}Q ${x},${y} ${x - size},${y}`;
|
||||
const rightTopCorner = (x: number, y: number, size: number): string => `L ${x - size},${y}Q ${x},${y} ${x},${y + size}`;
|
||||
|
||||
export interface GetSmoothStepPathParams {
|
||||
sourceX: number;
|
||||
sourceY: number;
|
||||
sourcePosition?: Position;
|
||||
targetX: number;
|
||||
targetY: number;
|
||||
targetPosition?: Position;
|
||||
borderRadius?: number;
|
||||
centerX?: number;
|
||||
centerY?: number;
|
||||
}
|
||||
|
||||
export function getSmoothStepPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition = Position.Bottom,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition = Position.Top,
|
||||
borderRadius = 5,
|
||||
centerX,
|
||||
centerY,
|
||||
}: GetSmoothStepPathParams): string {
|
||||
const [_centerX, _centerY, offsetX, offsetY] = getCenter({ sourceX, sourceY, targetX, targetY });
|
||||
const cornerWidth = Math.min(borderRadius, Math.abs(targetX - sourceX));
|
||||
const cornerHeight = Math.min(borderRadius, Math.abs(targetY - sourceY));
|
||||
const cornerSize = Math.min(cornerWidth, cornerHeight, offsetX, offsetY);
|
||||
const leftAndRight = [Position.Left, Position.Right];
|
||||
const cX = typeof centerX !== 'undefined' ? centerX : _centerX;
|
||||
const cY = typeof centerY !== 'undefined' ? centerY : _centerY;
|
||||
|
||||
let firstCornerPath = null;
|
||||
let secondCornerPath = null;
|
||||
|
||||
if (sourceX <= targetX) {
|
||||
firstCornerPath =
|
||||
sourceY <= targetY ? bottomLeftCorner(sourceX, cY, cornerSize) : topLeftCorner(sourceX, cY, cornerSize);
|
||||
secondCornerPath =
|
||||
sourceY <= targetY ? rightTopCorner(targetX, cY, cornerSize) : rightBottomCorner(targetX, cY, cornerSize);
|
||||
} else {
|
||||
firstCornerPath =
|
||||
sourceY < targetY ? bottomRightCorner(sourceX, cY, cornerSize) : topRightCorner(sourceX, cY, cornerSize);
|
||||
secondCornerPath =
|
||||
sourceY < targetY ? leftTopCorner(targetX, cY, cornerSize) : leftBottomCorner(targetX, cY, cornerSize);
|
||||
}
|
||||
|
||||
if (leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) {
|
||||
if (sourceX <= targetX) {
|
||||
firstCornerPath =
|
||||
sourceY <= targetY ? rightTopCorner(cX, sourceY, cornerSize) : rightBottomCorner(cX, sourceY, cornerSize);
|
||||
secondCornerPath =
|
||||
sourceY <= targetY ? bottomLeftCorner(cX, targetY, cornerSize) : topLeftCorner(cX, targetY, cornerSize);
|
||||
} else if (
|
||||
(sourcePosition === Position.Right && targetPosition === Position.Left) ||
|
||||
(sourcePosition === Position.Left && targetPosition === Position.Right) ||
|
||||
(sourcePosition === Position.Left && targetPosition === Position.Left)
|
||||
) {
|
||||
// and sourceX > targetX
|
||||
firstCornerPath =
|
||||
sourceY <= targetY ? leftTopCorner(cX, sourceY, cornerSize) : leftBottomCorner(cX, sourceY, cornerSize);
|
||||
secondCornerPath =
|
||||
sourceY <= targetY ? bottomRightCorner(cX, targetY, cornerSize) : topRightCorner(cX, targetY, cornerSize);
|
||||
}
|
||||
} else if (leftAndRight.includes(sourcePosition) && !leftAndRight.includes(targetPosition)) {
|
||||
if (sourceX <= targetX) {
|
||||
firstCornerPath =
|
||||
sourceY <= targetY
|
||||
? rightTopCorner(targetX, sourceY, cornerSize)
|
||||
: rightBottomCorner(targetX, sourceY, cornerSize);
|
||||
} else {
|
||||
firstCornerPath =
|
||||
sourceY <= targetY
|
||||
? leftTopCorner(targetX, sourceY, cornerSize)
|
||||
: leftBottomCorner(targetX, sourceY, cornerSize);
|
||||
}
|
||||
secondCornerPath = '';
|
||||
} else if (!leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) {
|
||||
if (sourceX <= targetX) {
|
||||
firstCornerPath =
|
||||
sourceY <= targetY
|
||||
? bottomLeftCorner(sourceX, targetY, cornerSize)
|
||||
: topLeftCorner(sourceX, targetY, cornerSize);
|
||||
} else {
|
||||
firstCornerPath =
|
||||
sourceY <= targetY
|
||||
? bottomRightCorner(sourceX, targetY, cornerSize)
|
||||
: topRightCorner(sourceX, targetY, cornerSize);
|
||||
}
|
||||
secondCornerPath = '';
|
||||
}
|
||||
|
||||
return `M ${sourceX},${sourceY}${firstCornerPath}${secondCornerPath}L ${targetX},${targetY}`;
|
||||
}
|
||||
|
||||
export default memo(
|
||||
({
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
label,
|
||||
labelStyle,
|
||||
labelShowBg,
|
||||
labelBgStyle,
|
||||
labelBgPadding,
|
||||
labelBgBorderRadius,
|
||||
style,
|
||||
sourcePosition = Position.Bottom,
|
||||
targetPosition = Position.Top,
|
||||
markerEnd,
|
||||
markerStart,
|
||||
borderRadius = 5,
|
||||
}: EdgeSmoothStepProps) => {
|
||||
const [centerX, centerY] = getCenter({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition });
|
||||
|
||||
const path = getSmoothStepPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition,
|
||||
borderRadius,
|
||||
});
|
||||
|
||||
return (
|
||||
<BaseEdge
|
||||
path={path}
|
||||
centerX={centerX}
|
||||
centerY={centerY}
|
||||
label={label}
|
||||
labelStyle={labelStyle}
|
||||
labelShowBg={labelShowBg}
|
||||
labelBgStyle={labelBgStyle}
|
||||
labelBgPadding={labelBgPadding}
|
||||
labelBgBorderRadius={labelBgBorderRadius}
|
||||
style={style}
|
||||
markerEnd={markerEnd}
|
||||
markerStart={markerStart}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,8 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { EdgeSmoothStepProps } from '../../types';
|
||||
import SmoothStepEdge from './SmoothStepEdge';
|
||||
|
||||
export default memo((props: EdgeSmoothStepProps) => {
|
||||
return <SmoothStepEdge {...props} borderRadius={0} />;
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import BaseEdge from './BaseEdge';
|
||||
import { EdgeProps } from '../../types';
|
||||
|
||||
export default memo(
|
||||
({
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
label,
|
||||
labelStyle,
|
||||
labelShowBg,
|
||||
labelBgStyle,
|
||||
labelBgPadding,
|
||||
labelBgBorderRadius,
|
||||
style,
|
||||
markerEnd,
|
||||
markerStart,
|
||||
}: EdgeProps) => {
|
||||
const yOffset = Math.abs(targetY - sourceY) / 2;
|
||||
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
|
||||
|
||||
const xOffset = Math.abs(targetX - sourceX) / 2;
|
||||
const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;
|
||||
const path = `M ${sourceX},${sourceY}L ${targetX},${targetY}`;
|
||||
|
||||
return (
|
||||
<BaseEdge
|
||||
path={path}
|
||||
centerX={centerX}
|
||||
centerY={centerY}
|
||||
label={label}
|
||||
labelStyle={labelStyle}
|
||||
labelShowBg={labelShowBg}
|
||||
labelBgStyle={labelBgStyle}
|
||||
labelBgPadding={labelBgPadding}
|
||||
labelBgBorderRadius={labelBgBorderRadius}
|
||||
style={style}
|
||||
markerEnd={markerEnd}
|
||||
markerStart={markerStart}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
export { default as SimpleBezierEdge } from './SimpleBezierEdge';
|
||||
export { default as SmoothStepEdge } from './SmoothStepEdge';
|
||||
export { default as StepEdge } from './StepEdge';
|
||||
export { default as StraightEdge } from './StraightEdge';
|
||||
export { default as BezierEdge } from './BezierEdge';
|
||||
@@ -0,0 +1,70 @@
|
||||
import { MouseEvent as ReactMouseEvent } from 'react';
|
||||
import { GetState } from 'zustand';
|
||||
|
||||
import { Edge, MarkerType, Position, ReactFlowState } from '../../types';
|
||||
|
||||
export const getMarkerEnd = (markerType?: MarkerType, markerEndId?: string): string => {
|
||||
if (typeof markerEndId !== 'undefined' && markerEndId) {
|
||||
return `url(#${markerEndId})`;
|
||||
}
|
||||
|
||||
return typeof markerType !== 'undefined' ? `url(#react-flow__${markerType})` : 'none';
|
||||
};
|
||||
|
||||
export interface GetCenterParams {
|
||||
sourceX: number;
|
||||
sourceY: number;
|
||||
targetX: number;
|
||||
targetY: number;
|
||||
sourcePosition?: Position;
|
||||
targetPosition?: Position;
|
||||
}
|
||||
|
||||
const LeftOrRight = [Position.Left, Position.Right];
|
||||
|
||||
export const getCenter = ({
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
sourcePosition = Position.Bottom,
|
||||
targetPosition = Position.Top,
|
||||
}: GetCenterParams): [number, number, number, number] => {
|
||||
const sourceIsLeftOrRight = LeftOrRight.includes(sourcePosition);
|
||||
const targetIsLeftOrRight = LeftOrRight.includes(targetPosition);
|
||||
|
||||
// we expect flows to be horizontal or vertical (all handles left or right respectively top or bottom)
|
||||
// a mixed edge is when one the source is on the left and the target is on the top for example.
|
||||
const mixedEdge = (sourceIsLeftOrRight && !targetIsLeftOrRight) || (targetIsLeftOrRight && !sourceIsLeftOrRight);
|
||||
|
||||
if (mixedEdge) {
|
||||
const xOffset = sourceIsLeftOrRight ? Math.abs(targetX - sourceX) : 0;
|
||||
const centerX = sourceX > targetX ? sourceX - xOffset : sourceX + xOffset;
|
||||
|
||||
const yOffset = sourceIsLeftOrRight ? 0 : Math.abs(targetY - sourceY);
|
||||
const centerY = sourceY < targetY ? sourceY + yOffset : sourceY - yOffset;
|
||||
|
||||
return [centerX, centerY, xOffset, yOffset];
|
||||
}
|
||||
|
||||
const xOffset = Math.abs(targetX - sourceX) / 2;
|
||||
const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;
|
||||
|
||||
const yOffset = Math.abs(targetY - sourceY) / 2;
|
||||
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
|
||||
|
||||
return [centerX, centerY, xOffset, yOffset];
|
||||
};
|
||||
|
||||
export function getMouseHandler(
|
||||
id: string,
|
||||
getState: GetState<ReactFlowState>,
|
||||
handler?: (event: ReactMouseEvent<SVGGElement, MouseEvent>, edge: Edge) => void
|
||||
) {
|
||||
return handler === undefined
|
||||
? handler
|
||||
: (event: ReactMouseEvent<SVGGElement, MouseEvent>) => {
|
||||
const edge = getState().edges.find((e) => e.id === id)!;
|
||||
handler(event, { ...edge });
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
import React, { memo, ComponentType, useState, useMemo } from 'react';
|
||||
import cc from 'classcat';
|
||||
import shallow from 'zustand/shallow';
|
||||
|
||||
import { useStore, useStoreApi } from '../../store';
|
||||
import { EdgeProps, WrapEdgeProps, ReactFlowState, Connection } from '../../types';
|
||||
import { handleMouseDown } from '../../components/Handle/handler';
|
||||
import { EdgeAnchor } from './EdgeAnchor';
|
||||
import { getMarkerId } from '../../utils/graph';
|
||||
import { getMouseHandler } from './utils';
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
addSelectedEdges: s.addSelectedEdges,
|
||||
connectionMode: s.connectionMode,
|
||||
});
|
||||
|
||||
export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
||||
const EdgeWrapper = ({
|
||||
id,
|
||||
className,
|
||||
type,
|
||||
data,
|
||||
onClick,
|
||||
onEdgeDoubleClick,
|
||||
selected,
|
||||
animated,
|
||||
label,
|
||||
labelStyle,
|
||||
labelShowBg,
|
||||
labelBgStyle,
|
||||
labelBgPadding,
|
||||
labelBgBorderRadius,
|
||||
style,
|
||||
source,
|
||||
target,
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
elementsSelectable,
|
||||
hidden,
|
||||
sourceHandleId,
|
||||
targetHandleId,
|
||||
onContextMenu,
|
||||
onMouseEnter,
|
||||
onMouseMove,
|
||||
onMouseLeave,
|
||||
edgeUpdaterRadius,
|
||||
onEdgeUpdate,
|
||||
onEdgeUpdateStart,
|
||||
onEdgeUpdateEnd,
|
||||
markerEnd,
|
||||
markerStart,
|
||||
rfId,
|
||||
}: WrapEdgeProps): JSX.Element | null => {
|
||||
const [updating, setUpdating] = useState<boolean>(false);
|
||||
const { addSelectedEdges, connectionMode } = useStore(selector, shallow);
|
||||
const store = useStoreApi();
|
||||
|
||||
const onEdgeClick = (event: React.MouseEvent<SVGGElement, MouseEvent>): void => {
|
||||
const edge = store.getState().edges.find((e) => e.id === id)!;
|
||||
|
||||
if (elementsSelectable) {
|
||||
store.setState({ nodesSelectionActive: false });
|
||||
addSelectedEdges([id]);
|
||||
}
|
||||
|
||||
onClick?.(event, edge);
|
||||
};
|
||||
|
||||
const onEdgeDoubleClickHandler = getMouseHandler(id, store.getState, onEdgeDoubleClick);
|
||||
const onEdgeContextMenu = getMouseHandler(id, store.getState, onContextMenu);
|
||||
const onEdgeMouseEnter = getMouseHandler(id, store.getState, onMouseEnter);
|
||||
const onEdgeMouseMove = getMouseHandler(id, store.getState, onMouseMove);
|
||||
const onEdgeMouseLeave = getMouseHandler(id, store.getState, onMouseLeave);
|
||||
|
||||
const handleEdgeUpdater = (event: React.MouseEvent<SVGGElement, MouseEvent>, isSourceHandle: boolean) => {
|
||||
const nodeId = isSourceHandle ? target : source;
|
||||
const handleId = (isSourceHandle ? targetHandleId : sourceHandleId) || null;
|
||||
const handleType = isSourceHandle ? 'target' : 'source';
|
||||
const isValidConnection = () => true;
|
||||
const isTarget = isSourceHandle;
|
||||
const edge = store.getState().edges.find((e) => e.id === id)!;
|
||||
|
||||
onEdgeUpdateStart?.(event, edge, handleType);
|
||||
|
||||
const _onEdgeUpdate = onEdgeUpdateEnd
|
||||
? (evt: MouseEvent): void => onEdgeUpdateEnd(evt, edge, handleType)
|
||||
: undefined;
|
||||
|
||||
const onConnectEdge = (connection: Connection) => {
|
||||
const { edges } = store.getState();
|
||||
const edge = edges.find((e) => e.id === id);
|
||||
|
||||
if (edge && onEdgeUpdate) {
|
||||
onEdgeUpdate(edge, connection);
|
||||
}
|
||||
};
|
||||
|
||||
handleMouseDown(
|
||||
event,
|
||||
handleId,
|
||||
nodeId,
|
||||
store.setState,
|
||||
onConnectEdge,
|
||||
isTarget,
|
||||
isValidConnection,
|
||||
connectionMode,
|
||||
handleType,
|
||||
_onEdgeUpdate,
|
||||
store.getState
|
||||
);
|
||||
};
|
||||
|
||||
const onEdgeUpdaterSourceMouseDown = (event: React.MouseEvent<SVGGElement, MouseEvent>): void =>
|
||||
handleEdgeUpdater(event, true);
|
||||
const onEdgeUpdaterTargetMouseDown = (event: React.MouseEvent<SVGGElement, MouseEvent>): void =>
|
||||
handleEdgeUpdater(event, false);
|
||||
|
||||
const onEdgeUpdaterMouseEnter = () => setUpdating(true);
|
||||
const onEdgeUpdaterMouseOut = () => setUpdating(false);
|
||||
const markerStartUrl = useMemo(() => `url(#${getMarkerId(markerStart, rfId)})`, [markerStart, rfId]);
|
||||
const markerEndUrl = useMemo(() => `url(#${getMarkerId(markerEnd, rfId)})`, [markerEnd, rfId]);
|
||||
|
||||
if (hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const inactive = !elementsSelectable && !onClick;
|
||||
const handleEdgeUpdate = typeof onEdgeUpdate !== 'undefined';
|
||||
const edgeClasses = cc([
|
||||
'react-flow__edge',
|
||||
`react-flow__edge-${type}`,
|
||||
className,
|
||||
{ selected, animated, inactive, updating },
|
||||
]);
|
||||
|
||||
return (
|
||||
<g
|
||||
className={edgeClasses}
|
||||
onClick={onEdgeClick}
|
||||
onDoubleClick={onEdgeDoubleClickHandler}
|
||||
onContextMenu={onEdgeContextMenu}
|
||||
onMouseEnter={onEdgeMouseEnter}
|
||||
onMouseMove={onEdgeMouseMove}
|
||||
onMouseLeave={onEdgeMouseLeave}
|
||||
>
|
||||
<EdgeComponent
|
||||
id={id}
|
||||
source={source}
|
||||
target={target}
|
||||
selected={selected}
|
||||
animated={animated}
|
||||
label={label}
|
||||
labelStyle={labelStyle}
|
||||
labelShowBg={labelShowBg}
|
||||
labelBgStyle={labelBgStyle}
|
||||
labelBgPadding={labelBgPadding}
|
||||
labelBgBorderRadius={labelBgBorderRadius}
|
||||
data={data}
|
||||
style={style}
|
||||
sourceX={sourceX}
|
||||
sourceY={sourceY}
|
||||
targetX={targetX}
|
||||
targetY={targetY}
|
||||
sourcePosition={sourcePosition}
|
||||
targetPosition={targetPosition}
|
||||
sourceHandleId={sourceHandleId}
|
||||
targetHandleId={targetHandleId}
|
||||
markerStart={markerStartUrl}
|
||||
markerEnd={markerEndUrl}
|
||||
/>
|
||||
{handleEdgeUpdate && (
|
||||
<g
|
||||
onMouseDown={onEdgeUpdaterSourceMouseDown}
|
||||
onMouseEnter={onEdgeUpdaterMouseEnter}
|
||||
onMouseOut={onEdgeUpdaterMouseOut}
|
||||
>
|
||||
<EdgeAnchor position={sourcePosition} centerX={sourceX} centerY={sourceY} radius={edgeUpdaterRadius} />
|
||||
</g>
|
||||
)}
|
||||
{handleEdgeUpdate && (
|
||||
<g
|
||||
onMouseDown={onEdgeUpdaterTargetMouseDown}
|
||||
onMouseEnter={onEdgeUpdaterMouseEnter}
|
||||
onMouseOut={onEdgeUpdaterMouseOut}
|
||||
>
|
||||
<EdgeAnchor position={targetPosition} centerX={targetX} centerY={targetY} radius={edgeUpdaterRadius} />
|
||||
</g>
|
||||
)}
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
EdgeWrapper.displayName = 'EdgeWrapper';
|
||||
|
||||
return memo(EdgeWrapper);
|
||||
};
|
||||
Reference in New Issue
Block a user