Skip to content

Events API

Complete reference for all event classes in Vyuh Node Flow.

NodeFlowEvents

The top-level container for all event handlers.

dart
NodeFlowEvents<T, dynamic>({
  NodeEvents<T>? node,
  PortEvents<T>? port,
  ConnectionEvents<T, dynamic>? connection,
  ViewportEvents? viewport,
  ValueChanged<SelectionState<T>>? onSelectionChange,
  VoidCallback? onInit,
  ValueChanged<FlowError>? onError,
})
PropertyTypeDescription
nodeNodeEvents<T>?Node interaction events (includes GroupNode & CommentNode)
portPortEvents<T>?Port interaction events
connectionConnectionEvents<T, dynamic>?Connection lifecycle events
viewportViewportEvents?Canvas pan/zoom events
onSelectionChangeValueChanged<SelectionState<T>>?Selection state changes
onInitVoidCallback?Editor initialization
onErrorValueChanged<FlowError>?Error handling

NodeEvents

Events for node interactions.

dart
NodeEvents<T>({
  ValueChanged<Node<T>>? onCreated,
  BeforeDeleteCallback<Node<T>>? onBeforeDelete,
  ValueChanged<Node<T>>? onDeleted,
  ValueChanged<Node<T>?>? onSelected,
  ValueChanged<Node<T>>? onTap,
  ValueChanged<Node<T>>? onDoubleTap,
  void Function(Node<T> node, ScreenPosition screenPosition)? onContextMenu,
  ValueChanged<Node<T>>? onDragStart,
  ValueChanged<Node<T>>? onDrag,
  ValueChanged<Node<T>>? onDragStop,
  ValueChanged<Node<T>>? onDragCancel,
  ValueChanged<Node<T>>? onResizeCancel,
  ValueChanged<Node<T>>? onMouseEnter,
  ValueChanged<Node<T>>? onMouseLeave,
})
EventTriggerSignature
onCreatedNode added to graphValueChanged<Node<T>>
onBeforeDeleteBefore node deleted (async, can cancel)Future<bool> Function(Node<T>)
onDeletedNode removed from graphValueChanged<Node<T>>
onSelectedSelection changesValueChanged<Node<T>?>
onTapSingle tapValueChanged<Node<T>>
onDoubleTapDouble tapValueChanged<Node<T>>
onContextMenuRight-click/long-press(Node<T>, ScreenPosition)
onDragStartDrag beginsValueChanged<Node<T>>
onDragDuring dragValueChanged<Node<T>>
onDragStopDrag ends successfullyValueChanged<Node<T>>
onDragCancelDrag cancelled (reverted)ValueChanged<Node<T>>
onResizeCancelResize cancelled (reverted)ValueChanged<Node<T>>
onMouseEnterMouse enters node boundsValueChanged<Node<T>>
onMouseLeaveMouse leaves node boundsValueChanged<Node<T>>

INFO

The onBeforeDelete callback is async and can be used to show confirmation dialogs before deletion. Locked nodes are automatically prevented from deletion without invoking this callback.

Example:

dart
NodeEvents<MyData>(
  onTap: (node) => print('Tapped: ${node.id}'),
  onDoubleTap: (node) => _editNode(node),
  onDragStop: (node) => _savePosition(node),
  onContextMenu: (node, pos) => _showMenu(node, pos),
  onBeforeDelete: (node) async {
    return await showDialog<bool>(
      context: context,
      builder: (ctx) => AlertDialog(
        title: Text('Delete Node?'),
        actions: [
          TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text('Cancel')),
          TextButton(onPressed: () => Navigator.pop(ctx, true), child: Text('Delete')),
        ],
      ),
    ) ?? false;
  },
)

PortEvents

Events for port interactions. All callbacks include the parent node for context.

dart
PortEvents<T>({
  void Function(Node<T> node, Port port)? onTap,
  void Function(Node<T> node, Port port)? onDoubleTap,
  void Function(Node<T> node, Port port)? onMouseEnter,
  void Function(Node<T> node, Port port)? onMouseLeave,
  void Function(Node<T> node, Port port, ScreenPosition screenPosition)? onContextMenu,
})

Use port.isOutput or port.isInput to determine the port direction.

EventTriggerSignature
onTapPort tapped(Node<T>, Port)
onDoubleTapPort double-tapped(Node<T>, Port)
onMouseEnterMouse enters port(Node<T>, Port)
onMouseLeaveMouse leaves port(Node<T>, Port)
onContextMenuRight-click on port(Node<T>, Port, ScreenPosition)

