@oscherbakov/react-flow-automated-layout 1.3.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 ADDED
@@ -0,0 +1,752 @@
1
+ # React Flow Automated Layout
2
+
3
+ A React library for automated layout of nested node graphs with parent-child relationships using React Flow. The library provides an easy-to-use context provider system that handles intelligent layouts for flowcharts and diagrams.
4
+
5
+ ## Examples in codesandbox.io
6
+ [Link](https://codesandbox.io/p/sandbox/wyp9px)
7
+
8
+ ## Key Features
9
+
10
+ - **Automated Layout**: Implements Dagre algorithm for automated graph layouts
11
+ - **Parent-Child Relationships**: Supports nested nodes with parent-child container relationships
12
+ - **Dynamic Resizing**: Automatic parent container resizing based on child nodes
13
+ - **Flexible Directions**: Support for both vertical (DOWN) and horizontal (RIGHT) layouts
14
+ - **Interactive UI**: Real-time layout adjustments with control panel
15
+ - **Selective Layout**: Apply layout to only selected nodes or the entire graph
16
+ - **Parent Inclusion Fix**: Automatically includes a selected child’s parent node to ensure accurate automated layouts when only child nodes are selected
17
+ - **Custom Controls**: Create your own control interfaces using the layout context
18
+ - **Auto-reconnection**: Smart edge reconnection when nodes are removed
19
+ - **Configurable Node Dimensions**: Set default dimensions for nodes that don't have explicit width/height
20
+ - **Extensible Engine System**: Designed to support custom layout engines in future releases
21
+
22
+ ## Breaking Changes in 1.0.0
23
+
24
+ Version 1.0.0 introduces one breaking change that require updates to your code:
25
+
26
+ ### 1. Parent-Child Relationship Maps
27
+
28
+ The most significant change is how parent-child relationships are managed:
29
+
30
+ **Old API (v0.x):**
31
+ ```javascript
32
+ // Map of parent IDs to arrays of child nodes
33
+ const parentIdWithNodes = new Map<string, Node[]>();
34
+
35
+ // Update on node changes
36
+ nodes.forEach((node) => {
37
+ if (node.parentId) {
38
+ if (!parentIdWithNodes.has(node.parentId)) {
39
+ parentIdWithNodes.set(node.parentId, []);
40
+ }
41
+ parentIdWithNodes.get(node.parentId).push(node);
42
+ } else {
43
+ if(!parentIdWithNodes.has("no-parent")) {
44
+ parentIdWithNodes.set("no-parent", []);
45
+ }
46
+ parentIdWithNodes.get("no-parent").push(node);
47
+ }
48
+ });
49
+ ```
50
+
51
+ **New API (v1.0.0):**
52
+ ```javascript
53
+ // Map of parent IDs to Sets of child IDs (more efficient lookup)
54
+ const nodeParentIdMapWithChildIdSet = new Map<string, Set<string>>();
55
+ const nodeIdWithNode = new Map<string, Node>();
56
+
57
+ // Update on node changes
58
+ nodes.forEach((node) => {
59
+ // Map for direct node lookup by ID
60
+ nodeIdWithNode.set(node.id, node);
61
+
62
+ // Map parent ID to Set of child IDs
63
+ const parentId = node.parentId || "no-parent";
64
+ if (!nodeParentIdMapWithChildIdSet.has(parentId)) {
65
+ nodeParentIdMapWithChildIdSet.set(parentId, new Set());
66
+ }
67
+ nodeParentIdMapWithChildIdSet.get(parentId)?.add(node.id);
68
+ });
69
+ ```
70
+
71
+ ### 2. LayoutProvider Props
72
+
73
+ The LayoutProvider component props have changed accordingly:
74
+
75
+ **Old API (v0.x):**
76
+ ```jsx
77
+ <LayoutProvider
78
+ // ...other props
79
+ parentIdWithNodes={parentIdWithNodes}
80
+ nodeIdWithNode={nodeIdWithNode}
81
+ >
82
+ ```
83
+
84
+ **New API (v1.0.0):**
85
+ ```jsx
86
+ <LayoutProvider
87
+ //All props now optional!
88
+ >
89
+ ```
90
+
91
+ ## Patch Updates
92
+
93
+ ### 1.2.6 (2026-03-31)
94
+
95
+ - **Reciprocal Bridge Layouts**: Added handling for reciprocal cross-container bridge patterns so `A -> B -> A` style relationships no longer collapse into arbitrary directional ordering
96
+ - **Synthetic Bridge Edges**: Introduced temporary intra-container bridge edges to preserve indirect ordering between sibling branches such as `A1 -> B1 -> A2`
97
+ - **Nested Ancestor Projection**: Generalized bridge detection to work across deeper descendant paths by projecting edge endpoints to the first child under the current layout ancestor
98
+ - **Bridge Alignment Pass**: Added a post-layout alignment pass that centers bridged sibling containers against the midpoint of the connected branches
99
+ - **Sibling Collision Resolution**: Added a follow-up collision pass so sibling containers are pushed clear after bridge alignment instead of overlapping
100
+ - **Bounds Recalculation**: Parent container dimensions are now recomputed from final post-alignment child positions to reserve space correctly at higher levels
101
+ - **Example Coverage**: Added a new `07 - Cyclic Containers` example and regression tests covering both direct and nested reciprocal bridge scenarios
102
+
103
+ ### 1.2.3 (2025-05-26)
104
+
105
+ - **Enhanced Edge Handling**: Completely redesigned edge processing system for better reliability with complex nested structures
106
+ - **Simplified Layout Pipeline**: Streamlined layout process with pre-computed temporary edge maps
107
+ - **Improved LCA Calculations**: Enhanced Lowest Common Ancestor detection for more accurate edge routing
108
+ - **Edge Loss Prevention**: Fixed critical edge loss issues in complex hierarchical structures
109
+ - **Root-Level Processing**: Added proper root-level edge processing for unresolved edges
110
+ - **Performance Optimizations**: Reduced complexity in edge processing with upfront calculations
111
+
112
+ ### 1.2.1 (2025-05-22)
113
+
114
+ - Added a new `disableAutoLayoutEffect` prop to `LayoutProvider`. This allows you to explicitly disable the automatic layout effect, giving you more control over when layouts are triggered.
115
+ - Improved the auto layout effect logic: layout will not run if `disableAutoLayoutEffect` is true or if a layout is already in progress, preventing unwanted or redundant layout runs.
116
+ - Enhanced the node discrepancy check: the layout effect now checks if the nodes in the context and the flow are in sync, and skips recalculation if not, reducing unnecessary layout operations.
117
+ - Added a console log to indicate when the auto layout effect is triggered (for easier debugging and development).
118
+
119
+ ### 1.1.1 (Unreleased)
120
+
121
+ - Improved node count discrepancy check to prevent unnecessary layout recalculations when the flow is not in sync with the context.
122
+ - Fixed edge mutation issue in getEdgesOfNodes function.
123
+ - Removed leftover console logs and old files.
124
+ - Minor code style improvements and refactoring.
125
+
126
+ ### 1.1.0 (2025-05-07)
127
+
128
+ - **Layout Engine System**: Implemented a new pluggable layout engine architecture to support multiple layout algorithms
129
+ - **Dagre Engine Adapter**: Converted existing Dagre implementation to use the new engine interface
130
+ - **Enhanced Configuration**: Added more flexible configuration options for layout engines
131
+ - **Improved Type Safety**: Better TypeScript type definitions for layout configuration
132
+ - **Smart Edge Routing**: Enhanced edge handling for connections between non-sibling nodes, automatically routing edges to appropriate parent containers
133
+ - **Per-Container Layout Direction**: Support for individual layout directions per container using `data.layoutDirection` property on parent nodes
134
+ - **Parallel Layout Processing**: Improved performance with asynchronous parallel processing of layouts
135
+
136
+ ### 1.0.0 (2025-05-02)
137
+
138
+ - **Optional Relationship Maps**: Made `nodeIdWithNode` and `nodeParentIdMapWithChildIdSet` optional in `LayoutProvider`. When not provided, these maps are now managed internally.
139
+ - **Simplified Usage**: Users no longer need to manually manage relationship maps if they don't need custom control over them.
140
+
141
+ ### 0.3.3 (2025-04-21)
142
+
143
+ - **Selection Handling**: Enhanced parent node filtering with improved handling of node selections
144
+ - **Layout Engine**: Improved layout calculation with better support for node dimensions and spacing
145
+
146
+
147
+ ### 0.3.2 (2025-04-20)
148
+
149
+ - **Selection Handling**: Refactored to use Node objects directly, improving type safety and reducing object lookups
150
+ - **Parent Node Dimensions**: Added measured property for more accurate parent node dimension tracking
151
+ - **Layout Engine**: Enhanced parent node handling in recursive layouts with better dimension updates and parent-child relationships
152
+ - **Performance**: Improved node/edge merging performance using Map-based lookups
153
+
154
+ ### 0.3.1 (2025-04-18)
155
+
156
+ - **Parent Inclusion Fix**: Automatically include a selected child’s parent node to ensure accurate automated layouts when only child nodes are selected
157
+
158
+
159
+ ## Installation
160
+
161
+ ```bash
162
+ npm install @jalez/react-flow-automated-layout
163
+ ```
164
+
165
+ ## Quick Start
166
+
167
+ First, set up your React Flow component and then wrap it with the LayoutProvider:
168
+
169
+ ### Simple Setup (v1.0.0+)
170
+
171
+ ```jsx
172
+ import { useState, useCallback } from 'react';
173
+ import { ReactFlow, ReactFlowProvider, useNodesState, useEdgesState } from '@xyflow/react';
174
+ import { LayoutProvider, LayoutControls } from '@jalez/react-flow-automated-layout';
175
+ import '@xyflow/react/dist/style.css';
176
+
177
+ function FlowDiagram() {
178
+ // Set up React Flow states
179
+ const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
180
+ const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
181
+
182
+ return (
183
+ <ReactFlowProvider>
184
+ <LayoutProvider>
185
+ <ReactFlow
186
+ nodes={nodes}
187
+ edges={edges}
188
+ onNodesChange={onNodesChange}
189
+ onEdgesChange={onEdgesChange}
190
+ fitView
191
+ >
192
+ {/* Add LayoutControls to your Controls component */}
193
+ <Controls position="top-right">
194
+ <LayoutControls
195
+ showDirectionControls={true}
196
+ showAutoLayoutToggle={true}
197
+ showSpacingControls={true}
198
+ showApplyLayoutButton={true}
199
+ />
200
+ </Controls>
201
+ <Background />
202
+ </ReactFlow>
203
+ </LayoutProvider>
204
+ </ReactFlowProvider>
205
+ );
206
+ }
207
+ ```
208
+
209
+ ### Manual Control Setup (All versions)
210
+
211
+ For cases where you need custom control over relationship maps:
212
+
213
+ ```jsx
214
+ import { useState, useCallback, useEffect } from 'react';
215
+ import { ReactFlow, ReactFlowProvider, useNodesState, useEdgesState } from '@xyflow/react';
216
+ import { LayoutProvider, LayoutControls } from '@jalez/react-flow-automated-layout';
217
+ import '@xyflow/react/dist/style.css';
218
+
219
+ function FlowDiagram() {
220
+ // Set up React Flow states
221
+ const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
222
+ const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
223
+
224
+ // Define a custom key for parentless nodes
225
+ const rootLevelKey = "root-level";
226
+
227
+ // Maps for parent-child relationships (required by LayoutProvider)
228
+ const [nodeParentIdMapWithChildIdSet, setNodeParentIdMapWithChildIdSet] = useState(new Map());
229
+ const [nodeIdWithNode, setNodeIdWithNode] = useState(new Map());
230
+
231
+ // Update these maps whenever nodes change
232
+ useEffect(() => {
233
+ const nodeParentIdMapWithChildIdSet = new Map();
234
+ const nodeIdWithNode = new Map();
235
+
236
+ nodes.forEach((node) => {
237
+ // Store node by ID for quick lookup
238
+ nodeIdWithNode.set(node.id, node);
239
+
240
+ // Map parent ID to Set of child IDs, using our custom rootLevelKey for parentless nodes
241
+ const parentId = node.parentId || rootLevelKey;
242
+ if (!nodeParentIdMapWithChildIdSet.has(parentId)) {
243
+ nodeParentIdMapWithChildIdSet.set(parentId, new Set());
244
+ }
245
+ nodeParentIdMapWithChildIdSet.get(parentId).add(node.id);
246
+ });
247
+
248
+ setNodeParentIdMapWithChildIdSet(nodeParentIdMapWithChildIdSet);
249
+ setNodeIdWithNode(nodeIdWithNode);
250
+ }, [nodes]);
251
+
252
+ // Callbacks to update nodes and edges (required by LayoutProvider)
253
+ const updateNodesHandler = useCallback((newNodes) => {
254
+ setNodes(newNodes);
255
+ }, [setNodes]);
256
+
257
+ const updateEdgesHandler = useCallback((newEdges) => {
258
+ setEdges(newEdges);
259
+ }, [setEdges]);
260
+
261
+ return (
262
+ <ReactFlowProvider>
263
+ <LayoutProvider
264
+ initialDirection="DOWN"
265
+ initialAutoLayout={true}
266
+ initialPadding={50}
267
+ initialSpacing={{ node: 50, layer: 50 }}
268
+ initialParentResizingOptions={{
269
+ padding: {
270
+ horizontal: 50,
271
+ vertical: 40,
272
+ },
273
+ minWidth: 150,
274
+ minHeight: 150,
275
+ }}
276
+ updateNodes={updateNodesHandler}
277
+ updateEdges={updateEdgesHandler}
278
+ nodeParentIdMapWithChildIdSet={nodeParentIdMapWithChildIdSet}
279
+ nodeIdWithNode={nodeIdWithNode}
280
+ noParentKey={rootLevelKey} // Pass the same custom key for consistency
281
+ >
282
+ <ReactFlow
283
+ nodes={nodes}
284
+ edges={edges}
285
+ onNodesChange={onNodesChange}
286
+ onEdgesChange={onEdgesChange}
287
+ fitView
288
+ >
289
+ {/* Add LayoutControls to your Controls component */}
290
+ <Controls position="top-right">
291
+ <LayoutControls
292
+ showDirectionControls={true}
293
+ showAutoLayoutToggle={true}
294
+ showSpacingControls={true}
295
+ showApplyLayoutButton={true}
296
+ />
297
+ </Controls>
298
+ <Background />
299
+ </ReactFlow>
300
+ </LayoutProvider>
301
+ </ReactFlowProvider>
302
+ );
303
+ }
304
+ ```
305
+
306
+ ## Parent-Child Relationships
307
+
308
+ To create parent-child relationships, set the `parentId` property on child nodes and the `extent` property to 'parent':
309
+
310
+ ```javascript
311
+ // Parent node
312
+ const parentNode = {
313
+ id: 'parent1',
314
+ type: 'group',
315
+ data: {},
316
+ position: { x: 0, y: 0 },
317
+ style: {
318
+ width: 400,
319
+ height: 400,
320
+ border: '1px solid #000',
321
+ }
322
+ };
323
+
324
+ // Child nodes
325
+ const childNodes = [
326
+ {
327
+ id: 'child1',
328
+ data: { label: 'Child 1' },
329
+ position: { x: 0, y: 0 },
330
+ parentId: 'parent1',
331
+ extent: 'parent'
332
+ },
333
+ {
334
+ id: 'child2',
335
+ data: { label: 'Child 2' },
336
+ position: { x: 0, y: 0 },
337
+ parentId: 'parent1',
338
+ extent: 'parent'
339
+ }
340
+ ];
341
+
342
+ // Initialize with both parent and child nodes
343
+ const initialNodes = [parentNode, ...childNodes];
344
+ ```
345
+
346
+ ## Node Dimensions
347
+
348
+ The layout system uses default node dimensions for calculating optimal positioning when nodes don't have explicit width and height values. This is an important feature to understand:
349
+
350
+ ### Default Node Dimensions and Style Priority
351
+
352
+ When the layout algorithm organizes your nodes, it needs to know how much space each node requires. The library handles this in the following priority order:
353
+
354
+ 1. If a node has explicit dimensions in its `style` property (`style.width` and `style.height`), these values are respected and used for layout calculations
355
+ 2. If no `style` dimensions are present, the library applies the default dimensions
356
+
357
+ ```javascript
358
+ // Default dimensions used internally if not specified on the node's style
359
+ const DEFAULT_NODE_WIDTH = 172;
360
+ const DEFAULT_NODE_HEIGHT = 36;
361
+ ```
362
+
363
+ **Important:** For the layout to work correctly, the system relies on the width and height properties of your nodes. The layout engine will use these values when positioning nodes, so it's crucial that:
364
+
365
+ 1. Either set width and height explicitly in your node's style properties
366
+ 2. Or let the layout system apply the default dimensions
367
+
368
+ This prioritization ensures that your custom node sizes are always respected while still allowing the layout algorithm to make accurate spacing calculations for nodes without explicit dimensions.
369
+
370
+ ### Configuring Default Dimensions
371
+
372
+ You can customize these defaults when initializing the LayoutProvider:
373
+
374
+ ```jsx
375
+ <LayoutProvider
376
+ // Other props...
377
+ initialNodeDimensions={{
378
+ width: 200, // Custom default width
379
+ height: 50 // Custom default height
380
+ }}
381
+ >
382
+ {/* Your React Flow component */}
383
+ </LayoutProvider>
384
+ ```
385
+
386
+ ### Runtime Adjustment
387
+
388
+ You can adjust node dimensions at runtime using the layout context:
389
+
390
+ ```jsx
391
+ const { setNodeWidth, setNodeHeight } = useLayoutContext();
392
+
393
+ // Update dimensions
394
+ setNodeWidth(180);
395
+ setNodeHeight(40);
396
+ ```
397
+
398
+ ### Important: Dimensions in Result Nodes
399
+
400
+ **When the layout algorithm returns updated nodes, it includes these default dimensions in the nodes' properties.** This means:
401
+
402
+ 1. Nodes without dimensions will have `width` and `height` properties added
403
+ 2. These properties will be reflected in the rendered nodes
404
+ 3. The layout calculations will be consistent with the visual representation
405
+
406
+ This is critical to understand because you should use these dimensions when working with the nodes returned by the layout system, rather than assuming nodes have their original dimensions.
407
+
408
+ Example of a node after layout:
409
+
410
+ ```javascript
411
+ // Original node (no dimensions)
412
+ const originalNode = {
413
+ id: 'node1',
414
+ data: { label: 'Node 1' },
415
+ position: { x: 0, y: 0 }
416
+ };
417
+
418
+ // Node after layout (with dimensions added)
419
+ const afterLayoutNode = {
420
+ id: 'node1',
421
+ data: { label: 'Node 1' },
422
+ position: { x: 100, y: 200 },
423
+ width: 172, // Added by the layout system
424
+ height: 36, // Added by the layout system
425
+ style: {
426
+ width: 172, // Also added to style
427
+ height: 36 // Also added to style
428
+ }
429
+ };
430
+ ```
431
+
432
+ This ensures that node dimensions are consistent across the entire application, leading to more predictable layouts.
433
+
434
+ ## Making Custom Controls
435
+
436
+ You can create your own custom controls by using the `useLayoutContext` hook:
437
+
438
+ ```jsx
439
+ import { useLayoutContext } from '@jalez/react-flow-automated-layout';
440
+
441
+ function CustomLayoutControl() {
442
+ const {
443
+ direction,
444
+ autoLayout,
445
+ nodeSpacing,
446
+ layerSpacing,
447
+ layoutInProgress,
448
+ setDirection,
449
+ setAutoLayout,
450
+ setNodeSpacing,
451
+ setLayerSpacing,
452
+ applyLayout
453
+ } = useLayoutContext();
454
+
455
+ return (
456
+ <div>
457
+ <h3>Custom Layout Controls</h3>
458
+
459
+ {/* Direction controls */}
460
+ <div>
461
+ <label>Direction:</label>
462
+ <div>
463
+ {(['DOWN', 'RIGHT', 'UP', 'LEFT']).map((dir) => (
464
+ <button
465
+ key={dir}
466
+ onClick={() => setDirection(dir)}
467
+ style={{
468
+ background: direction === dir ? '#0041d0' : '#f5f5f5',
469
+ color: direction === dir ? 'white' : 'black'
470
+ }}
471
+ >
472
+ {dir}
473
+ </button>
474
+ ))}
475
+ </div>
476
+ </div>
477
+
478
+ {/* Spacing control */}
479
+ <div>
480
+ <label>Spacing: {nodeSpacing}px</label>
481
+ <input
482
+ type="range"
483
+ min="20"
484
+ max="200"
485
+ value={nodeSpacing}
486
+ onChange={(e) => {
487
+ const value = parseInt(e.target.value);
488
+ setNodeSpacing(value);
489
+ setLayerSpacing(value);
490
+ }}
491
+ />
492
+ </div>
493
+
494
+ {/* Auto layout toggle */}
495
+ <div>
496
+ <label>
497
+ <input
498
+ type="checkbox"
499
+ checked={autoLayout}
500
+ onChange={() => setAutoLayout(!autoLayout)}
501
+ />
502
+ Auto Layout
503
+ </label>
504
+ </div>
505
+
506
+ {/* Apply layout button */}
507
+ {!autoLayout && (
508
+ <button
509
+ onClick={() => applyLayout()}
510
+ disabled={layoutInProgress}
511
+ >
512
+ {layoutInProgress ? 'Applying...' : 'Apply Layout'}
513
+ </button>
514
+ )}
515
+ </div>
516
+ );
517
+ }
518
+ ```
519
+
520
+ ## Technologies Used
521
+
522
+ - **React**: For building the user interface
523
+ - **TypeScript**: For type-safe development
524
+ - **@xyflow/react**: Core React Flow library for graph visualization
525
+ - **@dagrejs/dagre**: For automated layout calculations
526
+
527
+ ## Core Components
528
+
529
+ - **LayoutProvider**: Context provider that wraps your React Flow component
530
+ - **LayoutControls**: Ready-to-use UI component for adjusting layout settings
531
+ - **HierarchicalLayoutOrganizer**: Core layout engine that handles parent-child relationships
532
+ - **useLayoutContext**: Hook for accessing layout context in custom components
533
+ - **LayoutEngine**: Interface for creating custom layout engines (new in v1.1.0)
534
+
535
+ ## Layout Engine System
536
+
537
+ Starting with v1.1.0, the library implements a pluggable layout engine architecture that allows for different layout algorithms to be used. Currently, the library ships with the Dagre engine:
538
+
539
+ ```jsx
540
+ import { LayoutProvider, engines } from '@jalez/react-flow-automated-layout';
541
+
542
+ function FlowDiagram() {
543
+ return (
544
+ <LayoutProvider
545
+ // Optionally specify a different engine (dagre is the default)
546
+ engine={engines.dagre}
547
+ >
548
+ {/* Your React Flow component */}
549
+ </LayoutProvider>
550
+ );
551
+ }
552
+ ```
553
+
554
+ ### Per-Container Layout Direction (v1.1.0+)
555
+
556
+ You can now set different layout directions for individual containers by adding a `layoutDirection` property to the parent node's data object:
557
+
558
+ ```jsx
559
+ // Parent nodes with different layout directions
560
+ const parentNodes = [
561
+ {
562
+ id: 'container1',
563
+ type: 'group',
564
+ data: {
565
+ label: 'Vertical Layout',
566
+ layoutDirection: 'TB' // Top to Bottom layout for this container's children
567
+ },
568
+ position: { x: 0, y: 0 },
569
+ style: { width: 400, height: 400 }
570
+ },
571
+ {
572
+ id: 'container2',
573
+ type: 'group',
574
+ data: {
575
+ label: 'Horizontal Layout',
576
+ layoutDirection: 'LR' // Left to Right layout for this container's children
577
+ },
578
+ position: { x: 500, y: 0 },
579
+ style: { width: 400, height: 400 }
580
+ }
581
+ ];
582
+ ```
583
+
584
+ This allows you to create more complex diagrams with different layout directions per section, all while maintaining the global layout algorithm for parent relationships.
585
+
586
+ ### Smart Edge Routing (v1.1.0+)
587
+
588
+ The new edge handling system automatically manages connections between nodes in different containers, rerouting edges to the appropriate parent containers when necessary. This works automatically when you create edges between nodes that aren't direct siblings:
589
+
590
+ ```jsx
591
+ // Example edge between nodes in different containers
592
+ const crossContainerEdge = {
593
+ id: 'edge-cross-container',
594
+ source: 'node-in-container1',
595
+ target: 'node-in-container2'
596
+ };
597
+ ```
598
+
599
+ When this edge is processed by the layout engine, it will intelligently:
600
+
601
+ 1. Detect that source and target are in different containers
602
+ 2. Temporarily reroute the edge between the appropriate parent containers for layout calculations
603
+ 3. Preserve the original edge connectivity in the final rendered graph
604
+
605
+ This leads to cleaner diagrams with more logical edge paths, especially in complex nested structures.
606
+
607
+ ### Creating Custom Layout Engines
608
+
609
+ You can implement your own layout engine by implementing the LayoutEngine interface:
610
+
611
+ ```typescript
612
+ import { LayoutEngine } from '@jalez/react-flow-automated-layout';
613
+
614
+ const MyCustomEngine: LayoutEngine = {
615
+ calculate: async (nodes, edges, options) => {
616
+ // Your custom layout algorithm implementation
617
+ // Must return positioned nodes with { position: { x, y } }
618
+ return layoutedNodes;
619
+ }
620
+ };
621
+
622
+ // Then use your engine in the LayoutProvider
623
+ <LayoutProvider engine={MyCustomEngine}>
624
+ {/* Your React Flow component */}
625
+ </LayoutProvider>
626
+ ```
627
+
628
+ ### Layout Configuration
629
+
630
+ The layout engine system provides flexible configuration options:
631
+
632
+ ```typescript
633
+ // Example of configuration options for layout engines
634
+ const layoutConfig = {
635
+ nodes: nodes,
636
+ edges: edges,
637
+ dagreDirection: 'TB', // 'TB', 'BT', 'LR', or 'RL'
638
+ margin: 20,
639
+ nodeSpacing: 50,
640
+ layerSpacing: 50,
641
+ nodeWidth: 172,
642
+ nodeHeight: 36,
643
+ layoutHidden: false // Whether to include hidden nodes in layout
644
+ };
645
+
646
+ // Access configuration through the context
647
+ const { setLayoutConfig } = useLayoutContext();
648
+ setLayoutConfig(layoutConfig);
649
+ ```
650
+
651
+ ## Future Plans
652
+
653
+ - **Custom Layout Engines**: Support for pluggable layout engines beyond the built-in Dagre implementation
654
+ - **Advanced Layout Algorithms**: Integration with engines like ELK for more sophisticated layout options
655
+
656
+ ## Examples Included
657
+
658
+ The github repository includes several examples demonstrating different features:
659
+
660
+ ### 01 - Basic Layout
661
+
662
+ Demonstrates how LayoutProvider automatically organizes nested nodes with parent-child relationships while maintaining proper spacing and hierarchy.
663
+
664
+ ### 02 - Add Node on Edge Drop
665
+
666
+ Shows how LayoutProvider automatically reorganizes the diagram when new nodes are created, keeping the layout clean and organized.
667
+
668
+ ### 03 - Remove Node with Reconnection
669
+
670
+ Illustrates how LayoutProvider maintains a coherent layout when nodes are deleted, automatically rearranging connections and preserving the flow.
671
+
672
+ ### 04 - Select Node
673
+
674
+ Demonstrates selective layout application where only selected nodes are reorganized while the rest of the graph remains unchanged, allowing targeted layout adjustments to specific parts of complex diagrams.
675
+
676
+ ### 05 - Custom Controls
677
+
678
+ Shows how to build your own custom UI controls by accessing the layout context directly via the useLayoutContext hook, enabling fully customized layout interfaces.
679
+
680
+ ## API Reference
681
+
682
+ ### LayoutProvider Props
683
+
684
+ | Prop | Type | Default | Description |
685
+ |------|------|---------|-------------|
686
+ | children | ReactNode | | Child components |
687
+ | initialDirection | 'UP' \| 'DOWN' \| 'LEFT' \| 'RIGHT' | 'DOWN' | Initial layout direction |
688
+ | initialAutoLayout | boolean | false | Whether to automatically apply layout on changes |
689
+ | initialPadding | number | 50 | Padding around the layout |
690
+ | initialSpacing | { node: number, layer: number } | { node: 50, layer: 50 } | Spacing between nodes and layers |
691
+ | initialNodeDimensions | { width: number, height: number } | { width: 172, height: 36 } | Default dimensions for nodes without explicit width/height |
692
+ | initialParentResizingOptions | object | See below | Options for parent container resizing |
693
+ | updateNodes | (nodes: Node[]) => void | | Callback to update nodes |
694
+ | updateEdges | (edges: Edge[]) => void | | Callback to update edges |
695
+ | nodeParentIdMapWithChildIdSet | Map<string, Set<string>> | | Map of parent IDs to Sets of child IDs. Must include a key grouping top-level nodes without a parent. |
696
+ | nodeIdWithNode | Map<string, Node> | | Map of node IDs to node objects |
697
+ | noParentKey | string | 'no-parent' | Customizable key used to represent nodes without a parent in the nodeParentIdMapWithChildIdSet map |
698
+
699
+ #### Default Parent Resizing Options
700
+
701
+ ```javascript
702
+ {
703
+ padding: {
704
+ horizontal: 50,
705
+ vertical: 40
706
+ },
707
+ minWidth: 150,
708
+ minHeight: 150
709
+ }
710
+ ```
711
+
712
+ ### LayoutControls Props
713
+
714
+ | Prop | Type | Default | Description |
715
+ |------|------|---------|-------------|
716
+ | showDirectionControls | boolean | true | Show direction control buttons |
717
+ | showAutoLayoutToggle | boolean | true | Show auto-layout toggle switch |
718
+ | showSpacingControls | boolean | true | Show spacing slider controls |
719
+ | showApplyLayoutButton | boolean | true | Show apply layout button |
720
+
721
+ ### useLayoutContext Hook
722
+
723
+ ```jsx
724
+ import { useLayoutContext } from "@jalez/react-flow-automated-layout";
725
+
726
+ function MyCustomControl() {
727
+ const {
728
+ direction,
729
+ setDirection,
730
+ nodeSpacing,
731
+ layerSpacing,
732
+ setNodeSpacing,
733
+ setLayerSpacing,
734
+ applyLayout,
735
+ autoLayout,
736
+ setAutoLayout,
737
+ layoutInProgress
738
+ } = useLayoutContext();
739
+
740
+ // Your custom control implementation
741
+ }
742
+ ```
743
+
744
+
745
+
746
+ ## Contributing
747
+
748
+ Contributions are welcome! Feel free to open issues or submit pull requests to improve the project.
749
+
750
+ ## License
751
+
752
+ MIT