feat: Added reconnectable edges

Modified edges to support being disconnected and reconnected upon dragging their connection away from the currently connected node.

ref: N25B-114
This commit is contained in:
JGerla
2025-09-30 14:53:53 +02:00
parent e076331cfc
commit 0eb5b65f67
2 changed files with 50 additions and 29 deletions

View File

@@ -1,14 +1,17 @@
import './VisProgUI.css'
import { useState, useCallback } from 'react';
import {
useCallback,
useRef
} from 'react';
import {
Background,
Controls,
ReactFlow,
applyNodeChanges,
applyEdgeChanges,
addEdge,
type NodeChange,
type EdgeChange,
type Edge, type Connection
useNodesState,
useEdgesState,
reconnectEdge,
addEdge, type Edge, type Connection,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
@@ -18,39 +21,55 @@ const initialNodes = [
];
const initialEdges = [{id: 'n1-n2', source: 'n1', target: 'n2'}];
export default function App() {
const [nodes, setNodes] = useState(initialNodes);
const [edges, setEdges] = useState(initialEdges);
const VisualProgrammingUI = ()=> {
const edgeReconnectSuccessful = useRef(true);
const [nodes, , onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onNodesChange = useCallback(
(changes: NodeChange<{
id: string;
position: { x: number; y: number; };
data: { label: string; };
}>[]) => setNodes((nodesSnapshot) => applyNodeChanges(changes, nodesSnapshot)),
[],
);
const onEdgesChange = useCallback(
(changes: EdgeChange<{ id: string; source: string; target: string; }>[]) => setEdges((edgesSnapshot) => applyEdgeChanges(changes, edgesSnapshot)),
[],
);
const onConnect = useCallback(
(params: Edge | Connection) => setEdges((edgesSnapshot) => addEdge(params, edgesSnapshot)),
[],
(params: Edge | Connection) => setEdges((els) => addEdge(params, els)),
[setEdges],
);
const onReconnectStart = useCallback(() => {
edgeReconnectSuccessful.current = false;
}, []);
const onReconnect = useCallback((oldEdge: Edge, newConnection: Connection) => {
edgeReconnectSuccessful.current = true;
setEdges((els) => reconnectEdge(oldEdge, newConnection, els));
}, [setEdges]);
const onReconnectEnd = useCallback((_: unknown, edge: { id: string; }) => {
if (!edgeReconnectSuccessful.current) {
setEdges((eds) => eds.filter((e) => e.id !== edge.id));
}
edgeReconnectSuccessful.current = true;
}, [setEdges]);
return (
<div style={{ width: '100vw', height: '100vh' }}>
<div style={{ width: '90vw', height: '90vh' }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
snapToGrid
onReconnect={onReconnect}
onReconnectStart={onReconnectStart}
onReconnectEnd={onReconnectEnd}
onConnect={onConnect}
fitView
/>
attributionPosition="top-right"
>
<Controls />
<Background />
</ReactFlow>
</div>
);
}
};
export default VisualProgrammingUI

View File

@@ -1,10 +1,12 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './VisualProgrammingUI/VisProgUI.tsx'
import App from './App.tsx'
import VisualProgrammingUI from "./VisualProgrammingUI/VisProgUI.tsx";
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
<VisualProgrammingUI />
</StrictMode>,
)