Example:

dart
PortEvents<MyData>(
  onTap: (node, port) {
    print('Tapped ${port.isOutput ? 'output' : 'input'} port: ${port.id}');
  },
  onMouseEnter: (node, port) => _showTooltip(port),
  onMouseLeave: (node, port) => _hideTooltip(),
)

ConnectionEvents

Events for connection lifecycle and validation.

dart
ConnectionEvents<T, dynamic>({
  ValueChanged<Connection>? onCreated,
  BeforeDeleteCallback<Connection>? onBeforeDelete,
  ValueChanged<Connection>? onDeleted,
  ValueChanged<Connection?>? onSelected,
  ValueChanged<Connection>? onTap,
  ValueChanged<Connection>? onDoubleTap,
  ValueChanged<Connection>? onMouseEnter,
  ValueChanged<Connection>? onMouseLeave,
  void Function(Connection, ScreenPosition)? onContextMenu,
  void Function(Node<T> sourceNode, Port sourcePort)? onConnectStart,
  void Function(Node<T>? targetNode, Port? targetPort, GraphPosition position)? onConnectEnd,
  ConnectionValidationResult Function(ConnectionStartContext<T>)? onBeforeStart,
  ConnectionValidationResult Function(ConnectionCompleteContext<T>)? onBeforeComplete,
})
EventTriggerSignature
onCreatedConnection addedValueChanged<Connection>
onBeforeDeleteBefore connection deletedFuture<bool> Function(Connection)
onDeletedConnection removedValueChanged<Connection>
onSelectedSelection changesValueChanged<Connection?>
onTapSingle tapValueChanged<Connection>
onDoubleTapDouble tapValueChanged<Connection>
onMouseEnterMouse enters pathValueChanged<Connection>
onMouseLeaveMouse leaves pathValueChanged<Connection>
onContextMenuRight-click(Connection, ScreenPosition)
onConnectStartDrag begins from port(Node<T>, Port)
onConnectEndDrag ends(Node<T>?, Port?, GraphPosition)
onBeforeStartBefore connection startsReturns validation result
onBeforeCompleteBefore connection completesReturns validation result

ConnectionStartContext

Context provided to onBeforeStart when starting a connection drag.

dart
class ConnectionStartContext<T> {
  final Node<T> sourceNode;
  final Port sourcePort;
  final List<String> existingConnections;

  // Computed properties
  bool get isOutputPort;
  bool get isInputPort;
}
PropertyTypeDescription
sourceNodeNode<T>Node where connection is starting
sourcePortPortPort where connection is starting
existingConnectionsList<String>IDs of existing connections from this port
isOutputPortboolWhether this is an output port
isInputPortboolWhether this is an input port

ConnectionCompleteContext

Context provided to onBeforeComplete when attempting to complete a connection.

dart
class ConnectionCompleteContext<T> {
  final Node<T> sourceNode;
  final Port sourcePort;
  final Node<T> targetNode;
  final Port targetPort;
  final List<String> existingSourceConnections;
  final List<String> existingTargetConnections;

  // Computed properties
  bool get isOutputToInput;
  bool get isInputToOutput;
  bool get isSelfConnection;
  bool get isSamePort;
}
PropertyTypeDescription
sourceNodeNode<T>Source node
sourcePortPortSource port
targetNodeNode<T>Target node
targetPortPortTarget port
existingSourceConnectionsList<String>Existing connection IDs from source port
existingTargetConnectionsList<String>Existing connection IDs to target port
isOutputToInputboolOutput-to-input direction (typical)
isInputToOutputboolInput-to-output direction (reverse)
isSelfConnectionboolConnecting a node to itself
isSamePortboolConnecting a port to itself

ConnectionValidationResult

Return value for validation callbacks.

dart
class ConnectionValidationResult {
  final bool allowed;
  final String? reason;
  final bool showMessage;

  // Factory constructors
  const ConnectionValidationResult.allow();
  const ConnectionValidationResult.deny({String? reason, bool showMessage = false});
}

Example:

