@principal-ai/principal-view-react 0.13.6 → 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 +256 -1
- 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
|
@@ -2,6 +2,7 @@ import React, {
|
|
|
2
2
|
useMemo,
|
|
3
3
|
useState,
|
|
4
4
|
useEffect,
|
|
5
|
+
useLayoutEffect,
|
|
5
6
|
useCallback,
|
|
6
7
|
useRef,
|
|
7
8
|
useImperativeHandle,
|
|
@@ -21,8 +22,10 @@ import {
|
|
|
21
22
|
applyEdgeChanges,
|
|
22
23
|
type Edge,
|
|
23
24
|
type EdgeChange,
|
|
25
|
+
type EdgeTypes,
|
|
24
26
|
type NodeChange,
|
|
25
27
|
type Node,
|
|
28
|
+
type NodeTypes,
|
|
26
29
|
type Connection,
|
|
27
30
|
} from '@xyflow/react';
|
|
28
31
|
import '@xyflow/react/dist/style.css';
|
|
@@ -37,8 +40,6 @@ import type {
|
|
|
37
40
|
StateEvent,
|
|
38
41
|
ExtendedCanvas,
|
|
39
42
|
ComponentLibrary,
|
|
40
|
-
JsonValue,
|
|
41
|
-
PVEventSchema,
|
|
42
43
|
} from '@principal-ai/principal-view-core';
|
|
43
44
|
import { CanvasConverter } from '@principal-ai/principal-view-core';
|
|
44
45
|
import { useTheme } from '@principal-ade/industry-theme';
|
|
@@ -50,8 +51,6 @@ import {
|
|
|
50
51
|
convertToXYFlowNodes,
|
|
51
52
|
convertToXYFlowEdges,
|
|
52
53
|
} from '../utils/graphConverter';
|
|
53
|
-
import { EdgeInfoPanel } from './EdgeInfoPanel';
|
|
54
|
-
import { NodeInfoPanel } from './NodeInfoPanel';
|
|
55
54
|
|
|
56
55
|
/** Position change event for tracking node movements */
|
|
57
56
|
export interface NodePositionChange {
|
|
@@ -201,22 +200,6 @@ interface GraphRendererBaseProps {
|
|
|
201
200
|
*/
|
|
202
201
|
onNodeClick?: (nodeId: string, event: React.MouseEvent) => void;
|
|
203
202
|
|
|
204
|
-
/**
|
|
205
|
-
* Whether to show the node detail panel when nodes are clicked.
|
|
206
|
-
* Defaults to undefined (auto), which shows panel only when onNodeClick is not provided.
|
|
207
|
-
* Set to true to force showing panel even with custom onNodeClick handler.
|
|
208
|
-
* Set to false to hide panel completely.
|
|
209
|
-
*/
|
|
210
|
-
showNodeDetailPanel?: boolean;
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Optional callback to resolve event references to full event schemas.
|
|
214
|
-
* When a node has an eventRef (string reference like "order.completed"),
|
|
215
|
-
* this function is called to retrieve the full event definition.
|
|
216
|
-
* Return undefined if the event reference cannot be resolved.
|
|
217
|
-
*/
|
|
218
|
-
resolveEventRef?: (eventRef: string) => PVEventSchema | undefined;
|
|
219
|
-
|
|
220
203
|
/**
|
|
221
204
|
* When set, fits the viewport to show these specific nodes.
|
|
222
205
|
* Useful for focusing on a subset of the graph (e.g., scenario nodes).
|
|
@@ -246,14 +229,14 @@ export interface GraphRendererProps extends GraphRendererBaseProps {
|
|
|
246
229
|
}
|
|
247
230
|
|
|
248
231
|
// Define custom node types
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
};
|
|
232
|
+
// Type assertion needed because ReactFlow's NodeTypes expects generic components
|
|
233
|
+
// but CustomNode is properly typed with CustomNodeData for type safety internally
|
|
234
|
+
const nodeTypes = { custom: CustomNode } as NodeTypes;
|
|
252
235
|
|
|
253
236
|
// Define custom edge types
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
};
|
|
237
|
+
// Type assertion needed because ReactFlow's EdgeTypes expects generic components
|
|
238
|
+
// but CustomEdge is properly typed with CustomEdgeData for type safety internally
|
|
239
|
+
const edgeTypes = { custom: CustomEdge } as EdgeTypes;
|
|
257
240
|
|
|
258
241
|
// Animation state for nodes and edges
|
|
259
242
|
interface AnimationState {
|
|
@@ -455,8 +438,6 @@ interface GraphRendererInnerProps {
|
|
|
455
438
|
onEditStateChange?: (editState: EditState) => void;
|
|
456
439
|
editStateRef: React.MutableRefObject<EditState>;
|
|
457
440
|
onNodeClick?: (nodeId: string, event: React.MouseEvent) => void;
|
|
458
|
-
showNodeDetailPanel?: boolean;
|
|
459
|
-
resolveEventRef?: (eventRef: string) => PVEventSchema | undefined;
|
|
460
441
|
fitViewToNodeIds?: string[] | null;
|
|
461
442
|
fitViewPadding?: number;
|
|
462
443
|
}
|
|
@@ -487,8 +468,6 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
487
468
|
onEditStateChange,
|
|
488
469
|
editStateRef,
|
|
489
470
|
onNodeClick: onNodeClickProp,
|
|
490
|
-
showNodeDetailPanel,
|
|
491
|
-
resolveEventRef,
|
|
492
471
|
fitViewToNodeIds,
|
|
493
472
|
fitViewPadding = 0.2,
|
|
494
473
|
}) => {
|
|
@@ -539,9 +518,6 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
539
518
|
|
|
540
519
|
// Track selected nodes for info panel (supports multi-select)
|
|
541
520
|
const [selectedNodeIds, setSelectedNodeIds] = useState<Set<string>>(new Set());
|
|
542
|
-
// Track whether panel should be shown (only on explicit clicks, not after dragging)
|
|
543
|
-
const [showNodePanel, setShowNodePanel] = useState(false);
|
|
544
|
-
const [showEdgePanel, setShowEdgePanel] = useState(false);
|
|
545
521
|
|
|
546
522
|
// Track hidden nodes (shift-click to toggle)
|
|
547
523
|
const [hiddenNodeIds, setHiddenNodeIds] = useState<Set<string>>(new Set());
|
|
@@ -555,14 +531,12 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
555
531
|
validTypes: string[];
|
|
556
532
|
} | null>(null);
|
|
557
533
|
|
|
558
|
-
// Sync highlightedNodeId to selection state
|
|
534
|
+
// Sync highlightedNodeId to selection state
|
|
559
535
|
useEffect(() => {
|
|
560
536
|
if (highlightedNodeId) {
|
|
561
537
|
setSelectedNodeIds(new Set([highlightedNodeId]));
|
|
562
|
-
setShowNodePanel(true);
|
|
563
538
|
// Clear edge selection when highlighting a node
|
|
564
539
|
setSelectedEdgeIds(new Set());
|
|
565
|
-
setShowEdgePanel(false);
|
|
566
540
|
}
|
|
567
541
|
}, [highlightedNodeId]);
|
|
568
542
|
|
|
@@ -750,19 +724,15 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
750
724
|
}
|
|
751
725
|
return next;
|
|
752
726
|
});
|
|
753
|
-
setShowEdgePanel(true);
|
|
754
727
|
} else {
|
|
755
728
|
// Regular click: single select (replace selection)
|
|
756
729
|
const shouldDeselect = selectedEdgeIds.size === 1 && selectedEdgeIds.has(edge.id);
|
|
757
730
|
if (shouldDeselect) {
|
|
758
731
|
setSelectedEdgeIds(new Set());
|
|
759
|
-
setShowEdgePanel(false);
|
|
760
732
|
} else {
|
|
761
733
|
setSelectedEdgeIds(new Set([edge.id]));
|
|
762
|
-
setShowEdgePanel(true);
|
|
763
734
|
}
|
|
764
735
|
setSelectedNodeIds(new Set());
|
|
765
|
-
setShowNodePanel(false);
|
|
766
736
|
}
|
|
767
737
|
},
|
|
768
738
|
[editable, selectedEdgeIds]
|
|
@@ -810,12 +780,12 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
810
780
|
next.add(node.id);
|
|
811
781
|
}
|
|
812
782
|
|
|
813
|
-
//
|
|
783
|
+
// Update local nodes selection state immediately for edit mode
|
|
814
784
|
if (editable) {
|
|
815
785
|
setXyflowLocalNodes((nodes) =>
|
|
816
786
|
nodes.map((n) => ({
|
|
817
787
|
...n,
|
|
818
|
-
selected:
|
|
788
|
+
selected: next.has(n.id),
|
|
819
789
|
}))
|
|
820
790
|
);
|
|
821
791
|
}
|
|
@@ -825,25 +795,15 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
825
795
|
return;
|
|
826
796
|
}
|
|
827
797
|
|
|
828
|
-
// Determine if we should show the panel based on showNodeDetailPanel prop
|
|
829
|
-
const shouldShowPanel = showNodeDetailPanel !== false && (showNodeDetailPanel === true || !onNodeClickProp);
|
|
830
|
-
|
|
831
|
-
// If custom node click handler is provided, call it
|
|
832
|
-
if (onNodeClickProp) {
|
|
833
|
-
onNodeClickProp(node.id, event);
|
|
834
|
-
// If showNodeDetailPanel is not explicitly true, return early (old behavior)
|
|
835
|
-
if (showNodeDetailPanel !== true) {
|
|
836
|
-
return;
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
|
|
840
798
|
// Regular click: single select (replace selection)
|
|
799
|
+
event.preventDefault();
|
|
800
|
+
event.stopPropagation();
|
|
801
|
+
|
|
841
802
|
const shouldDeselect = selectedNodeIds.size === 1 && selectedNodeIds.has(node.id);
|
|
842
803
|
if (shouldDeselect) {
|
|
843
804
|
setSelectedNodeIds(new Set());
|
|
844
|
-
setShowNodePanel(false);
|
|
845
805
|
|
|
846
|
-
//
|
|
806
|
+
// Update local nodes selection state immediately for edit mode
|
|
847
807
|
if (editable) {
|
|
848
808
|
setXyflowLocalNodes((nodes) =>
|
|
849
809
|
nodes.map((n) => ({
|
|
@@ -854,11 +814,8 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
854
814
|
}
|
|
855
815
|
} else {
|
|
856
816
|
setSelectedNodeIds(new Set([node.id]));
|
|
857
|
-
if (shouldShowPanel) {
|
|
858
|
-
setShowNodePanel(true);
|
|
859
|
-
}
|
|
860
817
|
|
|
861
|
-
//
|
|
818
|
+
// Update local nodes selection state immediately for edit mode
|
|
862
819
|
if (editable) {
|
|
863
820
|
setXyflowLocalNodes((nodes) =>
|
|
864
821
|
nodes.map((n) => ({
|
|
@@ -869,48 +826,31 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
869
826
|
}
|
|
870
827
|
}
|
|
871
828
|
setSelectedEdgeIds(new Set());
|
|
872
|
-
|
|
829
|
+
|
|
830
|
+
// If custom node click handler is provided, call it after selection is updated
|
|
831
|
+
if (onNodeClickProp) {
|
|
832
|
+
onNodeClickProp(node.id, event);
|
|
833
|
+
}
|
|
873
834
|
},
|
|
874
|
-
[selectedNodeIds, onNodeClickProp,
|
|
835
|
+
[selectedNodeIds, onNodeClickProp, editable]
|
|
875
836
|
);
|
|
876
837
|
|
|
877
|
-
// Handle close edge info panel
|
|
878
|
-
const onCloseEdgeInfoPanel = useCallback(() => {
|
|
879
|
-
setSelectedEdgeIds(new Set());
|
|
880
|
-
setShowEdgePanel(false);
|
|
881
|
-
}, []);
|
|
882
|
-
|
|
883
|
-
// Handle edge side updates from EdgeInfoPanel
|
|
884
|
-
const handleUpdateEdgeSides = useCallback((edgeId: string, fromSide: string, toSide: string) => {
|
|
885
|
-
setLocalEdges((currentEdges) =>
|
|
886
|
-
currentEdges.map((edge) =>
|
|
887
|
-
edge.id === edgeId
|
|
888
|
-
? {
|
|
889
|
-
...edge,
|
|
890
|
-
data: {
|
|
891
|
-
...edge.data,
|
|
892
|
-
fromSide,
|
|
893
|
-
toSide,
|
|
894
|
-
},
|
|
895
|
-
}
|
|
896
|
-
: edge
|
|
897
|
-
)
|
|
898
|
-
);
|
|
899
|
-
}, []);
|
|
900
|
-
|
|
901
|
-
// Handle close node info panel
|
|
902
|
-
const onCloseNodeInfoPanel = useCallback(() => {
|
|
903
|
-
setSelectedNodeIds(new Set());
|
|
904
|
-
setShowNodePanel(false);
|
|
905
|
-
}, []);
|
|
906
838
|
|
|
907
839
|
// Handle pane click (clear selection when clicking empty space)
|
|
908
840
|
const onPaneClick = useCallback(() => {
|
|
909
841
|
setSelectedNodeIds(new Set());
|
|
910
842
|
setSelectedEdgeIds(new Set());
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
843
|
+
|
|
844
|
+
// In edit mode, also update local nodes selection state
|
|
845
|
+
if (editable) {
|
|
846
|
+
setXyflowLocalNodes((nodes) =>
|
|
847
|
+
nodes.map((n) => ({
|
|
848
|
+
...n,
|
|
849
|
+
selected: false,
|
|
850
|
+
}))
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
}, [editable]);
|
|
914
854
|
|
|
915
855
|
// Handle selection change from ReactFlow (box selection and clicks)
|
|
916
856
|
const handleSelectionChange = useCallback(
|
|
@@ -920,138 +860,51 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
920
860
|
return;
|
|
921
861
|
}
|
|
922
862
|
|
|
923
|
-
//
|
|
924
|
-
|
|
925
|
-
setSelectedEdgeIds(new Set(selectedEdges.map((e) => e.id)));
|
|
926
|
-
|
|
927
|
-
// Only show panels in edit mode or when explicitly enabled
|
|
863
|
+
// In edit mode, we manage selection ourselves via onNodeClick
|
|
864
|
+
// Skip handleSelectionChange to avoid ReactFlow overwriting our selection state
|
|
928
865
|
if (editable) {
|
|
929
|
-
|
|
930
|
-
if (selectedNodes.length > 0) {
|
|
931
|
-
setShowNodePanel(true);
|
|
932
|
-
}
|
|
933
|
-
if (selectedEdges.length > 0) {
|
|
934
|
-
setShowEdgePanel(true);
|
|
935
|
-
}
|
|
866
|
+
return;
|
|
936
867
|
}
|
|
937
|
-
},
|
|
938
|
-
[editable]
|
|
939
|
-
);
|
|
940
|
-
|
|
941
|
-
// Handle node update (internal - updates local state only)
|
|
942
|
-
const handleNodeUpdate = useCallback(
|
|
943
|
-
(nodeId: string, updates: { type?: string; data?: Record<string, JsonValue> }) => {
|
|
944
|
-
if (!editable) return;
|
|
945
|
-
|
|
946
|
-
// Update local nodes
|
|
947
|
-
setLocalNodes((prev) =>
|
|
948
|
-
prev.map((node) => {
|
|
949
|
-
if (node.id === nodeId) {
|
|
950
|
-
return {
|
|
951
|
-
...node,
|
|
952
|
-
type: updates.type ?? node.type,
|
|
953
|
-
data: updates.data ? { ...node.data, ...updates.data } : node.data,
|
|
954
|
-
};
|
|
955
|
-
}
|
|
956
|
-
return node;
|
|
957
|
-
})
|
|
958
|
-
);
|
|
959
868
|
|
|
960
|
-
|
|
961
|
-
updateEditState((prev) => {
|
|
962
|
-
const newUpdates = new Map(prev.nodeUpdates);
|
|
963
|
-
const existing = newUpdates.get(nodeId) || {};
|
|
964
|
-
newUpdates.set(nodeId, {
|
|
965
|
-
type: updates.type ?? existing.type,
|
|
966
|
-
data: updates.data ? { ...existing.data, ...updates.data } : existing.data,
|
|
967
|
-
});
|
|
968
|
-
return { ...prev, nodeUpdates: newUpdates };
|
|
969
|
-
});
|
|
970
|
-
},
|
|
971
|
-
[editable, updateEditState]
|
|
972
|
-
);
|
|
973
|
-
|
|
974
|
-
// Handle node delete (internal)
|
|
975
|
-
const handleNodeDelete = useCallback(
|
|
976
|
-
(nodeId: string) => {
|
|
977
|
-
if (!editable) return;
|
|
869
|
+
const newSelectedNodeIds = new Set(selectedNodes.map((n) => n.id));
|
|
978
870
|
|
|
979
|
-
//
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
// Track the change
|
|
984
|
-
updateEditState((prev) => {
|
|
985
|
-
const newDeletedNodes = new Set(prev.deletedNodeIds);
|
|
986
|
-
newDeletedNodes.add(nodeId);
|
|
987
|
-
// Remove any pending updates for this node
|
|
988
|
-
const newUpdates = new Map(prev.nodeUpdates);
|
|
989
|
-
newUpdates.delete(nodeId);
|
|
990
|
-
// Remove any position changes for this node
|
|
991
|
-
const newPositions = new Map(prev.positionChanges);
|
|
992
|
-
newPositions.delete(nodeId);
|
|
993
|
-
// Remove any dimension changes for this node
|
|
994
|
-
const newDimensions = new Map(prev.dimensionChanges);
|
|
995
|
-
newDimensions.delete(nodeId);
|
|
996
|
-
// Remove created edges that involve this node
|
|
997
|
-
const newCreatedEdges = prev.createdEdges.filter(
|
|
998
|
-
(e) => e.from !== nodeId && e.to !== nodeId
|
|
999
|
-
);
|
|
1000
|
-
return {
|
|
1001
|
-
...prev,
|
|
1002
|
-
deletedNodeIds: newDeletedNodes,
|
|
1003
|
-
nodeUpdates: newUpdates,
|
|
1004
|
-
positionChanges: newPositions,
|
|
1005
|
-
dimensionChanges: newDimensions,
|
|
1006
|
-
createdEdges: newCreatedEdges,
|
|
1007
|
-
};
|
|
1008
|
-
});
|
|
1009
|
-
|
|
1010
|
-
setSelectedNodeIds(new Set());
|
|
871
|
+
// Update selection state for read-only mode (for visual feedback)
|
|
872
|
+
setSelectedNodeIds(newSelectedNodeIds);
|
|
873
|
+
setSelectedEdgeIds(new Set(selectedEdges.map((e) => e.id)));
|
|
1011
874
|
},
|
|
1012
|
-
[editable
|
|
875
|
+
[editable]
|
|
1013
876
|
);
|
|
1014
877
|
|
|
1015
|
-
// Handle edge delete (internal)
|
|
1016
|
-
const handleEdgeDelete = useCallback(
|
|
1017
|
-
(edgeId: string) => {
|
|
1018
|
-
if (!editable) return;
|
|
1019
878
|
|
|
1020
|
-
|
|
1021
|
-
|
|
879
|
+
// Create edge helper
|
|
880
|
+
const createEdge = useCallback(
|
|
881
|
+
(from: string, to: string, type: string, sourceHandle?: string, targetHandle?: string) => {
|
|
882
|
+
const edgeId = `${from}-${to}-${type}-${Date.now()}`;
|
|
1022
883
|
|
|
1023
|
-
//
|
|
1024
|
-
|
|
884
|
+
// Add to local state with handle information
|
|
885
|
+
const newEdge: EdgeState & { sourceHandle?: string; targetHandle?: string } = {
|
|
886
|
+
id: edgeId,
|
|
887
|
+
type,
|
|
888
|
+
from,
|
|
889
|
+
to,
|
|
890
|
+
data: {},
|
|
891
|
+
createdAt: Date.now(),
|
|
892
|
+
updatedAt: Date.now(),
|
|
893
|
+
sourceHandle,
|
|
894
|
+
targetHandle,
|
|
895
|
+
};
|
|
896
|
+
setLocalEdges((prev) => [...prev, newEdge]);
|
|
1025
897
|
|
|
1026
898
|
// Track the change
|
|
1027
|
-
updateEditState((prev) => {
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
return { ...prev, createdEdges: newCreatedEdges };
|
|
1035
|
-
}
|
|
1036
|
-
// Otherwise mark as deleted with full edge info
|
|
1037
|
-
if (edgeToDelete) {
|
|
1038
|
-
const newDeletedEdges = [
|
|
1039
|
-
...prev.deletedEdges,
|
|
1040
|
-
{
|
|
1041
|
-
id: edgeId,
|
|
1042
|
-
from: edgeToDelete.from,
|
|
1043
|
-
to: edgeToDelete.to,
|
|
1044
|
-
type: edgeToDelete.type,
|
|
1045
|
-
},
|
|
1046
|
-
];
|
|
1047
|
-
return { ...prev, deletedEdges: newDeletedEdges };
|
|
1048
|
-
}
|
|
1049
|
-
return prev;
|
|
1050
|
-
});
|
|
1051
|
-
|
|
1052
|
-
setSelectedEdgeIds(new Set());
|
|
899
|
+
updateEditState((prev) => ({
|
|
900
|
+
...prev,
|
|
901
|
+
createdEdges: [
|
|
902
|
+
...prev.createdEdges,
|
|
903
|
+
{ id: edgeId, from, to, type, sourceHandle, targetHandle },
|
|
904
|
+
],
|
|
905
|
+
}));
|
|
1053
906
|
},
|
|
1054
|
-
[
|
|
907
|
+
[updateEditState]
|
|
1055
908
|
);
|
|
1056
909
|
|
|
1057
910
|
// Handle new connection from drag
|
|
@@ -1098,38 +951,7 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
1098
951
|
});
|
|
1099
952
|
}
|
|
1100
953
|
},
|
|
1101
|
-
[editable, nodes, configuration.allowedConnections]
|
|
1102
|
-
);
|
|
1103
|
-
|
|
1104
|
-
// Create edge helper
|
|
1105
|
-
const createEdge = useCallback(
|
|
1106
|
-
(from: string, to: string, type: string, sourceHandle?: string, targetHandle?: string) => {
|
|
1107
|
-
const edgeId = `${from}-${to}-${type}-${Date.now()}`;
|
|
1108
|
-
|
|
1109
|
-
// Add to local state with handle information
|
|
1110
|
-
const newEdge: EdgeState & { sourceHandle?: string; targetHandle?: string } = {
|
|
1111
|
-
id: edgeId,
|
|
1112
|
-
type,
|
|
1113
|
-
from,
|
|
1114
|
-
to,
|
|
1115
|
-
data: {},
|
|
1116
|
-
createdAt: Date.now(),
|
|
1117
|
-
updatedAt: Date.now(),
|
|
1118
|
-
sourceHandle,
|
|
1119
|
-
targetHandle,
|
|
1120
|
-
};
|
|
1121
|
-
setLocalEdges((prev) => [...prev, newEdge]);
|
|
1122
|
-
|
|
1123
|
-
// Track the change
|
|
1124
|
-
updateEditState((prev) => ({
|
|
1125
|
-
...prev,
|
|
1126
|
-
createdEdges: [
|
|
1127
|
-
...prev.createdEdges,
|
|
1128
|
-
{ id: edgeId, from, to, type, sourceHandle, targetHandle },
|
|
1129
|
-
],
|
|
1130
|
-
}));
|
|
1131
|
-
},
|
|
1132
|
-
[updateEditState]
|
|
954
|
+
[editable, nodes, configuration.allowedConnections, createEdge]
|
|
1133
955
|
);
|
|
1134
956
|
|
|
1135
957
|
// Handle edge type selection from picker
|
|
@@ -1305,36 +1127,6 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
1305
1127
|
// ============================================
|
|
1306
1128
|
|
|
1307
1129
|
// Get first selected edge (for single-selection info panel)
|
|
1308
|
-
const selectedEdgeId = useMemo(() => {
|
|
1309
|
-
if (selectedEdgeIds.size === 0) return null;
|
|
1310
|
-
return selectedEdgeIds.values().next().value;
|
|
1311
|
-
}, [selectedEdgeIds]);
|
|
1312
|
-
|
|
1313
|
-
const selectedEdge = useMemo(() => {
|
|
1314
|
-
if (!selectedEdgeId) return null;
|
|
1315
|
-
return edges.find((e) => e.id === selectedEdgeId);
|
|
1316
|
-
}, [selectedEdgeId, edges]);
|
|
1317
|
-
|
|
1318
|
-
const selectedEdgeTypeDefinition = useMemo(() => {
|
|
1319
|
-
if (!selectedEdge) return null;
|
|
1320
|
-
return configuration.edgeTypes[selectedEdge.type];
|
|
1321
|
-
}, [selectedEdge, configuration.edgeTypes]);
|
|
1322
|
-
|
|
1323
|
-
// Get first selected node (for single-selection info panel)
|
|
1324
|
-
const selectedNodeId = useMemo(() => {
|
|
1325
|
-
if (selectedNodeIds.size === 0) return null;
|
|
1326
|
-
return selectedNodeIds.values().next().value;
|
|
1327
|
-
}, [selectedNodeIds]);
|
|
1328
|
-
|
|
1329
|
-
const selectedNode = useMemo(() => {
|
|
1330
|
-
if (!selectedNodeId) return null;
|
|
1331
|
-
return nodes.find((n) => n.id === selectedNodeId);
|
|
1332
|
-
}, [selectedNodeId, nodes]);
|
|
1333
|
-
|
|
1334
|
-
const selectedNodeTypeDefinition = useMemo(() => {
|
|
1335
|
-
if (!selectedNode) return null;
|
|
1336
|
-
return configuration.nodeTypes[selectedNode.type];
|
|
1337
|
-
}, [selectedNode, configuration.nodeTypes]);
|
|
1338
1130
|
|
|
1339
1131
|
// ============================================
|
|
1340
1132
|
// ANIMATIONS
|
|
@@ -1504,7 +1296,7 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
1504
1296
|
// Local xyflow nodes state for dragging
|
|
1505
1297
|
const [xyflowLocalNodes, setXyflowLocalNodes] = useState<Node<CustomNodeData>[]>(xyflowNodesBase);
|
|
1506
1298
|
|
|
1507
|
-
// Sync when base
|
|
1299
|
+
// Sync when base node IDs change
|
|
1508
1300
|
const prevBaseNodesKeyRef = useRef(baseNodesKey);
|
|
1509
1301
|
useEffect(() => {
|
|
1510
1302
|
if (prevBaseNodesKeyRef.current !== baseNodesKey) {
|
|
@@ -1513,6 +1305,22 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
1513
1305
|
}
|
|
1514
1306
|
}, [baseNodesKey, xyflowNodesBase]);
|
|
1515
1307
|
|
|
1308
|
+
// Sync selection state to local nodes when selectedNodeIds changes (edit mode only)
|
|
1309
|
+
// Use useLayoutEffect to ensure sync happens before paint (prevents flash)
|
|
1310
|
+
const prevSelectedNodeIdsRef = useRef(selectedNodeIds);
|
|
1311
|
+
useLayoutEffect(() => {
|
|
1312
|
+
if (!editable) return;
|
|
1313
|
+
if (prevSelectedNodeIdsRef.current === selectedNodeIds) return;
|
|
1314
|
+
prevSelectedNodeIdsRef.current = selectedNodeIds;
|
|
1315
|
+
|
|
1316
|
+
setXyflowLocalNodes((localNodes) =>
|
|
1317
|
+
localNodes.map((localNode) => ({
|
|
1318
|
+
...localNode,
|
|
1319
|
+
selected: selectedNodeIds.has(localNode.id),
|
|
1320
|
+
}))
|
|
1321
|
+
);
|
|
1322
|
+
}, [editable, selectedNodeIds]);
|
|
1323
|
+
|
|
1516
1324
|
// Also sync when entering edit mode or when base nodes change content
|
|
1517
1325
|
const prevEditableRef = useRef(editable);
|
|
1518
1326
|
useEffect(() => {
|
|
@@ -1554,19 +1362,20 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
1554
1362
|
(changes: NodeChange[]) => {
|
|
1555
1363
|
if (!editable) return;
|
|
1556
1364
|
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1365
|
+
// Filter out selection changes - we manage selection ourselves via selectedNodeIds
|
|
1366
|
+
const nonSelectionChanges = changes.filter((change) => change.type !== 'select');
|
|
1367
|
+
|
|
1368
|
+
// Only apply changes if there are non-selection changes to apply
|
|
1369
|
+
if (nonSelectionChanges.length > 0) {
|
|
1370
|
+
setXyflowLocalNodes((nds) => {
|
|
1371
|
+
// Apply changes but preserve our selection state
|
|
1372
|
+
const updated = applyNodeChanges(nonSelectionChanges, nds) as Node<CustomNodeData>[];
|
|
1373
|
+
// Restore selection state from our managed selectedNodeIds
|
|
1374
|
+
return updated.map((node) => ({
|
|
1375
|
+
...node,
|
|
1376
|
+
selected: selectedNodeIds.has(node.id),
|
|
1377
|
+
}));
|
|
1378
|
+
});
|
|
1570
1379
|
}
|
|
1571
1380
|
|
|
1572
1381
|
// Track position changes on drag end
|
|
@@ -1601,23 +1410,6 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
1601
1410
|
change.resizing === false
|
|
1602
1411
|
);
|
|
1603
1412
|
|
|
1604
|
-
// Debug logging for dimension changes
|
|
1605
|
-
if (process.env.NODE_ENV === 'development') {
|
|
1606
|
-
const allDimensionChanges = changes.filter(c => c.type === 'dimensions');
|
|
1607
|
-
if (allDimensionChanges.length > 0) {
|
|
1608
|
-
console.log('[GraphRenderer] Dimension changes detected:', allDimensionChanges.map(c => ({
|
|
1609
|
-
// @ts-expect-error - accessing properties for debug
|
|
1610
|
-
id: c.id,
|
|
1611
|
-
// @ts-expect-error - accessing properties for debug
|
|
1612
|
-
dimensions: c.dimensions,
|
|
1613
|
-
// @ts-expect-error - accessing properties for debug
|
|
1614
|
-
resizing: c.resizing,
|
|
1615
|
-
// @ts-expect-error - accessing properties for debug
|
|
1616
|
-
isGroup: nodes.find(n => n.id === c.id)?.data?.canvasType === 'group'
|
|
1617
|
-
})));
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
1413
|
if (dimensionChanges.length > 0) {
|
|
1622
1414
|
updateEditState((prev) => {
|
|
1623
1415
|
const newDimensions = new Map(prev.dimensionChanges);
|
|
@@ -1646,7 +1438,7 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
1646
1438
|
});
|
|
1647
1439
|
}
|
|
1648
1440
|
},
|
|
1649
|
-
[editable, updateEditState]
|
|
1441
|
+
[editable, updateEditState, selectedNodeIds]
|
|
1650
1442
|
);
|
|
1651
1443
|
|
|
1652
1444
|
const xyflowEdgesBase = useMemo(() => {
|
|
@@ -1671,18 +1463,6 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
1671
1463
|
return true;
|
|
1672
1464
|
});
|
|
1673
1465
|
|
|
1674
|
-
// Debug: Log edge counts to help diagnose disappearing edges
|
|
1675
|
-
if (process.env.NODE_ENV === 'development') {
|
|
1676
|
-
console.log('[GraphRenderer] xyflowEdges computed:', {
|
|
1677
|
-
inputEdges: edges.length,
|
|
1678
|
-
convertedEdges: converted.length,
|
|
1679
|
-
filteredEdges: filtered.length,
|
|
1680
|
-
editable,
|
|
1681
|
-
propEdgesCount: propEdges.length,
|
|
1682
|
-
localEdgesCount: localEdges.length,
|
|
1683
|
-
});
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
1466
|
const mappedEdges = filtered.map((edge) => {
|
|
1687
1467
|
const animation = animationState.edgeAnimations[edge.id];
|
|
1688
1468
|
const isSelected = selectedEdgeIds.has(edge.id);
|
|
@@ -1808,7 +1588,7 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
1808
1588
|
}, 150);
|
|
1809
1589
|
|
|
1810
1590
|
return () => clearTimeout(timeoutId);
|
|
1811
|
-
}, [fitViewToNodeIdsKey, fitViewToNodeIds, fitViewPadding, fitView, fitViewDuration, getNodes]);
|
|
1591
|
+
}, [fitViewToNodeIdsKey, fitViewToNodeIds, fitViewPadding, fitView, fitViewDuration, getNodes, fitBounds]);
|
|
1812
1592
|
|
|
1813
1593
|
// ============================================
|
|
1814
1594
|
// RENDER
|
|
@@ -1831,7 +1611,8 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
1831
1611
|
onNodeDragStop={handleNodeDragStop}
|
|
1832
1612
|
proOptions={{ hideAttribution: true }}
|
|
1833
1613
|
nodesDraggable={editable}
|
|
1834
|
-
elementsSelectable={
|
|
1614
|
+
elementsSelectable={editable}
|
|
1615
|
+
selectNodesOnDrag={false}
|
|
1835
1616
|
nodesConnectable={editable}
|
|
1836
1617
|
edgesReconnectable={editable}
|
|
1837
1618
|
reconnectRadius={25}
|
|
@@ -1844,13 +1625,13 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
1844
1625
|
onReconnectEnd={handleReconnectEnd}
|
|
1845
1626
|
onPaneClick={onPaneClick}
|
|
1846
1627
|
onSelectionChange={handleSelectionChange}
|
|
1847
|
-
panOnDrag={
|
|
1628
|
+
panOnDrag={!editable}
|
|
1848
1629
|
panOnScroll={true}
|
|
1849
1630
|
zoomOnScroll={false}
|
|
1850
1631
|
zoomOnPinch={true}
|
|
1851
|
-
selectionOnDrag={
|
|
1632
|
+
selectionOnDrag={false}
|
|
1852
1633
|
selectionKeyCode={null}
|
|
1853
|
-
multiSelectionKeyCode=
|
|
1634
|
+
multiSelectionKeyCode="Shift"
|
|
1854
1635
|
>
|
|
1855
1636
|
{showBackground && (
|
|
1856
1637
|
<Background
|
|
@@ -1884,31 +1665,6 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
1884
1665
|
)}
|
|
1885
1666
|
</ReactFlow>
|
|
1886
1667
|
|
|
1887
|
-
{/* Single edge info panel - shown only on explicit click */}
|
|
1888
|
-
{selectedEdgeIds.size === 1 && selectedEdge && selectedEdgeTypeDefinition && showEdgePanel && (
|
|
1889
|
-
<EdgeInfoPanel
|
|
1890
|
-
edge={selectedEdge}
|
|
1891
|
-
typeDefinition={selectedEdgeTypeDefinition}
|
|
1892
|
-
sourceNodeId={selectedEdge.from}
|
|
1893
|
-
targetNodeId={selectedEdge.to}
|
|
1894
|
-
onClose={onCloseEdgeInfoPanel}
|
|
1895
|
-
onDelete={editable ? handleEdgeDelete : undefined}
|
|
1896
|
-
onUpdateSides={editable ? handleUpdateEdgeSides : undefined}
|
|
1897
|
-
/>
|
|
1898
|
-
)}
|
|
1899
|
-
|
|
1900
|
-
{/* Single node info panel - shown only on explicit click */}
|
|
1901
|
-
{selectedNodeIds.size === 1 && selectedNode && selectedNodeTypeDefinition && showNodePanel && showNodeDetailPanel !== false && (
|
|
1902
|
-
<NodeInfoPanel
|
|
1903
|
-
node={selectedNode}
|
|
1904
|
-
typeDefinition={selectedNodeTypeDefinition}
|
|
1905
|
-
availableNodeTypes={configuration.nodeTypes}
|
|
1906
|
-
onClose={onCloseNodeInfoPanel}
|
|
1907
|
-
onDelete={editable ? handleNodeDelete : undefined}
|
|
1908
|
-
onUpdate={editable ? handleNodeUpdate : undefined}
|
|
1909
|
-
resolveEventRef={resolveEventRef}
|
|
1910
|
-
/>
|
|
1911
|
-
)}
|
|
1912
1668
|
|
|
1913
1669
|
|
|
1914
1670
|
{pendingConnection && (
|
|
@@ -2285,8 +2041,6 @@ export const GraphRenderer = forwardRef<GraphRendererHandle, GraphRendererProps>
|
|
|
2285
2041
|
editable,
|
|
2286
2042
|
onPendingChangesChange,
|
|
2287
2043
|
onNodeClick,
|
|
2288
|
-
showNodeDetailPanel,
|
|
2289
|
-
resolveEventRef,
|
|
2290
2044
|
fitViewToNodeIds,
|
|
2291
2045
|
fitViewPadding,
|
|
2292
2046
|
} = props;
|
|
@@ -2316,8 +2070,6 @@ export const GraphRenderer = forwardRef<GraphRendererHandle, GraphRendererProps>
|
|
|
2316
2070
|
onPendingChangesChange={onPendingChangesChange}
|
|
2317
2071
|
editStateRef={editStateRef}
|
|
2318
2072
|
onNodeClick={onNodeClick}
|
|
2319
|
-
showNodeDetailPanel={showNodeDetailPanel}
|
|
2320
|
-
resolveEventRef={resolveEventRef}
|
|
2321
2073
|
fitViewToNodeIds={fitViewToNodeIds}
|
|
2322
2074
|
fitViewPadding={fitViewPadding}
|
|
2323
2075
|
/>
|