Skip to content
D
Documentation

Ports and connection rules

concept
2 min readUpdated

Ports express where a link may attach. Grafloria combines port anatomy, declared data types, and custom validators to decide whether a connection is offered.

mermaid
flowchart LR
  S["Source port"] --> A["Anatomy\ndirection and caps"]
  A --> V["Custom validators"]
  V --> D["Connection offered"]

Use a declared PortSpec when a node needs explicit direction, a connection cap, or a particular glyph:

html
<div id="diagram" style="height: 320px; width: 640px"></div>
ts
import { render } from '@grafloria/element';

const spec = {
  nodes: [
    {
      id: 'transform',
      position: { x: 240, y: 120 },
      size: { width: 170, height: 90 },
      label: 'Transform',
      ports: [
        { id: 'in', side: 'left', type: 'input' },
        { id: 'out', side: 'right', type: 'output' },
        {
          id: 'errors',
          side: 'bottom',
          type: 'output',
          maxConnections: 1,
          shape: { shape: 'diamond', size: 12 },
          label: { text: 'errors', layout: 'outside' },
        },
      ],
    },
  ],
  edges: [],
};

const container = document.getElementById('diagram')!;
container.style.height = '320px';
container.style.width = '640px';
const instance = render(spec, container);
instance.fitView(24);

The mounted diagram shows the Transform node with its declared input, output, and labelled diamond-shaped error port. The errors port accepts one connection; the other declared port has no such cap.

The render() call returns a DiagramInstance, so use the instance for view operations and reach its engine only when you need engine configuration.

The connection checks

1. Anatomy

The port's type controls direction: an input cannot start a wire, and an output cannot receive one. bi ports can do both. Caps control how many links a port accepts. Use maxConnections for the legacy total cap, or gating when you need separate incoming and outgoing caps or directional connectability.

ts
const ports = [
  { id: 'out', side: 'right', type: 'output', gating: { fromMaxLinks: 3 } },
  { id: 'in', side: 'left', type: 'input', gating: { toMaxLinks: 1 } },
];

The engine checks the source port first and the target port second. A full target is highlighted as invalid during a drag, and the connection is not offered.

2. Custom validation

Use registerConnectionValidator for a rule that depends on application data, such as forbidding a connection from reaching a node with the role sink. The validator receives a connection candidate containing both nodes, both ports, and an optional existing link when the user reconnects an edge. Return true to allow the candidate or false (or a reason string) to veto it.

ts
import { render } from '@grafloria/element';
import { registerConnectionValidator } from '@grafloria/renderer';

const container = document.getElementById('diagram')!;
container.style.height = '320px';
container.style.width = '640px';
const instance = render(
  {
    nodes: [
      {
        id: 'source',
        position: { x: 40, y: 100 },
        size: { width: 140, height: 70 },
        label: 'Source',
        data: { role: 'source' },
        ports: [{ id: 'out', side: 'right', type: 'output' }],
      },
      {
        id: 'sink',
        position: { x: 300, y: 100 },
        size: { width: 140, height: 70 },
        label: 'Sink',
        data: { role: 'sink' },
        ports: [
          { id: 'in', side: 'left', type: 'input' },
        ],
      },
    ],
    edges: [],
  },
  container,
);

const disposeValidator = registerConnectionValidator(({ targetNode }) => {
  return targetNode.getData('role') === 'sink'
    ? 'A sink cannot receive a connection.'
    : true;
});

instance.fitView(24);

The mounted diagram shows Source and Sink. The custom rule vetoes a connection whose target node has the role sink, so dragging from Source to Sink produces no link.

Keep validation scoped to the view

Connection validators are process-global, not per canvas. Keep the returned disposer and call it when the view unmounts. If the application owns the entire registry and needs a clean slate, call clearConnectionValidators as part of teardown. Otherwise a validator can affect another route, a remounted canvas, or a second development render.

In the view's unmount or destroy hook, call disposeValidator() and then instance.dispose(). Do not run either call immediately after mounting: that removes the live diagram and its rule.

Was this page helpful?