Skip to content
6 changes: 6 additions & 0 deletions .changeset/blue-shirts-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@xyflow/react': patch
'@xyflow/svelte': patch
---

Keep `onConnectEnd` and `isValidConnection` up to date in an ongoing connection
5 changes: 5 additions & 0 deletions .changeset/tender-ways-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@xyflow/react': patch
---

Consolidate drag handler effects in useDrag to fix programmatic selection issues
6 changes: 6 additions & 0 deletions examples/react/src/App/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import MovingHandles from '../examples/MovingHandles';
import DetachedHandle from '../examples/DetachedHandle';
import ZIndexMode from '../examples/ZIndexMode';
import Middlewares from '../examples/Middlewares';
import NodeSelectionBug from '../examples/NodeSelectionBug';

export interface IRoute {
name: string;
Expand Down Expand Up @@ -390,6 +391,11 @@ const routes: IRoute[] = [
path: 'z-index-mode',
component: ZIndexMode,
},
{
name: 'Node Selection Bug',
path: 'node-selection-bug',
component: NodeSelectionBug,
},
];

export default routes;
41 changes: 41 additions & 0 deletions examples/react/src/examples/NodeSelectionBug/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Node, ReactFlow, useNodesState } from '@xyflow/react';

import '@xyflow/react/dist/style.css';
import { useRef } from 'react';

export default function App() {
const [nodes, setNodes, onNodesChange] = useNodesState<Node>([
{
id: '0',
position: { x: 0, y: 0 },
data: { label: 'Rectangle Select Me First' },
},
]);
const id = useRef(0);
return (
<>
<button
onClick={() =>
setNodes((nodes) => [
...nodes.map((node) => ({ ...node, selected: false })),
{
id: (++id.current).toString(),
position: { x: -100, y: 100 * Math.floor((id.current + 1) / 2) },
data: { label: `Button Node ${id.current}` },
selected: true,
},
{
id: (++id.current).toString(),
position: { x: 100, y: (100 * id.current) / 2 },
data: { label: `Button Node ${id.current}` },
selected: true,
},
])
}
>
Click me to add nodes that are already selected.
</button>
<ReactFlow nodes={nodes} onNodesChange={onNodesChange} fitView></ReactFlow>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,10 @@ export function EdgeUpdateAnchors<EdgeType extends Edge = Edge>({
const {
autoPanOnConnect,
domNode,
isValidConnection,
connectionMode,
connectionRadius,
lib,
onConnectStart,
onConnectEnd,
cancelConnection,
nodeLookup,
rfId: flowId,
Expand Down Expand Up @@ -93,10 +91,10 @@ export function EdgeUpdateAnchors<EdgeType extends Edge = Edge>({
flowId,
cancelConnection,
panBy,
isValidConnection,
isValidConnection: (...args) => store.getState().isValidConnection?.(...args) ?? true,
onConnect: onConnectEdge,
onConnectStart: _onConnectStart,
onConnectEnd,
onConnectEnd: (...args) => store.getState().onConnectEnd?.(...args),
onReconnectEnd: _onReconnectEnd,
updateConnection,
getTransform: () => store.getState().transform,
Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/components/Handle/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,10 @@ function HandleComponent(
panBy: currentStore.panBy,
cancelConnection: currentStore.cancelConnection,
onConnectStart: currentStore.onConnectStart,
onConnectEnd: currentStore.onConnectEnd,
onConnectEnd: (...args) => store.getState().onConnectEnd?.(...args),
updateConnection: currentStore.updateConnection,
onConnect: onConnectExtended,
isValidConnection: isValidConnection || currentStore.isValidConnection,
isValidConnection: isValidConnection || ((...args) => store.getState().isValidConnection?.(...args) ?? true),
getTransform: () => store.getState().transform,
getFromHandle: () => store.getState().connection.fromHandle,
autoPanSpeed: currentStore.autoPanSpeed,
Expand Down
5 changes: 4 additions & 1 deletion packages/react/src/components/NodesSelection/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,14 @@ export function NodesSelection<NodeType extends Node>({
}
}, [disableKeyboardA11y]);

const shouldRender = !userSelectionActive && width !== null && height !== null;

useDrag({
nodeRef,
disabled: !shouldRender,
});

if (userSelectionActive || !width || !height) {
if (!shouldRender) {
return null;
}

Expand Down
31 changes: 16 additions & 15 deletions packages/react/src/hooks/useDrag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,23 @@ export function useDrag({
}, []);

useEffect(() => {
if (disabled) {
xyDrag.current?.destroy();
} else if (nodeRef.current) {
xyDrag.current?.update({
noDragClassName,
handleSelector,
domNode: nodeRef.current,
isSelectable,
nodeId,
nodeClickDistance,
});
return () => {
xyDrag.current?.destroy();
};
if (disabled || !nodeRef.current || !xyDrag.current) {
return;
}
}, [noDragClassName, handleSelector, disabled, isSelectable, nodeRef, nodeId]);

xyDrag.current.update({
noDragClassName,
handleSelector,
domNode: nodeRef.current,
isSelectable,
nodeId,
nodeClickDistance,
});

return () => {
xyDrag.current?.destroy();
};
}, [noDragClassName, handleSelector, disabled, isSelectable, nodeRef, nodeId, nodeClickDistance]);

return dragging;
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@
flowId,
cancelConnection,
panBy,
isValidConnection,
isValidConnection: (...args) => store.isValidConnection?.(...args) ?? true,
onConnectStart: _onConnectStart,
onConnectEnd: onconnectend,
onConnectEnd: (...args) => store.onconnectend?.(...args),
onConnect: (connection) => {
const reconnectedEdge = { ...edge, ...connection };
const newEdge = onbeforereconnect
Expand Down
15 changes: 4 additions & 11 deletions packages/svelte/src/lib/components/Handle/Handle.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -125,21 +125,14 @@
autoPanOnConnect: store.autoPanOnConnect,
autoPanSpeed: store.autoPanSpeed,
flowId: store.flowId,
isValidConnection: isValidConnection ?? store.isValidConnection,
isValidConnection:
isValidConnection || ((...args) => store.isValidConnection?.(...args) ?? true),
updateConnection: store.updateConnection,
cancelConnection: store.cancelConnection,
panBy: store.panBy,
onConnect: onConnectExtended,
onConnectStart: (event, startParams) => {
store.onconnectstart?.(event, {
nodeId: startParams.nodeId,
handleId: startParams.handleId,
handleType: startParams.handleType
});
},
onConnectEnd: (event, connectionState) => {
store.onconnectend?.(event, connectionState);
},
onConnectStart: store.onconnectstart,
onConnectEnd: (...args) => store.onconnectend?.(...args),
getTransform: () => [store.viewport.x, store.viewport.y, store.viewport.zoom],
getFromHandle: () => store.connection.fromHandle,
dragThreshold: store.connectionDragThreshold,
Expand Down
Loading