Merge pull request #5684 from ysds/fix/nodes-selection-drag-after-programmatic-selection

fix: ensure drag handlers update when nodes selection changes programmatically
This commit is contained in:
Moritz Klack
2026-02-18 21:13:47 +01:00
committed by GitHub
5 changed files with 72 additions and 16 deletions

View File

@@ -0,0 +1,5 @@
---
'@xyflow/react': patch
---
Consolidate drag handler effects in useDrag to fix programmatic selection issues

View File

@@ -62,6 +62,7 @@ import MovingHandles from '../examples/MovingHandles';
import DetachedHandle from '../examples/DetachedHandle'; import DetachedHandle from '../examples/DetachedHandle';
import ZIndexMode from '../examples/ZIndexMode'; import ZIndexMode from '../examples/ZIndexMode';
import Middlewares from '../examples/Middlewares'; import Middlewares from '../examples/Middlewares';
import NodeSelectionBug from '../examples/NodeSelectionBug';
export interface IRoute { export interface IRoute {
name: string; name: string;
@@ -390,6 +391,11 @@ const routes: IRoute[] = [
path: 'z-index-mode', path: 'z-index-mode',
component: ZIndexMode, component: ZIndexMode,
}, },
{
name: 'Node Selection Bug',
path: 'node-selection-bug',
component: NodeSelectionBug,
},
]; ];
export default routes; export default routes;

View File

@@ -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>
</>
);
}

View File

@@ -51,11 +51,14 @@ export function NodesSelection<NodeType extends Node>({
} }
}, [disableKeyboardA11y]); }, [disableKeyboardA11y]);
const shouldRender = !userSelectionActive && width !== null && height !== null;
useDrag({ useDrag({
nodeRef, nodeRef,
disabled: !shouldRender,
}); });
if (userSelectionActive || !width || !height) { if (!shouldRender) {
return null; return null;
} }

View File

@@ -52,10 +52,11 @@ export function useDrag({
}, []); }, []);
useEffect(() => { useEffect(() => {
if (disabled) { if (disabled || !nodeRef.current || !xyDrag.current) {
xyDrag.current?.destroy(); return;
} else if (nodeRef.current) { }
xyDrag.current?.update({
xyDrag.current.update({
noDragClassName, noDragClassName,
handleSelector, handleSelector,
domNode: nodeRef.current, domNode: nodeRef.current,
@@ -63,11 +64,11 @@ export function useDrag({
nodeId, nodeId,
nodeClickDistance, nodeClickDistance,
}); });
return () => { return () => {
xyDrag.current?.destroy(); xyDrag.current?.destroy();
}; };
} }, [noDragClassName, handleSelector, disabled, isSelectable, nodeRef, nodeId, nodeClickDistance]);
}, [noDragClassName, handleSelector, disabled, isSelectable, nodeRef, nodeId]);
return dragging; return dragging;
} }