fix(connectionline): draw line from handle where user starts closes #270

This commit is contained in:
moklick
2020-06-01 23:14:14 +02:00
parent 08948df140
commit ce32d34c6b
6 changed files with 41 additions and 30 deletions
+9 -11
View File
@@ -1,10 +1,11 @@
import React, { useEffect, useState, CSSProperties } from 'react'; import React, { useEffect, useState, CSSProperties } from 'react';
import cx from 'classnames'; import cx from 'classnames';
import { ElementId, Node, Transform, HandleElement, Position, ConnectionLineType } from '../../types'; import { ElementId, Node, Transform, HandleElement, Position, ConnectionLineType, HandleType } from '../../types';
interface ConnectionLineProps { interface ConnectionLineProps {
connectionSourceId: ElementId; connectionNodeId: ElementId;
connectionHandleType: HandleType;
connectionPositionX: number; connectionPositionX: number;
connectionPositionY: number; connectionPositionY: number;
connectionLineType: ConnectionLineType; connectionLineType: ConnectionLineType;
@@ -16,7 +17,8 @@ interface ConnectionLineProps {
} }
export default ({ export default ({
connectionSourceId, connectionNodeId,
connectionHandleType,
connectionLineStyle, connectionLineStyle,
connectionPositionX, connectionPositionX,
connectionPositionY, connectionPositionY,
@@ -27,8 +29,8 @@ export default ({
isInteractive, isInteractive,
}: ConnectionLineProps) => { }: ConnectionLineProps) => {
const [sourceNode, setSourceNode] = useState<Node | null>(null); const [sourceNode, setSourceNode] = useState<Node | null>(null);
const hasHandleId = connectionSourceId.includes('__'); const hasHandleId = connectionNodeId.includes('__');
const sourceIdSplitted = connectionSourceId.split('__'); const sourceIdSplitted = connectionNodeId.split('__');
const nodeId = sourceIdSplitted[0]; const nodeId = sourceIdSplitted[0];
const handleId = hasHandleId ? sourceIdSplitted[1] : null; const handleId = hasHandleId ? sourceIdSplitted[1] : null;
@@ -42,14 +44,10 @@ export default ({
} }
const connectionLineClasses: string = cx('react-flow__connection', className); const connectionLineClasses: string = cx('react-flow__connection', className);
const hasSource = sourceNode.__rg.handleBounds.source !== null;
// output nodes don't have source handles so we need to use the target one
const handleKey = hasSource ? 'source' : 'target';
const sourceHandle = handleId const sourceHandle = handleId
? sourceNode.__rg.handleBounds[handleKey].find((d: HandleElement) => d.id === handleId) ? sourceNode.__rg.handleBounds[connectionHandleType].find((d: HandleElement) => d.id === handleId)
: sourceNode.__rg.handleBounds[handleKey][0]; : sourceNode.__rg.handleBounds[connectionHandleType][0];
const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : sourceNode.__rg.width / 2; const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : sourceNode.__rg.width / 2;
const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : sourceNode.__rg.height; const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : sourceNode.__rg.height;
const sourceX = sourceNode.__rg.position.x + sourceHandleX; const sourceX = sourceNode.__rg.position.x + sourceHandleX;
+9 -9
View File
@@ -1,17 +1,17 @@
import React, { memo, MouseEvent as ReactMouseEvent, CSSProperties } from 'react'; import React, { memo, MouseEvent as ReactMouseEvent, CSSProperties } from 'react';
import cx from 'classnames'; import cx from 'classnames';
import { HandleType, ElementId, Position, XYPosition, OnConnectFunc, Connection } from '../../types'; import { HandleType, ElementId, Position, XYPosition, OnConnectFunc, Connection, SetConnectionId } from '../../types';
type ValidConnectionFunc = (connection: Connection) => boolean; type ValidConnectionFunc = (connection: Connection) => boolean;
type SetSourceIdFunc = (nodeId: ElementId | null) => void; type SetSourceIdFunc = (params: SetConnectionId) => void;
interface BaseHandleProps { interface BaseHandleProps {
type: HandleType; type: HandleType;
nodeId: ElementId; nodeId: ElementId;
onConnect: OnConnectFunc; onConnect: OnConnectFunc;
position: Position; position: Position;
setSourceId: SetSourceIdFunc; setConnectionNodeId: SetSourceIdFunc;
setPosition: (pos: XYPosition) => void; setPosition: (pos: XYPosition) => void;
isValidConnection: ValidConnectionFunc; isValidConnection: ValidConnectionFunc;
id?: ElementId | boolean; id?: ElementId | boolean;
@@ -29,8 +29,8 @@ type Result = {
function onMouseDown( function onMouseDown(
evt: ReactMouseEvent, evt: ReactMouseEvent,
nodeId: ElementId, nodeId: ElementId,
setSourceId: SetSourceIdFunc, setConnectionNodeId: SetSourceIdFunc,
setPosition: (pos: XYPosition) => any, setPosition: (pos: XYPosition) => void,
onConnect: OnConnectFunc, onConnect: OnConnectFunc,
isTarget: boolean, isTarget: boolean,
isValidConnection: ValidConnectionFunc isValidConnection: ValidConnectionFunc
@@ -48,7 +48,7 @@ function onMouseDown(
x: evt.clientX - containerBounds.left, x: evt.clientX - containerBounds.left,
y: evt.clientY - containerBounds.top, y: evt.clientY - containerBounds.top,
}); });
setSourceId(nodeId); setConnectionNodeId({ connectionNodeId: nodeId, connectionHandleType: isTarget ? 'target' : 'source' });
function resetRecentHandle(): void { function resetRecentHandle(): void {
if (!recentHoveredHandle) { if (!recentHoveredHandle) {
@@ -120,7 +120,7 @@ function onMouseDown(
} }
resetRecentHandle(); resetRecentHandle();
setSourceId(null); setConnectionNodeId({ connectionNodeId: null, connectionHandleType: null });
document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp); document.removeEventListener('mouseup', onMouseUp);
@@ -136,7 +136,7 @@ const BaseHandle = memo(
nodeId, nodeId,
onConnect, onConnect,
position, position,
setSourceId, setConnectionNodeId,
setPosition, setPosition,
className, className,
id = false, id = false,
@@ -157,7 +157,7 @@ const BaseHandle = memo(
data-handlepos={position} data-handlepos={position}
className={handleClasses} className={handleClasses}
onMouseDown={(evt) => onMouseDown={(evt) =>
onMouseDown(evt, nodeIdWithHandleId, setSourceId, setPosition, onConnect, isTarget, isValidConnection) onMouseDown(evt, nodeIdWithHandleId, setConnectionNodeId, setPosition, onConnect, isTarget, isValidConnection)
} }
{...rest} {...rest}
/> />
+2 -2
View File
@@ -18,7 +18,7 @@ const Handle = memo(
}: HandleProps) => { }: HandleProps) => {
const nodeId = useContext(NodeIdContext) as ElementId; const nodeId = useContext(NodeIdContext) as ElementId;
const setPosition = useStoreActions((a) => a.setConnectionPosition); const setPosition = useStoreActions((a) => a.setConnectionPosition);
const setSourceId = useStoreActions((a) => a.setConnectionSourceId); const setConnectionNodeId = useStoreActions((a) => a.setConnectionNodeId);
const onConnectAction = useStoreState((s) => s.onConnect); const onConnectAction = useStoreState((s) => s.onConnect);
const onConnectExtended = (params: Connection) => { const onConnectExtended = (params: Connection) => {
onConnectAction(params); onConnectAction(params);
@@ -29,7 +29,7 @@ const Handle = memo(
<BaseHandle <BaseHandle
nodeId={nodeId} nodeId={nodeId}
setPosition={setPosition} setPosition={setPosition}
setSourceId={setSourceId} setConnectionNodeId={setConnectionNodeId}
onConnect={onConnectExtended} onConnect={onConnectExtended}
type={type} type={type}
position={position} position={position}
+6 -3
View File
@@ -190,7 +190,8 @@ const EdgeRenderer = memo((props: EdgeRendererProps) => {
const [tX, tY, tScale] = useStoreState((s) => s.transform); const [tX, tY, tScale] = useStoreState((s) => s.transform);
const edges = useStoreState((s) => s.edges); const edges = useStoreState((s) => s.edges);
const nodes = useStoreState((s) => s.nodes); const nodes = useStoreState((s) => s.nodes);
const connectionSourceId = useStoreState((s) => s.connectionSourceId); const connectionNodeId = useStoreState((s) => s.connectionNodeId);
const connectionHandleType = useStoreState((s) => s.connectionHandleType);
const connectionPosition = useStoreState((s) => s.connectionPosition); const connectionPosition = useStoreState((s) => s.connectionPosition);
const selectedElements = useStoreState((s) => s.selectedElements); const selectedElements = useStoreState((s) => s.selectedElements);
const isInteractive = useStoreState((s) => s.isInteractive); const isInteractive = useStoreState((s) => s.isInteractive);
@@ -202,15 +203,17 @@ const EdgeRenderer = memo((props: EdgeRendererProps) => {
} }
const transformStyle = `translate(${tX},${tY}) scale(${tScale})`; const transformStyle = `translate(${tX},${tY}) scale(${tScale})`;
const renderConnectionLine = connectionNodeId && connectionHandleType;
return ( return (
<svg width={width} height={height} className="react-flow__edges"> <svg width={width} height={height} className="react-flow__edges">
<g transform={transformStyle}> <g transform={transformStyle}>
{edges.map((e: Edge) => renderEdge(e, props, nodes, selectedElements, isInteractive))} {edges.map((e: Edge) => renderEdge(e, props, nodes, selectedElements, isInteractive))}
{connectionSourceId && ( {renderConnectionLine && (
<ConnectionLine <ConnectionLine
nodes={nodes} nodes={nodes}
connectionSourceId={connectionSourceId} connectionNodeId={connectionNodeId!}
connectionHandleType={connectionHandleType!}
connectionPositionX={connectionPosition.x} connectionPositionX={connectionPosition.x}
connectionPositionY={connectionPosition.y} connectionPositionY={connectionPosition.y}
transform={[tX, tY, tScale]} transform={[tX, tY, tScale]}
+10 -5
View File
@@ -17,6 +17,8 @@ import {
XYPosition, XYPosition,
OnConnectFunc, OnConnectFunc,
SelectionRect, SelectionRect,
HandleType,
SetConnectionId,
} from '../types'; } from '../types';
type TransformXYK = { type TransformXYK = {
@@ -69,7 +71,8 @@ export interface StoreModel {
userSelectionRect: SelectionRect; userSelectionRect: SelectionRect;
connectionSourceId: ElementId | null; connectionNodeId: ElementId | null;
connectionHandleType: HandleType | null;
connectionPosition: XYPosition; connectionPosition: XYPosition;
snapToGrid: boolean; snapToGrid: boolean;
@@ -105,7 +108,7 @@ export interface StoreModel {
setConnectionPosition: Action<StoreModel, XYPosition>; setConnectionPosition: Action<StoreModel, XYPosition>;
setConnectionSourceId: Action<StoreModel, ElementId | null>; setConnectionNodeId: Action<StoreModel, SetConnectionId>;
setInteractive: Action<StoreModel, boolean>; setInteractive: Action<StoreModel, boolean>;
@@ -140,7 +143,8 @@ const storeModel: StoreModel = {
height: 0, height: 0,
draw: false, draw: false,
}, },
connectionSourceId: null, connectionNodeId: null,
connectionHandleType: 'source',
connectionPosition: { x: 0, y: 0 }, connectionPosition: { x: 0, y: 0 },
snapGrid: [16, 16], snapGrid: [16, 16],
@@ -330,8 +334,9 @@ const storeModel: StoreModel = {
state.connectionPosition = position; state.connectionPosition = position;
}), }),
setConnectionSourceId: action((state, sourceId) => { setConnectionNodeId: action((state, { connectionNodeId, connectionHandleType }) => {
state.connectionSourceId = sourceId; state.connectionNodeId = connectionNodeId;
state.connectionHandleType = connectionHandleType;
}), }),
setSnapGrid: action((state, { snapToGrid, snapGrid }) => { setSnapGrid: action((state, { snapToGrid, snapGrid }) => {
+5
View File
@@ -160,6 +160,11 @@ export enum ConnectionLineType {
export type OnConnectFunc = (connection: Connection) => void; export type OnConnectFunc = (connection: Connection) => void;
export type SetConnectionId = {
connectionNodeId: ElementId | null;
connectionHandleType: HandleType | null;
};
export interface HandleElement extends XYPosition, Dimensions { export interface HandleElement extends XYPosition, Dimensions {
id?: ElementId | null; id?: ElementId | null;
position: Position; position: Position;