@mp70/react-networks 0.1.4-alpha → 0.1.5-alpha

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
@@ -312,14 +312,20 @@ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) f
312
312
 
313
313
  ## 📄 License
314
314
 
315
- Dual-licensed under AGPL-3.0 or Commercial. See [LICENSE](LICENSE) for details.
315
+ Dual-licensed under:
316
+
317
+ - **AGPL-3.0** (Free) - For open source, personal, and educational use. Copyleft applies. [Read License](https://www.gnu.org/licenses/agpl-3.0.html)
318
+ - **Commercial** (Paid) - For proprietary/commercial use without copyleft obligations. [Contact us](mailto:info@serversearcher.com?subject=Commercial License Inquiry for @mp70/react-networks)
319
+
320
+ See [LICENSE](../../LICENSE) for details. By installing, you agree to use under either license. Contact us for commercial use.
316
321
 
317
322
  ## 🆘 Support
318
323
 
319
- - 📖 [Documentation](https://react-networks.dev)
320
- - 🐛 [Issue Tracker](https://github.com/your-org/react-networks/issues)
321
- - 💬 [Discussions](https://github.com/your-org/react-networks/discussions)
322
- - 📧 [Email Support](mailto:support@react-networks.dev)
324
+ - 📖 [Documentation](../../README.md)
325
+ - 📚 [API Documentation](docs/API.md)
326
+ - 🐛 [Issue Tracker](https://github.com/MP70/react-networks/issues)
327
+ - 💬 [Discussions](https://github.com/MP70/react-networks/discussions)
328
+ - 📧 [Email Support](mailto:info@serversearcher.com)
323
329
 
324
330
  ## 🙏 Acknowledgments
325
331
 
@@ -329,4 +335,4 @@ Dual-licensed under AGPL-3.0 or Commercial. See [LICENSE](LICENSE) for details.
329
335
 
330
336
  ---
331
337
 
332
- Made with ❤️ by the React Networks team
338
+ Made with ❤️ by [Matt Pickering](https://github.com/MP70)
package/dist/index.css CHANGED
@@ -32,7 +32,7 @@
32
32
  }
33
33
 
34
34
  .react-flow-light {
35
- --react-flow-background: #ffffff;
35
+ --react-flow-background: #f5f5f5;
36
36
  --react-flow-text: #1a1a1a;
37
37
  --react-flow-border: #e9ecef;
38
38
  --react-flow-controls-bg: #f8f9fa;
@@ -50,6 +50,15 @@
50
50
  background-color: var(--react-flow-background);
51
51
  }
52
52
 
53
+ /* Ensure canvas background is always off-white in light mode */
54
+ .react-flow-light .react-flow__viewport {
55
+ background-color: #f5f5f5 !important;
56
+ }
57
+
58
+ .react-flow-light .react-flow {
59
+ background-color: #f5f5f5;
60
+ }
61
+
53
62
  .react-flow-dark .react-flow__controls {
54
63
  background-color: var(--react-flow-controls-bg);
55
64
  border-color: var(--react-flow-controls-border);
@@ -90,6 +99,135 @@
90
99
  background-color: #e9ecef;
91
100
  }
92
101
 
102
+ /* Override React Flow default handle styles for tube fiber handles */
103
+ /* Use CSS variables set by inline styles to override React Flow defaults */
104
+ .react-flow__handle.tube-fiber-handle {
105
+ border: none !important;
106
+ transform: none !important;
107
+ background-clip: padding-box !important;
108
+ opacity: 1 !important;
109
+ /* Remove React Flow's default background completely */
110
+ background: var(--fiber-bg-image, var(--fiber-bg)) !important;
111
+ background-color: var(--fiber-bg) !important;
112
+ /* Ensure no default React Flow styles interfere */
113
+ box-shadow: none !important;
114
+ }
115
+
116
+ .react-flow__handle-right.tube-fiber-handle {
117
+ right: auto !important;
118
+ }
119
+
120
+ /* More specific selector to ensure override - target all possible React Flow handle states */
121
+ .react-flow__node .react-flow__handle.tube-fiber-handle,
122
+ .react-flow__node .react-flow__handle.tube-fiber-handle[data-handlepos],
123
+ .react-flow__handle.tube-fiber-handle[data-handlepos],
124
+ .react-flow__handle.tube-fiber-handle[data-fiber-idx] {
125
+ background: var(--fiber-bg-image, var(--fiber-bg)) !important;
126
+ background-color: var(--fiber-bg) !important;
127
+ background-image: var(--fiber-bg-image, none) !important;
128
+ }
129
+
130
+ /* Connection animation - pulse effect when edge is first created */
131
+ @keyframes edgeConnectPulse {
132
+ 0% {
133
+ opacity: 0;
134
+ stroke-width: 1;
135
+ }
136
+ 50% {
137
+ opacity: 1;
138
+ stroke-width: 4;
139
+ }
140
+ 100% {
141
+ opacity: 1;
142
+ stroke-width: 2;
143
+ }
144
+ }
145
+
146
+ .react-flow__edge.edge-connecting path,
147
+ .react-flow__edge.edge-connecting .react-flow__edge-path {
148
+ animation: edgeConnectPulse 1s ease-out;
149
+ }
150
+
151
+ /* Splice glow animation - fade in when both handles are connected */
152
+ @keyframes spliceGlowFadeIn {
153
+ 0% {
154
+ opacity: 0;
155
+ transform: translateX(-50%) scaleY(0.3);
156
+ }
157
+ 100% {
158
+ opacity: 1;
159
+ transform: translateX(-50%) scaleY(1);
160
+ }
161
+ }
162
+
163
+ .splice-glow {
164
+ animation: spliceGlowFadeIn 0.5s ease-out;
165
+ transform-origin: center;
166
+ }
167
+
168
+ /* Splice text animation - fade in when both handles are connected */
169
+ @keyframes spliceTextFadeIn {
170
+ 0% {
171
+ opacity: 0;
172
+ transform: scale(0.8);
173
+ }
174
+ 100% {
175
+ opacity: 1;
176
+ transform: scale(1);
177
+ }
178
+ }
179
+
180
+ /* Splice text overlay - blur and opacity for text readability */
181
+ .splice-text-overlay {
182
+ backdrop-filter: blur(2px);
183
+ -webkit-backdrop-filter: blur(2px);
184
+ }
185
+
186
+ /* Dark mode overlay */
187
+ .react-flow-dark .splice-text-overlay {
188
+ background: rgba(0, 0, 0, 0.6);
189
+ }
190
+
191
+ /* Light mode overlay */
192
+ .react-flow-light .splice-text-overlay {
193
+ background: rgba(255, 255, 255, 0.7);
194
+ }
195
+
196
+ /* Splice text label - theme-aware colors */
197
+ .splice-text-label {
198
+ color: #fff;
199
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8), 0 0 2px rgba(0, 0, 0, 0.6);
200
+ }
201
+
202
+ .splice-text-label.animate-in {
203
+ animation: spliceTextFadeIn 0.5s ease-out;
204
+ transform-origin: center;
205
+ }
206
+
207
+ /* Light mode text */
208
+ .react-flow-light .splice-text-label {
209
+ color: #1a1a1a;
210
+ text-shadow: 0 1px 2px rgba(255, 255, 255, 0.9), 0 0 4px rgba(255, 255, 255, 0.7);
211
+ }
212
+
213
+ /* Remove all borders from splice nodes */
214
+ .react-flow__node[data-type="splice"],
215
+ .react-flow__node[data-type="splice"] > div {
216
+ border: none !important;
217
+ outline: none !important;
218
+ }
219
+
220
+ .splice-node {
221
+ border: none !important;
222
+ outline: none !important;
223
+ }
224
+
225
+ /* Only remove box-shadow when not selected */
226
+ .react-flow__node[data-type="splice"]:not(.selected) > div,
227
+ .splice-node:not(.selected) {
228
+ box-shadow: none !important;
229
+ }
230
+
93
231
  /* React Networks Attribution */
94
232
  .react-networks-attribution {
95
233
  padding: 0;
package/dist/index.d.mts CHANGED
@@ -39,7 +39,7 @@ interface PortBlock {
39
39
  * Network node types supported by the diagram
40
40
  * @public
41
41
  */
42
- type NetworkNodeType = 'rack' | 'switch' | 'router' | 'server' | 'fiber' | 'patch-panel' | 'device' | 'vertical-pdu';
42
+ type NetworkNodeType = 'rack' | 'switch' | 'router' | 'server' | 'fiber' | 'patch-panel' | 'device' | 'vertical-pdu' | 'splice' | 'splice-tray' | 'tube' | 'cable';
43
43
  /**
44
44
  * Device status states
45
45
  * @public
@@ -118,6 +118,10 @@ interface NetworkNode {
118
118
  uNumberingDirection?: UNumberingDirection;
119
119
  /** Custom header text for rack nodes */
120
120
  customHeaderText?: string;
121
+ /** Holder position within tray (1-12) for splice nodes */
122
+ holderPosition?: number;
123
+ /** Loss value in dB for splice nodes */
124
+ lossDb?: number;
121
125
  /** Additional custom properties */
122
126
  [key: string]: any;
123
127
  };
@@ -126,7 +130,7 @@ interface NetworkNode {
126
130
  * Network edge types
127
131
  * @public
128
132
  */
129
- type NetworkEdgeType = 'fiber' | 'ethernet' | 'power';
133
+ type NetworkEdgeType = 'fiber' | 'ethernet' | 'power' | 'smoothstep' | 'step';
130
134
  /**
131
135
  * Network edge data structure
132
136
  * @public
@@ -242,6 +246,37 @@ interface FiberCable {
242
246
  to: string;
243
247
  };
244
248
  }
249
+ /**
250
+ * Splice configuration structure
251
+ * @public
252
+ */
253
+ interface SpliceConfig {
254
+ /** Unique splice identifier */
255
+ id: string;
256
+ /** Splice display name */
257
+ name: string;
258
+ /** Holder position within tray (1-12) */
259
+ holder: number;
260
+ /** Loss value in dB */
261
+ lossDb?: number;
262
+ }
263
+ /**
264
+ * Splice tray configuration structure
265
+ * @public
266
+ */
267
+ interface SpliceTrayConfig {
268
+ /** Unique tray identifier */
269
+ id: string;
270
+ /** Tray display name */
271
+ name: string;
272
+ /** Tray position coordinates */
273
+ position: {
274
+ x: number;
275
+ y: number;
276
+ };
277
+ /** Splices in this tray */
278
+ splices: SpliceConfig[];
279
+ }
245
280
  /**
246
281
  * Network diagram component props
247
282
  * @public
@@ -297,6 +332,10 @@ interface NetworkDiagramProps {
297
332
  downloadButtonText?: string;
298
333
  /** Download button styling options */
299
334
  downloadButtonStyle?: React.CSSProperties;
335
+ /** Allow reconnecting existing edges from either end */
336
+ allowReconnectExisting?: boolean;
337
+ /** Use smoothstep edges for tube connections (both tube-to-cable and cable-to-tube). Default is fiber. */
338
+ useSmoothstepEdgesForTubes?: boolean;
300
339
  }
301
340
 
302
341
  declare const NetworkDiagram: React$1.FC<NetworkDiagramProps>;
@@ -310,7 +349,9 @@ declare const RackNode: React$1.FC<RackNodeProps>;
310
349
 
311
350
  declare const FiberNode: React$1.FC<NodeProps<NetworkNode['data']>>;
312
351
 
313
- declare const FiberEdge: React$1.FC<EdgeProps<NetworkEdge['data']>>;
352
+ declare const FiberEdge: React$1.FC<EdgeProps<NetworkEdge['data']> & {
353
+ className?: string;
354
+ }>;
314
355
 
315
356
  declare const PowerEdge: React$1.FC<EdgeProps>;
316
357
 
@@ -318,6 +359,30 @@ declare const DeviceNode: React$1.FC<NodeProps<NetworkNode['data']>>;
318
359
 
319
360
  declare const VerticalPDU: React$1.FC<NodeProps<NetworkNode['data']>>;
320
361
 
362
+ declare const SpliceNode: React$1.FC<NodeProps<NetworkNode['data']>>;
363
+
364
+ declare const SpliceTrayNode: React$1.FC<NodeProps<NetworkNode['data']>>;
365
+
366
+ /**
367
+ * TubeNode
368
+ * - Rectangular node representing a buffer tube (horizontal orientation, rotated 180 degrees)
369
+ * - Renders 12 or 24 fiber handles on the top side with TIA-598 colors
370
+ * - Tube border tinted by tube index color (1..12 cycling)
371
+ * - startOn parameter controls handle positioning:
372
+ * - 'left-center' (default): handles centered on top, tube handle on bottom
373
+ * - 'left': handles on left side, tube handle on left
374
+ * - 'right': handles on right side with reversed colors, tube handle on right
375
+ */
376
+ declare const TubeNode: React$1.FC<NodeProps<NetworkNode['data']>>;
377
+
378
+ /**
379
+ * CableNode
380
+ * - Visually mirrors TubeNode but represents an entire cable
381
+ * - Each colored handle corresponds to a tube within the cable
382
+ * - Displays cable summary text (fiber count + cable identifier)
383
+ */
384
+ declare const CableNode: React$1.FC<NodeProps<NetworkNode['data']>>;
385
+
321
386
  interface InventoryRackDTO {
322
387
  id: string | number;
323
388
  name: string;
@@ -461,6 +526,29 @@ declare const updateDeviceUPosition$1: (device: NetworkNode, rack: NetworkNode,
461
526
  */
462
527
  declare const validateAndSnapDevice: (device: NetworkNode, rack: NetworkNode) => NetworkNode;
463
528
 
529
+ /**
530
+ * Snap a splice to the nearest holder position within a tray
531
+ */
532
+ declare const snapToSplicePosition: (splice: NetworkNode, tray: NetworkNode, newPosition: {
533
+ x: number;
534
+ y: number;
535
+ }, allNodes: NetworkNode[]) => {
536
+ x: number;
537
+ y: number;
538
+ holderPosition?: number;
539
+ };
540
+ /**
541
+ * Find the next available holder position
542
+ */
543
+ declare const findNextAvailableHolderPosition: (tray: NetworkNode, splice: NetworkNode, allNodes: NetworkNode[], startFrom?: number) => number;
544
+ /**
545
+ * Calculate splice position based on holder number within a tray
546
+ */
547
+ declare const calculateSplicePositionFromNumber: (tray: NetworkNode, holderPosition: number) => {
548
+ x: number;
549
+ y: number;
550
+ };
551
+
464
552
  /**
465
553
  * Configuration options for building nodes from rack schema
466
554
  */
@@ -526,6 +614,66 @@ declare function addDeviceToRack(racks: RackConfig[], rackId: string, device: Ra
526
614
  */
527
615
  declare function removeDeviceFromRack(racks: RackConfig[], rackId: string, deviceId: string): RackConfig[];
528
616
 
617
+ /**
618
+ * Configuration options for building nodes from splice schema
619
+ */
620
+ interface SpliceSchemaOptions {
621
+ /** How to handle splice placement conflicts */
622
+ conflictPolicy?: 'strict' | 'nearest';
623
+ /** Whether to validate all placements before creating nodes */
624
+ validatePlacements?: boolean;
625
+ }
626
+ /**
627
+ * Error thrown when splice placement conflicts occur in strict mode
628
+ */
629
+ declare class SplicePlacementError extends Error {
630
+ trayId: string;
631
+ spliceId: string;
632
+ holder: number;
633
+ conflictWith?: string | undefined;
634
+ constructor(trayId: string, spliceId: string, holder: number, conflictWith?: string | undefined);
635
+ }
636
+ /**
637
+ * Validation result for splice placement
638
+ */
639
+ interface SplicePlacementValidation {
640
+ isValid: boolean;
641
+ conflicts: Array<{
642
+ spliceId: string;
643
+ holder: number;
644
+ conflictWith: string;
645
+ }>;
646
+ outOfBounds: Array<{
647
+ spliceId: string;
648
+ holder: number;
649
+ max: number;
650
+ }>;
651
+ }
652
+ /**
653
+ * Validate splice placements within a tray schema
654
+ */
655
+ declare function validateSplicePlacements(tray: SpliceTrayConfig, _options?: SpliceSchemaOptions): SplicePlacementValidation;
656
+ /**
657
+ * Build NetworkNode array from SpliceTrayConfig array with comprehensive validation
658
+ */
659
+ declare function buildNodesFromSpliceConfig(trays: SpliceTrayConfig[], options?: SpliceSchemaOptions): NetworkNode[];
660
+ /**
661
+ * Create a SpliceTrayConfig from existing NetworkNode data (reverse operation)
662
+ */
663
+ declare function createSpliceConfigFromNodes(nodes: NetworkNode[]): SpliceTrayConfig[];
664
+ /**
665
+ * Update splice holder position in a splice schema
666
+ */
667
+ declare function updateSpliceHolderPosition(trays: SpliceTrayConfig[], trayId: string, spliceId: string, newHolder: number): SpliceTrayConfig[];
668
+ /**
669
+ * Add a splice to a tray schema
670
+ */
671
+ declare function addSpliceToTray(trays: SpliceTrayConfig[], trayId: string, splice: SpliceConfig, options?: SpliceSchemaOptions): SpliceTrayConfig[];
672
+ /**
673
+ * Remove a splice from a tray schema
674
+ */
675
+ declare function removeSpliceFromTray(trays: SpliceTrayConfig[], trayId: string, spliceId: string): SpliceTrayConfig[];
676
+
529
677
  /**
530
678
  * Power connector utilities for consistent styling across devices and PDUs
531
679
  */
@@ -577,6 +725,48 @@ declare function getDeviceConnectorType(deviceName: string): string;
577
725
  */
578
726
  declare function getStatusColor(status: DeviceStatus | string): string;
579
727
 
728
+ /**
729
+ * TIA-598-C standard fiber optic color codes (12 base colors)
730
+ * Colors 1-12: Blue, Orange, Green, Brown, Slate, White, Red, Black, Yellow, Violet, Rose, Aqua
731
+ * @public
732
+ */
733
+ declare const FIBER_COLORS_12: readonly ["#1f77b4", "#ff7f0e", "#2ca02c", "#8c564b", "#708090", "#ffffff", "#d62728", "#000000", "#ffdf00", "#8a2be2", "#ff69b4", "#00ffff"];
734
+ /**
735
+ * Fiber color ID type (1-24)
736
+ * @public
737
+ */
738
+ type FiberColorId = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24;
739
+ /**
740
+ * Get the base color for a fiber color ID
741
+ * Colors 13-24 use the same base colors as 1-12
742
+ * @param id - Fiber color ID (1-24)
743
+ * @returns Hex color code
744
+ * @public
745
+ */
746
+ declare function baseColorFor(id: FiberColorId | number): string;
747
+ /**
748
+ * Check if a fiber color ID uses a striped pattern (13-24)
749
+ * @param id - Fiber color ID (1-24)
750
+ * @returns True if the color should be striped
751
+ * @public
752
+ */
753
+ declare function isStriped(id: FiberColorId | number): boolean;
754
+ /**
755
+ * Get CSS properties for a fiber color (solid or striped)
756
+ * Colors 13-24 use a black stripe pattern over the base color
757
+ * @param id - Fiber color ID (1-24)
758
+ * @returns CSS properties for the color
759
+ * @public
760
+ */
761
+ declare function fiberSolidOrStriped(id: FiberColorId | number): React$1.CSSProperties;
762
+ /**
763
+ * Get the hex color code for a fiber color ID
764
+ * @param id - Fiber color ID (1-24)
765
+ * @returns Hex color code
766
+ * @public
767
+ */
768
+ declare function getFiberColor(id: FiberColorId | number): string;
769
+
580
770
  /**
581
771
  * Calculate the bounding box of a rack node
582
772
  * @param rack - The rack node
@@ -622,11 +812,13 @@ interface ReplaceEdgeOptions {
622
812
  /** Connection parameters for the new edge */
623
813
  connection: Connection;
624
814
  /** Type of edge to create */
625
- edgeType: 'power' | 'fiber';
815
+ edgeType: 'power' | 'fiber' | 'step' | 'smoothstep';
626
816
  /** Function to calculate zIndex for the edge */
627
817
  selectEdgeZIndex: (edge: NetworkEdge) => number;
628
818
  /** Whether to find edge by source or target */
629
819
  findBy: 'source' | 'target';
820
+ /** Optional edge data to set on the new edge */
821
+ data?: NetworkEdge['data'];
630
822
  }
631
823
  /**
632
824
  * Replace an existing edge with a new connection
@@ -670,4 +862,4 @@ declare function useNetworkDiagram(store: NetworkDiagramStore): NetworkDiagramSt
670
862
  declare function useNodes(store: NetworkDiagramStore): NetworkNode[];
671
863
  declare function useEdges(store: NetworkDiagramStore): NetworkEdge[];
672
864
 
673
- export { DeviceNode, DevicePlacementError, type DevicePlacementValidation, type FiberCable, FiberEdge, FiberNode, NetworkDiagram, type NetworkDiagramProps, type NetworkEdge, type NetworkNode, POWER_CONNECTORS, type Port, type PortBlock, PowerEdge, type RackConfig, type RackDevice, RackNode, type RackSchemaOptions, type ReplaceEdgeOptions, VerticalPDU, Width, addDeviceToRack, buildNodesFromRackConfig, calculateDevicePositionFromU, createNetworkDiagramStore, createRackConfigFromNodes, findNearestRack, findNextAvailableUPosition, generateLayout, getConnectorConfig, getDeviceConnectorType, getPDUPortType, getPowerPortStyle, getRackBounds, getStatusColor, inventoryCableToNetworkEdge, inventoryDeviceToNetworkNode, inventoryRackToNetworkNode, inventoryToNetworkDiagram, isHighPowerConnector, isPointInRack, isUPositionAvailable, removeDeviceFromRack, replaceEdge, snapToUPosition, updateDeviceUPosition$1 as updateDeviceUPosition, updateDeviceUPosition as updateDeviceUPositionInSchema, useEdges as useDiagramEdges, useNodes as useDiagramNodes, useNetworkDiagram, validateAndSnapDevice, validateRackDevicePlacements };
865
+ export { CableNode, DeviceNode, DevicePlacementError, type DevicePlacementValidation, FIBER_COLORS_12, type FiberCable, type FiberColorId, FiberEdge, FiberNode, NetworkDiagram, type NetworkDiagramProps, type NetworkEdge, type NetworkNode, POWER_CONNECTORS, type Port, type PortBlock, PowerEdge, type RackConfig, type RackDevice, RackNode, type RackSchemaOptions, type ReplaceEdgeOptions, type SpliceConfig, SpliceNode, SplicePlacementError, type SplicePlacementValidation, type SpliceSchemaOptions, type SpliceTrayConfig, SpliceTrayNode, TubeNode, VerticalPDU, Width, addDeviceToRack, addSpliceToTray, baseColorFor, buildNodesFromRackConfig, buildNodesFromSpliceConfig, calculateDevicePositionFromU, calculateSplicePositionFromNumber, createNetworkDiagramStore, createRackConfigFromNodes, createSpliceConfigFromNodes, fiberSolidOrStriped, findNearestRack, findNextAvailableHolderPosition, findNextAvailableUPosition, generateLayout, getConnectorConfig, getDeviceConnectorType, getFiberColor, getPDUPortType, getPowerPortStyle, getRackBounds, getStatusColor, inventoryCableToNetworkEdge, inventoryDeviceToNetworkNode, inventoryRackToNetworkNode, inventoryToNetworkDiagram, isHighPowerConnector, isPointInRack, isStriped, isUPositionAvailable, removeDeviceFromRack, removeSpliceFromTray, replaceEdge, snapToSplicePosition, snapToUPosition, updateDeviceUPosition$1 as updateDeviceUPosition, updateDeviceUPosition as updateDeviceUPositionInSchema, updateSpliceHolderPosition, useEdges as useDiagramEdges, useNodes as useDiagramNodes, useNetworkDiagram, validateAndSnapDevice, validateRackDevicePlacements, validateSplicePlacements };