/**
* @file Example demonstrating absolute port placement and inset ports.
* - Absolute positioning: Ports at specific x,y coordinates relative to node
* - Unit modes: "px" (default) or "percent" for responsive positioning
* - Inset ports: Ports placed inside the node boundary
*/
import * as React from "react";
import { NodeEditor } from "../../../../../NodeEditor";
import type { NodeDefinition, PortInstanceContext } from "../../../../../types/NodeDefinition";
import type { NodeEditorData, AbsolutePortPlacement } from "../../../../../types/core";
import { absolutePercent } from "../../../../../core/port/spatiality/placement";
import styles from "./AbsolutePortExample.module.css";
const INITIAL_DATA: NodeEditorData = {
nodes: {
"absolute-1": {
id: "absolute-1",
type: "absolute-ports",
position: { x: 100, y: 100 },
size: { width: 200, height: 150 },
data: { portCount: 4 },
},
"percent-1": {
id: "percent-1",
type: "percent-ports",
position: { x: 400, y: 100 },
size: { width: 200, height: 150 },
data: {},
},
"inset-1": {
id: "inset-1",
type: "inset-ports",
position: { x: 700, y: 100 },
size: { width: 180, height: 120 },
data: {},
},
"mixed-1": {
id: "mixed-1",
type: "mixed-ports",
position: { x: 250, y: 320 },
size: { width: 220, height: 180 },
data: { dynamicCount: 3 },
},
},
connections: {},
};
/**
* Node with absolute positioned ports around the perimeter
*/
const absolutePortsDefinition: NodeDefinition = {
type: "absolute-ports",
displayName: "Absolute Ports",
defaultSize: { width: 200, height: 150 },
ports: [
{
id: "corner-tl",
type: "input",
label: "TL",
position: { mode: "absolute", x: 0, y: 0 } as AbsolutePortPlacement,
},
{
id: "corner-tr",
type: "output",
label: "TR",
position: { mode: "absolute", x: 200, y: 0 } as AbsolutePortPlacement,
},
{
id: "corner-bl",
type: "input",
label: "BL",
position: { mode: "absolute", x: 0, y: 150 } as AbsolutePortPlacement,
},
{
id: "corner-br",
type: "output",
label: "BR",
position: { mode: "absolute", x: 200, y: 150 } as AbsolutePortPlacement,
},
// Dynamic ports along the top edge
{
id: "dynamic-top",
type: "input",
label: "Top",
position: { mode: "absolute", x: 50, y: 0 } as AbsolutePortPlacement,
instances: ({ node }: PortInstanceContext) => Number(node.data?.portCount ?? 2),
createPortId: ({ index }) => `dynamic-top-${index + 1}`,
createPortLabel: ({ index }) => `T${index + 1}`,
},
],
renderNode: () => (
Absolute Ports
Ports at corners + dynamic top ports
),
};
/**
* Node with percentage-based port positioning (responsive to node size)
*/
const percentPortsDefinition: NodeDefinition = {
type: "percent-ports",
displayName: "Percent Ports",
defaultSize: { width: 200, height: 150 },
ports: [
{
id: "percent-tl",
type: "input",
label: "0%",
position: absolutePercent(0, 0),
},
{
id: "percent-tr",
type: "output",
label: "100%",
position: absolutePercent(100, 0),
},
{
id: "percent-bl",
type: "input",
label: "0%",
position: absolutePercent(0, 100),
},
{
id: "percent-br",
type: "output",
label: "100%",
position: absolutePercent(100, 100),
},
{
id: "percent-center",
type: "output",
label: "50%",
position: absolutePercent(50, 50),
},
{
id: "percent-quarter",
type: "input",
label: "25%",
position: absolutePercent(25, 75),
},
],
renderNode: () => (
Percent Ports
Ports at % positions (responsive)
Resize to see ports move!
),
};
/**
* Node with inset ports (inside the node boundary)
*/
const insetPortsDefinition: NodeDefinition = {
type: "inset-ports",
displayName: "Inset Ports",
defaultSize: { width: 180, height: 120 },
ports: [
{
id: "inset-left-1",
type: "input",
label: "In 1",
position: { side: "left", align: 0.3, inset: true },
},
{
id: "inset-left-2",
type: "input",
label: "In 2",
position: { side: "left", align: 0.7, inset: true },
},
{
id: "inset-right-1",
type: "output",
label: "Out 1",
position: { side: "right", align: 0.3, inset: true },
},
{
id: "inset-right-2",
type: "output",
label: "Out 2",
position: { side: "right", align: 0.7, inset: true },
},
],
renderNode: () => (
Inset Ports
Ports inside node boundary
),
};
/**
* Node mixing absolute, inset, and normal ports
*/
const mixedPortsDefinition: NodeDefinition = {
type: "mixed-ports",
displayName: "Mixed Placement",
defaultSize: { width: 220, height: 180 },
ports: [
// Normal side ports
{
id: "normal-left",
type: "input",
label: "Normal",
position: "left",
},
{
id: "normal-right",
type: "output",
label: "Normal",
position: "right",
},
// Inset ports
{
id: "inset-top",
type: "input",
label: "Inset",
position: { side: "top", inset: true },
},
// Absolute port in center
{
id: "center",
type: "output",
label: "Center",
position: { mode: "absolute", x: 110, y: 90 } as AbsolutePortPlacement,
},
// Dynamic absolute ports along bottom
{
id: "bottom-dynamic",
type: "output",
label: "Bottom",
position: { mode: "absolute", x: 40, y: 180 } as AbsolutePortPlacement,
instances: ({ node }: PortInstanceContext) => Number(node.data?.dynamicCount ?? 2),
createPortId: ({ index }) => `bottom-${index + 1}`,
createPortLabel: ({ index }) => `B${index + 1}`,
},
],
renderNode: ({ node }) => {
const dynamicCount = Number(node.data?.dynamicCount ?? 2);
return (
Mixed Placement
Normal + Inset + Absolute
Dynamic: {dynamicCount} bottom ports
);
},
};
const NODE_DEFINITIONS: NodeDefinition[] = [
absolutePortsDefinition,
percentPortsDefinition,
insetPortsDefinition,
mixedPortsDefinition,
];
export const AbsolutePortExample: React.FC = () => {
return (
Absolute Ports (px): Positioned at specific x,y pixel coordinates
Percent Ports (%): Positioned at percentage of node size (0-100), responsive to resize
Inset Ports: Placed inside the node boundary (inset: true)
Dynamic Ports: Multiple instances with computed positions
);
};
export default AbsolutePortExample;