@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
|
@@ -52,6 +52,23 @@ export interface EdgeStateWithHandles extends EdgeState {
|
|
|
52
52
|
targetHandle?: string;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Map canvas side to source handle ID
|
|
57
|
+
* Source handles use '-out' suffix
|
|
58
|
+
*/
|
|
59
|
+
function sideToSourceHandle(side?: string): string | undefined {
|
|
60
|
+
if (!side) return undefined;
|
|
61
|
+
return `${side}-out`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Map canvas side to target handle ID
|
|
66
|
+
* Target handles use the side name directly
|
|
67
|
+
*/
|
|
68
|
+
function sideToTargetHandle(side?: string): string | undefined {
|
|
69
|
+
return side;
|
|
70
|
+
}
|
|
71
|
+
|
|
55
72
|
/**
|
|
56
73
|
* Convert our EdgeState to xyflow Edge format
|
|
57
74
|
*/
|
|
@@ -71,6 +88,12 @@ export function convertToXYFlowEdges(
|
|
|
71
88
|
const hasViolations = violations.some((v) => v.context?.edgeId === edge.id);
|
|
72
89
|
const edgeWithHandles = edge as EdgeStateWithHandles;
|
|
73
90
|
|
|
91
|
+
// Get handle IDs from edge data (fromSide/toSide) or explicit handles
|
|
92
|
+
const fromSide = edge.data?.fromSide as string | undefined;
|
|
93
|
+
const toSide = edge.data?.toSide as string | undefined;
|
|
94
|
+
const sourceHandle = edgeWithHandles.sourceHandle || sideToSourceHandle(fromSide);
|
|
95
|
+
const targetHandle = edgeWithHandles.targetHandle || sideToTargetHandle(toSide);
|
|
96
|
+
|
|
74
97
|
// Add arrow marker if edge type is directed
|
|
75
98
|
// Color priority: edge data color > type definition color > default
|
|
76
99
|
const edgeColor = edge.data?.color as string | undefined;
|
|
@@ -88,8 +111,8 @@ export function convertToXYFlowEdges(
|
|
|
88
111
|
id: edge.id,
|
|
89
112
|
source: edge.from,
|
|
90
113
|
target: edge.to,
|
|
91
|
-
sourceHandle
|
|
92
|
-
targetHandle
|
|
114
|
+
sourceHandle,
|
|
115
|
+
targetHandle,
|
|
93
116
|
type: 'custom',
|
|
94
117
|
animated: typeDefinition?.style === 'animated',
|
|
95
118
|
markerEnd,
|
|
@@ -97,6 +120,7 @@ export function convertToXYFlowEdges(
|
|
|
97
120
|
typeDefinition,
|
|
98
121
|
hasViolations,
|
|
99
122
|
data: edge.data,
|
|
123
|
+
edgeType: edge.type,
|
|
100
124
|
},
|
|
101
125
|
};
|
|
102
126
|
});
|
|
@@ -234,3 +258,75 @@ function applyCircularLayout<T extends Record<string, unknown>>(nodes: Node<T>[]
|
|
|
234
258
|
};
|
|
235
259
|
});
|
|
236
260
|
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Check if there is a cycle between two nodes (i.e., a path from target back to source).
|
|
264
|
+
* Returns true if adding an edge from `from` to `to` would complete a cycle.
|
|
265
|
+
*/
|
|
266
|
+
export function hasCycleBetweenNodes(
|
|
267
|
+
from: string,
|
|
268
|
+
to: string,
|
|
269
|
+
edges: Array<{ from: string; to: string }>
|
|
270
|
+
): boolean {
|
|
271
|
+
// Check if there's already a path from `to` back to `from`
|
|
272
|
+
// using BFS/DFS from `to` node
|
|
273
|
+
const visited = new Set<string>();
|
|
274
|
+
const queue = [to];
|
|
275
|
+
|
|
276
|
+
while (queue.length > 0) {
|
|
277
|
+
const current = queue.shift()!;
|
|
278
|
+
if (current === from) {
|
|
279
|
+
return true; // Found a path back, there would be a cycle
|
|
280
|
+
}
|
|
281
|
+
if (visited.has(current)) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
visited.add(current);
|
|
285
|
+
|
|
286
|
+
// Find all outgoing edges from current node
|
|
287
|
+
for (const edge of edges) {
|
|
288
|
+
if (edge.from === current && !visited.has(edge.to)) {
|
|
289
|
+
queue.push(edge.to);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export type CanvasSide = 'top' | 'right' | 'bottom' | 'left';
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Compute optimal edge sides based on relative node positions.
|
|
301
|
+
* Returns the best fromSide and toSide for connecting two nodes.
|
|
302
|
+
*/
|
|
303
|
+
export function computeOptimalEdgeSides(
|
|
304
|
+
fromPosition: { x: number; y: number },
|
|
305
|
+
toPosition: { x: number; y: number }
|
|
306
|
+
): { fromSide: CanvasSide; toSide: CanvasSide } {
|
|
307
|
+
const dx = toPosition.x - fromPosition.x;
|
|
308
|
+
const dy = toPosition.y - fromPosition.y;
|
|
309
|
+
|
|
310
|
+
// Determine primary direction based on which axis has larger delta
|
|
311
|
+
const isHorizontalDominant = Math.abs(dx) > Math.abs(dy);
|
|
312
|
+
|
|
313
|
+
if (isHorizontalDominant) {
|
|
314
|
+
// Horizontal connection
|
|
315
|
+
if (dx > 0) {
|
|
316
|
+
// Target is to the right of source
|
|
317
|
+
return { fromSide: 'right', toSide: 'left' };
|
|
318
|
+
} else {
|
|
319
|
+
// Target is to the left of source
|
|
320
|
+
return { fromSide: 'left', toSide: 'right' };
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
// Vertical connection
|
|
324
|
+
if (dy > 0) {
|
|
325
|
+
// Target is below source
|
|
326
|
+
return { fromSide: 'bottom', toSide: 'top' };
|
|
327
|
+
} else {
|
|
328
|
+
// Target is above source
|
|
329
|
+
return { fromSide: 'top', toSide: 'bottom' };
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|