dart
ConnectionEvents<MyData, dynamic>(
  onBeforeStart: (context) {
    // Validate port can start connections
    if (!context.sourcePort.isConnectable) {
      return ConnectionValidationResult.deny(
        reason: 'Port is not connectable',
        showMessage: true,
      );
    }
    return ConnectionValidationResult.allow();
  },
  onBeforeComplete: (context) {
    // Prevent self-connections
    if (context.isSelfConnection) {
      return ConnectionValidationResult.deny(
        reason: 'Cannot connect to same node',
        showMessage: true,
      );
    }
    // Only allow output-to-input
    if (!context.isOutputToInput) {
      return ConnectionValidationResult.deny(
        reason: 'Must connect output to input',
      );
    }
    return ConnectionValidationResult.allow();
  },
)

ViewportEvents

Events for canvas interactions.

dart
ViewportEvents({
  ValueChanged<GraphViewport>? onMove,
  ValueChanged<GraphViewport>? onMoveStart,
  ValueChanged<GraphViewport>? onMoveEnd,
  ValueChanged<GraphPosition>? onCanvasTap,
  ValueChanged<GraphPosition>? onCanvasDoubleTap,
  ValueChanged<GraphPosition>? onCanvasContextMenu,
})
EventTriggerSignature
onMoveDuring pan/zoomValueChanged<GraphViewport>
onMoveStartPan/zoom beginsValueChanged<GraphViewport>
onMoveEndPan/zoom endsValueChanged<GraphViewport>
onCanvasTapTap on empty canvasValueChanged<GraphPosition>
onCanvasDoubleTapDouble-tap on canvasValueChanged<GraphPosition>
onCanvasContextMenuRight-click on canvasValueChanged<GraphPosition>

INFO

Canvas positions are in graph coordinates (GraphPosition), automatically adjusted for pan and zoom.

Example:

dart
ViewportEvents(
  onCanvasTap: (pos) => controller.clearSelection(),
  onCanvasDoubleTap: (pos) => _addNodeAt(pos),
  onCanvasContextMenu: (pos) => _showAddMenu(pos),
  onMove: (viewport) => _updateMinimap(viewport),
)

SelectionState

Provided to onSelectionChange when selection changes.

dart
class SelectionState<T> {
  /// Currently selected nodes (includes GroupNode and CommentNode)
  final List<Node<T>> nodes;

  /// Currently selected connections
  final List<Connection> connections;

  /// Whether anything is selected
  bool get hasSelection;
}

INFO

GroupNode and CommentNode are included in the nodes list since they extend Node.

Example:

dart
onSelectionChange: (state) {
  if (state.hasSelection) {
    _showSelectionToolbar(state);
    print('Selected ${state.nodes.length} nodes');
  } else {
    _hideSelectionToolbar();
  }
}

FlowError

Error information passed to onError.

dart
class FlowError {
  final String message;
  final Object? error;
  final StackTrace? stackTrace;
}

Example:

dart
onError: (error) {
  print('Flow error: ${error.message}');
  if (error.error != null) {
    print('Caused by: ${error.error}');
  }
}

Complete Example

dart
NodeFlowEditor<WorkflowData, dynamic>(
  controller: controller,
  events: NodeFlowEvents(
    node: NodeEvents(
      onTap: (node) => setState(() => _selected = node),
      onDoubleTap: (node) => _editNode(node),
      onDragStop: (node) => _log('Moved: ${node.id}'),
      onContextMenu: (node, pos) => _showNodeMenu(node, pos),
    ),
    port: PortEvents(
      onMouseEnter: (node, port, _) => _showPortInfo(port),
      onMouseLeave: (_, __, ___) => _hidePortInfo(),
    ),
    connection: ConnectionEvents(
      onCreated: (conn) => _log('Connected: ${conn.id}'),
      onDeleted: (conn) => _log('Disconnected: ${conn.id}'),
      onMouseEnter: (conn) => conn.animated = true,
      onMouseLeave: (conn) => conn.animated = false,
      onBeforeComplete: (ctx) => _validateConnection(ctx),
    ),
    viewport: ViewportEvents(
      onCanvasTap: (_) => controller.clearSelection(),
      onCanvasContextMenu: (pos) => _showAddNodeMenu(pos),
    ),
    onSelectionChange: (state) {
      setState(() => _selectionCount = state.nodes.length);
    },
    onInit: () => _log('Editor ready'),
    onError: (error) => _log('Error: ${error.message}'),
  ),
)

copyWith Methods

All event classes support copyWith for creating modified copies:

dart
final baseEvents = NodeFlowEvents<MyData, dynamic>(
  node: NodeEvents(onTap: (n) => print('tap')),
);

final extendedEvents = baseEvents.copyWith(
  connection: ConnectionEvents(onCreated: (c) => print('created')),
);