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.js'; 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 * (`-