Diagram - Ports
Place fixed, proportional and measured ports, constrain connections, and update occupied ports with explicit policies.
#Usage
import { Diagram } from '@primeui/vue-diagram';<Diagram.Root :store="store" page-id="page">
<Diagram.NodeDef node-type="task" v-slot="{ node }">
<strong>{{ node.data?.['title'] }}</strong>
</Diagram.NodeDef>
<Diagram.NodePortsDef node-type="task" v-slot="{ ports }">
<Diagram.Port v-for="port in ports" :key="port.id" :port-id="port.id" v-slot="{ getPortProps }">
<button
v-bind="getPortProps({
type: 'button',
'aria-label': 'Connect ' + port.id,
style: {
position: 'absolute', width: '10px', height: '10px', padding: 0,
transform: 'translate(-50%, -50%)',
left: port.position.x + 'px', top: port.position.y + 'px'
}
})"
/>
</Diagram.Port>
</Diagram.NodePortsDef>
<Diagram.Viewport label="Task connections" style="height: 24rem" />
</Diagram.Root>Each portId must already exist in its node's saved ports. The prop getter
registers the native button as both a measured anchor and connection control;
it does not create the port record.
This skeleton assumes an initialized store with page page. Use First Diagram for model creation, style imports and store disposal.
A port is a named attachment point on a node. Put the port in the node's ports
array, then reference its ID in a connector endpoint. Use stable port IDs so
connections continue to identify the same attachment after an edit.
#Fixed and proportional positions
Choose Grow width 50%. The upper source's fixed port keeps its authored local point. The lower source's dynamic port follows the right-center of its effective bounds. Both attached connectors follow their respective ports. Undo restores the source widths.
import { type DiagramNodeRecord } from '@primeui/vue-diagram';
type PortRecord = DiagramNodeRecord['ports'][number];
const fixed: PortRecord = {
id: 'fixed-out',
position: { x: 160, y: 45 },
direction: 'output'
};
const proportional: PortRecord = {
id: 'dynamic-out',
mode: 'dynamic',
position: { x: 1, y: 0.5 },
direction: 'output'
};These are port records; attach them to a node as shown in the demo's complete source.
| Placement | Position |
|---|---|
Omitted mode or mode: 'fixed' | Node-local world units; the point does not scale when the node resizes |
mode: 'dynamic' | Normalized fractions of effective width and height: (0, 0) is top-left, (1, 0.5) is right-center |
| Measured point | An HTML marker's center converted to node-local coordinates; overrides either declared mode |
Node transforms place the local point in the scene, including rotation and
parent transforms. A connector endpoint such as
{ kind: 'node', nodeId: 'source', portId: 'dynamic-out' } stays attached to that
port. Omit portId to dock to the node body instead. Body docking uses the node's
picking boundary; a custom symbol path does not supply arbitrary exact-outline
docking. See Shapes.
#Custom boundary controls
Put interactive boundary controls in Diagram.NodePortsDef. This region is a
sibling of the clipped node body, so circles remain complete at an ellipse or
diamond boundary. Viewport and ancestor-container clips still apply.
The slot receives node, geometry, actions, and ports. Each port has its
saved id and configured node-local position, before DOM measurement overrides.
Position the control from this value. Do not position it from its own measured
scene anchor. The region shares the node's local origin and follows its rotation.
A matching ports definition replaces the native decorative dots. A native button
bound through Diagram.Port.getPortProps supplies its measured anchor and connection interaction.
The viewport removes that endpoint's built-in connection button, leaving one
focusable control. Ports without a custom control keep the built-in control.
Use a native button when possible. For a non-native host, Diagram supplies
role="button" and tabindex="0" when those attributes are absent. Explicit
author values remain in place. Disabling a port that owns an active connection
cancels that interaction before it can commit.
Choose Raise output and watch the attached connector follow the button. Center output restores its position. This presentation change creates no model edit or Undo step. The measurement owner observes both body and ports, but only the body supplies intrinsic node size. Stable measurements do not rewrite the saved port position.
Outside a ports definition, Diagram.Port only measures an anchor in node
content. It does not make the host interactive. Keep one marker per endpoint.
The configured point remains the fallback before measurement and after the
marker is removed. Port measurement does not require intrinsic node sizing.
Custom HTML controls are not automatically included in portable exports.
#Pointer acquisition and keyboard input
Ports have a circular 22 client CSS pixel acquisition radius by default.
The visible dot can remain small while its target spans 44px. The radius stays
constant across Diagram zoom, device pixel ratio, and positive axis-aligned outer
CSS scaling. Viewport and ancestor-container clipping still apply.
<Diagram.Viewport :port-hit-radius="28" />portHitRadius accepts finite nonnegative numbers. 0 disables proximity
acquisition while direct controls and keyboard activation remain available.
Explicit port controls and edit handles take precedence. Otherwise, Diagram
chooses the nearest exposed detailed port, with port ID breaking ties. It does
not skip an invalid candidate to find a farther valid one. Ordinary buttons,
links, and fields retain their events unless marked as port controls.
Give each custom button an accessible name. Drag between ports, or activate the
source and target buttons with Enter or Space. Escape cancels. :disabled="true" on Diagram.Port
disables the custom control and excludes that endpoint from pointer and keyboard
acquisition. Diagram operation permissions also apply. Core still validates
capacity, type, and final acceptance.
#Direction and connection type
Port direction and type describe compatibility. They do not select a connector path such as straight or Bézier. This example tries three targets using the session's connection gateway:
Connect valid input succeeds. Try wrong direction and Try wrong type report a rejection and leave the model unchanged.
| Field | Connection behavior |
|---|---|
direction: 'input' | May be a target; rejected as a source |
direction: 'output' | May be a source; rejected as a target |
direction: 'both' or omitted | Permits either endpoint role |
connectionType | If both attached ports have nonempty tags, the tags must match; an omitted tag does not require a match |
Connection policies apply through session.value.connections.execute() and the
canvas authoring flow. Direct host transactions are application-owned; use the
connection gateway for interactive edits and inspect its diagnostics. Additional
node-type, cycle, self-loop and approval rules are described in the
Connection rules.
#Capacity
Set capacity to a nonnegative integer to limit endpoint attachments at a port.
Omit it for no port-specific limit. A capacity of zero permits no attachments.
Capacity counts endpoint attachments, so both ends of a self-loop at the same
port count when self-loops are otherwise allowed.
Choose Connect first target, then Try second target. The source capacity is one, so the second attempt is rejected. Choose Undo, then try the second target again: capacity is available. A node-wide capacity can separately count all incident endpoint attachments, including body connections.
#Update or remove an occupied port
Use the kind: 'port' connection command for a port already in a node. A
replacement retains or redirects its attached endpoints to the replacement ID.
Removal needs an explicit policy when the port is occupied.
Choose Move occupied port to replace its point while retaining its ID. The edge follows the new position. Choose a Removal policy, read its outcome below the controls, then choose Apply removal. Try each policy, using Undo or Reset between attempts to return to the original attachment. Reset also restores the Reject policy.
| Command | Outcome for attached edges |
|---|---|
replacement supplied | Update the node's port list and rebind attached endpoints to replacement.id; validate prospective connections |
No replacement, omitted policy or 'reject' | Reject occupied-port removal; keep the port and edges |
No replacement, 'detach' | Remove the port; turn each affected endpoint into a point at its current scene position, expressed in the connector's coordinate space |
No replacement, 'delete' | Remove the port and its attached connectors |
The following action reads the current session in a child component rendered
inside Diagram.Root. In setup, context refs require .value.
import { useDiagramContext } from '@primeui/vue-diagram';
const { session } = useDiagramContext();
const result = session.value.connections.execute({
kind: 'port',
nodeId: 'source',
portId: 'occupied-out',
policy: 'detach'
});The port and connected-edge changes form one transaction and one Undo step. Undo restores their previous records together. A replacement can also change direction, type or capacity; incompatible prospective attachments reject the whole operation. Current editing permissions still apply to the node and connected edges. Detach explicitly permits the resulting dangling points even when ordinary dangling connection creation is disabled.
An unoccupied port can be removed without a policy. The command requires an
existing portId; adding a new port is a separate update to the node's port
array. Use a new ID and preserve any ports referenced by existing edges.
#Controls and reset
The demos expose named buttons for the same operations, so dragging is optional.
Use Create connections for port dragging,
keyboard connection mode and cancellation. Undo uses DiagramCommandButton
and its current command policy. Reset replaces the owned store, clears history
and local controls, remounts the root, and restores the initial fitted view.
Measured layout changes remain presentation state rather than saved edits.