@principal-ai/principal-view-react 0.6.7 → 0.6.9

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.
@@ -0,0 +1,630 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { GraphRenderer } from '../components/GraphRenderer';
4
+ import type { ExtendedCanvas, GraphEvent } from '@principal-ai/principal-view-core';
5
+
6
+ // Helper component that sets initial node states via events
7
+ const GraphWithInitialStates: React.FC<{
8
+ canvas: ExtendedCanvas;
9
+ width: number;
10
+ height: number;
11
+ showMinimap: boolean;
12
+ initialStates?: Record<string, string>;
13
+ }> = ({ canvas, width, height, showMinimap, initialStates }) => {
14
+ const [events, setEvents] = useState<GraphEvent[]>([]);
15
+
16
+ useEffect(() => {
17
+ if (initialStates) {
18
+ const stateEvents: GraphEvent[] = Object.entries(initialStates).map(([nodeId, newState], idx) => ({
19
+ id: `init-state-${idx}`,
20
+ type: 'state_changed',
21
+ timestamp: Date.now(),
22
+ category: 'state' as const,
23
+ payload: { nodeId, newState },
24
+ }));
25
+ setEvents(stateEvents);
26
+ }
27
+ }, []);
28
+
29
+ return (
30
+ <GraphRenderer
31
+ canvas={canvas}
32
+ width={width}
33
+ height={height}
34
+ showMinimap={showMinimap}
35
+ events={events}
36
+ />
37
+ );
38
+ };
39
+
40
+ const meta = {
41
+ title: 'Audit/ColorPriority',
42
+ component: GraphRenderer,
43
+ parameters: {
44
+ layout: 'centered',
45
+ },
46
+ tags: ['autodocs'],
47
+ } satisfies Meta<typeof GraphRenderer>;
48
+
49
+ export default meta;
50
+ type Story = StoryObj<typeof meta>;
51
+
52
+ // ============================================================================
53
+ // Node Color Priority Story
54
+ // Priority: state color > node data color (pv.fill) > node.color > type definition color > default (#888)
55
+ // ============================================================================
56
+
57
+ const nodeColorPriorityCanvas: ExtendedCanvas = {
58
+ nodes: [
59
+ // 1. Default color only (no color specified anywhere)
60
+ {
61
+ id: 'default-only',
62
+ type: 'text',
63
+ x: 100,
64
+ y: 100,
65
+ width: 180,
66
+ height: 100,
67
+ text: 'SOURCE: Default\nEXPECT: Gray',
68
+ pv: {
69
+ nodeType: 'default-demo',
70
+ shape: 'rectangle',
71
+ },
72
+ },
73
+ // 2. Type definition color via node.color
74
+ {
75
+ id: 'node-color',
76
+ type: 'text',
77
+ x: 320,
78
+ y: 100,
79
+ width: 180,
80
+ height: 100,
81
+ text: 'SOURCE: node.color\nEXPECT: BLUE',
82
+ color: '#0000FF',
83
+ pv: {
84
+ nodeType: 'color-demo',
85
+ shape: 'rectangle',
86
+ },
87
+ },
88
+ // 3. pv.fill overrides node.color
89
+ {
90
+ id: 'pv-fill',
91
+ type: 'text',
92
+ x: 540,
93
+ y: 100,
94
+ width: 180,
95
+ height: 100,
96
+ text: 'SOURCE: pv.fill\nEXPECT: GREEN\n(node.color=BLUE ignored)',
97
+ color: '#0000FF', // This should be overridden
98
+ pv: {
99
+ nodeType: 'fill-demo',
100
+ shape: 'rectangle',
101
+ fill: '#00FF00', // This wins
102
+ },
103
+ },
104
+ // 4. State color overrides everything (idle state)
105
+ {
106
+ id: 'state-color-idle',
107
+ type: 'text',
108
+ x: 760,
109
+ y: 100,
110
+ width: 180,
111
+ height: 100,
112
+ text: 'SOURCE: state.idle.color\nEXPECT: RED\n(pv.fill & node.color ignored)',
113
+ color: '#0000FF', // Overridden
114
+ pv: {
115
+ nodeType: 'state-demo',
116
+ shape: 'rectangle',
117
+ fill: '#00FF00', // Overridden
118
+ states: {
119
+ idle: { label: 'Idle', color: '#FF0000' },
120
+ active: { label: 'Active', color: '#FFFF00' },
121
+ },
122
+ },
123
+ },
124
+ ],
125
+ edges: [],
126
+ pv: {
127
+ version: '1.0.0',
128
+ name: 'Node Color Priority Demo',
129
+ description: 'Shows how node colors are prioritized',
130
+ edgeTypes: {},
131
+ },
132
+ };
133
+
134
+ export const NodeColorPriority: Story = {
135
+ render: () => (
136
+ <GraphWithInitialStates
137
+ canvas={nodeColorPriorityCanvas}
138
+ width={1050}
139
+ height={320}
140
+ showMinimap={false}
141
+ initialStates={{ 'state-color-idle': 'idle' }}
142
+ />
143
+ ),
144
+ parameters: {
145
+ docs: {
146
+ description: {
147
+ story: `
148
+ **Node Fill Color Priority** (highest to lowest):
149
+
150
+ 1. **State color** - When a node has an active state with a color defined
151
+ 2. **pv.fill** - Explicit fill color in the PV extension
152
+ 3. **node.color** - The standard canvas node color property
153
+ 4. **Type definition color** - Color defined in the node type
154
+ 5. **Default** - Falls back to #888
155
+
156
+ In this demo:
157
+ - Node 1: No color specified → uses default #888
158
+ - Node 2: Only node.color → shows blue
159
+ - Node 3: Both node.color AND pv.fill → pv.fill (green) wins
160
+ - Node 4: Has state "idle" with purple color → state color wins over everything
161
+ `,
162
+ },
163
+ },
164
+ },
165
+ };
166
+
167
+ // ============================================================================
168
+ // Node Stroke Color Priority Story
169
+ // Priority: pv.stroke > type definition stroke > fill color
170
+ // ============================================================================
171
+
172
+ const strokeColorPriorityCanvas: ExtendedCanvas = {
173
+ nodes: [
174
+ // 1. Stroke defaults to fill color
175
+ {
176
+ id: 'stroke-default',
177
+ type: 'text',
178
+ x: 100,
179
+ y: 100,
180
+ width: 200,
181
+ height: 100,
182
+ text: 'SOURCE: Default (=fill)\nEXPECT BORDER: BLUE\n(same as fill color)',
183
+ color: '#0000FF',
184
+ pv: {
185
+ nodeType: 'stroke-default-demo',
186
+ shape: 'rectangle',
187
+ },
188
+ },
189
+ // 2. Explicit stroke color
190
+ {
191
+ id: 'stroke-explicit',
192
+ type: 'text',
193
+ x: 340,
194
+ y: 100,
195
+ width: 200,
196
+ height: 100,
197
+ text: 'SOURCE: pv.stroke\nEXPECT BORDER: RED\n(fill is BLUE)',
198
+ color: '#0000FF', // Fill is blue
199
+ pv: {
200
+ nodeType: 'stroke-explicit-demo',
201
+ shape: 'rectangle',
202
+ stroke: '#FF0000', // But stroke is red
203
+ },
204
+ },
205
+ // 3. Different fill and stroke
206
+ {
207
+ id: 'fill-and-stroke',
208
+ type: 'text',
209
+ x: 580,
210
+ y: 100,
211
+ width: 200,
212
+ height: 100,
213
+ text: 'SOURCE: pv.fill + pv.stroke\nEXPECT BORDER: YELLOW\nEXPECT FILL: GREEN',
214
+ pv: {
215
+ nodeType: 'both-demo',
216
+ shape: 'rectangle',
217
+ fill: '#00FF00',
218
+ stroke: '#FFFF00',
219
+ },
220
+ },
221
+ ],
222
+ edges: [],
223
+ pv: {
224
+ version: '1.0.0',
225
+ name: 'Stroke Color Priority Demo',
226
+ description: 'Shows how stroke colors are prioritized',
227
+ edgeTypes: {},
228
+ },
229
+ };
230
+
231
+ export const StrokeColorPriority: Story = {
232
+ args: {
233
+ canvas: strokeColorPriorityCanvas,
234
+ width: 880,
235
+ height: 320,
236
+ showMinimap: false,
237
+ },
238
+ parameters: {
239
+ docs: {
240
+ description: {
241
+ story: `
242
+ **Node Stroke Color Priority** (highest to lowest):
243
+
244
+ 1. **pv.stroke** - Explicit stroke color in the PV extension
245
+ 2. **Type definition stroke** - Stroke defined in node type
246
+ 3. **Fill color** - Falls back to the resolved fill color
247
+
248
+ In this demo:
249
+ - Node 1: No stroke specified → border matches fill color (blue)
250
+ - Node 2: Blue fill but red pv.stroke → red border
251
+ - Node 3: Green fill with orange stroke → independent colors
252
+ `,
253
+ },
254
+ },
255
+ },
256
+ };
257
+
258
+ // ============================================================================
259
+ // Edge Color Priority Story
260
+ // Priority: edge.color > edge type definition color > default
261
+ // ============================================================================
262
+
263
+ const edgeColorPriorityCanvas: ExtendedCanvas = {
264
+ nodes: [
265
+ {
266
+ id: 'source-1',
267
+ type: 'text',
268
+ x: 100,
269
+ y: 100,
270
+ width: 180,
271
+ height: 70,
272
+ text: 'SOURCE: edgeType color\nEXPECT EDGE: BLUE',
273
+ color: '#888888',
274
+ pv: { nodeType: 'source', shape: 'rectangle' },
275
+ },
276
+ {
277
+ id: 'target-1',
278
+ type: 'text',
279
+ x: 450,
280
+ y: 100,
281
+ width: 100,
282
+ height: 70,
283
+ text: 'Target',
284
+ color: '#888888',
285
+ pv: { nodeType: 'target', shape: 'rectangle' },
286
+ },
287
+ {
288
+ id: 'source-2',
289
+ type: 'text',
290
+ x: 100,
291
+ y: 220,
292
+ width: 180,
293
+ height: 70,
294
+ text: 'SOURCE: edge.color\nEXPECT EDGE: RED\n(edgeType=BLUE ignored)',
295
+ color: '#888888',
296
+ pv: { nodeType: 'source', shape: 'rectangle' },
297
+ },
298
+ {
299
+ id: 'target-2',
300
+ type: 'text',
301
+ x: 450,
302
+ y: 220,
303
+ width: 100,
304
+ height: 70,
305
+ text: 'Target',
306
+ color: '#888888',
307
+ pv: { nodeType: 'target', shape: 'rectangle' },
308
+ },
309
+ {
310
+ id: 'source-3',
311
+ type: 'text',
312
+ x: 100,
313
+ y: 340,
314
+ width: 180,
315
+ height: 70,
316
+ text: 'SOURCE: Default\nEXPECT EDGE: Gray',
317
+ color: '#888888',
318
+ pv: { nodeType: 'source', shape: 'rectangle' },
319
+ },
320
+ {
321
+ id: 'target-3',
322
+ type: 'text',
323
+ x: 450,
324
+ y: 340,
325
+ width: 100,
326
+ height: 70,
327
+ text: 'Target',
328
+ color: '#888888',
329
+ pv: { nodeType: 'target', shape: 'rectangle' },
330
+ },
331
+ ],
332
+ edges: [
333
+ // 1. Edge type color only (from edgeTypes definition)
334
+ {
335
+ id: 'edge-type-color',
336
+ fromNode: 'source-1',
337
+ toNode: 'target-1',
338
+ pv: {
339
+ edgeType: 'dataflow', // Uses edgeTypes.dataflow.color = BLUE
340
+ },
341
+ },
342
+ // 2. Edge's own color overrides edge type
343
+ {
344
+ id: 'edge-own-color',
345
+ fromNode: 'source-2',
346
+ toNode: 'target-2',
347
+ color: '#FF0000', // RED - overrides the edge type color
348
+ pv: {
349
+ edgeType: 'dataflow', // Would be blue, but overridden
350
+ },
351
+ },
352
+ // 3. No color specified (default)
353
+ {
354
+ id: 'edge-default',
355
+ fromNode: 'source-3',
356
+ toNode: 'target-3',
357
+ pv: {
358
+ edgeType: 'uncolored', // No color in edge type
359
+ },
360
+ },
361
+ ],
362
+ pv: {
363
+ version: '1.0.0',
364
+ name: 'Edge Color Priority Demo',
365
+ description: 'Shows how edge colors are prioritized',
366
+ edgeTypes: {
367
+ dataflow: {
368
+ style: 'solid',
369
+ color: '#0000FF', // BLUE
370
+ directed: true,
371
+ },
372
+ uncolored: {
373
+ style: 'dashed',
374
+ directed: true,
375
+ // No color defined
376
+ },
377
+ },
378
+ },
379
+ };
380
+
381
+ export const EdgeColorPriority: Story = {
382
+ args: {
383
+ canvas: edgeColorPriorityCanvas,
384
+ width: 650,
385
+ height: 520,
386
+ showMinimap: false,
387
+ },
388
+ parameters: {
389
+ docs: {
390
+ description: {
391
+ story: `
392
+ **Edge Color Priority** (highest to lowest):
393
+
394
+ 1. **edge.color** - The edge's own color property
395
+ 2. **Edge type definition color** - Color from pv.edgeTypes[type].color
396
+ 3. **Default** - System default edge color
397
+
398
+ In this demo:
399
+ - Edge 1: Uses "dataflow" type → shows cyan from edgeTypes
400
+ - Edge 2: Has edge.color AND uses "dataflow" type → purple (edge.color) wins
401
+ - Edge 3: Uses "uncolored" type with no color → default styling
402
+ `,
403
+ },
404
+ },
405
+ },
406
+ };
407
+
408
+ // ============================================================================
409
+ // Icon Priority Story
410
+ // Priority: node data icon > state icon > type definition icon
411
+ // ============================================================================
412
+
413
+ const iconPriorityCanvas: ExtendedCanvas = {
414
+ nodes: [
415
+ // 1. Type definition icon only
416
+ {
417
+ id: 'icon-type',
418
+ type: 'text',
419
+ x: 100,
420
+ y: 100,
421
+ width: 180,
422
+ height: 100,
423
+ text: 'SOURCE: pv.icon\nEXPECT ICON: Settings',
424
+ color: '#0000FF',
425
+ pv: {
426
+ nodeType: 'icon-type-demo',
427
+ shape: 'rectangle',
428
+ icon: 'settings',
429
+ },
430
+ },
431
+ // 2. State icon (when in a state)
432
+ {
433
+ id: 'icon-state',
434
+ type: 'text',
435
+ x: 320,
436
+ y: 100,
437
+ width: 180,
438
+ height: 100,
439
+ text: 'SOURCE: state.icon\nEXPECT ICON: Check\n(pv.icon=settings ignored)',
440
+ color: '#00FF00',
441
+ pv: {
442
+ nodeType: 'icon-state-demo',
443
+ shape: 'rectangle',
444
+ icon: 'settings', // Type icon - should be overridden
445
+ states: {
446
+ complete: { label: 'Complete', color: '#00FF00', icon: 'check' },
447
+ },
448
+ },
449
+ },
450
+ ],
451
+ edges: [],
452
+ pv: {
453
+ version: '1.0.0',
454
+ name: 'Icon Priority Demo',
455
+ description: 'Shows how icons are prioritized',
456
+ edgeTypes: {},
457
+ },
458
+ };
459
+
460
+ export const IconPriority: Story = {
461
+ render: () => (
462
+ <GraphWithInitialStates
463
+ canvas={iconPriorityCanvas}
464
+ width={600}
465
+ height={320}
466
+ showMinimap={false}
467
+ initialStates={{ 'icon-state': 'complete' }}
468
+ />
469
+ ),
470
+ parameters: {
471
+ docs: {
472
+ description: {
473
+ story: `
474
+ **Icon Priority** (highest to lowest):
475
+
476
+ 1. **Node data icon override** - Explicitly set icon in node data
477
+ 2. **State icon** - Icon from the current state definition
478
+ 3. **Type definition icon** - Icon from pv.icon
479
+
480
+ In this demo:
481
+ - Node 1: Only has type icon (settings) → shows settings
482
+ - Node 2: Has type icon (settings) BUT is in "complete" state with check icon → shows check
483
+ `,
484
+ },
485
+ },
486
+ },
487
+ };
488
+
489
+ // ============================================================================
490
+ // Combined Priority Demo - All in One
491
+ // ============================================================================
492
+
493
+ const combinedPriorityCanvas: ExtendedCanvas = {
494
+ nodes: [
495
+ // Row 1 label
496
+ {
497
+ id: 'label-row1',
498
+ type: 'text',
499
+ x: 50,
500
+ y: 50,
501
+ width: 700,
502
+ height: 40,
503
+ text: 'FILL COLOR PRIORITY (low to high): Default → node.color → pv.fill → state.color',
504
+ pv: { nodeType: 'label', shape: 'rectangle' },
505
+ },
506
+ // Lowest priority
507
+ {
508
+ id: 'fill-1',
509
+ type: 'text',
510
+ x: 50,
511
+ y: 120,
512
+ width: 150,
513
+ height: 80,
514
+ text: 'SOURCE: Default\nEXPECT: Gray',
515
+ pv: { nodeType: 'fill-demo', shape: 'rectangle' },
516
+ },
517
+ // node.color
518
+ {
519
+ id: 'fill-2',
520
+ type: 'text',
521
+ x: 230,
522
+ y: 120,
523
+ width: 150,
524
+ height: 80,
525
+ text: 'SOURCE: node.color\nEXPECT: BLUE',
526
+ color: '#0000FF',
527
+ pv: { nodeType: 'fill-demo', shape: 'rectangle' },
528
+ },
529
+ // pv.fill
530
+ {
531
+ id: 'fill-3',
532
+ type: 'text',
533
+ x: 410,
534
+ y: 120,
535
+ width: 150,
536
+ height: 80,
537
+ text: 'SOURCE: pv.fill\nEXPECT: GREEN',
538
+ color: '#0000FF',
539
+ pv: { nodeType: 'fill-demo', shape: 'rectangle', fill: '#00FF00' },
540
+ },
541
+ // state color
542
+ {
543
+ id: 'fill-4',
544
+ type: 'text',
545
+ x: 590,
546
+ y: 120,
547
+ width: 150,
548
+ height: 80,
549
+ text: 'SOURCE: state.color\nEXPECT: RED',
550
+ color: '#0000FF',
551
+ pv: {
552
+ nodeType: 'fill-demo',
553
+ shape: 'rectangle',
554
+ fill: '#00FF00',
555
+ states: { error: { label: 'Error', color: '#FF0000' } },
556
+ },
557
+ },
558
+ // Row 2 label
559
+ {
560
+ id: 'label-row2',
561
+ type: 'text',
562
+ x: 50,
563
+ y: 240,
564
+ width: 500,
565
+ height: 40,
566
+ text: 'STROKE PRIORITY (low to high): fill color → pv.stroke',
567
+ pv: { nodeType: 'label', shape: 'rectangle' },
568
+ },
569
+ // Stroke = fill
570
+ {
571
+ id: 'stroke-1',
572
+ type: 'text',
573
+ x: 50,
574
+ y: 310,
575
+ width: 180,
576
+ height: 80,
577
+ text: 'SOURCE: Default (=fill)\nEXPECT BORDER: BLUE',
578
+ color: '#0000FF',
579
+ pv: { nodeType: 'stroke-demo', shape: 'rectangle' },
580
+ },
581
+ // pv.stroke
582
+ {
583
+ id: 'stroke-2',
584
+ type: 'text',
585
+ x: 260,
586
+ y: 310,
587
+ width: 180,
588
+ height: 80,
589
+ text: 'SOURCE: pv.stroke\nEXPECT BORDER: YELLOW',
590
+ color: '#0000FF',
591
+ pv: { nodeType: 'stroke-demo', shape: 'rectangle', stroke: '#FFFF00' },
592
+ },
593
+ ],
594
+ edges: [],
595
+ pv: {
596
+ version: '1.0.0',
597
+ name: 'Combined Color Priority Demo',
598
+ description: 'Overview of all color priorities',
599
+ edgeTypes: {},
600
+ },
601
+ };
602
+
603
+ export const CombinedPriorityOverview: Story = {
604
+ render: () => (
605
+ <GraphWithInitialStates
606
+ canvas={combinedPriorityCanvas}
607
+ width={850}
608
+ height={480}
609
+ showMinimap={false}
610
+ initialStates={{ 'fill-4': 'error' }}
611
+ />
612
+ ),
613
+ parameters: {
614
+ docs: {
615
+ description: {
616
+ story: `
617
+ **Complete Color Priority Reference**
618
+
619
+ This story shows all color priority chains at a glance:
620
+
621
+ **Fill Color:** Default (#888) → node.color → pv.fill → State color
622
+
623
+ **Stroke Color:** Fill color → pv.stroke
624
+
625
+ Higher priority always wins when multiple values are specified.
626
+ `,
627
+ },
628
+ },
629
+ },
630
+ };
@@ -28,7 +28,7 @@ const sampleCanvas: ExtendedCanvas = {
28
28
  height: 70,
29
29
  text: 'Source',
30
30
  color: '#4A90E2',
31
- vv: {
31
+ pv: {
32
32
  nodeType: 'process',
33
33
  shape: 'rectangle',
34
34
  icon: 'Settings',
@@ -49,7 +49,7 @@ const sampleCanvas: ExtendedCanvas = {
49
49
  height: 70,
50
50
  text: 'Processor',
51
51
  color: '#4A90E2',
52
- vv: {
52
+ pv: {
53
53
  nodeType: 'process',
54
54
  shape: 'rectangle',
55
55
  icon: 'Settings',
@@ -70,7 +70,7 @@ const sampleCanvas: ExtendedCanvas = {
70
70
  height: 100,
71
71
  text: 'Storage',
72
72
  color: '#7B68EE',
73
- vv: {
73
+ pv: {
74
74
  nodeType: 'data',
75
75
  shape: 'circle',
76
76
  icon: 'Database',
@@ -82,16 +82,16 @@ const sampleCanvas: ExtendedCanvas = {
82
82
  id: 'edge-1',
83
83
  fromNode: 'node-1',
84
84
  toNode: 'node-2',
85
- vv: { edgeType: 'dataflow' },
85
+ pv: { edgeType: 'dataflow' },
86
86
  },
87
87
  {
88
88
  id: 'edge-2',
89
89
  fromNode: 'node-2',
90
90
  toNode: 'node-3',
91
- vv: { edgeType: 'dataflow' },
91
+ pv: { edgeType: 'dataflow' },
92
92
  },
93
93
  ],
94
- vv: {
94
+ pv: {
95
95
  version: '1.0.0',
96
96
  name: 'Event-Driven Animation Demo',
97
97
  description: 'Demonstrates event-driven animations',