diff --git a/.changeset/twelve-llamas-sort.md b/.changeset/twelve-llamas-sort.md
new file mode 100644
index 00000000..f9cc76c9
--- /dev/null
+++ b/.changeset/twelve-llamas-sort.md
@@ -0,0 +1,7 @@
+---
+'@xyflow/react': minor
+'@xyflow/svelte': minor
+'@xyflow/system': minor
+---
+
+Add connectionDragThreshold prop
diff --git a/examples/react/src/examples/CustomConnectionLine/index.tsx b/examples/react/src/examples/CustomConnectionLine/index.tsx
index 2e5e2969..243a4f11 100644
--- a/examples/react/src/examples/CustomConnectionLine/index.tsx
+++ b/examples/react/src/examples/CustomConnectionLine/index.tsx
@@ -36,6 +36,7 @@ const ConnectionLineFlow = () => {
onEdgesChange={onEdgesChange}
connectionLineComponent={ConnectionLine}
onConnect={onConnect}
+ connectionDragThreshold={25}
>
diff --git a/examples/svelte/src/routes/examples/custom-connection-line/+page.svelte b/examples/svelte/src/routes/examples/custom-connection-line/+page.svelte
index c7c6f92c..1da89e75 100644
--- a/examples/svelte/src/routes/examples/custom-connection-line/+page.svelte
+++ b/examples/svelte/src/routes/examples/custom-connection-line/+page.svelte
@@ -24,7 +24,14 @@
-
+
diff --git a/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx b/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx
index 4aed1c21..c9f3b7f0 100644
--- a/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx
+++ b/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx
@@ -1,5 +1,12 @@
// Reconnectable edges have a anchors around their handles to reconnect the edge.
-import { XYHandle, type Connection, EdgePosition, FinalConnectionState, HandleType } from '@xyflow/system';
+import {
+ XYHandle,
+ type Connection,
+ EdgePosition,
+ FinalConnectionState,
+ HandleType,
+ OnConnectStart,
+} from '@xyflow/system';
import { EdgeAnchor } from '../Edges/EdgeAnchor';
import type { EdgeWrapperProps, Edge } from '../../types/edges';
@@ -60,15 +67,17 @@ export function EdgeUpdateAnchors({
} = store.getState();
const isTarget = oppositeHandle.type === 'target';
- setReconnecting(true);
- onReconnectStart?.(event, edge, oppositeHandle.type);
-
const _onReconnectEnd = (evt: MouseEvent | TouchEvent, connectionState: FinalConnectionState) => {
setReconnecting(false);
onReconnectEnd?.(evt, edge, oppositeHandle.type, connectionState);
};
const onConnectEdge = (connection: Connection) => onReconnect?.(edge, connection);
+ const _onConnectStart: OnConnectStart = (_event, params) => {
+ setReconnecting(true);
+ onReconnectStart?.(event, edge, oppositeHandle.type);
+ onConnectStart?.(_event, params);
+ };
XYHandle.onPointerDown(event.nativeEvent, {
autoPanOnConnect,
@@ -86,12 +95,13 @@ export function EdgeUpdateAnchors({
panBy,
isValidConnection,
onConnect: onConnectEdge,
- onConnectStart,
+ onConnectStart: _onConnectStart,
onConnectEnd,
onReconnectEnd: _onReconnectEnd,
updateConnection,
getTransform: () => store.getState().transform,
getFromHandle: () => store.getState().connection.fromHandle,
+ dragThreshold: store.getState().connectionDragThreshold,
});
};
diff --git a/packages/react/src/components/Handle/index.tsx b/packages/react/src/components/Handle/index.tsx
index b2c3373e..31615f0c 100644
--- a/packages/react/src/components/Handle/index.tsx
+++ b/packages/react/src/components/Handle/index.tsx
@@ -149,6 +149,7 @@ function HandleComponent(
getTransform: () => store.getState().transform,
getFromHandle: () => store.getState().connection.fromHandle,
autoPanSpeed: currentStore.autoPanSpeed,
+ dragThreshold: currentStore.connectionDragThreshold,
});
}
diff --git a/packages/react/src/components/StoreUpdater/index.tsx b/packages/react/src/components/StoreUpdater/index.tsx
index 5a5878c3..1506bd48 100644
--- a/packages/react/src/components/StoreUpdater/index.tsx
+++ b/packages/react/src/components/StoreUpdater/index.tsx
@@ -65,6 +65,7 @@ const reactFlowFieldsToTrack = [
'isValidConnection',
'selectNodesOnDrag',
'nodeDragThreshold',
+ 'connectionDragThreshold',
'onBeforeDelete',
'debug',
'autoPanSpeed',
diff --git a/packages/react/src/container/ReactFlow/index.tsx b/packages/react/src/container/ReactFlow/index.tsx
index f0e73f4f..33b5ee54 100644
--- a/packages/react/src/container/ReactFlow/index.tsx
+++ b/packages/react/src/container/ReactFlow/index.tsx
@@ -139,6 +139,7 @@ function ReactFlow(
style,
id,
nodeDragThreshold,
+ connectionDragThreshold,
viewport,
onViewportChange,
width,
@@ -306,6 +307,7 @@ function ReactFlow(
isValidConnection={isValidConnection}
selectNodesOnDrag={selectNodesOnDrag}
nodeDragThreshold={nodeDragThreshold}
+ connectionDragThreshold={connectionDragThreshold}
onBeforeDelete={onBeforeDelete}
paneClickDistance={paneClickDistance}
debug={debug}
diff --git a/packages/react/src/store/initialState.ts b/packages/react/src/store/initialState.ts
index 2532b5ad..bfc9baaa 100644
--- a/packages/react/src/store/initialState.ts
+++ b/packages/react/src/store/initialState.ts
@@ -107,6 +107,7 @@ const getInitialState = ({
noPanClassName: 'nopan',
nodeOrigin: storeNodeOrigin,
nodeDragThreshold: 1,
+ connectionDragThreshold: 1,
snapGrid: [15, 15],
snapToGrid: false,
diff --git a/packages/react/src/types/component-props.ts b/packages/react/src/types/component-props.ts
index 483b727a..c7de86bf 100644
--- a/packages/react/src/types/component-props.ts
+++ b/packages/react/src/types/component-props.ts
@@ -658,6 +658,12 @@ export interface ReactFlowProps
import { useStore } from '$lib/store';
import type { Edge } from '$lib/types';
- import { XYHandle, type HandleType } from '@xyflow/system';
+ import { XYHandle, type HandleType, type OnConnectStart } from '@xyflow/system';
import { getContext } from 'svelte';
import { EdgeLabel } from '../EdgeLabel';
import type { EdgeReconnectAnchorProps } from './types';
@@ -12,6 +12,7 @@
position,
class: className,
size = 25,
+ dragThreshold = 1,
children,
...rest
}: EdgeReconnectAnchorProps = $props();
@@ -52,8 +53,11 @@
let newEdge: Edge | undefined;
let edge = edgeLookup.get(edgeId)!;
- reconnecting = true;
- onreconnectstart?.(event, edge, type);
+ const _onConnectStart: OnConnectStart = (evt, params) => {
+ reconnecting = true;
+ onreconnectstart?.(event, edge, type);
+ onconnectstart?.(evt, params);
+ };
const opposite =
type === 'target'
@@ -79,7 +83,7 @@
cancelConnection,
panBy,
isValidConnection,
- onConnectStart: onconnectstart,
+ onConnectStart: _onConnectStart,
onConnectEnd: onconnectend,
onConnect: (connection) => {
newEdge = { ...edge, ...connection };
@@ -97,7 +101,8 @@
},
updateConnection,
getTransform: () => [store.viewport.x, store.viewport.y, store.viewport.zoom],
- getFromHandle: () => store.connection.fromHandle
+ getFromHandle: () => store.connection.fromHandle,
+ dragThreshold: dragThreshold ?? store.connectionDragThreshold
});
};
diff --git a/packages/svelte/src/lib/components/EdgeReconnectAnchor/types.ts b/packages/svelte/src/lib/components/EdgeReconnectAnchor/types.ts
index 74ed6d6d..38b71232 100644
--- a/packages/svelte/src/lib/components/EdgeReconnectAnchor/types.ts
+++ b/packages/svelte/src/lib/components/EdgeReconnectAnchor/types.ts
@@ -10,4 +10,5 @@ export type EdgeReconnectAnchorProps = {
position?: XYPosition;
size?: number;
children?: Snippet;
+ dragThreshold?: number;
} & HTMLAttributes;
diff --git a/packages/svelte/src/lib/components/Handle/Handle.svelte b/packages/svelte/src/lib/components/Handle/Handle.svelte
index 8f5675c8..d636f4cf 100644
--- a/packages/svelte/src/lib/components/Handle/Handle.svelte
+++ b/packages/svelte/src/lib/components/Handle/Handle.svelte
@@ -139,7 +139,8 @@
store.onconnectend?.(event, connectionState);
},
getTransform: () => [store.viewport.x, store.viewport.y, store.viewport.zoom],
- getFromHandle: () => store.connection.fromHandle
+ getFromHandle: () => store.connection.fromHandle,
+ dragThreshold: store.connectionDragThreshold
});
}
}
diff --git a/packages/svelte/src/lib/container/SvelteFlow/Wrapper.svelte b/packages/svelte/src/lib/container/SvelteFlow/Wrapper.svelte
index 206fdc8c..847dc4e4 100644
--- a/packages/svelte/src/lib/container/SvelteFlow/Wrapper.svelte
+++ b/packages/svelte/src/lib/container/SvelteFlow/Wrapper.svelte
@@ -64,6 +64,7 @@
fitViewOptions,
nodeOrigin,
nodeDragThreshold,
+ connectionDragThreshold,
minZoom,
maxZoom,
initialViewport,
diff --git a/packages/svelte/src/lib/container/SvelteFlow/types.ts b/packages/svelte/src/lib/container/SvelteFlow/types.ts
index d575db1a..9d40b5b3 100644
--- a/packages/svelte/src/lib/container/SvelteFlow/types.ts
+++ b/packages/svelte/src/lib/container/SvelteFlow/types.ts
@@ -170,6 +170,12 @@ export type SvelteFlowProps<
* @default 0
*/
nodeClickDistance?: number;
+ /**
+ * The threshold in pixels that the mouse must move before a connection line starts to drag.
+ * This is useful to prevent accidental connections when clicking on a handle.
+ * @default 1
+ */
+ connectionDragThreshold?: number;
/** Minimum zoom level
* @default 0.5
*/
diff --git a/packages/svelte/src/lib/store/initial-store.svelte.ts b/packages/svelte/src/lib/store/initial-store.svelte.ts
index ae09b576..a2d1ea68 100644
--- a/packages/svelte/src/lib/store/initial-store.svelte.ts
+++ b/packages/svelte/src/lib/store/initial-store.svelte.ts
@@ -286,6 +286,7 @@ export function getInitialStore dragThreshold * dragThreshold;
+
+ if (!nextConnectionStarted) {
+ return;
+ }
+
+ startConnection();
+
+ connectionStarted = nextConnectionStarted;
+ }
+
if (!getFromHandle() || !fromHandle) {
onPointerUp(event);
return;
diff --git a/packages/system/src/xyhandle/types.ts b/packages/system/src/xyhandle/types.ts
index d3625c9b..baa8cf0a 100644
--- a/packages/system/src/xyhandle/types.ts
+++ b/packages/system/src/xyhandle/types.ts
@@ -37,6 +37,7 @@ export type OnPointerDownParams = {
getTransform: () => Transform;
getFromHandle: () => Handle | null;
autoPanSpeed?: number;
+ dragThreshold?: number;
};
export type IsValidParams = {