Merge pull request #4594 from xyflow/fix-reconnect-loose
Fix reconnect with loose connectionMode
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
'@xyflow/react': patch
|
||||||
|
'@xyflow/system': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixed reconnecting edges with loose connectionMode
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// Reconnectable edges have a anchors around their handles to reconnect the edge.
|
// Reconnectable edges have a anchors around their handles to reconnect the edge.
|
||||||
import { XYHandle, type Connection, EdgePosition, FinalConnectionState } from '@xyflow/system';
|
import { XYHandle, type Connection, EdgePosition, FinalConnectionState, HandleType } from '@xyflow/system';
|
||||||
|
|
||||||
import { EdgeAnchor } from '../Edges/EdgeAnchor';
|
import { EdgeAnchor } from '../Edges/EdgeAnchor';
|
||||||
import type { EdgeWrapperProps, Edge } from '../../types/edges';
|
import type { EdgeWrapperProps, Edge } from '../../types/edges';
|
||||||
@@ -9,8 +9,6 @@ type EdgeUpdateAnchorsProps<EdgeType extends Edge = Edge> = {
|
|||||||
edge: EdgeType;
|
edge: EdgeType;
|
||||||
isReconnectable: boolean | 'source' | 'target';
|
isReconnectable: boolean | 'source' | 'target';
|
||||||
reconnectRadius: EdgeWrapperProps['reconnectRadius'];
|
reconnectRadius: EdgeWrapperProps['reconnectRadius'];
|
||||||
sourceHandleId: Edge['sourceHandle'];
|
|
||||||
targetHandleId: Edge['targetHandle'];
|
|
||||||
onReconnect: EdgeWrapperProps<EdgeType>['onReconnect'];
|
onReconnect: EdgeWrapperProps<EdgeType>['onReconnect'];
|
||||||
onReconnectStart: EdgeWrapperProps<EdgeType>['onReconnectStart'];
|
onReconnectStart: EdgeWrapperProps<EdgeType>['onReconnectStart'];
|
||||||
onReconnectEnd: EdgeWrapperProps<EdgeType>['onReconnectEnd'];
|
onReconnectEnd: EdgeWrapperProps<EdgeType>['onReconnectEnd'];
|
||||||
@@ -22,8 +20,6 @@ export function EdgeUpdateAnchors<EdgeType extends Edge = Edge>({
|
|||||||
isReconnectable,
|
isReconnectable,
|
||||||
reconnectRadius,
|
reconnectRadius,
|
||||||
edge,
|
edge,
|
||||||
targetHandleId,
|
|
||||||
sourceHandleId,
|
|
||||||
sourceX,
|
sourceX,
|
||||||
sourceY,
|
sourceY,
|
||||||
targetX,
|
targetX,
|
||||||
@@ -38,7 +34,10 @@ export function EdgeUpdateAnchors<EdgeType extends Edge = Edge>({
|
|||||||
}: EdgeUpdateAnchorsProps<EdgeType>) {
|
}: EdgeUpdateAnchorsProps<EdgeType>) {
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
|
|
||||||
const handleEdgeUpdater = (event: React.MouseEvent<SVGGElement, MouseEvent>, isSourceHandle: boolean) => {
|
const handleEdgeUpdater = (
|
||||||
|
event: React.MouseEvent<SVGGElement, MouseEvent>,
|
||||||
|
oppositeHandle: { nodeId: string; id: string | null; type: HandleType }
|
||||||
|
) => {
|
||||||
// avoid triggering edge updater if mouse btn is not left
|
// avoid triggering edge updater if mouse btn is not left
|
||||||
if (event.button !== 0) {
|
if (event.button !== 0) {
|
||||||
return;
|
return;
|
||||||
@@ -59,18 +58,14 @@ export function EdgeUpdateAnchors<EdgeType extends Edge = Edge>({
|
|||||||
panBy,
|
panBy,
|
||||||
updateConnection,
|
updateConnection,
|
||||||
} = store.getState();
|
} = store.getState();
|
||||||
const nodeId = isSourceHandle ? edge.target : edge.source;
|
const isTarget = oppositeHandle.type === 'target';
|
||||||
const handleId = (isSourceHandle ? targetHandleId : sourceHandleId) || null;
|
|
||||||
const handleType = isSourceHandle ? 'target' : 'source';
|
|
||||||
|
|
||||||
const isTarget = isSourceHandle;
|
|
||||||
|
|
||||||
setReconnecting(true);
|
setReconnecting(true);
|
||||||
onReconnectStart?.(event, edge, handleType);
|
onReconnectStart?.(event, edge, oppositeHandle.type);
|
||||||
|
|
||||||
const _onReconnectEnd = (evt: MouseEvent | TouchEvent, connectionState: FinalConnectionState) => {
|
const _onReconnectEnd = (evt: MouseEvent | TouchEvent, connectionState: FinalConnectionState) => {
|
||||||
setReconnecting(false);
|
setReconnecting(false);
|
||||||
onReconnectEnd?.(evt, edge, handleType, connectionState);
|
onReconnectEnd?.(evt, edge, oppositeHandle.type, connectionState);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onConnectEdge = (connection: Connection) => onReconnect?.(edge, connection);
|
const onConnectEdge = (connection: Connection) => onReconnect?.(edge, connection);
|
||||||
@@ -80,11 +75,11 @@ export function EdgeUpdateAnchors<EdgeType extends Edge = Edge>({
|
|||||||
connectionMode,
|
connectionMode,
|
||||||
connectionRadius,
|
connectionRadius,
|
||||||
domNode,
|
domNode,
|
||||||
handleId,
|
handleId: oppositeHandle.id,
|
||||||
nodeId,
|
nodeId: oppositeHandle.nodeId,
|
||||||
nodeLookup,
|
nodeLookup,
|
||||||
isTarget,
|
isTarget,
|
||||||
edgeUpdaterType: handleType,
|
edgeUpdaterType: oppositeHandle.type,
|
||||||
lib,
|
lib,
|
||||||
flowId,
|
flowId,
|
||||||
cancelConnection,
|
cancelConnection,
|
||||||
@@ -101,15 +96,15 @@ export function EdgeUpdateAnchors<EdgeType extends Edge = Edge>({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onReconnectSourceMouseDown = (event: React.MouseEvent<SVGGElement, MouseEvent>): void =>
|
const onReconnectSourceMouseDown = (event: React.MouseEvent<SVGGElement, MouseEvent>): void =>
|
||||||
handleEdgeUpdater(event, true);
|
handleEdgeUpdater(event, { nodeId: edge.target, id: edge.targetHandle ?? null, type: 'target' });
|
||||||
const onReconnectTargetMouseDown = (event: React.MouseEvent<SVGGElement, MouseEvent>): void =>
|
const onReconnectTargetMouseDown = (event: React.MouseEvent<SVGGElement, MouseEvent>): void =>
|
||||||
handleEdgeUpdater(event, false);
|
handleEdgeUpdater(event, { nodeId: edge.source, id: edge.sourceHandle ?? null, type: 'source' });
|
||||||
const onReconnectMouseEnter = () => setUpdateHover(true);
|
const onReconnectMouseEnter = () => setUpdateHover(true);
|
||||||
const onReconnectMouseOut = () => setUpdateHover(false);
|
const onReconnectMouseOut = () => setUpdateHover(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{(isReconnectable === 'source' || isReconnectable === true) && (
|
{(isReconnectable === true || isReconnectable === 'source') && (
|
||||||
<EdgeAnchor
|
<EdgeAnchor
|
||||||
position={sourcePosition}
|
position={sourcePosition}
|
||||||
centerX={sourceX}
|
centerX={sourceX}
|
||||||
@@ -121,7 +116,7 @@ export function EdgeUpdateAnchors<EdgeType extends Edge = Edge>({
|
|||||||
type="source"
|
type="source"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(isReconnectable === 'target' || isReconnectable === true) && (
|
{(isReconnectable === true || isReconnectable === 'target') && (
|
||||||
<EdgeAnchor
|
<EdgeAnchor
|
||||||
position={targetPosition}
|
position={targetPosition}
|
||||||
centerX={targetX}
|
centerX={targetX}
|
||||||
|
|||||||
@@ -255,8 +255,6 @@ export function EdgeWrapper<EdgeType extends Edge = Edge>({
|
|||||||
targetPosition={targetPosition}
|
targetPosition={targetPosition}
|
||||||
setUpdateHover={setUpdateHover}
|
setUpdateHover={setUpdateHover}
|
||||||
setReconnecting={setReconnecting}
|
setReconnecting={setReconnecting}
|
||||||
sourceHandleId={edge.sourceHandle}
|
|
||||||
targetHandleId={edge.targetHandle}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</g>
|
</g>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ function onPointerDown(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fromHandleInternal = getHandle(nodeId, handleType, handleId, nodeLookup);
|
const fromHandleInternal = getHandle(nodeId, handleType, handleId, nodeLookup, connectionMode);
|
||||||
if (!fromHandleInternal) {
|
if (!fromHandleInternal) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -289,7 +289,7 @@ function isValidHandle(
|
|||||||
|
|
||||||
result.isValid = isValid && isValidConnection(connection);
|
result.isValid = isValid && isValidConnection(connection);
|
||||||
|
|
||||||
result.toHandle = getHandle(handleNodeId, handleType, handleId, nodeLookup, false);
|
result.toHandle = getHandle(handleNodeId, handleType, handleId, nodeLookup, connectionMode, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { getHandlePosition, getOverlappingArea, nodeToRect } from '../utils';
|
import { getHandlePosition, getOverlappingArea, nodeToRect } from '../utils';
|
||||||
import type { HandleType, XYPosition, Handle, InternalNodeBase, NodeLookup } from '../types';
|
import type { HandleType, XYPosition, Handle, InternalNodeBase, NodeLookup, ConnectionMode } from '../types';
|
||||||
|
|
||||||
function getNodesWithinDistance(position: XYPosition, nodeLookup: NodeLookup, distance: number): InternalNodeBase[] {
|
function getNodesWithinDistance(position: XYPosition, nodeLookup: NodeLookup, distance: number): InternalNodeBase[] {
|
||||||
const nodes: InternalNodeBase[] = [];
|
const nodes: InternalNodeBase[] = [];
|
||||||
@@ -78,6 +78,7 @@ export function getHandle(
|
|||||||
handleType: HandleType,
|
handleType: HandleType,
|
||||||
handleId: string | null,
|
handleId: string | null,
|
||||||
nodeLookup: NodeLookup,
|
nodeLookup: NodeLookup,
|
||||||
|
connectionMode: ConnectionMode,
|
||||||
withAbsolutePosition = false
|
withAbsolutePosition = false
|
||||||
): Handle | null {
|
): Handle | null {
|
||||||
const node = nodeLookup.get(nodeId);
|
const node = nodeLookup.get(nodeId);
|
||||||
@@ -85,7 +86,10 @@ export function getHandle(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handles = node.internals.handleBounds?.[handleType];
|
const handles =
|
||||||
|
connectionMode === 'strict'
|
||||||
|
? node.internals.handleBounds?.[handleType]
|
||||||
|
: [...(node.internals.handleBounds?.source ?? []), ...(node.internals.handleBounds?.target ?? [])];
|
||||||
const handle = (handleId ? handles?.find((h) => h.id === handleId) : handles?.[0]) ?? null;
|
const handle = (handleId ? handles?.find((h) => h.id === handleId) : handles?.[0]) ?? null;
|
||||||
|
|
||||||
return handle && withAbsolutePosition
|
return handle && withAbsolutePosition
|
||||||
|
|||||||
Reference in New Issue
Block a user