@rokkit/graph 1.7.0 → 1.8.1

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.
Files changed (120) hide show
  1. package/README.md +67 -1
  2. package/dist/Graph.svelte +234 -259
  3. package/dist/Graph.svelte.d.ts +1 -1
  4. package/dist/GraphLegend.svelte +81 -10
  5. package/dist/GraphLegend.svelte.d.ts +15 -0
  6. package/dist/GraphState.svelte.d.ts +168 -51
  7. package/dist/GraphState.svelte.js +406 -305
  8. package/dist/actions/canvas.d.ts +18 -0
  9. package/dist/actions/canvas.js +59 -0
  10. package/dist/actions/choices.d.ts +12 -0
  11. package/dist/actions/choices.js +23 -0
  12. package/dist/actions/interactions.d.ts +20 -0
  13. package/dist/actions/interactions.js +57 -0
  14. package/dist/actions/listen.d.ts +2 -0
  15. package/dist/actions/listen.js +10 -0
  16. package/dist/canvas/frame.d.ts +41 -0
  17. package/dist/canvas/frame.js +34 -0
  18. package/dist/controls/BundleControl.svelte +11 -10
  19. package/dist/controls/BundleControl.svelte.d.ts +0 -7
  20. package/dist/controls/DensityControl.svelte +17 -9
  21. package/dist/controls/DensityControl.svelte.d.ts +0 -8
  22. package/dist/controls/DepthControl.svelte +11 -4
  23. package/dist/controls/DepthControl.svelte.d.ts +0 -9
  24. package/dist/controls/DrillBar.svelte +63 -0
  25. package/dist/controls/DrillBar.svelte.d.ts +9 -0
  26. package/dist/controls/EdgeStyleControl.svelte +10 -10
  27. package/dist/controls/EdgeStyleControl.svelte.d.ts +0 -6
  28. package/dist/controls/HiddenControl.svelte +27 -0
  29. package/dist/controls/HiddenControl.svelte.d.ts +7 -0
  30. package/dist/controls/MeasureControl.svelte +39 -0
  31. package/dist/controls/MeasureControl.svelte.d.ts +12 -0
  32. package/dist/controls/ToggleControl.svelte +33 -0
  33. package/dist/controls/ToggleControl.svelte.d.ts +13 -0
  34. package/dist/controls/ViolationsControl.svelte +27 -0
  35. package/dist/controls/ViolationsControl.svelte.d.ts +7 -0
  36. package/dist/controls/ZoomControl.svelte +21 -18
  37. package/dist/controls/zoom.d.ts +3 -0
  38. package/dist/controls/zoom.js +6 -0
  39. package/dist/diagrams/ArcDiagram.svelte +116 -0
  40. package/dist/diagrams/ArcDiagram.svelte.d.ts +31 -0
  41. package/dist/diagrams/CallTree.svelte +17 -5
  42. package/dist/diagrams/CallTree.svelte.d.ts +5 -1
  43. package/dist/diagrams/DependencyDiagram.svelte +18 -6
  44. package/dist/diagrams/DependencyDiagram.svelte.d.ts +5 -1
  45. package/dist/diagrams/DependencyMatrix.svelte +156 -0
  46. package/dist/diagrams/DependencyMatrix.svelte.d.ts +34 -0
  47. package/dist/diagrams/ErDiagram.svelte +18 -6
  48. package/dist/diagrams/ErDiagram.svelte.d.ts +5 -1
  49. package/dist/diagrams/LayersDiagram.svelte +124 -0
  50. package/dist/diagrams/LayersDiagram.svelte.d.ts +30 -0
  51. package/dist/diagrams/Neighborhood.svelte +26 -6
  52. package/dist/diagrams/Neighborhood.svelte.d.ts +11 -1
  53. package/dist/diagrams/PolymetricTree.svelte +116 -0
  54. package/dist/diagrams/PolymetricTree.svelte.d.ts +28 -0
  55. package/dist/diagrams/StructureDiagram.svelte +38 -8
  56. package/dist/diagrams/StructureDiagram.svelte.d.ts +9 -1
  57. package/dist/diagrams/Sunburst.svelte +44 -8
  58. package/dist/diagrams/Sunburst.svelte.d.ts +12 -2
  59. package/dist/diagrams/Treemap.svelte +44 -8
  60. package/dist/diagrams/Treemap.svelte.d.ts +12 -2
  61. package/dist/index.d.ts +13 -3
  62. package/dist/index.js +11 -2
  63. package/dist/layout/arcs.d.ts +6 -0
  64. package/dist/layout/arcs.js +141 -0
  65. package/dist/layout/cards.js +9 -2
  66. package/dist/layout/edges.d.ts +13 -1
  67. package/dist/layout/edges.js +35 -0
  68. package/dist/layout/extent.d.ts +17 -0
  69. package/dist/layout/extent.js +27 -0
  70. package/dist/layout/flow.js +3 -1
  71. package/dist/layout/index.d.ts +4 -1
  72. package/dist/layout/index.js +17 -2
  73. package/dist/layout/layers.d.ts +6 -0
  74. package/dist/layout/layers.js +180 -0
  75. package/dist/layout/matrix.d.ts +50 -0
  76. package/dist/layout/matrix.js +110 -0
  77. package/dist/layout/neighborhood.js +16 -7
  78. package/dist/layout/options.d.ts +1 -1
  79. package/dist/layout/options.js +16 -3
  80. package/dist/layout/percentile.d.ts +8 -0
  81. package/dist/layout/percentile.js +13 -0
  82. package/dist/layout/polymetric.d.ts +3 -0
  83. package/dist/layout/polymetric.js +221 -0
  84. package/dist/layout/radial.js +3 -1
  85. package/dist/layout/structure.js +7 -1
  86. package/dist/layout/sunburst.js +11 -4
  87. package/dist/layout/types.d.ts +83 -0
  88. package/dist/layout/world.js +11 -4
  89. package/dist/messages.d.ts +14 -0
  90. package/dist/messages.js +17 -0
  91. package/dist/model/condense.d.ts +8 -0
  92. package/dist/model/condense.js +114 -0
  93. package/dist/model/entities.d.ts +12 -0
  94. package/dist/model/entities.js +12 -0
  95. package/dist/model/normalize.d.ts +2 -6
  96. package/dist/model/normalize.js +83 -5
  97. package/dist/model/relationships.d.ts +30 -0
  98. package/dist/model/relationships.js +51 -0
  99. package/dist/model/share.d.ts +9 -0
  100. package/dist/model/share.js +43 -0
  101. package/dist/model/tree.d.ts +7 -1
  102. package/dist/model/tree.js +11 -4
  103. package/dist/preset.d.ts +18 -0
  104. package/dist/preset.js +34 -3
  105. package/dist/schema/EntitiesView.svelte +8 -6
  106. package/dist/schema/EntityView.svelte +13 -10
  107. package/dist/state/GraphConfig.svelte.d.ts +124 -0
  108. package/dist/state/GraphConfig.svelte.js +253 -0
  109. package/dist/state/GraphDrill.svelte.d.ts +52 -0
  110. package/dist/state/GraphDrill.svelte.js +115 -0
  111. package/dist/state/GraphGroups.svelte.d.ts +35 -0
  112. package/dist/state/GraphGroups.svelte.js +73 -0
  113. package/dist/state/GraphSelection.svelte.d.ts +53 -0
  114. package/dist/state/GraphSelection.svelte.js +94 -0
  115. package/dist/state/intents.d.ts +33 -0
  116. package/dist/state/intents.js +30 -0
  117. package/dist/state/text.d.ts +28 -0
  118. package/dist/state/text.js +56 -0
  119. package/dist/types.d.ts +92 -1
  120. package/package.json +5 -4
