@principal-ai/principal-view-react 0.14.29 → 0.14.31
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/SequenceDiagramRenderer.d.ts +4 -0
- package/dist/components/SequenceDiagramRenderer.d.ts.map +1 -1
- package/dist/components/SequenceDiagramRenderer.js +92 -20
- package/dist/components/SequenceDiagramRenderer.js.map +1 -1
- package/dist/components/WorkflowSequenceDiagram.d.ts +5 -1
- package/dist/components/WorkflowSequenceDiagram.d.ts.map +1 -1
- package/dist/components/WorkflowSequenceDiagram.js +9 -2
- package/dist/components/WorkflowSequenceDiagram.js.map +1 -1
- package/package.json +1 -1
- package/src/components/SequenceDiagramRenderer.tsx +128 -19
- package/src/components/WorkflowSequenceDiagram.tsx +16 -0
- package/src/stories/WorkflowSequenceDiagram.stories.tsx +777 -0
|
@@ -36,15 +36,71 @@ import {
|
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Minimal marker node for arrow-centric sequence diagrams
|
|
39
|
-
* Invisible - just used for positioning,
|
|
39
|
+
* Invisible - just used for positioning, selection is shown on edge labels
|
|
40
|
+
* Can optionally show a label for better clickability
|
|
40
41
|
*/
|
|
41
|
-
function SequenceMarkerNode({ data }: NodeProps) {
|
|
42
|
+
function SequenceMarkerNode({ data, selected }: NodeProps) {
|
|
43
|
+
const { theme } = useTheme();
|
|
44
|
+
const showLabel = data.showEventLabels === true; // Only show if explicitly enabled
|
|
45
|
+
const label = data.label as string | undefined;
|
|
46
|
+
|
|
47
|
+
// If labels are shown on nodes, render them
|
|
48
|
+
if (showLabel && label) {
|
|
49
|
+
return (
|
|
50
|
+
<div
|
|
51
|
+
style={{
|
|
52
|
+
width: '100%',
|
|
53
|
+
height: '100%',
|
|
54
|
+
position: 'relative',
|
|
55
|
+
cursor: 'pointer',
|
|
56
|
+
display: 'flex',
|
|
57
|
+
flexDirection: 'column',
|
|
58
|
+
alignItems: 'center',
|
|
59
|
+
justifyContent: 'center',
|
|
60
|
+
}}
|
|
61
|
+
title={data.fullName as string}
|
|
62
|
+
>
|
|
63
|
+
<div
|
|
64
|
+
style={{
|
|
65
|
+
padding: '6px 12px',
|
|
66
|
+
background: selected ? theme.colors.primary : theme.colors.background,
|
|
67
|
+
color: selected ? theme.colors.background : theme.colors.text,
|
|
68
|
+
border: `2px solid ${selected ? theme.colors.primary : theme.colors.border}`,
|
|
69
|
+
borderRadius: 4,
|
|
70
|
+
fontSize: theme.fontSizes[1],
|
|
71
|
+
fontWeight: selected ? theme.fontWeights.bold : theme.fontWeights.medium,
|
|
72
|
+
fontFamily: theme.fonts.body,
|
|
73
|
+
whiteSpace: 'nowrap',
|
|
74
|
+
pointerEvents: 'none',
|
|
75
|
+
userSelect: 'none',
|
|
76
|
+
transition: 'all 0.2s ease',
|
|
77
|
+
boxShadow: selected ? `0 2px 8px ${theme.colors.primary}40` : 'none',
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
{label}
|
|
81
|
+
</div>
|
|
82
|
+
<Handle
|
|
83
|
+
type="target"
|
|
84
|
+
position={Position.Top}
|
|
85
|
+
style={{ visibility: 'hidden' }}
|
|
86
|
+
/>
|
|
87
|
+
<Handle
|
|
88
|
+
type="source"
|
|
89
|
+
position={Position.Bottom}
|
|
90
|
+
style={{ visibility: 'hidden' }}
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Default: invisible node (selection shown on edge labels)
|
|
42
97
|
return (
|
|
43
98
|
<div
|
|
44
99
|
style={{
|
|
45
100
|
width: '100%',
|
|
46
101
|
height: '100%',
|
|
47
102
|
opacity: 0,
|
|
103
|
+
cursor: 'pointer',
|
|
48
104
|
}}
|
|
49
105
|
title={data.fullName as string}
|
|
50
106
|
>
|
|
@@ -80,6 +136,7 @@ function SequenceArrowEdge({
|
|
|
80
136
|
|
|
81
137
|
// Use a straight line for same-lane, bezier for cross-lane
|
|
82
138
|
const isSameLane = !data?.crossesLanes;
|
|
139
|
+
const isSourceSelected = data?.isSourceSelected === true;
|
|
83
140
|
|
|
84
141
|
const [edgePath, labelX, labelY] = getBezierPath({
|
|
85
142
|
sourceX,
|
|
@@ -108,16 +165,19 @@ function SequenceArrowEdge({
|
|
|
108
165
|
style={{
|
|
109
166
|
position: 'absolute',
|
|
110
167
|
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
|
111
|
-
background: theme.colors.background,
|
|
112
|
-
padding: '2px 8px',
|
|
168
|
+
background: isSourceSelected ? theme.colors.primary : theme.colors.background,
|
|
169
|
+
padding: isSourceSelected ? '4px 10px' : '2px 8px',
|
|
113
170
|
borderRadius: 4,
|
|
114
171
|
fontSize: theme.fontSizes[0],
|
|
115
|
-
fontWeight: theme.fontWeights.medium,
|
|
172
|
+
fontWeight: isSourceSelected ? theme.fontWeights.bold : theme.fontWeights.medium,
|
|
116
173
|
fontFamily: theme.fonts.body,
|
|
117
|
-
color: theme.colors.text,
|
|
118
|
-
border:
|
|
174
|
+
color: isSourceSelected ? theme.colors.background : theme.colors.text,
|
|
175
|
+
border: `${isSourceSelected ? 2 : 1}px solid ${isSourceSelected ? theme.colors.primary : theme.colors.border}`,
|
|
119
176
|
pointerEvents: 'all',
|
|
120
177
|
whiteSpace: 'nowrap',
|
|
178
|
+
cursor: 'pointer',
|
|
179
|
+
transition: 'all 0.2s ease',
|
|
180
|
+
boxShadow: isSourceSelected ? `0 2px 8px ${theme.colors.primary}60` : 'none',
|
|
121
181
|
}}
|
|
122
182
|
className="nodrag nopan"
|
|
123
183
|
>
|
|
@@ -154,6 +214,7 @@ function SequenceArrowParticipantEdge({
|
|
|
154
214
|
|
|
155
215
|
// Style based on whether it's a move event (IPC) or transform event (internal)
|
|
156
216
|
const isMoveEvent = data?.isMoveEvent === true;
|
|
217
|
+
const isSourceSelected = data?.isSourceSelected === true;
|
|
157
218
|
const strokeColor = isMoveEvent ? (theme.colors.accent || '#f48771') : theme.colors.primary;
|
|
158
219
|
|
|
159
220
|
// Same lane: render as activation bar
|
|
@@ -200,16 +261,19 @@ function SequenceArrowParticipantEdge({
|
|
|
200
261
|
style={{
|
|
201
262
|
position: 'absolute',
|
|
202
263
|
transform: `translate(0, -50%) translate(${sourceX + 15}px,${barY + barHeight / 2}px)`,
|
|
203
|
-
background: theme.colors.background,
|
|
204
|
-
padding: '2px 8px',
|
|
264
|
+
background: isSourceSelected ? strokeColor : theme.colors.background,
|
|
265
|
+
padding: isSourceSelected ? '4px 10px' : '2px 8px',
|
|
205
266
|
borderRadius: 4,
|
|
206
267
|
fontSize: theme.fontSizes[0],
|
|
207
|
-
fontWeight: theme.fontWeights.medium,
|
|
268
|
+
fontWeight: isSourceSelected ? theme.fontWeights.bold : theme.fontWeights.medium,
|
|
208
269
|
fontFamily: theme.fonts.body,
|
|
209
|
-
color: strokeColor,
|
|
210
|
-
border:
|
|
270
|
+
color: isSourceSelected ? theme.colors.background : strokeColor,
|
|
271
|
+
border: `${isSourceSelected ? 2 : 1}px solid ${strokeColor}`,
|
|
211
272
|
pointerEvents: 'all',
|
|
212
273
|
whiteSpace: 'nowrap',
|
|
274
|
+
cursor: 'pointer',
|
|
275
|
+
transition: 'all 0.2s ease',
|
|
276
|
+
boxShadow: isSourceSelected ? `0 2px 8px ${strokeColor}60` : 'none',
|
|
213
277
|
}}
|
|
214
278
|
className="nodrag nopan"
|
|
215
279
|
>
|
|
@@ -248,16 +312,19 @@ function SequenceArrowParticipantEdge({
|
|
|
248
312
|
style={{
|
|
249
313
|
position: 'absolute',
|
|
250
314
|
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY - 12}px)`,
|
|
251
|
-
background: theme.colors.background,
|
|
252
|
-
padding: isMoveEvent ? '3px 10px' : '2px 8px',
|
|
315
|
+
background: isSourceSelected ? strokeColor : theme.colors.background,
|
|
316
|
+
padding: isSourceSelected ? '5px 12px' : (isMoveEvent ? '3px 10px' : '2px 8px'),
|
|
253
317
|
borderRadius: 4,
|
|
254
318
|
fontSize: theme.fontSizes[0],
|
|
255
|
-
fontWeight: isMoveEvent ? theme.fontWeights.bold : theme.fontWeights.medium,
|
|
319
|
+
fontWeight: isSourceSelected ? theme.fontWeights.bold : (isMoveEvent ? theme.fontWeights.bold : theme.fontWeights.medium),
|
|
256
320
|
fontFamily: theme.fonts.body,
|
|
257
|
-
color: strokeColor,
|
|
258
|
-
border:
|
|
321
|
+
color: isSourceSelected ? theme.colors.background : strokeColor,
|
|
322
|
+
border: `${isSourceSelected ? 3 : (isMoveEvent ? 2 : 1)}px solid ${strokeColor}`,
|
|
259
323
|
pointerEvents: 'all',
|
|
260
324
|
whiteSpace: 'nowrap',
|
|
325
|
+
cursor: 'pointer',
|
|
326
|
+
transition: 'all 0.2s ease',
|
|
327
|
+
boxShadow: isSourceSelected ? `0 4px 12px ${strokeColor}60` : 'none',
|
|
261
328
|
}}
|
|
262
329
|
className="nodrag nopan"
|
|
263
330
|
>
|
|
@@ -457,6 +524,9 @@ export interface SequenceDiagramRendererProps {
|
|
|
457
524
|
/** Callback when a node is clicked */
|
|
458
525
|
onNodeClick?: (nodeId: string, event: React.MouseEvent) => void;
|
|
459
526
|
|
|
527
|
+
/** ID of the currently selected node (for visual highlighting) */
|
|
528
|
+
selectedNodeId?: string;
|
|
529
|
+
|
|
460
530
|
/** Optional class name */
|
|
461
531
|
className?: string;
|
|
462
532
|
|
|
@@ -474,6 +544,9 @@ export interface SequenceDiagramRendererProps {
|
|
|
474
544
|
|
|
475
545
|
/** Whether swimlane headers should stick to the top when scrolling vertically (default: true) */
|
|
476
546
|
stickyHeaders?: boolean;
|
|
547
|
+
|
|
548
|
+
/** Whether to show event labels on nodes (default: false, labels already shown on edges) */
|
|
549
|
+
showEventLabels?: boolean;
|
|
477
550
|
}
|
|
478
551
|
|
|
479
552
|
/**
|
|
@@ -490,6 +563,8 @@ function SequenceDiagramInner({
|
|
|
490
563
|
showControls = true,
|
|
491
564
|
showBackground = false, // Default to false since swimlanes provide visual structure
|
|
492
565
|
stickyHeaders = true,
|
|
566
|
+
selectedNodeId,
|
|
567
|
+
showEventLabels = false, // Default to false - labels already shown on edges
|
|
493
568
|
}: SequenceDiagramRendererProps) {
|
|
494
569
|
const { theme } = useTheme();
|
|
495
570
|
|
|
@@ -507,12 +582,35 @@ function SequenceDiagramInner({
|
|
|
507
582
|
);
|
|
508
583
|
|
|
509
584
|
// Compute layout
|
|
510
|
-
const { nodes, edges, swimlanes, totalHeight } = useSequenceLayout(
|
|
585
|
+
const { nodes: layoutNodes, edges, swimlanes, totalHeight } = useSequenceLayout(
|
|
511
586
|
events,
|
|
512
587
|
sequenceEdges,
|
|
513
588
|
layoutOptions
|
|
514
589
|
);
|
|
515
590
|
|
|
591
|
+
// Mark selected node and add showEventLabels to node data
|
|
592
|
+
const nodes = useMemo(() => {
|
|
593
|
+
return layoutNodes.map(node => ({
|
|
594
|
+
...node,
|
|
595
|
+
selected: node.id === selectedNodeId,
|
|
596
|
+
data: {
|
|
597
|
+
...node.data,
|
|
598
|
+
showEventLabels,
|
|
599
|
+
},
|
|
600
|
+
}));
|
|
601
|
+
}, [layoutNodes, selectedNodeId, showEventLabels]);
|
|
602
|
+
|
|
603
|
+
// Add selectedNodeId to edge data so edges can highlight their labels
|
|
604
|
+
const edgesWithSelection = useMemo(() => {
|
|
605
|
+
return edges.map(edge => ({
|
|
606
|
+
...edge,
|
|
607
|
+
data: {
|
|
608
|
+
...edge.data,
|
|
609
|
+
isSourceSelected: edge.source === selectedNodeId,
|
|
610
|
+
},
|
|
611
|
+
}));
|
|
612
|
+
}, [edges, selectedNodeId]);
|
|
613
|
+
|
|
516
614
|
// Handle node click
|
|
517
615
|
const handleNodeClick = useCallback(
|
|
518
616
|
(_event: React.MouseEvent, node: { id: string }) => {
|
|
@@ -521,6 +619,16 @@ function SequenceDiagramInner({
|
|
|
521
619
|
[onNodeClick]
|
|
522
620
|
);
|
|
523
621
|
|
|
622
|
+
// Handle edge click - extract source event from edge and trigger node selection
|
|
623
|
+
const handleEdgeClick = useCallback(
|
|
624
|
+
(_event: React.MouseEvent, edge: { id: string; source: string; target: string }) => {
|
|
625
|
+
// When clicking an edge, select the source event (the label describes the source)
|
|
626
|
+
// The edge label comes from the source event, so clicking it should select that event
|
|
627
|
+
onNodeClick?.(edge.source, _event);
|
|
628
|
+
},
|
|
629
|
+
[onNodeClick]
|
|
630
|
+
);
|
|
631
|
+
|
|
524
632
|
// When sticky headers are enabled, limit upward panning to prevent headers from disconnecting
|
|
525
633
|
// from swimlane backgrounds. Allow downward panning to see all content.
|
|
526
634
|
const translateExtent = useMemo(() => {
|
|
@@ -562,10 +670,11 @@ function SequenceDiagramInner({
|
|
|
562
670
|
return (
|
|
563
671
|
<ReactFlow
|
|
564
672
|
nodes={nodes}
|
|
565
|
-
edges={
|
|
673
|
+
edges={edgesWithSelection}
|
|
566
674
|
nodeTypes={nodeTypes}
|
|
567
675
|
edgeTypes={edgeTypes}
|
|
568
676
|
onNodeClick={handleNodeClick}
|
|
677
|
+
onEdgeClick={handleEdgeClick}
|
|
569
678
|
{...viewportConfig}
|
|
570
679
|
minZoom={0.1}
|
|
571
680
|
maxZoom={2}
|
|
@@ -42,6 +42,12 @@ export interface WorkflowSequenceDiagramProps {
|
|
|
42
42
|
|
|
43
43
|
/** Callback when an event node is clicked - receives the event index (0-based) */
|
|
44
44
|
onEventIndexChange?: (eventIndex: number) => void;
|
|
45
|
+
|
|
46
|
+
/** Currently selected event index (0-based) for visual highlighting */
|
|
47
|
+
selectedEventIndex?: number;
|
|
48
|
+
|
|
49
|
+
/** Whether to show event labels on nodes (default: false, labels already shown on edges) */
|
|
50
|
+
showEventLabels?: boolean;
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
/**
|
|
@@ -184,6 +190,8 @@ export function WorkflowSequenceDiagram({
|
|
|
184
190
|
showBackground = false,
|
|
185
191
|
className,
|
|
186
192
|
onEventIndexChange,
|
|
193
|
+
selectedEventIndex,
|
|
194
|
+
showEventLabels = false, // Default to false - labels already on edges
|
|
187
195
|
}: WorkflowSequenceDiagramProps) {
|
|
188
196
|
// Convert workflow to sequence format
|
|
189
197
|
const { events, edges } = useMemo(
|
|
@@ -206,6 +214,12 @@ export function WorkflowSequenceDiagram({
|
|
|
206
214
|
[onEventIndexChange]
|
|
207
215
|
);
|
|
208
216
|
|
|
217
|
+
// Convert selected event index to node ID
|
|
218
|
+
const selectedNodeId = useMemo(() => {
|
|
219
|
+
if (selectedEventIndex === undefined || selectedEventIndex === null) return undefined;
|
|
220
|
+
return `event-${selectedEventIndex}`;
|
|
221
|
+
}, [selectedEventIndex]);
|
|
222
|
+
|
|
209
223
|
// If no events, show empty state
|
|
210
224
|
if (events.length === 0) {
|
|
211
225
|
return (
|
|
@@ -237,6 +251,8 @@ export function WorkflowSequenceDiagram({
|
|
|
237
251
|
showBackground={showBackground}
|
|
238
252
|
className={className}
|
|
239
253
|
onNodeClick={onEventIndexChange ? handleNodeClick : undefined}
|
|
254
|
+
selectedNodeId={selectedNodeId}
|
|
255
|
+
showEventLabels={showEventLabels}
|
|
240
256
|
/>
|
|
241
257
|
);
|
|
242
258
|
}
|