@principal-ai/principal-view-react 0.6.11 → 0.6.13
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 +6 -0
- package/dist/components/GraphRenderer.d.ts.map +1 -1
- package/dist/components/GraphRenderer.js +49 -4
- package/dist/components/GraphRenderer.js.map +1 -1
- package/dist/edges/CustomEdge.d.ts +1 -0
- package/dist/edges/CustomEdge.d.ts.map +1 -1
- package/dist/edges/CustomEdge.js +17 -3
- package/dist/edges/CustomEdge.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/nodes/CustomNode.d.ts.map +1 -1
- package/dist/nodes/CustomNode.js +2 -2
- package/dist/nodes/CustomNode.js.map +1 -1
- package/dist/utils/graphConverter.d.ts +23 -0
- package/dist/utils/graphConverter.d.ts.map +1 -1
- package/dist/utils/graphConverter.js +85 -2
- package/dist/utils/graphConverter.js.map +1 -1
- package/package.json +1 -1
- package/src/components/GraphRenderer.tsx +67 -1
- package/src/edges/CustomEdge.tsx +34 -3
- package/src/index.ts +3 -1
- package/src/nodes/CustomNode.tsx +5 -3
- package/src/stories/CanvasEdgeTypes.stories.tsx +980 -0
- package/src/stories/GraphRenderer.stories.tsx +1 -0
- package/src/utils/graphConverter.ts +98 -2
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.convertToXYFlowNodes = convertToXYFlowNodes;
|
|
4
4
|
exports.convertToXYFlowEdges = convertToXYFlowEdges;
|
|
5
5
|
exports.autoLayoutNodes = autoLayoutNodes;
|
|
6
|
+
exports.hasCycleBetweenNodes = hasCycleBetweenNodes;
|
|
7
|
+
exports.computeOptimalEdgeSides = computeOptimalEdgeSides;
|
|
6
8
|
const react_1 = require("@xyflow/react");
|
|
7
9
|
/**
|
|
8
10
|
* Convert our NodeState to xyflow Node format
|
|
@@ -33,6 +35,22 @@ function convertToXYFlowNodes(nodes, configuration, violations = []) {
|
|
|
33
35
|
};
|
|
34
36
|
});
|
|
35
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Map canvas side to source handle ID
|
|
40
|
+
* Source handles use '-out' suffix
|
|
41
|
+
*/
|
|
42
|
+
function sideToSourceHandle(side) {
|
|
43
|
+
if (!side)
|
|
44
|
+
return undefined;
|
|
45
|
+
return `${side}-out`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Map canvas side to target handle ID
|
|
49
|
+
* Target handles use the side name directly
|
|
50
|
+
*/
|
|
51
|
+
function sideToTargetHandle(side) {
|
|
52
|
+
return side;
|
|
53
|
+
}
|
|
36
54
|
/**
|
|
37
55
|
* Convert our EdgeState to xyflow Edge format
|
|
38
56
|
*/
|
|
@@ -45,6 +63,11 @@ function convertToXYFlowEdges(edges, configuration, violations = []) {
|
|
|
45
63
|
}
|
|
46
64
|
const hasViolations = violations.some((v) => v.context?.edgeId === edge.id);
|
|
47
65
|
const edgeWithHandles = edge;
|
|
66
|
+
// Get handle IDs from edge data (fromSide/toSide) or explicit handles
|
|
67
|
+
const fromSide = edge.data?.fromSide;
|
|
68
|
+
const toSide = edge.data?.toSide;
|
|
69
|
+
const sourceHandle = edgeWithHandles.sourceHandle || sideToSourceHandle(fromSide);
|
|
70
|
+
const targetHandle = edgeWithHandles.targetHandle || sideToTargetHandle(toSide);
|
|
48
71
|
// Add arrow marker if edge type is directed
|
|
49
72
|
// Color priority: edge data color > type definition color > default
|
|
50
73
|
const edgeColor = edge.data?.color;
|
|
@@ -60,8 +83,8 @@ function convertToXYFlowEdges(edges, configuration, violations = []) {
|
|
|
60
83
|
id: edge.id,
|
|
61
84
|
source: edge.from,
|
|
62
85
|
target: edge.to,
|
|
63
|
-
sourceHandle
|
|
64
|
-
targetHandle
|
|
86
|
+
sourceHandle,
|
|
87
|
+
targetHandle,
|
|
65
88
|
type: 'custom',
|
|
66
89
|
animated: typeDefinition?.style === 'animated',
|
|
67
90
|
markerEnd,
|
|
@@ -69,6 +92,7 @@ function convertToXYFlowEdges(edges, configuration, violations = []) {
|
|
|
69
92
|
typeDefinition,
|
|
70
93
|
hasViolations,
|
|
71
94
|
data: edge.data,
|
|
95
|
+
edgeType: edge.type,
|
|
72
96
|
},
|
|
73
97
|
};
|
|
74
98
|
});
|
|
@@ -181,4 +205,63 @@ function applyCircularLayout(nodes) {
|
|
|
181
205
|
};
|
|
182
206
|
});
|
|
183
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Check if there is a cycle between two nodes (i.e., a path from target back to source).
|
|
210
|
+
* Returns true if adding an edge from `from` to `to` would complete a cycle.
|
|
211
|
+
*/
|
|
212
|
+
function hasCycleBetweenNodes(from, to, edges) {
|
|
213
|
+
// Check if there's already a path from `to` back to `from`
|
|
214
|
+
// using BFS/DFS from `to` node
|
|
215
|
+
const visited = new Set();
|
|
216
|
+
const queue = [to];
|
|
217
|
+
while (queue.length > 0) {
|
|
218
|
+
const current = queue.shift();
|
|
219
|
+
if (current === from) {
|
|
220
|
+
return true; // Found a path back, there would be a cycle
|
|
221
|
+
}
|
|
222
|
+
if (visited.has(current)) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
visited.add(current);
|
|
226
|
+
// Find all outgoing edges from current node
|
|
227
|
+
for (const edge of edges) {
|
|
228
|
+
if (edge.from === current && !visited.has(edge.to)) {
|
|
229
|
+
queue.push(edge.to);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Compute optimal edge sides based on relative node positions.
|
|
237
|
+
* Returns the best fromSide and toSide for connecting two nodes.
|
|
238
|
+
*/
|
|
239
|
+
function computeOptimalEdgeSides(fromPosition, toPosition) {
|
|
240
|
+
const dx = toPosition.x - fromPosition.x;
|
|
241
|
+
const dy = toPosition.y - fromPosition.y;
|
|
242
|
+
// Determine primary direction based on which axis has larger delta
|
|
243
|
+
const isHorizontalDominant = Math.abs(dx) > Math.abs(dy);
|
|
244
|
+
if (isHorizontalDominant) {
|
|
245
|
+
// Horizontal connection
|
|
246
|
+
if (dx > 0) {
|
|
247
|
+
// Target is to the right of source
|
|
248
|
+
return { fromSide: 'right', toSide: 'left' };
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
// Target is to the left of source
|
|
252
|
+
return { fromSide: 'left', toSide: 'right' };
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
// Vertical connection
|
|
257
|
+
if (dy > 0) {
|
|
258
|
+
// Target is below source
|
|
259
|
+
return { fromSide: 'bottom', toSide: 'top' };
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
// Target is above source
|
|
263
|
+
return { fromSide: 'top', toSide: 'bottom' };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
184
267
|
//# sourceMappingURL=graphConverter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graphConverter.js","sourceRoot":"","sources":["../../src/utils/graphConverter.ts"],"names":[],"mappings":";;AAaA,oDAiCC;
|
|
1
|
+
{"version":3,"file":"graphConverter.js","sourceRoot":"","sources":["../../src/utils/graphConverter.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -40,6 +40,8 @@ import {
|
|
|
40
40
|
convertToXYFlowNodes,
|
|
41
41
|
convertToXYFlowEdges,
|
|
42
42
|
autoLayoutNodes,
|
|
43
|
+
hasCycleBetweenNodes,
|
|
44
|
+
computeOptimalEdgeSides,
|
|
43
45
|
} from '../utils/graphConverter';
|
|
44
46
|
import { EdgeInfoPanel } from './EdgeInfoPanel';
|
|
45
47
|
import { NodeInfoPanel } from './NodeInfoPanel';
|
|
@@ -90,6 +92,13 @@ interface GraphRendererBaseProps {
|
|
|
90
92
|
/** Optional violations to highlight */
|
|
91
93
|
violations?: Violation[];
|
|
92
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Whether to automatically update edge sides (fromSide/toSide) when nodes are moved.
|
|
97
|
+
* Only updates edges where there is no cycle between the connected nodes.
|
|
98
|
+
* Uses position-based logic to determine optimal connection sides.
|
|
99
|
+
*/
|
|
100
|
+
autoUpdateEdgeSides?: boolean;
|
|
101
|
+
|
|
93
102
|
/** Optional configuration name for identification (used with multi-config setups) */
|
|
94
103
|
configName?: string;
|
|
95
104
|
|
|
@@ -209,6 +218,7 @@ interface GraphRendererInnerProps {
|
|
|
209
218
|
events?: GraphEvent[];
|
|
210
219
|
onEventProcessed?: (event: GraphEvent) => void;
|
|
211
220
|
editable?: boolean;
|
|
221
|
+
autoUpdateEdgeSides?: boolean;
|
|
212
222
|
onPendingChangesChange?: (hasChanges: boolean) => void;
|
|
213
223
|
onEditStateChange?: (editState: EditState) => void;
|
|
214
224
|
editStateRef: React.MutableRefObject<EditState>;
|
|
@@ -229,6 +239,7 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
229
239
|
events = [],
|
|
230
240
|
onEventProcessed,
|
|
231
241
|
editable = false,
|
|
242
|
+
autoUpdateEdgeSides = false,
|
|
232
243
|
onPendingChangesChange,
|
|
233
244
|
onEditStateChange,
|
|
234
245
|
editStateRef,
|
|
@@ -909,9 +920,62 @@ const GraphRendererInner: React.FC<GraphRendererInnerProps> = ({
|
|
|
909
920
|
}
|
|
910
921
|
return { ...prev, positionChanges: newPositions };
|
|
911
922
|
});
|
|
923
|
+
|
|
924
|
+
// Auto-update edge sides if enabled
|
|
925
|
+
if (autoUpdateEdgeSides) {
|
|
926
|
+
setXyflowLocalNodes((currentNodes) => {
|
|
927
|
+
// Build a position map from current xyflow nodes
|
|
928
|
+
const nodePositions = new Map<string, { x: number; y: number }>();
|
|
929
|
+
for (const node of currentNodes) {
|
|
930
|
+
nodePositions.set(node.id, node.position);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// Get moved node IDs
|
|
934
|
+
const movedNodeIds = new Set(positionChanges.map((c) => c.id));
|
|
935
|
+
|
|
936
|
+
// Update edges connected to moved nodes
|
|
937
|
+
setLocalEdges((currentEdges) => {
|
|
938
|
+
return currentEdges.map((edge) => {
|
|
939
|
+
// Only process edges connected to moved nodes
|
|
940
|
+
if (!movedNodeIds.has(edge.from) && !movedNodeIds.has(edge.to)) {
|
|
941
|
+
return edge;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// Check for cycles - skip if there's a cycle between these nodes
|
|
945
|
+
const edgesWithoutCurrent = currentEdges.filter((e) => e.id !== edge.id);
|
|
946
|
+
if (hasCycleBetweenNodes(edge.from, edge.to, edgesWithoutCurrent)) {
|
|
947
|
+
return edge; // Don't auto-update edges that are part of a cycle
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Get positions of both nodes
|
|
951
|
+
const fromPos = nodePositions.get(edge.from);
|
|
952
|
+
const toPos = nodePositions.get(edge.to);
|
|
953
|
+
if (!fromPos || !toPos) {
|
|
954
|
+
return edge;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// Compute optimal sides
|
|
958
|
+
const { fromSide, toSide } = computeOptimalEdgeSides(fromPos, toPos);
|
|
959
|
+
|
|
960
|
+
// Update edge data with new sides
|
|
961
|
+
return {
|
|
962
|
+
...edge,
|
|
963
|
+
data: {
|
|
964
|
+
...edge.data,
|
|
965
|
+
fromSide,
|
|
966
|
+
toSide,
|
|
967
|
+
},
|
|
968
|
+
updatedAt: Date.now(),
|
|
969
|
+
};
|
|
970
|
+
});
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
return currentNodes;
|
|
974
|
+
});
|
|
975
|
+
}
|
|
912
976
|
}
|
|
913
977
|
},
|
|
914
|
-
[editable, updateEditState]
|
|
978
|
+
[editable, autoUpdateEdgeSides, updateEditState]
|
|
915
979
|
);
|
|
916
980
|
|
|
917
981
|
const xyflowEdges = useMemo(() => {
|
|
@@ -1364,6 +1428,7 @@ export const GraphRenderer = forwardRef<GraphRendererHandle, GraphRendererProps>
|
|
|
1364
1428
|
events,
|
|
1365
1429
|
onEventProcessed,
|
|
1366
1430
|
editable,
|
|
1431
|
+
autoUpdateEdgeSides,
|
|
1367
1432
|
onPendingChangesChange,
|
|
1368
1433
|
} = props;
|
|
1369
1434
|
|
|
@@ -1382,6 +1447,7 @@ export const GraphRenderer = forwardRef<GraphRendererHandle, GraphRendererProps>
|
|
|
1382
1447
|
events={events}
|
|
1383
1448
|
onEventProcessed={onEventProcessed}
|
|
1384
1449
|
editable={editable}
|
|
1450
|
+
autoUpdateEdgeSides={autoUpdateEdgeSides}
|
|
1385
1451
|
onPendingChangesChange={onPendingChangesChange}
|
|
1386
1452
|
editStateRef={editStateRef}
|
|
1387
1453
|
/>
|
package/src/edges/CustomEdge.tsx
CHANGED
|
@@ -7,6 +7,7 @@ export interface CustomEdgeData extends Record<string, unknown> {
|
|
|
7
7
|
typeDefinition: EdgeTypeDefinition;
|
|
8
8
|
hasViolations?: boolean;
|
|
9
9
|
data?: Record<string, unknown>;
|
|
10
|
+
edgeType?: string;
|
|
10
11
|
// Animation control
|
|
11
12
|
animationType?: 'flow' | 'particle' | 'pulse' | 'glow' | null;
|
|
12
13
|
animationDuration?: number;
|
|
@@ -34,12 +35,14 @@ export const CustomEdge: React.FC<EdgeProps<any>> = ({
|
|
|
34
35
|
typeDefinition,
|
|
35
36
|
hasViolations,
|
|
36
37
|
data: edgeData,
|
|
38
|
+
edgeType,
|
|
37
39
|
animationType,
|
|
38
40
|
animationDuration = 1000,
|
|
39
41
|
animationDirection = 'forward',
|
|
40
42
|
} = edgeProps || ({} as CustomEdgeData);
|
|
41
43
|
|
|
42
44
|
const [particlePosition, setParticlePosition] = useState(0);
|
|
45
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
43
46
|
const pathRef = useRef<SVGPathElement>(null);
|
|
44
47
|
|
|
45
48
|
// Particle animation effect
|
|
@@ -147,7 +150,11 @@ export const CustomEdge: React.FC<EdgeProps<any>> = ({
|
|
|
147
150
|
const particlePos = animationType === 'particle' ? getParticleTransform() : null;
|
|
148
151
|
|
|
149
152
|
return (
|
|
150
|
-
|
|
153
|
+
<g
|
|
154
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
155
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
156
|
+
style={{ cursor: 'pointer' }}
|
|
157
|
+
>
|
|
151
158
|
{/* Hidden path for particle position calculation */}
|
|
152
159
|
<path
|
|
153
160
|
ref={pathRef}
|
|
@@ -164,7 +171,7 @@ export const CustomEdge: React.FC<EdgeProps<any>> = ({
|
|
|
164
171
|
fill="none"
|
|
165
172
|
stroke="transparent"
|
|
166
173
|
strokeWidth={Math.max(width + 10, 20)}
|
|
167
|
-
style={{
|
|
174
|
+
style={{ pointerEvents: 'stroke' }}
|
|
168
175
|
/>
|
|
169
176
|
|
|
170
177
|
<BaseEdge
|
|
@@ -275,6 +282,30 @@ export const CustomEdge: React.FC<EdgeProps<any>> = ({
|
|
|
275
282
|
</EdgeLabelRenderer>
|
|
276
283
|
)}
|
|
277
284
|
|
|
285
|
+
{/* Hover tooltip showing edge type */}
|
|
286
|
+
{isHovered && (
|
|
287
|
+
<EdgeLabelRenderer>
|
|
288
|
+
<div
|
|
289
|
+
style={{
|
|
290
|
+
position: 'absolute',
|
|
291
|
+
transform: `translate(-50%, -100%) translate(${labelX}px,${labelY - 12}px)`,
|
|
292
|
+
backgroundColor: 'rgba(0, 0, 0, 0.85)',
|
|
293
|
+
color: 'white',
|
|
294
|
+
padding: '4px 8px',
|
|
295
|
+
borderRadius: '4px',
|
|
296
|
+
fontSize: '11px',
|
|
297
|
+
fontWeight: 500,
|
|
298
|
+
whiteSpace: 'nowrap',
|
|
299
|
+
pointerEvents: 'none',
|
|
300
|
+
zIndex: 1000,
|
|
301
|
+
boxShadow: '0 2px 8px rgba(0,0,0,0.2)',
|
|
302
|
+
}}
|
|
303
|
+
>
|
|
304
|
+
{edgeType || 'edge'}
|
|
305
|
+
</div>
|
|
306
|
+
</EdgeLabelRenderer>
|
|
307
|
+
)}
|
|
308
|
+
|
|
278
309
|
{/* CSS animations for all edge animation types */}
|
|
279
310
|
<style>{`
|
|
280
311
|
/* Flow animation - forward direction */
|
|
@@ -343,6 +374,6 @@ export const CustomEdge: React.FC<EdgeProps<any>> = ({
|
|
|
343
374
|
}
|
|
344
375
|
}
|
|
345
376
|
`}</style>
|
|
346
|
-
|
|
377
|
+
</g>
|
|
347
378
|
);
|
|
348
379
|
};
|
package/src/index.ts
CHANGED
|
@@ -70,7 +70,9 @@ export {
|
|
|
70
70
|
convertToXYFlowNodes,
|
|
71
71
|
convertToXYFlowEdges,
|
|
72
72
|
autoLayoutNodes,
|
|
73
|
+
hasCycleBetweenNodes,
|
|
74
|
+
computeOptimalEdgeSides,
|
|
73
75
|
} from './utils/graphConverter';
|
|
74
|
-
export type { EdgeStateWithHandles } from './utils/graphConverter';
|
|
76
|
+
export type { EdgeStateWithHandles, CanvasSide } from './utils/graphConverter';
|
|
75
77
|
export { Icon, resolveIcon } from './utils/iconResolver';
|
|
76
78
|
export type { IconProps } from './utils/iconResolver';
|
package/src/nodes/CustomNode.tsx
CHANGED
|
@@ -245,8 +245,9 @@ export const CustomNode: React.FC<NodeProps<any>> = ({ data, selected }) => {
|
|
|
245
245
|
|
|
246
246
|
return (
|
|
247
247
|
<>
|
|
248
|
-
{/* Input handles -
|
|
248
|
+
{/* Input handles - all 4 sides for incoming connections */}
|
|
249
249
|
<Handle type="target" position={Position.Top} id="top" style={getHandleStyle('top')} />
|
|
250
|
+
<Handle type="target" position={Position.Bottom} id="bottom" style={getHandleStyle('bottom')} />
|
|
250
251
|
<Handle type="target" position={Position.Left} id="left" style={getHandleStyle('left')} />
|
|
251
252
|
<Handle type="target" position={Position.Right} id="right" style={getHandleStyle('right')} />
|
|
252
253
|
|
|
@@ -349,11 +350,12 @@ export const CustomNode: React.FC<NodeProps<any>> = ({ data, selected }) => {
|
|
|
349
350
|
</div>
|
|
350
351
|
)}
|
|
351
352
|
|
|
352
|
-
{/* Output handles -
|
|
353
|
+
{/* Output handles - all 4 sides for outgoing connections */}
|
|
354
|
+
<Handle type="source" position={Position.Top} id="top-out" style={getHandleStyle('top')} />
|
|
353
355
|
<Handle
|
|
354
356
|
type="source"
|
|
355
357
|
position={Position.Bottom}
|
|
356
|
-
id="bottom"
|
|
358
|
+
id="bottom-out"
|
|
357
359
|
style={getHandleStyle('bottom')}
|
|
358
360
|
/>
|
|
359
361
|
<Handle type="source" position={Position.Left} id="left-out" style={getHandleStyle('left')} />
|