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 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;
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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
@@ -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 }) => {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user