@principal-ai/subsystems-react 0.35.7 → 0.36.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.
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/stories/Subsystem/{ComponentGraph → AggregateGraph}/aggregateViewFixture.d.ts.map +1 -1
- package/dist/stories/Subsystem/{ComponentGraph → AggregateGraph}/aggregateViewFixture.js.map +1 -1
- package/dist/subsystem/SubsystemAggregateGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemAggregateGraph.js +152 -21
- package/dist/subsystem/SubsystemAggregateGraph.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts +8 -0
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +9 -5
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/graphChrome.d.ts +15 -0
- package/dist/subsystem/graphChrome.d.ts.map +1 -1
- package/dist/subsystem/graphChrome.js +17 -0
- package/dist/subsystem/graphChrome.js.map +1 -1
- package/dist/subsystem/model.d.ts +26 -0
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +90 -2
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/nodes.d.ts.map +1 -1
- package/dist/subsystem/nodes.js +12 -9
- package/dist/subsystem/nodes.js.map +1 -1
- package/dist/utils/elkLayout.d.ts +45 -0
- package/dist/utils/elkLayout.d.ts.map +1 -1
- package/dist/utils/elkLayout.js +114 -84
- package/dist/utils/elkLayout.js.map +1 -1
- package/package.json +1 -1
- package/src/graphify/signature.test.ts +1 -1
- package/src/index.ts +1 -0
- package/src/stories/Subsystem/{ComponentGraph → AggregateGraph}/AggregateFrameGraph.stories.tsx +1 -1
- package/src/stories/Subsystem/ComponentGraph/FrameworkStereotype.stories.tsx +4 -4
- package/src/stories/Subsystem/ComponentGraph/ModuleBadges.stories.tsx +5 -5
- package/src/stories/Subsystem/ComponentGraph/Spotlights.stories.tsx +5 -5
- package/src/subsystem/SubsystemAggregateGraph.tsx +191 -21
- package/src/subsystem/SubsystemComponentGraph.tsx +18 -4
- package/src/subsystem/graphChrome.tsx +20 -0
- package/src/subsystem/model.test.ts +91 -0
- package/src/subsystem/model.ts +94 -2
- package/src/subsystem/nodes.tsx +12 -9
- package/src/utils/elkLayout.test.ts +117 -3
- package/src/utils/elkLayout.ts +150 -81
- /package/dist/stories/Subsystem/{ComponentGraph → AggregateGraph}/aggregateViewFixture.d.ts +0 -0
- /package/dist/stories/Subsystem/{ComponentGraph → AggregateGraph}/aggregateViewFixture.js +0 -0
- /package/src/stories/Subsystem/{ComponentGraph → AggregateGraph}/aggregateViewFixture.ts +0 -0
package/src/utils/elkLayout.ts
CHANGED
|
@@ -80,6 +80,16 @@ export interface ElkLayoutOptions {
|
|
|
80
80
|
* Members reference their immediate parent via React Flow `parentId`.
|
|
81
81
|
*/
|
|
82
82
|
groups?: Array<{ id: string; memberIds: string[]; parentId?: string; minWidth?: number }>;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Keep groups that hold a single leaf instead of dropping them and
|
|
86
|
+
* promoting their member to the parent. A one-child group is still a real
|
|
87
|
+
* boundary — a process with a single module, or a process whose components
|
|
88
|
+
* carry no module — so callers that own the grouping semantics opt in here.
|
|
89
|
+
* Callers must ensure each leaf appears in at most one group, or ELK throws
|
|
90
|
+
* on the duplicate. @default false
|
|
91
|
+
*/
|
|
92
|
+
keepSingletonGroups?: boolean;
|
|
83
93
|
}
|
|
84
94
|
|
|
85
95
|
/** Result of ELK layout computation */
|
|
@@ -341,6 +351,126 @@ function getElkOptions(options: ElkLayoutOptions): LayoutOptions {
|
|
|
341
351
|
return baseOptions;
|
|
342
352
|
}
|
|
343
353
|
|
|
354
|
+
/** A group definition accepted by {@link planCompoundGroups}. */
|
|
355
|
+
export interface CompoundGroupDef {
|
|
356
|
+
id: string;
|
|
357
|
+
memberIds: string[];
|
|
358
|
+
minWidth?: number;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** Which groups ELK builds, and which it drops. */
|
|
362
|
+
export interface CompoundGroupPlan {
|
|
363
|
+
/**
|
|
364
|
+
* Built groups in build order — a group's children always appear before it,
|
|
365
|
+
* so callers can map ids to shells in one forward pass.
|
|
366
|
+
*/
|
|
367
|
+
built: Array<{ id: string; childIds: string[]; minWidth?: number }>;
|
|
368
|
+
/**
|
|
369
|
+
* Dropped groups. Their members are promoted into the nearest built
|
|
370
|
+
* ancestor, so callers must clear those members' `parentId`.
|
|
371
|
+
*/
|
|
372
|
+
skipped: string[];
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Decide which compound groups survive, independently of the ELK runtime.
|
|
377
|
+
*
|
|
378
|
+
* Groups resolve bottom-up: a group is ready once every member is a leaf or an
|
|
379
|
+
* already-resolved group. A group is dropped when it would hold nothing, or —
|
|
380
|
+
* unless `keepSingletonGroups` — when it holds a single leaf, since a frame
|
|
381
|
+
* wrapped around one box reads worse than the box alone. Callers that keep
|
|
382
|
+
* singletons own the grouping semantics and must guarantee each leaf appears
|
|
383
|
+
* in at most one group, or ELK throws on the duplicate.
|
|
384
|
+
*
|
|
385
|
+
* Pure so the skip rules are testable without an ELK worker.
|
|
386
|
+
*/
|
|
387
|
+
export function planCompoundGroups(
|
|
388
|
+
groupDefs: CompoundGroupDef[],
|
|
389
|
+
leafIds: Iterable<string>,
|
|
390
|
+
keepSingletonGroups = false,
|
|
391
|
+
): CompoundGroupPlan {
|
|
392
|
+
const leaves = new Set(leafIds);
|
|
393
|
+
const byId = new Map(groupDefs.map((g) => [g.id, g]));
|
|
394
|
+
const built: CompoundGroupPlan['built'] = [];
|
|
395
|
+
const skipped = new Set<string>();
|
|
396
|
+
const pending = [...groupDefs];
|
|
397
|
+
|
|
398
|
+
/** Leaves below a group, counting through nested groups. */
|
|
399
|
+
const leafDescendantCount = (memberIds: string[]): number => {
|
|
400
|
+
let n = 0;
|
|
401
|
+
for (const mid of memberIds) {
|
|
402
|
+
if (leaves.has(mid)) n += 1;
|
|
403
|
+
else {
|
|
404
|
+
const g = byId.get(mid);
|
|
405
|
+
if (g) n += leafDescendantCount(g.memberIds);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return n;
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
while (pending.length > 0) {
|
|
412
|
+
let progress = false;
|
|
413
|
+
for (let i = pending.length - 1; i >= 0; i--) {
|
|
414
|
+
const g = pending[i]!;
|
|
415
|
+
const childIds: string[] = [];
|
|
416
|
+
let ready = true;
|
|
417
|
+
for (const mid of g.memberIds) {
|
|
418
|
+
if (leaves.has(mid)) {
|
|
419
|
+
childIds.push(mid);
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (skipped.has(mid)) {
|
|
423
|
+
// Promote a dropped group's members into this parent.
|
|
424
|
+
const dropped = byId.get(mid);
|
|
425
|
+
if (!dropped) {
|
|
426
|
+
ready = false;
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
for (const sm of dropped.memberIds) {
|
|
430
|
+
if (leaves.has(sm) || built.some((b) => b.id === sm)) childIds.push(sm);
|
|
431
|
+
else if (!skipped.has(sm)) {
|
|
432
|
+
ready = false;
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (!ready) break;
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (built.some((b) => b.id === mid)) {
|
|
440
|
+
childIds.push(mid);
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
if (byId.has(mid)) {
|
|
444
|
+
ready = false;
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
// Unknown id — ignored (external stubs etc. may be absent).
|
|
448
|
+
}
|
|
449
|
+
if (!ready) continue;
|
|
450
|
+
|
|
451
|
+
pending.splice(i, 1);
|
|
452
|
+
progress = true;
|
|
453
|
+
|
|
454
|
+
if (
|
|
455
|
+
childIds.length < 1 ||
|
|
456
|
+
(!keepSingletonGroups && leafDescendantCount(g.memberIds) < 2)
|
|
457
|
+
) {
|
|
458
|
+
skipped.add(g.id);
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
built.push({ id: g.id, childIds, minWidth: g.minWidth });
|
|
463
|
+
}
|
|
464
|
+
if (!progress) {
|
|
465
|
+
// Cycle or unresolved refs — leave the rest unbuilt.
|
|
466
|
+
for (const g of pending) skipped.add(g.id);
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return { built, skipped: [...skipped] };
|
|
472
|
+
}
|
|
473
|
+
|
|
344
474
|
/**
|
|
345
475
|
* Compute ELK layout for nodes and edges
|
|
346
476
|
*
|
|
@@ -354,7 +484,7 @@ export async function computeElkLayout(
|
|
|
354
484
|
edges: Edge[],
|
|
355
485
|
options: ElkLayoutOptions = {}
|
|
356
486
|
): Promise<ElkLayoutResult> {
|
|
357
|
-
const { preserveNodePositions = true } = options;
|
|
487
|
+
const { preserveNodePositions = true, keepSingletonGroups = false } = options;
|
|
358
488
|
const edgeLabels = options.edgeLabels;
|
|
359
489
|
const direction = options.direction ?? 'RIGHT';
|
|
360
490
|
|
|
@@ -516,89 +646,28 @@ export async function computeElkLayout(
|
|
|
516
646
|
'elk.spacing.nodeNode': '40',
|
|
517
647
|
};
|
|
518
648
|
|
|
519
|
-
|
|
520
|
-
const
|
|
521
|
-
let n = 0;
|
|
522
|
-
for (const mid of memberIds) {
|
|
523
|
-
if (elkById.has(mid)) n += 1;
|
|
524
|
-
else {
|
|
525
|
-
const g = groupById.get(mid);
|
|
526
|
-
if (g) n += leafDescendantCount(g.memberIds);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
return n;
|
|
530
|
-
};
|
|
531
|
-
|
|
649
|
+
const plan = planCompoundGroups(groupDefs, elkById.keys(), keepSingletonGroups);
|
|
650
|
+
const skippedGroups = new Set(plan.skipped);
|
|
532
651
|
const builtGroups = new Map<string, ElkNode>();
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
const childNodes: ElkNode[] = [];
|
|
542
|
-
let ready = true;
|
|
543
|
-
for (const mid of g.memberIds) {
|
|
544
|
-
if (elkById.has(mid)) {
|
|
545
|
-
childNodes.push(elkById.get(mid)!);
|
|
546
|
-
continue;
|
|
547
|
-
}
|
|
548
|
-
if (skippedGroups.has(mid)) {
|
|
549
|
-
// Promote skipped group's members into this parent.
|
|
550
|
-
const skipped = groupById.get(mid);
|
|
551
|
-
if (!skipped) {
|
|
552
|
-
ready = false;
|
|
553
|
-
break;
|
|
554
|
-
}
|
|
555
|
-
for (const sm of skipped.memberIds) {
|
|
556
|
-
if (elkById.has(sm)) childNodes.push(elkById.get(sm)!);
|
|
557
|
-
else if (builtGroups.has(sm)) childNodes.push(builtGroups.get(sm)!);
|
|
558
|
-
else if (!skippedGroups.has(sm)) {
|
|
559
|
-
ready = false;
|
|
560
|
-
break;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
if (!ready) break;
|
|
564
|
-
continue;
|
|
565
|
-
}
|
|
566
|
-
if (builtGroups.has(mid)) {
|
|
567
|
-
childNodes.push(builtGroups.get(mid)!);
|
|
568
|
-
continue;
|
|
569
|
-
}
|
|
570
|
-
if (groupById.has(mid)) {
|
|
571
|
-
ready = false;
|
|
572
|
-
break;
|
|
573
|
-
}
|
|
574
|
-
// Unknown id — ignore (external stubs etc. may be absent).
|
|
575
|
-
}
|
|
576
|
-
if (!ready) continue;
|
|
577
|
-
|
|
578
|
-
pending.splice(i, 1);
|
|
579
|
-
progress = true;
|
|
580
|
-
|
|
581
|
-
// Skip frames with fewer than 2 leaf descendants — promote children up.
|
|
582
|
-
if (leafDescendantCount(g.memberIds) < 2 || childNodes.length < 1) {
|
|
583
|
-
skippedGroups.add(g.id);
|
|
584
|
-
continue;
|
|
652
|
+
for (const g of plan.built) {
|
|
653
|
+
const children: ElkNode[] = [];
|
|
654
|
+
for (const cid of g.childIds) {
|
|
655
|
+
const leaf = elkById.get(cid);
|
|
656
|
+
if (leaf) children.push(leaf);
|
|
657
|
+
else {
|
|
658
|
+
const nested = builtGroups.get(cid);
|
|
659
|
+
if (nested) children.push(nested);
|
|
585
660
|
}
|
|
586
|
-
|
|
587
|
-
builtGroups.set(g.id, {
|
|
588
|
-
id: g.id,
|
|
589
|
-
children: childNodes,
|
|
590
|
-
layoutOptions: g.minWidth == null ? compoundLayoutOptions : {
|
|
591
|
-
...compoundLayoutOptions,
|
|
592
|
-
'elk.nodeSize.constraints': 'MINIMUM_SIZE',
|
|
593
|
-
'elk.nodeSize.minimum': `(${g.minWidth},0)`,
|
|
594
|
-
},
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
if (!progress) {
|
|
598
|
-
// Cycle or unresolved refs — leave remaining groups unbuilt.
|
|
599
|
-
for (const g of pending) skippedGroups.add(g.id);
|
|
600
|
-
break;
|
|
601
661
|
}
|
|
662
|
+
builtGroups.set(g.id, {
|
|
663
|
+
id: g.id,
|
|
664
|
+
children,
|
|
665
|
+
layoutOptions: g.minWidth == null ? compoundLayoutOptions : {
|
|
666
|
+
...compoundLayoutOptions,
|
|
667
|
+
'elk.nodeSize.constraints': 'MINIMUM_SIZE',
|
|
668
|
+
'elk.nodeSize.minimum': `(${g.minWidth},0)`,
|
|
669
|
+
},
|
|
670
|
+
});
|
|
602
671
|
}
|
|
603
672
|
|
|
604
673
|
const groupedLeafIds = new Set<string>();
|
|
File without changes
|
|
File without changes
|
|
File without changes
|