/** * Graph path utilities: compute which nodes/edges are "on the path" of an update. * Used to show connection ant trail only along the full chain (upstream → updating → downstream). * Works with any node types; any node can signal it is updating via startConnectionPathUpdate(id). */ export type GraphEdge = { source: string; target: string } /** Nodes reachable from seedIds by following edges forward (source → target). */ export function getDownstreamNodeIds(edges: GraphEdge[], seedIds: string[]): Set { const out = new Set(seedIds) let added = true while (added) { added = false for (const e of edges) { if (out.has(e.source) && !out.has(e.target)) { out.add(e.target) added = true } } } return out } /** Nodes that can reach any seed by following edges backward (target → source). */ export function getUpstreamNodeIds(edges: GraphEdge[], seedIds: string[]): Set { const out = new Set(seedIds) let added = true while (added) { added = false for (const e of edges) { if (out.has(e.target) && !out.has(e.source)) { out.add(e.source) added = true } } } return out } /** * All node ids that lie on the path of an update. * - If triggerNodeIds is non-empty: path = nodes that are both downstream of a trigger and * upstream of an updating node (only the chain that actually triggered the update). * - Otherwise: path = upstream ∪ updating ∪ downstream of updating (legacy full upstream/downstream). * An edge should show the update animation iff both its source and target are in this set. */ export function getPathNodeIds( edges: GraphEdge[], updatingNodeIds: string[], triggerNodeIds?: string[] ): Set { if (updatingNodeIds.length === 0) return new Set() const upstream = getUpstreamNodeIds(edges, updatingNodeIds) if (triggerNodeIds != null && triggerNodeIds.length > 0) { const downstreamOfTrigger = getDownstreamNodeIds(edges, triggerNodeIds) const path = new Set() upstream.forEach((id) => { if (downstreamOfTrigger.has(id)) path.add(id) }) return path } const downstream = getDownstreamNodeIds(edges, updatingNodeIds) const path = new Set(upstream) downstream.forEach((id) => path.add(id)) return path }