Select the edge you click in the flow editor

React Flow's selection changes had nowhere to go: the canvas passed
edges but no onEdgesChange, so no edge ever carried .selected and both
the selected-edge stroke and its rest colour were unreachable. The edges
are derived from the bindings, so the selected ids are held on their own
and marked on after the layout has had the array — a rebuild cannot drop
the selection, and selecting cannot make the graph lay itself out again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKL7sUgNWhukDEz95vSMQv
This commit is contained in:
2026-08-20 13:18:13 +02:00
co-authored by Claude Opus 5
parent 9c6c106065
commit f719816992
2 changed files with 30 additions and 2 deletions
+30 -1
View File
@@ -2,6 +2,7 @@ import {
Background,
BackgroundVariant,
type Connection,
type EdgeChange,
type Node as FlowCanvasNode,
type NodeChange,
ReactFlow,
@@ -219,6 +220,11 @@ function FlowEditorInner({
const [selectedId, setSelectedId] = useState<string | null>(null)
const [paletteOpen, setPaletteOpen] = useState(false)
const [inspected, setInspected] = useState<InspectedEdge | null>(null)
// The edges are derived, so xyflow's own selection would be thrown away on
// every rebuild: the ids live here instead and are marked on at render.
const [selectedEdges, setSelectedEdges] = useState<ReadonlySet<string>>(
() => new Set(),
)
const [rebind, setRebind] = useState<Rebind | null>(null)
const [renamed, setRenamed] = useState<MessageRename | null>(null)
const [flowPanelOpen, setFlowPanelOpen] = useState(false)
@@ -423,6 +429,20 @@ function FlowEditorInner({
[onNodesChange],
)
/** Selection is all an edge change can carry here: nothing else is stored. */
const onEdgesChange = useCallback((changes: EdgeChange[]) => {
const selections = changes.filter((change) => change.type === "select")
if (!selections.length) return
setSelectedEdges((current) => {
const next = new Set(current)
for (const change of selections) {
if (change.selected) next.add(change.id)
else next.delete(change.id)
}
return next
})
}, [])
// Dashboards and other flows wired into this one. They are drawn but never
// stored: they join at render, after everything that reads or writes
// canvasNodes, so an autosave, an undo or a delete cannot reach them.
@@ -485,6 +505,14 @@ function FlowEditorInner({
[external, positions],
)
// Marked after the layout has had the derived array, so selecting an edge
// never sends the graph through the layout again.
const shownEdges = useMemo(
() =>
edges.map((edge) => ({ ...edge, selected: selectedEdges.has(edge.id) })),
[edges, selectedEdges],
)
const shownNodes = useMemo(
() => [
...renderedNodes.map((node) => ({
@@ -911,8 +939,9 @@ function FlowEditorInner({
>
<ReactFlow
nodes={shownNodes}
edges={edges}
edges={shownEdges}
onNodesChange={trackMeasured}
onEdgesChange={onEdgesChange}
onNodesDelete={(deleted) =>
deleteNodes(deleted.filter(isDocumentNode).map((node) => node.id))
}