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.

Loading Demo...
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.

PlacementPosition
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 pointAn 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.

Loading Demo...

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:

Loading Demo...

Connect valid input succeeds. Try wrong direction and Try wrong type report a rejection and leave the model unchanged.

FieldConnection behavior
direction: 'input'May be a target; rejected as a source
direction: 'output'May be a source; rejected as a target
direction: 'both' or omittedPermits either endpoint role
connectionTypeIf 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.

Loading Demo...

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.

Loading Demo...

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.

CommandOutcome for attached edges
replacement suppliedUpdate 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.