@principal-ai/principal-view-react 0.16.64 → 0.16.66
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/pierre/PierreFileView.d.ts.map +1 -1
- package/dist/pierre/PierreFileView.js +4 -1
- package/dist/pierre/PierreFileView.js.map +1 -1
- package/dist/pierre/PierreSnippetView.d.ts.map +1 -1
- package/dist/pierre/PierreSnippetView.js +8 -1
- package/dist/pierre/PierreSnippetView.js.map +1 -1
- package/dist/pierre/PierreThroughlineCodeView.d.ts +3 -1
- package/dist/pierre/PierreThroughlineCodeView.d.ts.map +1 -1
- package/dist/pierre/PierreThroughlineCodeView.js +53 -13
- package/dist/pierre/PierreThroughlineCodeView.js.map +1 -1
- package/dist/pierre/index.d.ts +1 -0
- package/dist/pierre/index.d.ts.map +1 -1
- package/dist/pierre/index.js +1 -0
- package/dist/pierre/index.js.map +1 -1
- package/dist/pierre/pierreFileLang.d.ts +23 -0
- package/dist/pierre/pierreFileLang.d.ts.map +1 -0
- package/dist/pierre/pierreFileLang.js +48 -0
- package/dist/pierre/pierreFileLang.js.map +1 -0
- package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
- package/dist/subsystem/ComponentDeclaration.js +2 -1
- package/dist/subsystem/ComponentDeclaration.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +56 -1
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/package.json +1 -1
- package/src/components/PendingChanges.test.tsx +12 -0
- package/src/pierre/PierreFileView.tsx +6 -2
- package/src/pierre/PierreSnippetView.tsx +10 -2
- package/src/pierre/PierreThroughlineCodeView.tsx +62 -14
- package/src/pierre/index.ts +5 -0
- package/src/pierre/pierreFileLang.test.ts +26 -0
- package/src/pierre/pierreFileLang.ts +51 -0
- package/src/stories/Pierre/CodeView.stories.tsx +40 -17
- package/src/subsystem/ComponentDeclaration.tsx +2 -1
- package/src/subsystem/SubsystemComponentGraph.tsx +57 -0
- package/src/components/GraphRenderer.test.tsx +0 -118
|
@@ -739,6 +739,50 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
|
|
|
739
739
|
[fitFocusBounds, renderThroughlineViewer],
|
|
740
740
|
);
|
|
741
741
|
|
|
742
|
+
// Arrow keys step through the focused throughline once a step is active
|
|
743
|
+
// (sidebar click or drawer open). Ignores typing targets and chords.
|
|
744
|
+
useEffect(() => {
|
|
745
|
+
if (focusedThroughlineId == null || focusedStepIndex == null || !throughlines) {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
const tl = throughlines.find((t) => t.id === focusedThroughlineId);
|
|
749
|
+
if (!tl || tl.steps.length === 0) return;
|
|
750
|
+
|
|
751
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
752
|
+
const el = e.target as HTMLElement | null;
|
|
753
|
+
if (
|
|
754
|
+
el &&
|
|
755
|
+
(el.tagName === 'INPUT' ||
|
|
756
|
+
el.tagName === 'TEXTAREA' ||
|
|
757
|
+
el.tagName === 'SELECT' ||
|
|
758
|
+
el.isContentEditable)
|
|
759
|
+
) {
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
763
|
+
|
|
764
|
+
let next: number | null = null;
|
|
765
|
+
if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
|
|
766
|
+
next = Math.min(tl.steps.length - 1, focusedStepIndex + 1);
|
|
767
|
+
} else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
|
|
768
|
+
next = Math.max(0, focusedStepIndex - 1);
|
|
769
|
+
} else {
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
if (next === focusedStepIndex) return;
|
|
773
|
+
e.preventDefault();
|
|
774
|
+
focusThroughlineStep(tl, next);
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
window.addEventListener('keydown', onKeyDown);
|
|
778
|
+
return () => window.removeEventListener('keydown', onKeyDown);
|
|
779
|
+
}, [
|
|
780
|
+
focusedThroughlineId,
|
|
781
|
+
focusedStepIndex,
|
|
782
|
+
throughlines,
|
|
783
|
+
focusThroughlineStep,
|
|
784
|
+
]);
|
|
785
|
+
|
|
742
786
|
const toggleThroughlineCollapsed = useCallback((tlId: string) => {
|
|
743
787
|
setExpandedThroughlines((prev) => {
|
|
744
788
|
const next = new Set(prev);
|
|
@@ -1363,6 +1407,15 @@ function ThroughlineFlow({
|
|
|
1363
1407
|
const [headerHover, setHeaderHover] = useState(false);
|
|
1364
1408
|
const [closeHover, setCloseHover] = useState(false);
|
|
1365
1409
|
const [hoveredStep, setHoveredStep] = useState<number | null>(null);
|
|
1410
|
+
const stepButtonRefs = useRef<(HTMLButtonElement | null)[]>([]);
|
|
1411
|
+
|
|
1412
|
+
// Keep DOM focus on the active step so the browser focus ring (and
|
|
1413
|
+
// subsequent arrow keys) follow arrow navigation, not the originally
|
|
1414
|
+
// clicked button.
|
|
1415
|
+
useEffect(() => {
|
|
1416
|
+
if (active?.stepIndex == null) return;
|
|
1417
|
+
stepButtonRefs.current[active.stepIndex]?.focus({ preventScroll: true });
|
|
1418
|
+
}, [active?.stepIndex]);
|
|
1366
1419
|
|
|
1367
1420
|
return (
|
|
1368
1421
|
<div style={{ margin: '4px 0', borderRadius: 8 }}>
|
|
@@ -1460,6 +1513,9 @@ function ThroughlineFlow({
|
|
|
1460
1513
|
return (
|
|
1461
1514
|
<button
|
|
1462
1515
|
key={`${step.edgeId}-${i}`}
|
|
1516
|
+
ref={(el) => {
|
|
1517
|
+
stepButtonRefs.current[i] = el;
|
|
1518
|
+
}}
|
|
1463
1519
|
type="button"
|
|
1464
1520
|
onMouseEnter={() => setHoveredStep(i)}
|
|
1465
1521
|
onMouseLeave={() => setHoveredStep(null)}
|
|
@@ -1473,6 +1529,7 @@ function ThroughlineFlow({
|
|
|
1473
1529
|
textAlign: 'left',
|
|
1474
1530
|
borderRadius: 6,
|
|
1475
1531
|
border: 'none',
|
|
1532
|
+
outline: 'none',
|
|
1476
1533
|
background: stepActive || hoveredStep === i ? hoverBg : 'transparent',
|
|
1477
1534
|
cursor: 'pointer',
|
|
1478
1535
|
transition: 'background 120ms ease',
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { render, screen } from '@testing-library/react';
|
|
3
|
-
import { GraphRenderer } from './GraphRenderer';
|
|
4
|
-
import type { GraphConfiguration, NodeState, EdgeState } from '@principal-ai/principal-view-core';
|
|
5
|
-
|
|
6
|
-
describe('GraphRenderer', () => {
|
|
7
|
-
const testConfig: GraphConfiguration = {
|
|
8
|
-
metadata: {
|
|
9
|
-
name: 'Test Graph',
|
|
10
|
-
version: '1.0.0',
|
|
11
|
-
},
|
|
12
|
-
nodeTypes: {
|
|
13
|
-
user: {
|
|
14
|
-
shape: 'circle',
|
|
15
|
-
color: '#4CAF50',
|
|
16
|
-
dataSchema: {
|
|
17
|
-
userId: { type: 'string', required: true },
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
edgeTypes: {
|
|
22
|
-
connection: {
|
|
23
|
-
style: 'solid',
|
|
24
|
-
directed: true,
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
allowedConnections: [{ from: 'user', to: 'user', via: 'connection' }],
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const testNodes: NodeState[] = [
|
|
31
|
-
{
|
|
32
|
-
id: 'user-1',
|
|
33
|
-
type: 'user',
|
|
34
|
-
data: { userId: 'alice' },
|
|
35
|
-
createdAt: Date.now(),
|
|
36
|
-
updatedAt: Date.now(),
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
id: 'user-2',
|
|
40
|
-
type: 'user',
|
|
41
|
-
data: { userId: 'bob' },
|
|
42
|
-
createdAt: Date.now(),
|
|
43
|
-
updatedAt: Date.now(),
|
|
44
|
-
},
|
|
45
|
-
];
|
|
46
|
-
|
|
47
|
-
const testEdges: EdgeState[] = [
|
|
48
|
-
{
|
|
49
|
-
id: 'conn-1',
|
|
50
|
-
type: 'connection',
|
|
51
|
-
from: 'user-1',
|
|
52
|
-
to: 'user-2',
|
|
53
|
-
createdAt: Date.now(),
|
|
54
|
-
updatedAt: Date.now(),
|
|
55
|
-
},
|
|
56
|
-
];
|
|
57
|
-
|
|
58
|
-
it('should render without crashing', () => {
|
|
59
|
-
render(<GraphRenderer configuration={testConfig} nodes={testNodes} edges={testEdges} />);
|
|
60
|
-
|
|
61
|
-
expect(screen.getByText(/Graph Renderer/i)).toBeDefined();
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('should display configuration name', () => {
|
|
65
|
-
render(<GraphRenderer configuration={testConfig} nodes={testNodes} edges={testEdges} />);
|
|
66
|
-
|
|
67
|
-
expect(screen.getByText(/Test Graph/i)).toBeDefined();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('should display node count', () => {
|
|
71
|
-
render(<GraphRenderer configuration={testConfig} nodes={testNodes} edges={testEdges} />);
|
|
72
|
-
|
|
73
|
-
expect(screen.getByText(/Nodes: 2/i)).toBeDefined();
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('should display edge count', () => {
|
|
77
|
-
render(<GraphRenderer configuration={testConfig} nodes={testNodes} edges={testEdges} />);
|
|
78
|
-
|
|
79
|
-
expect(screen.getByText(/Edges: 1/i)).toBeDefined();
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('should render with empty nodes and edges', () => {
|
|
83
|
-
render(<GraphRenderer configuration={testConfig} nodes={[]} edges={[]} />);
|
|
84
|
-
|
|
85
|
-
expect(screen.getByText(/Nodes: 0/i)).toBeDefined();
|
|
86
|
-
expect(screen.getByText(/Edges: 0/i)).toBeDefined();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('should apply custom className', () => {
|
|
90
|
-
const { container } = render(
|
|
91
|
-
<GraphRenderer
|
|
92
|
-
configuration={testConfig}
|
|
93
|
-
nodes={testNodes}
|
|
94
|
-
edges={testEdges}
|
|
95
|
-
className="custom-class"
|
|
96
|
-
/>
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
const element = container.querySelector('.custom-class');
|
|
100
|
-
expect(element).toBeDefined();
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('should apply custom width and height', () => {
|
|
104
|
-
const { container } = render(
|
|
105
|
-
<GraphRenderer
|
|
106
|
-
configuration={testConfig}
|
|
107
|
-
nodes={testNodes}
|
|
108
|
-
edges={testEdges}
|
|
109
|
-
width="500px"
|
|
110
|
-
height="400px"
|
|
111
|
-
/>
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
const element = container.querySelector('div');
|
|
115
|
-
expect(element?.style.width).toBe('500px');
|
|
116
|
-
expect(element?.style.height).toBe('400px');
|
|
117
|
-
});
|
|
118
|
-
});
|