@@ -0,0 +1,53 @@
1
+ import { SvelteSet } from 'svelte/reactivity';
2
+ import type { Relationship } from '../model/relationships.js';
3
+ import type { RoutedEdge } from '../layout/types.js';
4
+ import type { GraphModel, GraphNode } from '../types.js';
5
+ type Sources = {
6
+ /** The canonical model. */
7
+ model: () => GraphModel;
8
+ /** The structural edges the active layout routed — geometry for relationships. */
9
+ routedEdges: () => RoutedEdge[];
10
+ /** The consumer's selection callback, read at call time (it is config, and can change). */
11
+ onselect: () => ((id: string | null) => void) | undefined;
12
+ };
13
+ /**
14
+ * What the reader has picked and opened: the selected node, what it touches, and which cards
15
+ * are expanded — plus the transitions that change them.
16
+ */
17
+ export declare class GraphSelection {
18
+ #private;
19
+ constructor(sources: Sources);
20
+ get value(): string | null;
21
+ get related(): SvelteSet<string>;
22
+ get entity(): GraphNode | null;
23
+ get relationships(): Relationship[];
24
+ /** Take a value the CALLER supplied — no callback, it already knows. */
25
+ adopt(id: string | null): void;
26
+ select(id: string): void;
27
+ /**
28
+ * Drop the selection, and SAY SO.
29
+ *
30
+ * Found by dbd consuming the package: it owns `selected` in its route and branches on it
31
+ * to show the entity panel, so a silent clear left that panel open over nothing. A
32
+ * controlled consumer cannot observe an internal value — the callback is the only
33
+ * channel, which is why it carries `null` rather than being skipped.
34
+ *
35
+ * Guarded on an actual change, or a background click on an already-empty canvas
36
+ * round-trips through the consumer's setter on every stray click.
37
+ */
38
+ clear(): void;
39
+ /**
40
+ * Show one node's rows in full, whatever the density says.
41
+ *
42
+ * Per NODE rather than a global density change: a reader who clicks "+3 more" on one
43
+ * card is asking about that card, and expanding all of them answers a question they did
44
+ * not ask — on a large diagram it also relayouts everything under them.
45
+ */
46
+ toggleExpanded(id: string): void;
47
+ isExpanded(id: string): boolean;
48
+ /** The set the layouts read to size expanded cards. */
49
+ get expanded(): SvelteSet<string>;
50
+ nodeState(id: string): 'selected' | 'related' | 'dim' | null;
51
+ edgeState(edge: RoutedEdge): 'highlight' | 'dim' | null;
52
+ }
53
+ export {};
@@ -0,0 +1,94 @@
1
+ import { SvelteSet } from 'svelte/reactivity';
2
+ import { relationshipsOf } from '../model/relationships.js';
3
+ /**
4
+ * What the reader has picked and opened: the selected node, what it touches, and which cards
5
+ * are expanded — plus the transitions that change them.
6
+ */
7
+ export class GraphSelection {
8
+ #sources;
9
+ #value = $state(null);
10
+ #expanded = new SvelteSet();
11
+ #related;
12
+ #entity;
13
+ /** From the canonical model, not the layout's filtered edges — see `relationshipsOf`. */
14
+ #relationships;
15
+ constructor(sources) {
16
+ this.#sources = sources;
17
+ // Derived in the constructor, after `#sources` exists — a field initializer would read
18
+ // it before it is assigned.
19
+ this.#related = $derived(this.#value
20
+ ? new SvelteSet(sources.model().neighbors.get(this.#value) ?? [])
21
+ : new SvelteSet());
22
+ this.#entity = $derived(this.#value ? (sources.model().byId.get(this.#value) ?? null) : null);
23
+ this.#relationships = $derived(relationshipsOf(sources.model(), this.#value, sources.routedEdges()));
24
+ }
25
+ get value() {
26
+ return this.#value;
27
+ }
28
+ get related() {
29
+ return this.#related;
30
+ }
31
+ get entity() {
32
+ return this.#entity;
33
+ }
34
+ get relationships() {
35
+ return this.#relationships;
36
+ }
37
+ /** Take a value the CALLER supplied — no callback, it already knows. */
38
+ adopt(id) {
39
+ this.#value = id;
40
+ }
41
+ select(id) {
42
+ this.#value = id;
43
+ this.#sources.onselect()?.(id);
44
+ }
45
+ /**
46
+ * Drop the selection, and SAY SO.
47
+ *
48
+ * Found by dbd consuming the package: it owns `selected` in its route and branches on it
49
+ * to show the entity panel, so a silent clear left that panel open over nothing. A
50
+ * controlled consumer cannot observe an internal value — the callback is the only
51
+ * channel, which is why it carries `null` rather than being skipped.
52
+ *
53
+ * Guarded on an actual change, or a background click on an already-empty canvas
54
+ * round-trips through the consumer's setter on every stray click.
55
+ */
56
+ clear() {
57
+ if (this.#value === null)
58
+ return;
59
+ this.#value = null;
60
+ this.#sources.onselect()?.(null);
61
+ }
62
+ /**
63
+ * Show one node's rows in full, whatever the density says.
64
+ *
65
+ * Per NODE rather than a global density change: a reader who clicks "+3 more" on one
66
+ * card is asking about that card, and expanding all of them answers a question they did
67
+ * not ask — on a large diagram it also relayouts everything under them.
68
+ */
69
+ toggleExpanded(id) {
70
+ if (this.#expanded.has(id))
71
+ this.#expanded.delete(id);
72
+ else
73
+ this.#expanded.add(id);
74
+ }
75
+ isExpanded(id) {
76
+ return this.#expanded.has(id);
77
+ }
78
+ /** The set the layouts read to size expanded cards. */
79
+ get expanded() {
80
+ return this.#expanded;
81
+ }
82
+ nodeState(id) {
83
+ if (!this.#value)
84
+ return null;
85
+ if (id === this.#value)
86
+ return 'selected';
87
+ return this.#related.has(id) ? 'related' : 'dim';
88
+ }
89
+ edgeState(edge) {
90
+ if (!this.#value)
91
+ return null;
92
+ return edge.fromKey === this.#value || edge.toKey === this.#value ? 'highlight' : 'dim';
93
+ }
94
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * What a press or a double-click on the graph does, as data.
3
+ *
4
+ * The state writes an intent onto each element (`data-graph-press`, `data-graph-open`, with
5
+ * `data-graph-key`); the `interactions` action reads it back and calls `GraphState.act`, which
6
+ * looks it up here. So the meaning of a click lives in one table — not in a handler on every
7
+ * element — and each entry is testable on a bare state.
8
+ */
9
+ import type { Breadcrumb } from './GraphDrill.svelte.js';
10
+ import type { GroupAction } from './GraphGroups.svelte.js';
11
+ import type { Cluster } from '../layout/types.js';
12
+ export type Intent = 'select' | 'toggle' | 'drill' | 'group' | 'expand' | 'crumb' | 'open-selected' | 'group-selected' | 'clear';
13
+ /** What an intent may call — the slice of `GraphState` the table needs. */
14
+ export type Actor = {
15
+ select(id: string): void;
16
+ clear(): void;
17
+ readonly value: string | null;
18
+ drillInto(cluster: Cluster): unknown;
19
+ drillTo(path: string[]): unknown;
20
+ toggleGroup(id: string): void;
21
+ toggleExpanded(id: string): void;
22
+ isGroup(id: string): boolean;
23
+ groupOf(id: string): string | null;
24
+ boxFor(key: string): Cluster | undefined;
25
+ readonly breadcrumbs: Breadcrumb[];
26
+ readonly drillTarget: Cluster | null;
27
+ readonly groupAction: GroupAction | null;
28
+ };
29
+ type Perform = (graph: Actor, key: string) => void;
30
+ export declare const INTENTS: Record<Intent, Perform>;
31
+ /** Whether `value` names an intent — the action reads it from the DOM, where anything goes. */
32
+ export declare const isIntent: (value: string | null | undefined) => value is Intent;
33
+ export {};
@@ -0,0 +1,30 @@
1
+ /** A group toggles itself; a member of an open group folds that group back. */
2
+ const groupFor = (graph, key) => (graph.isGroup(key) ? key : (graph.groupOf(key) ?? key));
3
+ export const INTENTS = {
4
+ select: (graph, key) => graph.select(key),
5
+ /** Select, or clear when it is already the selection — a matrix row's press. */
6
+ toggle: (graph, key) => (graph.value === key ? graph.clear() : graph.select(key)),
7
+ drill: (graph, key) => {
8
+ const cluster = graph.boxFor(key);
9
+ if (cluster)
10
+ graph.drillInto(cluster);
11
+ },
12
+ group: (graph, key) => graph.toggleGroup(groupFor(graph, key)),
13
+ expand: (graph, key) => graph.toggleExpanded(key),
14
+ crumb: (graph, key) => {
15
+ const crumb = graph.breadcrumbs[Number(key)];
16
+ if (crumb)
17
+ graph.drillTo(crumb.path);
18
+ },
19
+ 'open-selected': (graph) => {
20
+ if (graph.drillTarget)
21
+ graph.drillInto(graph.drillTarget);
22
+ },
23
+ 'group-selected': (graph) => {
24
+ if (graph.groupAction)
25
+ graph.toggleGroup(graph.groupAction.group);
26
+ },
27
+ clear: (graph) => graph.clear()
28
+ };
29
+ /** Whether `value` names an intent — the action reads it from the DOM, where anything goes. */
30
+ export const isIntent = (value) => value !== null && value !== undefined && Object.hasOwn(INTENTS, value);
@@ -0,0 +1,28 @@
1
+ import type { Card, ChannelScale, Density, LayoutResult, PolymetricChannel } from '../layout/types.js';
2
+ import type { GraphEdge } from '../types.js';
3
+ /** The default accessible name: what the diagram holds. */
4
+ export declare function diagramLabel(nodes: number, edges: number): string;
5
+ /**
6
+ * The "more rows" control on a card: `show less` when expanded, `+ N more` when rows are
7
+ * hidden, and `no keys · N rows` when a key filter matched nothing (see `GraphState.moreLabel`).
8
+ */
9
+ export declare function moreRowsLabel(expanded: boolean, card: Pick<Card, 'more' | 'vis'> | undefined, density: Density): string | null;
10
+ export type ChannelRow = {
11
+ key: PolymetricChannel;
12
+ label: string;
13
+ scale: ChannelScale;
14
+ };
15
+ /** One legend row per polymetric channel present, labelled from the locale (#168). */
16
+ export declare function channelRows(channels: LayoutResult['channels']): ChannelRow[];
17
+ /**
18
+ * What each side of an arc diagram shows (#169): the relations drawn there, from the data, or
19
+ * Declared / Observed from the locale when a side's edges name none.
20
+ */
21
+ export declare function sideNames(edges: GraphEdge[], above: string | undefined): {
22
+ below: string;
23
+ above: string;
24
+ };
25
+ /** What a failed drill says: the error's own message, in the locale's sentence. */
26
+ export declare function drillErrorText(error: unknown): string | null;
27
+ /** Underscores are the WIRE format (dbd sends `materialized_view`), not something to read. */
28
+ export declare const readable: (value: string) => string;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The words the state produces, from `messages.graph` (see `messages.ts`). Pure functions of
3
+ * their inputs and the active locale, so the canvas has no English of its own and each is
4
+ * testable without a renderer.
5
+ */
6
+ import { counted, say } from '../messages.js';
7
+ import { sideOf } from '../layout/arcs.js';
8
+ /** The default accessible name: what the diagram holds. */
9
+ export function diagramLabel(nodes, edges) {
10
+ return say('diagram', {
11
+ nodes: counted(nodes, 'nodeOne', 'nodeMany'),
12
+ edges: counted(edges, 'relationshipOne', 'relationshipMany')
13
+ });
14
+ }
15
+ /**
16
+ * The "more rows" control on a card: `show less` when expanded, `+ N more` when rows are
17
+ * hidden, and `no keys · N rows` when a key filter matched nothing (see `GraphState.moreLabel`).
18
+ */
19
+ export function moreRowsLabel(expanded, card, density) {
20
+ if (expanded)
21
+ return say('less');
22
+ if (!card || card.more <= 0)
23
+ return null;
24
+ // Only `keys` filters rows by a property — at `names` an empty card is what was asked for.
25
+ if (density !== 'keys' || card.vis.length > 0)
26
+ return say('more', { n: card.more });
27
+ return say('noKeys', { rows: counted(card.more, 'rowOne', 'rowMany') });
28
+ }
29
+ const CHANNEL_LABEL = { width: 'width', height: 'height', color: 'shade' };
30
+ /** One legend row per polymetric channel present, labelled from the locale (#168). */
31
+ export function channelRows(channels) {
32
+ if (!channels)
33
+ return [];
34
+ const keys = channels.color ? ['width', 'height', 'color'] : ['width', 'height'];
35
+ return keys.map((key) => ({ key, label: say(CHANNEL_LABEL[key]), scale: channels[key] }));
36
+ }
37
+ /**
38
+ * What each side of an arc diagram shows (#169): the relations drawn there, from the data, or
39
+ * Declared / Observed from the locale when a side's edges name none.
40
+ */
41
+ export function sideNames(edges, above) {
42
+ const named = { below: new Set(), above: new Set() };
43
+ for (const edge of edges)
44
+ if (edge.relation)
45
+ named[sideOf(edge, above)].add(edge.relation);
46
+ const name = (side, fallback) => named[side].size > 0 ? [...named[side]].join(' · ') : say(fallback);
47
+ return { below: name('below', 'sideBelow'), above: name('above', 'sideAbove') };
48
+ }
49
+ /** What a failed drill says: the error's own message, in the locale's sentence. */
50
+ export function drillErrorText(error) {
51
+ if (error === null || error === undefined)
52
+ return null;
53
+ return say('drillError', { message: error instanceof Error ? error.message : String(error) });
54
+ }
55
+ /** Underscores are the WIRE format (dbd sends `materialized_view`), not something to read. */
56
+ export const readable = (value) => value.replace(/_/g, ' ');
package/dist/types.d.ts CHANGED
@@ -12,7 +12,11 @@ export type GraphRow = {
12
12
  * (a view reading a table, a procedure calling a function). Layouts branch on
13
13
  * this rather than on any schema-specific notion.
14
14
  */
15
- export type EdgeKind = 'reference' | 'dependency';
15
+ /**
16
+ * `reference` (a foreign key) and `dependency` come from the data. `containment` is drawn by a
17
+ * tree layout (`polymetric`) between a container and what it holds — the tree's own links.
18
+ */
19
+ export type EdgeKind = 'reference' | 'dependency' | 'containment';
16
20
  export type GraphNode = {
17
21
  /** Stable key. `${group}.${label}` when a group is present, else `label`. */
18
22
  id: string;
@@ -54,8 +58,25 @@ export type GraphNode = {
54
58
  *
55
59
  * Accepts a delimited STRING too (`'dbd/core/lexer/parse'`), since that is how a file path
56
60
  * arrives. See `GraphFields.pathDelimiter`.
61
+ *
62
+ * A host that gives containment as `parent` ids instead (#168) need not convert: a node with
63
+ * no path but a parent chain gets one, as ids (`GraphFields.parent`). The path is still what
64
+ * the model holds — the chain is read once, at normalisation.
57
65
  */
58
66
  path?: string[];
67
+ /**
68
+ * The node ids this node STANDS FOR — a group, typically one strongly-connected component
69
+ * the host condensed (#166). rokkit computes no SCCs: it draws what the host grouped. While
70
+ * the group is collapsed its members are hidden and their edges reroute to it.
71
+ */
72
+ members?: string[];
73
+ /** Whether a group starts collapsed. A node with `members` and no flag is collapsed. */
74
+ collapsed?: boolean;
75
+ /**
76
+ * The layer the HOST assigned (#167) — 0 at the top, foundations at the bottom. rokkit
77
+ * computes no depth; the `layers` layout ranks by this.
78
+ */
79
+ layer?: number;
59
80
  rows: GraphRow[];
60
81
  note?: string;
61
82
  /** Source fields the map did not claim, passed through untouched. */
@@ -94,10 +115,46 @@ export type GraphEdge = {
94
115
  * edge is kept and marked, and it is the renderer's job to dim it.
95
116
  */
96
117
  unplaced?: 'source' | 'target' | 'both';
118
+ /**
119
+ * Drawn over the picture but never allowed to shape it — a co-change pair, a layering
120
+ * rule broken, a suggested dependency.
121
+ *
122
+ * Such an edge is exactly what the reader wants to see and exactly what must not re-rank a
123
+ * layered layout or pull a cluster ordering: files that change together with no import
124
+ * between them would otherwise be drawn next to each other, hiding the very coupling the
125
+ * overlay exists to expose. So they never enter `GraphModel.edges`; they live in
126
+ * `GraphModel.overlays` and are routed after the layout has run.
127
+ */
128
+ overlay?: boolean;
129
+ /** A strength this edge carries — co-change count, call frequency. Drives stroke width. */
130
+ weight?: number;
131
+ /**
132
+ * The cheapest link to cut to break a cycle — the edge with the fewest occurrences (#166).
133
+ * Naming it is the actionable output of a cycles view, so it is styleable on its own
134
+ * (`data-edge-weakest`).
135
+ */
136
+ weakest?: boolean;
137
+ /**
138
+ * Coupling nothing in the code explains — a pair that changes together with no import
139
+ * between them (#169). The host computes it; the graph only styles it (`data-edge-hidden`).
140
+ */
141
+ hidden?: boolean;
142
+ /** How many model edges this one stands for, when it aggregates a collapsed group's edges. */
143
+ count?: number;
144
+ /**
145
+ * How the edge sits against the layering (#167): `down` one layer (conformant), `skip` more
146
+ * than one down (legal, worth seeing), `up` climbing (the violation), `level` within a layer.
147
+ * The host says; when it does not, the `layers` layout derives it from the layers.
148
+ */
149
+ conformance?: Conformance;
97
150
  };
151
+ export type Conformance = 'down' | 'skip' | 'up' | 'level';
98
152
  export type GraphModel = {
99
153
  nodes: GraphNode[];
154
+ /** Structural edges — the ones layouts, neighbours and relationships see. */
100
155
  edges: GraphEdge[];
156
+ /** Overlay edges (`GraphEdge.overlay`) — routed after layout, never shaping it. */
157
+ overlays: GraphEdge[];
101
158
  byId: Map<string, GraphNode>;
102
159
  /** Undirected adjacency, self-edges excluded. */
103
160
  neighbors: Map<string, Set<string>>;
@@ -153,6 +210,19 @@ export type GraphProps = {
153
210
  label?: string;
154
211
  /** Fires on selection AND on clear, where it receives `null`. */
155
212
  onselect?: (id: string | null) => void;
213
+ /**
214
+ * The containment subtree drawn as the whole canvas (`world`, `sunburst`, `structure`).
215
+ * Bindable: drilling moves it. `[]` is the root.
216
+ */
217
+ focusPath?: string[];
218
+ /** The reader drilled into a box — see `GraphStateConfig.ondrill`. */
219
+ ondrill?: (path: string[], node: GraphNode | null) => void | Promise<void>;
220
+ /** The reader drilled out — see `GraphStateConfig.ondrillup`. */
221
+ ondrillup?: (path: string[]) => void | Promise<void>;
222
+ /** The reader expanded a group — see `GraphStateConfig.onexpand`. */
223
+ onexpand?: (id: string, node: GraphNode) => void;
224
+ /** The reader collapsed a group — see `GraphStateConfig.oncollapse`. */
225
+ oncollapse?: (id: string, node: GraphNode) => void;
156
226
  /** Icon class per node kind and row badge, merged over the built-in map. */
157
227
  icons?: Record<string, string>;
158
228
  class?: string;
@@ -207,6 +277,27 @@ export type GraphFields = {
207
277
  defaultEdgeKind?: EdgeKind;
208
278
  /** Path to the consumer's own relationship word — see `GraphEdge.relation`. */
209
279
  relation?: string;
280
+ /** Path to a truthy flag marking an overlay edge — see `GraphEdge.overlay`. */
281
+ overlay?: string;
282
+ /** Path to a group's member ids — see `GraphNode.members`. */
283
+ members?: string;
284
+ /** Path to a group's starting state — see `GraphNode.collapsed`. */
285
+ collapsed?: string;
286
+ /** Path to a truthy flag marking the weakest edge of a cycle — see `GraphEdge.weakest`. */
287
+ weakest?: string;
288
+ /** Path to a truthy flag marking hidden coupling — see `GraphEdge.hidden`. */
289
+ hidden?: string;
290
+ /** Path to a node's host-assigned layer — see `GraphNode.layer`. */
291
+ layer?: string;
292
+ /**
293
+ * Path to a node's PARENT id — containment as a parent chain rather than a `path` (#168).
294
+ * Used only for a node with no `path`; it becomes one, as ids.
295
+ */
296
+ parent?: string;
297
+ /** Path to an edge's conformance to the layering — see `GraphEdge.conformance`. */
298
+ conformance?: string;
299
+ /** Path to an edge's strength — see `GraphEdge.weight`. */
300
+ edgeWeight?: string;
210
301
  cardinality?: string;
211
302
  action?: string;
212
303
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rokkit/graph",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
4
4
  "description": "Node-link diagram components — pluggable layouts, ER/schema views",
5
5
  "repository": {
6
6
  "type": "git",
@@ -46,10 +46,11 @@
46
46
  "check:types": "tsc --noEmit"
47
47
  },
48
48
  "dependencies": {
49
- "@rokkit/core": "1.7.0"
49
+ "@rokkit/core": "1.8.0",
50
+ "@rokkit/states": "1.8.0"
50
51
  },
51
52
  "peerDependencies": {
52
- "@rokkit/ui": "1.7.0",
53
+ "@rokkit/ui": "1.8.0",
53
54
  "svelte": "^5.0.0"
54
55
  },
55
56
  "peerDependenciesMeta": {
@@ -58,7 +59,7 @@
58
59
  }
59
60
  },
60
61
  "devDependencies": {
61
- "@rokkit/ui": "1.7.0",
62
+ "@rokkit/ui": "1.8.0",
62
63
  "@sveltejs/package": "^2.5.8",
63
64
  "@sveltejs/vite-plugin-svelte": "^6.2.4",
64
65
  "svelte": "^5.55.7",