@bavuchoko/edge-node 0.0.0-stage → 0.1.0

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,20 @@
1
+ import type { GraphNode, NodeData, NodeId, Point } from './types';
2
+ export type LayoutNodeInput<TData = NodeData> = {
3
+ id: NodeId;
4
+ data: TData;
5
+ /** Column index (0 = leftmost). */
6
+ column: number;
7
+ /** Order within column (top to bottom). */
8
+ row: number;
9
+ /** Fixed position (e.g. restored from storage). Overrides `column` / `row` placement. */
10
+ position?: Point;
11
+ width?: number;
12
+ height?: number;
13
+ };
14
+ /**
15
+ * Simple left-to-right column layout. Data matching `NodeData` is checked
16
+ * against it (so `status: 'success'` stays a literal); any other shape is
17
+ * inferred — pass `layoutColumns<MyData>(...)` to pin it down.
18
+ */
19
+ export declare function layoutColumns(inputs: LayoutNodeInput<NodeData>[]): GraphNode<NodeData>[];
20
+ export declare function layoutColumns<TData>(inputs: LayoutNodeInput<TData>[]): GraphNode<TData>[];
@@ -0,0 +1,46 @@
1
+ import type { EdgeId, EdgePatch, GraphEdge, GraphEdgeInput, GraphNode, GraphState, NodeData, NodeId, Point, Selection, Viewport } from './types';
2
+ export declare function createInitialState<TData = NodeData>(nodes: GraphNode<TData>[], edges: GraphEdgeInput[], viewport?: Viewport): GraphState<TData>;
3
+ export type GraphAction = {
4
+ type: 'setViewport';
5
+ viewport: Viewport;
6
+ } | {
7
+ type: 'moveNode';
8
+ id: NodeId;
9
+ position: Point;
10
+ } | {
11
+ type: 'resizeNode';
12
+ id: NodeId;
13
+ width: number;
14
+ height: number;
15
+ } | {
16
+ type: 'setSelection';
17
+ selection: Selection;
18
+ } | {
19
+ type: 'startConnection';
20
+ sourceId: NodeId;
21
+ pointer: Point;
22
+ } | {
23
+ type: 'updateConnection';
24
+ pointer: Point;
25
+ } | {
26
+ type: 'cancelConnection';
27
+ } | {
28
+ type: 'completeConnection';
29
+ targetId: NodeId;
30
+ allowCycles?: boolean;
31
+ } | {
32
+ type: 'updateEdge';
33
+ id: EdgeId;
34
+ patch: EdgePatch;
35
+ } | {
36
+ type: 'addEdge';
37
+ edge: GraphEdge;
38
+ } | {
39
+ type: 'removeEdge';
40
+ id: EdgeId;
41
+ } | {
42
+ type: 'deleteSelection';
43
+ };
44
+ export declare function graphReducer<TData>(state: GraphState<TData>, action: GraphAction): GraphState<TData>;
45
+ export declare function getNodes<TData>(state: GraphState<TData>): GraphNode<TData>[];
46
+ export declare function getEdges<TData>(state: GraphState<TData>): GraphEdge[];
package/dist/style.css ADDED
@@ -0,0 +1,628 @@
1
+ .graph-canvas {
2
+ position: relative;
3
+ width: 100%;
4
+ height: 100%;
5
+ overflow: hidden;
6
+ background-color: var(--graph-bg, #141414);
7
+ background-image:
8
+ radial-gradient(
9
+ circle at 1px 1px,
10
+ var(--graph-grid-color, rgba(255, 255, 255, 0.04)) 1px,
11
+ transparent 0
12
+ );
13
+ background-size: var(--graph-grid-size, 24px) var(--graph-grid-size, 24px);
14
+ cursor: grab;
15
+ touch-action: none;
16
+ user-select: none;
17
+ }
18
+
19
+ .graph-canvas.is-panning {
20
+ cursor: grabbing;
21
+ }
22
+
23
+ .graph-canvas.is-connecting {
24
+ cursor: crosshair;
25
+ }
26
+
27
+ .graph-world {
28
+ position: absolute;
29
+ inset: 0;
30
+ transform-origin: 0 0;
31
+ width: 0;
32
+ height: 0;
33
+ }
34
+
35
+ .graph-edges {
36
+ position: absolute;
37
+ left: 0;
38
+ top: 0;
39
+ overflow: visible;
40
+ width: 1px;
41
+ height: 1px;
42
+ pointer-events: none;
43
+ z-index: 1;
44
+ }
45
+
46
+ .graph-edges .graph-edge {
47
+ pointer-events: stroke;
48
+ cursor: pointer;
49
+ }
50
+
51
+ .graph-edge {
52
+ color: var(--graph-edge-color, #6b7280);
53
+ transition: opacity 0.15s ease;
54
+ }
55
+
56
+ .graph-edge:hover {
57
+ color: var(--graph-edge-hover-color, #9ca3af);
58
+ }
59
+
60
+ .graph-edge.is-upstream {
61
+ color: var(--graph-dep-upstream, #a78bfa);
62
+ }
63
+
64
+ .graph-edge.is-downstream {
65
+ color: var(--graph-dep-downstream, #f59e0b);
66
+ }
67
+
68
+ .graph-edge.is-selected {
69
+ color: var(--graph-edge-selected-color, #93c5fd);
70
+ }
71
+
72
+ .graph-edge.is-dimmed {
73
+ opacity: var(--graph-dep-dim-opacity, 0.25);
74
+ }
75
+
76
+ .graph-edge.is-pending {
77
+ opacity: 0.5;
78
+ }
79
+
80
+ .graph-edge__hit {
81
+ fill: none;
82
+ stroke: transparent;
83
+ pointer-events: stroke;
84
+ }
85
+
86
+ .graph-edge__path {
87
+ fill: none;
88
+ stroke: currentColor;
89
+ stroke-width: var(--graph-edge-width, 2);
90
+ pointer-events: none;
91
+ vector-effect: non-scaling-stroke;
92
+ transition: stroke 0.12s ease, stroke-width 0.12s ease;
93
+ }
94
+
95
+ .graph-edge--optional .graph-edge__path {
96
+ stroke-dasharray: var(--graph-edge-optional-dash, 5 5);
97
+ }
98
+
99
+ .graph-edge:hover .graph-edge__path {
100
+ stroke-width: var(--graph-edge-hover-width, 2.75);
101
+ }
102
+
103
+ .graph-edge.is-upstream .graph-edge__path,
104
+ .graph-edge.is-downstream .graph-edge__path {
105
+ stroke-width: var(--graph-edge-hover-width, 2.75);
106
+ }
107
+
108
+ .graph-edge.is-selected .graph-edge__path {
109
+ stroke-width: var(--graph-edge-selected-width, 3);
110
+ }
111
+
112
+ .graph-edge__arrow {
113
+ fill: currentColor;
114
+ pointer-events: none;
115
+ transition: fill 0.12s ease;
116
+ }
117
+
118
+ .graph-edge__label {
119
+ fill: var(--graph-edge-label-color, #9ca3af);
120
+ font-size: 11px;
121
+ font-weight: 500;
122
+ text-anchor: middle;
123
+ paint-order: stroke;
124
+ stroke: var(--graph-bg, #141414);
125
+ stroke-width: 4px;
126
+ stroke-linejoin: round;
127
+ pointer-events: none;
128
+ }
129
+
130
+ .graph-edge.is-upstream .graph-edge__label,
131
+ .graph-edge.is-downstream .graph-edge__label,
132
+ .graph-edge.is-selected .graph-edge__label {
133
+ fill: currentColor;
134
+ }
135
+
136
+ .graph-connection-line__path {
137
+ fill: none;
138
+ stroke: var(--graph-edge-draft-color, #60a5fa);
139
+ stroke-width: var(--graph-edge-draft-width, 2);
140
+ stroke-dasharray: var(--graph-edge-draft-dash, 6 4);
141
+ pointer-events: none;
142
+ vector-effect: non-scaling-stroke;
143
+ }
144
+
145
+ .graph-connection-line__arrow {
146
+ fill: var(--graph-edge-draft-color, #60a5fa);
147
+ pointer-events: none;
148
+ }
149
+
150
+ .graph-nodes {
151
+ position: absolute;
152
+ left: 0;
153
+ top: 0;
154
+ z-index: 2;
155
+ }
156
+
157
+ .graph-node {
158
+ position: absolute;
159
+ box-sizing: border-box;
160
+ display: flex;
161
+ flex-direction: column;
162
+ padding: 14px 16px 14px;
163
+ border-radius: var(--graph-node-radius, 6px);
164
+ background: var(--graph-node-bg, #1e1e1e);
165
+ border: 1px solid var(--graph-node-border, #2e2e2e);
166
+ color: var(--graph-node-title, #f3f4f6);
167
+ text-align: left;
168
+ box-shadow: var(--graph-node-shadow, 0 8px 24px rgba(0, 0, 0, 0.35));
169
+ cursor: grab;
170
+ overflow: visible;
171
+ transition: opacity 0.15s ease;
172
+ }
173
+
174
+ .graph-node:hover {
175
+ border-color: var(--graph-node-border-hover, #3f3f46);
176
+ }
177
+
178
+ .graph-node.is-selected {
179
+ border-color: var(--graph-node-border-selected, #60a5fa);
180
+ box-shadow:
181
+ 0 0 0 1px color-mix(in srgb, var(--graph-node-border-selected, #60a5fa) 35%, transparent),
182
+ var(--graph-node-shadow, 0 8px 24px rgba(0, 0, 0, 0.4));
183
+ }
184
+
185
+ .graph-node.is-upstream {
186
+ border-color: var(--graph-dep-upstream, #a78bfa);
187
+ }
188
+
189
+ .graph-node.is-downstream {
190
+ border-color: var(--graph-dep-downstream, #f59e0b);
191
+ }
192
+
193
+ .graph-node.is-dimmed {
194
+ opacity: var(--graph-dep-dim-opacity, 0.25);
195
+ }
196
+
197
+ .graph-node__header {
198
+ display: flex;
199
+ flex-direction: column;
200
+ gap: 6px;
201
+ margin-bottom: 10px;
202
+ }
203
+
204
+ .graph-node__header-row {
205
+ display: flex;
206
+ align-items: center;
207
+ justify-content: space-between;
208
+ gap: 8px;
209
+ }
210
+
211
+ .graph-node__role {
212
+ max-width: 160px;
213
+ overflow: hidden;
214
+ padding: 1px 6px;
215
+ border-radius: 4px;
216
+ font-size: 11px;
217
+ font-weight: 600;
218
+ line-height: 1.5;
219
+ text-overflow: ellipsis;
220
+ white-space: nowrap;
221
+ }
222
+
223
+ .graph-node__role--upstream {
224
+ color: var(--graph-dep-upstream, #a78bfa);
225
+ background: color-mix(in srgb, var(--graph-dep-upstream, #a78bfa) 14%, transparent);
226
+ }
227
+
228
+ .graph-node__role--downstream {
229
+ color: var(--graph-dep-downstream, #f59e0b);
230
+ background: color-mix(in srgb, var(--graph-dep-downstream, #f59e0b) 14%, transparent);
231
+ }
232
+
233
+ .graph-node__status {
234
+ display: inline-flex;
235
+ width: fit-content;
236
+ }
237
+
238
+ .graph-node__status--success {
239
+ color: var(--graph-status-success, #22c55e);
240
+ }
241
+
242
+ .graph-node__status--error {
243
+ color: var(--graph-status-error, #ef4444);
244
+ }
245
+
246
+ .graph-node__status--idle {
247
+ color: var(--graph-status-idle, #9ca3af);
248
+ }
249
+
250
+ .graph-node__stats {
251
+ margin: 0;
252
+ font-size: 11px;
253
+ line-height: 1.4;
254
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
255
+ letter-spacing: -0.01em;
256
+ overflow-wrap: anywhere;
257
+ }
258
+
259
+ .graph-node.is-success .graph-node__stats {
260
+ color: var(--graph-status-success, #4ade80);
261
+ }
262
+
263
+ .graph-node.is-error .graph-node__stats {
264
+ color: var(--graph-status-error, #f87171);
265
+ }
266
+
267
+ .graph-node.is-idle .graph-node__stats {
268
+ color: var(--graph-status-idle, #9ca3af);
269
+ }
270
+
271
+ .graph-node__title {
272
+ margin: 0;
273
+ font-size: 16px;
274
+ font-weight: 650;
275
+ line-height: 1.25;
276
+ color: var(--graph-node-title, #f9fafb);
277
+ letter-spacing: -0.02em;
278
+ }
279
+
280
+ .graph-node__subtitle {
281
+ margin: 4px 0 0;
282
+ font-size: 12px;
283
+ color: var(--graph-node-subtitle, #6b7280);
284
+ line-height: 1.3;
285
+ }
286
+
287
+ .graph-node__footer {
288
+ display: flex;
289
+ align-items: center;
290
+ gap: 8px;
291
+ margin-top: 12px;
292
+ }
293
+
294
+ .graph-node__badge {
295
+ display: inline-flex;
296
+ align-items: center;
297
+ padding: 2px 8px;
298
+ border-radius: 4px;
299
+ background: var(--graph-node-badge-bg, #2563eb);
300
+ color: var(--graph-node-badge-text, #eff6ff);
301
+ font-size: 11px;
302
+ font-weight: 600;
303
+ line-height: 1.4;
304
+ }
305
+
306
+ .graph-node__handle {
307
+ position: absolute;
308
+ top: 50%;
309
+ width: 12px;
310
+ height: 12px;
311
+ margin: 0;
312
+ padding: 0;
313
+ border: 2px solid var(--graph-node-handle, #9ca3af);
314
+ border-radius: 50%;
315
+ background: var(--graph-node-bg, #1e1e1e);
316
+ transform: translateY(-50%);
317
+ cursor: crosshair;
318
+ z-index: 2;
319
+ }
320
+
321
+ .graph-node__handle::before {
322
+ content: '';
323
+ position: absolute;
324
+ inset: -10px;
325
+ }
326
+
327
+ .graph-node__handle:hover,
328
+ .graph-node__handle:focus-visible {
329
+ border-color: var(--graph-node-border-selected, #60a5fa);
330
+ background: var(--graph-node-handle-active, #2563eb);
331
+ outline: none;
332
+ }
333
+
334
+ .graph-node__handle--source {
335
+ right: -7px;
336
+ }
337
+
338
+ .graph-node__handle--target {
339
+ left: -7px;
340
+ }
341
+
342
+ .graph-hud {
343
+ position: absolute;
344
+ left: 16px;
345
+ bottom: 14px;
346
+ color: var(--graph-hud-color, #6b7280);
347
+ font-size: 12px;
348
+ pointer-events: none;
349
+ letter-spacing: 0.01em;
350
+ }
351
+
352
+ .graph-hud {
353
+ display: flex;
354
+ flex-direction: column;
355
+ gap: 6px;
356
+ }
357
+
358
+ .graph-hud__legend {
359
+ display: inline-flex;
360
+ align-items: center;
361
+ gap: 6px;
362
+ }
363
+
364
+ .graph-hud__swatch {
365
+ display: inline-block;
366
+ width: 10px;
367
+ height: 10px;
368
+ border-radius: 2px;
369
+ }
370
+
371
+ .graph-hud__swatch:not(:first-child) {
372
+ margin-left: 8px;
373
+ }
374
+
375
+ .graph-hud__swatch--upstream {
376
+ background: var(--graph-dep-upstream, #a78bfa);
377
+ }
378
+
379
+ .graph-hud__swatch--downstream {
380
+ background: var(--graph-dep-downstream, #f59e0b);
381
+ }
382
+
383
+ .graph-edge-editor {
384
+ position: absolute;
385
+ z-index: 3;
386
+ display: grid;
387
+ gap: 6px;
388
+ width: 340px;
389
+ padding: 6px;
390
+ border: 1px solid var(--graph-node-border, #2e2e2e);
391
+ border-radius: var(--graph-node-radius, 6px);
392
+ background: var(--graph-node-bg, #1e1e1e);
393
+ box-shadow: var(--graph-node-shadow, 0 8px 24px rgba(0, 0, 0, 0.35));
394
+ color: var(--graph-node-title, #f9fafb);
395
+ font-size: 12px;
396
+ transform: translateX(-50%);
397
+ cursor: default;
398
+ user-select: none;
399
+ }
400
+
401
+ .graph-edge-editor__row {
402
+ display: flex;
403
+ align-items: center;
404
+ justify-content: space-between;
405
+ gap: 6px;
406
+ }
407
+
408
+ .graph-edge-editor__kinds {
409
+ display: flex;
410
+ padding: 2px;
411
+ border-radius: 4px;
412
+ background: color-mix(in srgb, var(--graph-node-title, #f9fafb) 6%, transparent);
413
+ }
414
+
415
+ .graph-edge-editor__kind {
416
+ display: inline-flex;
417
+ align-items: center;
418
+ gap: 6px;
419
+ padding: 4px 8px;
420
+ border: 0;
421
+ border-radius: 3px;
422
+ background: transparent;
423
+ color: var(--graph-node-subtitle, #6b7280);
424
+ font-size: 12px;
425
+ cursor: pointer;
426
+ }
427
+
428
+ .graph-edge-editor__kind:hover {
429
+ color: var(--graph-node-title, #f9fafb);
430
+ }
431
+
432
+ .graph-edge-editor__kind.is-active {
433
+ background: var(--graph-node-bg, #1e1e1e);
434
+ color: var(--graph-node-title, #f9fafb);
435
+ box-shadow: 0 0 0 1px var(--graph-node-border, #2e2e2e);
436
+ }
437
+
438
+ .graph-edge-editor__field {
439
+ display: grid;
440
+ grid-template-columns: 56px 1fr;
441
+ align-items: center;
442
+ gap: 6px;
443
+ }
444
+
445
+ .graph-edge-editor__field > span {
446
+ padding-left: 2px;
447
+ color: var(--graph-node-subtitle, #6b7280);
448
+ font-size: 11px;
449
+ }
450
+
451
+ .graph-edge-editor__relations {
452
+ display: grid;
453
+ grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
454
+ align-items: end;
455
+ column-gap: 6px;
456
+ margin: 0;
457
+ padding: 6px 0 0;
458
+ border: 0;
459
+ border-top: 1px solid var(--graph-node-border, #2e2e2e);
460
+ }
461
+
462
+ .graph-edge-editor__relations > legend {
463
+ float: left;
464
+ grid-column: 1 / -1;
465
+ width: 100%;
466
+ margin-bottom: 4px;
467
+ padding: 0 2px;
468
+ color: var(--graph-node-subtitle, #6b7280);
469
+ font-size: 10px;
470
+ font-weight: 600;
471
+ letter-spacing: 0.04em;
472
+ text-transform: uppercase;
473
+ }
474
+
475
+ .graph-edge-editor__field input,
476
+ .graph-edge-editor__relation input {
477
+ min-width: 0;
478
+ padding: 5px 8px;
479
+ border: 1px solid var(--graph-node-border, #2e2e2e);
480
+ border-radius: 4px;
481
+ background: transparent;
482
+ color: inherit;
483
+ font: inherit;
484
+ user-select: text;
485
+ }
486
+
487
+ .graph-edge-editor__field input::placeholder,
488
+ .graph-edge-editor__relation input::placeholder {
489
+ color: var(--graph-node-subtitle, #6b7280);
490
+ }
491
+
492
+ .graph-edge-editor__field input:focus,
493
+ .graph-edge-editor__relation input:focus {
494
+ outline: none;
495
+ border-color: var(--graph-node-border-selected, #60a5fa);
496
+ }
497
+
498
+ .graph-edge-editor__relation {
499
+ display: grid;
500
+ gap: 4px;
501
+ min-width: 0;
502
+ }
503
+
504
+ .graph-edge-editor__relation > span {
505
+ overflow: hidden;
506
+ padding-left: 2px;
507
+ color: var(--graph-node-title, #f9fafb);
508
+ font-size: 11px;
509
+ font-weight: 600;
510
+ text-overflow: ellipsis;
511
+ white-space: nowrap;
512
+ }
513
+
514
+ .graph-edge-editor__arrow {
515
+ margin-bottom: 9px;
516
+ color: var(--graph-node-subtitle, #6b7280);
517
+ }
518
+
519
+ .graph-edge-editor__delete {
520
+ display: inline-flex;
521
+ align-items: center;
522
+ justify-content: center;
523
+ width: 28px;
524
+ height: 28px;
525
+ padding: 0;
526
+ border: 0;
527
+ border-radius: 4px;
528
+ background: transparent;
529
+ color: var(--graph-node-subtitle, #6b7280);
530
+ cursor: pointer;
531
+ }
532
+
533
+ .graph-edge-editor__delete:hover {
534
+ background: color-mix(in srgb, var(--graph-status-error, #ef4444) 14%, transparent);
535
+ color: var(--graph-status-error, #ef4444);
536
+ }
537
+
538
+ /* View-only mode: edges and handles ignore the pointer; nodes only expand. */
539
+ .graph-canvas.is-readonly .graph-edges .graph-edge,
540
+ .graph-canvas.is-readonly .graph-node__handle {
541
+ pointer-events: none;
542
+ }
543
+
544
+ .graph-canvas.is-readonly .graph-node {
545
+ cursor: default;
546
+ }
547
+
548
+ .graph-canvas.is-readonly .graph-node.is-expandable {
549
+ cursor: pointer;
550
+ }
551
+
552
+ .graph-node.is-expanded {
553
+ z-index: 3;
554
+ }
555
+
556
+ .graph-node__header-end {
557
+ display: flex;
558
+ align-items: center;
559
+ gap: 6px;
560
+ }
561
+
562
+ .graph-node__toggle {
563
+ display: inline-flex;
564
+ align-items: center;
565
+ justify-content: center;
566
+ width: 22px;
567
+ height: 22px;
568
+ margin: -4px -6px -4px 0;
569
+ padding: 0;
570
+ border: 0;
571
+ border-radius: 4px;
572
+ background: transparent;
573
+ color: var(--graph-node-subtitle, #6b7280);
574
+ cursor: pointer;
575
+ }
576
+
577
+ .graph-node__toggle:hover {
578
+ background: color-mix(in srgb, var(--graph-node-title, #f9fafb) 8%, transparent);
579
+ color: var(--graph-node-title, #f9fafb);
580
+ }
581
+
582
+ .graph-node__toggle svg {
583
+ transition: transform 0.15s ease;
584
+ }
585
+
586
+ .graph-node.is-expanded .graph-node__toggle svg {
587
+ transform: rotate(180deg);
588
+ }
589
+
590
+ .graph-node__details {
591
+ margin-top: 12px;
592
+ padding-top: 12px;
593
+ border-top: 1px solid var(--graph-node-border, #2e2e2e);
594
+ color: var(--graph-node-title, #f9fafb);
595
+ font-size: 12px;
596
+ cursor: default;
597
+ }
598
+
599
+ .graph-node__detail-list {
600
+ display: grid;
601
+ gap: 6px;
602
+ margin: 0;
603
+ }
604
+
605
+ .graph-node__detail-row {
606
+ display: grid;
607
+ grid-template-columns: minmax(80px, auto) 1fr;
608
+ gap: 12px;
609
+ }
610
+
611
+ .graph-node__detail-row dt {
612
+ color: var(--graph-node-subtitle, #6b7280);
613
+ }
614
+
615
+ .graph-node__detail-row dd {
616
+ margin: 0;
617
+ overflow-wrap: anywhere;
618
+ }
619
+
620
+ /* renderNode: the card body is the host's; keep only the frame. */
621
+ .graph-node--custom {
622
+ padding: 0;
623
+ }
624
+
625
+ .graph-node--custom > .graph-node__details {
626
+ margin-top: 0;
627
+ padding: 12px 16px 14px;
628
+ }