@principal-ai/principal-view-react 0.15.1 → 0.15.3

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.
@@ -28,6 +28,8 @@ export interface CustomNodeData extends Record<string, unknown> {
28
28
  animationDuration?: number;
29
29
  // Edit mode - shows larger connection handles
30
30
  editable?: boolean;
31
+ // Pending text change (from inline editing, not yet saved)
32
+ pendingText?: string;
31
33
  // Whether tooltips are enabled (defaults to true)
32
34
  tooltipsEnabled?: boolean;
33
35
  // Whether shift key is currently pressed (for tooltip control)
@@ -77,10 +79,13 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = (props) =>
77
79
 
78
80
  // Fall through to legacy rendering for non-OTEL nodes
79
81
  const { theme } = useTheme();
80
- const { onNodeResizeEnd, onToggleNodeHidden, onHideUnconnectedNodes } = useGraphEdit();
82
+ const { onNodeResizeEnd, onNodeTextChange, onToggleNodeHidden, onHideUnconnectedNodes } = useGraphEdit();
81
83
  const nodeId = useNodeId();
82
84
  const [isHovered, setIsHovered] = useState(false);
85
+ const [isEditing, setIsEditing] = useState(false);
86
+ const [editingText, setEditingText] = useState('');
83
87
  const nodeRef = useRef<HTMLDivElement>(null);
88
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
84
89
  const nodeProps = data;
85
90
  const {
86
91
  typeDefinition,
@@ -449,8 +454,8 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = (props) =>
449
454
  // Use fillColor as the primary "color" for backwards compatibility
450
455
  const color = fillColor;
451
456
 
452
- // Get display name - use name from props (falls back to node.id in converter)
453
- const displayName = nodeProps.name;
457
+ // Get display name - use pending text if available, otherwise use name from props
458
+ const displayName = nodeProps.pendingText ?? nodeProps.name;
454
459
 
455
460
  // Extract identifier based on node type (for display below the label)
456
461
  // Supports: event.name, otel.spanPattern, otel.scope, otel.resourceMatch, boundary.direction
@@ -545,8 +550,58 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = (props) =>
545
550
 
546
551
  const animationClass = getAnimationClass();
547
552
 
548
- // Check if this is a group node
553
+ // Check if this is a group node or text node (canvas types that support inline editing)
549
554
  const isGroup = nodeData.canvasType === 'group';
555
+ const isTextNode = nodeData.canvasType === 'text';
556
+ const isInlineEditable = (isGroup || isTextNode) && editable;
557
+
558
+ // Double-click handler for inline text editing
559
+ const lastClickTimeRef = useRef<number>(0);
560
+ const handleNodeDoubleClick = useCallback((event: React.MouseEvent) => {
561
+ if (!isInlineEditable || !nodeId || !onNodeTextChange) return;
562
+
563
+ event.stopPropagation();
564
+ event.preventDefault();
565
+
566
+ setEditingText(displayName || '');
567
+ setIsEditing(true);
568
+
569
+ // Focus the textarea after state update
570
+ setTimeout(() => {
571
+ textareaRef.current?.focus();
572
+ textareaRef.current?.select();
573
+ }, 0);
574
+ }, [isInlineEditable, nodeId, onNodeTextChange, displayName]);
575
+
576
+ // Single click tracking for double-click detection
577
+ const handleNodeClick = useCallback((event: React.MouseEvent) => {
578
+ const now = Date.now();
579
+ const timeSinceLastClick = now - lastClickTimeRef.current;
580
+
581
+ if (timeSinceLastClick < 300) {
582
+ // Double-click detected
583
+ handleNodeDoubleClick(event);
584
+ }
585
+
586
+ lastClickTimeRef.current = now;
587
+ }, [handleNodeDoubleClick]);
588
+
589
+ // Save text changes
590
+ const handleSaveText = useCallback(() => {
591
+ if (!nodeId || !onNodeTextChange) return;
592
+
593
+ if (editingText !== displayName) {
594
+ onNodeTextChange(nodeId, editingText);
595
+ }
596
+
597
+ setIsEditing(false);
598
+ }, [nodeId, onNodeTextChange, editingText, displayName]);
599
+
600
+ // Cancel text editing
601
+ const handleCancelEdit = useCallback(() => {
602
+ setIsEditing(false);
603
+ setEditingText('');
604
+ }, []);
550
605
 
551
606
  // Shape-specific styles
552
607
  const getShapeStyles = () => {
@@ -946,6 +1001,7 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = (props) =>
946
1001
  ref={nodeRef}
947
1002
  style={{ position: 'relative', width: '100%', height: '100%' }}
948
1003
  onMouseDown={handleMouseDown}
1004
+ onClick={handleNodeClick}
949
1005
  onMouseEnter={() => setIsHovered(true)}
950
1006
  onMouseLeave={() => setIsHovered(false)}
951
1007
  >
@@ -1018,6 +1074,57 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = (props) =>
1018
1074
  )}
1019
1075
  </div>
1020
1076
  </div>
1077
+ {/* Inline text editor overlay */}
1078
+ {isEditing && isInlineEditable && (
1079
+ <div
1080
+ style={{
1081
+ position: 'absolute',
1082
+ top: 0,
1083
+ left: 0,
1084
+ width: '100%',
1085
+ height: '100%',
1086
+ zIndex: 1000,
1087
+ display: 'flex',
1088
+ flexDirection: 'column',
1089
+ padding: theme.space[1],
1090
+ boxSizing: 'border-box',
1091
+ }}
1092
+ onClick={(e) => e.stopPropagation()}
1093
+ >
1094
+ <textarea
1095
+ ref={textareaRef}
1096
+ value={editingText}
1097
+ onChange={(e) => setEditingText(e.target.value)}
1098
+ placeholder="Enter text..."
1099
+ style={{
1100
+ flex: 1,
1101
+ width: '100%',
1102
+ padding: theme.space[2],
1103
+ fontSize: theme.fontSizes[1],
1104
+ fontFamily: theme.fonts.body,
1105
+ color: theme.colors.text,
1106
+ backgroundColor: theme.colors.background,
1107
+ border: `3px solid ${theme.colors.primary}`,
1108
+ borderRadius: theme.radii[1],
1109
+ outline: 'none',
1110
+ resize: 'none',
1111
+ boxSizing: 'border-box',
1112
+ boxShadow: '0 8px 24px rgba(0, 0, 0, 0.3)',
1113
+ }}
1114
+ onKeyDown={(e) => {
1115
+ if (e.key === 'Enter' && !e.shiftKey) {
1116
+ e.preventDefault();
1117
+ handleSaveText();
1118
+ } else if (e.key === 'Escape') {
1119
+ e.preventDefault();
1120
+ handleCancelEdit();
1121
+ }
1122
+ }}
1123
+ onBlur={handleSaveText}
1124
+ />
1125
+ </div>
1126
+ )}
1127
+
1021
1128
  {tooltipsEnabled && (
1022
1129
  <NodeTooltip
1023
1130
  description={description}