import React$1, { Component, ReactNode, ErrorInfo, CSSProperties } from 'react';
import { Node, FitViewOptions, Connection, NodeTypes, EdgeTypes, Edge, ConnectionMode, ReactFlowInstance, Viewport } from '@xyflow/react';
export { Connection, ConnectionMode, EdgeTypes, FitViewOptions, NodeTypes, Position, ReactFlowInstance, Viewport, XYPosition } from '@xyflow/react';
export { ColorMode, OtdrTraceStrip, OtdrTraceStripColors, OtdrTraceStripEvent, OtdrTraceStripEventKind, OtdrTraceStripProps, OtdrTraceStripTrace, formatOtdrDistance, otdrEventKindLabel, otdrTraceStripPalette } from './otdr.mjs';
type FibreFlowCableKind = 'simple' | 'tube';
interface FibreFlowEndpoint {
tube?: number;
fiber: number;
}
interface FibreFlowSplice {
id: string;
from: FibreFlowEndpoint;
to: FibreFlowEndpoint;
lossDb?: number;
circuitId?: string;
label?: string;
color?: string;
}
interface FibreFlowCable {
id: string;
label: string;
kind?: FibreFlowCableKind;
fiberCount?: number;
tubeCount?: number;
fibersPerTube?: number;
tubeStartIndex?: number;
fiberStartIndex?: number;
tubeColorMap?: Record;
jacketColor?: string;
distanceLabel?: string;
distanceMeters?: number;
}
interface FibreFlowClosure {
id: string;
chamberId?: string;
leftCable: FibreFlowCable;
rightCable: FibreFlowCable;
splices: FibreFlowSplice[];
}
interface FibreFlowCircuit {
id: string;
label: string;
color?: string;
}
/**
* Rack device width configuration
* @public
*/
declare enum Width {
/** Full width device (19" rack width) */
FULL = "full",
/** Half width device (9.5" rack width) */
HALF = "half"
}
/**
* Port configuration for network devices
* @public
*/
/**
* Port type enumeration
* @public
*/
type PortType = "ethernet" | "rj45" | "fiber" | "console" | "mgmt" | "usb" | "vga" | "serial" | "power_ac" | "power_c13" | "power_c19";
/**
* Cable types used for connected network ports.
* @public
*/
type PortCableType = "smf" | "cat6" | "om3" | "om4" | "om5" | "cat5e" | "cat6a" | "cat7" | "fiber" | "ethernet";
/**
* Device image fit options.
*
* Values mirror object-fit terms for API ergonomics and are mapped to valid
* background-size values by renderers where needed.
*
* @public
*/
type DeviceImageFit = "cover" | "contain" | "contain-height" | "fill" | "none" | "scale-down";
/**
* Device image repeat options.
* @public
*/
type DeviceImageRepeat = "no-repeat" | "repeat" | "repeat-x" | "repeat-y";
/**
* Shared image configuration for rack-mounted devices and device nodes.
* @public
*/
interface DeviceImageOptions {
/** Optional front image URL for device background (PNG, SVG, JPEG, etc.) */
frontImageUrl?: string;
/** Optional rear image URL for device background (PNG, SVG, JPEG, etc.) */
rearImageUrl?: string;
/** Optional attribution URL for front image (e.g. source/license link) */
frontImageAttribution?: string;
/** Optional attribution URL for rear image (e.g. source/license link) */
rearImageAttribution?: string;
/** Optional background sizing for device images */
imageFit?: DeviceImageFit;
/** Optional background position for device images */
imagePosition?: string;
/** Optional background repeat for device images */
imageRepeat?: DeviceImageRepeat;
/** When true, stretch device image to fill the device bounds (overrides imageFit) */
stretchToFit?: boolean;
}
/**
* Explicit handle placement for a device/PDU port.
* @public
*/
interface PortPosition {
/**
* Handle side on the node. When the offset is omitted, ports on the same
* side and face are distributed automatically across groups.
*/
side?: "top" | "right" | "bottom" | "left";
/** Side offset as percentage (0-100) */
offsetPercent?: number;
/** Side offset in pixels */
offsetPx?: number;
/** Absolute X position as percentage (0-100) of the owning face frame (element centre). Requires `yPercent`. */
xPercent?: number;
/** Absolute Y position as percentage (0-100) of the owning face frame (element centre). Requires `xPercent`. */
yPercent?: number;
/**
* Authored element width as a percentage (0-100) of the owning face frame.
*
* Contractual: percent-based geometry is the render contract shared by ports,
* cages, and bays across every surface (2D elevation, 3D, faceplate). Paired
* with `heightPercent` it is the element's bounding box, and because it is
* frame-relative it survives image rescaling.
*/
widthPercent?: number;
/**
* Authored element height as a percentage (0-100) of the owning face frame.
* Contractual — see {@link PortPosition.widthPercent}.
*/
heightPercent?: number;
}
/**
* Authoring input for port/bay geometry: the canonical {@link PortPosition}
* object, or a semantic edge string such as `'left-bottom'` that
* {@link normalizePortPositionInput} expands. Renderers always normalize
* before reading fields.
* @public
*/
type PortPositionInput = PortPosition | string;
/**
* IEC 60320 appliance couplers modelled by the renderer and the power
* validation mate table. Inlets are even-numbered, connectors odd.
* @public
*/
type PowerConnectorType = "C13" | "C14" | "C15" | "C16" | "C19" | "C20" | "C21" | "C22";
/**
* Recommended simple port input for device and PDU rendering.
* Pass a flat array in `ports`; `react-networks` groups entries into front/rear
* `PortBlock[]` internally using each port's `face` and `group`.
* @public
*/
interface DevicePort {
/** Stable handle identifier override (defaults to `group + label`, or `label` for power) */
id?: string;
/** Display label for the port */
label: string;
/** Front/rear device face. `back` is accepted as an alias for `rear`. */
face?: DeviceFace | "back";
/** Optional block/group name (for example `Network Ports`, `QSFP28`, `Power Ports`) */
group?: string;
/** Optional source/detected family label used for visual kind inference */
sourceTypeLabel?: string;
/** Whether the port is currently connected */
connected?: boolean;
/** Type of port for styling and validation */
type?: PortType;
/** Cable type for connection validation */
cableType?: PortCableType;
/** Optional power connector type for rendering (mainly for PDU ports) */
connectorType?: PowerConnectorType;
/** Optional compatibility keys. When omitted, defaults are derived from type/cable/connector data. */
compatibility?: string[];
/** Optional sort order within a face/group bucket */
order?: number;
/**
* Optional placement hint.
* Use `side` with `offsetPercent`/`offsetPx` for edge-anchored placement,
* or `xPercent`/`yPercent` for exact coordinates.
* With no resolvable placement, the renderer distributes ports across the
* whole face, including ports from other groups. A lone `xPercent` or
* `yPercent` is incomplete and uses that automatic placement.
* Semantic edge strings (e.g. `'left-bottom'`) are also accepted and
* normalized by the renderer.
*/
position?: PortPositionInput;
}
interface Port {
/** Stable handle identifier override (defaults to `label`) */
id?: string;
/** Display label for the port */
label: string;
/** Optional source/detected family label used for visual kind inference */
sourceTypeLabel?: string;
/** Whether the port is currently connected */
connected: boolean;
/** Type of port for styling and validation */
type?: PortType;
/** Cable type for connection validation */
cableType?: PortCableType;
/** Optional power connector type for rendering (mainly for PDU ports) */
connectorType?: PowerConnectorType;
/** Optional compatibility keys. When omitted, defaults are derived from type/cable/connector data. */
compatibility?: string[];
/** Optional explicit handle placement (object or semantic edge string) */
position?: PortPositionInput;
}
/**
* Group of related ports on a device
* @public
*/
interface PortBlock {
/** Name of the port group */
name: string;
/** Array of ports in this group */
ports: Port[];
}
/**
* Containable bay classification. Mirrors dc_designer `bay_kind`.
*
* A bay HOLDS a field-replaceable unit (drive, PSU, fan, line card, …) and —
* unlike a port — never mounts a cable handle. Transceiver cages are the one
* capability overlap (containable AND connectable): they are authored as
* ports with `bay` metadata (see `TemplatePort`) so the frozen handle-id
* grammar and cable endpoints survive optic swaps, and therefore do NOT appear
* as a `bayKind` value here — `'transceiver_cage'` lives on `TemplatePort.bay`.
*
* @public
*/
type TemplateBayKind = "storage_bay" | "psu_bay" | "fan_bay" | "pcie_slot" | "linecard_slot" | "supervisor_slot" | "fabric_slot" | "io_slot" | "other";
/**
* Occupancy/health of a single bay instance.
* `predicted` marks a modelled (not yet observed) state.
* @public
*/
type DeviceBayStatus = "ok" | "failed" | "predicted";
/**
* Per-bay instance state, keyed by bay id on node data. Rides on node data
* alongside `handleSideMap`; the renderer paints occupancy/failed treatment
* from it.
* @public
*/
interface DeviceBayInstanceState {
/** Whether the bay currently holds a unit. */
occupied?: boolean;
/** Health/occupancy status driving the bay marker's visual treatment. */
status?: DeviceBayStatus;
}
/**
* A per-slot sub-rectangle inside a bay (e.g. one socket in a DIMM bank, one
* drive position in a carrier). Geometry uses the same `PortPosition` percent
* contract as ports and bays.
* @public
*/
interface DeviceBaySlot {
/** Stable identifier for the slot (unique within its bay). */
id: string;
/** Human-readable label. */
label: string;
/** Slot centre + bounding box as percentages of the owning face frame. */
position?: PortPosition;
}
/**
* Containable-only element on a device face: the renderer draws an outline +
* label for it but never mounts a React Flow handle. The node-data analogue of
* the template `TemplateBay`; `face` selects the owning face (default
* front, `back` accepted as an alias for `rear`).
* @public
*/
interface DeviceBay {
/** Stable identifier for the bay (unique within the device). */
id: string;
/** Human-readable label drawn on the bay outline. */
label: string;
/** Bay classification (drives visual treatment and click payloads). */
bayKind: TemplateBayKind;
/** Front/rear device face. `back` is accepted as an alias for `rear`. */
face?: DeviceFace | "back";
/** Bay centre + bounding box as percentages of the owning face frame. */
position?: PortPosition;
/** Optional per-slot sub-rectangles. */
slots?: DeviceBaySlot[];
}
/**
* Network node types supported by the diagram
* @public
*/
type NetworkNodeType = "rack" | "switch" | "router" | "server" | "fiber" | "patch-panel" | "device" | "vertical-pdu" | "distribution-board" | "splice" | "splice-tray" | "tube" | "cable" | "multi-tube-cable" | "coupler" | "closure" | "fibre-split" | "fibre-flow-cable" | "fibre-flow-closure";
/**
* Device status states
* @public
*/
type DeviceStatus = "active" | "inactive" | "maintenance";
/**
* Device face orientations
* @public
*/
type DeviceFace = "front" | "rear";
type DiagramPortHandleRef = {
nodeId: string;
handleId: string;
};
type DiagramPortClickPayload = {
nodeId: string;
handleId: string;
groupName: string;
portLabel: string;
face: DeviceFace;
};
/**
* Reference to a single bay on a node — the bay analogue of
* {@link DiagramPortHandleRef}. Used by `NetworkDiagram.highlightedBays`.
* @public
*/
type DiagramBayRef = {
nodeId: string;
bayId: string;
};
/**
* Payload for a bay click — the bay analogue of {@link DiagramPortClickPayload}
* (`bayId`/`bayKind`/`bayLabel` in place of `handleId`/`groupName`/`portLabel`).
* @public
*/
type DiagramBayClickPayload = {
nodeId: string;
bayId: string;
bayKind: TemplateBayKind;
bayLabel: string;
face: DeviceFace;
};
/**
* Rack rendering layout modes.
* @public
*/
type RackLayoutMode = "single-face" | "split-face";
/**
* Rack mounting behavior for a device.
* @public
*/
type RackMountMode = "front" | "rear" | "both";
/**
* Power port side configuration
* @public
*/
type PowerSide = "left" | "right";
/**
* U numbering direction for racks
* @public
*/
type UNumberingDirection = "bottom-up" | "top-down";
/**
* Shared node data fields available across node types.
* Specific node kinds can narrow these fields via `NetworkNodeDataByType`.
* @public
*/
interface NetworkNodeDataBase extends DeviceImageOptions {
/** Display label for the node */
label: string;
/** Current status of the node/device */
status?: DeviceStatus | string;
/** Simple flat port input or grouped port blocks. Use `portGroups` / `rearPortGroups` for explicit grouped control. */
ports?: DevicePort[] | PortBlock[] | number;
/** Advanced grouped front port input */
portGroups?: PortBlock[];
/** Advanced grouped rear port input */
rearPortGroups?: PortBlock[];
/** Grouped rear port blocks (rack JSON alias for `rearPortGroups`) */
rearPorts?: PortBlock[];
/** List of connected node IDs */
connections?: string[];
/** NetBox integration ID */
netboxId?: number | string;
/** NetBox object type */
netboxType?: "device" | "rack" | "interface" | "cable" | string;
/** Inventory integration ID */
inventoryId?: number | string;
/** Inventory object type */
inventoryType?: "device" | "rack" | "interface" | "cable" | string;
/** Rack height in U units */
uHeight?: number;
/** Device position within rack in U units */
uPosition?: number;
/** Whether this node is currently mounted inside a rack container */
isRackMounted?: boolean;
/** Front/rear orientation for devices and rack faces */
face?: DeviceFace;
/** Rack rendering layout mode */
rackLayout?: RackLayoutMode;
/** Gap in pixels between front and rear faces in split-face mode */
rackFaceGapPx?: number;
/** Device mount behavior within a rack */
mountMode?: RackMountMode;
/** Device type specification */
deviceType?: string;
/**
* When false, device elevation renders as a status-colored block without front/rear photos.
* Defaults to true (show photos when URLs exist).
*/
showDeviceImages?: boolean;
/** Device manufacturer */
manufacturer?: string;
/** Device role in network */
role?: string;
/** Device width configuration */
width?: Width | number;
/** Power port side for rear face */
powerSide?: PowerSide;
/** Vertical PDU height in centimeters */
heightCm?: number;
/** Number of AC power ports */
powerPorts?: number;
/** Optional explicit PDU connector sequence */
portTypes?: Array<"C13" | "C19">;
/** Optional grouped PDU ports (supports custom positions/types) */
pduPortGroups?: PortBlock[];
/** Vertical PDU width in pixels */
widthPx?: number;
/** Optional management unit position for vertical PDUs */
mgmt_unit?: "top" | "middle" | "bottom";
/** U numbering direction for rack nodes */
uNumberingDirection?: UNumberingDirection;
/** Custom header text for rack nodes */
customHeaderText?: string;
/** Rack UI marker visibility */
showUMarkers?: boolean;
/** Holder position within tray (1-12) for splice nodes */
holderPosition?: number;
/** Loss value in dB for splice nodes */
lossDb?: number;
/** Splice mode */
mode?: "single" | "ribbon";
/** Legacy ribbon flag */
ribbon?: boolean;
/** Ribbon fiber IDs */
ribbonFiberIds?: number[];
/** Connector type for coupler nodes */
connector?: "SC" | "LC";
/** Number of fibers for coupler nodes (1, 2, or 4) */
couplerFiberCount?: 1 | 2 | 4;
/** Optional port labels for left side of coupler */
portLabelsLeft?: string[];
/** Optional port labels for right side of coupler */
portLabelsRight?: string[];
/** When true, coupler output (right) ports inherit left-side fiber color instead of default yellow. */
passThroughColor?: boolean;
/** Slot position for couplers inside panels (1-based, vertical slots aligned to right edge) */
slotPosition?: number;
/** Width override for panel-like container nodes */
panelWidth?: number;
/** Height override for panel-like container nodes */
panelHeight?: number;
/** Width override for closure nodes */
closureWidth?: number;
/** Height override for closure nodes */
closureHeight?: number;
/** Optional rack width in pixels (for rack nodes, default: 300px) */
rackWidthPx?: number;
/** Number of rows for container nodes (patch-panel, closure) */
rows?: number;
/** Number of columns for container nodes (patch-panel, closure) */
cols?: number;
/** Number of trays for closure nodes */
trayCount?: number;
/** Header text for container nodes */
header?: string;
/** Type of fibre splitter: 'splitter' or 'WDM' (default: 'splitter') */
splitterType?: "splitter" | "WDM";
/** Number of input fibers on the left (1 or 2, default: 1) */
inputCount?: number;
/** Number of output fibers on the right (1-64, default: 8) */
outputCount?: number;
/** Tube index (1-based) */
tubeIndex?: number;
/** Tube count for multi-tube cables */
tubeCount?: number;
/** Tube start index for multi-tube cables */
tubeStartIndex?: number;
/** Number of fibers per tube */
fibersPerTube?: number;
/** Total fiber count for cables */
fiberCount?: number;
/** Cable identifier */
cableId?: string;
/** Primary node color override */
color?: string;
/** Rotation in degrees for rotatable nodes */
rotation?: number;
/** Tube handle orientation */
startOn?: "left-center" | "left" | "right";
/** Rack metadata for integrations */
facilityId?: string | number | null;
/** Rack serial metadata */
serial?: string | null;
/** Asset tag metadata */
assetTag?: string | null;
/** Site metadata */
site?: string;
/** Tenant metadata */
tenant?: string;
/** Primary IPv4 metadata */
primaryIp4?: string;
/** Primary IPv6 metadata */
primaryIp6?: string;
/** Rack name metadata */
rack?: string | number | null;
/** Optional arrays used by inventory/netbox transforms */
racks?: unknown[];
devices?: unknown[];
cables?: unknown[];
/** UI color mode metadata */
colorMode?: "light" | "dark" | "system";
/** Generic dimensions used by some helpers */
height?: number;
headerHeight?: number;
/** Optional precomputed map for handle-to-face resolution */
handleSideMap?: Record;
/**
* Per-bay instance state keyed by bay id (occupancy/health). Rides on node
* data alongside `handleSideMap`; DeviceNode paints bay markers from it.
*/
bayState?: Record;
/** Additional extension metadata */
metadata?: Record;
}
/**
* Rack node data.
* @public
*/
interface RackNodeData extends NetworkNodeDataBase {
ports?: number;
}
/**
* What kind of power-distribution equipment a `vertical-pdu` node stands for.
* The renderer draws them all as a vertical strip; the label is carried so
* consumers and validation can tell a rack PDU from an RPP, a busway tap, or a
* sub-panel.
* @public
*/
type PduBoardType = "pdu" | "rpp" | "panel" | "busway" | "other";
/** Supply family of a PDU strip. @public */
type PduSupply = "ac" | "dc";
/**
* A/B designation of the branch circuit feeding a PDU. `primary` is the A
* feed, `secondary` the B feed; redundancy rules read it to tell the two
* halves of a 2N design apart.
* @public
*/
type PowerFeedType = "primary" | "secondary";
/**
* Which half of an IEC 60320 / IEC 60309 pairing a connector is. Inlets on
* equipment are male (pins); cord connectors are female (sockets).
* @public
*/
type PowerConnectorGender = "male" | "female";
/**
* Vertical PDU node data.
*
* Everything here is optional and additive — a PDU authored before the inlet
* model (label + `powerPorts` + `portTypes`) renders and validates exactly as
* it did, and the inlet handle is not mounted at all unless inlet data is
* present.
*
* The strip has two electrically distinct sides:
*
* - the **inlet** — where a branch circuit from a distribution-board breaker
* way lands (fixed wiring: IEC 60309, hardwired, C20, NEMA locking). One per
* strip, handle id `inlet`.
* - the **outlets** — where equipment cords land, handle ids `"1"…"N"`
* top-down under IEC 60320 mate rules.
*
* **Reserved handle ids.** `inlet` (and `incomer`, the distribution-board
* spelling) name the supply inlet. Do not give an outlet in `pduPortGroups` an
* `id` or `label` that lowercases to either: the strip would mount two handles
* with the same id. A declared outlet deliberately wins that collision — it
* classifies as a cord source, not as the supply inlet, so the cords it carries
* still validate — but the inlet handle then becomes unreachable. Number the
* outlets, or give them a distinct prefix (`rear-power-1`).
* @public
*/
interface VerticalPduNodeData extends NetworkNodeDataBase {
/**
* Free-text inlet connector designation, e.g. `"IEC 60309 32A"`,
* `"C20"`, `"NEMA L6-30P"`, `"hardwired"`. Free text because the inlet
* families a rack PDU can carry are regional and open-ended; the connector
* *mate* rules only apply to the IEC 60320 cord side (the outlets).
*/
inletConnector?: string;
/** Inlet current rating in amps (the branch-circuit rating the strip takes). */
inletAmps?: number;
/** Inlet gender — equipment inlets are normally `male`. */
inletGender?: PowerConnectorGender;
/** A/B designation of the feed landing on the inlet. */
feedType?: PowerFeedType;
/** What kind of distribution equipment this strip stands for. */
boardType?: PduBoardType;
/** Supply family. */
supply?: PduSupply;
/** Nominal supply voltage in volts (integer, e.g. 230). */
voltage?: number;
/** Supply phase count. Outlets are attributed round-robin across it. */
phases?: 1 | 3;
/**
* Rating in amps of the strip's own outlet/bank overcurrent protection
* (internal breakers or fuses), when it has any. PWR-A06 judges the cords
* below a strip that declares it against this rating instead of the upstream
* way's.
*/
outletProtectionA?: number;
/** Optional equipment name, distinct from the drawn `label`. */
name?: string;
}
/**
* Device-like node data (servers/switches/routers/generic device).
* @public
*/
interface DeviceNodeData extends NetworkNodeDataBase {
ports?: DevicePort[] | PortBlock[] | number;
/**
* Containable-only bays drawn as outlines + labels (never cable handles).
* Each bay's `face` selects the owning face (default front).
*/
bays?: DeviceBay[];
/**
* Nameplate/maximum power draw in **watts**. Named after NetBox's
* `DeviceType.airflow`-adjacent power fields (`maximum_draw`), which are watts
* — keep the unit in watts so a NetBox sync is a straight field copy.
*/
maximumDrawW?: number;
/**
* Allocated (design/budgeted) power draw in **watts**. NetBox's
* `allocated_draw`; used by load rules to sum a board way's committed load.
*/
allocatedDrawW?: number;
/**
* How many power feeds this device is expected to have (NetBox models this as
* redundant power ports). `2` marks an A+B device, so losing one feed is
* `degraded` rather than unpowered.
*/
expectedFeeds?: 1 | 2;
}
/**
* Switch state of a single breaker way.
* @public
*/
type BreakerSwitchState = "closed" | "open" | "tripped";
/**
* Phase label carried by a breaker way. Purely a label — the library does not
* model three-phase electrically (see PRD workstream C non-goals).
* @public
*/
type BreakerPhase = "L1" | "L2" | "L3" | "3P";
/**
* One breaker way (slot) on a distribution board. A way is not its own node in
* the physical view: it is a connection endpoint on the board, mounting exactly
* one power handle.
* @public
*/
interface BreakerWay {
/** Stable identifier for the way (unique within the board). */
id: string;
/** Human-readable label drawn on the way row (e.g. `1`, `L1-3`). */
label: string;
/** Pole count (1 = single-pole, 3 = three-pole). */
poles?: number;
/** Breaker rating in amps. */
ratingA?: number;
/** Phase label. `3P` (or `poles: 3`) marks a three-phase way. */
phase?: BreakerPhase;
/**
* Circuit voltage this way supplies, overriding the board's: line-to-neutral
* for a single-pole way, line-to-line for a 2-pole or three-phase way.
*/
voltageV?: number;
/**
* Optional explicit placement as percentages of the board frame. Omitted ways
* are auto-laid-out in the generated faceplate grid, which is the normal case.
*/
position?: PortPosition;
}
/**
* Per-way instance state keyed by way id, mirroring {@link DeviceBayInstanceState}.
* @public
*/
interface BreakerWayInstanceState {
/** Whether the breaker is closed, deliberately open, or tripped. */
switchState: BreakerSwitchState;
/** Optional health status, reusing the bay status vocabulary. */
status?: DeviceBayStatus;
}
/**
* Distribution board node data. Boards are free-floating by default; set
* {@link DistributionBoardNodeData.rackMountable} to opt a board into
* rack assignment.
* @public
*/
interface DistributionBoardNodeData extends NetworkNodeDataBase {
/** The board's breaker ways, rendered top-to-bottom in column order. */
ways: BreakerWay[];
/** Per-way switch/health state keyed by way id. */
wayState?: Record;
/**
* Nominal board voltage in volts — the legacy single figure. When neither
* {@link DistributionBoardNodeData.phaseVoltageV} nor
* {@link DistributionBoardNodeData.lineToLineVoltageV} is set, the load rules
* (PWR-B01/B02) use it unchanged for every way: as the line-to-neutral
* voltage of single-pole ways and the line-to-line voltage of 2-pole /
* three-phase ways. That is only right when all the board's ways are one
* kind (PWR-B03 flags boards where it cannot be); declare the explicit pair
* for a mixed board.
*/
voltageV?: number;
/**
* Nominal **line-to-neutral** (phase) voltage in volts — what a single-pole
* way supplies (`230` on a 230/400 V board, `120` on 120/208 V, `277` on
* 277/480 V). Defaults to `lineToLineVoltageV / √3` when only that is set.
*/
phaseVoltageV?: number;
/**
* Nominal **line-to-line** voltage in volts — what a 2-pole or three-phase
* way supplies (`400`, `208`, `480`). Defaults to `phaseVoltageV × √3` when
* only that is set. With no voltage declared at all the rules assume
* 120/208 V under `NEC` and 230/400 V under `IEC` (and PWR-B03 says so).
*/
lineToLineVoltageV?: number;
/**
* Power factor (0–1] the load rules apply when converting nameplate watts to
* amps. Defaults to 1 (watts treated as volt-amperes, NetBox's convention).
*/
powerFactor?: number;
/**
* What kind of distribution asset this board is. Drives the header badge
* only — an `rpp` is drawn `RPP`, a `busway` `BUSWAY`, and anything else
* (including omitted) falls back to `DB`. Purely a labelling concern: every
* board type shares one electrical model of ways and an incomer.
*/
boardType?: PduBoardType;
/**
* Rating of the board's incomer/main device in amps, shown in the header.
* Distinct from a way's `ratingA`: this is the device feeding the busbar,
* not one of the outgoing ways.
*/
incomerRatingA?: number;
/** Which code vocabulary consumer rules should read this board under. */
codeMode?: "NEC" | "IEC";
/**
* Continuous-load derate ceiling as a percentage of a way's rating. Defaults
* by `codeMode`: 80 under `NEC` (the default mode), 100 under `IEC`.
*/
maxUtilization?: number;
/**
* Opt-in flag marking this board as rack-mountable. `distribution-board` is
* deliberately NOT in `RACK_ASSIGNABLE_NODE_TYPES` — boards are wall-mounted
* free-floating nodes by default, and this flag is the hook for the
* rack-mounted minority.
*/
rackMountable?: boolean;
/** Number of way columns in the generated faceplate (default 1). */
wayColumns?: number;
}
/**
* Fibre flow cable node data.
* @public
*/
interface FibreFlowCableNodeData extends NetworkNodeDataBase {
cable: FibreFlowCable;
side: "left" | "right";
rowHeight: number;
columnWidth: number;
showTubeLabels: boolean;
showFiberNumbers: boolean;
layout: unknown;
colorMode?: "dark" | "light";
}
/**
* Fibre flow closure node data.
* @public
*/
interface FibreFlowClosureNodeData extends NetworkNodeDataBase {
width?: number;
height?: number;
headerHeight?: number;
colorMode?: "dark" | "light";
}
/**
* Mapping from node type to corresponding data type.
* @public
*/
interface NetworkNodeDataByType {
rack: RackNodeData;
switch: DeviceNodeData;
router: DeviceNodeData;
server: DeviceNodeData;
fiber: NetworkNodeDataBase;
"patch-panel": NetworkNodeDataBase;
device: DeviceNodeData;
"vertical-pdu": VerticalPduNodeData;
"distribution-board": DistributionBoardNodeData;
splice: NetworkNodeDataBase;
"splice-tray": NetworkNodeDataBase;
tube: NetworkNodeDataBase;
cable: NetworkNodeDataBase;
"multi-tube-cable": NetworkNodeDataBase;
coupler: NetworkNodeDataBase;
closure: NetworkNodeDataBase;
"fibre-split": NetworkNodeDataBase;
"fibre-flow-cable": FibreFlowCableNodeData;
"fibre-flow-closure": FibreFlowClosureNodeData;
}
/**
* Union of all node data variants.
* @public
*/
type NetworkNodeData = NetworkNodeDataByType[NetworkNodeType];
/**
* Network node structure with data discriminated by `type`.
* @public
*/
type NetworkNode = TType extends NetworkNodeType ? Node, TType> & {
/** Type of network node */
type: TType;
/** Node data and configuration */
data: NetworkNodeDataByType[TType] & TExtra;
} : never;
/**
* React Flow node specialized for this library's data. Use as the type argument
* to `NodeProps` in custom node components under `@xyflow/react` v12
* (v12 parameterizes `NodeProps`/`NodeType` by the node, not the data).
* @public
*/
type NetworkFlowNode = Node, NetworkNodeType>;
/**
* Network edge types
* @public
*/
type NetworkEdgeType = "fiber" | "ethernet" | "power" | "smoothstep" | "step" | "thick-cable" | "fibre-flow" | "fibre-flow-link";
/**
* Edge data payload.
* @public
*/
interface NetworkEdgeData {
/**
* Index signature so this data satisfies `@xyflow/react` v12's `Edge`
* constraint (`Record`). Edge data is never `Omit`-ed, so
* (unlike the node data types) an index signature is safe here.
*/
[key: string]: unknown;
/** Connection bandwidth */
bandwidth?: string;
/**
* Cable length, in the source system's unit (see {@link NetworkEdgeData.lengthUnit}).
* Unit-less: prefer {@link NetworkEdgeData.lengthCm} for comparisons.
*/
length?: number;
/** Unit of {@link NetworkEdgeData.length} as the source system reported it (e.g. `m`, `ft`). */
lengthUnit?: string;
/**
* Cable length normalised to centimetres. The NetBox/inventory converters set
* it when the source length has a known unit; PWR-A12 reads it in preference
* to the unit-less `length`.
*/
lengthCm?: number;
/** Edge color for styling */
color?: string;
/** Edge kind for styling/validation */
kind?: "fiber" | "power" | "network";
/** Whether this edge represents a ribbon fiber connection */
isRibbon?: boolean;
/** Array of fiber IDs for ribbon mode (6-12 fibers) */
ribbonFiberIds?: number[];
/** Highlight edge with a pulsing stroke */
highlight?: boolean;
/** Override highlight color (defaults to edge color) */
highlightColor?: string;
/** Override highlight max stroke width */
highlightWidth?: number;
/** Optional tag for grouping edges in demos */
traceTag?: string;
/** Legacy striped edge flag */
striped?: boolean;
/** Cable type metadata used by demos/helpers */
cableType?: PortCableType;
/** NetBox integration metadata */
netboxId?: number | string;
netboxType?: "device" | "rack" | "interface" | "cable" | string;
/** Inventory integration metadata */
inventoryId?: number | string;
inventoryType?: "device" | "rack" | "interface" | "cable" | string;
/** Status metadata */
status?: string;
/** Endpoint metadata */
terminationA?: string;
terminationB?: string;
/** Optional display label for the edge */
label?: string;
/** Optional opacity for styling (0–1) */
opacity?: number;
/** Optional power edge stroke width override */
powerStrokeWidth?: number;
/** Optional power edge stroke dasharray override */
powerStrokeDasharray?: string;
/** Optional power edge opacity override (0–1) */
powerOpacity?: number;
/**
* Power energisation overlay: `true` renders the edge as live (marching
* dashes), `false` renders it dimmed/grey. Omit to keep the classic
* (unaware) power edge styling. Produced by `computeEnergization`.
*/
energized?: boolean;
/**
* Direction power travels across the edge, relative to its own endpoints.
* Draws a flow arrow on an energised edge; ignored otherwise.
*/
flowDirection?: "source-to-target" | "target-to-source";
/** Additional extension metadata */
metadata?: Record;
}
/**
* Network edge data structure
* @public
*/
type NetworkEdge = {
/** Unique identifier for the edge */
id: string;
/** Source node ID */
source: string;
/** Target node ID */
target: string;
/** Source handle/port ID */
sourceHandle?: string;
/** Target handle/port ID */
targetHandle?: string;
/** Type of network connection */
type?: NetworkEdgeType;
/** Whether the edge should be hidden from rendering */
hidden?: boolean;
/**
* Whether the edge may be deleted by user interaction (keyboard Delete,
* batch delete). Mirrors React Flow's `Edge.deletable`; defaults to true.
* Deletion programmatically through `edges` prop updates is unaffected.
*/
deletable?: boolean;
/**
* Width (px) of the invisible interaction stroke rendered beneath the edge,
* forwarded to React Flow's `Edge.interactionWidth`. It widens the edge's
* click/hover target, but the stroke also sits above neighboring node
* handles in z-order — an oversized value can occlude nearby ports and make
* them unreachable for connection drops in dense layouts. When omitted, the
* diagram's `edgeInteractionWidth` default applies.
*/
interactionWidth?: number;
/** Edge data and configuration */
data?: TData;
};
/**
* React Flow edge specialized for this library's data. Use as the type argument
* to `EdgeProps` in custom edge components under `@xyflow/react` v12
* (v12 parameterizes `EdgeProps` by the edge, not the data).
* @public
*/
type NetworkFlowEdge = Edge;
/**
* Canonical persisted/editor document schema independent from React Flow handle ids.
* Prefer this model for saving diagrams, syncing external systems, and controlled
* document-mode usage via `document` / `onDocumentChange`.
* @public
*/
interface DiagramDocument {
/**
* Document schema version. Always `1` for documents emitted by this library.
* A missing version on input is treated as legacy v0 (identical to `1` today);
* a version greater than `1` is parsed best-effort with a console warning.
*/
version: 1;
nodes: DiagramNode[];
edges: DiagramEdge[];
ui?: DiagramUiState;
metadata?: Record;
}
/**
* Node kinds used by the document model.
* @public
*/
type DiagramNodeKind = "rack" | "equipment" | "pdu" | "distribution-board" | "fiber" | "tube" | "cable" | "multi-tube-cable" | "splice" | "coupler" | "splitter" | "container" | "fibre-flow-cable" | "fibre-flow-closure";
/**
* Placement data for document nodes.
* @public
*/
interface DiagramNodePlacement {
position: {
x: number;
y: number;
};
parentId?: string;
extent?: "parent";
rack?: {
uPosition?: number;
mountMode?: RackMountMode;
rackLayout?: RackLayoutMode;
rackWidthPx?: number;
rackFaceGapPx?: number;
};
container?: {
slotPosition?: number;
holderPosition?: number;
};
}
/**
* Endpoint definition for document nodes.
* @public
*/
interface DiagramEndpoint {
/**
* Opaque unique key for this endpoint within its document. The shipped
* adapters mint handle-grammar strings here for render-side stability, but
* consumers may supply any unique string — the reverse adapter resolves
* edges through the endpoint RECORD, so renaming a port's `label` does not
* require re-keying when ids are consumer-minted. Do not parse this value.
*
* When a device mounts the same port id on both faces, the shipped forward
* adapter mints the rear endpoint as `--rear` (the renderer's split-face
* handle alias) so ids stay unique; the reverse adapter gives the rebuilt rear
* port back its shared ``.
*/
id: string;
label: string;
face?: DeviceFace | "internal";
group?: string;
role?: "network" | "power" | "fiber" | "tube" | "splice" | "coupler" | "splitter" | "cable" | "generic";
medium?: "ethernet" | "fiber" | "power" | "tube" | "generic";
type?: PortType;
cableType?: PortCableType;
connector?: string;
/**
* Free-text source-of-truth port type label (e.g. `"QSFP28"`, `"RJ45"`) used
* to pick the port's connector visual. Preserved verbatim across the document
* round-trip so a port keeps its icon.
*/
sourceTypeLabel?: string;
compatibility?: string[];
layout?: PortPosition;
}
/**
* Render-focused node data for the document model.
* @public
*/
interface DiagramNodeRender {
status?: DeviceStatus | string;
face?: DeviceFace;
width?: Width | number;
widthPx?: number;
height?: number;
header?: string;
color?: string;
colorMode?: "light" | "dark" | "system";
rackWidthPx?: number;
rackFaceGapPx?: number;
images?: DeviceImageOptions;
dimensions?: Record;
showUMarkers?: boolean;
customHeaderText?: string;
powerSide?: PowerSide;
rotation?: number;
}
/**
* External reference attached to a document node.
* @public
*/
interface DiagramExternalRef {
source: "netbox" | "inventory" | string;
id: string | number;
type?: string;
metadata?: Record;
}
/**
* Document node.
* @public
*/
interface DiagramNode {
id: string;
kind: DiagramNodeKind;
subtype?: string;
label: string;
placement: DiagramNodePlacement;
endpoints?: DiagramEndpoint[];
render?: DiagramNodeRender;
config?: Record;
externalRefs?: DiagramExternalRef[];
metadata?: Record;
}
/**
* Endpoint reference used by document edges.
* @public
*/
interface DiagramEdgeEndpoint {
nodeId: string;
endpointId?: string | null;
}
/**
* Document edge.
* @public
*/
interface DiagramEdge {
id: string;
source: DiagramEdgeEndpoint;
target: DiagramEdgeEndpoint;
type?: NetworkEdgeType;
hidden?: boolean;
/** Mirrors {@link NetworkEdge.deletable}; omitted when the runtime edge left it unset. */
deletable?: boolean;
/** Mirrors {@link NetworkEdge.interactionWidth}; omitted when unset. */
interactionWidth?: number;
data?: NetworkEdgeData;
metadata?: Record;
}
/**
* Non-topology UI state stored alongside a document.
* @public
*/
interface DiagramUiState {
nodeState?: Record;
}>;
metadata?: Record;
}
/**
* Rack device types
* @public
*/
type RackDeviceType = "server" | "switch" | "router" | "patch-panel" | "ups";
/**
* Rack configuration structure
* @public
*/
interface RackConfig {
/** Unique rack identifier */
id: string;
/** Rack display name */
name: string;
/** Rack position coordinates */
position: {
x: number;
y: number;
};
/** Rack height in U units */
units: number;
/** U numbering direction (default: 'bottom-up') */
uNumberingDirection?: UNumberingDirection;
/** Custom header text for rack */
customHeaderText?: string;
/** Rack rendering layout mode */
rackLayout?: RackLayoutMode;
/** Gap in pixels between front and rear faces in split-face mode */
rackFaceGapPx?: number;
/** Devices mounted in the rack */
devices: RackDevice[];
/** Optional rack width in pixels (default: 300px which represents standard 48.26cm/19" rack) */
rackWidthPx?: number;
}
/**
* Device mounted in a rack
* @public
*/
interface RackDevice extends DeviceImageOptions {
/** Unique device identifier */
id: string;
/** Device display name */
name: string;
/** Starting U position in rack */
unit: number;
/** Device height in U units */
height: number;
/** Device type */
type: RackDeviceType;
/** Device width configuration */
width?: Width;
/** Device mount behavior within a rack */
mountMode?: RackMountMode;
/** Device role in network */
role?: string;
/** Recommended simple port input (flat `DevicePort[]` or grouped `PortBlock[]`). */
ports?: DevicePort[] | PortBlock[];
/** Current status of the device */
status?: DeviceStatus | string;
/** Advanced grouped front port input */
portGroups?: PortBlock[];
/** Advanced grouped rear port input */
rearPortGroups?: PortBlock[];
/** Grouped rear port blocks (rack JSON alias for `rearPortGroups`) */
rearPorts?: PortBlock[];
/** Power port side for rear face */
powerSide?: PowerSide;
/**
* Containable-only bays drawn as outlines + labels (never cable handles).
* Each bay's `face` selects the owning face (default front).
*
* Passed straight through to the device node's own `bays` node-data field, so
* authoring bays here is equivalent to authoring them on node data — hosts
* building from `rackConfig.devices[]` no longer need the per-device extra
* node-data channel just to render bays.
*/
bays?: DeviceBay[];
/** Connected device IDs */
connections?: string[];
}
/**
* Fiber cable types
* @public
*/
type FibreCableType = "single-mode" | "multi-mode";
/**
* Fiber cable configuration
* @public
*/
interface FibreCable {
/** Unique cable identifier */
id: string;
/** Cable display name */
name: string;
/** Fiber cable type */
type: FibreCableType;
/** Cable length */
length: number;
/** Cable color for visualization */
color: string;
/** Connection endpoints */
connections: {
/** Source connection point */
from: string;
/** Target connection point */
to: string;
};
}
/**
* Splice configuration structure
* @public
*/
interface SpliceConfig {
/** Unique splice identifier */
id: string;
/** Splice display name */
name: string;
/** Holder position within tray (1-12) */
holder: number;
/** Loss value in dB */
lossDb?: number;
/** Splice mode: 'single' for individual fiber, 'ribbon' for ribbon splices */
mode?: "single" | "ribbon";
/** For ribbon mode: array of fiber IDs (6-12) in the ribbon */
ribbonFiberIds?: number[];
}
/**
* Splice tray configuration structure
* @public
*/
interface SpliceTrayConfig {
/** Unique tray identifier */
id: string;
/** Tray display name */
name: string;
/** Tray position coordinates */
position: {
x: number;
y: number;
};
/** Splices in this tray */
splices: SpliceConfig[];
}
/**
* Fine-grained interaction controls for network diagrams.
* @public
*/
interface NetworkDiagramInteractionOptions {
/** Enable node dragging. */
nodesDraggable?: boolean;
/** Enable creating connections from node handles. */
nodesConnectable?: boolean;
/** Enable selecting nodes/edges. */
elementsSelectable?: boolean;
/** Enable keyboard/mouse focus on edges. */
edgesFocusable?: boolean;
/** Enable reconnecting existing edges. */
edgesReconnectable?: boolean;
/** Enable click-to-connect behavior on handles. */
connectOnClick?: boolean;
/** Enable panning by dragging the canvas. */
panOnDrag?: boolean;
/** Enable panning by scrolling. */
panOnScroll?: boolean;
/** Enable zoom via mouse wheel/trackpad scroll. */
zoomOnScroll?: boolean;
/** Enable pinch-zoom gestures. */
zoomOnPinch?: boolean;
/** Enable zoom on double click. */
zoomOnDoubleClick?: boolean;
/** Enable deleting selected edges with keyboard keys. */
allowKeyboardDelete?: boolean;
/** Show or hide the built-in React Flow controls widget. */
showControls?: boolean;
/** Show or hide the minimap. */
showMiniMap?: boolean;
/** Show or hide the interactive lock/unlock button in controls. */
showInteractiveControl?: boolean;
}
/**
* Selection action panel options rendered above selected racks/devices.
* @public
*/
interface NetworkDiagramActionPanelOptions {
/** Enable or disable the floating action panel entirely. Defaults to true. */
enabled?: boolean;
/** Show or hide the front/rear toggle button. Defaults to true. */
showRearToggle?: boolean;
/** Show or hide the De-rack button for rack-mounted devices. Defaults to true. */
showDeRack?: boolean;
/** Show a rack's Zoom to edit button, which enlarges its U rows. Defaults to true. */
showZoomToEdit?: boolean;
}
/**
* Serializable flow snapshot payload for save/restore helpers.
* @public
*/
interface NetworkDiagramFlowObject {
/** Snapshot nodes */
nodes: NetworkNode[];
/** Snapshot edges */
edges: NetworkEdge[];
/** Optional viewport state */
viewport?: Viewport;
}
/**
* Options for exported diagram images.
* @public
*/
interface NetworkDiagramToImageOptions {
/** Export width in px (clamped to 1-8192). */
width?: number;
/** Export height in px (clamped to 1-8192). */
height?: number;
backgroundColor?: string;
/** Export padding in px (clamped to 0-2048). */
padding?: number;
/** Device pixel ratio multiplier (clamped to 0.1-4). */
pixelRatio?: number;
/** JPEG/WebP quality (clamped to 0-1 when used). */
quality?: number;
filter?: (domNode: HTMLElement) => boolean;
}
/**
* Fit-view options that support targeting node IDs directly.
* @public
*/
interface NetworkDiagramFitViewOptions extends Omit {
/** Optional node IDs to fit */
nodes?: string[];
}
/**
* Exposed methods for save/restore and export integrations.
* @public
*/
interface NetworkDiagramFlowMethods {
toObject: () => NetworkDiagramFlowObject;
setViewport: (viewport: Viewport) => void;
setNodes: (nodes: NetworkNode[]) => void;
setEdges: (edges: NetworkEdge[]) => void;
toImage: (options?: NetworkDiagramToImageOptions) => Promise;
getNodes: () => NetworkNode[];
getEdges: () => NetworkEdge[];
/**
* Fit all nodes or an ID subset using configured policy defaults. Per-call
* options override them, and subset floors use current measured bounds.
*/
fitView: (options?: NetworkDiagramFitViewOptions) => void;
}
/**
* Network diagram component props
* @public
*/
interface NetworkDiagramProps {
/** Controlled document model. When provided, it takes precedence over nodes/edges props. */
document?: DiagramDocument;
/** Initial document for uncontrolled document mode. */
initialDocument?: DiagramDocument;
/** Callback when the document changes in document mode. */
onDocumentChange?: (document: DiagramDocument) => void;
/** Controlled nodes array */
nodes?: NetworkNode[];
/** Controlled edges array */
edges?: NetworkEdge[];
/** Callback when nodes change (controlled mode) */
onNodesChange?: (nodes: NetworkNode[]) => void;
/** Callback when edges change (controlled mode) */
onEdgesChange?: (edges: NetworkEdge[]) => void;
/** Initial nodes for uncontrolled mode */
initialNodes?: NetworkNode[];
/** Initial edges for uncontrolled mode */
initialEdges?: NetworkEdge[];
/** Enable debug mode */
debug?: boolean;
/** Callback when a node is clicked or selection changes (null when deselected) */
onNodeClick?: (node: NetworkNode | null) => void;
/** Callback when an edge is clicked */
onEdgeClick?: (edge: NetworkEdge) => void;
/** Callback when the diagram background (pane) is clicked */
onPaneClick?: () => void;
/** Custom React Flow node types to merge with defaults */
nodeTypes?: NodeTypes;
/** Custom React Flow edge types to merge with defaults */
edgeTypes?: EdgeTypes;
/** Optional onConnect override (React Flow connection handler) */
onConnect?: (connection: Connection) => void;
/** Optional onEdgeUpdate override */
onEdgeUpdate?: (oldEdge: Edge, newConnection: Connection) => void;
/** Optional isValidConnection override */
isValidConnection?: (connection: Connection) => boolean;
/** Optional connection mode override */
connectionMode?: ConnectionMode;
/** Callback when a device face changes */
onFaceChange?: (nodeId: string, face: DeviceFace) => void;
/** Callback when rack face changes */
onRackFaceChange?: (rackId: string, face: DeviceFace) => void;
/** CSS class name */
className?: string;
/** Inline styles */
style?: React.CSSProperties;
/** If true, vertically align rack bottoms to the same baseline */
alignRacksToBottom?: boolean;
/** Allow rack-mounted devices to move between racks/ungroup on drag and drop. Defaults to true. */
reAssignable?: boolean;
/** Color mode for the diagram */
colorMode?: "light" | "dark" | "system";
/** Callback when React Flow instance is initialized */
onInit?: (instance: ReactFlowInstance) => void;
/** Callback to get React Flow methods for save/restore */
onFlowMethods?: (methods: NetworkDiagramFlowMethods) => void;
/** Show download button */
showDownloadButton?: boolean;
/** Download button position */
downloadButtonPosition?: "top-right" | "top-left" | "bottom-right" | "bottom-left";
/** Custom download button text */
downloadButtonText?: string;
/** Download button styling options */
downloadButtonStyle?: React.CSSProperties;
/** Use smoothstep edges for tube connections (both tube-to-cable and cable-to-tube). Default is fiber. */
useSmoothstepEdgesForTubes?: boolean;
/** Force read-only behavior for the diagram (no drag/connect/delete/reconnect/pan/zoom edits). */
readOnly?: boolean;
/** Centralized interaction options for consistent edit/lock behavior. */
interaction?: NetworkDiagramInteractionOptions;
/** Configuration for the floating action panel shown on selected rack/device nodes. */
actionPanel?: NetworkDiagramActionPanelOptions;
/** Optional fitView options passed to React Flow */
fitViewOptions?: FitViewOptions;
/** Whether React Flow performs its built-in fit-on-init. Defaults to `true`.
*
* The init fit does not run at mount — it runs once node dimensions are first
* measured, which can be seconds later. A host that restores a persisted
* viewport must set this to `false`, otherwise the init fit lands *after* the
* restore and silently overwrites the restored pan/zoom.
*
* Ignored when `initialViewport.mode` is `'fit'` (library computes bounds from
* declared node dimensions instead).
*/
fitViewOnInit?: boolean;
/**
* Minimum zoom passed to React Flow. Defaults to 0.1 so the always-on
* fitView can actually fit tall scenes (React Flow's own 0.5 default clamps
* the fit and crops full-height racks, which viewport culling then never
* mounts).
*/
minZoom?: number;
/** Maximum zoom passed to React Flow. Defaults to 4. */
maxZoom?: number;
/**
* Let React Flow render only viewport-visible nodes and edges (virtualization).
*
* Defaults to `false`, which renders every element — the safest choice for
* offscreen measurement, edge routing, and the initial fit-to-view, since all
* nodes mount and report their dimensions up front.
*
* Enable this for very large topologies where mounting every rack is a
* performance cost. The tradeoff: virtualization improves large-diagram
* performance but can affect offscreen behaviour — nodes outside the viewport
* are not mounted, so their measured dimensions, the edges routed to/from
* them, and any fit-to-view that depends on their bounds may be wrong until
* they scroll into view. With a tight `minZoom` a fit-to-view can cull
* off-screen nodes so they never mount at all.
*
* Export-safe: this is force-disabled for the duration of a
* {@link NetworkDiagramFlowMethods.toImage} capture (and the built-in download
* button), so a PNG export includes every element even when virtualization is
* on. Culling is restored once the capture completes.
*/
onlyRenderVisibleElements?: boolean;
/** Node IDs to keep at full opacity when diagram focus dimming is enabled */
highlightedNodeIds?: string[];
/** Edge IDs to keep at full opacity when diagram focus dimming is enabled */
highlightedEdgeIds?: string[];
/**
* When true, non-highlighted nodes and edges are dimmed while focus IDs are set.
* Edges connected to highlighted nodes remain in focus unless
* `connectEdgesToHighlightedNodes` is false.
*/
dimUnhighlighted?: boolean;
/** Opacity for dimmed nodes/edges (0-1). Defaults to 0.25 */
dimOpacity?: number;
/** When false, only explicit highlightedEdgeIds stay bright during focus. */
connectEdgesToHighlightedNodes?: boolean;
/**
* Declarative multi-severity highlighting. Each group paints its member edges
* with the group's `color`/`width` through the same per-edge highlight channel
* (`data.highlight`/`highlightColor`/`highlightWidth`) the built-in edge
* renderers read, so groups render identically to hand-authored highlights.
*
* Overlap policy: **later groups win** — treat the array as severity tiers
* ordered least-to-most specific, so the highest-priority tier is placed last.
*
* When `highlightGroups` is non-empty and `dimUnhighlighted` is set, the dim
* complement is the union of every group's `nodeIds`/`edgeIds` plus the legacy
* `highlightedNodeIds`/`highlightedEdgeIds` props. `label` is carried through
* for future legend rendering and is unused by the diagram today.
*/
highlightGroups?: ReadonlyArray;
/** When false, devices render without rack photos. Status border still uses `getStatusColor`. Default true. */
showDeviceImages?: boolean;
/** Port handles kept highlighted during diagram trace/focus. */
highlightedPortHandles?: DiagramPortHandleRef[];
/** Fired when a port handle is clicked (typically in read-only trace mode). */
onPortClick?: (payload: DiagramPortClickPayload) => void;
/**
* Bays kept highlighted (e.g. the exact failed-drive slot to pull). The bay
* analogue of `highlightedPortHandles`; delivered through the same
* instance-scoped highlight store.
*/
highlightedBays?: DiagramBayRef[];
/** Fired when a bay outline is clicked. The bay analogue of `onPortClick`. */
onBayClick?: (payload: DiagramBayClickPayload) => void;
/**
* Pointer distance (px) within which a drag snaps onto a handle, forwarded to
* React Flow's `connectionRadius`. Larger values make small port handles easier
* to target; smaller values reduce mis-targeting on densely packed devices.
* When omitted, React Flow's default is used.
*/
connectionRadius?: number;
/**
* Default `interactionWidth` (px) applied to rendered edges, i.e. the width
* of each edge's invisible click/hover stroke. React Flow's component
* default is 20px, which blankets handles spaced closer than ~10px to a
* running edge (stacked splice ports, patch panels) and swallows connection
* drops meant for them. Defaults to 8 — still an easy click target while
* keeping tightly packed ports reachable. Per-edge `interactionWidth`
* overrides this value; pass 0 to disable edge interaction strokes.
*/
edgeInteractionWidth?: number;
/**
* Fired when a connection attempt is rejected by the diagram's validation
* (incompatible endpoints, an occupied non-replaceable handle, a disallowed
* fiber pairing, etc.). Lets host apps surface feedback instead of failing
* silently. Not called for the continuous hover validation during a drag —
* only when an actual connect or reconnect attempt is rejected.
*/
onConnectRejected?: (payload: NetworkConnectionRejection) => void;
/**
* Library-managed initial camera. Prefer this over demo-level fitView timers.
* `'fit'` uses declared top-level node dimensions (v12 SSR model).
*/
initialViewport?: NetworkDiagramInitialViewport;
/** Fired once after `initialViewport` fit or restore completes. */
onViewportReady?: () => void;
/** Hide the canvas until the initial viewport is applied. Defaults to true when fitting. */
hideUntilViewportReady?: boolean;
/**
* What to show inside the canvas when the diagram has no nodes to render.
*
* Defaults to a small centred, themed "No nodes to display" message. Pass
* your own node for a custom empty state, or `false` to render nothing (the
* bare grid). The built-in overlay is `pointer-events: none`, so
* drag-and-drop onto an empty canvas keeps working; keep that in mind if you
* supply your own — an interactive empty state must opt its own controls
* back in with `pointer-events: auto`.
*
* The overlay appears only after the node count has been zero for a short
* moment (~150ms), so a controlled diagram that renders `nodes={[]}` for a
* tick while data loads does not flash the message. While you know you are
* loading, pass `emptyState={false}` to suppress it entirely.
*/
emptyState?: React.ReactNode | false;
/**
* Wrap the React Flow subtree in `DiagramErrorBoundary` so a throwing
* custom node type renders a themed fallback inside the canvas instead of
* unmounting the host page. Defaults to `true`; pass `false` to opt out and
* let errors propagate to your own boundary.
*
* Because it defaults to `true`, diagram render errors no longer reach an
* error boundary you wrap around `NetworkDiagram`, nor any reporting hooked
* up to it. Pass {@link NetworkDiagramProps.onError} to keep reporting them,
* or `errorBoundary={false}` for the previous propagate-to-you behaviour.
*/
errorBoundary?: boolean;
/**
* Fallback rendered in place of the diagram when the built-in error boundary
* catches an error. A render prop receives the error and a `reset` callback
* that clears the error and re-renders the diagram. Ignored when
* `errorBoundary` is `false`.
*/
errorFallback?: React.ReactNode | ((error: Error, reset: () => void) => React.ReactNode);
/**
* Called when the built-in error boundary catches an error. This is the way
* to keep existing error reporting (Sentry, etc.) working now that the
* boundary is on by default and swallows errors before they reach your own
* boundary. Ignored when `errorBoundary` is `false`.
*/
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
}
/**
* One tier of {@link NetworkDiagramProps.highlightGroups} declarative
* highlighting.
*
* @public
* @remarks
* A group names some `nodeIds`/`edgeIds` and paints its member edges with
* `color`/`width` via the built-in per-edge highlight channel. Use one group
* per severity/category (e.g. error, warning, info) and order the array
* least-to-most specific, since **later groups win** where members overlap. The
* optional `label` is reserved for future legend rendering and is not drawn by
* the diagram today.
*
* @example
* ```tsx
* const groups: NetworkDiagramHighlightGroup[] = [
* { edgeIds: warningEdgeIds, color: "#d97706", width: 4, label: "Warning" },
* { edgeIds: errorEdgeIds, nodeIds: errorNodeIds, color: "#dc2626", width: 6, label: "Error" },
* ];
*
* ```
*/
interface NetworkDiagramHighlightGroup {
/** Node IDs in this tier. Contributes to the dim complement when dimming. */
readonly nodeIds?: readonly string[];
/** Edge IDs in this tier. Painted with `color`/`width`; also kept bright when dimming. */
readonly edgeIds?: readonly string[];
/** Highlight color for member edges. When omitted, edges keep their own color. */
readonly color?: string;
/** Highlight stroke width for member edges. When omitted, the renderer default is used. */
readonly width?: number;
/** Human-readable tier name, reserved for future legend rendering. Unused in render today. */
readonly label?: string;
}
/** Initial camera strategy for {@link NetworkDiagram}. @public */
type NetworkDiagramInitialViewport = {
mode: 'fit';
padding?: number;
minZoom?: number;
maxZoom?: number;
/**
* Refit when top-level node layout changes. Default (unset): refit until
* the user moves the camera (pan, zoom, Zoom to edit, or setViewport),
* then leave their camera alone. `true` always refits; `false` never does.
*/
refitOnLayoutChange?: boolean;
} | {
mode: 'restore';
viewport: Viewport;
} | {
mode: 'none';
};
/** Payload describing why a connection attempt was rejected. */
interface NetworkConnectionRejection {
/** The connection the user attempted to make. */
connection: Connection;
/** Human-readable reason the connection was rejected. */
reason: string;
/** Whether the rejection happened while creating a new edge or reconnecting an existing one. */
phase: "connect" | "reconnect";
}
/**
* Main component for rendering interactive network diagrams.
*
* The NetworkDiagram component provides a complete solution for visualizing
* network infrastructure including racks, devices, fiber connections, and power distribution.
*
* @example
* ```tsx
* import { NetworkDiagram, NetworkNode, NetworkEdge } from 'react-networks';
*
* const nodes: NetworkNode[] = [
* {
* id: 'rack-1',
* type: 'rack',
* position: { x: 100, y: 100 },
* data: { label: 'Main Rack', uHeight: 42 }
* }
* ];
*
*
* ```
*
* ## Empty and error states
*
* - `emptyState` — what to show inside the canvas when there are no nodes.
* Defaults to a small centred, themed "No nodes to display" message; pass
* your own node, or `false` for a bare grid. The built-in overlay is
* `pointer-events: none`, so dropping onto an empty canvas still works. It
* only appears once the node count has been zero for ~150ms, so a controlled
* diagram passing `nodes={[]}` while it loads never flashes the message —
* and `emptyState={false}` is the escape hatch to suppress it outright while
* your own loading UI is on screen.
* - `errorBoundary` / `errorFallback` / `onError` — the React Flow subtree is
* wrapped in `DiagramErrorBoundary` by default, so a throwing custom node
* type renders a themed fallback inside the canvas instead of unmounting the
* host page. `errorFallback` replaces that fallback (a function receives
* `(error, reset)`), `onError` reports the error, and `errorBoundary={false}`
* opts out entirely so errors reach your own boundary. Note the default is
* `true`, so errors no longer reach a boundary you wrap around the diagram —
* pass `onError` to keep your existing error reporting.
*
* In development the diagram also warns once (through `warnDev`) if its
* container measures 0px in either axis — the usual cause of a diagram that
* renders "nothing" is a parent without an explicit height.
*
* @example
* ```tsx
* Nothing here yet — drag a device in.
}
* onError={(error) => reportToSentry(error)}
* />
* ```
*
* @public
*/
declare const NetworkDiagram: React$1.FC;
interface DiagramErrorBoundaryProps {
/** Content to render when an error is caught */
children: ReactNode;
/** Optional fallback UI when an error occurs (default: simple message) */
fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode);
/** Optional callback when an error is caught (e.g. for logging) */
onError?: (error: Error, errorInfo: ErrorInfo) => void;
/**
* Optional callback fired when the boundary is reset (the `reset` handed to a
* render-prop fallback, or the default fallback's "Try again"). Lets a parent
* clear any state it derived from the caught error.
*/
onReset?: () => void;
}
interface DiagramErrorBoundaryState {
error: Error | null;
}
/**
* Error boundary that catches errors in the diagram tree (e.g. React Flow,
* custom nodes) and renders a fallback instead of crashing the whole app.
* Wrap <NetworkDiagram> (or the diagram subtree) with this for production.
*
* @example
* ```tsx
* (
*
*
Diagram failed: {error.message}
*
*
* )}
* onError={(err, info) => logToService(err, info)}
* >
*
*
* ```
*/
declare class DiagramErrorBoundary extends Component {
state: DiagramErrorBoundaryState;
static getDerivedStateFromError(error: Error): DiagramErrorBoundaryState;
componentDidCatch(error: Error, errorInfo: ErrorInfo): void;
reset: () => void;
render(): ReactNode;
}
type FibreFlowLayoutDirection = 'horizontal' | 'vertical';
type FibreFlowColorMode = 'dark' | 'light';
/**
* Props for {@link FibreFlowMap}.
*
* `FibreFlowMap` is a **viewer by default**: `readOnly` defaults to `true`, so
* connect / reconnect / delete gestures are inert unless you explicitly pass
* `readOnly={false}`. Even with editing enabled the edit state is **ephemeral**
* — edits mutate internal component state only and the component exposes no edit
* callback, so they are lost on the next layout rebuild unless a consumer
* observes them. Treat `readOnly={false}` as a preview affordance, not a
* persistence path.
*
* Referential stability of `closures`, `circuits`, and `highlightedCircuitId`
* is a **perf nicety, not a correctness requirement**. Internal node/edge
* (layout and edit) state is reset only when the *content* actually changes —
* the reset effect compares ordered ids (plus the layout-affecting scalar
* props), so passing a freshly-built but value-equal array (`x ?? []`,
* `closures.map(...)` that yields the same ids) is safe and preserves view/edit
* state. Memoizing still avoids wasted layout work (a dev-mode warning flags the
* value-equal-but-new-identity case). For full control over when resets happen,
* pass {@link FibreFlowMapProps.resetKey}.
*/
interface FibreFlowMapProps {
/**
* Closures to render. May be omitted (defaults to an empty diagram).
* Referential stability is optional — a new but value-equal array (same
* ordered closure ids) does not reset internal state.
*/
closures?: FibreFlowClosure[];
/**
* Circuits used for edge colouring/labels. Referential stability is optional
* — a new but value-equal array (same ordered circuit ids) does not reset
* internal state.
*/
circuits?: FibreFlowCircuit[];
/**
* Circuit to highlight (others are dimmed). Changing it re-derives the edge
* colouring and resets internal state; a value-equal value does not.
*/
highlightedCircuitId?: string;
layout?: FibreFlowLayoutDirection;
rowHeight?: number;
tubeGap?: number;
closureGap?: number;
cableColumnWidth?: number;
spliceAreaWidth?: number;
showTubeLabels?: boolean;
showFiberNumbers?: boolean;
colorMode?: FibreFlowColorMode;
showCircuitId?: boolean;
/**
* When `true`, strands that carry nothing are removed from the drawing.
*
* A strand `(tube, fiber)` on a cable counts as **used** when any splice in
* any closure of this diagram references it on that cable. Cables are shared
* between adjacent closures via `leftCable`/`rightCable`, so the set is
* computed globally — a pass-through strand spliced only in the neighbouring
* closure stays visible here.
*
* Hidden strands are never dropped silently: each tube that lost rows renders
* a slim collapsed indicator (`· 10 of 12 unused`), and a fully unused tube
* collapses to that indicator alone. Handles remain keyed by `(tube, fiber)`,
* so splice edges resolve unchanged and the prop can be toggled at runtime.
*
* Hidden strands mount **no handles**, so a new splice cannot be drawn to or
* from them while they are collapsed. Prefer this prop for read-only viewing;
* leave it `false` (or toggle it off) when `readOnly={false}` editing is in
* use.
*
* @defaultValue false
*/
hideUnusedFibres?: boolean;
className?: string;
style?: React$1.CSSProperties;
/**
* When `true` (the **default**) the map is a read-only viewer: connect,
* reconnect and delete gestures are inert. Pass `readOnly={false}` to enable
* editing, but note edit state is ephemeral (no edit callback is exposed).
*/
readOnly?: boolean;
interaction?: NetworkDiagramInteractionOptions;
/**
* Opt-in explicit reset control. When provided, internal node/edge state is
* reset **only** when `resetKey` changes — never on `closures`/`circuits`
* identity or content changes. Use this when you drive edits and want to keep
* transient state across data updates, bumping the key yourself to force a
* rebuild (e.g. on an explicit "reload" action). When omitted, resets are
* content-driven (ordered ids + layout props).
*/
resetKey?: string | number;
/**
* Callback invoked with imperative flow methods (export / fit / read) once
* they are available, mirroring `NetworkDiagram`'s `onFlowMethods`. Lets
* closure diagrams join the same PNG/jsPDF export pipeline as `NetworkDiagram`
* (`toImage`) and re-fit imperatively. See {@link FibreFlowMapFlowMethods}.
*/
onFlowMethods?: (methods: FibreFlowMapFlowMethods) => void;
}
/**
* Imperative flow methods surfaced by {@link FibreFlowMap} through the
* `onFlowMethods` callback.
*
* @remarks
* This is the {@link FibreFlowMap} counterpart to `NetworkDiagram`'s
* `NetworkDiagramFlowMethods`, exposing the read/export-oriented subset that
* closure diagrams need to join the same export pipeline as `NetworkDiagram`
* (for example a shared "Download PNG" / jsPDF report path). The method
* semantics and option types are identical to the corresponding
* `NetworkDiagramFlowMethods` members — `toImage` clamps its options to safe
* bounds and lazy-loads `html-to-image`, and `fitView` accepts node-id targets.
*
* Reach for it when you need to trigger an export or re-fit imperatively from a
* parent (a toolbar button, a PDF export routine) rather than declaratively via
* props.
*
* @example
* ```tsx
* const methodsRef = useRef();
* { methodsRef.current = methods; }}
* />;
* // later, e.g. in a "Download" button handler:
* const pngDataUrl = await methodsRef.current?.toImage({ pixelRatio: 2 });
* ```
*
* @public
*/
interface FibreFlowMapFlowMethods {
/** Export the current diagram to a PNG data URL. Options are clamped to safe bounds. */
toImage: (options?: NetworkDiagramToImageOptions) => Promise;
/** Fit the viewport to the diagram, optionally targeting specific node ids. */
fitView: (options?: NetworkDiagramFitViewOptions) => void;
/** Get the current React Flow nodes. */
getNodes: () => NetworkNode[];
/** Get the current React Flow edges. */
getEdges: () => NetworkEdge[];
}
declare const FibreFlowMap: React$1.FC;
/**
* Snap a device position to the nearest U position within a rack
*/
declare const snapToUPosition: (device: NetworkNode, rack: NetworkNode, newPosition: {
x: number;
y: number;
}, allNodes: NetworkNode[]) => {
x: number;
y: number;
uPosition?: number;
};
/**
* Check if a U position is available in a rack
*/
declare const isUPositionAvailable: (rack: NetworkNode, device: NetworkNode, uPosition: number, allNodes: NetworkNode[]) => boolean;
/**
* Find the next available U position in a rack
*/
declare const findNextAvailableUPosition: (rack: NetworkNode, device: NetworkNode, allNodes: NetworkNode[], startFrom?: number) => number | undefined;
/**
* Calculate device position based on U position within a rack
*/
declare const calculateDevicePositionFromU: (rack: NetworkNode, uPosition: number, deviceHeight?: number, deviceData?: Pick) => {
x: number;
y: number;
};
/**
* Update device U position and recalculate position
*/
declare const updateDeviceUPosition$1: (device: NetworkNode<"device" | "server" | "switch" | "router" | "patch-panel" | "vertical-pdu">, rack: NetworkNode, newUPosition: number) => NetworkNode<"device" | "server" | "switch" | "router" | "patch-panel" | "vertical-pdu">;
/**
* Validate and snap device to valid U position
*/
declare const validateAndSnapDevice: (device: NetworkNode<"device" | "server" | "switch" | "router" | "patch-panel" | "vertical-pdu">, rack: NetworkNode) => NetworkNode<"device" | "server" | "switch" | "router" | "patch-panel" | "vertical-pdu">;
/**
* Panel header height in pixels
*/
declare const PANEL_HEADER_HEIGHT = 32;
/**
* Height of each holder position in pixels (one splice per holder)
* Different heights for ribbon mode (taller) vs single mode (compact)
*/
declare const HOLDER_HEIGHT_PX_SINGLE = 20;
declare const HOLDER_HEIGHT_PX_RIBBON = 36;
declare const HOLDER_HEIGHT_PX = 20;
/**
* Tray header height in pixels (smaller than rack header for compact trays)
*/
declare const TRAY_HEADER_HEIGHT = 32;
/**
* Rendered splice width in pixels — the exact width the SpliceNode renderer
* uses.
*
* @remarks
* Exposed so consumers composing a fibre scene by hand (positioning splices to
* line up with rendered tray/holder geometry) use the real value instead of a
* local copy that would silently drift when the renderer changes. Pairs with
* {@link SPLICE_HEIGHT_PX} and {@link calculateSplicePositionFromNumber}.
* @public
*/
declare const SPLICE_WIDTH_PX = 69;
/**
* Rendered splice height in pixels for single-mode (compact) splices.
* @public
*/
declare const SPLICE_HEIGHT_PX_SINGLE = 16;
/**
* Rendered splice height in pixels for ribbon-mode (taller) splices.
* @public
*/
declare const SPLICE_HEIGHT_PX_RIBBON = 32;
/**
* Rendered splice height in pixels. Defaults to single-mode; for ribbon splices
* use {@link SPLICE_HEIGHT_PX_RIBBON}.
*
* @remarks
* The height counterpart to {@link SPLICE_WIDTH_PX}: the exact value the
* SpliceNode renderer uses, exposed so hand-composed fibre scenes don't fork a
* magic number that drifts.
* @public
*/
declare const SPLICE_HEIGHT_PX = 16;
/**
* Tray width in pixels (narrower to fit splice properly)
*/
declare const TRAY_WIDTH_PX = 80;
/**
* Rendered splice-tray height when the tray holds ribbon splices.
*/
declare const TRAY_NODE_HEIGHT_RIBBON: number;
/** Default tray height — compact single-mode layout. */
declare const TRAY_NODE_HEIGHT: number;
/**
* Fixed X position to center splices within tray content
*/
declare const SPLICE_CENTER_X: number;
/**
* Compute the `{ x, y }` offset (relative to the tray node) at which a splice in
* a given 1-based holder position renders, in single (compact) mode.
*
* @remarks
* Pure geometry — no DOM, React or reactflow. Exposed so consumers hand-building
* a fibre closure scene (a tray with splices/tubes wired up and rendered through
* `NetworkDiagram`) position splices with the same formula the renderer uses,
* instead of forking it. This single-mode variant has no access to splice data;
* for ribbon splices use {@link calculateSplicePositionFromNumberWithData} with
* `isRibbonMode: true`. `x` is fixed at {@link SPLICE_CENTER_X}.
*
* @example
* ```ts
* calculateSplicePositionFromNumber(tray, 1); // { x: SPLICE_CENTER_X, y: 34 }
* ```
* @public
*/
declare const calculateSplicePositionFromNumber: (tray: NetworkNode, holderPosition: number) => {
x: number;
y: number;
};
/**
* Compute the `{ x, y }` offset (relative to the tray node) at which a splice in
* a given 1-based holder position renders, honoring the splice mode.
*
* @remarks
* Pure geometry — no DOM, React or reactflow. The mode-aware companion to
* {@link calculateSplicePositionFromNumber}: ribbon splices are taller
* ({@link SPLICE_HEIGHT_PX_RIBBON} in {@link HOLDER_HEIGHT_PX_RIBBON} holders)
* than single splices ({@link SPLICE_HEIGHT_PX_SINGLE} in
* {@link HOLDER_HEIGHT_PX_SINGLE} holders); the splice is centered vertically in
* its holder slot. `x` is fixed at {@link SPLICE_CENTER_X}.
*
* @example
* ```ts
* calculateSplicePositionFromNumberWithData(tray, 1, true); // ribbon: { x, y: 34 }
* ```
* @public
*/
declare const calculateSplicePositionFromNumberWithData: (tray: NetworkNode, holderPosition: number, isRibbonMode?: boolean) => {
x: number;
y: number;
};
type ComputeInitialViewportOptions = {
padding?: number;
minZoom?: number;
maxZoom?: number;
};
/**
* Compute a viewport that fits top-level nodes using declared dimensions (v12 SSR model).
* Does not require nodes to be mounted in the DOM.
* @public
*/
declare function computeInitialViewport(nodes: readonly NetworkNode[], container: {
width: number;
height: number;
}, options?: ComputeInitialViewportOptions): Viewport | null;
/**
* Default `fitView` padding applied when the consumer passes no
* `fitViewOptions.padding`.
*
* React Flow's own default is `0.1`, which leaves rack/ODF scenes touching the
* canvas edges. `0.14` keeps a visible gutter without meaningfully shrinking
* the content.
*
* @public
*/
declare const DIAGRAM_DEFAULT_FIT_PADDING = 0.14;
/**
* Smallest authored label size (CSS px) that survives the zoom level-of-detail
* pass — i.e. the class the readable-zoom floor is computed against.
*
* @deprecated The diagram no longer applies a readable-zoom floor to its fits
* (since 0.12.0 every fit frames the complete scene and the zoom
* level-of-detail pass handles legibility). Kept for compatibility; it will be
* removed in the next major version.
* @public
*/
declare const DIAGRAM_SMALLEST_LABEL_PX = 9;
/**
* Rendered size (CSS px) below which {@link DIAGRAM_SMALLEST_LABEL_PX} text is
* treated as unreadable.
*
* @deprecated The diagram no longer applies a readable-zoom floor to its fits
* (since 0.12.0 every fit frames the complete scene and the zoom
* level-of-detail pass handles legibility). Kept for compatibility; it will be
* removed in the next major version.
* @public
*/
declare const DIAGRAM_MIN_READABLE_LABEL_PX = 9;
/**
* Hard ceiling on the readable-zoom floor. Nothing zooms past this in the name
* of readability.
*
* Derived from {@link DIAGRAM_LABEL_LOD_ZOOM} rather than set independently: a
* ceiling *below* the level-of-detail threshold would land a floored scene in
* the band where the micro-label tier is still hidden and the primary 9px label
* renders under 9 device px — a lift that buys nothing. Raising the floor to
* the threshold is the smallest zoom at which the full label set is back.
*
* @deprecated The diagram no longer applies a readable-zoom floor to its fits
* (since 0.12.0 every fit frames the complete scene and the zoom
* level-of-detail pass handles legibility). Kept for compatibility; it will be
* removed in the next major version.
* @public
*/
declare const DIAGRAM_MAX_READABLE_ZOOM_FLOOR = 0.8;
/**
* How far above the natural fit the readable-zoom floor may zoom in.
*
* The floor trades framing for legibility, and that trade is only worth making
* when it is nearly free. A 42U ODF rack fits at ~0.15 zoom: lifting it to 0.8
* would replace a whole-rack overview with a six-panel keyhole and *still* be a
* worse view. So the floor is all-or-nothing — if the readable zoom is more
* than this multiple of the natural fit, the scene keeps its overview and the
* zoom level-of-detail pass handles legibility instead.
*
* @deprecated The diagram no longer applies a readable-zoom floor to its fits
* (since 0.12.0 every fit frames the complete scene and the zoom
* level-of-detail pass handles legibility). Kept for compatibility; it will be
* removed in the next major version.
* @public
*/
declare const DIAGRAM_MAX_READABLE_ZOOM_LIFT = 1.5;
/**
* Inputs to {@link computeReadableZoomFloor}.
*
* @deprecated The diagram no longer applies a readable-zoom floor to its fits
* (since 0.12.0 every fit frames the complete scene and the zoom
* level-of-detail pass handles legibility). Kept for compatibility; it will be
* removed in the next major version.
* @public
*/
interface ReadableZoomFloorOptions {
/**
* The zoom the content would fit at with no floor. **Required for a floor to
* apply** — without it the lift cannot be bounded, and an unbounded floor
* wrecks the framing of any large scene.
*/
fittedZoom?: number;
/** Smallest label class, in authored CSS px. Defaults to {@link DIAGRAM_SMALLEST_LABEL_PX}. */
smallestLabelPx?: number;
/** Rendered px that class must reach. Defaults to {@link DIAGRAM_MIN_READABLE_LABEL_PX}. */
minReadableLabelPx?: number;
/** Ceiling for the floor. Defaults to {@link DIAGRAM_MAX_READABLE_ZOOM_FLOOR}. */
maxFloorZoom?: number;
/** Lift budget over the natural fit. Defaults to {@link DIAGRAM_MAX_READABLE_ZOOM_LIFT}. */
maxLift?: number;
/** Diagram `minZoom`. A floor at or below it is a no-op. Defaults to `0.1`. */
minZoom?: number;
/** Diagram `maxZoom`. The floor never exceeds it. Defaults to `4`. */
maxZoom?: number;
}
/**
* The zoom below which the smallest surviving label class renders too small to
* read — or `undefined` when no floor should be applied.
*
* Returns `undefined` (leave the fit alone) when:
* - `fittedZoom` is unknown, so the lift cannot be bounded;
* - the content already fits at or above the readable zoom;
* - reaching the readable zoom would cost more than `maxLift` — a scene that
* large is better served by its overview plus the label LOD pass;
* - the readable zoom is not above the diagram's own `minZoom`.
*
* Pure: no DOM, no React Flow store. Feeding the result to
* `fitViewOptions.minZoom` makes React Flow's fit clamp to it while still
* centring on the content bounds.
*
* @deprecated The diagram no longer applies a readable-zoom floor to its fits
* (since 0.12.0 every fit frames the complete scene and the zoom
* level-of-detail pass handles legibility). Kept for compatibility; it will be
* removed in the next major version.
* @public
*/
declare function computeReadableZoomFloor(options?: ReadableZoomFloorOptions): number | undefined;
/**
* Context for {@link resolveFitViewOptions}.
*
* @public
*/
interface FitViewPolicyContext {
/** Diagram `minZoom` prop. */
minZoom?: number;
/** Diagram `maxZoom` prop. */
maxZoom?: number;
/**
* The zoom the content would fit at, when known. Enables the floor.
* @deprecated The diagram no longer passes a fitted zoom; see
* {@link computeReadableZoomFloor}.
*/
fittedZoom?: number;
/**
* Pre-computed floor, bypassing {@link computeReadableZoomFloor}.
* @deprecated See {@link computeReadableZoomFloor}.
*/
readableZoomFloor?: number;
}
/**
* Merge the library's fit-view defaults under the consumer's own options.
*
* Anything the consumer set is preserved verbatim — including `padding: 0` and
* an explicit `minZoom`, which opt out of the padding default and the
* readable-zoom floor respectively. Only unset keys are filled in, and `minZoom`
* is only added when a floor actually applies.
*
* @public
*/
declare function resolveFitViewOptions(options: FitViewOptions | undefined, context?: FitViewPolicyContext): FitViewOptions;
/**
* Zoom below which the diagram drops its micro-label tier (bay labels,
* manufacturer, U indicator).
*
* Those classes are authored at 8-9 CSS px because they have to fit inside
* 1U-scale geometry. Below this zoom they render under ~7 device px, where they
* are illegible noise that hides the shapes underneath.
*
* @public
*/
declare const DIAGRAM_LABEL_LOD_ZOOM = 0.8;
/**
* Class placed on the diagram wrapper while the viewport is below
* {@link DIAGRAM_LABEL_LOD_ZOOM}.
*
* Visibility is CSS-only (descendant rules in the library stylesheet), so the
* level-of-detail switch costs one `classList` write per threshold crossing
* rather than a re-render per node. Consumers can hook their own rules onto the
* same class.
*
* @public
*/
declare const DIAGRAM_ZOOM_LOD_CLASS = "rn-zoom-below-detail";
/**
* Whether `zoom` is below the micro-label threshold.
*
* Non-finite input is treated as "not below" so a store that has not published
* a transform yet renders the full label set rather than a stripped-down one.
*
* @public
*/
declare function isBelowLabelLodZoom(zoom: number, threshold?: number): boolean;
/**
* Read the zoom scalar out of a React Flow store transform tuple.
*
* @public
*/
declare function readTransformZoom(transform: unknown): number;
/**
* Resolve declared pixel dimensions for bounds / SSR-style layout.
* Prefers explicit node.width/height, then measured, then type-specific defaults.
* @public
*/
declare function resolveNodeDimensions(node: NetworkNode): {
width: number;
height: number;
};
/**
* Top-level nodes with declared dimensions for getNodesBounds.
* @public
*/
declare function prepareTopLevelNodesForBounds(nodes: readonly NetworkNode[]): NetworkNode[];
type RackMountedNodeType = 'device' | 'server' | 'switch' | 'router' | 'patch-panel' | 'vertical-pdu';
/**
* Configuration options for building nodes from rack schema
*/
interface RackSchemaOptions {
/** How to handle device placement conflicts */
conflictPolicy?: 'strict' | 'nearest';
/** Whether to validate all placements before creating nodes */
validatePlacements?: boolean;
/** Custom device type mapping from schema to NetworkNode type */
deviceTypeMapping?: Record;
}
/**
* Error thrown when device placement conflicts occur in strict mode
*/
declare class DevicePlacementError extends Error {
rackId: string;
deviceId: string;
uPosition: number;
uHeight: number;
conflictWith?: string | undefined;
/**
* Every placement problem found in the rack, one human-readable line each
* (conflicts and out-of-bounds). The positional fields describe the first.
*/
readonly issues: string[];
constructor(rackId: string, deviceId: string, uPosition: number, uHeight: number, conflictWith?: string | undefined, issues?: string[]);
}
/**
* Validation result for device placement
*/
interface DevicePlacementValidation {
isValid: boolean;
conflicts: Array<{
deviceId: string;
uPosition: number;
uHeight: number;
conflictWith: string;
}>;
outOfBounds: Array<{
deviceId: string;
uPosition: number;
uHeight: number;
rackUHeight: number;
}>;
}
/**
* Validate device placements within a rack schema
*
* Checks for out-of-bounds placements and device-to-device conflicts.
* Uses the generic validatePlacements utility for consistent validation logic.
*
* @param rack - The rack configuration to validate
* @param _options - Validation options (currently unused)
* @returns Validation result with conflicts and out-of-bounds errors
* @public
*
* @example
* ```ts
* const rack: RackConfig = {
* id: 'rack-1',
* units: 42,
* devices: [
* { id: 'dev-1', unit: 1, height: 2, ... },
* { id: 'dev-2', unit: 2, height: 1, ... } // Conflicts with dev-1
* ]
* };
* const result = validateRackDevicePlacements(rack);
* if (!result.isValid) {
* console.log('Conflicts:', result.conflicts);
* }
* ```
*/
declare function validateRackDevicePlacements(rack: RackConfig, _options?: RackSchemaOptions): DevicePlacementValidation;
/**
* Build NetworkNode array from RackConfig array with comprehensive validation
*
* Converts rack configurations to React Flow nodes with automatic validation
* and conflict resolution. Supports strict and nearest conflict policies.
*
* @param racks - Array of rack configurations
* @param options - Build options including conflict policy and validation settings
* @returns Array of NetworkNode objects ready for React Flow
* @throws {DevicePlacementError} If validation fails in strict mode
* @public
*
* @example
* ```ts
* const racks: RackConfig[] = [{
* id: 'rack-1',
* name: 'Main Rack',
* units: 42,
* devices: [...]
* }];
* const nodes = buildNodesFromRackConfig(racks, {
* conflictPolicy: 'strict',
* validatePlacements: true
* });
* ```
*/
declare function buildNodesFromRackConfig(racks: RackConfig[], options?: RackSchemaOptions): NetworkNode[];
/**
* Create a RackConfig from existing NetworkNode data (reverse operation)
*/
declare function createRackConfigFromNodes(nodes: NetworkNode[]): RackConfig[];
/**
* Update device U position in a rack schema
*/
declare function updateDeviceUPosition(racks: RackConfig[], rackId: string, deviceId: string, newUPosition: number): RackConfig[];
/**
* Add a device to a rack schema
*/
declare function addDeviceToRack(racks: RackConfig[], rackId: string, device: RackDevice, options?: RackSchemaOptions): RackConfig[];
/**
* Remove a device from a rack schema
*/
declare function removeDeviceFromRack(racks: RackConfig[], rackId: string, deviceId: string): RackConfig[];
/**
* Configuration options for building nodes from splice schema
*/
interface SpliceSchemaOptions {
/** How to handle splice placement conflicts */
conflictPolicy?: 'strict' | 'nearest';
/** Whether to validate all placements before creating nodes */
validatePlacements?: boolean;
}
/**
* Error thrown when splice placement conflicts occur in strict mode
*/
declare class SplicePlacementError extends Error {
trayId: string;
spliceId: string;
holder: number;
conflictWith?: string | undefined;
constructor(trayId: string, spliceId: string, holder: number, conflictWith?: string | undefined);
}
/**
* Validation result for splice placement
*/
interface SplicePlacementValidation {
isValid: boolean;
conflicts: Array<{
spliceId: string;
holder: number;
conflictWith: string;
}>;
outOfBounds: Array<{
spliceId: string;
holder: number;
max: number;
}>;
}
/**
* Validate splice placements within a tray schema
*
* Checks for out-of-bounds placements and splice-to-splice conflicts.
* Uses the generic validatePlacements utility for consistent validation logic.
*
* @param tray - The splice tray configuration to validate
* @param _options - Validation options (currently unused)
* @returns Validation result with conflicts and out-of-bounds errors
* @public
*
* @example
* ```ts
* const tray: SpliceTrayConfig = {
* id: 'tray-1',
* splices: [
* { id: 'splice-1', holder: 1, ... },
* { id: 'splice-2', holder: 1, ... } // Conflicts with splice-1
* ]
* };
* const result = validateSplicePlacements(tray);
* if (!result.isValid) {
* console.log('Conflicts:', result.conflicts);
* }
* ```
*/
declare function validateSplicePlacements(tray: SpliceTrayConfig, _options?: SpliceSchemaOptions): SplicePlacementValidation;
/**
* Build NetworkNode array from SpliceTrayConfig array with comprehensive validation
*
* Converts splice tray configurations to React Flow nodes with automatic validation
* and conflict resolution. Supports strict and nearest conflict policies.
*
* @param trays - Array of splice tray configurations
* @param options - Build options including conflict policy and validation settings
* @returns Array of NetworkNode objects ready for React Flow
* @throws {SplicePlacementError} If validation fails in strict mode
* @public
*
* @example
* ```ts
* const trays: SpliceTrayConfig[] = [{
* id: 'tray-1',
* name: 'Tray 1',
* splices: [...]
* }];
* const nodes = buildNodesFromSpliceConfig(trays, {
* conflictPolicy: 'strict',
* validatePlacements: true
* });
* ```
*/
declare function buildNodesFromSpliceConfig(trays: SpliceTrayConfig[], options?: SpliceSchemaOptions): NetworkNode[];
/**
* Create a SpliceTrayConfig from existing NetworkNode data (reverse operation)
*/
declare function createSpliceConfigFromNodes(nodes: NetworkNode[]): SpliceTrayConfig[];
/**
* Update splice holder position in a splice schema
*/
declare function updateSpliceHolderPosition(trays: SpliceTrayConfig[], trayId: string, spliceId: string, newHolder: number): SpliceTrayConfig[];
/**
* Add a splice to a tray schema
*/
declare function addSpliceToTray(trays: SpliceTrayConfig[], trayId: string, splice: SpliceConfig, options?: SpliceSchemaOptions): SpliceTrayConfig[];
/**
* Remove a splice from a tray schema
*/
declare function removeSpliceFromTray(trays: SpliceTrayConfig[], trayId: string, spliceId: string): SpliceTrayConfig[];
/**
* Normalize flat `DevicePort[]` input into explicit front/rear grouped blocks.
* This is the bridge between the recommended simple port schema and the
* lower-level grouped renderer input.
* @public
*/
declare function buildPortBlocksFromPorts(devicePorts: DevicePort[]): {
ports: PortBlock[];
rearPorts: PortBlock[];
};
/**
* Node-data subset accepted by {@link getResolvedPortBlocks}: a flat
* `DevicePort[]` on `ports`, grouped `PortBlock[]` on
* `ports`/`portGroups`/`rearPortGroups`, or a `rearPorts` block fallback.
* @public
*/
type ResolvedPortBlockInput = Pick & {
rearPorts?: PortBlock[];
};
/**
* Resolve the grouped front/rear port blocks from any supported input form:
* flat `ports: DevicePort[]`, grouped `PortBlock[]`, or explicit
* `portGroups` / `rearPortGroups`.
* @public
*/
declare function getResolvedPortBlocks(data: ResolvedPortBlockInput | undefined): {
ports: PortBlock[];
rearPorts: PortBlock[];
};
/**
* Resolve the front-facing grouped port blocks for a node/device data object.
* @public
*/
declare function getFrontPortBlocks(data: Pick | undefined): PortBlock[];
/**
* Resolve the rear-facing grouped port blocks for a node/device data object.
* @public
*/
declare function getRearPortBlocks(data: Pick | undefined): PortBlock[];
/**
* Determine whether a node/device data object resolves to any front ports.
* @public
*/
declare function hasFrontPorts(data: Pick | undefined): boolean;
/**
* Determine whether a node/device data object resolves to any rear ports.
* @public
*/
declare function hasRearPorts(data: Pick | undefined): boolean;
/**
* Choose the default device face based on the resolved front/rear port data.
* @public
*/
declare function getDefaultDeviceFace(data: Pick | undefined): DeviceFace;
/**
* Resolve the effective device face for a flat port input.
* @public
*/
declare function resolveDevicePortFace(port: Pick): DeviceFace;
/**
* Resolve the effective port group name for a flat port input.
* @public
*/
declare function resolveDevicePortGroup(port: Pick): string;
/**
* Normalize unknown **flat** port array input into canonical {@link DevicePort} entries.
*
* This handles a flat `DevicePort[]` only; grouped `PortBlock[]` input returns
* `undefined` (blocks have no `label`). To resolve flat ports from any supported
* form — including grouped `PortBlock[]` — use {@link resolveNodeDevicePorts}.
* @public
*/
declare function normalizeDevicePorts(value: unknown): DevicePort[] | undefined;
/**
* Resolve flat device ports from any supported port input.
*
* Accepts every form the renderer accepts (`getResolvedPortBlocks`): a node-data
* record (`ports` / `portGroups` / `rearPortGroups` / `rearPorts`), a flat
* `DevicePort[]`, or a grouped `PortBlock[]`. Grouped `PortBlock[]` carried on
* `ports` are treated as front groups (taking precedence over `portGroups`, matching
* the renderer); rear groups come from `rearPortGroups`, falling back to `rearPorts`.
* @public
*/
declare function resolveNodeDevicePorts(value: unknown): DevicePort[] | undefined;
/**
* Flatten grouped port blocks into canonical flat {@link DevicePort} entries.
* @public
*/
declare function flattenDevicePortsFromGroups({ portGroups, rearPortGroups, }: {
portGroups?: PortBlock[];
rearPortGroups?: PortBlock[];
}): DevicePort[];
/**
* Build grouped port blocks from flat device port input.
* @public
*/
declare function buildDevicePortGroupsFromPorts(ports: DevicePort[] | undefined): {
portGroups: PortBlock[];
rearPortGroups: PortBlock[];
};
interface ListPossiblePortHandleIdsOptions {
deviceType?: string;
face?: DeviceFace;
}
/**
* List the **canonical** handle ids that may exist on a rendered node for a port.
*
* The returned ids are in canonical form. When a device renders both faces and a
* front/rear handle id collides, the renderer disambiguates the hidden face with a
* `--rear` / `--front` suffix (see `normalizeHandleId`). This function does not emit
* those split-face aliases, so callers reconciling stored/rendered handles should
* compare through {@link normalizeHandleId} (which strips the suffix) rather than by
* raw string equality. {@link validatePortHandleOnNodeData} does this for you.
* @public
*/
declare function listPossiblePortHandleIds(portBlockName: string, port: Pick, options?: ListPossiblePortHandleIdsOptions): string[];
/**
* A rendered/stored handle id resolved back to the port that owns it.
* @public
*/
interface DescribedPortHandle {
/** The port the handle belongs to. */
port: DevicePort;
/** The port block (group) the port renders under. */
blockName: string;
/** The device face the block renders on. */
face: DeviceFace;
}
/**
* Resolve a handle id back to the port that owns it — the inverse of
* {@link buildPortHandleId} / {@link listPossiblePortHandleIds} over a node's
* resolved port blocks. Directional suffixes and split-face aliases are
* normalized before comparison (via {@link normalizeHandleId}), so ids taken
* straight from edges match. Pass `options.sanitizeHandle` to match under an
* additional app-side normalization (e.g. a `+`-insensitive comparison). Prefer
* {@link handlesMatch} / {@link findEdgesForPortHandle} for whole-handle matching.
* Returns `undefined` when no port on the node renders that handle.
* @public
*/
declare function describePortHandle(nodeData: Pick | undefined, handle: string, options?: {
deviceType?: string;
sanitizeHandle?: (value: string) => string;
}): DescribedPortHandle | undefined;
/**
* Validate that a handle id exists on node data after grouping resolution.
* @public
*/
declare function validatePortHandleOnNodeData(nodeData: Pick | undefined, handle: string, options?: {
deviceType?: string;
sanitizeHandle?: (value: string) => string;
}): boolean;
/**
* Convert flat ports to grouped blocks using the same rules as renderers.
* @public
*/
declare function resolveDevicePortBlocksFromFlatPorts(ports: DevicePort[]): {
portGroups: PortBlock[];
rearPortGroups: PortBlock[];
};
/**
* Normalize source/target directional suffixes used for bidirectional handles.
* Example: `Rear Ports-1-target` -\> `Rear Ports-1`.
*/
declare function normalizeDirectionalHandleId(handleId: string | null | undefined): string;
/**
* Normalize rendered handle ids back to their canonical endpoint id.
* This removes the directional suffix and the split-face alias, in the order
* the renderer composes them: `[--rear|--front][-source|-target]`.
* (Previously only `--rear` was stripped; `--front` ghost aliases now
* normalize too.)
*
* Losslessness: the strip is unconditional, so it is only safe when the base
* itself cannot end in a reserved suffix. {@link buildPortHandleId} guarantees
* this for device ports — a label like `target` or `foo-target` is escaped at
* build time (`-%74arget`, `-foo%2Dtarget`) so it survives
* normalization intact. Bare power labels and explicit `port.id`s are emitted
* verbatim, so a caller that chooses an id ending in `-source`/`-target`/
* `--rear`/`--front` owns that collision.
*/
declare function normalizeHandleId(handleId: string | null | undefined): string;
/**
* Returns true when a port type is considered power-related.
*/
declare function isPowerPortType(portType: string | undefined): boolean;
/**
* Handle id of a vertical PDU's supply inlet — the single point where a branch
* circuit from a distribution-board breaker way lands.
*
* Deliberately non-numeric so it can never collide with an outlet id, which is
* always a bare 1-based number (`"1"…"N"`).
* @public
*/
declare const PDU_INLET_HANDLE_ID = "inlet";
/**
* Handle id of a distribution board's incomer — the board's own supply inlet,
* mounted by `DistributionBoardNode`. The board analogue of
* {@link PDU_INLET_HANDLE_ID}.
* @public
*/
declare const BOARD_INCOMER_HANDLE_ID = "incomer";
/**
* Whether a handle id names a power-distribution node's supply inlet
* (`inlet` on a PDU strip, `incomer` on a distribution board). Directional and
* face-alias suffixes are normalized away first.
* @public
*/
declare function isPowerInletHandleId(handleId: string | null | undefined): boolean;
/**
* Build a stable handle id for a port.
*
* Three shapes, in priority order:
* 1. An explicit `port.id` is returned verbatim — it is the caller's contract,
* including any collision with a fixed grammar shape.
* 2. A power port returns its `label` verbatim, preserving legacy ids (bare
* outlet numbers, `PSU-A`, ...).
* 3. Otherwise the group and label are percent-escaped and joined
* (`-