completeConnectionDrag method

Connection? completeConnectionDrag({
  1. required String targetNodeId,
  2. required String targetPortId,
})

Completes a connection drag by creating the connection.

Call this from PortWidget's GestureDetector.onPanEnd when over a valid target.

Parameters:

  • targetNodeId: The ID of the target node
  • targetPortId: The ID of the target port

Returns the created connection, or null if creation failed.

Implementation

Connection? completeConnectionDrag({
  required String targetNodeId,
  required String targetPortId,
}) {
  final temp = interaction.temporaryConnection.value;
  if (temp == null) {
    // Fire connection end event with failure
    events.connection?.onConnectEnd?.call(false);
    return null;
  }

  // Validate connection before creating
  final validationResult = canConnect(
    targetNodeId: targetNodeId,
    targetPortId: targetPortId,
  );
  if (!validationResult.allowed) {
    cancelConnectionDrag();
    return null;
  }

  // Reset highlighted port
  final highlightedNode = _nodes[targetNodeId];
  if (highlightedNode != null) {
    final highlightedPort = highlightedNode.allPorts
        .where((p) => p.id == targetPortId)
        .firstOrNull;
    if (highlightedPort != null) {
      runInAction(() => highlightedPort.highlighted.value = false);
    }
  }

  // Determine actual source/target based on port direction:
  // - If started from output: start is source, target is target
  // - If started from input: target is source, start is target
  final String sourceNodeId;
  final String sourcePortId;
  final String actualTargetNodeId;
  final String actualTargetPortId;

  if (temp.isStartFromOutput) {
    // Output → Input: start is source, target is target
    sourceNodeId = temp.startNodeId;
    sourcePortId = temp.startPortId;
    actualTargetNodeId = targetNodeId;
    actualTargetPortId = targetPortId;
  } else {
    // Input ← Output: target is source, start is target
    sourceNodeId = targetNodeId;
    sourcePortId = targetPortId;
    actualTargetNodeId = temp.startNodeId;
    actualTargetPortId = temp.startPortId;
  }

  // Check if target port allows multiple connections
  final targetNode = _nodes[actualTargetNodeId];
  if (targetNode != null) {
    final targetPort = targetNode.allPorts
        .where((p) => p.id == actualTargetPortId)
        .firstOrNull;
    if (targetPort != null && !targetPort.multiConnections) {
      // Remove existing connections to target port
      final connectionsToRemove = _connections
          .where(
            (conn) =>
                conn.targetNodeId == actualTargetNodeId &&
                conn.targetPortId == actualTargetPortId,
          )
          .toList();
      runInAction(() {
        for (final connection in connectionsToRemove) {
          removeConnection(connection.id);
        }
      });
    }
  }

  // Create the new connection
  final createdConnection = runInAction(() {
    final connection = Connection(
      id: '${sourceNodeId}_${sourcePortId}_${actualTargetNodeId}_$actualTargetPortId',
      sourceNodeId: sourceNodeId,
      sourcePortId: sourcePortId,
      targetNodeId: actualTargetNodeId,
      targetPortId: actualTargetPortId,
    );
    addConnection(connection);

    // Clear temporary connection state and re-enable panning
    // Cursor is derived from state via Observer in widget MouseRegions
    interaction.temporaryConnection.value = null;
    interaction.panEnabled.value = true;

    return connection;
  });

  // Fire connection end event with success
  events.connection?.onConnectEnd?.call(true);

  return createdConnection;
}