handleExpandPath method

Future<SchematicLayoutResult?> handleExpandPath(
  1. List<String> pathSegments, {
  2. String? targetWireName,
})

Batch-expand an entire hierarchy path in a single layout cycle.

Applies all graph mutations (expandChild at each level, and optionally expandWire at the leaf) before serialising and computing layout. This avoids the N × (toJsGraph + ELK + setState) overhead incurred by expanding one level at a time.

pathSegments are instance names from top to bottom. If targetWireName is non-null the last segment is treated as the container of that wire; otherwise it is the target module.

Implementation

Future<SchematicLayoutResult?> handleExpandPath(
  List<String> pathSegments, {
  String? targetWireName,
}) async {
  if (isToggling || pathSegments.isEmpty) {
    return null;
  }
  final engine = _layoutEngine;
  if (engine == null || schematicAdapter == null) {
    return null;
  }

  setState(() {
    isToggling = true;
    togglingLabel = 'Expanding...';
    pendingToggleNodeId = pathSegments.last;
  });

  // Let the spinner appear before heavy processing.
  final completer = Completer<void>();
  WidgetsBinding.instance.addPostFrameCallback((_) {
    completer.complete();
  });
  await completer.future;
  SchedulerBinding.instance.ensureVisualUpdate();

  // Fetch connectivity only when a wire needs to be revealed at the
  // leaf. Path-only expansion (finding a module) never needs wires.
  if (targetWireName != null) {
    // pathSegments are instance names (e.g. 'ch0') but
    // ensureConnectivity expects a node address (e.g. '0.0').
    // Resolve through the schematic graph.
    final nodeId = schematicAdapter!.schematic.resolvePathToNodeId(
      pathSegments,
    );
    if (nodeId != null) {
      await ensureConnectivity(nodeId);
    }
  }

  try {
    final changed = schematicAdapter!.expandPath(
      pathSegments,
      targetWireName: targetWireName,
    );
    if (!changed) {
      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) {
      if (mounted) {
        setState(() {
          isToggling = false;
          pendingToggleNodeId = null;
        });
      }
      return null;
    }

    setState(() {
      layout = newLayout;
      isToggling = false;
      pendingToggleNodeId = null;
      focusPortId = null;
      recentlyToggledNodeId = null;
    });

    return newLayout;
  } on Exception {
    if (mounted) {
      setState(() {
        isToggling = false;
        pendingToggleNodeId = null;
      });
    }
    return null;
  }
}