handlePortExpand method
Handle incremental port expansion.
Reveals only the internal edges connected to portId on node nodeId,
plus the submodules at the other end of those edges. This is the
Dart-first path only (port expansion is not supported in JS-first mode).
Implementation
Future<SchematicLayoutResult?> handlePortExpand(
String nodeId,
String portId,
) async {
if (isToggling) {
return null;
}
final engine = _layoutEngine;
if (engine == null || schematicAdapter == null) {
return null;
}
// Show the spinner only when a network fetch is actually needed.
if (needsConnectivity(nodeId)) {
setState(() {
isToggling = true;
togglingLabel = 'Expanding...';
pendingToggleNodeId = nodeId;
});
// Yield to let the spinner frame paint before the heavy fetch.
final completer = Completer<void>();
WidgetsBinding.instance.addPostFrameCallback((_) {
completer.complete();
});
await completer.future;
SchedulerBinding.instance.ensureVisualUpdate();
// Fetch + rebuild adapter.
await ensureConnectivity(nodeId);
} else {
// Already loaded — gate re-entry without triggering a repaint or
// showing the spinner overlay. Direct field mutation is safe here
// because Dart is single-threaded: the gate is set synchronously
// before the first await below.
isToggling = true;
pendingToggleNodeId = nodeId;
await ensureConnectivity(nodeId); // fast no-op
}
final preExpandSnapshot = schematicAdapter!.schematic.expansionSnapshot();
try {
final expanded = schematicAdapter!.expandPort(nodeId, portId);
if (!expanded) {
if (mounted) {
setState(() {
isToggling = false;
pendingToggleNodeId = null;
});
}
return null;
}
final elkGraph = schematicAdapter!.schematic.toJsGraph();
final newLayout = await engine.computeLayoutFromElkGraph(
elkGraph,
sessionId: _sessionId,
);
if (newLayout.hasError || !mounted) {
schematicAdapter!.schematic.restoreExpansionSnapshot(preExpandSnapshot);
if (mounted) {
setState(() {
isToggling = false;
pendingToggleNodeId = null;
});
}
return null;
}
setState(() {
layout = newLayout;
isToggling = false;
pendingToggleNodeId = null;
// Focus on the specific port that was clicked so the canvas
// pans to keep it centred at the current zoom level.
focusPortId = portId;
recentlyToggledNodeId = null; // port focus overrides node fit
});
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {
focusPortId = null;
});
}
});
return newLayout;
} on Exception {
schematicAdapter!.schematic.restoreExpansionSnapshot(preExpandSnapshot);
if (mounted) {
setState(() {
isToggling = false;
pendingToggleNodeId = null;
});
}
return null;
}
}