@principal-ai/subsystems-react 0.53.1 → 0.53.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.
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/subsystem/C4Graph.d.ts +83 -0
- package/dist/subsystem/C4Graph.d.ts.map +1 -1
- package/dist/subsystem/C4Graph.js +314 -105
- package/dist/subsystem/C4Graph.js.map +1 -1
- package/dist/subsystem/C4NodeCard.d.ts +45 -10
- package/dist/subsystem/C4NodeCard.d.ts.map +1 -1
- package/dist/subsystem/C4NodeCard.js +75 -21
- package/dist/subsystem/C4NodeCard.js.map +1 -1
- package/dist/subsystem/IssueList.js +2 -2
- package/dist/subsystem/IssueList.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +24 -7
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/model.d.ts +13 -2
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/nodes.d.ts.map +1 -1
- package/dist/subsystem/nodes.js +14 -6
- package/dist/subsystem/nodes.js.map +1 -1
- package/dist/utils/elkLayout.d.ts +49 -13
- package/dist/utils/elkLayout.d.ts.map +1 -1
- package/dist/utils/elkLayout.js +57 -8
- package/dist/utils/elkLayout.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/stories/Subsystem/C4Graph/C4NodeGallery.stories.tsx +1 -1
- package/src/stories/Subsystem/C4Graph/C4Transition.stories.tsx +2 -1
- package/src/subsystem/C4Graph.groups.test.ts +217 -0
- package/src/subsystem/C4Graph.morph.test.tsx +208 -0
- package/src/subsystem/C4Graph.tsx +344 -111
- package/src/subsystem/C4NodeCard.test.ts +47 -8
- package/src/subsystem/C4NodeCard.tsx +84 -20
- package/src/subsystem/IssueList.tsx +2 -2
- package/src/subsystem/SubsystemComponentGraph.tsx +25 -7
- package/src/subsystem/model.ts +13 -2
- package/src/subsystem/nodes.tsx +14 -6
- package/src/utils/elkLayout.test.ts +131 -0
- package/src/utils/elkLayout.ts +81 -14
package/src/utils/elkLayout.ts
CHANGED
|
@@ -126,7 +126,7 @@ export interface ElkLayoutOptions {
|
|
|
126
126
|
* nests this group under another group; omit for a root-level frame.
|
|
127
127
|
* Members reference their immediate parent via React Flow `parentId`.
|
|
128
128
|
*/
|
|
129
|
-
groups?:
|
|
129
|
+
groups?: CompoundGroupDef[];
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
132
|
* Keep groups that hold a single leaf instead of dropping them and
|
|
@@ -195,6 +195,46 @@ export interface ElkLayoutResult {
|
|
|
195
195
|
absoluteRects: Map<string, { x: number; y: number; width: number; height: number }>;
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
/**
|
|
199
|
+
* A node's rect in absolute (root) coordinates — the space `edgePathPoints` and
|
|
200
|
+
* the synthesized frame bounds are in.
|
|
201
|
+
*
|
|
202
|
+
* `absoluteRects` is the only map that is absolute for *every* node ELK placed.
|
|
203
|
+
* The alternatives each have a hole: `result.nodes` is parent-relative for a real
|
|
204
|
+
* group's children and has **no entry at all** for the group itself, and
|
|
205
|
+
* `groupBounds` is parent-relative for a nested group (and is the only source for
|
|
206
|
+
* a flattened group, which never enters the ELK tree).
|
|
207
|
+
*/
|
|
208
|
+
export function absoluteBoundsOf(
|
|
209
|
+
result: Pick<ElkLayoutResult, 'absoluteRects' | 'groupBounds'>,
|
|
210
|
+
id: string,
|
|
211
|
+
): { x: number; y: number; width: number; height: number } | undefined {
|
|
212
|
+
return result.absoluteRects.get(id) ?? result.groupBounds.get(id);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Where a node belongs in React Flow coordinates: a root node keeps its absolute
|
|
217
|
+
* position, a child is expressed relative to its parent.
|
|
218
|
+
*
|
|
219
|
+
* Convert in exactly one place, from the absolute space, subtracting the parent's
|
|
220
|
+
* absolute origin **once**. The two failure modes here are silent — a child
|
|
221
|
+
* double-offset by its parent's origin lands outside its own card, and a compound
|
|
222
|
+
* parent with no `result.nodes` entry silently keeps the authored position it was
|
|
223
|
+
* built with, so the container drifts away from its siblings, its frame and its
|
|
224
|
+
* edges while everything around it uses ELK's answer.
|
|
225
|
+
*/
|
|
226
|
+
export function flowPositionOf(
|
|
227
|
+
result: Pick<ElkLayoutResult, 'absoluteRects' | 'groupBounds'>,
|
|
228
|
+
id: string,
|
|
229
|
+
parentId?: string,
|
|
230
|
+
): { x: number; y: number } | undefined {
|
|
231
|
+
const abs = result.absoluteRects.get(id);
|
|
232
|
+
if (!abs) return undefined;
|
|
233
|
+
if (!parentId) return { x: abs.x, y: abs.y };
|
|
234
|
+
const origin = absoluteBoundsOf(result, parentId);
|
|
235
|
+
return origin ? { x: abs.x - origin.x, y: abs.y - origin.y } : { x: abs.x, y: abs.y };
|
|
236
|
+
}
|
|
237
|
+
|
|
198
238
|
/** Point in 2D space */
|
|
199
239
|
export interface Point {
|
|
200
240
|
x: number;
|
|
@@ -526,20 +566,31 @@ export function getElkOptions(options: ElkLayoutOptions): LayoutOptions {
|
|
|
526
566
|
export interface CompoundGroupDef {
|
|
527
567
|
id: string;
|
|
528
568
|
memberIds: string[];
|
|
569
|
+
/** Nested inside another group. Omitted for a root-level group. */
|
|
570
|
+
parentId?: string;
|
|
529
571
|
minWidth?: number;
|
|
530
572
|
/**
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
*
|
|
535
|
-
* the
|
|
573
|
+
* This id is *also* a drawn node — a card that hosts a group. Set it when the
|
|
574
|
+
* element is drawn as a card but must be laid out as a parent: the drill-down's
|
|
575
|
+
* opened container.
|
|
576
|
+
*
|
|
577
|
+
* The node and the group are not alternatives. The node is how the group is
|
|
578
|
+
* *drawn*, and the group is how it is *laid out* — so this says which one wins
|
|
579
|
+
* when a member id could be either. Without it the frame takes the bare leaf box
|
|
580
|
+
* and the group is built but never referenced, leaving its components laid out
|
|
581
|
+
* nowhere: the container reads as closed, at its 250x150 card size, whatever
|
|
582
|
+
* ELK was told about its children.
|
|
536
583
|
*/
|
|
537
|
-
|
|
584
|
+
drawnAsCard?: boolean;
|
|
538
585
|
/**
|
|
539
586
|
* Space reserved above the first child, in px. Set it when the parent draws
|
|
540
587
|
* its own chrome at the top (C4's grown container draws a label slot and a
|
|
541
588
|
* technology row) so ELK still owns the fit — it pads the parent by this and
|
|
542
589
|
* the children land below the chrome rather than under it.
|
|
590
|
+
*
|
|
591
|
+
* Note: there is deliberately no per-group partition. `partitionByNode` bands
|
|
592
|
+
* leaves, and ELK derives a compound node's band from its children, so a
|
|
593
|
+
* partition set on the parent itself is ignored.
|
|
543
594
|
*/
|
|
544
595
|
padTop?: number;
|
|
545
596
|
}
|
|
@@ -550,7 +601,7 @@ export interface CompoundGroupPlan {
|
|
|
550
601
|
* Built groups in build order — a group's children always appear before it,
|
|
551
602
|
* so callers can map ids to shells in one forward pass.
|
|
552
603
|
*/
|
|
553
|
-
built: Array<{ id: string; childIds: string[]; minWidth?: number; padTop?: number
|
|
604
|
+
built: Array<{ id: string; childIds: string[]; minWidth?: number; padTop?: number }>;
|
|
554
605
|
/**
|
|
555
606
|
* Dropped groups. Their members are promoted into the nearest built
|
|
556
607
|
* ancestor, so callers must clear those members' `parentId`.
|
|
@@ -645,7 +696,14 @@ export function planCompoundGroups(
|
|
|
645
696
|
continue;
|
|
646
697
|
}
|
|
647
698
|
|
|
648
|
-
built.push({
|
|
699
|
+
built.push({
|
|
700
|
+
id: g.id,
|
|
701
|
+
childIds,
|
|
702
|
+
minWidth: g.minWidth,
|
|
703
|
+
// Carried through, not just validated here: the caller turns each of
|
|
704
|
+
// these into ELK layout options on the parent node.
|
|
705
|
+
padTop: g.padTop,
|
|
706
|
+
});
|
|
649
707
|
}
|
|
650
708
|
if (!progress) {
|
|
651
709
|
// Cycle or unresolved refs — leave the rest unbuilt.
|
|
@@ -882,6 +940,9 @@ export async function computeElkLayout(
|
|
|
882
940
|
const plan = planCompoundGroups(groupDefs, elkById.keys(), keepSingletonGroups);
|
|
883
941
|
const skippedGroups = new Set(plan.skipped);
|
|
884
942
|
const builtGroups = new Map<string, ElkNode>();
|
|
943
|
+
// Ids that are drawn as a card *and* laid out as a group. Such an id is in
|
|
944
|
+
// `elkById` too, so this is what says which of the two an enclosing frame takes.
|
|
945
|
+
const drawnAsCardIds = new Set(groupDefs.filter((g) => g.drawnAsCard).map((g) => g.id));
|
|
885
946
|
// Flat groups are not ELK parents at all: their members lay out at the root,
|
|
886
947
|
// and the frame is synthesized from the members' bounds after layout. This
|
|
887
948
|
// keeps every edge on one spacing instead of inheriting a nested frame's.
|
|
@@ -909,11 +970,18 @@ export async function computeElkLayout(
|
|
|
909
970
|
if (flatGroupIds.has(g.id)) continue;
|
|
910
971
|
const children: ElkNode[] = [];
|
|
911
972
|
for (const cid of g.childIds) {
|
|
912
|
-
|
|
913
|
-
|
|
973
|
+
// A member id can be a drawn node *and* a built group. Preferring the leaf —
|
|
974
|
+
// which is what this did unconditionally — silently drops the group: the
|
|
975
|
+
// frame ends up holding a bare 250x150 box, nothing references the group, and
|
|
976
|
+
// its components are laid out nowhere. So the card ids take the group, and
|
|
977
|
+
// everything else keeps the leaf. (For those, `builtGroups` has no entry
|
|
978
|
+
// anyway: flat groups are skipped above, and a nested group has no node.)
|
|
979
|
+
const nested = builtGroups.get(cid);
|
|
980
|
+
if (nested && drawnAsCardIds.has(cid)) children.push(nested);
|
|
914
981
|
else {
|
|
915
|
-
const
|
|
916
|
-
if (
|
|
982
|
+
const leaf = elkById.get(cid);
|
|
983
|
+
if (leaf) children.push(leaf);
|
|
984
|
+
else if (nested) children.push(nested);
|
|
917
985
|
}
|
|
918
986
|
}
|
|
919
987
|
const sizeOptions: Record<string, string> = {};
|
|
@@ -928,7 +996,6 @@ export async function computeElkLayout(
|
|
|
928
996
|
// the parent's height still comes from ELK's fit.
|
|
929
997
|
sizeOptions['elk.padding'] = `[top=${g.padTop},left=12,bottom=12,right=12]`;
|
|
930
998
|
}
|
|
931
|
-
if (g.partition != null) sizeOptions['partition'] = String(g.partition);
|
|
932
999
|
builtGroups.set(g.id, {
|
|
933
1000
|
id: g.id,
|
|
934
1001
|
children,
|