feat(connectionline): custom component option #516

This commit is contained in:
moklick
2020-09-19 13:38:40 +02:00
parent 43e9461776
commit 1f7d88fd5e
10 changed files with 142 additions and 24 deletions
+1
View File
@@ -123,6 +123,7 @@ const BasicFlow = () => <ReactFlow elements={elements} />;
#### Connection Line Options #### Connection Line Options
- `connectionLineType`: connection line type = `default` (bezier), `straight`, `step`, `smoothstep` - `connectionLineType`: connection line type = `default` (bezier), `straight`, `step`, `smoothstep`
- `connectionLineStyle`: connection style as svg attributes - `connectionLineStyle`: connection style as svg attributes
- `connectionLineComponent`: [custom connection line component](/example/src/CustomConnectionLin/index.js)
#### Keys #### Keys
- `deleteKeyCode`: default: `8` (delete) - `deleteKeyCode`: default: `8` (delete)
@@ -0,0 +1,25 @@
import React from 'react';
export default ({
sourceX,
sourceY,
sourcePosition,
targetX,
targetY,
targetPosition,
connectionLineType,
connectionLineStyle,
}) => {
return (
<g>
<path
fill="none"
stroke="#222"
strokeWidth={1.5}
className="animated"
d={`M${sourceX},${sourceY} C ${sourceX} ${targetY} ${sourceX} ${targetY} ${targetX},${targetY}`}
/>
<circle cx={targetX} cy={targetY} fill="#fff" r={3} stroke="#222" strokeWidth={1.5} />
</g>
);
};
+25
View File
@@ -0,0 +1,25 @@
import React, { useState } from 'react';
import ReactFlow, { removeElements, addEdge, Background } from 'react-flow-renderer';
import ConnectionLine from './ConnectionLine';
const initialElements = [{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } }];
const ConnectionLineFlow = () => {
const [elements, setElements] = useState(initialElements);
const onElementsRemove = (elementsToRemove) => setElements((els) => removeElements(elementsToRemove, els));
const onConnect = (params) => setElements((els) => addEdge(params, els));
return (
<ReactFlow
elements={elements}
connectionLineComponent={ConnectionLine}
onElementsRemove={onElementsRemove}
onConnect={onConnect}
>
<Background variant="lines" />
</ReactFlow>
);
};
export default ConnectionLineFlow;
+5
View File
@@ -14,6 +14,7 @@ import Horizontal from './Horizontal';
import Provider from './Provider'; import Provider from './Provider';
import Hidden from './Hidden'; import Hidden from './Hidden';
import EdgeTypes from './EdgeTypes'; import EdgeTypes from './EdgeTypes';
import CustomConnectionLine from './CustomConnectionLine';
import './index.css'; import './index.css';
@@ -74,6 +75,10 @@ const routes = [
path: '/edge-types', path: '/edge-types',
component: EdgeTypes, component: EdgeTypes,
}, },
{
path: '/custom-connectionline',
component: CustomConnectionLine,
},
]; ];
const navLinks = routes.filter((route) => route.label); const navLinks = routes.filter((route) => route.label);
+34 -11
View File
@@ -1,9 +1,17 @@
import React, { useEffect, useState, CSSProperties } from 'react'; import React, { useEffect, useState, CSSProperties } from 'react';
import cc from 'classcat';
import { getBezierPath } from '../Edges/BezierEdge'; import { getBezierPath } from '../Edges/BezierEdge';
import { getSmoothStepPath } from '../Edges/SmoothStepEdge'; import { getSmoothStepPath } from '../Edges/SmoothStepEdge';
import { ElementId, Node, Transform, HandleElement, Position, ConnectionLineType, HandleType } from '../../types'; import {
ElementId,
Node,
Transform,
HandleElement,
Position,
ConnectionLineType,
ConnectionLineComponent,
HandleType,
} from '../../types';
interface ConnectionLineProps { interface ConnectionLineProps {
connectionNodeId: ElementId; connectionNodeId: ElementId;
@@ -15,7 +23,7 @@ interface ConnectionLineProps {
transform: Transform; transform: Transform;
isConnectable: boolean; isConnectable: boolean;
connectionLineStyle?: CSSProperties; connectionLineStyle?: CSSProperties;
className?: string; CustomConnectionLineComponent?: ConnectionLineComponent;
} }
export default ({ export default ({
@@ -26,9 +34,9 @@ export default ({
connectionPositionY, connectionPositionY,
connectionLineType = ConnectionLineType.Bezier, connectionLineType = ConnectionLineType.Bezier,
nodes = [], nodes = [],
className,
transform, transform,
isConnectable, isConnectable,
CustomConnectionLineComponent,
}: ConnectionLineProps) => { }: ConnectionLineProps) => {
const [sourceNode, setSourceNode] = useState<Node | null>(null); const [sourceNode, setSourceNode] = useState<Node | null>(null);
const hasHandleId = connectionNodeId.includes('__'); const hasHandleId = connectionNodeId.includes('__');
@@ -45,8 +53,6 @@ export default ({
return null; return null;
} }
const connectionLineClasses: string = cc(['react-flow__connection', className]);
const sourceHandle = handleId const sourceHandle = handleId
? sourceNode.__rf.handleBounds[connectionHandleType].find((d: HandleElement) => d.id === handleId) ? sourceNode.__rf.handleBounds[connectionHandleType].find((d: HandleElement) => d.id === handleId)
: sourceNode.__rf.handleBounds[connectionHandleType][0]; : sourceNode.__rf.handleBounds[connectionHandleType][0];
@@ -55,14 +61,31 @@ export default ({
const sourceX = sourceNode.__rf.position.x + sourceHandleX; const sourceX = sourceNode.__rf.position.x + sourceHandleX;
const sourceY = sourceNode.__rf.position.y + sourceHandleY; const sourceY = sourceNode.__rf.position.y + sourceHandleY;
const targetX = (connectionPositionX - transform[0]) * (1 / transform[2]); const targetX = (connectionPositionX - transform[0]) / transform[2];
const targetY = (connectionPositionY - transform[1]) * (1 / transform[2]); const targetY = (connectionPositionY - transform[1]) / transform[2];
let dAttr: string = '';
const isRightOrLeft = sourceHandle?.position === Position.Left || sourceHandle?.position === Position.Right; const isRightOrLeft = sourceHandle?.position === Position.Left || sourceHandle?.position === Position.Right;
const targetPosition = isRightOrLeft ? Position.Left : Position.Top; const targetPosition = isRightOrLeft ? Position.Left : Position.Top;
if (CustomConnectionLineComponent) {
return (
<g className="react-flow__connection">
<CustomConnectionLineComponent
sourceX={sourceX}
sourceY={sourceY}
sourcePosition={sourceHandle?.position}
targetX={targetX}
targetY={targetY}
targetPosition={targetPosition}
connectionLineType={connectionLineType}
connectionLineStyle={connectionLineStyle}
/>
</g>
);
}
let dAttr: string = '';
if (connectionLineType === ConnectionLineType.Bezier) { if (connectionLineType === ConnectionLineType.Bezier) {
dAttr = getBezierPath({ dAttr = getBezierPath({
sourceX, sourceX,
@@ -96,7 +119,7 @@ export default ({
} }
return ( return (
<g className={connectionLineClasses}> <g className="react-flow__connection">
<path d={dAttr} className="react-flow__connection-path" style={connectionLineStyle} /> <path d={dAttr} className="react-flow__connection-path" style={connectionLineStyle} />
</g> </g>
); );
+24 -12
View File
@@ -4,7 +4,17 @@ import { useStoreState } from '../../store/hooks';
import ConnectionLine from '../../components/ConnectionLine/index'; import ConnectionLine from '../../components/ConnectionLine/index';
import { isEdge } from '../../utils/graph'; import { isEdge } from '../../utils/graph';
import MarkerDefinitions from './MarkerDefinitions'; import MarkerDefinitions from './MarkerDefinitions';
import { XYPosition, Position, Edge, Node, ElementId, HandleElement, Elements, ConnectionLineType } from '../../types'; import {
XYPosition,
Position,
Edge,
Node,
ElementId,
HandleElement,
Elements,
ConnectionLineType,
ConnectionLineComponent,
} from '../../types';
interface EdgeRendererProps { interface EdgeRendererProps {
edgeTypes: any; edgeTypes: any;
@@ -13,6 +23,7 @@ interface EdgeRendererProps {
onElementClick?: (event: React.MouseEvent, element: Node | Edge) => void; onElementClick?: (event: React.MouseEvent, element: Node | Edge) => void;
arrowHeadColor: string; arrowHeadColor: string;
markerEndId?: string; markerEndId?: string;
connectionLineComponent?: ConnectionLineComponent;
} }
interface EdgePositions { interface EdgePositions {
@@ -193,19 +204,19 @@ function renderEdge(
} }
const EdgeRenderer = (props: EdgeRendererProps) => { const EdgeRenderer = (props: EdgeRendererProps) => {
const [tX, tY, tScale] = useStoreState((s) => s.transform); const [tX, tY, tScale] = useStoreState((state) => state.transform);
const edges = useStoreState((s) => s.edges); const edges = useStoreState((state) => state.edges);
const nodes = useStoreState((s) => s.nodes); const nodes = useStoreState((state) => state.nodes);
const connectionNodeId = useStoreState((s) => s.connectionNodeId); const connectionNodeId = useStoreState((state) => state.connectionNodeId);
const connectionHandleType = useStoreState((s) => s.connectionHandleType); const connectionHandleType = useStoreState((state) => state.connectionHandleType);
const connectionPosition = useStoreState((s) => s.connectionPosition); const connectionPosition = useStoreState((state) => state.connectionPosition);
const selectedElements = useStoreState((s) => s.selectedElements); const selectedElements = useStoreState((state) => state.selectedElements);
const nodesConnectable = useStoreState((s) => s.nodesConnectable); const nodesConnectable = useStoreState((state) => state.nodesConnectable);
const elementsSelectable = useStoreState((s) => s.elementsSelectable); const elementsSelectable = useStoreState((state) => state.elementsSelectable);
const width = useStoreState((state) => state.width); const width = useStoreState((state) => state.width);
const height = useStoreState((state) => state.height); const height = useStoreState((state) => state.height);
const { connectionLineStyle, connectionLineType, arrowHeadColor } = props; const { connectionLineStyle, connectionLineType, arrowHeadColor, connectionLineComponent } = props;
if (!width) { if (!width) {
return null; return null;
@@ -218,7 +229,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
<svg width={width} height={height} className="react-flow__edges"> <svg width={width} height={height} className="react-flow__edges">
<MarkerDefinitions color={arrowHeadColor} /> <MarkerDefinitions color={arrowHeadColor} />
<g transform={transformStyle}> <g transform={transformStyle}>
{edges.map((edge) => renderEdge(edge, props, nodes, selectedElements, elementsSelectable))} {edges.map((edge: Edge) => renderEdge(edge, props, nodes, selectedElements, elementsSelectable))}
{renderConnectionLine && ( {renderConnectionLine && (
<ConnectionLine <ConnectionLine
nodes={nodes} nodes={nodes}
@@ -230,6 +241,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
connectionLineStyle={connectionLineStyle} connectionLineStyle={connectionLineStyle}
connectionLineType={connectionLineType} connectionLineType={connectionLineType}
isConnectable={nodesConnectable} isConnectable={nodesConnectable}
CustomConnectionLineComponent={connectionLineComponent}
/> />
)} )}
</g> </g>
+4
View File
@@ -20,6 +20,7 @@ import {
Edge, Edge,
Connection, Connection,
ConnectionLineType, ConnectionLineType,
ConnectionLineComponent,
FlowTransform, FlowTransform,
OnConnectStartFunc, OnConnectStartFunc,
OnConnectStopFunc, OnConnectStopFunc,
@@ -57,6 +58,7 @@ export interface GraphViewProps {
edgeTypes: EdgeTypesType; edgeTypes: EdgeTypesType;
connectionLineType: ConnectionLineType; connectionLineType: ConnectionLineType;
connectionLineStyle?: CSSProperties; connectionLineStyle?: CSSProperties;
connectionLineComponent?: ConnectionLineComponent;
deleteKeyCode: number; deleteKeyCode: number;
snapToGrid: boolean; snapToGrid: boolean;
snapGrid: [number, number]; snapGrid: [number, number];
@@ -97,6 +99,7 @@ const GraphView = ({
onSelectionContextMenu, onSelectionContextMenu,
connectionLineType, connectionLineType,
connectionLineStyle, connectionLineStyle,
connectionLineComponent,
selectionKeyCode, selectionKeyCode,
onElementsRemove, onElementsRemove,
deleteKeyCode, deleteKeyCode,
@@ -289,6 +292,7 @@ const GraphView = ({
connectionLineStyle={connectionLineStyle} connectionLineStyle={connectionLineStyle}
arrowHeadColor={arrowHeadColor} arrowHeadColor={arrowHeadColor}
markerEndId={markerEndId} markerEndId={markerEndId}
connectionLineComponent={connectionLineComponent}
/> />
<UserSelection selectionKeyPressed={selectionKeyPressed} /> <UserSelection selectionKeyPressed={selectionKeyPressed} />
{nodesSelectionActive && ( {nodesSelectionActive && (
+4
View File
@@ -26,6 +26,7 @@ import {
Edge, Edge,
Connection, Connection,
ConnectionLineType, ConnectionLineType,
ConnectionLineComponent,
FlowTransform, FlowTransform,
OnConnectStartFunc, OnConnectStartFunc,
OnConnectStopFunc, OnConnectStopFunc,
@@ -65,6 +66,7 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
edgeTypes: EdgeTypesType; edgeTypes: EdgeTypesType;
connectionLineType: ConnectionLineType; connectionLineType: ConnectionLineType;
connectionLineStyle?: CSSProperties; connectionLineStyle?: CSSProperties;
connectionLineComponent?: ConnectionLineComponent;
deleteKeyCode: number; deleteKeyCode: number;
selectionKeyCode: number; selectionKeyCode: number;
snapToGrid: boolean; snapToGrid: boolean;
@@ -116,6 +118,7 @@ const ReactFlow = ({
onSelectionContextMenu, onSelectionContextMenu,
connectionLineType, connectionLineType,
connectionLineStyle, connectionLineStyle,
connectionLineComponent,
deleteKeyCode, deleteKeyCode,
selectionKeyCode, selectionKeyCode,
snapToGrid, snapToGrid,
@@ -162,6 +165,7 @@ const ReactFlow = ({
edgeTypes={edgeTypesParsed} edgeTypes={edgeTypesParsed}
connectionLineType={connectionLineType} connectionLineType={connectionLineType}
connectionLineStyle={connectionLineStyle} connectionLineStyle={connectionLineStyle}
connectionLineComponent={connectionLineComponent}
selectionKeyCode={selectionKeyCode} selectionKeyCode={selectionKeyCode}
onElementsRemove={onElementsRemove} onElementsRemove={onElementsRemove}
deleteKeyCode={deleteKeyCode} deleteKeyCode={deleteKeyCode}
+7 -1
View File
@@ -78,6 +78,11 @@
.react-flow__connection { .react-flow__connection {
pointer-events: none; pointer-events: none;
.animated {
stroke-dasharray: 5;
animation: dashdraw 0.5s linear infinite;
}
} }
.react-flow__connection-path { .react-flow__connection-path {
@@ -117,6 +122,7 @@
.react-flow__node-default.selectable, .react-flow__node-default.selectable,
.react-flow__node-input.selectable, .react-flow__node-input.selectable,
.react-flow__node-output.selectable { .react-flow__node-output.selectable {
&.selected, &.selected,
&.selected:hover { &.selected:hover {
box-shadow: 0 0 0 1px #333; box-shadow: 0 0 0 1px #333;
@@ -193,4 +199,4 @@
right: 0; right: 0;
top: 50%; top: 50%;
transform: translate(0, -50%); transform: translate(0, -50%);
} }
+13
View File
@@ -248,6 +248,19 @@ export enum ConnectionLineType {
SmoothStep = 'smoothstep', SmoothStep = 'smoothstep',
} }
export type ConnectionLineComponentProps = {
sourceX: number;
sourceY: number;
sourcePosition?: Position;
targetX: number;
targetY: number;
targetPosition?: Position;
connectionLineStyle?: CSSProperties;
connectionLineType: ConnectionLineType;
};
export type ConnectionLineComponent = React.ComponentType<ConnectionLineComponentProps>;
export type OnConnectFunc = (connection: Connection) => void; export type OnConnectFunc = (connection: Connection) => void;
export type OnConnectStartParams = { export type OnConnectStartParams = {
nodeId: ElementId | null; nodeId: ElementId | null;