Keep an endpoint's measurement so its drag stops relighting the edges

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7LwYgJfpkbLCTeiAf8U4A
This commit is contained in:
2026-08-16 18:47:17 +02:00
co-authored by Claude Fable 5
parent 63531fa36e
commit fcd43e9ad9
+38 -12
View File
@@ -3,6 +3,7 @@ import {
BackgroundVariant,
type Connection,
type Node as FlowCanvasNode,
type NodeChange,
ReactFlow,
ReactFlowProvider,
useNodesState,
@@ -412,6 +413,25 @@ function FlowEditorInner({
() => placementsFor(flowName),
)
// React Flow measures a node once and keeps the size on it. Endpoints are
// rebuilt on every drag frame, so unless the measurement is carried over
// they arrive unmeasured and React Flow drops the edges attached to them
// until it has measured again — remounting those edges, which makes them
// pulse as if a value had just landed. Their own drag lit up the canvas.
const measured = useRef(new Map<string, { width: number; height: number }>())
const trackMeasured = useCallback(
(changes: NodeChange<FlowCanvasNode>[]) => {
for (const change of changes) {
if (change.type !== "dimensions" || !change.dimensions) continue
if (isEndpointNode({ id: change.id })) {
measured.current.set(change.id, change.dimensions)
}
}
onNodesChange(changes)
},
[onNodesChange],
)
/** Where clicking an endpoint takes you: the thing it stands for. */
const openEndpoint = useCallback(
(id: string) => {
@@ -435,17 +455,23 @@ function FlowEditorInner({
// stored: they join at render, after everything that reads or writes
// canvasNodes, so an autosave, an undo or a delete cannot reach them.
// biome-ignore lint/correctness/useExhaustiveDependencies: positions change on every drag frame; the key covers the wiring.
const external = useMemo(
() =>
deriveEndpoints(
detail.endpoints ?? [],
definitions,
flowName,
new Map(canvasNodes.map((node) => [node.id, node.position])),
moved,
),
[detail.endpoints, key, flowName, moved],
)
const external = useMemo(() => {
const built = deriveEndpoints(
detail.endpoints ?? [],
definitions,
flowName,
new Map(canvasNodes.map((node) => [node.id, node.position])),
moved,
)
return {
...built,
nodes: built.nodes.map((node) => {
const size = measured.current.get(node.id)
return size ? { ...node, measured: size, ...size } : node
}),
}
// biome-ignore lint/correctness/useExhaustiveDependencies: positions change on every drag frame; the key covers the wiring.
}, [detail.endpoints, key, flowName, moved])
// Edges follow from the name bindings, so they are derived, never stored.
// Kept off `external` deliberately: an endpoint's edges depend on which
@@ -856,7 +882,7 @@ function FlowEditorInner({
<ReactFlow
nodes={shownNodes}
edges={edges}
onNodesChange={onNodesChange}
onNodesChange={trackMeasured}
onNodeDrag={(_event, _node, dragged) => {
// An endpoint's position is ours, not React Flow's, so it only
// follows the pointer if we move it every frame.