@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
package/src/edges/CustomEdge.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
-
import { BaseEdge, EdgeLabelRenderer,
|
|
3
|
-
import type { EdgeProps } from '@xyflow/react';
|
|
2
|
+
import { BaseEdge, EdgeLabelRenderer, getSmoothStepPath } from '@xyflow/react';
|
|
3
|
+
import type { EdgeProps, Edge } from '@xyflow/react';
|
|
4
4
|
import type { EdgeTypeDefinition } from '@principal-ai/principal-view-core';
|
|
5
5
|
import { useTheme } from '@principal-ade/industry-theme';
|
|
6
6
|
|
|
@@ -22,8 +22,7 @@ export interface CustomEdgeData extends Record<string, unknown> {
|
|
|
22
22
|
/**
|
|
23
23
|
* Custom edge component for xyflow that renders based on EdgeTypeDefinition
|
|
24
24
|
*/
|
|
25
|
-
|
|
26
|
-
export const CustomEdge: React.FC<EdgeProps<any>> = ({
|
|
25
|
+
export const CustomEdge: React.FC<EdgeProps<Edge<CustomEdgeData>>> = ({
|
|
27
26
|
id,
|
|
28
27
|
sourceX,
|
|
29
28
|
sourceY,
|
|
@@ -36,7 +35,7 @@ export const CustomEdge: React.FC<EdgeProps<any>> = ({
|
|
|
36
35
|
selected,
|
|
37
36
|
}) => {
|
|
38
37
|
const { theme } = useTheme();
|
|
39
|
-
const edgeProps = data
|
|
38
|
+
const edgeProps = data;
|
|
40
39
|
const {
|
|
41
40
|
typeDefinition,
|
|
42
41
|
hasViolations,
|
|
@@ -78,14 +77,16 @@ export const CustomEdge: React.FC<EdgeProps<any>> = ({
|
|
|
78
77
|
const color = hasViolations ? '#D0021B' : edgeColor || typeDefinition.color || '#888';
|
|
79
78
|
const width = typeDefinition.width || 2;
|
|
80
79
|
|
|
81
|
-
// Get
|
|
82
|
-
const [edgePath, labelX, labelY] =
|
|
80
|
+
// Get SmoothStep path (orthogonal routing with rounded corners)
|
|
81
|
+
const [edgePath, labelX, labelY] = getSmoothStepPath({
|
|
83
82
|
sourceX,
|
|
84
83
|
sourceY,
|
|
85
84
|
sourcePosition,
|
|
86
85
|
targetX,
|
|
87
86
|
targetY,
|
|
88
87
|
targetPosition,
|
|
88
|
+
borderRadius: 0,
|
|
89
|
+
offset: 20,
|
|
89
90
|
});
|
|
90
91
|
|
|
91
92
|
// Style based on edge type
|
|
@@ -117,7 +117,7 @@ export function usePathBasedEvents({
|
|
|
117
117
|
const latestEvent = events[events.length - 1];
|
|
118
118
|
|
|
119
119
|
switch (latestEvent.type) {
|
|
120
|
-
case 'component-activity':
|
|
120
|
+
case 'component-activity': {
|
|
121
121
|
// Milestone 1: Component activity from log
|
|
122
122
|
// Check if log level is high enough to trigger animation
|
|
123
123
|
const levels = ['debug', 'info', 'warn', 'error'];
|
|
@@ -125,6 +125,7 @@ export function usePathBasedEvents({
|
|
|
125
125
|
processActivityEvent(latestEvent);
|
|
126
126
|
}
|
|
127
127
|
break;
|
|
128
|
+
}
|
|
128
129
|
|
|
129
130
|
case 'component-action':
|
|
130
131
|
// Milestone 2: Specific action from pattern match
|
package/src/index.ts
CHANGED
|
@@ -37,12 +37,6 @@ export type {
|
|
|
37
37
|
PendingChanges,
|
|
38
38
|
} from './components/GraphRenderer';
|
|
39
39
|
|
|
40
|
-
export { EdgeInfoPanel } from './components/EdgeInfoPanel';
|
|
41
|
-
export type { EdgeInfoPanelProps } from './components/EdgeInfoPanel';
|
|
42
|
-
|
|
43
|
-
export { NodeInfoPanel } from './components/NodeInfoPanel';
|
|
44
|
-
export type { NodeInfoPanelProps } from './components/NodeInfoPanel';
|
|
45
|
-
|
|
46
40
|
export { ConfigurationSelector } from './components/ConfigurationSelector';
|
|
47
41
|
export type { ConfigurationSelectorProps } from './components/ConfigurationSelector';
|
|
48
42
|
|
package/src/nodes/CustomNode.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState, useRef } from 'react';
|
|
2
2
|
import { Handle, Position, NodeResizer } from '@xyflow/react';
|
|
3
|
-
import type { NodeProps } from '@xyflow/react';
|
|
3
|
+
import type { NodeProps, Node } from '@xyflow/react';
|
|
4
4
|
import type { NodeTypeDefinition } from '@principal-ai/principal-view-core';
|
|
5
5
|
import { useTheme } from '@principal-ade/industry-theme';
|
|
6
6
|
import { resolveIcon } from '../utils/iconResolver';
|
|
@@ -61,12 +61,11 @@ export interface CustomNodeData extends Record<string, unknown> {
|
|
|
61
61
|
/**
|
|
62
62
|
* Custom node component for xyflow that renders based on NodeTypeDefinition
|
|
63
63
|
*/
|
|
64
|
-
|
|
65
|
-
export const CustomNode: React.FC<NodeProps<any>> = ({ data, selected, dragging }) => {
|
|
64
|
+
export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, selected, dragging }) => {
|
|
66
65
|
const { theme } = useTheme();
|
|
67
66
|
const [isHovered, setIsHovered] = useState(false);
|
|
68
67
|
const nodeRef = useRef<HTMLDivElement>(null);
|
|
69
|
-
const nodeProps = data
|
|
68
|
+
const nodeProps = data;
|
|
70
69
|
const {
|
|
71
70
|
typeDefinition,
|
|
72
71
|
state,
|
|
@@ -95,20 +94,56 @@ export const CustomNode: React.FC<NodeProps<any>> = ({ data, selected, dragging
|
|
|
95
94
|
const description = nodeData?.description as string | undefined;
|
|
96
95
|
const sources = nodeData?.sources as string[] | undefined;
|
|
97
96
|
|
|
97
|
+
// Get badge shape styles based on node shape
|
|
98
|
+
const getBadgeShapeStyles = (): React.CSSProperties => {
|
|
99
|
+
const shape = typeDefinition.shape;
|
|
100
|
+
const baseSize = 18;
|
|
101
|
+
|
|
102
|
+
switch (shape) {
|
|
103
|
+
case 'circle':
|
|
104
|
+
return {
|
|
105
|
+
width: baseSize,
|
|
106
|
+
height: baseSize,
|
|
107
|
+
borderRadius: '50%',
|
|
108
|
+
};
|
|
109
|
+
case 'diamond':
|
|
110
|
+
return {
|
|
111
|
+
width: baseSize - 4,
|
|
112
|
+
height: baseSize - 4,
|
|
113
|
+
borderRadius: 0,
|
|
114
|
+
transform: 'rotate(45deg)',
|
|
115
|
+
};
|
|
116
|
+
case 'hexagon':
|
|
117
|
+
return {
|
|
118
|
+
width: baseSize,
|
|
119
|
+
height: baseSize,
|
|
120
|
+
borderRadius: 0,
|
|
121
|
+
clipPath: 'polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%)',
|
|
122
|
+
};
|
|
123
|
+
case 'rectangle':
|
|
124
|
+
default:
|
|
125
|
+
return {
|
|
126
|
+
width: baseSize,
|
|
127
|
+
height: baseSize,
|
|
128
|
+
borderRadius: 0,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
98
133
|
// Render Sources badge (top-right)
|
|
99
134
|
const renderSourcesBadge = () => {
|
|
100
135
|
const sources = nodeData?.sources as string[] | undefined;
|
|
101
136
|
if (!sources || sources.length === 0) return null;
|
|
102
137
|
|
|
138
|
+
const shapeStyles = getBadgeShapeStyles();
|
|
139
|
+
|
|
103
140
|
return (
|
|
104
141
|
<div
|
|
105
142
|
style={{
|
|
106
143
|
position: 'absolute',
|
|
107
144
|
top: -6,
|
|
108
145
|
right: -6,
|
|
109
|
-
|
|
110
|
-
height: 18,
|
|
111
|
-
borderRadius: '50%',
|
|
146
|
+
...shapeStyles,
|
|
112
147
|
backgroundColor: '#10b981', // Green for sources
|
|
113
148
|
color: 'white',
|
|
114
149
|
fontSize: theme.fontSizes[0],
|
|
@@ -123,7 +158,7 @@ export const CustomNode: React.FC<NodeProps<any>> = ({ data, selected, dragging
|
|
|
123
158
|
}}
|
|
124
159
|
title={`Sources: ${sources.join(', ')}`}
|
|
125
160
|
>
|
|
126
|
-
S
|
|
161
|
+
<span style={{ transform: shapeStyles.transform ? 'rotate(-45deg)' : undefined }}>S</span>
|
|
127
162
|
</div>
|
|
128
163
|
);
|
|
129
164
|
};
|
|
@@ -152,15 +187,15 @@ export const CustomNode: React.FC<NodeProps<any>> = ({ data, selected, dragging
|
|
|
152
187
|
implemented: 'Implemented - Code exists',
|
|
153
188
|
};
|
|
154
189
|
|
|
190
|
+
const shapeStyles = getBadgeShapeStyles();
|
|
191
|
+
|
|
155
192
|
return (
|
|
156
193
|
<div
|
|
157
194
|
style={{
|
|
158
195
|
position: 'absolute',
|
|
159
196
|
top: -6,
|
|
160
197
|
left: -6,
|
|
161
|
-
|
|
162
|
-
height: 18,
|
|
163
|
-
borderRadius: '50%',
|
|
198
|
+
...shapeStyles,
|
|
164
199
|
backgroundColor: statusColors[status],
|
|
165
200
|
color: 'white',
|
|
166
201
|
fontSize: theme.fontSizes[0],
|
|
@@ -175,7 +210,9 @@ export const CustomNode: React.FC<NodeProps<any>> = ({ data, selected, dragging
|
|
|
175
210
|
}}
|
|
176
211
|
title={statusTitles[status]}
|
|
177
212
|
>
|
|
178
|
-
{
|
|
213
|
+
<span style={{ transform: shapeStyles.transform ? 'rotate(-45deg)' : undefined }}>
|
|
214
|
+
{statusLabels[status]}
|
|
215
|
+
</span>
|
|
179
216
|
</div>
|
|
180
217
|
);
|
|
181
218
|
};
|
|
@@ -340,7 +377,7 @@ export const CustomNode: React.FC<NodeProps<any>> = ({ data, selected, dragging
|
|
|
340
377
|
default:
|
|
341
378
|
return {
|
|
342
379
|
...baseStyles,
|
|
343
|
-
borderRadius: '
|
|
380
|
+
borderRadius: '0',
|
|
344
381
|
};
|
|
345
382
|
}
|
|
346
383
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
3
|
import { GraphRenderer } from '../components/GraphRenderer';
|
|
4
|
-
import type { ExtendedCanvas, GraphEvent } from '@principal-ai/principal-view-core';
|
|
4
|
+
import type { ExtendedCanvas, GraphEvent, ComponentLibrary } from '@principal-ai/principal-view-core';
|
|
5
5
|
import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
|
|
6
6
|
|
|
7
7
|
// Helper component that sets initial node states via events
|
|
@@ -27,6 +27,7 @@ const GraphWithInitialStates: React.FC<{
|
|
|
27
27
|
);
|
|
28
28
|
setEvents(stateEvents);
|
|
29
29
|
}
|
|
30
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally run only on mount
|
|
30
31
|
}, []);
|
|
31
32
|
|
|
32
33
|
return (
|
|
@@ -638,3 +639,257 @@ Higher priority always wins when multiple values are specified.
|
|
|
638
639
|
},
|
|
639
640
|
},
|
|
640
641
|
};
|
|
642
|
+
|
|
643
|
+
// ============================================================================
|
|
644
|
+
// Library Color Priority Story
|
|
645
|
+
// Priority: library.nodeComponents.color → node.color → pv.fill → state.color
|
|
646
|
+
// ============================================================================
|
|
647
|
+
|
|
648
|
+
const libraryColorTestLibrary: ComponentLibrary = {
|
|
649
|
+
version: '1.0.0',
|
|
650
|
+
name: 'Color Test Library',
|
|
651
|
+
description: 'Library for testing color inheritance',
|
|
652
|
+
resources: {},
|
|
653
|
+
nodeComponents: {
|
|
654
|
+
'service-red': {
|
|
655
|
+
description: 'Service with red color from library',
|
|
656
|
+
shape: 'rectangle',
|
|
657
|
+
color: '#FF0000', // RED from library
|
|
658
|
+
icon: 'Server',
|
|
659
|
+
},
|
|
660
|
+
'service-green': {
|
|
661
|
+
description: 'Service with green color from library',
|
|
662
|
+
shape: 'rectangle',
|
|
663
|
+
color: '#00FF00', // GREEN from library
|
|
664
|
+
icon: 'Database',
|
|
665
|
+
},
|
|
666
|
+
'service-blue': {
|
|
667
|
+
description: 'Service with blue color from library',
|
|
668
|
+
shape: 'rectangle',
|
|
669
|
+
color: '#0000FF', // BLUE from library
|
|
670
|
+
icon: 'Settings',
|
|
671
|
+
},
|
|
672
|
+
'service-purple': {
|
|
673
|
+
description: 'Service with purple color from library',
|
|
674
|
+
shape: 'rectangle',
|
|
675
|
+
color: '#8B00FF', // PURPLE from library
|
|
676
|
+
icon: 'Zap',
|
|
677
|
+
},
|
|
678
|
+
},
|
|
679
|
+
edgeComponents: {
|
|
680
|
+
'flow-orange': {
|
|
681
|
+
description: 'Flow edge with orange color from library',
|
|
682
|
+
style: 'solid',
|
|
683
|
+
color: '#FFA500', // ORANGE from library
|
|
684
|
+
directed: true,
|
|
685
|
+
},
|
|
686
|
+
'flow-cyan': {
|
|
687
|
+
description: 'Flow edge with cyan color from library',
|
|
688
|
+
style: 'solid',
|
|
689
|
+
color: '#00FFFF', // CYAN from library
|
|
690
|
+
directed: true,
|
|
691
|
+
},
|
|
692
|
+
},
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
const libraryColorPriorityCanvas: ExtendedCanvas = {
|
|
696
|
+
nodes: [
|
|
697
|
+
// 1. Library color only (nodeType references library, no other colors)
|
|
698
|
+
{
|
|
699
|
+
id: 'library-only',
|
|
700
|
+
type: 'text',
|
|
701
|
+
x: 100,
|
|
702
|
+
y: 100,
|
|
703
|
+
width: 180,
|
|
704
|
+
height: 100,
|
|
705
|
+
text: 'SOURCE: library\nEXPECT: RED\n(from service-red)',
|
|
706
|
+
pv: {
|
|
707
|
+
nodeType: 'service-red', // Should get RED from library
|
|
708
|
+
shape: 'rectangle',
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
// 2. Library color overridden by node.color
|
|
712
|
+
{
|
|
713
|
+
id: 'library-vs-node-color',
|
|
714
|
+
type: 'text',
|
|
715
|
+
x: 320,
|
|
716
|
+
y: 100,
|
|
717
|
+
width: 180,
|
|
718
|
+
height: 100,
|
|
719
|
+
text: 'SOURCE: node.color\nEXPECT: YELLOW\n(library GREEN ignored)',
|
|
720
|
+
color: '#FFFF00', // YELLOW - should override library GREEN
|
|
721
|
+
pv: {
|
|
722
|
+
nodeType: 'service-green', // Would be GREEN, but overridden
|
|
723
|
+
shape: 'rectangle',
|
|
724
|
+
},
|
|
725
|
+
},
|
|
726
|
+
// 3. Library color overridden by pv.fill
|
|
727
|
+
{
|
|
728
|
+
id: 'library-vs-pv-fill',
|
|
729
|
+
type: 'text',
|
|
730
|
+
x: 540,
|
|
731
|
+
y: 100,
|
|
732
|
+
width: 180,
|
|
733
|
+
height: 100,
|
|
734
|
+
text: 'SOURCE: pv.fill\nEXPECT: CYAN\n(library BLUE ignored)',
|
|
735
|
+
pv: {
|
|
736
|
+
nodeType: 'service-blue', // Would be BLUE, but overridden
|
|
737
|
+
shape: 'rectangle',
|
|
738
|
+
fill: '#00FFFF', // CYAN - should override library BLUE
|
|
739
|
+
},
|
|
740
|
+
},
|
|
741
|
+
// 4. Library color overridden by state color
|
|
742
|
+
{
|
|
743
|
+
id: 'library-vs-state',
|
|
744
|
+
type: 'text',
|
|
745
|
+
x: 760,
|
|
746
|
+
y: 100,
|
|
747
|
+
width: 180,
|
|
748
|
+
height: 100,
|
|
749
|
+
text: 'SOURCE: state.color\nEXPECT: ORANGE\n(library PURPLE ignored)',
|
|
750
|
+
pv: {
|
|
751
|
+
nodeType: 'service-purple', // Would be PURPLE, but overridden by state
|
|
752
|
+
shape: 'rectangle',
|
|
753
|
+
states: {
|
|
754
|
+
active: { label: 'Active', color: '#FFA500' }, // ORANGE
|
|
755
|
+
},
|
|
756
|
+
},
|
|
757
|
+
},
|
|
758
|
+
// 5. Another library color node for comparison
|
|
759
|
+
{
|
|
760
|
+
id: 'library-green',
|
|
761
|
+
type: 'text',
|
|
762
|
+
x: 100,
|
|
763
|
+
y: 250,
|
|
764
|
+
width: 180,
|
|
765
|
+
height: 100,
|
|
766
|
+
text: 'SOURCE: library\nEXPECT: GREEN\n(from service-green)',
|
|
767
|
+
pv: {
|
|
768
|
+
nodeType: 'service-green',
|
|
769
|
+
shape: 'rectangle',
|
|
770
|
+
},
|
|
771
|
+
},
|
|
772
|
+
// 6. Library blue for comparison
|
|
773
|
+
{
|
|
774
|
+
id: 'library-blue',
|
|
775
|
+
type: 'text',
|
|
776
|
+
x: 320,
|
|
777
|
+
y: 250,
|
|
778
|
+
width: 180,
|
|
779
|
+
height: 100,
|
|
780
|
+
text: 'SOURCE: library\nEXPECT: BLUE\n(from service-blue)',
|
|
781
|
+
pv: {
|
|
782
|
+
nodeType: 'service-blue',
|
|
783
|
+
shape: 'rectangle',
|
|
784
|
+
},
|
|
785
|
+
},
|
|
786
|
+
],
|
|
787
|
+
edges: [
|
|
788
|
+
// Edge using library color
|
|
789
|
+
{
|
|
790
|
+
id: 'edge-library-orange',
|
|
791
|
+
fromNode: 'library-green',
|
|
792
|
+
toNode: 'library-blue',
|
|
793
|
+
pv: {
|
|
794
|
+
edgeType: 'flow-orange', // Should get ORANGE from library
|
|
795
|
+
},
|
|
796
|
+
},
|
|
797
|
+
// Edge with library color overridden
|
|
798
|
+
{
|
|
799
|
+
id: 'edge-override',
|
|
800
|
+
fromNode: 'library-only',
|
|
801
|
+
toNode: 'library-vs-node-color',
|
|
802
|
+
color: '#FF00FF', // MAGENTA - overrides library
|
|
803
|
+
pv: {
|
|
804
|
+
edgeType: 'flow-cyan', // Would be CYAN, but overridden
|
|
805
|
+
},
|
|
806
|
+
},
|
|
807
|
+
],
|
|
808
|
+
pv: {
|
|
809
|
+
version: '1.0.0',
|
|
810
|
+
name: 'Library Color Priority Demo',
|
|
811
|
+
description: 'Shows how library.yaml colors are applied and overridden',
|
|
812
|
+
edgeTypes: {},
|
|
813
|
+
},
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
// Helper component for library color test
|
|
817
|
+
const GraphWithLibrary: React.FC<{
|
|
818
|
+
canvas: ExtendedCanvas;
|
|
819
|
+
library: ComponentLibrary;
|
|
820
|
+
width: number;
|
|
821
|
+
height: number;
|
|
822
|
+
showMinimap: boolean;
|
|
823
|
+
initialStates?: Record<string, string>;
|
|
824
|
+
}> = ({ canvas, library, width, height, showMinimap, initialStates }) => {
|
|
825
|
+
const [events, setEvents] = useState<GraphEvent[]>([]);
|
|
826
|
+
|
|
827
|
+
useEffect(() => {
|
|
828
|
+
if (initialStates) {
|
|
829
|
+
const stateEvents: GraphEvent[] = Object.entries(initialStates).map(
|
|
830
|
+
([nodeId, newState], idx) => ({
|
|
831
|
+
id: `init-state-${idx}`,
|
|
832
|
+
type: 'state_changed',
|
|
833
|
+
timestamp: Date.now(),
|
|
834
|
+
category: 'state' as const,
|
|
835
|
+
payload: { nodeId, newState },
|
|
836
|
+
})
|
|
837
|
+
);
|
|
838
|
+
setEvents(stateEvents);
|
|
839
|
+
}
|
|
840
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally run only on mount
|
|
841
|
+
}, []);
|
|
842
|
+
|
|
843
|
+
return (
|
|
844
|
+
<GraphRenderer
|
|
845
|
+
canvas={canvas}
|
|
846
|
+
library={library}
|
|
847
|
+
width={width}
|
|
848
|
+
height={height}
|
|
849
|
+
showMinimap={showMinimap}
|
|
850
|
+
events={events}
|
|
851
|
+
/>
|
|
852
|
+
);
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
export const LibraryColorPriority: Story = {
|
|
856
|
+
render: () => (
|
|
857
|
+
<GraphWithLibrary
|
|
858
|
+
canvas={libraryColorPriorityCanvas}
|
|
859
|
+
library={libraryColorTestLibrary}
|
|
860
|
+
width={1050}
|
|
861
|
+
height={450}
|
|
862
|
+
showMinimap={false}
|
|
863
|
+
initialStates={{ 'library-vs-state': 'active' }}
|
|
864
|
+
/>
|
|
865
|
+
),
|
|
866
|
+
parameters: {
|
|
867
|
+
docs: {
|
|
868
|
+
description: {
|
|
869
|
+
story: `
|
|
870
|
+
**Library Color Priority** - Tests colors from library.yaml nodeComponents
|
|
871
|
+
|
|
872
|
+
This story validates that the \`library\` prop correctly provides colors to nodes via nodeType lookup.
|
|
873
|
+
|
|
874
|
+
**Full Fill Color Priority Chain** (lowest to highest):
|
|
875
|
+
1. **Default** - Falls back to #888 gray
|
|
876
|
+
2. **Library nodeComponent color** - Color from library.yaml nodeComponents[nodeType].color
|
|
877
|
+
3. **node.color** - The standard canvas node color property
|
|
878
|
+
4. **pv.fill** - Explicit fill color in the PV extension
|
|
879
|
+
5. **state.color** - When a node has an active state with a color defined
|
|
880
|
+
|
|
881
|
+
**In this demo:**
|
|
882
|
+
- Row 1 Node 1: Uses \`service-red\` nodeType → gets RED from library
|
|
883
|
+
- Row 1 Node 2: Uses \`service-green\` nodeType but has node.color=YELLOW → YELLOW wins
|
|
884
|
+
- Row 1 Node 3: Uses \`service-blue\` nodeType but has pv.fill=CYAN → CYAN wins
|
|
885
|
+
- Row 1 Node 4: Uses \`service-purple\` nodeType but is in "active" state → ORANGE wins
|
|
886
|
+
- Row 2: Pure library colors (GREEN, BLUE) for visual comparison
|
|
887
|
+
|
|
888
|
+
**Edge Colors:**
|
|
889
|
+
- Bottom edge: Uses \`flow-orange\` edgeType → gets ORANGE from library
|
|
890
|
+
- Top edge: Uses \`flow-cyan\` but has edge.color=MAGENTA → MAGENTA wins
|
|
891
|
+
`,
|
|
892
|
+
},
|
|
893
|
+
},
|
|
894
|
+
},
|
|
895
|
+
};
|