@principal-ai/principal-view-react 0.14.30 → 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 +2 -0
- package/dist/components/SequenceDiagramRenderer.d.ts.map +1 -1
- package/dist/components/SequenceDiagramRenderer.js +85 -39
- package/dist/components/SequenceDiagramRenderer.js.map +1 -1
- package/dist/components/WorkflowSequenceDiagram.d.ts +3 -1
- package/dist/components/WorkflowSequenceDiagram.d.ts.map +1 -1
- package/dist/components/WorkflowSequenceDiagram.js +3 -2
- package/dist/components/WorkflowSequenceDiagram.js.map +1 -1
- package/package.json +1 -1
- package/src/components/SequenceDiagramRenderer.tsx +114 -43
- package/src/components/WorkflowSequenceDiagram.tsx +5 -0
- package/src/stories/WorkflowSequenceDiagram.stories.tsx +777 -0
|
@@ -36,42 +36,74 @@ import {
|
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Minimal marker node for arrow-centric sequence diagrams
|
|
39
|
-
* Invisible - just used for positioning,
|
|
40
|
-
*
|
|
39
|
+
* Invisible - just used for positioning, selection is shown on edge labels
|
|
40
|
+
* Can optionally show a label for better clickability
|
|
41
41
|
*/
|
|
42
42
|
function SequenceMarkerNode({ data, selected }: NodeProps) {
|
|
43
43
|
const { theme } = useTheme();
|
|
44
|
+
const showLabel = data.showEventLabels === true; // Only show if explicitly enabled
|
|
45
|
+
const label = data.label as string | undefined;
|
|
44
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)
|
|
45
97
|
return (
|
|
46
98
|
<div
|
|
47
99
|
style={{
|
|
48
100
|
width: '100%',
|
|
49
101
|
height: '100%',
|
|
50
102
|
opacity: 0,
|
|
51
|
-
|
|
103
|
+
cursor: 'pointer',
|
|
52
104
|
}}
|
|
53
105
|
title={data.fullName as string}
|
|
54
106
|
>
|
|
55
|
-
{/* Show indicator when selected */}
|
|
56
|
-
{selected && (
|
|
57
|
-
<div
|
|
58
|
-
style={{
|
|
59
|
-
position: 'absolute',
|
|
60
|
-
top: '50%',
|
|
61
|
-
left: '50%',
|
|
62
|
-
transform: 'translate(-50%, -50%)',
|
|
63
|
-
width: '12px',
|
|
64
|
-
height: '12px',
|
|
65
|
-
borderRadius: '50%',
|
|
66
|
-
backgroundColor: theme.colors.primary,
|
|
67
|
-
border: `2px solid ${theme.colors.background}`,
|
|
68
|
-
boxShadow: `0 0 0 2px ${theme.colors.primary}40`,
|
|
69
|
-
opacity: 1,
|
|
70
|
-
zIndex: 10,
|
|
71
|
-
pointerEvents: 'none',
|
|
72
|
-
}}
|
|
73
|
-
/>
|
|
74
|
-
)}
|
|
75
107
|
<Handle
|
|
76
108
|
type="target"
|
|
77
109
|
position={Position.Top}
|
|
@@ -104,6 +136,7 @@ function SequenceArrowEdge({
|
|
|
104
136
|
|
|
105
137
|
// Use a straight line for same-lane, bezier for cross-lane
|
|
106
138
|
const isSameLane = !data?.crossesLanes;
|
|
139
|
+
const isSourceSelected = data?.isSourceSelected === true;
|
|
107
140
|
|
|
108
141
|
const [edgePath, labelX, labelY] = getBezierPath({
|
|
109
142
|
sourceX,
|
|
@@ -132,16 +165,19 @@ function SequenceArrowEdge({
|
|
|
132
165
|
style={{
|
|
133
166
|
position: 'absolute',
|
|
134
167
|
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
|
135
|
-
background: theme.colors.background,
|
|
136
|
-
padding: '2px 8px',
|
|
168
|
+
background: isSourceSelected ? theme.colors.primary : theme.colors.background,
|
|
169
|
+
padding: isSourceSelected ? '4px 10px' : '2px 8px',
|
|
137
170
|
borderRadius: 4,
|
|
138
171
|
fontSize: theme.fontSizes[0],
|
|
139
|
-
fontWeight: theme.fontWeights.medium,
|
|
172
|
+
fontWeight: isSourceSelected ? theme.fontWeights.bold : theme.fontWeights.medium,
|
|
140
173
|
fontFamily: theme.fonts.body,
|
|
141
|
-
color: theme.colors.text,
|
|
142
|
-
border:
|
|
174
|
+
color: isSourceSelected ? theme.colors.background : theme.colors.text,
|
|
175
|
+
border: `${isSourceSelected ? 2 : 1}px solid ${isSourceSelected ? theme.colors.primary : theme.colors.border}`,
|
|
143
176
|
pointerEvents: 'all',
|
|
144
177
|
whiteSpace: 'nowrap',
|
|
178
|
+
cursor: 'pointer',
|
|
179
|
+
transition: 'all 0.2s ease',
|
|
180
|
+
boxShadow: isSourceSelected ? `0 2px 8px ${theme.colors.primary}60` : 'none',
|
|
145
181
|
}}
|
|
146
182
|
className="nodrag nopan"
|
|
147
183
|
>
|
|
@@ -178,6 +214,7 @@ function SequenceArrowParticipantEdge({
|
|
|
178
214
|
|
|
179
215
|
// Style based on whether it's a move event (IPC) or transform event (internal)
|
|
180
216
|
const isMoveEvent = data?.isMoveEvent === true;
|
|
217
|
+
const isSourceSelected = data?.isSourceSelected === true;
|
|
181
218
|
const strokeColor = isMoveEvent ? (theme.colors.accent || '#f48771') : theme.colors.primary;
|
|
182
219
|
|
|
183
220
|
// Same lane: render as activation bar
|
|
@@ -224,16 +261,19 @@ function SequenceArrowParticipantEdge({
|
|
|
224
261
|
style={{
|
|
225
262
|
position: 'absolute',
|
|
226
263
|
transform: `translate(0, -50%) translate(${sourceX + 15}px,${barY + barHeight / 2}px)`,
|
|
227
|
-
background: theme.colors.background,
|
|
228
|
-
padding: '2px 8px',
|
|
264
|
+
background: isSourceSelected ? strokeColor : theme.colors.background,
|
|
265
|
+
padding: isSourceSelected ? '4px 10px' : '2px 8px',
|
|
229
266
|
borderRadius: 4,
|
|
230
267
|
fontSize: theme.fontSizes[0],
|
|
231
|
-
fontWeight: theme.fontWeights.medium,
|
|
268
|
+
fontWeight: isSourceSelected ? theme.fontWeights.bold : theme.fontWeights.medium,
|
|
232
269
|
fontFamily: theme.fonts.body,
|
|
233
|
-
color: strokeColor,
|
|
234
|
-
border:
|
|
270
|
+
color: isSourceSelected ? theme.colors.background : strokeColor,
|
|
271
|
+
border: `${isSourceSelected ? 2 : 1}px solid ${strokeColor}`,
|
|
235
272
|
pointerEvents: 'all',
|
|
236
273
|
whiteSpace: 'nowrap',
|
|
274
|
+
cursor: 'pointer',
|
|
275
|
+
transition: 'all 0.2s ease',
|
|
276
|
+
boxShadow: isSourceSelected ? `0 2px 8px ${strokeColor}60` : 'none',
|
|
237
277
|
}}
|
|
238
278
|
className="nodrag nopan"
|
|
239
279
|
>
|
|
@@ -272,16 +312,19 @@ function SequenceArrowParticipantEdge({
|
|
|
272
312
|
style={{
|
|
273
313
|
position: 'absolute',
|
|
274
314
|
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY - 12}px)`,
|
|
275
|
-
background: theme.colors.background,
|
|
276
|
-
padding: isMoveEvent ? '3px 10px' : '2px 8px',
|
|
315
|
+
background: isSourceSelected ? strokeColor : theme.colors.background,
|
|
316
|
+
padding: isSourceSelected ? '5px 12px' : (isMoveEvent ? '3px 10px' : '2px 8px'),
|
|
277
317
|
borderRadius: 4,
|
|
278
318
|
fontSize: theme.fontSizes[0],
|
|
279
|
-
fontWeight: isMoveEvent ? theme.fontWeights.bold : theme.fontWeights.medium,
|
|
319
|
+
fontWeight: isSourceSelected ? theme.fontWeights.bold : (isMoveEvent ? theme.fontWeights.bold : theme.fontWeights.medium),
|
|
280
320
|
fontFamily: theme.fonts.body,
|
|
281
|
-
color: strokeColor,
|
|
282
|
-
border:
|
|
321
|
+
color: isSourceSelected ? theme.colors.background : strokeColor,
|
|
322
|
+
border: `${isSourceSelected ? 3 : (isMoveEvent ? 2 : 1)}px solid ${strokeColor}`,
|
|
283
323
|
pointerEvents: 'all',
|
|
284
324
|
whiteSpace: 'nowrap',
|
|
325
|
+
cursor: 'pointer',
|
|
326
|
+
transition: 'all 0.2s ease',
|
|
327
|
+
boxShadow: isSourceSelected ? `0 4px 12px ${strokeColor}60` : 'none',
|
|
285
328
|
}}
|
|
286
329
|
className="nodrag nopan"
|
|
287
330
|
>
|
|
@@ -501,6 +544,9 @@ export interface SequenceDiagramRendererProps {
|
|
|
501
544
|
|
|
502
545
|
/** Whether swimlane headers should stick to the top when scrolling vertically (default: true) */
|
|
503
546
|
stickyHeaders?: boolean;
|
|
547
|
+
|
|
548
|
+
/** Whether to show event labels on nodes (default: false, labels already shown on edges) */
|
|
549
|
+
showEventLabels?: boolean;
|
|
504
550
|
}
|
|
505
551
|
|
|
506
552
|
/**
|
|
@@ -518,6 +564,7 @@ function SequenceDiagramInner({
|
|
|
518
564
|
showBackground = false, // Default to false since swimlanes provide visual structure
|
|
519
565
|
stickyHeaders = true,
|
|
520
566
|
selectedNodeId,
|
|
567
|
+
showEventLabels = false, // Default to false - labels already shown on edges
|
|
521
568
|
}: SequenceDiagramRendererProps) {
|
|
522
569
|
const { theme } = useTheme();
|
|
523
570
|
|
|
@@ -541,15 +588,28 @@ function SequenceDiagramInner({
|
|
|
541
588
|
layoutOptions
|
|
542
589
|
);
|
|
543
590
|
|
|
544
|
-
// Mark selected node
|
|
591
|
+
// Mark selected node and add showEventLabels to node data
|
|
545
592
|
const nodes = useMemo(() => {
|
|
546
|
-
if (!selectedNodeId) return layoutNodes;
|
|
547
|
-
|
|
548
593
|
return layoutNodes.map(node => ({
|
|
549
594
|
...node,
|
|
550
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
|
+
},
|
|
551
611
|
}));
|
|
552
|
-
}, [
|
|
612
|
+
}, [edges, selectedNodeId]);
|
|
553
613
|
|
|
554
614
|
// Handle node click
|
|
555
615
|
const handleNodeClick = useCallback(
|
|
@@ -559,6 +619,16 @@ function SequenceDiagramInner({
|
|
|
559
619
|
[onNodeClick]
|
|
560
620
|
);
|
|
561
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
|
+
|
|
562
632
|
// When sticky headers are enabled, limit upward panning to prevent headers from disconnecting
|
|
563
633
|
// from swimlane backgrounds. Allow downward panning to see all content.
|
|
564
634
|
const translateExtent = useMemo(() => {
|
|
@@ -600,10 +670,11 @@ function SequenceDiagramInner({
|
|
|
600
670
|
return (
|
|
601
671
|
<ReactFlow
|
|
602
672
|
nodes={nodes}
|
|
603
|
-
edges={
|
|
673
|
+
edges={edgesWithSelection}
|
|
604
674
|
nodeTypes={nodeTypes}
|
|
605
675
|
edgeTypes={edgeTypes}
|
|
606
676
|
onNodeClick={handleNodeClick}
|
|
677
|
+
onEdgeClick={handleEdgeClick}
|
|
607
678
|
{...viewportConfig}
|
|
608
679
|
minZoom={0.1}
|
|
609
680
|
maxZoom={2}
|
|
@@ -45,6 +45,9 @@ export interface WorkflowSequenceDiagramProps {
|
|
|
45
45
|
|
|
46
46
|
/** Currently selected event index (0-based) for visual highlighting */
|
|
47
47
|
selectedEventIndex?: number;
|
|
48
|
+
|
|
49
|
+
/** Whether to show event labels on nodes (default: false, labels already shown on edges) */
|
|
50
|
+
showEventLabels?: boolean;
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
/**
|
|
@@ -188,6 +191,7 @@ export function WorkflowSequenceDiagram({
|
|
|
188
191
|
className,
|
|
189
192
|
onEventIndexChange,
|
|
190
193
|
selectedEventIndex,
|
|
194
|
+
showEventLabels = false, // Default to false - labels already on edges
|
|
191
195
|
}: WorkflowSequenceDiagramProps) {
|
|
192
196
|
// Convert workflow to sequence format
|
|
193
197
|
const { events, edges } = useMemo(
|
|
@@ -248,6 +252,7 @@ export function WorkflowSequenceDiagram({
|
|
|
248
252
|
className={className}
|
|
249
253
|
onNodeClick={onEventIndexChange ? handleNodeClick : undefined}
|
|
250
254
|
selectedNodeId={selectedNodeId}
|
|
255
|
+
showEventLabels={showEventLabels}
|
|
251
256
|
/>
|
|
252
257
|
);
|
|
253
258
|
}
|