Connector API
On this page
Connector must render inside a Node. It owns (or adopts) a core ConnectorMirror registered against the parent node.
| Prop | Meaning |
|---|---|
id | Stable domain identity; minted when omitted — supply it for persistence |
name | Required. The connector’s key within its parent node |
rules | Partial<ConnectorRules> admission rules (see below) |
metadata | SnapLineMetadata attached to the connector |
callbacks | ConnectorCallbacks (new-line payload resolver plus pointer, candidate, and connection events) |
surfaceStrategies | Custom target hit-test and anchor strategies |
children | Snippet receiving an action; apply it with use: to one custom HTML or SVG input root |
colliderRadius | Target snapping radius override |
edgePan | Pan the camera when dragging a line near the viewport edge (default true) |
connectorObject | Adopt an existing ConnectorMirror |
data | Extra data-* attributes for the port element |
object() returns the underlying ConnectorMirror. name and connectorObject are construction-time identities; every other prop is
reactive.
Without a child snippet, Connector renders its default port div. A custom
root can be an SVG stroke:
<svg viewBox="0 0 160 80">
<Connector name="surface">
{#snippet children(bindConnector)}
<path
use:bindConnector
d="M 0 0 H 160 V 80 H 0 Z"
fill="none"
stroke="transparent"
stroke-width="16"
pointer-events="stroke"
/>
{/snippet}
</Connector>
</svg> The custom element is the native source hit area. SnapLine does not run a
manual source hit test. For target snapping, set colliderRadius large enough
to enclose the custom shape; any configured targetHitTest callbacks replace
the default center candidate inside that broad-phase circle.
Connection rules
| Rule | Default | Meaning |
|---|---|---|
maxOutgoing | "unlimited" | Maximum outgoing lines (previews reserve a slot); 0 makes the connector target-only |
maxIncoming | 1 | Maximum settled incoming lines; 0 makes the connector source-only |
reconnect | true | Allow dragging a settled line’s end to a new target |
allowParallel | false | Both endpoints must allow parallel lines between the same pair |
onFull | "reject" | Policy when a proposal would exceed maxIncoming: "reject" it, or "replace-oldest" to evict the oldest incoming lines |
isValidConnection | — | Line-aware admission predicate; synchronous and side-effect free, called repeatedly during candidate discovery and rechecked on drop. Either endpoint may veto |
An omitted rules prop therefore yields an unlimited-outgoing,
single-incoming connector that rejects extra incoming lines.
Topology methods on ConnectorMirror such as createLine, deleteLine,
and deleteAllLines are @internal. Change
topology by updating your canonical LineRecords through ControlledGraph.
Spawning a node from an empty-space drop
onDragEnd reports why a drag ended through outcome:
outcome | Meaning |
|---|---|
"connected" | Settled onto an admitting target |
"refused" | Dropped over a target that declined (capacity, predicate, roles) |
"empty-space" | No connector was resolved under the pointer |
"cancelled" | Pointer cancel or teardown |
"empty-space" is the hook for “drag a line out and drop it on the canvas to
make a new node”. SnapLine deliberately has no node-change request channel:
your framework decides when a node exists, so you mount the node and add
the line record yourself.
onDragEnd: ({ outcome, position, connector }) => {
if (outcome !== "empty-space") return;
const nodeId = mint();
nodes = [...nodes, { id: nodeId, x: position.x, y: position.y }];
graph.setLines([
...lines,
{
id: mint(),
fromConnectorId: connector.connectorId,
toConnectorId: `${nodeId}:input`,
},
]);
}; The record names a connector that has not mounted yet. That is fine: it stays latent, and connector registration schedules the pass that converges it, so the line appears the moment the node does. Note the dragged preview is discarded at drop like any unaccepted gesture — the new line appears with the node rather than continuing the stroke.
A role-ineligible connector (maxIncoming: 0) is filtered out during
candidate resolution, so dropping on one reports "empty-space", not "refused". A refusal is a connector that resolves as a candidate and then
declines at drop, which is what isValidConnection’s phase distinguishes.