fix(connectionline): draw line from handle where user starts closes #270
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import React, { useEffect, useState, CSSProperties } from 'react';
|
||||
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 {
|
||||
connectionSourceId: ElementId;
|
||||
connectionNodeId: ElementId;
|
||||
connectionHandleType: HandleType;
|
||||
connectionPositionX: number;
|
||||
connectionPositionY: number;
|
||||
connectionLineType: ConnectionLineType;
|
||||
@@ -16,7 +17,8 @@ interface ConnectionLineProps {
|
||||
}
|
||||
|
||||
export default ({
|
||||
connectionSourceId,
|
||||
connectionNodeId,
|
||||
connectionHandleType,
|
||||
connectionLineStyle,
|
||||
connectionPositionX,
|
||||
connectionPositionY,
|
||||
@@ -27,8 +29,8 @@ export default ({
|
||||
isInteractive,
|
||||
}: ConnectionLineProps) => {
|
||||
const [sourceNode, setSourceNode] = useState<Node | null>(null);
|
||||
const hasHandleId = connectionSourceId.includes('__');
|
||||
const sourceIdSplitted = connectionSourceId.split('__');
|
||||
const hasHandleId = connectionNodeId.includes('__');
|
||||
const sourceIdSplitted = connectionNodeId.split('__');
|
||||
const nodeId = sourceIdSplitted[0];
|
||||
const handleId = hasHandleId ? sourceIdSplitted[1] : null;
|
||||
|
||||
@@ -42,14 +44,10 @@ export default ({
|
||||
}
|
||||
|
||||
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
|
||||
? sourceNode.__rg.handleBounds[handleKey].find((d: HandleElement) => d.id === handleId)
|
||||
: sourceNode.__rg.handleBounds[handleKey][0];
|
||||
? sourceNode.__rg.handleBounds[connectionHandleType].find((d: HandleElement) => d.id === handleId)
|
||||
: sourceNode.__rg.handleBounds[connectionHandleType][0];
|
||||
const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : sourceNode.__rg.width / 2;
|
||||
const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : sourceNode.__rg.height;
|
||||
const sourceX = sourceNode.__rg.position.x + sourceHandleX;
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import React, { memo, MouseEvent as ReactMouseEvent, CSSProperties } from 'react';
|
||||
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 SetSourceIdFunc = (nodeId: ElementId | null) => void;
|
||||
type SetSourceIdFunc = (params: SetConnectionId) => void;
|
||||
|
||||
interface BaseHandleProps {
|
||||
type: HandleType;
|
||||
nodeId: ElementId;
|
||||
onConnect: OnConnectFunc;
|
||||
position: Position;
|
||||
setSourceId: SetSourceIdFunc;
|
||||
setConnectionNodeId: SetSourceIdFunc;
|
||||
setPosition: (pos: XYPosition) => void;
|
||||
isValidConnection: ValidConnectionFunc;
|
||||
id?: ElementId | boolean;
|
||||
@@ -29,8 +29,8 @@ type Result = {
|
||||
function onMouseDown(
|
||||
evt: ReactMouseEvent,
|
||||
nodeId: ElementId,
|
||||
setSourceId: SetSourceIdFunc,
|
||||
setPosition: (pos: XYPosition) => any,
|
||||
setConnectionNodeId: SetSourceIdFunc,
|
||||
setPosition: (pos: XYPosition) => void,
|
||||
onConnect: OnConnectFunc,
|
||||
isTarget: boolean,
|
||||
isValidConnection: ValidConnectionFunc
|
||||
@@ -48,7 +48,7 @@ function onMouseDown(
|
||||
x: evt.clientX - containerBounds.left,
|
||||
y: evt.clientY - containerBounds.top,
|
||||
});
|
||||
setSourceId(nodeId);
|
||||
setConnectionNodeId({ connectionNodeId: nodeId, connectionHandleType: isTarget ? 'target' : 'source' });
|
||||
|
||||
function resetRecentHandle(): void {
|
||||
if (!recentHoveredHandle) {
|
||||
@@ -120,7 +120,7 @@ function onMouseDown(
|
||||
}
|
||||
|
||||
resetRecentHandle();
|
||||
setSourceId(null);
|
||||
setConnectionNodeId({ connectionNodeId: null, connectionHandleType: null });
|
||||
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', onMouseUp);
|
||||
@@ -136,7 +136,7 @@ const BaseHandle = memo(
|
||||
nodeId,
|
||||
onConnect,
|
||||
position,
|
||||
setSourceId,
|
||||
setConnectionNodeId,
|
||||
setPosition,
|
||||
className,
|
||||
id = false,
|
||||
@@ -157,7 +157,7 @@ const BaseHandle = memo(
|
||||
data-handlepos={position}
|
||||
className={handleClasses}
|
||||
onMouseDown={(evt) =>
|
||||
onMouseDown(evt, nodeIdWithHandleId, setSourceId, setPosition, onConnect, isTarget, isValidConnection)
|
||||
onMouseDown(evt, nodeIdWithHandleId, setConnectionNodeId, setPosition, onConnect, isTarget, isValidConnection)
|
||||
}
|
||||
{...rest}
|
||||
/>
|
||||
|
||||
@@ -18,7 +18,7 @@ const Handle = memo(
|
||||
}: HandleProps) => {
|
||||
const nodeId = useContext(NodeIdContext) as ElementId;
|
||||
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 onConnectExtended = (params: Connection) => {
|
||||
onConnectAction(params);
|
||||
@@ -29,7 +29,7 @@ const Handle = memo(
|
||||
<BaseHandle
|
||||
nodeId={nodeId}
|
||||
setPosition={setPosition}
|
||||
setSourceId={setSourceId}
|
||||
setConnectionNodeId={setConnectionNodeId}
|
||||
onConnect={onConnectExtended}
|
||||
type={type}
|
||||
position={position}
|
||||
|
||||
@@ -190,7 +190,8 @@ const EdgeRenderer = memo((props: EdgeRendererProps) => {
|
||||
const [tX, tY, tScale] = useStoreState((s) => s.transform);
|
||||
const edges = useStoreState((s) => s.edges);
|
||||
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 selectedElements = useStoreState((s) => s.selectedElements);
|
||||
const isInteractive = useStoreState((s) => s.isInteractive);
|
||||
@@ -202,15 +203,17 @@ const EdgeRenderer = memo((props: EdgeRendererProps) => {
|
||||
}
|
||||
|
||||
const transformStyle = `translate(${tX},${tY}) scale(${tScale})`;
|
||||
const renderConnectionLine = connectionNodeId && connectionHandleType;
|
||||
|
||||
return (
|
||||
<svg width={width} height={height} className="react-flow__edges">
|
||||
<g transform={transformStyle}>
|
||||
{edges.map((e: Edge) => renderEdge(e, props, nodes, selectedElements, isInteractive))}
|
||||
{connectionSourceId && (
|
||||
{renderConnectionLine && (
|
||||
<ConnectionLine
|
||||
nodes={nodes}
|
||||
connectionSourceId={connectionSourceId}
|
||||
connectionNodeId={connectionNodeId!}
|
||||
connectionHandleType={connectionHandleType!}
|
||||
connectionPositionX={connectionPosition.x}
|
||||
connectionPositionY={connectionPosition.y}
|
||||
transform={[tX, tY, tScale]}
|
||||
|
||||
+10
-5
@@ -17,6 +17,8 @@ import {
|
||||
XYPosition,
|
||||
OnConnectFunc,
|
||||
SelectionRect,
|
||||
HandleType,
|
||||
SetConnectionId,
|
||||
} from '../types';
|
||||
|
||||
type TransformXYK = {
|
||||
@@ -69,7 +71,8 @@ export interface StoreModel {
|
||||
|
||||
userSelectionRect: SelectionRect;
|
||||
|
||||
connectionSourceId: ElementId | null;
|
||||
connectionNodeId: ElementId | null;
|
||||
connectionHandleType: HandleType | null;
|
||||
connectionPosition: XYPosition;
|
||||
|
||||
snapToGrid: boolean;
|
||||
@@ -105,7 +108,7 @@ export interface StoreModel {
|
||||
|
||||
setConnectionPosition: Action<StoreModel, XYPosition>;
|
||||
|
||||
setConnectionSourceId: Action<StoreModel, ElementId | null>;
|
||||
setConnectionNodeId: Action<StoreModel, SetConnectionId>;
|
||||
|
||||
setInteractive: Action<StoreModel, boolean>;
|
||||
|
||||
@@ -140,7 +143,8 @@ const storeModel: StoreModel = {
|
||||
height: 0,
|
||||
draw: false,
|
||||
},
|
||||
connectionSourceId: null,
|
||||
connectionNodeId: null,
|
||||
connectionHandleType: 'source',
|
||||
connectionPosition: { x: 0, y: 0 },
|
||||
|
||||
snapGrid: [16, 16],
|
||||
@@ -330,8 +334,9 @@ const storeModel: StoreModel = {
|
||||
state.connectionPosition = position;
|
||||
}),
|
||||
|
||||
setConnectionSourceId: action((state, sourceId) => {
|
||||
state.connectionSourceId = sourceId;
|
||||
setConnectionNodeId: action((state, { connectionNodeId, connectionHandleType }) => {
|
||||
state.connectionNodeId = connectionNodeId;
|
||||
state.connectionHandleType = connectionHandleType;
|
||||
}),
|
||||
|
||||
setSnapGrid: action((state, { snapToGrid, snapGrid }) => {
|
||||
|
||||
@@ -160,6 +160,11 @@ export enum ConnectionLineType {
|
||||
|
||||
export type OnConnectFunc = (connection: Connection) => void;
|
||||
|
||||
export type SetConnectionId = {
|
||||
connectionNodeId: ElementId | null;
|
||||
connectionHandleType: HandleType | null;
|
||||
};
|
||||
|
||||
export interface HandleElement extends XYPosition, Dimensions {
|
||||
id?: ElementId | null;
|
||||
position: Position;
|
||||
|
||||
Reference in New Issue
Block a user