@principal-ai/principal-view-react 0.14.3 → 0.14.4

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 (66) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +2 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/nodes/CustomNode.d.ts +4 -0
  6. package/dist/nodes/CustomNode.d.ts.map +1 -1
  7. package/dist/nodes/CustomNode.js +30 -1
  8. package/dist/nodes/CustomNode.js.map +1 -1
  9. package/dist/nodes/otel/OtelBoundaryNode.d.ts +55 -0
  10. package/dist/nodes/otel/OtelBoundaryNode.d.ts.map +1 -0
  11. package/dist/nodes/otel/OtelBoundaryNode.js +90 -0
  12. package/dist/nodes/otel/OtelBoundaryNode.js.map +1 -0
  13. package/dist/nodes/otel/OtelEventNode.d.ts +59 -0
  14. package/dist/nodes/otel/OtelEventNode.d.ts.map +1 -0
  15. package/dist/nodes/otel/OtelEventNode.js +90 -0
  16. package/dist/nodes/otel/OtelEventNode.js.map +1 -0
  17. package/dist/nodes/otel/OtelResourceNode.d.ts +53 -0
  18. package/dist/nodes/otel/OtelResourceNode.d.ts.map +1 -0
  19. package/dist/nodes/otel/OtelResourceNode.js +114 -0
  20. package/dist/nodes/otel/OtelResourceNode.js.map +1 -0
  21. package/dist/nodes/otel/OtelScopeNode.d.ts +53 -0
  22. package/dist/nodes/otel/OtelScopeNode.d.ts.map +1 -0
  23. package/dist/nodes/otel/OtelScopeNode.js +90 -0
  24. package/dist/nodes/otel/OtelScopeNode.js.map +1 -0
  25. package/dist/nodes/otel/OtelSpanConventionNode.d.ts +61 -0
  26. package/dist/nodes/otel/OtelSpanConventionNode.d.ts.map +1 -0
  27. package/dist/nodes/otel/OtelSpanConventionNode.js +143 -0
  28. package/dist/nodes/otel/OtelSpanConventionNode.js.map +1 -0
  29. package/dist/nodes/otel/index.d.ts +17 -0
  30. package/dist/nodes/otel/index.d.ts.map +1 -0
  31. package/dist/nodes/otel/index.js +13 -0
  32. package/dist/nodes/otel/index.js.map +1 -0
  33. package/dist/nodes/otel/shared/NodeBadges.d.ts +10 -0
  34. package/dist/nodes/otel/shared/NodeBadges.d.ts.map +1 -0
  35. package/dist/nodes/otel/shared/NodeBadges.js +178 -0
  36. package/dist/nodes/otel/shared/NodeBadges.js.map +1 -0
  37. package/dist/nodes/otel/shared/NodeContent.d.ts +10 -0
  38. package/dist/nodes/otel/shared/NodeContent.d.ts.map +1 -0
  39. package/dist/nodes/otel/shared/NodeContent.js +96 -0
  40. package/dist/nodes/otel/shared/NodeContent.js.map +1 -0
  41. package/dist/nodes/otel/shared/index.d.ts +8 -0
  42. package/dist/nodes/otel/shared/index.d.ts.map +1 -0
  43. package/dist/nodes/otel/shared/index.js +8 -0
  44. package/dist/nodes/otel/shared/index.js.map +1 -0
  45. package/dist/nodes/otel/shared/types.d.ts +129 -0
  46. package/dist/nodes/otel/shared/types.d.ts.map +1 -0
  47. package/dist/nodes/otel/shared/types.js +5 -0
  48. package/dist/nodes/otel/shared/types.js.map +1 -0
  49. package/dist/nodes/otel/shared/useNodeBehavior.d.ts +42 -0
  50. package/dist/nodes/otel/shared/useNodeBehavior.d.ts.map +1 -0
  51. package/dist/nodes/otel/shared/useNodeBehavior.js +61 -0
  52. package/dist/nodes/otel/shared/useNodeBehavior.js.map +1 -0
  53. package/package.json +1 -1
  54. package/src/index.ts +17 -0
  55. package/src/nodes/CustomNode.tsx +40 -1
  56. package/src/nodes/otel/OtelBoundaryNode.tsx +234 -0
  57. package/src/nodes/otel/OtelEventNode.tsx +233 -0
  58. package/src/nodes/otel/OtelResourceNode.tsx +261 -0
  59. package/src/nodes/otel/OtelScopeNode.tsx +231 -0
  60. package/src/nodes/otel/OtelSpanConventionNode.tsx +309 -0
  61. package/src/nodes/otel/index.ts +23 -0
  62. package/src/nodes/otel/shared/NodeBadges.tsx +295 -0
  63. package/src/nodes/otel/shared/NodeContent.tsx +204 -0
  64. package/src/nodes/otel/shared/index.ts +8 -0
  65. package/src/nodes/otel/shared/types.ts +138 -0
  66. package/src/nodes/otel/shared/useNodeBehavior.ts +114 -0
