@mp70/react-networks 0.3.0-alpha.3 → 0.5.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @mp70/react-networks
1
+ # react-networks
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@mp70/react-networks.svg)](https://www.npmjs.com/package/@mp70/react-networks)
4
4
  [![license](https://img.shields.io/npm/l/@mp70/react-networks.svg)](https://www.npmjs.com/package/@mp70/react-networks)
@@ -56,13 +56,10 @@ const nodes: NetworkNode[] = [
56
56
  data: {
57
57
  label: 'Web Server',
58
58
  uPosition: 1,
59
- ports: [{
60
- name: 'Network Ports',
61
- ports: [
62
- { label: 'eth0', connected: true, type: 'ethernet' },
63
- { label: 'eth1', connected: false, type: 'ethernet' }
64
- ]
65
- }]
59
+ ports: [
60
+ { id: 'eth0', label: 'eth0', group: 'Network Ports', connected: true, type: 'ethernet' },
61
+ { id: 'eth1', label: 'eth1', group: 'Network Ports', connected: false, type: 'ethernet' }
62
+ ]
66
63
  }
67
64
  }
68
65
  ];
@@ -250,13 +247,10 @@ const rackSchema: RackConfig[] = [
250
247
  height: 1,
251
248
  type: 'server',
252
249
  width: Width.FULL,
253
- ports: [{
254
- name: 'Network Ports',
255
- ports: [
256
- { label: 'eth0', connected: true, type: 'ethernet' },
257
- { label: 'eth1', connected: false, type: 'ethernet' }
258
- ]
259
- }]
250
+ ports: [
251
+ { id: 'eth0', label: 'eth0', group: 'Network Ports', connected: true, type: 'ethernet' },
252
+ { id: 'eth1', label: 'eth1', group: 'Network Ports', connected: false, type: 'ethernet' }
253
+ ]
260
254
  },
261
255
  {
262
256
  id: 'switch-1',
@@ -265,13 +259,10 @@ const rackSchema: RackConfig[] = [
265
259
  height: 1,
266
260
  type: 'switch',
267
261
  width: Width.FULL,
268
- ports: [{
269
- name: 'Uplink Ports',
270
- ports: [
271
- { label: 'uplink-1', connected: true, type: 'fiber' },
272
- { label: 'uplink-2', connected: false, type: 'fiber' }
273
- ]
274
- }]
262
+ ports: [
263
+ { id: 'port-1', label: 'port-1', group: 'Uplink Ports', connected: true, type: 'fiber' },
264
+ { id: 'port-2', label: 'port-2', group: 'Uplink Ports', connected: false, type: 'fiber' }
265
+ ]
275
266
  }
276
267
  ]
277
268
  }
@@ -280,6 +271,8 @@ const rackSchema: RackConfig[] = [
280
271
  const nodes = buildNodesFromRackConfig(rackSchema);
281
272
  ```
282
273
 
274
+ For the simplest device API, pass a flat `ports: DevicePort[]` array and set `group` / `face` per port. Use `portGroups` and `rearPortGroups` only when you need explicit grouped front/rear control.
275
+
283
276
  Device images (e.g. server front/rear photos or SVGs) use `frontImageUrl` and `rearImageUrl` on each device; supported formats include PNG, SVG, JPEG, GIF, and WebP.
284
277
 
285
278
  ### Inventory Integration
package/dist/index.css CHANGED
@@ -1,4 +1,4 @@
1
- /* Library styles for @mp70/react-networks */
1
+ /* Library styles for react-networks */
2
2
 
3
3
  /* Edge zIndex styles for dynamic prominence */
4
4
  .edge-prominent {
@@ -393,6 +393,11 @@
393
393
  text-align: center;
394
394
  }
395
395
 
396
+ .device-node__content--image-badge {
397
+ overflow: visible;
398
+ text-align: left;
399
+ }
400
+
396
401
  .device-node__label {
397
402
  font-size: 7px;
398
403
  font-weight: bold;
@@ -402,6 +407,15 @@
402
407
  white-space: nowrap;
403
408
  }
404
409
 
410
+ .device-node__label--badge {
411
+ background: rgba(15, 23, 42, 0.88);
412
+ border: 1px solid rgba(148, 163, 184, 0.45);
413
+ border-radius: 2px;
414
+ display: inline-flex;
415
+ max-width: 100%;
416
+ padding: 1px 4px;
417
+ }
418
+
405
419
  .device-node__manufacturer {
406
420
  color: #9ca3af;
407
421
  font-size: 6px;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/styles/index.css"],"sourcesContent":["/* Library styles for @mp70/react-networks */\n\n/* Edge zIndex styles for dynamic prominence */\n.edge-prominent {\n z-index: 1000 !important;\n opacity: 1 !important;\n}\n\n.edge-dimmed {\n z-index: 500 !important;\n opacity: 0.3 !important;\n}\n\n/* Dynamic zIndex classes */\n.edge-z-1000 {\n z-index: 1000 !important;\n}\n\n.edge-z-500 {\n z-index: 500 !important;\n}\n\n/* React Flow theming */\n.react-flow-dark {\n --react-flow-background: #1a1a1a;\n --react-flow-text: #ffffff;\n --react-flow-border: #404040;\n --react-flow-controls-bg: #2d2d2d;\n --react-flow-controls-border: #404040;\n --react-flow-minimap-bg: #2d2d2d;\n --react-flow-minimap-border: #404040;\n}\n\n.react-flow-light {\n --react-flow-background: #f5f5f5;\n --react-flow-text: #1a1a1a;\n --react-flow-border: #e9ecef;\n --react-flow-controls-bg: #f8f9fa;\n --react-flow-controls-border: #e9ecef;\n --react-flow-minimap-bg: #f8f9fa;\n --react-flow-minimap-border: #e9ecef;\n}\n\n/* Apply ReactFlow theming */\n.react-flow-dark .react-flow__background {\n background-color: var(--react-flow-background);\n}\n\n.react-flow-light .react-flow__background {\n background-color: var(--react-flow-background);\n}\n\n/* Ensure canvas background is always off-white in light mode */\n.react-flow-light .react-flow__viewport {\n background-color: #f5f5f5 !important;\n}\n\n.react-flow-light .react-flow {\n background-color: #f5f5f5;\n}\n\n.react-flow-dark .react-flow__controls {\n background-color: var(--react-flow-controls-bg);\n border-color: var(--react-flow-controls-border);\n}\n\n.react-flow-light .react-flow__controls {\n background-color: var(--react-flow-controls-bg);\n border-color: var(--react-flow-controls-border);\n}\n\n.react-flow-dark .react-flow__minimap {\n background-color: var(--react-flow-minimap-bg);\n border-color: var(--react-flow-minimap-border);\n}\n\n.react-flow-light .react-flow__minimap {\n background-color: var(--react-flow-minimap-bg);\n border-color: var(--react-flow-minimap-border);\n}\n\n.react-flow-dark .react-flow__controls-button {\n background-color: var(--react-flow-controls-bg);\n color: var(--react-flow-text);\n border-color: var(--react-flow-controls-border);\n}\n\n.react-flow-light .react-flow__controls-button {\n background-color: var(--react-flow-controls-bg);\n color: var(--react-flow-text);\n border-color: var(--react-flow-controls-border);\n}\n\n.react-flow-dark .react-flow__controls-button:hover {\n background-color: #4b5563;\n}\n\n.react-flow-light .react-flow__controls-button:hover {\n background-color: #e9ecef;\n}\n\n/* Mobile-friendly control buttons - larger touch targets */\n@media (max-width: 768px) {\n .react-flow__controls-button {\n width: 44px !important;\n height: 44px !important;\n min-width: 44px !important;\n min-height: 44px !important;\n }\n}\n\n/* Override React Flow default handle styles for tube fiber handles */\n/* Use CSS variables set by inline styles to override React Flow defaults */\n.react-flow__handle.tube-fiber-handle {\n border: none !important;\n transform: none !important;\n background-clip: padding-box !important;\n opacity: 1 !important;\n /* Ensure no default React Flow styles interfere */\n box-shadow: none !important;\n /* Don't set background here - let inline styles or specific rules handle it */\n}\n\n/* Tube fiber handles - use fiber color */\n.react-flow__handle.tube-fiber-handle {\n background: var(--fiber-bg-image, var(--fiber-bg)) !important;\n background-color: var(--fiber-bg) !important;\n}\n\n/* Fibre flow map handles */\n.react-flow__handle.fibre-flow-handle {\n border: none !important;\n transform: none !important;\n background-clip: padding-box !important;\n opacity: 1 !important;\n box-shadow: none !important;\n}\n\n.react-flow__handle.fibre-flow-handle {\n background: var(--fiber-bg-image, var(--fiber-bg)) !important;\n background-color: var(--fiber-bg) !important;\n background-image: var(--fiber-bg-image, none) !important;\n}\n\n\n.react-flow__handle-right.tube-fiber-handle {\n right: auto !important;\n}\n\n/* More specific selector to ensure override - target all possible React Flow handle states */\n/* Only apply to non-ribbon handles */\n.react-flow__node .react-flow__handle.tube-fiber-handle:not(.ribbon-bundle-handle),\n.react-flow__node .react-flow__handle.tube-fiber-handle:not(.ribbon-bundle-handle)[data-handlepos],\n.react-flow__handle.tube-fiber-handle:not(.ribbon-bundle-handle)[data-handlepos],\n.react-flow__handle.tube-fiber-handle:not(.ribbon-bundle-handle)[data-fiber-idx] {\n background: var(--fiber-bg-image, var(--fiber-bg)) !important;\n background-color: var(--fiber-bg) !important;\n background-image: var(--fiber-bg-image, none) !important;\n}\n\n/* Ribbon bundle handles - simple solid color (removed gradient complexity) */\n\n/* Connection animation - pulse effect when edge is first created */\n@keyframes edgeConnectPulse {\n 0% {\n opacity: 0;\n stroke-width: 1;\n }\n 50% {\n opacity: 1;\n stroke-width: 4;\n }\n 100% {\n opacity: 1;\n stroke-width: 2;\n }\n}\n\n.react-flow__edge.edge-connecting path,\n.react-flow__edge.edge-connecting .react-flow__edge-path {\n animation: edgeConnectPulse 1s ease-out;\n}\n\n/* Highlighted edge pulse + dash motion (trace paths) */\n@keyframes rnEdgeHighlightPulse {\n 0%,\n 100% {\n opacity: 0.85;\n stroke-width: var(--rn-edge-highlight-from, 2px);\n }\n 50% {\n opacity: 1;\n stroke-width: var(--rn-edge-highlight-to, 6px);\n }\n}\n\n@keyframes rnEdgeHighlightDash {\n 0% {\n stroke-dashoffset: 0;\n }\n 100% {\n stroke-dashoffset: -48;\n }\n}\n\n.rn-edge-highlight {\n animation: rnEdgeHighlightPulse 2.4s ease-in-out infinite,\n rnEdgeHighlightDash 1.8s linear infinite;\n opacity: 0.9;\n stroke-linecap: round;\n}\n\n/* Splice glow animation - fade in when both handles are connected */\n@keyframes spliceGlowFadeIn {\n 0% {\n opacity: 0;\n transform: translateX(-50%) scaleY(0.3);\n }\n 100% {\n opacity: 1;\n transform: translateX(-50%) scaleY(1);\n }\n}\n\n.splice-glow {\n animation: spliceGlowFadeIn 0.5s ease-out;\n transform-origin: center;\n}\n\n/* Splice text animation - fade in when both handles are connected */\n@keyframes spliceTextFadeIn {\n 0% {\n opacity: 0;\n transform: scale(0.8);\n }\n 100% {\n opacity: 1;\n transform: scale(1);\n }\n}\n\n/* Splice text overlay - blur and opacity for text readability */\n.splice-text-overlay {\n backdrop-filter: blur(2px);\n -webkit-backdrop-filter: blur(2px);\n}\n\n/* Dark mode overlay */\n.react-flow-dark .splice-text-overlay {\n background: rgba(0, 0, 0, 0.6);\n}\n\n/* Light mode overlay */\n.react-flow-light .splice-text-overlay {\n background: rgba(255, 255, 255, 0.7);\n}\n\n/* Splice text label - theme-aware colors */\n.splice-text-label {\n color: #fff;\n text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8), 0 0 2px rgba(0, 0, 0, 0.6);\n}\n\n.splice-text-label.animate-in {\n animation: spliceTextFadeIn 0.5s ease-out;\n transform-origin: center;\n}\n\n/* Light mode text */\n.react-flow-light .splice-text-label {\n color: #1a1a1a;\n text-shadow: 0 1px 2px rgba(255, 255, 255, 0.9), 0 0 4px rgba(255, 255, 255, 0.7);\n}\n\n/* Remove all borders from splice nodes */\n.react-flow__node[data-type=\"splice\"],\n.react-flow__node[data-type=\"splice\"] > div {\n border: none !important;\n outline: none !important;\n}\n\n.splice-node {\n background: transparent !important;\n border: none !important;\n outline: none !important;\n}\n\n/* Only remove box-shadow when not selected */\n.react-flow__node[data-type=\"splice\"]:not(.selected) > div,\n.splice-node:not(.selected) {\n box-shadow: none !important;\n}\n\n/* React Networks Attribution */\n.react-networks-attribution {\n padding: 0;\n margin: 0;\n z-index: 1000;\n}\n\n.react-networks-attribution-link {\n font-size: 7px;\n color: #9ca3af;\n text-decoration: none;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n transition: color 0.2s ease;\n padding: 1px 4px;\n border-radius: 2px;\n display: inline-block;\n background: rgba(0, 0, 0, 0.3);\n backdrop-filter: blur(4px);\n}\n\n.react-networks-attribution-link:hover {\n color: #e5e7eb;\n background: rgba(0, 0, 0, 0.4);\n}\n\n/* Light mode attribution */\n.react-flow-light .react-networks-attribution-link {\n color: #6b7280;\n background: rgba(255, 255, 255, 0.6);\n}\n\n.react-flow-light .react-networks-attribution-link:hover {\n color: #1a1a1a;\n background: rgba(255, 255, 255, 0.8);\n}\n\n/* Coupler node styles */\n.coupler-node {\n transition: box-shadow 0.2s ease;\n}\n\n.coupler-node.selected {\n box-shadow: 0 0 0 2px #3b82f6 !important;\n}\n\n/* Patch panel node styles */\n.patch-panel-node {\n transition: box-shadow 0.2s ease, border-color 0.2s ease;\n}\n\n.patch-panel-node.selected {\n box-shadow: 0 0 0 2px #3b82f6 !important;\n border-color: #3b82f6 !important;\n}\n\n/* Closure node styles */\n.closure-node {\n transition: box-shadow 0.2s ease, border-color 0.2s ease;\n}\n\n.closure-node.selected {\n box-shadow: 0 0 0 2px #3b82f6 !important;\n border-color: #3b82f6 !important;\n}\n\n/* Device node styles */\n.device-node {\n background-color: #1f2937;\n background-image: var(--rn-device-bg-image, none);\n background-position: var(--rn-device-bg-position, center);\n background-repeat: var(--rn-device-bg-repeat, no-repeat);\n background-size: var(--rn-device-bg-size, cover);\n border: 1px solid var(--rn-device-border-color, #10b981);\n border-radius: 2px;\n box-shadow: var(--rn-device-box-shadow, 0 1px 2px rgba(0, 0, 0, 0.1));\n box-sizing: border-box;\n color: white;\n cursor: grab;\n display: flex;\n flex-direction: column;\n font-family: monospace;\n font-size: 8px;\n height: var(--rn-device-height, 20px);\n justify-content: center;\n left: var(--rn-device-left, auto);\n margin: 0;\n padding: 0;\n position: relative;\n transform: translateY(var(--rn-device-translate-y, 0px));\n width: var(--rn-device-width, 100%);\n}\n\n.device-node__content {\n display: flex;\n flex: 1;\n flex-direction: column;\n justify-content: center;\n overflow: hidden;\n padding: 2px;\n text-align: center;\n}\n\n.device-node__label {\n font-size: 7px;\n font-weight: bold;\n line-height: 1.1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.device-node__manufacturer {\n color: #9ca3af;\n font-size: 6px;\n line-height: 1;\n}\n\n.device-node__u-indicator {\n background: #374151;\n border-radius: 1px;\n bottom: 1px;\n color: #9ca3af;\n font-size: 6px;\n line-height: 1;\n padding: 1px 2px;\n position: absolute;\n right: 1px;\n}\n\n.device-node__status-indicator {\n background: var(--rn-device-status-color, #6b7280);\n border-radius: 50%;\n bottom: 1px;\n height: 4px;\n left: 1px;\n position: absolute;\n width: 4px;\n}\n\n.device-node__split-face {\n background-color: #1f2937;\n}\n\n.device-node__split-face--front {\n border-left: 1px dashed rgba(156, 163, 175, 0.35);\n}\n\n.device-node__split-face--rear {\n border-right: 1px dashed rgba(156, 163, 175, 0.35);\n}\n\n.device-node__face-bridge {\n position: relative;\n}\n\n.device-node__face-bridge-line {\n background: linear-gradient(180deg, transparent 0%, rgba(156, 163, 175, 0.8) 20%, rgba(156, 163, 175, 0.8) 80%, transparent 100%);\n left: 50%;\n position: absolute;\n top: 1px;\n transform: translateX(-50%);\n width: 1px;\n height: calc(100% - 2px);\n}\n\n.device-node__face-bridge-label {\n background: rgba(17, 24, 39, 0.82);\n border: 1px solid rgba(156, 163, 175, 0.4);\n border-radius: 999px;\n color: #d1d5db;\n font-size: 6px;\n font-weight: 700;\n left: 50%;\n letter-spacing: 0.04em;\n max-width: 120px;\n overflow: hidden;\n padding: 1px 6px;\n position: absolute;\n text-overflow: ellipsis;\n top: 50%;\n transform: translate(-50%, -50%) rotate(-90deg);\n white-space: nowrap;\n}\n\n/* Light mode adjustments */\n.react-flow-light .coupler-node {\n background: #f9fafb;\n color: #1f2937;\n border-color: #d1d5db;\n}\n\n.react-flow-light .patch-panel-node {\n background: transparent !important;\n color: #1f2937;\n border-color: #d1d5db;\n}\n\n.react-flow-light .closure-node {\n background: transparent !important;\n color: #1f2937;\n border-color: #d1d5db;\n}\n\n/* Triangle node styles */\n.react-flow__node[data-type=\"fibre-split\"],\n.react-flow__node.react-flow__node-fibre-split {\n background: transparent !important;\n border: none !important;\n padding: 0 !important;\n}\n\n.react-flow__node[data-type=\"fibre-split\"] > div,\n.react-flow__node.react-flow__node-fibre-split > div {\n background: transparent !important;\n border: none !important;\n padding: 0 !important;\n box-shadow: none !important;\n overflow: visible !important;\n}\n\n.react-flow__node[data-type=\"fibre-split\"] svg,\n.react-flow__node.react-flow__node-fibre-split svg {\n display: block !important;\n visibility: visible !important;\n opacity: 1 !important;\n}\n\n.triangle-node {\n position: relative !important;\n display: flex !important;\n align-items: center !important;\n justify-content: center !important;\n background: transparent !important;\n border: none !important;\n padding: 0 !important;\n margin: 0 !important;\n box-shadow: none !important;\n overflow: visible !important;\n box-sizing: border-box !important;\n}\n\n\n.triangle-node-text {\n text-align: center;\n position: relative;\n z-index: 2;\n width: 46px;\n height: 46px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0;\n color: #000000;\n font-weight: bold;\n font-size: 12px;\n}\n\n/* Cable node rotation */\n.cable-node-rotated {\n transform-origin: center center !important;\n}\n\n/* Ensure Fibre Flow edges render with full saturation and aren't affected by node overlays */\n.react-flow__edge[data-type=\"fibre-flow\"] path,\n.react-flow__edge[data-type=\"fibre-flow\"] .react-flow__edge-path {\n opacity: 1 !important;\n stroke-opacity: 1 !important;\n mix-blend-mode: normal !important;\n filter: none !important;\n}\n\n.react-flow__edge[data-type=\"fibre-flow-link\"] path,\n.react-flow__edge[data-type=\"fibre-flow-link\"] .react-flow__edge-path {\n opacity: 1 !important;\n stroke-opacity: 1 !important;\n mix-blend-mode: normal !important;\n filter: none !important;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/styles/index.css"],"sourcesContent":["/* Library styles for react-networks */\n\n/* Edge zIndex styles for dynamic prominence */\n.edge-prominent {\n z-index: 1000 !important;\n opacity: 1 !important;\n}\n\n.edge-dimmed {\n z-index: 500 !important;\n opacity: 0.3 !important;\n}\n\n/* Dynamic zIndex classes */\n.edge-z-1000 {\n z-index: 1000 !important;\n}\n\n.edge-z-500 {\n z-index: 500 !important;\n}\n\n/* React Flow theming */\n.react-flow-dark {\n --react-flow-background: #1a1a1a;\n --react-flow-text: #ffffff;\n --react-flow-border: #404040;\n --react-flow-controls-bg: #2d2d2d;\n --react-flow-controls-border: #404040;\n --react-flow-minimap-bg: #2d2d2d;\n --react-flow-minimap-border: #404040;\n}\n\n.react-flow-light {\n --react-flow-background: #f5f5f5;\n --react-flow-text: #1a1a1a;\n --react-flow-border: #e9ecef;\n --react-flow-controls-bg: #f8f9fa;\n --react-flow-controls-border: #e9ecef;\n --react-flow-minimap-bg: #f8f9fa;\n --react-flow-minimap-border: #e9ecef;\n}\n\n/* Apply ReactFlow theming */\n.react-flow-dark .react-flow__background {\n background-color: var(--react-flow-background);\n}\n\n.react-flow-light .react-flow__background {\n background-color: var(--react-flow-background);\n}\n\n/* Ensure canvas background is always off-white in light mode */\n.react-flow-light .react-flow__viewport {\n background-color: #f5f5f5 !important;\n}\n\n.react-flow-light .react-flow {\n background-color: #f5f5f5;\n}\n\n.react-flow-dark .react-flow__controls {\n background-color: var(--react-flow-controls-bg);\n border-color: var(--react-flow-controls-border);\n}\n\n.react-flow-light .react-flow__controls {\n background-color: var(--react-flow-controls-bg);\n border-color: var(--react-flow-controls-border);\n}\n\n.react-flow-dark .react-flow__minimap {\n background-color: var(--react-flow-minimap-bg);\n border-color: var(--react-flow-minimap-border);\n}\n\n.react-flow-light .react-flow__minimap {\n background-color: var(--react-flow-minimap-bg);\n border-color: var(--react-flow-minimap-border);\n}\n\n.react-flow-dark .react-flow__controls-button {\n background-color: var(--react-flow-controls-bg);\n color: var(--react-flow-text);\n border-color: var(--react-flow-controls-border);\n}\n\n.react-flow-light .react-flow__controls-button {\n background-color: var(--react-flow-controls-bg);\n color: var(--react-flow-text);\n border-color: var(--react-flow-controls-border);\n}\n\n.react-flow-dark .react-flow__controls-button:hover {\n background-color: #4b5563;\n}\n\n.react-flow-light .react-flow__controls-button:hover {\n background-color: #e9ecef;\n}\n\n/* Mobile-friendly control buttons - larger touch targets */\n@media (max-width: 768px) {\n .react-flow__controls-button {\n width: 44px !important;\n height: 44px !important;\n min-width: 44px !important;\n min-height: 44px !important;\n }\n}\n\n/* Override React Flow default handle styles for tube fiber handles */\n/* Use CSS variables set by inline styles to override React Flow defaults */\n.react-flow__handle.tube-fiber-handle {\n border: none !important;\n transform: none !important;\n background-clip: padding-box !important;\n opacity: 1 !important;\n /* Ensure no default React Flow styles interfere */\n box-shadow: none !important;\n /* Don't set background here - let inline styles or specific rules handle it */\n}\n\n/* Tube fiber handles - use fiber color */\n.react-flow__handle.tube-fiber-handle {\n background: var(--fiber-bg-image, var(--fiber-bg)) !important;\n background-color: var(--fiber-bg) !important;\n}\n\n/* Fibre flow map handles */\n.react-flow__handle.fibre-flow-handle {\n border: none !important;\n transform: none !important;\n background-clip: padding-box !important;\n opacity: 1 !important;\n box-shadow: none !important;\n}\n\n.react-flow__handle.fibre-flow-handle {\n background: var(--fiber-bg-image, var(--fiber-bg)) !important;\n background-color: var(--fiber-bg) !important;\n background-image: var(--fiber-bg-image, none) !important;\n}\n\n\n.react-flow__handle-right.tube-fiber-handle {\n right: auto !important;\n}\n\n/* More specific selector to ensure override - target all possible React Flow handle states */\n/* Only apply to non-ribbon handles */\n.react-flow__node .react-flow__handle.tube-fiber-handle:not(.ribbon-bundle-handle),\n.react-flow__node .react-flow__handle.tube-fiber-handle:not(.ribbon-bundle-handle)[data-handlepos],\n.react-flow__handle.tube-fiber-handle:not(.ribbon-bundle-handle)[data-handlepos],\n.react-flow__handle.tube-fiber-handle:not(.ribbon-bundle-handle)[data-fiber-idx] {\n background: var(--fiber-bg-image, var(--fiber-bg)) !important;\n background-color: var(--fiber-bg) !important;\n background-image: var(--fiber-bg-image, none) !important;\n}\n\n/* Ribbon bundle handles - simple solid color (removed gradient complexity) */\n\n/* Connection animation - pulse effect when edge is first created */\n@keyframes edgeConnectPulse {\n 0% {\n opacity: 0;\n stroke-width: 1;\n }\n 50% {\n opacity: 1;\n stroke-width: 4;\n }\n 100% {\n opacity: 1;\n stroke-width: 2;\n }\n}\n\n.react-flow__edge.edge-connecting path,\n.react-flow__edge.edge-connecting .react-flow__edge-path {\n animation: edgeConnectPulse 1s ease-out;\n}\n\n/* Highlighted edge pulse + dash motion (trace paths) */\n@keyframes rnEdgeHighlightPulse {\n 0%,\n 100% {\n opacity: 0.85;\n stroke-width: var(--rn-edge-highlight-from, 2px);\n }\n 50% {\n opacity: 1;\n stroke-width: var(--rn-edge-highlight-to, 6px);\n }\n}\n\n@keyframes rnEdgeHighlightDash {\n 0% {\n stroke-dashoffset: 0;\n }\n 100% {\n stroke-dashoffset: -48;\n }\n}\n\n.rn-edge-highlight {\n animation: rnEdgeHighlightPulse 2.4s ease-in-out infinite,\n rnEdgeHighlightDash 1.8s linear infinite;\n opacity: 0.9;\n stroke-linecap: round;\n}\n\n/* Splice glow animation - fade in when both handles are connected */\n@keyframes spliceGlowFadeIn {\n 0% {\n opacity: 0;\n transform: translateX(-50%) scaleY(0.3);\n }\n 100% {\n opacity: 1;\n transform: translateX(-50%) scaleY(1);\n }\n}\n\n.splice-glow {\n animation: spliceGlowFadeIn 0.5s ease-out;\n transform-origin: center;\n}\n\n/* Splice text animation - fade in when both handles are connected */\n@keyframes spliceTextFadeIn {\n 0% {\n opacity: 0;\n transform: scale(0.8);\n }\n 100% {\n opacity: 1;\n transform: scale(1);\n }\n}\n\n/* Splice text overlay - blur and opacity for text readability */\n.splice-text-overlay {\n backdrop-filter: blur(2px);\n -webkit-backdrop-filter: blur(2px);\n}\n\n/* Dark mode overlay */\n.react-flow-dark .splice-text-overlay {\n background: rgba(0, 0, 0, 0.6);\n}\n\n/* Light mode overlay */\n.react-flow-light .splice-text-overlay {\n background: rgba(255, 255, 255, 0.7);\n}\n\n/* Splice text label - theme-aware colors */\n.splice-text-label {\n color: #fff;\n text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8), 0 0 2px rgba(0, 0, 0, 0.6);\n}\n\n.splice-text-label.animate-in {\n animation: spliceTextFadeIn 0.5s ease-out;\n transform-origin: center;\n}\n\n/* Light mode text */\n.react-flow-light .splice-text-label {\n color: #1a1a1a;\n text-shadow: 0 1px 2px rgba(255, 255, 255, 0.9), 0 0 4px rgba(255, 255, 255, 0.7);\n}\n\n/* Remove all borders from splice nodes */\n.react-flow__node[data-type=\"splice\"],\n.react-flow__node[data-type=\"splice\"] > div {\n border: none !important;\n outline: none !important;\n}\n\n.splice-node {\n background: transparent !important;\n border: none !important;\n outline: none !important;\n}\n\n/* Only remove box-shadow when not selected */\n.react-flow__node[data-type=\"splice\"]:not(.selected) > div,\n.splice-node:not(.selected) {\n box-shadow: none !important;\n}\n\n/* React Networks Attribution */\n.react-networks-attribution {\n padding: 0;\n margin: 0;\n z-index: 1000;\n}\n\n.react-networks-attribution-link {\n font-size: 7px;\n color: #9ca3af;\n text-decoration: none;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n transition: color 0.2s ease;\n padding: 1px 4px;\n border-radius: 2px;\n display: inline-block;\n background: rgba(0, 0, 0, 0.3);\n backdrop-filter: blur(4px);\n}\n\n.react-networks-attribution-link:hover {\n color: #e5e7eb;\n background: rgba(0, 0, 0, 0.4);\n}\n\n/* Light mode attribution */\n.react-flow-light .react-networks-attribution-link {\n color: #6b7280;\n background: rgba(255, 255, 255, 0.6);\n}\n\n.react-flow-light .react-networks-attribution-link:hover {\n color: #1a1a1a;\n background: rgba(255, 255, 255, 0.8);\n}\n\n/* Coupler node styles */\n.coupler-node {\n transition: box-shadow 0.2s ease;\n}\n\n.coupler-node.selected {\n box-shadow: 0 0 0 2px #3b82f6 !important;\n}\n\n/* Patch panel node styles */\n.patch-panel-node {\n transition: box-shadow 0.2s ease, border-color 0.2s ease;\n}\n\n.patch-panel-node.selected {\n box-shadow: 0 0 0 2px #3b82f6 !important;\n border-color: #3b82f6 !important;\n}\n\n/* Closure node styles */\n.closure-node {\n transition: box-shadow 0.2s ease, border-color 0.2s ease;\n}\n\n.closure-node.selected {\n box-shadow: 0 0 0 2px #3b82f6 !important;\n border-color: #3b82f6 !important;\n}\n\n/* Device node styles */\n.device-node {\n background-color: #1f2937;\n background-image: var(--rn-device-bg-image, none);\n background-position: var(--rn-device-bg-position, center);\n background-repeat: var(--rn-device-bg-repeat, no-repeat);\n background-size: var(--rn-device-bg-size, cover);\n border: 1px solid var(--rn-device-border-color, #10b981);\n border-radius: 2px;\n box-shadow: var(--rn-device-box-shadow, 0 1px 2px rgba(0, 0, 0, 0.1));\n box-sizing: border-box;\n color: white;\n cursor: grab;\n display: flex;\n flex-direction: column;\n font-family: monospace;\n font-size: 8px;\n height: var(--rn-device-height, 20px);\n justify-content: center;\n left: var(--rn-device-left, auto);\n margin: 0;\n padding: 0;\n position: relative;\n transform: translateY(var(--rn-device-translate-y, 0px));\n width: var(--rn-device-width, 100%);\n}\n\n.device-node__content {\n display: flex;\n flex: 1;\n flex-direction: column;\n justify-content: center;\n overflow: hidden;\n padding: 2px;\n text-align: center;\n}\n\n.device-node__content--image-badge {\n overflow: visible;\n text-align: left;\n}\n\n.device-node__label {\n font-size: 7px;\n font-weight: bold;\n line-height: 1.1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.device-node__label--badge {\n background: rgba(15, 23, 42, 0.88);\n border: 1px solid rgba(148, 163, 184, 0.45);\n border-radius: 2px;\n display: inline-flex;\n max-width: 100%;\n padding: 1px 4px;\n}\n\n.device-node__manufacturer {\n color: #9ca3af;\n font-size: 6px;\n line-height: 1;\n}\n\n.device-node__u-indicator {\n background: #374151;\n border-radius: 1px;\n bottom: 1px;\n color: #9ca3af;\n font-size: 6px;\n line-height: 1;\n padding: 1px 2px;\n position: absolute;\n right: 1px;\n}\n\n.device-node__status-indicator {\n background: var(--rn-device-status-color, #6b7280);\n border-radius: 50%;\n bottom: 1px;\n height: 4px;\n left: 1px;\n position: absolute;\n width: 4px;\n}\n\n.device-node__split-face {\n background-color: #1f2937;\n}\n\n.device-node__split-face--front {\n border-left: 1px dashed rgba(156, 163, 175, 0.35);\n}\n\n.device-node__split-face--rear {\n border-right: 1px dashed rgba(156, 163, 175, 0.35);\n}\n\n.device-node__face-bridge {\n position: relative;\n}\n\n.device-node__face-bridge-line {\n background: linear-gradient(180deg, transparent 0%, rgba(156, 163, 175, 0.8) 20%, rgba(156, 163, 175, 0.8) 80%, transparent 100%);\n left: 50%;\n position: absolute;\n top: 1px;\n transform: translateX(-50%);\n width: 1px;\n height: calc(100% - 2px);\n}\n\n.device-node__face-bridge-label {\n background: rgba(17, 24, 39, 0.82);\n border: 1px solid rgba(156, 163, 175, 0.4);\n border-radius: 999px;\n color: #d1d5db;\n font-size: 6px;\n font-weight: 700;\n left: 50%;\n letter-spacing: 0.04em;\n max-width: 120px;\n overflow: hidden;\n padding: 1px 6px;\n position: absolute;\n text-overflow: ellipsis;\n top: 50%;\n transform: translate(-50%, -50%) rotate(-90deg);\n white-space: nowrap;\n}\n\n/* Light mode adjustments */\n.react-flow-light .coupler-node {\n background: #f9fafb;\n color: #1f2937;\n border-color: #d1d5db;\n}\n\n.react-flow-light .patch-panel-node {\n background: transparent !important;\n color: #1f2937;\n border-color: #d1d5db;\n}\n\n.react-flow-light .closure-node {\n background: transparent !important;\n color: #1f2937;\n border-color: #d1d5db;\n}\n\n/* Triangle node styles */\n.react-flow__node[data-type=\"fibre-split\"],\n.react-flow__node.react-flow__node-fibre-split {\n background: transparent !important;\n border: none !important;\n padding: 0 !important;\n}\n\n.react-flow__node[data-type=\"fibre-split\"] > div,\n.react-flow__node.react-flow__node-fibre-split > div {\n background: transparent !important;\n border: none !important;\n padding: 0 !important;\n box-shadow: none !important;\n overflow: visible !important;\n}\n\n.react-flow__node[data-type=\"fibre-split\"] svg,\n.react-flow__node.react-flow__node-fibre-split svg {\n display: block !important;\n visibility: visible !important;\n opacity: 1 !important;\n}\n\n.triangle-node {\n position: relative !important;\n display: flex !important;\n align-items: center !important;\n justify-content: center !important;\n background: transparent !important;\n border: none !important;\n padding: 0 !important;\n margin: 0 !important;\n box-shadow: none !important;\n overflow: visible !important;\n box-sizing: border-box !important;\n}\n\n\n.triangle-node-text {\n text-align: center;\n position: relative;\n z-index: 2;\n width: 46px;\n height: 46px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0;\n color: #000000;\n font-weight: bold;\n font-size: 12px;\n}\n\n/* Cable node rotation */\n.cable-node-rotated {\n transform-origin: center center !important;\n}\n\n/* Ensure Fibre Flow edges render with full saturation and aren't affected by node overlays */\n.react-flow__edge[data-type=\"fibre-flow\"] path,\n.react-flow__edge[data-type=\"fibre-flow\"] .react-flow__edge-path {\n opacity: 1 !important;\n stroke-opacity: 1 !important;\n mix-blend-mode: normal !important;\n filter: none !important;\n}\n\n.react-flow__edge[data-type=\"fibre-flow-link\"] path,\n.react-flow__edge[data-type=\"fibre-flow-link\"] .react-flow__edge-path {\n opacity: 1 !important;\n stroke-opacity: 1 !important;\n mix-blend-mode: normal !important;\n filter: none !important;\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.mts CHANGED
@@ -61,7 +61,7 @@ interface NetBoxMappedPort {
61
61
  position?: NetBoxPortPosition;
62
62
  }
63
63
  interface NetBoxDeviceTransformOptions {
64
- /** Include interface arrays as `ports`/`rearPorts` in generated nodes */
64
+ /** Include interface arrays as flat `ports` in generated nodes */
65
65
  includeInterfacePorts?: boolean;
66
66
  /** Include per-port position metadata when provided */
67
67
  includePortPositions?: boolean;
@@ -253,7 +253,7 @@ type PortCableType = 'smf' | 'cat6' | 'om3' | 'om4' | 'om5' | 'cat5e' | 'cat6a'
253
253
  *
254
254
  * @public
255
255
  */
256
- type DeviceImageFit = 'cover' | 'contain' | 'fill' | 'none' | 'scale-down';
256
+ type DeviceImageFit = 'cover' | 'contain' | 'contain-height' | 'fill' | 'none' | 'scale-down';
257
257
  /**
258
258
  * Device image repeat options.
259
259
  * @public
@@ -297,6 +297,40 @@ interface PortPosition {
297
297
  /** Absolute Y position as percentage (0-100) */
298
298
  yPercent?: number;
299
299
  }
300
+ /**
301
+ * Recommended simple port input for device and PDU rendering.
302
+ * Pass a flat array in `ports`; `react-networks` groups entries into front/rear
303
+ * `PortBlock[]` internally using each port's `face` and `group`.
304
+ * @public
305
+ */
306
+ interface DevicePort {
307
+ /** Stable handle identifier override (defaults to `group + label`, or `label` for power) */
308
+ id?: string;
309
+ /** Display label for the port */
310
+ label: string;
311
+ /** Front/rear device face. `back` is accepted as an alias for `rear`. */
312
+ face?: DeviceView | 'back';
313
+ /** Optional block/group name (for example `Network Ports`, `QSFP28`, `Power Ports`) */
314
+ group?: string;
315
+ /** Whether the port is currently connected */
316
+ connected?: boolean;
317
+ /** Type of port for styling and validation */
318
+ type?: PortType;
319
+ /** Cable type for connection validation */
320
+ cableType?: PortCableType;
321
+ /** Optional power connector type for rendering (mainly for PDU ports) */
322
+ connectorType?: 'C13' | 'C14' | 'C19' | 'C20';
323
+ /** Optional compatibility keys. When omitted, defaults are derived from type/cable/connector data. */
324
+ compatibility?: string[];
325
+ /** Optional sort order within a face/group bucket */
326
+ order?: number;
327
+ /**
328
+ * Optional placement hint.
329
+ * Use `side` with `offsetPercent`/`offsetPx` for edge-anchored placement,
330
+ * or `xPercent`/`yPercent` for exact coordinates.
331
+ */
332
+ position?: PortPosition;
333
+ }
300
334
  interface Port {
301
335
  /** Stable handle identifier override (defaults to `label`) */
302
336
  id?: string;
@@ -310,6 +344,8 @@ interface Port {
310
344
  cableType?: PortCableType;
311
345
  /** Optional power connector type for rendering (mainly for PDU ports) */
312
346
  connectorType?: 'C13' | 'C14' | 'C19' | 'C20';
347
+ /** Optional compatibility keys. When omitted, defaults are derived from type/cable/connector data. */
348
+ compatibility?: string[];
313
349
  /** Optional explicit handle placement */
314
350
  position?: PortPosition;
315
351
  }
@@ -368,10 +404,12 @@ interface NetworkNodeDataBase extends DeviceImageOptions {
368
404
  label: string;
369
405
  /** Current status of the node/device */
370
406
  status?: DeviceStatus | string;
371
- /** Port configurations for device-like nodes */
372
- ports?: PortBlock[] | number;
373
- /** Rear port configurations */
374
- rearPorts?: PortBlock[];
407
+ /** Simple flat port input. Use `portGroups` / `rearPortGroups` for explicit grouped control. */
408
+ ports?: DevicePort[] | number;
409
+ /** Advanced grouped front port input */
410
+ portGroups?: PortBlock[];
411
+ /** Advanced grouped rear port input */
412
+ rearPortGroups?: PortBlock[];
375
413
  /** List of connected node IDs */
376
414
  connections?: string[];
377
415
  /** NetBox integration ID */
@@ -386,6 +424,8 @@ interface NetworkNodeDataBase extends DeviceImageOptions {
386
424
  uHeight?: number;
387
425
  /** Device position within rack in U units */
388
426
  uPosition?: number;
427
+ /** Whether this node is currently mounted inside a rack container */
428
+ isRackMounted?: boolean;
389
429
  /** Current front/rear view orientation */
390
430
  view?: DeviceView;
391
431
  /** Rack face orientation */
@@ -521,14 +561,14 @@ interface NetworkNodeDataBase extends DeviceImageOptions {
521
561
  * @public
522
562
  */
523
563
  interface RackNodeData extends NetworkNodeDataBase {
524
- ports?: number | PortBlock[];
564
+ ports?: number;
525
565
  }
526
566
  /**
527
567
  * Device-like node data (servers/switches/routers/generic device).
528
568
  * @public
529
569
  */
530
570
  interface DeviceNodeData extends NetworkNodeDataBase {
531
- ports?: PortBlock[] | number;
571
+ ports?: DevicePort[] | number;
532
572
  }
533
573
  /**
534
574
  * Fibre flow cable node data.
@@ -668,9 +708,148 @@ type NetworkEdge<TData extends NetworkEdgeData = NetworkEdgeData> = {
668
708
  targetHandle?: string;
669
709
  /** Type of network connection */
670
710
  type?: NetworkEdgeType;
711
+ /** Whether the edge should be hidden from rendering */
712
+ hidden?: boolean;
671
713
  /** Edge data and configuration */
672
714
  data?: TData;
673
715
  };
716
+ /**
717
+ * High-level document format independent from React Flow handle ids.
718
+ * @public
719
+ */
720
+ interface DiagramDocument {
721
+ nodes: DiagramNode[];
722
+ edges: DiagramEdge[];
723
+ ui?: DiagramUiState;
724
+ metadata?: Record<string, unknown>;
725
+ }
726
+ /**
727
+ * Node kinds used by the document model.
728
+ * @public
729
+ */
730
+ type DiagramNodeKind = 'rack' | 'equipment' | 'pdu' | 'fiber' | 'tube' | 'cable' | 'multi-tube-cable' | 'splice' | 'coupler' | 'splitter' | 'container' | 'fibre-flow-cable' | 'fibre-flow-closure';
731
+ /**
732
+ * Placement data for document nodes.
733
+ * @public
734
+ */
735
+ interface DiagramNodePlacement {
736
+ position: {
737
+ x: number;
738
+ y: number;
739
+ };
740
+ parentId?: string;
741
+ extent?: 'parent';
742
+ rack?: {
743
+ uPosition?: number;
744
+ mountMode?: RackMountMode;
745
+ rackLayout?: RackLayoutMode;
746
+ rackWidthPx?: number;
747
+ rackFaceGapPx?: number;
748
+ };
749
+ container?: {
750
+ slotPosition?: number;
751
+ holderPosition?: number;
752
+ };
753
+ }
754
+ /**
755
+ * Endpoint definition for document nodes.
756
+ * @public
757
+ */
758
+ interface DiagramEndpoint {
759
+ id: string;
760
+ label: string;
761
+ face?: DeviceView | 'internal';
762
+ group?: string;
763
+ role?: 'network' | 'power' | 'fiber' | 'tube' | 'splice' | 'coupler' | 'splitter' | 'cable' | 'generic';
764
+ medium?: 'ethernet' | 'fiber' | 'power' | 'tube' | 'generic';
765
+ type?: PortType;
766
+ cableType?: PortCableType;
767
+ connector?: string;
768
+ compatibility?: string[];
769
+ layout?: PortPosition;
770
+ }
771
+ /**
772
+ * Render-focused node data for the document model.
773
+ * @public
774
+ */
775
+ interface DiagramNodeRender {
776
+ status?: DeviceStatus | string;
777
+ view?: DeviceView;
778
+ face?: DeviceView;
779
+ width?: Width | number;
780
+ widthPx?: number;
781
+ height?: number;
782
+ header?: string;
783
+ color?: string;
784
+ colorMode?: 'light' | 'dark' | 'system';
785
+ rackWidthPx?: number;
786
+ rackFaceGapPx?: number;
787
+ images?: DeviceImageOptions;
788
+ dimensions?: Record<string, number | undefined>;
789
+ showUMarkers?: boolean;
790
+ customHeaderText?: string;
791
+ powerSide?: PowerSide;
792
+ rotation?: number;
793
+ }
794
+ /**
795
+ * External reference attached to a document node.
796
+ * @public
797
+ */
798
+ interface DiagramExternalRef {
799
+ source: 'netbox' | 'inventory' | string;
800
+ id: string | number;
801
+ type?: string;
802
+ metadata?: Record<string, unknown>;
803
+ }
804
+ /**
805
+ * Document node.
806
+ * @public
807
+ */
808
+ interface DiagramNode {
809
+ id: string;
810
+ kind: DiagramNodeKind;
811
+ subtype?: string;
812
+ label: string;
813
+ placement: DiagramNodePlacement;
814
+ endpoints?: DiagramEndpoint[];
815
+ render?: DiagramNodeRender;
816
+ config?: Record<string, unknown>;
817
+ externalRefs?: DiagramExternalRef[];
818
+ metadata?: Record<string, unknown>;
819
+ }
820
+ /**
821
+ * Endpoint reference used by document edges.
822
+ * @public
823
+ */
824
+ interface DiagramEdgeEndpoint {
825
+ nodeId: string;
826
+ endpointId?: string | null;
827
+ }
828
+ /**
829
+ * Document edge.
830
+ * @public
831
+ */
832
+ interface DiagramEdge {
833
+ id: string;
834
+ source: DiagramEdgeEndpoint;
835
+ target: DiagramEdgeEndpoint;
836
+ type?: NetworkEdgeType;
837
+ hidden?: boolean;
838
+ data?: NetworkEdgeData;
839
+ metadata?: Record<string, unknown>;
840
+ }
841
+ /**
842
+ * Non-topology UI state stored alongside a document.
843
+ * @public
844
+ */
845
+ interface DiagramUiState {
846
+ nodeState?: Record<string, {
847
+ view?: DeviceView;
848
+ face?: DeviceView;
849
+ handleSideMap?: Record<string, DeviceView>;
850
+ }>;
851
+ metadata?: Record<string, unknown>;
852
+ }
674
853
  /**
675
854
  * Rack device types
676
855
  * @public
@@ -726,12 +905,14 @@ interface RackDevice extends DeviceImageOptions {
726
905
  mountMode?: RackMountMode;
727
906
  /** Device role in network */
728
907
  role?: string;
908
+ /** Recommended simple port input. Use `portGroups` / `rearPortGroups` for explicit grouped control. */
909
+ ports?: DevicePort[];
729
910
  /** Current status of the device */
730
911
  status?: DeviceStatus | string;
731
- /** Front port configurations */
732
- ports?: PortBlock[];
733
- /** Rear port configurations */
734
- rearPorts?: PortBlock[];
912
+ /** Advanced grouped front port input */
913
+ portGroups?: PortBlock[];
914
+ /** Advanced grouped rear port input */
915
+ rearPortGroups?: PortBlock[];
735
916
  /** Power port side for rear view */
736
917
  powerSide?: PowerSide;
737
918
  /** Connected device IDs */
@@ -938,6 +1119,12 @@ interface NetworkDiagramStoreApi {
938
1119
  * @public
939
1120
  */
940
1121
  interface NetworkDiagramProps {
1122
+ /** Controlled document model. When provided, it takes precedence over nodes/edges props. */
1123
+ document?: DiagramDocument;
1124
+ /** Initial document for uncontrolled document mode. */
1125
+ initialDocument?: DiagramDocument;
1126
+ /** Callback when the document changes in document mode. */
1127
+ onDocumentChange?: (document: DiagramDocument) => void;
941
1128
  /** Controlled nodes array */
942
1129
  nodes?: NetworkNode[];
943
1130
  /** Controlled edges array */
@@ -1016,30 +1203,6 @@ interface NetworkDiagramProps {
1016
1203
  fitViewOptions?: FitViewOptions;
1017
1204
  }
1018
1205
 
1019
- /**
1020
- * Main component for rendering interactive network diagrams.
1021
- *
1022
- * The NetworkDiagram component provides a complete solution for visualizing
1023
- * network infrastructure including racks, devices, fiber connections, and power distribution.
1024
- *
1025
- * @example
1026
- * ```tsx
1027
- * import { NetworkDiagram, NetworkNode, NetworkEdge } from '@mp70/react-networks';
1028
- *
1029
- * const nodes: NetworkNode[] = [
1030
- * {
1031
- * id: 'rack-1',
1032
- * type: 'rack',
1033
- * position: { x: 100, y: 100 },
1034
- * data: { label: 'Main Rack', uHeight: 42 }
1035
- * }
1036
- * ];
1037
- *
1038
- * <NetworkDiagram nodes={nodes} edges={[]} />
1039
- * ```
1040
- *
1041
- * @public
1042
- */
1043
1206
  declare const NetworkDiagram: React$1.FC<NetworkDiagramProps>;
1044
1207
 
1045
1208
  interface DiagramErrorBoundaryProps {
@@ -1159,12 +1322,9 @@ declare const PowerEdge: React$1.FC<EdgeProps>;
1159
1322
  * label: 'Web Server',
1160
1323
  * deviceType: 'server',
1161
1324
  * uPosition: 1,
1162
- * ports: [{
1163
- * name: 'Network Ports',
1164
- * ports: [
1165
- * { label: 'eth0', connected: true, type: 'ethernet' }
1166
- * ]
1167
- * }]
1325
+ * ports: [
1326
+ * { id: 'eth0', label: 'eth0', group: 'Network Ports', connected: true, type: 'ethernet' }
1327
+ * ]
1168
1328
  * }
1169
1329
  * }
1170
1330
  * ```
@@ -1928,6 +2088,43 @@ declare function addSpliceToTray(trays: SpliceTrayConfig[], trayId: string, spli
1928
2088
  */
1929
2089
  declare function removeSpliceFromTray(trays: SpliceTrayConfig[], trayId: string, spliceId: string): SpliceTrayConfig[];
1930
2090
 
2091
+ type PortFaces = {
2092
+ ports: PortBlock[];
2093
+ rearPorts: PortBlock[];
2094
+ };
2095
+ type PortFaceData = Pick<NetworkNode['data'], 'ports' | 'portGroups' | 'rearPortGroups'>;
2096
+ declare function buildPortBlocksFromPorts(devicePorts: DevicePort[]): PortFaces;
2097
+ declare function getResolvedPortBlocks(data: PortFaceData | undefined): PortFaces;
2098
+ declare function getFrontPortBlocks(data: PortFaceData | undefined): PortBlock[];
2099
+ declare function getRearPortBlocks(data: PortFaceData | undefined): PortBlock[];
2100
+ declare function hasFrontPorts(data: PortFaceData | undefined): boolean;
2101
+ declare function hasRearPorts(data: PortFaceData | undefined): boolean;
2102
+ declare function getDefaultDeviceView(data: PortFaceData | undefined): DeviceView;
2103
+
2104
+ interface NetworkGraphSnapshot {
2105
+ nodes: NetworkNode[];
2106
+ edges: NetworkEdge[];
2107
+ }
2108
+ declare function networkGraphToDiagramDocument(snapshot: NetworkGraphSnapshot, previousDocument?: DiagramDocument): DiagramDocument;
2109
+ declare function diagramDocumentToNetworkGraph(document: DiagramDocument): NetworkGraphSnapshot;
2110
+
2111
+ type EndpointConnectionKind = 'power' | 'network' | 'unknown';
2112
+ interface EndpointMetadata {
2113
+ normalizedHandle: string;
2114
+ face?: DeviceView;
2115
+ kind: EndpointConnectionKind;
2116
+ compatibility: string[];
2117
+ }
2118
+ declare function resolveEndpointCompatibilityKeys(input: {
2119
+ compatibility?: string[];
2120
+ kind?: EndpointConnectionKind;
2121
+ type?: string;
2122
+ cableType?: string;
2123
+ connectorType?: string;
2124
+ handleId?: string;
2125
+ }): string[];
2126
+ declare function getEndpointMetadata(node?: NetworkNode, handle?: string | null): EndpointMetadata;
2127
+
1931
2128
  /**
1932
2129
  * Power connector utilities for consistent styling across devices and PDUs
1933
2130
  */
@@ -2259,6 +2456,10 @@ interface TemplateParseResult {
2259
2456
  *
2260
2457
  * **Port position semantics**: Positions use `xPercent` / `yPercent` (0-100)
2261
2458
  * to express where the port CENTER should be relative to the device face.
2459
+ * Raw imports may also use semantic edge strings like `top-left` or
2460
+ * `right-middle`; those are normalized into side/offset placements during
2461
+ * import. `xPercent` / `yPercent` remain the manual override path for
2462
+ * detailed templates.
2262
2463
  * The renderer (`DeviceNode`) converts these to pixel coordinates and clamps
2263
2464
  * them so the port stays fully inside the device bounds. Clamp bounds are
2264
2465
  * computed from the **actual** rendered port dimensions (which vary by port
@@ -2280,7 +2481,7 @@ interface RawTemplatePort {
2280
2481
  type?: string;
2281
2482
  connectorType?: string;
2282
2483
  connector_type?: string;
2283
- position?: {
2484
+ position?: string | {
2284
2485
  side?: string;
2285
2486
  offsetPercent?: number;
2286
2487
  offset_percent?: number;
@@ -2399,4 +2600,4 @@ declare function useNetworkDiagram(store: NetworkDiagramStore): NetworkDiagramSt
2399
2600
  declare function useNodes(store: NetworkDiagramStore): NetworkNode[];
2400
2601
  declare function useEdges(store: NetworkDiagramStore): NetworkEdge[];
2401
2602
 
2402
- export { COUPLERS_PER_PANEL, COUPLER_SLOT_HEIGHT_PX, COUPLER_WIDTH_PX, CableNode, ClosureNode, CouplerNode, type DeviceImageFit, type DeviceImageOptions, type DeviceImageRepeat, DeviceNode, DevicePlacementError, type DevicePlacementValidation, type DeviceTemplateV2, DiagramErrorBoundary, type DiagramErrorBoundaryProps, FIBER_COLORS_12, type FiberCable, type FiberColorId, FiberEdge, FiberNode, FibreCableWithTubesExpanded, type FibreFlowCable, type FibreFlowCableKind, type FibreFlowCableNodeData, type FibreFlowCircuit, type FibreFlowClosure, type FibreFlowClosureNodeData, type FibreFlowEndpoint, FibreFlowMap, type FibreFlowMapProps, type FibreFlowSplice, FibreSplitNode, HANDLE_EXTENSION_PX, type MountClass, MultiTubeCableNode, NetworkDiagram, type NetworkDiagramFitViewOptions, type NetworkDiagramFlowMethods, type NetworkDiagramFlowObject, type NetworkDiagramOptions, type NetworkDiagramProps, type NetworkDiagramStoreApi, type NetworkDiagramToImageOptions, type NetworkEdge, type NetworkEdgeData, type NetworkNode, type NetworkNodeData, type NetworkNodeDataBase, type NetworkNodeDataByType, PANEL_BORDER_WIDTH, PANEL_HEADER_HEIGHT, PANEL_HEIGHT_PX, PANEL_PADDING, PANEL_TUBE_TRAY_SPACING, PANEL_WIDTH_PX, POWER_CONNECTORS, PatchPanelNode, type Port, type PortBlock, type PortCableType, type PortType, PowerEdge, RACK_HEADER_HEIGHT, RACK_WIDTH_PX, type RackConfig, type RackDevice, type RackLayoutMode, type RackMountMode, RackNode, type RackSchemaOptions, type RawNetboxTemplate, type RawTemplatePort, type RawTemplatePortBlock, type ReplaceEdgeOptions, type SpliceConfig, SpliceNode, SplicePlacementError, type SplicePlacementValidation, type SpliceSchemaOptions, type SpliceTrayConfig, SpliceTrayNode, TEMPLATE_SCHEMA_VERSION, type TemplateFaceData, type TemplateFrontRearPortLink, type TemplateParseResult, type TemplatePort, type TemplatePortBlock, type TemplateSource, type TemplateValidationIssue, type TemplateValidationReport, type TemplateWidth, TubeNode, U_HEIGHT_PX, type ValidateConnectionParams, type ValidationResult, VerticalPDU, Width, addDeviceToRack, addSpliceToTray, baseColorFor, buildNodesFromRackConfig, buildNodesFromSpliceConfig, calculateClosureDimensions, calculateCouplerRightX, calculateCouplerSlotSpacing, calculateCouplerSpacing, calculateDevicePositionFromU, calculatePanelDimensions, calculateSlotPositionFromY, calculateSplicePositionFromNumber, calculateUPositions, calculateYFromSlotPosition, countTemplateFacePorts, createNetworkDiagramStore, createRackConfigFromNodes, createSpliceConfigFromNodes, fiberSolidOrStriped, findNearestRack, findNextAvailableHolderPosition, findNextAvailableUPosition, generateLayout, getConnectorConfig, getCouplerDimensions, getDeviceConnectorType, getFiberColor, getInnerContentWidth, getPDUPortType, getPanelHeight, getPanelWidth, getPowerPortStyle, getRackBounds, getStatusColor, getTrayDimensions, getTubeDimensions, inventoryCableToNetworkEdge, inventoryDeviceToNetworkNode, inventoryRackToNetworkNode, inventoryToNetworkDiagram, isHighPowerConnector, isPointInRack, isStriped, isUPositionAvailable, netboxCableToNetworkEdge, netboxDeviceToNetworkNode, netboxRackToNetworkNode, netboxToNetworkDiagram, parseNetboxTemplate, removeDeviceFromRack, removeSpliceFromTray, replaceEdge, snapToCouplerPosition, snapToSplicePosition, snapToUPosition, updateDeviceUPosition$1 as updateDeviceUPosition, updateDeviceUPosition as updateDeviceUPositionInSchema, updateSpliceHolderPosition, useEdges as useDiagramEdges, useNodes as useDiagramNodes, useNetworkDiagram, validateAndSnapDevice, validateRackDevicePlacements, validateRenderFidelity, validateSplicePlacements };
2603
+ export { COUPLERS_PER_PANEL, COUPLER_SLOT_HEIGHT_PX, COUPLER_WIDTH_PX, CableNode, ClosureNode, CouplerNode, type DeviceImageFit, type DeviceImageOptions, type DeviceImageRepeat, DeviceNode, DevicePlacementError, type DevicePlacementValidation, type DevicePort, type DeviceStatus, type DeviceTemplateV2, type DeviceView, type DiagramDocument, type DiagramEdge, type DiagramEdgeEndpoint, type DiagramEndpoint, DiagramErrorBoundary, type DiagramErrorBoundaryProps, type DiagramExternalRef, type DiagramNode, type DiagramNodeKind, type DiagramNodePlacement, type DiagramNodeRender, type DiagramUiState, FIBER_COLORS_12, type FiberCable, type FiberCableType, type FiberColorId, FiberEdge, FiberNode, FibreCableWithTubesExpanded, type FibreFlowCable, type FibreFlowCableKind, type FibreFlowCableNodeData, type FibreFlowCircuit, type FibreFlowClosure, type FibreFlowClosureNodeData, type FibreFlowEndpoint, FibreFlowMap, type FibreFlowMapProps, type FibreFlowSplice, FibreSplitNode, HANDLE_EXTENSION_PX, type MountClass, MultiTubeCableNode, NetworkDiagram, type NetworkDiagramFitViewOptions, type NetworkDiagramFlowMethods, type NetworkDiagramFlowObject, type NetworkDiagramOptions, type NetworkDiagramProps, type NetworkDiagramStoreApi, type NetworkDiagramToImageOptions, type NetworkEdge, type NetworkEdgeData, type NetworkGraphSnapshot, type NetworkNode, type NetworkNodeData, type NetworkNodeDataBase, type NetworkNodeDataByType, PANEL_BORDER_WIDTH, PANEL_HEADER_HEIGHT, PANEL_HEIGHT_PX, PANEL_PADDING, PANEL_TUBE_TRAY_SPACING, PANEL_WIDTH_PX, POWER_CONNECTORS, PatchPanelNode, type Port, type PortBlock, type PortCableType, type PortPosition, type PortType, PowerEdge, type PowerSide, RACK_HEADER_HEIGHT, RACK_WIDTH_PX, type RackConfig, type RackDevice, type RackDeviceType, type RackLayoutMode, type RackMountMode, RackNode, type RackSchemaOptions, type RawNetboxTemplate, type RawTemplatePort, type RawTemplatePortBlock, type ReplaceEdgeOptions, type SpliceConfig, SpliceNode, SplicePlacementError, type SplicePlacementValidation, type SpliceSchemaOptions, type SpliceTrayConfig, SpliceTrayNode, TEMPLATE_SCHEMA_VERSION, type TemplateFaceData, type TemplateFrontRearPortLink, type TemplateParseResult, type TemplatePort, type TemplatePortBlock, type TemplateSource, type TemplateValidationIssue, type TemplateValidationReport, type TemplateWidth, TubeNode, type UNumberingDirection, U_HEIGHT_PX, type ValidateConnectionParams, type ValidationResult, VerticalPDU, Width, addDeviceToRack, addSpliceToTray, baseColorFor, buildNodesFromRackConfig, buildNodesFromSpliceConfig, buildPortBlocksFromPorts, calculateClosureDimensions, calculateCouplerRightX, calculateCouplerSlotSpacing, calculateCouplerSpacing, calculateDevicePositionFromU, calculatePanelDimensions, calculateSlotPositionFromY, calculateSplicePositionFromNumber, calculateUPositions, calculateYFromSlotPosition, countTemplateFacePorts, createNetworkDiagramStore, createRackConfigFromNodes, createSpliceConfigFromNodes, diagramDocumentToNetworkGraph, fiberSolidOrStriped, findNearestRack, findNextAvailableHolderPosition, findNextAvailableUPosition, generateLayout, getConnectorConfig, getCouplerDimensions, getDefaultDeviceView, getDeviceConnectorType, getEndpointMetadata, getFiberColor, getFrontPortBlocks, getInnerContentWidth, getPDUPortType, getPanelHeight, getPanelWidth, getPowerPortStyle, getRackBounds, getRearPortBlocks, getResolvedPortBlocks, getStatusColor, getTrayDimensions, getTubeDimensions, hasFrontPorts, hasRearPorts, inventoryCableToNetworkEdge, inventoryDeviceToNetworkNode, inventoryRackToNetworkNode, inventoryToNetworkDiagram, isHighPowerConnector, isPointInRack, isStriped, isUPositionAvailable, netboxCableToNetworkEdge, netboxDeviceToNetworkNode, netboxRackToNetworkNode, netboxToNetworkDiagram, networkGraphToDiagramDocument, parseNetboxTemplate, removeDeviceFromRack, removeSpliceFromTray, replaceEdge, resolveEndpointCompatibilityKeys, snapToCouplerPosition, snapToSplicePosition, snapToUPosition, updateDeviceUPosition$1 as updateDeviceUPosition, updateDeviceUPosition as updateDeviceUPositionInSchema, updateSpliceHolderPosition, useEdges as useDiagramEdges, useNodes as useDiagramNodes, useNetworkDiagram, validateAndSnapDevice, validateRackDevicePlacements, validateRenderFidelity, validateSplicePlacements };