canConnect method
Validates whether a connection can be made from the current drag state to the specified target port.
This method checks:
- Not connecting a port to itself
- Direction compatibility (output→input or input←output)
- Port is connectable
- No duplicate connections
- Max connections limit not exceeded
- Custom validation via ConnectionEvents.onBeforeComplete callback
Returns a ConnectionValidationResult indicating if the connection is valid.
Implementation
ConnectionValidationResult canConnect({
required String targetNodeId,
required String targetPortId,
bool skipCustomValidation = false,
}) {
final temp = interaction.temporaryConnection.value;
if (temp == null) {
return const ConnectionValidationResult.deny(
reason: 'No active connection drag',
);
}
// Cannot connect a port to itself
if (temp.startNodeId == targetNodeId && temp.startPortId == targetPortId) {
return const ConnectionValidationResult.deny(
reason: 'Cannot connect a port to itself',
);
}
// Get target node
final targetNode = _nodes[targetNodeId];
if (targetNode == null) {
return const ConnectionValidationResult.deny(
reason: 'Target node not found',
);
}
// Cannot connect same direction ports
final targetIsOutput = targetNode.outputPorts.any(
(p) => p.id == targetPortId,
);
if (temp.isStartFromOutput == targetIsOutput) {
return ConnectionValidationResult.deny(
reason: targetIsOutput
? 'Cannot connect output to output'
: 'Cannot connect input to input',
);
}
// Get source node and port
final sourceNode = _nodes[temp.startNodeId];
if (sourceNode == null) {
return const ConnectionValidationResult.deny(
reason: 'Source node not found',
);
}
final sourcePort = sourceNode.allPorts
.where((p) => p.id == temp.startPortId)
.firstOrNull;
if (sourcePort == null) {
return const ConnectionValidationResult.deny(
reason: 'Source port not found',
);
}
// Get target port
final targetPort = targetNode.allPorts
.where((p) => p.id == targetPortId)
.firstOrNull;
if (targetPort == null) {
return const ConnectionValidationResult.deny(
reason: 'Target port not found',
);
}
// Both ports must be connectable
if (!sourcePort.isConnectable) {
return const ConnectionValidationResult.deny(
reason: 'Source port is not connectable',
);
}
if (!targetPort.isConnectable) {
return const ConnectionValidationResult.deny(
reason: 'Target port is not connectable',
);
}
// Direction compatibility
if (temp.isStartFromOutput) {
if (!targetPort.isInput) {
return const ConnectionValidationResult.deny(
reason: 'Target port cannot receive connections',
);
}
} else {
if (!targetPort.isOutput) {
return const ConnectionValidationResult.deny(
reason: 'Target port cannot emit connections',
);
}
}
// Determine actual source/target
final Node<T> actualSourceNode;
final Port actualSourcePort;
final Node<T> actualTargetNode;
final Port actualTargetPort;
if (temp.isStartFromOutput) {
actualSourceNode = sourceNode;
actualSourcePort = sourcePort;
actualTargetNode = targetNode;
actualTargetPort = targetPort;
} else {
actualSourceNode = targetNode;
actualSourcePort = targetPort;
actualTargetNode = sourceNode;
actualTargetPort = sourcePort;
}
// Get existing connections for both ports
final existingSourceConnections = _connections
.where(
(conn) =>
conn.sourceNodeId == actualSourceNode.id &&
conn.sourcePortId == actualSourcePort.id,
)
.map((c) => c.id)
.toList();
final existingTargetConnections = _connections
.where(
(conn) =>
conn.targetNodeId == actualTargetNode.id &&
conn.targetPortId == actualTargetPort.id,
)
.map((c) => c.id)
.toList();
// No duplicate connections
final duplicateExists = _connections.any(
(conn) =>
conn.sourceNodeId == actualSourceNode.id &&
conn.sourcePortId == actualSourcePort.id &&
conn.targetNodeId == actualTargetNode.id &&
conn.targetPortId == actualTargetPort.id,
);
if (duplicateExists) {
return const ConnectionValidationResult.deny(
reason: 'Connection already exists',
);
}
// Max connections limit
if (actualTargetPort.maxConnections != null) {
if (existingTargetConnections.length >=
actualTargetPort.maxConnections!) {
return const ConnectionValidationResult.deny(
reason: 'Target port has maximum connections',
);
}
}
// Custom validation callback
if (!skipCustomValidation) {
final onBeforeComplete = events.connection?.onBeforeComplete;
if (onBeforeComplete != null) {
final context = ConnectionCompleteContext<T>(
sourceNode: actualSourceNode,
sourcePort: actualSourcePort,
targetNode: actualTargetNode,
targetPort: actualTargetPort,
existingSourceConnections: existingSourceConnections,
existingTargetConnections: existingTargetConnections,
);
final result = onBeforeComplete(context);
if (!result.allowed) {
return result;
}
}
}
return const ConnectionValidationResult.allow();
}