@@ -8,6 +8,15 @@ import { NodeTooltip } from '../components/NodeTooltip';
8
8
  import type { OtelInfo } from '../components/NodeTooltip';
9
9
  import { useGraphEdit } from '../contexts/GraphEditContext';
10
10
 
11
+ // OTEL node components
12
+ import {
13
+ OtelSpanConventionNode,
14
+ OtelEventNode,
15
+ OtelScopeNode,
16
+ OtelResourceNode,
17
+ OtelBoundaryNode,
18
+ } from './otel';
19
+
11
20
  /**
12
21
  * Converts a hex color to a lighter/tinted version (opaque, not transparent)
13
22
  * @param hexColor - Hex color string (e.g., "#FF5733" or "#888")
@@ -61,8 +70,38 @@ export interface CustomNodeData extends Record<string, unknown> {
61
70
 
62
71
  /**
63
72
  * Custom node component for xyflow that renders based on NodeTypeDefinition
73
+ *
74
+ * This component now delegates to specialized OTEL node components when the
75
+ * node type matches an OTEL concept. This allows each OTEL node type to have
76
+ * its own specialized rendering and features (e.g., workflow chips on span nodes).
64
77
  */
65
- export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, selected, dragging }) => {
78
+ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = (props) => {
79
+ const { data, selected, dragging } = props;
80
+
81
+ // Determine the OTEL node type from nodeData.nodeType (pv.nodeType in canvas)
82
+ // The typeDefinition.shape can also hint at OTEL types but nodeType is authoritative
83
+ const nodeType = data.data?.nodeType as string | undefined;
84
+
85
+ // Delegate to specialized OTEL node components
86
+ switch (nodeType) {
87
+ case 'otel-span-convention':
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ return <OtelSpanConventionNode {...(props as any)} />;
90
+ case 'otel-event':
91
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
92
+ return <OtelEventNode {...(props as any)} />;
93
+ case 'otel-scope':
94
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
+ return <OtelScopeNode {...(props as any)} />;
96
+ case 'otel-resource':
97
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
98
+ return <OtelResourceNode {...(props as any)} />;
99
+ case 'otel-boundary':
100
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
101
+ return <OtelBoundaryNode {...(props as any)} />;
102
+ }
103
+
104
+ // Fall through to legacy rendering for non-OTEL nodes
66
105
  const { theme } = useTheme();
67
106
  const { onNodeResizeEnd, onToggleNodeHidden, onHideUnconnectedNodes } = useGraphEdit();
68
107
  const nodeId = useNodeId();
@@ -0,0 +1,234 @@
1
+ /**
2
+ * OTEL Boundary Node
3
+ *
4
+ * Renders boundary nodes as rounded rectangles with:
5
+ * - Direction identifier (inbound/outbound)
6
+ * - Direction badge (arrow indicating flow)
7
+ * - Status and references badges
8
+ */
9
+
10
+ import React from 'react';
11
+ import { Handle, Position, NodeResizer } from '@xyflow/react';
12
+ import type { NodeProps, Node } from '@xyflow/react';
13
+ import { useTheme } from '@principal-ade/industry-theme';
14
+ import { NodeTooltip } from '../../components/NodeTooltip';
15
+ import type { OtelInfo } from '../../components/NodeTooltip';
16
+ import { NodeBadges } from './shared/NodeBadges';
17
+ import { NodeContent } from './shared/NodeContent';
18
+ import { useNodeBehavior } from './shared/useNodeBehavior';
19
+ import type { BoundaryData } from './shared/types';
20
+
21
+ function hexToLightColor(hexColor: string, lightness = 0.88): string {
22
+ const hex = hexColor.replace('#', '');
23
+ const r = parseInt(hex.substring(0, 2), 16);
24
+ const g = parseInt(hex.substring(2, 4), 16);
25
+ const b = parseInt(hex.substring(4, 6), 16);
26
+ const newR = Math.round(r + (255 - r) * lightness);
27
+ const newG = Math.round(g + (255 - g) * lightness);
28
+ const newB = Math.round(b + (255 - b) * lightness);
29
+ const toHex = (n: number) => n.toString(16).padStart(2, '0');
30
+ return `#${toHex(newR)}${toHex(newG)}${toHex(newB)}`;
31
+ }
32
+
33
+ export interface OtelBoundaryNodeData extends Record<string, unknown> {
34
+ name: string;
35
+ typeDefinition: {
36
+ color?: string;
37
+ icon?: string;
38
+ states?: Record<string, { color?: string; label?: string; icon?: string }>;
39
+ };
40
+ state?: string;
41
+ hasViolations?: boolean;
42
+ data: {
43
+ color?: string;
44
+ scopeColor?: string;
45
+ spanColor?: string;
46
+ stroke?: string;
47
+ icon?: string;
48
+ status?: 'draft' | 'approved' | 'implemented';
49
+ description?: string;
50
+ sources?: string[];
51
+ references?: string[];
52
+ states?: Record<string, { color?: string; label?: string; icon?: string }>;
53
+ boundary?: BoundaryData;
54
+ otel?: { files?: string[] };
55
+ };
56
+ editable?: boolean;
57
+ tooltipsEnabled?: boolean;
58
+ shiftKeyPressed?: boolean;
59
+ isHighlighted?: boolean;
60
+ isActive?: boolean;
61
+ isHidden?: boolean;
62
+ animationType?: 'pulse' | 'flash' | 'shake' | 'entry' | null;
63
+ animationDuration?: number;
64
+ }
65
+
66
+ export const OtelBoundaryNode: React.FC<NodeProps<Node<OtelBoundaryNodeData>>> = ({
67
+ data,
68
+ selected,
69
+ dragging,
70
+ }) => {
71
+ const { theme } = useTheme();
72
+ const nodeProps = data;
73
+ const {
74
+ typeDefinition,
75
+ state,
76
+ hasViolations,
77
+ data: nodeData,
78
+ editable = false,
79
+ tooltipsEnabled = true,
80
+ shiftKeyPressed = false,
81
+ isHighlighted = false,
82
+ isActive = true,
83
+ isHidden = false,
84
+ animationType,
85
+ animationDuration = 1000,
86
+ } = nodeProps;
87
+
88
+ const {
89
+ nodeRef,
90
+ showTooltip,
91
+ handleMouseDown,
92
+ handleMouseEnter,
93
+ handleMouseLeave,
94
+ handleResizeEnd,
95
+ } = useNodeBehavior({ editable, tooltipsEnabled, shiftKeyPressed, selected, dragging });
96
+
97
+ const nodeOpacity = isHidden ? 0.4 : isActive ? 1 : 0.1;
98
+
99
+ // Color resolution
100
+ const spanColor = nodeData.spanColor as string | undefined;
101
+ const nodeDataColor = nodeData.color as string | undefined;
102
+ const baseFillColor = spanColor || nodeDataColor || typeDefinition.color || '#06b6d4';
103
+ const fillColor = baseFillColor;
104
+ const scopeColor = nodeData.scopeColor as string | undefined;
105
+ const nodeDataStroke = nodeData.stroke as string | undefined;
106
+ const strokeColor = nodeDataStroke || scopeColor || fillColor;
107
+
108
+ // Display info
109
+ const displayName = nodeProps.name;
110
+ const boundaryData = nodeData.boundary;
111
+ const identifier = boundaryData?.direction;
112
+
113
+ // Badge data
114
+ const status = nodeData.status;
115
+ const sourceFiles = nodeData.otel?.files || nodeData.sources;
116
+ const references = nodeData.references;
117
+ const description = nodeData.description;
118
+
119
+ // Icon
120
+ const icon =
121
+ (nodeData.icon as string) ||
122
+ (state && nodeData.states?.[state]?.icon) ||
123
+ typeDefinition.icon;
124
+
125
+ const stateDefinitions = nodeData.states || typeDefinition.states;
126
+
127
+ const getAnimationClass = () => {
128
+ switch (animationType) {
129
+ case 'pulse': return 'node-pulse';
130
+ case 'flash': return 'node-flash';
131
+ case 'shake': return 'node-shake';
132
+ case 'entry': return 'node-entry';
133
+ default: return '';
134
+ }
135
+ };
136
+
137
+ const borderStyle = status === 'draft' ? 'dotted' : status === 'approved' ? 'dashed' : 'solid';
138
+
139
+ const boundaryStyle: React.CSSProperties = {
140
+ padding: '12px 16px',
141
+ backgroundColor: hexToLightColor(fillColor),
142
+ color: '#000',
143
+ border: `2px ${borderStyle} ${hasViolations ? '#D0021B' : strokeColor}`,
144
+ fontSize: theme.fontSizes[0],
145
+ fontWeight: theme.fontWeights.medium,
146
+ fontFamily: theme.fonts.body,
147
+ width: '100%',
148
+ height: '100%',
149
+ minWidth: 20,
150
+ minHeight: 20,
151
+ display: 'flex',
152
+ flexDirection: 'column',
153
+ alignItems: 'center',
154
+ justifyContent: 'center',
155
+ gap: '4px',
156
+ boxShadow: isHighlighted
157
+ ? '0 0 0 3px #3b82f6, 0 0 20px rgba(59, 130, 246, 0.5)'
158
+ : selected
159
+ ? `0 0 0 2px ${strokeColor}`
160
+ : '0 2px 4px rgba(0,0,0,0.1)',
161
+ opacity: nodeOpacity,
162
+ transition: 'box-shadow 0.2s ease, opacity 0.3s ease',
163
+ animationDuration: animationType ? `${animationDuration}ms` : undefined,
164
+ boxSizing: 'border-box',
165
+ borderRadius: '8px', // Rounded corners for boundary nodes
166
+ };
167
+
168
+ const handleStyle = editable
169
+ ? { background: fillColor, width: 12, height: 12, border: '2px solid white', boxShadow: '0 0 0 1px ' + fillColor }
170
+ : { background: fillColor, width: 8, height: 8, opacity: 0, pointerEvents: 'none' as const };
171
+
172
+ return (
173
+ <>
174
+ {editable && (
175
+ <NodeResizer
176
+ color={strokeColor}
177
+ isVisible={selected}
178
+ minWidth={40}
179
+ minHeight={30}
180
+ onResizeEnd={handleResizeEnd}
181
+ handleStyle={{ width: 8, height: 8, borderRadius: 2, zIndex: 20 }}
182
+ lineStyle={{ borderWidth: 1, zIndex: 20 }}
183
+ />
184
+ )}
185
+
186
+ <Handle type="target" position={Position.Top} id="top" style={handleStyle} />
187
+ <Handle type="target" position={Position.Bottom} id="bottom" style={handleStyle} />
188
+ <Handle type="target" position={Position.Left} id="left" style={handleStyle} />
189
+ <Handle type="target" position={Position.Right} id="right" style={handleStyle} />
190
+
191
+ <div
192
+ ref={nodeRef}
193
+ style={{ position: 'relative', width: '100%', height: '100%' }}
194
+ onMouseDown={handleMouseDown}
195
+ onMouseEnter={handleMouseEnter}
196
+ onMouseLeave={handleMouseLeave}
197
+ >
198
+ <NodeBadges
199
+ shape="rectangle"
200
+ status={status}
201
+ sourceFiles={sourceFiles}
202
+ references={references}
203
+ boundary={boundaryData}
204
+ opacity={nodeOpacity}
205
+ />
206
+ <div style={boundaryStyle} className={getAnimationClass()}>
207
+ <NodeContent
208
+ displayName={displayName}
209
+ identifier={identifier}
210
+ icon={icon}
211
+ state={state}
212
+ stateDefinitions={stateDefinitions}
213
+ hasViolations={hasViolations}
214
+ />
215
+ </div>
216
+ {tooltipsEnabled && (
217
+ <NodeTooltip
218
+ description={description}
219
+ otel={nodeData.otel as OtelInfo}
220
+ sources={nodeData.sources}
221
+ references={references}
222
+ visible={showTooltip}
223
+ nodeRef={nodeRef}
224
+ />
225
+ )}
226
+ </div>
227
+
228
+ <Handle type="source" position={Position.Top} id="top-out" style={handleStyle} />
229
+ <Handle type="source" position={Position.Bottom} id="bottom-out" style={handleStyle} />
230
+ <Handle type="source" position={Position.Left} id="left-out" style={handleStyle} />
231
+ <Handle type="source" position={Position.Right} id="right-out" style={handleStyle} />
232
+ </>
233
+ );
234
+ };
@@ -0,0 +1,233 @@
1
+ /**
2
+ * OTEL Event Node
3
+ *
4
+ * Renders event nodes as rectangles with:
5
+ * - Event name identifier
6
+ * - Event attributes schema
7
+ * - Status, sources, and references badges
8
+ */
9
+
10
+ import React from 'react';
11
+ import { Handle, Position, NodeResizer } from '@xyflow/react';
12
+ import type { NodeProps, Node } from '@xyflow/react';
13
+ import { useTheme } from '@principal-ade/industry-theme';
14
+ import { NodeTooltip } from '../../components/NodeTooltip';
15
+ import type { OtelInfo } from '../../components/NodeTooltip';
16
+ import { NodeBadges } from './shared/NodeBadges';
17
+ import { NodeContent } from './shared/NodeContent';
18
+ import { useNodeBehavior } from './shared/useNodeBehavior';
19
+
20
+ function hexToLightColor(hexColor: string, lightness = 0.88): string {
21
+ const hex = hexColor.replace('#', '');
22
+ const r = parseInt(hex.substring(0, 2), 16);
23
+ const g = parseInt(hex.substring(2, 4), 16);
24
+ const b = parseInt(hex.substring(4, 6), 16);
25
+ const newR = Math.round(r + (255 - r) * lightness);
26
+ const newG = Math.round(g + (255 - g) * lightness);
27
+ const newB = Math.round(b + (255 - b) * lightness);
28
+ const toHex = (n: number) => n.toString(16).padStart(2, '0');
29
+ return `#${toHex(newR)}${toHex(newG)}${toHex(newB)}`;
30
+ }
31
+
32
+ export interface OtelEventNodeData extends Record<string, unknown> {
33
+ name: string;
34
+ typeDefinition: {
35
+ color?: string;
36
+ icon?: string;
37
+ states?: Record<string, { color?: string; label?: string; icon?: string }>;
38
+ };
39
+ state?: string;
40
+ hasViolations?: boolean;
41
+ data: {
42
+ color?: string;
43
+ scopeColor?: string;
44
+ spanColor?: string;
45
+ stroke?: string;
46
+ icon?: string;
47
+ status?: 'draft' | 'approved' | 'implemented';
48
+ description?: string;
49
+ sources?: string[];
50
+ references?: string[];
51
+ states?: Record<string, { color?: string; label?: string; icon?: string }>;
52
+ event?: { name?: string; description?: string; attributes?: Record<string, unknown> };
53
+ eventRef?: string;
54
+ otel?: { files?: string[] };
55
+ };
56
+ editable?: boolean;
57
+ tooltipsEnabled?: boolean;
58
+ shiftKeyPressed?: boolean;
59
+ isHighlighted?: boolean;
60
+ isActive?: boolean;
61
+ isHidden?: boolean;
62
+ animationType?: 'pulse' | 'flash' | 'shake' | 'entry' | null;
63
+ animationDuration?: number;
64
+ }
65
+
66
+ export const OtelEventNode: React.FC<NodeProps<Node<OtelEventNodeData>>> = ({
67
+ data,
68
+ selected,
69
+ dragging,
70
+ }) => {
71
+ const { theme } = useTheme();
72
+ const nodeProps = data;
73
+ const {
74
+ typeDefinition,
75
+ state,
76
+ hasViolations,
77
+ data: nodeData,
78
+ editable = false,
79
+ tooltipsEnabled = true,
80
+ shiftKeyPressed = false,
81
+ isHighlighted = false,
82
+ isActive = true,
83
+ isHidden = false,
84
+ animationType,
85
+ animationDuration = 1000,
86
+ } = nodeProps;
87
+
88
+ const {
89
+ nodeRef,
90
+ showTooltip,
91
+ handleMouseDown,
92
+ handleMouseEnter,
93
+ handleMouseLeave,
94
+ handleResizeEnd,
95
+ } = useNodeBehavior({ editable, tooltipsEnabled, shiftKeyPressed, selected, dragging });
96
+
97
+ const nodeOpacity = isHidden ? 0.4 : isActive ? 1 : 0.1;
98
+
99
+ // Color resolution
100
+ const spanColor = nodeData.spanColor as string | undefined;
101
+ const nodeDataColor = nodeData.color as string | undefined;
102
+ const baseFillColor = spanColor || nodeDataColor || typeDefinition.color || '#3b82f6';
103
+ const fillColor = baseFillColor;
104
+ const scopeColor = nodeData.scopeColor as string | undefined;
105
+ const nodeDataStroke = nodeData.stroke as string | undefined;
106
+ const strokeColor = nodeDataStroke || scopeColor || fillColor;
107
+
108
+ // Display info
109
+ const displayName = nodeProps.name;
110
+ const eventData = nodeData.event;
111
+ const identifier = eventData?.name || (nodeData.eventRef as string);
112
+
113
+ // Badge data
114
+ const status = nodeData.status;
115
+ const sourceFiles = nodeData.otel?.files || nodeData.sources;
116
+ const references = nodeData.references;
117
+ const description = nodeData.description;
118
+
119
+ // Icon
120
+ const icon =
121
+ (nodeData.icon as string) ||
122
+ (state && nodeData.states?.[state]?.icon) ||
123
+ typeDefinition.icon;
124
+
125
+ const stateDefinitions = nodeData.states || typeDefinition.states;
126
+
127
+ const getAnimationClass = () => {
128
+ switch (animationType) {
129
+ case 'pulse': return 'node-pulse';
130
+ case 'flash': return 'node-flash';
131
+ case 'shake': return 'node-shake';
132
+ case 'entry': return 'node-entry';
133
+ default: return '';
134
+ }
135
+ };
136
+
137
+ const borderStyle = status === 'draft' ? 'dotted' : status === 'approved' ? 'dashed' : 'solid';
138
+
139
+ const rectangleStyle: React.CSSProperties = {
140
+ padding: '12px 16px',
141
+ backgroundColor: hexToLightColor(fillColor),
142
+ color: '#000',
143
+ border: `2px ${borderStyle} ${hasViolations ? '#D0021B' : strokeColor}`,
144
+ fontSize: theme.fontSizes[0],
145
+ fontWeight: theme.fontWeights.medium,
146
+ fontFamily: theme.fonts.body,
147
+ width: '100%',
148
+ height: '100%',
149
+ minWidth: 20,
150
+ minHeight: 20,
151
+ display: 'flex',
152
+ flexDirection: 'column',
153
+ alignItems: 'center',
154
+ justifyContent: 'center',
155
+ gap: '4px',
156
+ boxShadow: isHighlighted
157
+ ? '0 0 0 3px #3b82f6, 0 0 20px rgba(59, 130, 246, 0.5)'
158
+ : selected
159
+ ? `0 0 0 2px ${strokeColor}`
160
+ : '0 2px 4px rgba(0,0,0,0.1)',
161
+ opacity: nodeOpacity,
162
+ transition: 'box-shadow 0.2s ease, opacity 0.3s ease',
163
+ animationDuration: animationType ? `${animationDuration}ms` : undefined,
164
+ boxSizing: 'border-box',
165
+ borderRadius: 0,
166
+ };
167
+
168
+ const handleStyle = editable
169
+ ? { background: fillColor, width: 12, height: 12, border: '2px solid white', boxShadow: '0 0 0 1px ' + fillColor }
170
+ : { background: fillColor, width: 8, height: 8, opacity: 0, pointerEvents: 'none' as const };
171
+
172
+ return (
173
+ <>
174
+ {editable && (
175
+ <NodeResizer
176
+ color={strokeColor}
177
+ isVisible={selected}
178
+ minWidth={40}
179
+ minHeight={30}
180
+ onResizeEnd={handleResizeEnd}
181
+ handleStyle={{ width: 8, height: 8, borderRadius: 2, zIndex: 20 }}
182
+ lineStyle={{ borderWidth: 1, zIndex: 20 }}
183
+ />
184
+ )}
185
+
186
+ <Handle type="target" position={Position.Top} id="top" style={handleStyle} />
187
+ <Handle type="target" position={Position.Bottom} id="bottom" style={handleStyle} />
188
+ <Handle type="target" position={Position.Left} id="left" style={handleStyle} />
189
+ <Handle type="target" position={Position.Right} id="right" style={handleStyle} />
190
+
191
+ <div
192
+ ref={nodeRef}
193
+ style={{ position: 'relative', width: '100%', height: '100%' }}
194
+ onMouseDown={handleMouseDown}
195
+ onMouseEnter={handleMouseEnter}
196
+ onMouseLeave={handleMouseLeave}
197
+ >
198
+ <NodeBadges
199
+ shape="rectangle"
200
+ status={status}
201
+ sourceFiles={sourceFiles}
202
+ references={references}
203
+ opacity={nodeOpacity}
204
+ />
205
+ <div style={rectangleStyle} className={getAnimationClass()}>
206
+ <NodeContent
207
+ displayName={displayName}
208
+ identifier={identifier}
209
+ icon={icon}
210
+ state={state}
211
+ stateDefinitions={stateDefinitions}
212
+ hasViolations={hasViolations}
213
+ />
214
+ </div>
215
+ {tooltipsEnabled && (
216
+ <NodeTooltip
217
+ description={description}
218
+ otel={nodeData.otel as OtelInfo}
219
+ sources={nodeData.sources}
220
+ references={references}
221
+ visible={showTooltip}
222
+ nodeRef={nodeRef}
223
+ />
224
+ )}
225
+ </div>
226
+
227
+ <Handle type="source" position={Position.Top} id="top-out" style={handleStyle} />
228
+ <Handle type="source" position={Position.Bottom} id="bottom-out" style={handleStyle} />
229
+ <Handle type="source" position={Position.Left} id="left-out" style={handleStyle} />
230
+ <Handle type="source" position={Position.Right} id="right-out" style={handleStyle} />
231
+ </>
232
+ );
233
+ };