chore: setup monorepo using preconstruct
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import React, { memo, useCallback } from 'react';
|
||||
|
||||
import { useStore } from '../../store';
|
||||
import { EdgeMarker, ReactFlowState } from '../../types';
|
||||
import { getMarkerId } from '../../utils/graph';
|
||||
import { useMarkerSymbol } from './MarkerSymbols';
|
||||
interface MarkerProps extends EdgeMarker {
|
||||
id: string;
|
||||
}
|
||||
interface MarkerDefinitionsProps {
|
||||
defaultColor: string;
|
||||
rfId?: string;
|
||||
}
|
||||
|
||||
const Marker = ({
|
||||
id,
|
||||
type,
|
||||
color,
|
||||
width = 12.5,
|
||||
height = 12.5,
|
||||
markerUnits = 'strokeWidth',
|
||||
strokeWidth,
|
||||
orient = 'auto',
|
||||
}: MarkerProps) => {
|
||||
const Symbol = useMarkerSymbol(type);
|
||||
|
||||
return (
|
||||
<marker
|
||||
className="react-flow__arrowhead"
|
||||
id={id}
|
||||
markerWidth={`${width}`}
|
||||
markerHeight={`${height}`}
|
||||
viewBox="-10 -10 20 20"
|
||||
markerUnits={markerUnits}
|
||||
orient={orient}
|
||||
refX="0"
|
||||
refY="0"
|
||||
>
|
||||
<Symbol color={color} strokeWidth={strokeWidth} />
|
||||
</marker>
|
||||
);
|
||||
};
|
||||
|
||||
const markerSelector =
|
||||
({ defaultColor, rfId }: { defaultColor: string; rfId?: string }) =>
|
||||
(s: ReactFlowState) => {
|
||||
const ids: string[] = [];
|
||||
|
||||
return s.edges
|
||||
.reduce<MarkerProps[]>((markers, edge) => {
|
||||
[edge.markerStart, edge.markerEnd].forEach((marker) => {
|
||||
if (marker && typeof marker === 'object') {
|
||||
const markerId = getMarkerId(marker, rfId);
|
||||
if (!ids.includes(markerId)) {
|
||||
markers.push({ id: markerId, color: marker.color || defaultColor, ...marker });
|
||||
ids.push(markerId);
|
||||
}
|
||||
}
|
||||
});
|
||||
return markers;
|
||||
}, [])
|
||||
.sort((a, b) => a.id.localeCompare(b.id));
|
||||
};
|
||||
|
||||
// when you have multiple flows on a page and you hide the first one, the other ones have no markers anymore
|
||||
// when they do have markers with the same ids. To prevent this the user can pass a unique id to the react flow wrapper
|
||||
// that we can then use for creating our unique marker ids
|
||||
const MarkerDefinitions = ({ defaultColor, rfId }: MarkerDefinitionsProps) => {
|
||||
const markers = useStore(
|
||||
useCallback(markerSelector({ defaultColor, rfId }), [defaultColor, rfId]),
|
||||
// the id includes all marker options, so we just need to look at that part of the marker
|
||||
(a, b) => !(a.length !== b.length || a.some((m, i) => m.id !== b[i].id))
|
||||
);
|
||||
|
||||
return (
|
||||
<defs>
|
||||
{markers.map((marker: MarkerProps) => (
|
||||
<Marker
|
||||
id={marker.id}
|
||||
key={marker.id}
|
||||
type={marker.type}
|
||||
color={marker.color}
|
||||
width={marker.width}
|
||||
height={marker.height}
|
||||
markerUnits={marker.markerUnits}
|
||||
strokeWidth={marker.strokeWidth}
|
||||
orient={marker.orient}
|
||||
/>
|
||||
))}
|
||||
</defs>
|
||||
);
|
||||
};
|
||||
|
||||
MarkerDefinitions.displayName = 'MarkerDefinitions';
|
||||
|
||||
export default memo(MarkerDefinitions);
|
||||
@@ -0,0 +1,54 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { MarkerType, EdgeMarker } from '../../types';
|
||||
|
||||
type SymbolProps = Omit<EdgeMarker, 'type'>;
|
||||
|
||||
const ArrowSymbol = ({ color = 'none', strokeWidth = 1 }: SymbolProps) => {
|
||||
return (
|
||||
<polyline
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={strokeWidth}
|
||||
fill="none"
|
||||
points="-5,-4 0,0 -5,4"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ArrowClosedSymbol = ({ color = 'none', strokeWidth = 1 }: SymbolProps) => {
|
||||
return (
|
||||
<polyline
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={strokeWidth}
|
||||
fill={color}
|
||||
points="-5,-4 0,0 -5,4 -5,-4"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const MarkerSymbols = {
|
||||
[MarkerType.Arrow]: ArrowSymbol,
|
||||
[MarkerType.ArrowClosed]: ArrowClosedSymbol,
|
||||
};
|
||||
|
||||
export function useMarkerSymbol(type: MarkerType) {
|
||||
const symbol = useMemo(() => {
|
||||
const symbolExists = MarkerSymbols.hasOwnProperty(type);
|
||||
|
||||
if (!symbolExists) {
|
||||
// @ts-ignore
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn(`[React Flow]: Marker type "${type}" doesn't exist. Help: https://reactflow.dev/error#900`);
|
||||
}
|
||||
return () => null;
|
||||
}
|
||||
|
||||
return MarkerSymbols[type];
|
||||
}, [type]);
|
||||
return symbol;
|
||||
}
|
||||
|
||||
export default MarkerSymbols;
|
||||
@@ -0,0 +1,221 @@
|
||||
import React, { memo, CSSProperties } from 'react';
|
||||
import shallow from 'zustand/shallow';
|
||||
import cc from 'classcat';
|
||||
|
||||
import { useStore } from '../../store';
|
||||
import ConnectionLine from '../../components/ConnectionLine/index';
|
||||
import MarkerDefinitions from './MarkerDefinitions';
|
||||
import { getEdgePositions, getHandle, getNodeData } from './utils';
|
||||
import useVisibleEdges from '../../hooks/useVisibleEdges';
|
||||
|
||||
import {
|
||||
Position,
|
||||
Edge,
|
||||
ConnectionLineType,
|
||||
ConnectionLineComponent,
|
||||
ConnectionMode,
|
||||
OnEdgeUpdateFunc,
|
||||
HandleType,
|
||||
ReactFlowState,
|
||||
EdgeTypesWrapped,
|
||||
} from '../../types';
|
||||
|
||||
interface EdgeRendererProps {
|
||||
edgeTypes: EdgeTypesWrapped;
|
||||
connectionLineType: ConnectionLineType;
|
||||
connectionLineStyle?: CSSProperties;
|
||||
connectionLineComponent?: ConnectionLineComponent;
|
||||
connectionLineContainerStyle?: CSSProperties;
|
||||
onEdgeClick?: (event: React.MouseEvent, node: Edge) => void;
|
||||
onEdgeDoubleClick?: (event: React.MouseEvent, edge: Edge) => void;
|
||||
defaultMarkerColor: string;
|
||||
onlyRenderVisibleElements: boolean;
|
||||
onEdgeUpdate?: OnEdgeUpdateFunc;
|
||||
onEdgeContextMenu?: (event: React.MouseEvent, edge: Edge) => void;
|
||||
onEdgeMouseEnter?: (event: React.MouseEvent, edge: Edge) => void;
|
||||
onEdgeMouseMove?: (event: React.MouseEvent, edge: Edge) => void;
|
||||
onEdgeMouseLeave?: (event: React.MouseEvent, edge: Edge) => void;
|
||||
onEdgeUpdateStart?: (event: React.MouseEvent, edge: Edge, handleType: HandleType) => void;
|
||||
onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge, handleType: HandleType) => void;
|
||||
edgeUpdaterRadius?: number;
|
||||
noPanClassName?: string;
|
||||
elevateEdgesOnSelect: boolean;
|
||||
rfId?: string;
|
||||
}
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
connectionNodeId: s.connectionNodeId,
|
||||
connectionHandleId: s.connectionHandleId,
|
||||
connectionHandleType: s.connectionHandleType,
|
||||
connectionPosition: s.connectionPosition,
|
||||
nodesConnectable: s.nodesConnectable,
|
||||
elementsSelectable: s.elementsSelectable,
|
||||
width: s.width,
|
||||
height: s.height,
|
||||
connectionMode: s.connectionMode,
|
||||
nodeInternals: s.nodeInternals,
|
||||
});
|
||||
|
||||
const EdgeRenderer = (props: EdgeRendererProps) => {
|
||||
const {
|
||||
connectionNodeId,
|
||||
connectionHandleId,
|
||||
connectionHandleType,
|
||||
connectionPosition,
|
||||
nodesConnectable,
|
||||
elementsSelectable,
|
||||
width,
|
||||
height,
|
||||
connectionMode,
|
||||
nodeInternals,
|
||||
} = useStore(selector, shallow);
|
||||
const edgeTree = useVisibleEdges(props.onlyRenderVisibleElements, nodeInternals, props.elevateEdgesOnSelect);
|
||||
|
||||
if (!width) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
connectionLineType,
|
||||
defaultMarkerColor,
|
||||
connectionLineStyle,
|
||||
connectionLineComponent,
|
||||
connectionLineContainerStyle,
|
||||
} = props;
|
||||
const renderConnectionLine = connectionNodeId && connectionHandleType;
|
||||
|
||||
return (
|
||||
<>
|
||||
{edgeTree.map(({ level, edges, isMaxLevel }) => (
|
||||
<svg
|
||||
key={level}
|
||||
style={{ zIndex: level }}
|
||||
width={width}
|
||||
height={height}
|
||||
className="react-flow__edges react-flow__container"
|
||||
>
|
||||
{isMaxLevel && <MarkerDefinitions defaultColor={defaultMarkerColor} rfId={props.rfId} />}
|
||||
<g>
|
||||
{edges.map((edge: Edge) => {
|
||||
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(nodeInternals, edge.source);
|
||||
const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(nodeInternals, edge.target);
|
||||
|
||||
if (!sourceIsValid || !targetIsValid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const edgeType = edge.type || 'default';
|
||||
const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default;
|
||||
// when connection type is loose we can define all handles as sources
|
||||
const targetNodeHandles =
|
||||
connectionMode === ConnectionMode.Strict
|
||||
? targetHandleBounds!.target
|
||||
: targetHandleBounds!.target || targetHandleBounds!.source;
|
||||
const sourceHandle = getHandle(sourceHandleBounds!.source!, edge.sourceHandle || null);
|
||||
const targetHandle = getHandle(targetNodeHandles!, edge.targetHandle || null);
|
||||
const sourcePosition = sourceHandle?.position || Position.Bottom;
|
||||
const targetPosition = targetHandle?.position || Position.Top;
|
||||
|
||||
if (!sourceHandle) {
|
||||
// @ts-ignore
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn(
|
||||
`[React Flow]: Couldn't create edge for source handle id: ${edge.sourceHandle}; edge id: ${edge.id}. Help: https://reactflow.dev/error#800`
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!targetHandle) {
|
||||
// @ts-ignore
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn(
|
||||
`[React Flow]: Couldn't create edge for target handle id: ${edge.targetHandle}; edge id: ${edge.id}. Help: https://reactflow.dev/error#800`
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(
|
||||
sourceNodeRect,
|
||||
sourceHandle,
|
||||
sourcePosition,
|
||||
targetNodeRect,
|
||||
targetHandle,
|
||||
targetPosition
|
||||
);
|
||||
|
||||
return (
|
||||
<EdgeComponent
|
||||
key={edge.id}
|
||||
id={edge.id}
|
||||
className={cc([edge.className, props.noPanClassName])}
|
||||
type={edgeType}
|
||||
data={edge.data}
|
||||
selected={!!edge.selected}
|
||||
animated={!!edge.animated}
|
||||
hidden={!!edge.hidden}
|
||||
label={edge.label}
|
||||
labelStyle={edge.labelStyle}
|
||||
labelShowBg={edge.labelShowBg}
|
||||
labelBgStyle={edge.labelBgStyle}
|
||||
labelBgPadding={edge.labelBgPadding}
|
||||
labelBgBorderRadius={edge.labelBgBorderRadius}
|
||||
style={edge.style}
|
||||
source={edge.source}
|
||||
target={edge.target}
|
||||
sourceHandleId={edge.sourceHandle}
|
||||
targetHandleId={edge.targetHandle}
|
||||
markerEnd={edge.markerEnd}
|
||||
markerStart={edge.markerStart}
|
||||
sourceX={sourceX}
|
||||
sourceY={sourceY}
|
||||
targetX={targetX}
|
||||
targetY={targetY}
|
||||
sourcePosition={sourcePosition}
|
||||
targetPosition={targetPosition}
|
||||
elementsSelectable={elementsSelectable}
|
||||
onEdgeUpdate={props.onEdgeUpdate}
|
||||
onContextMenu={props.onEdgeContextMenu}
|
||||
onMouseEnter={props.onEdgeMouseEnter}
|
||||
onMouseMove={props.onEdgeMouseMove}
|
||||
onMouseLeave={props.onEdgeMouseLeave}
|
||||
onClick={props.onEdgeClick}
|
||||
edgeUpdaterRadius={props.edgeUpdaterRadius}
|
||||
onEdgeDoubleClick={props.onEdgeDoubleClick}
|
||||
onEdgeUpdateStart={props.onEdgeUpdateStart}
|
||||
onEdgeUpdateEnd={props.onEdgeUpdateEnd}
|
||||
rfId={props.rfId}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</g>
|
||||
</svg>
|
||||
))}
|
||||
{renderConnectionLine && (
|
||||
<svg
|
||||
style={connectionLineContainerStyle}
|
||||
width={width}
|
||||
height={height}
|
||||
className="react-flow__edges react-flow__connectionline react-flow__container"
|
||||
>
|
||||
<ConnectionLine
|
||||
connectionNodeId={connectionNodeId!}
|
||||
connectionHandleId={connectionHandleId}
|
||||
connectionHandleType={connectionHandleType!}
|
||||
connectionPositionX={connectionPosition.x}
|
||||
connectionPositionY={connectionPosition.y}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
connectionLineType={connectionLineType}
|
||||
isConnectable={nodesConnectable}
|
||||
CustomConnectionLineComponent={connectionLineComponent}
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
EdgeRenderer.displayName = 'EdgeRenderer';
|
||||
|
||||
export default memo(EdgeRenderer);
|
||||
@@ -0,0 +1,191 @@
|
||||
import { ComponentType } from 'react';
|
||||
import { BezierEdge, SmoothStepEdge, StepEdge, StraightEdge, SimpleBezierEdge } from '../../components/Edges';
|
||||
import wrapEdge from '../../components/Edges/wrapEdge';
|
||||
import {
|
||||
EdgeProps,
|
||||
EdgeTypes,
|
||||
EdgeTypesWrapped,
|
||||
HandleElement,
|
||||
NodeHandleBounds,
|
||||
NodeInternals,
|
||||
Position,
|
||||
Rect,
|
||||
Transform,
|
||||
XYPosition,
|
||||
} from '../../types';
|
||||
import { internalsSymbol, rectToBox } from '../../utils';
|
||||
|
||||
export type CreateEdgeTypes = (edgeTypes: EdgeTypes) => EdgeTypesWrapped;
|
||||
|
||||
export function createEdgeTypes(edgeTypes: EdgeTypes): EdgeTypesWrapped {
|
||||
const standardTypes: EdgeTypesWrapped = {
|
||||
default: wrapEdge((edgeTypes.default || BezierEdge) as ComponentType<EdgeProps>),
|
||||
straight: wrapEdge((edgeTypes.bezier || StraightEdge) as ComponentType<EdgeProps>),
|
||||
step: wrapEdge((edgeTypes.step || StepEdge) as ComponentType<EdgeProps>),
|
||||
smoothstep: wrapEdge((edgeTypes.step || SmoothStepEdge) as ComponentType<EdgeProps>),
|
||||
simplebezier: wrapEdge((edgeTypes.simplebezier || SimpleBezierEdge) as ComponentType<EdgeProps>),
|
||||
};
|
||||
|
||||
const wrappedTypes = {} as EdgeTypesWrapped;
|
||||
const specialTypes: EdgeTypesWrapped = Object.keys(edgeTypes)
|
||||
.filter((k) => !['default', 'bezier'].includes(k))
|
||||
.reduce((res, key) => {
|
||||
res[key] = wrapEdge((edgeTypes[key] || BezierEdge) as ComponentType<EdgeProps>);
|
||||
|
||||
return res;
|
||||
}, wrappedTypes);
|
||||
|
||||
return {
|
||||
...standardTypes,
|
||||
...specialTypes,
|
||||
};
|
||||
}
|
||||
|
||||
export function getHandlePosition(position: Position, nodeRect: Rect, handle: any | null = null): XYPosition {
|
||||
const x = (handle?.x || 0) + nodeRect.x;
|
||||
const y = (handle?.y || 0) + nodeRect.y;
|
||||
const width = handle?.width || nodeRect.width;
|
||||
const height = handle?.height || nodeRect.height;
|
||||
|
||||
switch (position) {
|
||||
case Position.Top:
|
||||
return {
|
||||
x: x + width / 2,
|
||||
y,
|
||||
};
|
||||
case Position.Right:
|
||||
return {
|
||||
x: x + width,
|
||||
y: y + height / 2,
|
||||
};
|
||||
case Position.Bottom:
|
||||
return {
|
||||
x: x + width / 2,
|
||||
y: y + height,
|
||||
};
|
||||
case Position.Left:
|
||||
return {
|
||||
x,
|
||||
y: y + height / 2,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function getHandle(bounds: HandleElement[], handleId: string | null): HandleElement | null {
|
||||
if (!bounds) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// there is no handleId when there are no multiple handles/ handles with ids
|
||||
// so we just pick the first one
|
||||
let handle = null;
|
||||
if (bounds.length === 1 || !handleId) {
|
||||
handle = bounds[0];
|
||||
} else if (handleId) {
|
||||
handle = bounds.find((d) => d.id === handleId);
|
||||
}
|
||||
|
||||
return typeof handle === 'undefined' ? null : handle;
|
||||
}
|
||||
|
||||
interface EdgePositions {
|
||||
sourceX: number;
|
||||
sourceY: number;
|
||||
targetX: number;
|
||||
targetY: number;
|
||||
}
|
||||
|
||||
export const getEdgePositions = (
|
||||
sourceNodeRect: Rect,
|
||||
sourceHandle: HandleElement | unknown,
|
||||
sourcePosition: Position,
|
||||
targetNodeRect: Rect,
|
||||
targetHandle: HandleElement | unknown,
|
||||
targetPosition: Position
|
||||
): EdgePositions => {
|
||||
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNodeRect, sourceHandle);
|
||||
const targetHandlePos = getHandlePosition(targetPosition, targetNodeRect, targetHandle);
|
||||
|
||||
return {
|
||||
sourceX: sourceHandlePos.x,
|
||||
sourceY: sourceHandlePos.y,
|
||||
targetX: targetHandlePos.x,
|
||||
targetY: targetHandlePos.y,
|
||||
};
|
||||
};
|
||||
|
||||
interface IsEdgeVisibleParams {
|
||||
sourcePos: XYPosition;
|
||||
targetPos: XYPosition;
|
||||
sourceWidth: number;
|
||||
sourceHeight: number;
|
||||
targetWidth: number;
|
||||
targetHeight: number;
|
||||
width: number;
|
||||
height: number;
|
||||
transform: Transform;
|
||||
}
|
||||
|
||||
export function isEdgeVisible({
|
||||
sourcePos,
|
||||
targetPos,
|
||||
sourceWidth,
|
||||
sourceHeight,
|
||||
targetWidth,
|
||||
targetHeight,
|
||||
width,
|
||||
height,
|
||||
transform,
|
||||
}: IsEdgeVisibleParams): boolean {
|
||||
const edgeBox = {
|
||||
x: Math.min(sourcePos.x, targetPos.x),
|
||||
y: Math.min(sourcePos.y, targetPos.y),
|
||||
x2: Math.max(sourcePos.x + sourceWidth, targetPos.x + targetWidth),
|
||||
y2: Math.max(sourcePos.y + sourceHeight, targetPos.y + targetHeight),
|
||||
};
|
||||
|
||||
if (edgeBox.x === edgeBox.x2) {
|
||||
edgeBox.x2 += 1;
|
||||
}
|
||||
|
||||
if (edgeBox.y === edgeBox.y2) {
|
||||
edgeBox.y2 += 1;
|
||||
}
|
||||
|
||||
const viewBox = rectToBox({
|
||||
x: (0 - transform[0]) / transform[2],
|
||||
y: (0 - transform[1]) / transform[2],
|
||||
width: width / transform[2],
|
||||
height: height / transform[2],
|
||||
});
|
||||
|
||||
const xOverlap = Math.max(0, Math.min(viewBox.x2, edgeBox.x2) - Math.max(viewBox.x, edgeBox.x));
|
||||
const yOverlap = Math.max(0, Math.min(viewBox.y2, edgeBox.y2) - Math.max(viewBox.y, edgeBox.y));
|
||||
const overlappingArea = Math.ceil(xOverlap * yOverlap);
|
||||
|
||||
return overlappingArea > 0;
|
||||
}
|
||||
|
||||
export function getNodeData(nodeInternals: NodeInternals, nodeId: string): [Rect, NodeHandleBounds | null, boolean] {
|
||||
const node = nodeInternals.get(nodeId);
|
||||
const handleBounds = node?.[internalsSymbol]?.handleBounds || null;
|
||||
|
||||
const isInvalid =
|
||||
!node ||
|
||||
!handleBounds ||
|
||||
!node.width ||
|
||||
!node.height ||
|
||||
typeof node.positionAbsolute?.x === 'undefined' ||
|
||||
typeof node.positionAbsolute?.y === 'undefined';
|
||||
|
||||
return [
|
||||
{
|
||||
x: node?.positionAbsolute?.x || 0,
|
||||
y: node?.positionAbsolute?.y || 0,
|
||||
width: node?.width || 0,
|
||||
height: node?.height || 0,
|
||||
},
|
||||
handleBounds,
|
||||
!isInvalid,
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user