@principal-ai/principal-view-react 0.13.7 → 0.13.8
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/dist/components/GraphRenderer.d.ts +1 -15
- package/dist/components/GraphRenderer.d.ts.map +1 -1
- package/dist/components/GraphRenderer.js +94 -269
- package/dist/components/GraphRenderer.js.map +1 -1
- package/dist/components/NodeInfoPanel.d.ts.map +1 -1
- package/dist/components/NodeInfoPanel.js.map +1 -1
- package/dist/edges/CustomEdge.d.ts +2 -2
- package/dist/edges/CustomEdge.d.ts.map +1 -1
- package/dist/edges/CustomEdge.js +5 -4
- package/dist/edges/CustomEdge.js.map +1 -1
- package/dist/hooks/usePathBasedEvents.d.ts.map +1 -1
- package/dist/hooks/usePathBasedEvents.js +2 -1
- package/dist/hooks/usePathBasedEvents.js.map +1 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/nodes/CustomNode.d.ts +2 -2
- package/dist/nodes/CustomNode.d.ts.map +1 -1
- package/dist/nodes/CustomNode.js +41 -10
- package/dist/nodes/CustomNode.js.map +1 -1
- package/package.json +1 -1
- package/src/components/GraphRenderer.tsx +106 -354
- package/src/edges/CustomEdge.tsx +8 -7
- package/src/hooks/usePathBasedEvents.ts +2 -1
- package/src/index.ts +0 -6
- package/src/nodes/CustomNode.tsx +50 -13
- package/src/stories/ColorPriority.stories.tsx +2 -0
- package/src/stories/EventDrivenAnimations.stories.tsx +332 -326
- package/src/stories/GraphRenderer.stories.tsx +2 -2
- package/src/stories/NodeDefinitionComparison.stories.tsx +1 -1
- package/src/stories/NodeDimensionsTesting.stories.tsx +2 -2
- package/src/stories/OtelComponents.stories.tsx +0 -47
- package/src/stories/data/graph-converter-test-execution.json +244 -26
- package/src/stories/data/graph-converter-validated-execution.json +6 -6
- package/src/components/EdgeInfoPanel.tsx +0 -247
- package/src/components/NodeInfoPanel.tsx +0 -724
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import '@xyflow/react/dist/style.css';
|
|
3
|
-
import type { Violation, GraphEvent, ExtendedCanvas, ComponentLibrary
|
|
3
|
+
import type { Violation, GraphEvent, ExtendedCanvas, ComponentLibrary } from '@principal-ai/principal-view-core';
|
|
4
4
|
/** Position change event for tracking node movements */
|
|
5
5
|
export interface NodePositionChange {
|
|
6
6
|
nodeId: string;
|
|
@@ -138,20 +138,6 @@ interface GraphRendererBaseProps {
|
|
|
138
138
|
* Receives the node ID and the click event. If provided, overrides default node selection behavior.
|
|
139
139
|
*/
|
|
140
140
|
onNodeClick?: (nodeId: string, event: React.MouseEvent) => void;
|
|
141
|
-
/**
|
|
142
|
-
* Whether to show the node detail panel when nodes are clicked.
|
|
143
|
-
* Defaults to undefined (auto), which shows panel only when onNodeClick is not provided.
|
|
144
|
-
* Set to true to force showing panel even with custom onNodeClick handler.
|
|
145
|
-
* Set to false to hide panel completely.
|
|
146
|
-
*/
|
|
147
|
-
showNodeDetailPanel?: boolean;
|
|
148
|
-
/**
|
|
149
|
-
* Optional callback to resolve event references to full event schemas.
|
|
150
|
-
* When a node has an eventRef (string reference like "order.completed"),
|
|
151
|
-
* this function is called to retrieve the full event definition.
|
|
152
|
-
* Return undefined if the event reference cannot be resolved.
|
|
153
|
-
*/
|
|
154
|
-
resolveEventRef?: (eventRef: string) => PVEventSchema | undefined;
|
|
155
141
|
/**
|
|
156
142
|
* When set, fits the viewport to show these specific nodes.
|
|
157
143
|
* Useful for focusing on a subset of the graph (e.g., scenario nodes).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GraphRenderer.d.ts","sourceRoot":"","sources":["../../src/components/GraphRenderer.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"GraphRenderer.d.ts","sourceRoot":"","sources":["../../src/components/GraphRenderer.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo, useState, useEffect, useCallback, useRef, useImperativeHandle, forwardRef, } from 'react';
|
|
2
|
+
import { useMemo, useState, useEffect, useLayoutEffect, useCallback, useRef, useImperativeHandle, forwardRef, } from 'react';
|
|
3
3
|
import { ReactFlow, Background, BackgroundVariant, Controls, MiniMap, ReactFlowProvider, useReactFlow, useUpdateNodeInternals, useViewport, applyNodeChanges, applyEdgeChanges, } from '@xyflow/react';
|
|
4
4
|
import '@xyflow/react/dist/style.css';
|
|
5
5
|
import { CanvasConverter } from '@principal-ai/principal-view-core';
|
|
@@ -7,16 +7,14 @@ import { useTheme } from '@principal-ade/industry-theme';
|
|
|
7
7
|
import { CustomNode } from '../nodes/CustomNode';
|
|
8
8
|
import { CustomEdge } from '../edges/CustomEdge';
|
|
9
9
|
import { convertToXYFlowNodes, convertToXYFlowEdges, } from '../utils/graphConverter';
|
|
10
|
-
import { EdgeInfoPanel } from './EdgeInfoPanel';
|
|
11
|
-
import { NodeInfoPanel } from './NodeInfoPanel';
|
|
12
10
|
// Define custom node types
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
};
|
|
11
|
+
// Type assertion needed because ReactFlow's NodeTypes expects generic components
|
|
12
|
+
// but CustomNode is properly typed with CustomNodeData for type safety internally
|
|
13
|
+
const nodeTypes = { custom: CustomNode };
|
|
16
14
|
// Define custom edge types
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
};
|
|
15
|
+
// Type assertion needed because ReactFlow's EdgeTypes expects generic components
|
|
16
|
+
// but CustomEdge is properly typed with CustomEdgeData for type safety internally
|
|
17
|
+
const edgeTypes = { custom: CustomEdge };
|
|
20
18
|
const createEmptyEditState = () => ({
|
|
21
19
|
positionChanges: new Map(),
|
|
22
20
|
dimensionChanges: new Map(),
|
|
@@ -80,7 +78,7 @@ const CenterIndicator = ({ color }) => {
|
|
|
80
78
|
/**
|
|
81
79
|
* Inner component that uses ReactFlow hooks
|
|
82
80
|
*/
|
|
83
|
-
const GraphRendererInner = ({ configuration, nodes: propNodes, edges: propEdges, violations = [], configName: _configName, showMinimap = false, showControls = true, showBackground = true, backgroundVariant = 'lines', backgroundGap, showCenterIndicator = false, showTooltips = true, fitViewDuration = 200, highlightedNodeId, activeNodeIds, events = [], onEventProcessed, editable = false, onPendingChangesChange, onEditStateChange, editStateRef, onNodeClick: onNodeClickProp,
|
|
81
|
+
const GraphRendererInner = ({ configuration, nodes: propNodes, edges: propEdges, violations = [], configName: _configName, showMinimap = false, showControls = true, showBackground = true, backgroundVariant = 'lines', backgroundGap, showCenterIndicator = false, showTooltips = true, fitViewDuration = 200, highlightedNodeId, activeNodeIds, events = [], onEventProcessed, editable = false, onPendingChangesChange, onEditStateChange, editStateRef, onNodeClick: onNodeClickProp, fitViewToNodeIds, fitViewPadding = 0.2, }) => {
|
|
84
82
|
const { fitView, fitBounds, getNodes } = useReactFlow();
|
|
85
83
|
const updateNodeInternals = useUpdateNodeInternals();
|
|
86
84
|
const { theme } = useTheme();
|
|
@@ -118,21 +116,16 @@ const GraphRendererInner = ({ configuration, nodes: propNodes, edges: propEdges,
|
|
|
118
116
|
const [selectedEdgeIds, setSelectedEdgeIds] = useState(new Set());
|
|
119
117
|
// Track selected nodes for info panel (supports multi-select)
|
|
120
118
|
const [selectedNodeIds, setSelectedNodeIds] = useState(new Set());
|
|
121
|
-
// Track whether panel should be shown (only on explicit clicks, not after dragging)
|
|
122
|
-
const [showNodePanel, setShowNodePanel] = useState(false);
|
|
123
|
-
const [showEdgePanel, setShowEdgePanel] = useState(false);
|
|
124
119
|
// Track hidden nodes (shift-click to toggle)
|
|
125
120
|
const [hiddenNodeIds, setHiddenNodeIds] = useState(new Set());
|
|
126
121
|
// Track pending connection for edge type picker
|
|
127
122
|
const [pendingConnection, setPendingConnection] = useState(null);
|
|
128
|
-
// Sync highlightedNodeId to selection state
|
|
123
|
+
// Sync highlightedNodeId to selection state
|
|
129
124
|
useEffect(() => {
|
|
130
125
|
if (highlightedNodeId) {
|
|
131
126
|
setSelectedNodeIds(new Set([highlightedNodeId]));
|
|
132
|
-
setShowNodePanel(true);
|
|
133
127
|
// Clear edge selection when highlighting a node
|
|
134
128
|
setSelectedEdgeIds(new Set());
|
|
135
|
-
setShowEdgePanel(false);
|
|
136
129
|
}
|
|
137
130
|
}, [highlightedNodeId]);
|
|
138
131
|
// ============================================
|
|
@@ -289,21 +282,17 @@ const GraphRendererInner = ({ configuration, nodes: propNodes, edges: propEdges,
|
|
|
289
282
|
}
|
|
290
283
|
return next;
|
|
291
284
|
});
|
|
292
|
-
setShowEdgePanel(true);
|
|
293
285
|
}
|
|
294
286
|
else {
|
|
295
287
|
// Regular click: single select (replace selection)
|
|
296
288
|
const shouldDeselect = selectedEdgeIds.size === 1 && selectedEdgeIds.has(edge.id);
|
|
297
289
|
if (shouldDeselect) {
|
|
298
290
|
setSelectedEdgeIds(new Set());
|
|
299
|
-
setShowEdgePanel(false);
|
|
300
291
|
}
|
|
301
292
|
else {
|
|
302
293
|
setSelectedEdgeIds(new Set([edge.id]));
|
|
303
|
-
setShowEdgePanel(true);
|
|
304
294
|
}
|
|
305
295
|
setSelectedNodeIds(new Set());
|
|
306
|
-
setShowNodePanel(false);
|
|
307
296
|
}
|
|
308
297
|
}, [editable, selectedEdgeIds]);
|
|
309
298
|
// Handle node click (toggle selection, supports Cmd/Ctrl for hide/dim)
|
|
@@ -342,33 +331,24 @@ const GraphRendererInner = ({ configuration, nodes: propNodes, edges: propEdges,
|
|
|
342
331
|
else {
|
|
343
332
|
next.add(node.id);
|
|
344
333
|
}
|
|
345
|
-
//
|
|
334
|
+
// Update local nodes selection state immediately for edit mode
|
|
346
335
|
if (editable) {
|
|
347
336
|
setXyflowLocalNodes((nodes) => nodes.map((n) => ({
|
|
348
337
|
...n,
|
|
349
|
-
selected:
|
|
338
|
+
selected: next.has(n.id),
|
|
350
339
|
})));
|
|
351
340
|
}
|
|
352
341
|
return next;
|
|
353
342
|
});
|
|
354
343
|
return;
|
|
355
344
|
}
|
|
356
|
-
// Determine if we should show the panel based on showNodeDetailPanel prop
|
|
357
|
-
const shouldShowPanel = showNodeDetailPanel !== false && (showNodeDetailPanel === true || !onNodeClickProp);
|
|
358
|
-
// If custom node click handler is provided, call it
|
|
359
|
-
if (onNodeClickProp) {
|
|
360
|
-
onNodeClickProp(node.id, event);
|
|
361
|
-
// If showNodeDetailPanel is not explicitly true, return early (old behavior)
|
|
362
|
-
if (showNodeDetailPanel !== true) {
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
345
|
// Regular click: single select (replace selection)
|
|
346
|
+
event.preventDefault();
|
|
347
|
+
event.stopPropagation();
|
|
367
348
|
const shouldDeselect = selectedNodeIds.size === 1 && selectedNodeIds.has(node.id);
|
|
368
349
|
if (shouldDeselect) {
|
|
369
350
|
setSelectedNodeIds(new Set());
|
|
370
|
-
|
|
371
|
-
// Also update local nodes selection state immediately
|
|
351
|
+
// Update local nodes selection state immediately for edit mode
|
|
372
352
|
if (editable) {
|
|
373
353
|
setXyflowLocalNodes((nodes) => nodes.map((n) => ({
|
|
374
354
|
...n,
|
|
@@ -378,10 +358,7 @@ const GraphRendererInner = ({ configuration, nodes: propNodes, edges: propEdges,
|
|
|
378
358
|
}
|
|
379
359
|
else {
|
|
380
360
|
setSelectedNodeIds(new Set([node.id]));
|
|
381
|
-
|
|
382
|
-
setShowNodePanel(true);
|
|
383
|
-
}
|
|
384
|
-
// Also update local nodes selection state immediately
|
|
361
|
+
// Update local nodes selection state immediately for edit mode
|
|
385
362
|
if (editable) {
|
|
386
363
|
setXyflowLocalNodes((nodes) => nodes.map((n) => ({
|
|
387
364
|
...n,
|
|
@@ -390,152 +367,64 @@ const GraphRendererInner = ({ configuration, nodes: propNodes, edges: propEdges,
|
|
|
390
367
|
}
|
|
391
368
|
}
|
|
392
369
|
setSelectedEdgeIds(new Set());
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
setShowEdgePanel(false);
|
|
399
|
-
}, []);
|
|
400
|
-
// Handle edge side updates from EdgeInfoPanel
|
|
401
|
-
const handleUpdateEdgeSides = useCallback((edgeId, fromSide, toSide) => {
|
|
402
|
-
setLocalEdges((currentEdges) => currentEdges.map((edge) => edge.id === edgeId
|
|
403
|
-
? {
|
|
404
|
-
...edge,
|
|
405
|
-
data: {
|
|
406
|
-
...edge.data,
|
|
407
|
-
fromSide,
|
|
408
|
-
toSide,
|
|
409
|
-
},
|
|
410
|
-
}
|
|
411
|
-
: edge));
|
|
412
|
-
}, []);
|
|
413
|
-
// Handle close node info panel
|
|
414
|
-
const onCloseNodeInfoPanel = useCallback(() => {
|
|
415
|
-
setSelectedNodeIds(new Set());
|
|
416
|
-
setShowNodePanel(false);
|
|
417
|
-
}, []);
|
|
370
|
+
// If custom node click handler is provided, call it after selection is updated
|
|
371
|
+
if (onNodeClickProp) {
|
|
372
|
+
onNodeClickProp(node.id, event);
|
|
373
|
+
}
|
|
374
|
+
}, [selectedNodeIds, onNodeClickProp, editable]);
|
|
418
375
|
// Handle pane click (clear selection when clicking empty space)
|
|
419
376
|
const onPaneClick = useCallback(() => {
|
|
420
377
|
setSelectedNodeIds(new Set());
|
|
421
378
|
setSelectedEdgeIds(new Set());
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
379
|
+
// In edit mode, also update local nodes selection state
|
|
380
|
+
if (editable) {
|
|
381
|
+
setXyflowLocalNodes((nodes) => nodes.map((n) => ({
|
|
382
|
+
...n,
|
|
383
|
+
selected: false,
|
|
384
|
+
})));
|
|
385
|
+
}
|
|
386
|
+
}, [editable]);
|
|
425
387
|
// Handle selection change from ReactFlow (box selection and clicks)
|
|
426
388
|
const handleSelectionChange = useCallback(({ nodes: selectedNodes, edges: selectedEdges }) => {
|
|
427
389
|
// Ignore selection changes when we're hiding a node
|
|
428
390
|
if (hidingNodeRef.current) {
|
|
429
391
|
return;
|
|
430
392
|
}
|
|
431
|
-
//
|
|
432
|
-
|
|
433
|
-
setSelectedEdgeIds(new Set(selectedEdges.map((e) => e.id)));
|
|
434
|
-
// Only show panels in edit mode or when explicitly enabled
|
|
393
|
+
// In edit mode, we manage selection ourselves via onNodeClick
|
|
394
|
+
// Skip handleSelectionChange to avoid ReactFlow overwriting our selection state
|
|
435
395
|
if (editable) {
|
|
436
|
-
|
|
437
|
-
if (selectedNodes.length > 0) {
|
|
438
|
-
setShowNodePanel(true);
|
|
439
|
-
}
|
|
440
|
-
if (selectedEdges.length > 0) {
|
|
441
|
-
setShowEdgePanel(true);
|
|
442
|
-
}
|
|
396
|
+
return;
|
|
443
397
|
}
|
|
398
|
+
const newSelectedNodeIds = new Set(selectedNodes.map((n) => n.id));
|
|
399
|
+
// Update selection state for read-only mode (for visual feedback)
|
|
400
|
+
setSelectedNodeIds(newSelectedNodeIds);
|
|
401
|
+
setSelectedEdgeIds(new Set(selectedEdges.map((e) => e.id)));
|
|
444
402
|
}, [editable]);
|
|
445
|
-
//
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
updateEditState((prev) => {
|
|
462
|
-
const newUpdates = new Map(prev.nodeUpdates);
|
|
463
|
-
const existing = newUpdates.get(nodeId) || {};
|
|
464
|
-
newUpdates.set(nodeId, {
|
|
465
|
-
type: updates.type ?? existing.type,
|
|
466
|
-
data: updates.data ? { ...existing.data, ...updates.data } : existing.data,
|
|
467
|
-
});
|
|
468
|
-
return { ...prev, nodeUpdates: newUpdates };
|
|
469
|
-
});
|
|
470
|
-
}, [editable, updateEditState]);
|
|
471
|
-
// Handle node delete (internal)
|
|
472
|
-
const handleNodeDelete = useCallback((nodeId) => {
|
|
473
|
-
if (!editable)
|
|
474
|
-
return;
|
|
475
|
-
// Remove from local state
|
|
476
|
-
setLocalNodes((prev) => prev.filter((n) => n.id !== nodeId));
|
|
477
|
-
setLocalEdges((prev) => prev.filter((e) => e.from !== nodeId && e.to !== nodeId));
|
|
478
|
-
// Track the change
|
|
479
|
-
updateEditState((prev) => {
|
|
480
|
-
const newDeletedNodes = new Set(prev.deletedNodeIds);
|
|
481
|
-
newDeletedNodes.add(nodeId);
|
|
482
|
-
// Remove any pending updates for this node
|
|
483
|
-
const newUpdates = new Map(prev.nodeUpdates);
|
|
484
|
-
newUpdates.delete(nodeId);
|
|
485
|
-
// Remove any position changes for this node
|
|
486
|
-
const newPositions = new Map(prev.positionChanges);
|
|
487
|
-
newPositions.delete(nodeId);
|
|
488
|
-
// Remove any dimension changes for this node
|
|
489
|
-
const newDimensions = new Map(prev.dimensionChanges);
|
|
490
|
-
newDimensions.delete(nodeId);
|
|
491
|
-
// Remove created edges that involve this node
|
|
492
|
-
const newCreatedEdges = prev.createdEdges.filter((e) => e.from !== nodeId && e.to !== nodeId);
|
|
493
|
-
return {
|
|
494
|
-
...prev,
|
|
495
|
-
deletedNodeIds: newDeletedNodes,
|
|
496
|
-
nodeUpdates: newUpdates,
|
|
497
|
-
positionChanges: newPositions,
|
|
498
|
-
dimensionChanges: newDimensions,
|
|
499
|
-
createdEdges: newCreatedEdges,
|
|
500
|
-
};
|
|
501
|
-
});
|
|
502
|
-
setSelectedNodeIds(new Set());
|
|
503
|
-
}, [editable, updateEditState]);
|
|
504
|
-
// Handle edge delete (internal)
|
|
505
|
-
const handleEdgeDelete = useCallback((edgeId) => {
|
|
506
|
-
if (!editable)
|
|
507
|
-
return;
|
|
508
|
-
// Find the edge before removing it so we can track its full info
|
|
509
|
-
const edgeToDelete = localEdges.find((e) => e.id === edgeId);
|
|
510
|
-
// Remove from local state
|
|
511
|
-
setLocalEdges((prev) => prev.filter((e) => e.id !== edgeId));
|
|
403
|
+
// Create edge helper
|
|
404
|
+
const createEdge = useCallback((from, to, type, sourceHandle, targetHandle) => {
|
|
405
|
+
const edgeId = `${from}-${to}-${type}-${Date.now()}`;
|
|
406
|
+
// Add to local state with handle information
|
|
407
|
+
const newEdge = {
|
|
408
|
+
id: edgeId,
|
|
409
|
+
type,
|
|
410
|
+
from,
|
|
411
|
+
to,
|
|
412
|
+
data: {},
|
|
413
|
+
createdAt: Date.now(),
|
|
414
|
+
updatedAt: Date.now(),
|
|
415
|
+
sourceHandle,
|
|
416
|
+
targetHandle,
|
|
417
|
+
};
|
|
418
|
+
setLocalEdges((prev) => [...prev, newEdge]);
|
|
512
419
|
// Track the change
|
|
513
|
-
updateEditState((prev) => {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
}
|
|
522
|
-
// Otherwise mark as deleted with full edge info
|
|
523
|
-
if (edgeToDelete) {
|
|
524
|
-
const newDeletedEdges = [
|
|
525
|
-
...prev.deletedEdges,
|
|
526
|
-
{
|
|
527
|
-
id: edgeId,
|
|
528
|
-
from: edgeToDelete.from,
|
|
529
|
-
to: edgeToDelete.to,
|
|
530
|
-
type: edgeToDelete.type,
|
|
531
|
-
},
|
|
532
|
-
];
|
|
533
|
-
return { ...prev, deletedEdges: newDeletedEdges };
|
|
534
|
-
}
|
|
535
|
-
return prev;
|
|
536
|
-
});
|
|
537
|
-
setSelectedEdgeIds(new Set());
|
|
538
|
-
}, [editable, updateEditState, localEdges]);
|
|
420
|
+
updateEditState((prev) => ({
|
|
421
|
+
...prev,
|
|
422
|
+
createdEdges: [
|
|
423
|
+
...prev.createdEdges,
|
|
424
|
+
{ id: edgeId, from, to, type, sourceHandle, targetHandle },
|
|
425
|
+
],
|
|
426
|
+
}));
|
|
427
|
+
}, [updateEditState]);
|
|
539
428
|
// Handle new connection from drag
|
|
540
429
|
const handleConnect = useCallback((connection) => {
|
|
541
430
|
if (!editable || !connection.source || !connection.target)
|
|
@@ -568,32 +457,7 @@ const GraphRendererInner = ({ configuration, nodes: propNodes, edges: propEdges,
|
|
|
568
457
|
validTypes: uniqueTypes,
|
|
569
458
|
});
|
|
570
459
|
}
|
|
571
|
-
}, [editable, nodes, configuration.allowedConnections]);
|
|
572
|
-
// Create edge helper
|
|
573
|
-
const createEdge = useCallback((from, to, type, sourceHandle, targetHandle) => {
|
|
574
|
-
const edgeId = `${from}-${to}-${type}-${Date.now()}`;
|
|
575
|
-
// Add to local state with handle information
|
|
576
|
-
const newEdge = {
|
|
577
|
-
id: edgeId,
|
|
578
|
-
type,
|
|
579
|
-
from,
|
|
580
|
-
to,
|
|
581
|
-
data: {},
|
|
582
|
-
createdAt: Date.now(),
|
|
583
|
-
updatedAt: Date.now(),
|
|
584
|
-
sourceHandle,
|
|
585
|
-
targetHandle,
|
|
586
|
-
};
|
|
587
|
-
setLocalEdges((prev) => [...prev, newEdge]);
|
|
588
|
-
// Track the change
|
|
589
|
-
updateEditState((prev) => ({
|
|
590
|
-
...prev,
|
|
591
|
-
createdEdges: [
|
|
592
|
-
...prev.createdEdges,
|
|
593
|
-
{ id: edgeId, from, to, type, sourceHandle, targetHandle },
|
|
594
|
-
],
|
|
595
|
-
}));
|
|
596
|
-
}, [updateEditState]);
|
|
460
|
+
}, [editable, nodes, configuration.allowedConnections, createEdge]);
|
|
597
461
|
// Handle edge type selection from picker
|
|
598
462
|
const handleEdgeTypeSelect = useCallback((type) => {
|
|
599
463
|
if (!pendingConnection)
|
|
@@ -731,37 +595,6 @@ const GraphRendererInner = ({ configuration, nodes: propNodes, edges: propEdges,
|
|
|
731
595
|
// SELECTED ITEMS
|
|
732
596
|
// ============================================
|
|
733
597
|
// Get first selected edge (for single-selection info panel)
|
|
734
|
-
const selectedEdgeId = useMemo(() => {
|
|
735
|
-
if (selectedEdgeIds.size === 0)
|
|
736
|
-
return null;
|
|
737
|
-
return selectedEdgeIds.values().next().value;
|
|
738
|
-
}, [selectedEdgeIds]);
|
|
739
|
-
const selectedEdge = useMemo(() => {
|
|
740
|
-
if (!selectedEdgeId)
|
|
741
|
-
return null;
|
|
742
|
-
return edges.find((e) => e.id === selectedEdgeId);
|
|
743
|
-
}, [selectedEdgeId, edges]);
|
|
744
|
-
const selectedEdgeTypeDefinition = useMemo(() => {
|
|
745
|
-
if (!selectedEdge)
|
|
746
|
-
return null;
|
|
747
|
-
return configuration.edgeTypes[selectedEdge.type];
|
|
748
|
-
}, [selectedEdge, configuration.edgeTypes]);
|
|
749
|
-
// Get first selected node (for single-selection info panel)
|
|
750
|
-
const selectedNodeId = useMemo(() => {
|
|
751
|
-
if (selectedNodeIds.size === 0)
|
|
752
|
-
return null;
|
|
753
|
-
return selectedNodeIds.values().next().value;
|
|
754
|
-
}, [selectedNodeIds]);
|
|
755
|
-
const selectedNode = useMemo(() => {
|
|
756
|
-
if (!selectedNodeId)
|
|
757
|
-
return null;
|
|
758
|
-
return nodes.find((n) => n.id === selectedNodeId);
|
|
759
|
-
}, [selectedNodeId, nodes]);
|
|
760
|
-
const selectedNodeTypeDefinition = useMemo(() => {
|
|
761
|
-
if (!selectedNode)
|
|
762
|
-
return null;
|
|
763
|
-
return configuration.nodeTypes[selectedNode.type];
|
|
764
|
-
}, [selectedNode, configuration.nodeTypes]);
|
|
765
598
|
// ============================================
|
|
766
599
|
// ANIMATIONS
|
|
767
600
|
// ============================================
|
|
@@ -902,7 +735,7 @@ const GraphRendererInner = ({ configuration, nodes: propNodes, edges: propEdges,
|
|
|
902
735
|
}, [edges]);
|
|
903
736
|
// Local xyflow nodes state for dragging
|
|
904
737
|
const [xyflowLocalNodes, setXyflowLocalNodes] = useState(xyflowNodesBase);
|
|
905
|
-
// Sync when base
|
|
738
|
+
// Sync when base node IDs change
|
|
906
739
|
const prevBaseNodesKeyRef = useRef(baseNodesKey);
|
|
907
740
|
useEffect(() => {
|
|
908
741
|
if (prevBaseNodesKeyRef.current !== baseNodesKey) {
|
|
@@ -910,6 +743,20 @@ const GraphRendererInner = ({ configuration, nodes: propNodes, edges: propEdges,
|
|
|
910
743
|
setXyflowLocalNodes(xyflowNodesBase);
|
|
911
744
|
}
|
|
912
745
|
}, [baseNodesKey, xyflowNodesBase]);
|
|
746
|
+
// Sync selection state to local nodes when selectedNodeIds changes (edit mode only)
|
|
747
|
+
// Use useLayoutEffect to ensure sync happens before paint (prevents flash)
|
|
748
|
+
const prevSelectedNodeIdsRef = useRef(selectedNodeIds);
|
|
749
|
+
useLayoutEffect(() => {
|
|
750
|
+
if (!editable)
|
|
751
|
+
return;
|
|
752
|
+
if (prevSelectedNodeIdsRef.current === selectedNodeIds)
|
|
753
|
+
return;
|
|
754
|
+
prevSelectedNodeIdsRef.current = selectedNodeIds;
|
|
755
|
+
setXyflowLocalNodes((localNodes) => localNodes.map((localNode) => ({
|
|
756
|
+
...localNode,
|
|
757
|
+
selected: selectedNodeIds.has(localNode.id),
|
|
758
|
+
})));
|
|
759
|
+
}, [editable, selectedNodeIds]);
|
|
913
760
|
// Also sync when entering edit mode or when base nodes change content
|
|
914
761
|
const prevEditableRef = useRef(editable);
|
|
915
762
|
useEffect(() => {
|
|
@@ -942,14 +789,19 @@ const GraphRendererInner = ({ configuration, nodes: propNodes, edges: propEdges,
|
|
|
942
789
|
const handleNodesChange = useCallback((changes) => {
|
|
943
790
|
if (!editable)
|
|
944
791
|
return;
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
792
|
+
// Filter out selection changes - we manage selection ourselves via selectedNodeIds
|
|
793
|
+
const nonSelectionChanges = changes.filter((change) => change.type !== 'select');
|
|
794
|
+
// Only apply changes if there are non-selection changes to apply
|
|
795
|
+
if (nonSelectionChanges.length > 0) {
|
|
796
|
+
setXyflowLocalNodes((nds) => {
|
|
797
|
+
// Apply changes but preserve our selection state
|
|
798
|
+
const updated = applyNodeChanges(nonSelectionChanges, nds);
|
|
799
|
+
// Restore selection state from our managed selectedNodeIds
|
|
800
|
+
return updated.map((node) => ({
|
|
801
|
+
...node,
|
|
802
|
+
selected: selectedNodeIds.has(node.id),
|
|
803
|
+
}));
|
|
804
|
+
});
|
|
953
805
|
}
|
|
954
806
|
// Track position changes on drag end
|
|
955
807
|
const positionChanges = changes.filter((change) => change.type === 'position' &&
|
|
@@ -963,22 +815,6 @@ const GraphRendererInner = ({ configuration, nodes: propNodes, edges: propEdges,
|
|
|
963
815
|
change.dimensions !== undefined &&
|
|
964
816
|
'resizing' in change &&
|
|
965
817
|
change.resizing === false);
|
|
966
|
-
// Debug logging for dimension changes
|
|
967
|
-
if (process.env.NODE_ENV === 'development') {
|
|
968
|
-
const allDimensionChanges = changes.filter(c => c.type === 'dimensions');
|
|
969
|
-
if (allDimensionChanges.length > 0) {
|
|
970
|
-
console.log('[GraphRenderer] Dimension changes detected:', allDimensionChanges.map(c => ({
|
|
971
|
-
// @ts-expect-error - accessing properties for debug
|
|
972
|
-
id: c.id,
|
|
973
|
-
// @ts-expect-error - accessing properties for debug
|
|
974
|
-
dimensions: c.dimensions,
|
|
975
|
-
// @ts-expect-error - accessing properties for debug
|
|
976
|
-
resizing: c.resizing,
|
|
977
|
-
// @ts-expect-error - accessing properties for debug
|
|
978
|
-
isGroup: nodes.find(n => n.id === c.id)?.data?.canvasType === 'group'
|
|
979
|
-
})));
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
818
|
if (dimensionChanges.length > 0) {
|
|
983
819
|
updateEditState((prev) => {
|
|
984
820
|
const newDimensions = new Map(prev.dimensionChanges);
|
|
@@ -1005,7 +841,7 @@ const GraphRendererInner = ({ configuration, nodes: propNodes, edges: propEdges,
|
|
|
1005
841
|
return { ...prev, positionChanges: newPositions };
|
|
1006
842
|
});
|
|
1007
843
|
}
|
|
1008
|
-
}, [editable, updateEditState]);
|
|
844
|
+
}, [editable, updateEditState, selectedNodeIds]);
|
|
1009
845
|
const xyflowEdgesBase = useMemo(() => {
|
|
1010
846
|
const converted = convertToXYFlowEdges(edges, configuration, violations);
|
|
1011
847
|
// Filter out edges connected to inactive or hidden nodes
|
|
@@ -1024,17 +860,6 @@ const GraphRendererInner = ({ configuration, nodes: propNodes, edges: propEdges,
|
|
|
1024
860
|
}
|
|
1025
861
|
return true;
|
|
1026
862
|
});
|
|
1027
|
-
// Debug: Log edge counts to help diagnose disappearing edges
|
|
1028
|
-
if (process.env.NODE_ENV === 'development') {
|
|
1029
|
-
console.log('[GraphRenderer] xyflowEdges computed:', {
|
|
1030
|
-
inputEdges: edges.length,
|
|
1031
|
-
convertedEdges: converted.length,
|
|
1032
|
-
filteredEdges: filtered.length,
|
|
1033
|
-
editable,
|
|
1034
|
-
propEdgesCount: propEdges.length,
|
|
1035
|
-
localEdgesCount: localEdges.length,
|
|
1036
|
-
});
|
|
1037
|
-
}
|
|
1038
863
|
const mappedEdges = filtered.map((edge) => {
|
|
1039
864
|
const animation = animationState.edgeAnimations[edge.id];
|
|
1040
865
|
const isSelected = selectedEdgeIds.has(edge.id);
|
|
@@ -1139,18 +964,18 @@ const GraphRendererInner = ({ configuration, nodes: propNodes, edges: propEdges,
|
|
|
1139
964
|
}
|
|
1140
965
|
}, 150);
|
|
1141
966
|
return () => clearTimeout(timeoutId);
|
|
1142
|
-
}, [fitViewToNodeIdsKey, fitViewToNodeIds, fitViewPadding, fitView, fitViewDuration, getNodes]);
|
|
967
|
+
}, [fitViewToNodeIdsKey, fitViewToNodeIds, fitViewPadding, fitView, fitViewDuration, getNodes, fitBounds]);
|
|
1143
968
|
// ============================================
|
|
1144
969
|
// RENDER
|
|
1145
970
|
// ============================================
|
|
1146
|
-
return (_jsxs(_Fragment, { children: [_jsxs(ReactFlow, { nodes: xyflowNodes, edges: xyflowEdges, nodeTypes: nodeTypes, edgeTypes: edgeTypes, minZoom: 0.1, maxZoom: 4, defaultEdgeOptions: { type: 'custom' }, onEdgeClick: onEdgeClick, onNodeClick: onNodeClick, onNodeDrag: handleNodeDrag, onNodeDragStop: handleNodeDragStop, proOptions: { hideAttribution: true }, nodesDraggable: editable, elementsSelectable:
|
|
971
|
+
return (_jsxs(_Fragment, { children: [_jsxs(ReactFlow, { nodes: xyflowNodes, edges: xyflowEdges, nodeTypes: nodeTypes, edgeTypes: edgeTypes, minZoom: 0.1, maxZoom: 4, defaultEdgeOptions: { type: 'custom' }, onEdgeClick: onEdgeClick, onNodeClick: onNodeClick, onNodeDrag: handleNodeDrag, onNodeDragStop: handleNodeDragStop, proOptions: { hideAttribution: true }, nodesDraggable: editable, elementsSelectable: editable, selectNodesOnDrag: false, nodesConnectable: editable, edgesReconnectable: editable, reconnectRadius: 25, elevateEdgesOnSelect: true, onNodesChange: handleNodesChange, onEdgesChange: handleEdgesChange, onConnect: handleConnect, onReconnectStart: handleReconnectStart, onReconnect: handleReconnect, onReconnectEnd: handleReconnectEnd, onPaneClick: onPaneClick, onSelectionChange: handleSelectionChange, panOnDrag: !editable, panOnScroll: true, zoomOnScroll: false, zoomOnPinch: true, selectionOnDrag: false, selectionKeyCode: null, multiSelectionKeyCode: "Shift", children: [showBackground && (_jsx(Background, { color: backgroundVariant === 'dots' ? theme.colors.border : theme.colors.textMuted, gap: backgroundGap ?? (backgroundVariant === 'dots' ? 16 : 50), size: backgroundVariant === 'dots' ? 1 : 0.5, variant: backgroundVariant === 'dots'
|
|
1147
972
|
? BackgroundVariant.Dots
|
|
1148
973
|
: backgroundVariant === 'lines'
|
|
1149
974
|
? BackgroundVariant.Lines
|
|
1150
975
|
: BackgroundVariant.Cross })), showControls && _jsx(Controls, { showZoom: true, showFitView: true, showInteractive: true }), showMinimap && (_jsx(MiniMap, { nodeColor: (node) => {
|
|
1151
976
|
const nodeData = node.data;
|
|
1152
977
|
return nodeData?.typeDefinition?.color || theme.colors.secondary;
|
|
1153
|
-
}, nodeBorderRadius: 2, pannable: true, zoomable: true })), showCenterIndicator && _jsx(CenterIndicator, { color: theme.colors.textMuted }), editable && alignmentGuides.length > 0 && (_jsx(AlignmentGuidesOverlay, { guides: alignmentGuides, color: theme.colors.primary }))] }, `${baseNodesKey}-${baseEdgesKey}`),
|
|
978
|
+
}, nodeBorderRadius: 2, pannable: true, zoomable: true })), showCenterIndicator && _jsx(CenterIndicator, { color: theme.colors.textMuted }), editable && alignmentGuides.length > 0 && (_jsx(AlignmentGuidesOverlay, { guides: alignmentGuides, color: theme.colors.primary }))] }, `${baseNodesKey}-${baseEdgesKey}`), pendingConnection && (_jsxs("div", { style: {
|
|
1154
979
|
position: 'absolute',
|
|
1155
980
|
top: '50%',
|
|
1156
981
|
left: '50%',
|
|
@@ -1423,8 +1248,8 @@ export const GraphRenderer = forwardRef((props, ref) => {
|
|
|
1423
1248
|
}
|
|
1424
1249
|
const { configuration, nodes, edges } = canvasData;
|
|
1425
1250
|
// Extract only the props that inner component needs
|
|
1426
|
-
const { violations, configName, showMinimap, showControls, showBackground, backgroundVariant, backgroundGap, showCenterIndicator, showTooltips, fitViewDuration, highlightedNodeId, activeNodeIds, events, onEventProcessed, editable, onPendingChangesChange, onNodeClick,
|
|
1427
|
-
return (_jsx("div", { className: className, style: { width, height, position: 'relative' }, children: _jsx(ReactFlowProvider, { children: _jsx(GraphRendererInner, { configuration: configuration, nodes: nodes, edges: edges, violations: violations, configName: configName, showMinimap: showMinimap, showControls: showControls, showBackground: showBackground, backgroundVariant: backgroundVariant, backgroundGap: backgroundGap, showCenterIndicator: showCenterIndicator, showTooltips: showTooltips, fitViewDuration: fitViewDuration, highlightedNodeId: highlightedNodeId, activeNodeIds: activeNodeIds, events: events, onEventProcessed: onEventProcessed, editable: editable, onPendingChangesChange: onPendingChangesChange, editStateRef: editStateRef, onNodeClick: onNodeClick,
|
|
1251
|
+
const { violations, configName, showMinimap, showControls, showBackground, backgroundVariant, backgroundGap, showCenterIndicator, showTooltips, fitViewDuration, highlightedNodeId, activeNodeIds, events, onEventProcessed, editable, onPendingChangesChange, onNodeClick, fitViewToNodeIds, fitViewPadding, } = props;
|
|
1252
|
+
return (_jsx("div", { className: className, style: { width, height, position: 'relative' }, children: _jsx(ReactFlowProvider, { children: _jsx(GraphRendererInner, { configuration: configuration, nodes: nodes, edges: edges, violations: violations, configName: configName, showMinimap: showMinimap, showControls: showControls, showBackground: showBackground, backgroundVariant: backgroundVariant, backgroundGap: backgroundGap, showCenterIndicator: showCenterIndicator, showTooltips: showTooltips, fitViewDuration: fitViewDuration, highlightedNodeId: highlightedNodeId, activeNodeIds: activeNodeIds, events: events, onEventProcessed: onEventProcessed, editable: editable, onPendingChangesChange: onPendingChangesChange, editStateRef: editStateRef, onNodeClick: onNodeClick, fitViewToNodeIds: fitViewToNodeIds, fitViewPadding: fitViewPadding }) }) }));
|
|
1428
1253
|
});
|
|
1429
1254
|
GraphRenderer.displayName = 'GraphRenderer';
|
|
1430
1255
|
//# sourceMappingURL=GraphRenderer.js.map
|