Events API

API reference for the event system classes

Events API

Complete reference for all event classes in Vyuh Node Flow.

NodeFlowEvents

The top-level container for all event handlers.

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

NodeEvents

Events for node interactions.

NodeEvents<T>({
  ValueChanged<Node<T>>? onCreated,
  ValueChanged<Node<T>>? onDeleted,
  ValueChanged<Node<T>?>? onSelected,
  ValueChanged<Node<T>>? onTap,
  ValueChanged<Node<T>>? onDoubleTap,
  void Function(Node<T>, Offset)? onContextMenu,
  ValueChanged<Node<T>>? onDragStart,
  ValueChanged<Node<T>>? onDrag,
  ValueChanged<Node<T>>? onDragStop,
  ValueChanged<Node<T>>? onMouseEnter,
  ValueChanged<Node<T>>? onMouseLeave,
})
EventTriggerSignature
onCreatedNode added to graphValueChanged<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>, Offset)
onDragStartDrag beginsValueChanged<Node<T>>
onDragDuring dragValueChanged<Node<T>>
onDragStopDrag endsValueChanged<Node<T>>
onMouseEnterMouse enters node boundsValueChanged<Node<T>>
onMouseLeaveMouse leaves node boundsValueChanged<Node<T>>

Example:

NodeEvents<MyData>(
  onTap: (node) => print('Tapped: ${node.id}'),
  onDoubleTap: (node) => _editNode(node),
  onDragStop: (node) => _savePosition(node),
  onContextMenu: (node, pos) => _showMenu(node, pos),
)

PortEvents

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

PortEvents<T>({
  void Function(Node<T>, Port, bool)? onTap,
  void Function(Node<T>, Port, bool)? onDoubleTap,
  void Function(Node<T>, Port, bool)? onMouseEnter,
  void Function(Node<T>, Port, bool)? onMouseLeave,
  void Function(Node<T>, Port, bool, Offset)? onContextMenu,
})

The bool parameter indicates whether it's an output port (true) or input port (false).

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

Example:

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

ConnectionEvents

Events for connection lifecycle and validation.

ConnectionEvents<T>({
  ValueChanged<Connection>? onCreated,
  ValueChanged<Connection>? onDeleted,
  ValueChanged<Connection?>? onSelected,
  ValueChanged<Connection>? onTap,
  ValueChanged<Connection>? onDoubleTap,
  ValueChanged<Connection>? onMouseEnter,
  ValueChanged<Connection>? onMouseLeave,
  void Function(Connection, Offset)? onContextMenu,
  void Function(String nodeId, String portId, bool isOutput)? onConnectStart,
  void Function(bool success)? onConnectEnd,
  ConnectionValidationResult Function(ConnectionStartContext<T>)? onBeforeStart,
  ConnectionValidationResult Function(ConnectionCompleteContext<T>)? onBeforeComplete,
})
EventTriggerSignature
onCreatedConnection addedValueChanged<Connection>
onDeletedConnection removedValueChanged<Connection>
onSelectedSelection changesValueChanged<Connection?>
onTapSingle tapValueChanged<Connection>
onDoubleTapDouble tapValueChanged<Connection>
onMouseEnterMouse enters pathValueChanged<Connection>
onMouseLeaveMouse leaves pathValueChanged<Connection>
onContextMenuRight-click(Connection, Offset)
onConnectStartDrag begins from port(nodeId, portId, isOutput)
onConnectEndDrag ends(bool success)
onBeforeStartBefore connection startsReturns validation result
onBeforeCompleteBefore connection completesReturns validation result

ConnectionStartContext

Context provided to onBeforeStart when starting a connection drag.

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.

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.

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:

ConnectionEvents<MyData>(
  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.

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

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

Example:

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

AnnotationEvents

Events for annotation interactions (sticky notes, groups).

AnnotationEvents({
  ValueChanged<Annotation>? onCreated,
  ValueChanged<Annotation>? onDeleted,
  ValueChanged<Annotation?>? onSelected,
  ValueChanged<Annotation>? onTap,
  ValueChanged<Annotation>? onDoubleTap,
  void Function(Annotation, Offset)? onContextMenu,
  ValueChanged<Annotation>? onMouseEnter,
  ValueChanged<Annotation>? onMouseLeave,
})
EventTriggerSignature
onCreatedAnnotation createdValueChanged<Annotation>
onDeletedAnnotation deletedValueChanged<Annotation>
onSelectedSelection changesValueChanged<Annotation?>
onTapSingle tapValueChanged<Annotation>
onDoubleTapDouble tapValueChanged<Annotation>
onContextMenuRight-click(Annotation, Offset)
onMouseEnterMouse enters boundsValueChanged<Annotation>
onMouseLeaveMouse leaves boundsValueChanged<Annotation>

SelectionState

Provided to onSelectionChange when selection changes.

class SelectionState<T> {
  final List<Node<T>> nodes;
  final List<Connection> connections;
  final List<Annotation> annotations;

  bool get hasSelection;
}

Example:

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

FlowError

Error information passed to onError.

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

Example:

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

Complete Example

NodeFlowEditor<WorkflowData>(
  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:

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

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

On this page