@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.
Files changed (37) hide show
  1. package/dist/components/GraphRenderer.d.ts +1 -15
  2. package/dist/components/GraphRenderer.d.ts.map +1 -1
  3. package/dist/components/GraphRenderer.js +94 -269
  4. package/dist/components/GraphRenderer.js.map +1 -1
  5. package/dist/components/NodeInfoPanel.d.ts.map +1 -1
  6. package/dist/components/NodeInfoPanel.js.map +1 -1
  7. package/dist/edges/CustomEdge.d.ts +2 -2
  8. package/dist/edges/CustomEdge.d.ts.map +1 -1
  9. package/dist/edges/CustomEdge.js +5 -4
  10. package/dist/edges/CustomEdge.js.map +1 -1
  11. package/dist/hooks/usePathBasedEvents.d.ts.map +1 -1
  12. package/dist/hooks/usePathBasedEvents.js +2 -1
  13. package/dist/hooks/usePathBasedEvents.js.map +1 -1
  14. package/dist/index.d.ts +0 -4
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +0 -2
  17. package/dist/index.js.map +1 -1
  18. package/dist/nodes/CustomNode.d.ts +2 -2
  19. package/dist/nodes/CustomNode.d.ts.map +1 -1
  20. package/dist/nodes/CustomNode.js +41 -10
  21. package/dist/nodes/CustomNode.js.map +1 -1
  22. package/package.json +1 -1
  23. package/src/components/GraphRenderer.tsx +106 -354
  24. package/src/edges/CustomEdge.tsx +8 -7
  25. package/src/hooks/usePathBasedEvents.ts +2 -1
  26. package/src/index.ts +0 -6
  27. package/src/nodes/CustomNode.tsx +50 -13
  28. package/src/stories/ColorPriority.stories.tsx +256 -1
  29. package/src/stories/EventDrivenAnimations.stories.tsx +332 -326
  30. package/src/stories/GraphRenderer.stories.tsx +2 -2
  31. package/src/stories/NodeDefinitionComparison.stories.tsx +1 -1
  32. package/src/stories/NodeDimensionsTesting.stories.tsx +2 -2
  33. package/src/stories/OtelComponents.stories.tsx +0 -47
  34. package/src/stories/data/graph-converter-test-execution.json +244 -26
  35. package/src/stories/data/graph-converter-validated-execution.json +6 -6
  36. package/src/components/EdgeInfoPanel.tsx +0 -247
  37. package/src/components/NodeInfoPanel.tsx +0 -724
@@ -1,6 +1,6 @@
1
1
  import React, { useState, useEffect, useRef } from 'react';
2
- import { BaseEdge, EdgeLabelRenderer, getBezierPath } from '@xyflow/react';
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
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 as CustomEdgeData | undefined;
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 Bezier path
82
- const [edgePath, labelX, labelY] = getBezierPath({
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
 
@@ -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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
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 as CustomNodeData;
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
- width: 18,
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
- width: 18,
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
- {statusLabels[status]}
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: '8px',
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
+ };