Skip to content
D
Documentation

Validate connections

how-to
2 min readUpdated

Refuse connections that break an application rule, and give users a reason for each refusal.

Use a registered validator for rules that depend on your diagram's data, such as preventing a Sink from acting as a connection source. The validator evaluates a proposed connection on the live canvas; framework bindings still use the same model and rule.

Mount the diagram and register a rule

The examples mount a small Source → Transform → Sink diagram. A registered rule rejects any proposal whose source node has data.role === 'sink'; returning a string supplies the refusal reason, while true allows the proposal. The callback can also update the small status line so the reason appears in the view.

For typed graph data and mounting the canvas in JavaScript, Angular, Qwik, React, or Vue, see Build runnable workflows. This page adds the connection rule: register registerConnectionValidator when the view mounts and keep its returned disposer for teardown.

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

const host = document.createElement('div');
host.style.height = '500px';
host.style.width = '100%';
document.body.append(host);

const status = document.createElement('p');
status.textContent = 'Try drawing a connection out of the Sink.';
document.body.insertBefore(status, host);

const nodes = [
  { id: 'source', position: { x: 80, y: 120 }, size: { width: 120, height: 46 }, label: 'Source', data: { role: 'source' } },
  { id: 'transform', position: { x: 320, y: 120 }, size: { width: 120, height: 46 }, label: 'Transform', data: { role: 'transform' } },
  { id: 'sink', position: { x: 560, y: 120 }, size: { width: 120, height: 46 }, label: 'Sink', data: { role: 'sink' } },
];
const edges = [];

const dispose = registerConnectionValidator(({ sourceNode }) => {
  if (sourceNode.data?.role === 'sink') {
    status.textContent = 'Refused: a Sink has no outputs';
    return 'A Sink has no outputs';
  }
  return true;
});

const instance = render({ nodes, edges }, host);
instance.fitView();
window.addEventListener('pagehide', dispose, { once: true });

When the canvas mounts, it shows Source, Transform, and Sink with no links. Drag from Source to Transform or Transform to Sink to make a legal connection; dragging out of Sink leaves no link and changes the status line to “Refused: a Sink has no outputs.” The validator's returned string is the rule's reason; the status line is application UI updated by the same callback.

Validator contract

Write the rule against the source and target nodes. Return true to allow the proposal, false to veto without a reason, or a string to veto with that reason. Every registered validator must pass, so a single veto refuses the connection.

Return valueTypeEffect
AllowtrueAccepts this validator's candidate.
VetofalseRefuses without a reason string.
Veto with reasonstringRefuses and supplies the returned text as the reason.

Pitfalls

  • The validator registry is process-global, not per canvas. Keep the disposer returned by registerConnectionValidator and call it when this view unmounts; otherwise its rule also applies to other diagrams in the same process.
  • clearConnectionValidators removes every registered validator, not only the current view's. Use it only when you intentionally want a clean registry; normal teardown calls the view's own disposer.
  • Port direction and type compatibility are separate built-in validation layers. Use Configure ports for rules expressed by port direction or data types; register a custom validator for application-specific rules.

See it running

Open the live connection-validation demo to try a rejected Sink connection and accepted connections in the browser. Its source drives the connection pipeline with a registered validator.

Was this page helpful?