refactor(connectionline): separate edge renderer and connectionline

This commit is contained in:
moklick
2023-01-25 17:47:13 +01:00
parent fe0f59adf4
commit 3d6937b3e4
7 changed files with 152 additions and 159 deletions
@@ -1,27 +1,19 @@
import { memo } from 'react';
import { memo, ReactNode } from 'react';
import { shallow } from 'zustand/shallow';
import cc from 'classcat';
import { useStore } from '../../hooks/useStore';
import useVisibleEdges from '../../hooks/useVisibleEdges';
import ConnectionLine from '../../components/ConnectionLine/index';
import MarkerDefinitions from './MarkerDefinitions';
import { getEdgePositions, getHandle, getNodeData } from './utils';
import { GraphViewProps } from '../GraphView';
import { devWarn } from '../../utils';
import { ConnectionMode, Position } from '../../types';
import type { Edge, ReactFlowState } from '../../types';
type EdgeRendererProps = Pick<
GraphViewProps,
| 'edgeTypes'
| 'connectionLineType'
| 'connectionLineType'
| 'connectionLineStyle'
| 'connectionLineComponent'
| 'connectionLineContainerStyle'
| 'connectionLineContainerStyle'
| 'onEdgeClick'
| 'onEdgeDoubleClick'
| 'defaultMarkerColor'
@@ -40,11 +32,10 @@ type EdgeRendererProps = Pick<
| 'disableKeyboardA11y'
> & {
elevateEdgesOnSelect: boolean;
children: ReactNode;
};
const selector = (s: ReactFlowState) => ({
connectionNodeId: s.connectionNodeId,
connectionHandleType: s.connectionHandleType,
nodesConnectable: s.nodesConnectable,
edgesFocusable: s.edgesFocusable,
elementsSelectable: s.elementsSelectable,
@@ -52,35 +43,38 @@ const selector = (s: ReactFlowState) => ({
height: s.height,
connectionMode: s.connectionMode,
nodeInternals: s.nodeInternals,
onError: s.onError,
});
const EdgeRenderer = (props: EdgeRendererProps) => {
const {
connectionNodeId,
connectionHandleType,
nodesConnectable,
edgesFocusable,
elementsSelectable,
width,
height,
connectionMode,
nodeInternals,
} = useStore(selector, shallow);
const edgeTree = useVisibleEdges(props.onlyRenderVisibleElements, nodeInternals, props.elevateEdgesOnSelect);
const EdgeRenderer = ({
defaultMarkerColor,
onlyRenderVisibleElements,
elevateEdgesOnSelect,
rfId,
edgeTypes,
noPanClassName,
onEdgeUpdate,
onEdgeContextMenu,
onEdgeMouseEnter,
onEdgeMouseMove,
onEdgeMouseLeave,
onEdgeClick,
edgeUpdaterRadius,
onEdgeDoubleClick,
onEdgeUpdateStart,
onEdgeUpdateEnd,
children,
}: EdgeRendererProps) => {
const { edgesFocusable, elementsSelectable, width, height, connectionMode, nodeInternals, onError } = useStore(
selector,
shallow
);
const edgeTree = useVisibleEdges(onlyRenderVisibleElements, nodeInternals, elevateEdgesOnSelect);
if (!width) {
return null;
}
const {
connectionLineType,
defaultMarkerColor,
connectionLineStyle,
connectionLineComponent,
connectionLineContainerStyle,
} = props;
const renderConnectionLine = connectionNodeId && connectionHandleType;
return (
<>
{edgeTree.map(({ level, edges, isMaxLevel }) => (
@@ -91,11 +85,11 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
height={height}
className="react-flow__edges react-flow__container"
>
{isMaxLevel && <MarkerDefinitions defaultColor={defaultMarkerColor} rfId={props.rfId} />}
{isMaxLevel && <MarkerDefinitions defaultColor={defaultMarkerColor} rfId={rfId} />}
<g>
{edges.map((edge: Edge) => {
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(nodeInternals.get(edge.source)!);
const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(nodeInternals.get(edge.target)!);
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(nodeInternals.get(edge.source));
const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(nodeInternals.get(edge.target));
if (!sourceIsValid || !targetIsValid) {
return null;
@@ -103,31 +97,29 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
let edgeType = edge.type || 'default';
if (!props.edgeTypes[edgeType]) {
devWarn(
`Edge type "${edgeType}" not found. Using fallback type "default". Help: https://reactflow.dev/error#300`
);
if (!edgeTypes[edgeType]) {
onError?.('003', `Edge type "${edgeType}" not found. Using fallback type "default".`);
edgeType = 'default';
}
const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default;
// when connection type is loose we can define all handles as sources
const EdgeComponent = edgeTypes[edgeType] || edgeTypes.default;
// when connection type is loose we can define all handles as sources and connect source -> source
const targetNodeHandles =
connectionMode === ConnectionMode.Strict
? targetHandleBounds!.target
: (targetHandleBounds!.target ?? []).concat(targetHandleBounds!.source ?? []);
const sourceHandle = getHandle(sourceHandleBounds!.source!, edge.sourceHandle || null);
const targetHandle = getHandle(targetNodeHandles!, edge.targetHandle || null);
const sourceHandle = getHandle(sourceHandleBounds!.source!, edge.sourceHandle);
const targetHandle = getHandle(targetNodeHandles!, edge.targetHandle);
const sourcePosition = sourceHandle?.position || Position.Bottom;
const targetPosition = targetHandle?.position || Position.Top;
const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined'));
if (!sourceHandle || !targetHandle) {
devWarn(
`Couldn't create edge for ${!sourceHandle ? 'source' : 'target'} handle id: ${
onError?.(
'008',
`Couldn't create edge for ${!sourceHandle ? 'source' : 'target'} handle id: "${
!sourceHandle ? edge.sourceHandle : edge.targetHandle
}; edge id: ${edge.id}. Help: https://reactflow.dev/error#800`
}", edge id: ${edge.id}.`
);
return null;
@@ -146,7 +138,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
<EdgeComponent
key={edge.id}
id={edge.id}
className={cc([edge.className, props.noPanClassName])}
className={cc([edge.className, noPanClassName])}
type={edgeType}
data={edge.data}
selected={!!edge.selected}
@@ -172,17 +164,17 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
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}
onEdgeUpdate={onEdgeUpdate}
onContextMenu={onEdgeContextMenu}
onMouseEnter={onEdgeMouseEnter}
onMouseMove={onEdgeMouseMove}
onMouseLeave={onEdgeMouseLeave}
onClick={onEdgeClick}
edgeUpdaterRadius={edgeUpdaterRadius}
onEdgeDoubleClick={onEdgeDoubleClick}
onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd}
rfId={rfId}
ariaLabel={edge.ariaLabel}
isFocusable={isFocusable}
pathOptions={'pathOptions' in edge ? edge.pathOptions : undefined}
@@ -193,23 +185,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
</g>
</svg>
))}
{renderConnectionLine && (
<svg
style={connectionLineContainerStyle}
width={width}
height={height}
className="react-flow__edges react-flow__connectionline react-flow__container"
>
<ConnectionLine
connectionNodeId={connectionNodeId!}
connectionHandleType={connectionHandleType!}
connectionLineStyle={connectionLineStyle}
connectionLineType={connectionLineType}
isConnectable={nodesConnectable}
CustomConnectionLineComponent={connectionLineComponent}
/>
</svg>
)}
{children}
</>
);
};
@@ -72,21 +72,18 @@ export function getHandlePosition(position: Position, nodeRect: Rect, handle: Ha
}
}
export function getHandle(bounds: HandleElement[], handleId: string | null): HandleElement | null {
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: HandleElement | null = null;
if (bounds.length === 1 || !handleId) {
handle = bounds[0];
} else if (handleId) {
handle = bounds.find((d) => d.id === handleId)!;
if (handleId) {
return bounds.find((d) => d.id === handleId)!;
} else if (bounds.length === 1) {
return bounds[0];
}
return typeof handle === 'undefined' ? null : handle;
return null;
}
interface EdgePositions {
@@ -167,16 +164,15 @@ export function isEdgeVisible({
return overlappingArea > 0;
}
export function getNodeData(node: Node): [Rect, NodeHandleBounds | null, boolean] {
export function getNodeData(node?: Node): [Rect, NodeHandleBounds | null, boolean] {
const handleBounds = node?.[internalsSymbol]?.handleBounds || null;
const isInvalid =
!node ||
!handleBounds ||
!node.width ||
!node.height ||
typeof node.positionAbsolute?.x === 'undefined' ||
typeof node.positionAbsolute?.y === 'undefined';
const isValid =
handleBounds &&
node?.width &&
node?.height &&
typeof node?.positionAbsolute?.x !== 'undefined' &&
typeof node?.positionAbsolute?.y !== 'undefined';
return [
{
@@ -186,6 +182,6 @@ export function getNodeData(node: Node): [Rect, NodeHandleBounds | null, boolean
height: node?.height || 0,
},
handleBounds,
!isInvalid,
!!isValid,
];
}