@transitrix/diagrams 1.4.0 → 1.6.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/activities/__tests__/gantt.test.js +39 -0
- package/dist/capability-map/CapabilityCardNode.d.ts +19 -0
- package/dist/capability-map/CapabilityCardNode.js +66 -0
- package/dist/capability-map/CapabilityMapView.d.ts +59 -0
- package/dist/capability-map/CapabilityMapView.js +204 -0
- package/dist/capability-map/CapabilityRootNode.d.ts +12 -0
- package/dist/capability-map/CapabilityRootNode.js +23 -0
- package/dist/capability-map/__tests__/CapabilityMapView.test.d.ts +1 -0
- package/dist/capability-map/__tests__/CapabilityMapView.test.js +217 -0
- package/dist/capability-map/__tests__/address.test.d.ts +1 -0
- package/dist/capability-map/__tests__/address.test.js +49 -0
- package/dist/capability-map/__tests__/dsm-layout.test.d.ts +1 -0
- package/dist/capability-map/__tests__/dsm-layout.test.js +46 -0
- package/dist/capability-map/__tests__/dsm-mutations.test.d.ts +1 -0
- package/dist/capability-map/__tests__/dsm-mutations.test.js +125 -0
- package/dist/capability-map/__tests__/dsm-validate.test.d.ts +1 -0
- package/dist/capability-map/__tests__/dsm-validate.test.js +56 -0
- package/dist/capability-map/address.d.ts +12 -0
- package/dist/capability-map/address.js +48 -0
- package/dist/capability-map/dsm-layout.d.ts +9 -0
- package/dist/capability-map/dsm-layout.js +125 -0
- package/dist/capability-map/dsm-mutations.d.ts +28 -0
- package/dist/capability-map/dsm-mutations.js +196 -0
- package/dist/capability-map/dsm-schema.d.ts +71 -0
- package/dist/capability-map/dsm-schema.js +14 -0
- package/dist/capability-map/dsm-theme.d.ts +20 -0
- package/dist/capability-map/dsm-theme.js +36 -0
- package/dist/capability-map/dsm-validate.d.ts +2 -0
- package/dist/capability-map/dsm-validate.js +93 -0
- package/dist/capability-map/index.d.ts +9 -0
- package/dist/capability-map/index.js +6 -0
- package/dist/compliance/__tests__/trace.test.d.ts +1 -0
- package/dist/compliance/__tests__/trace.test.js +144 -0
- package/dist/compliance/classify.js +7 -1
- package/dist/compliance/index.d.ts +2 -1
- package/dist/compliance/index.js +1 -0
- package/dist/compliance/reverse-index.js +4 -1
- package/dist/compliance/trace.d.ts +26 -0
- package/dist/compliance/trace.js +89 -0
- package/dist/compliance/types.d.ts +94 -1
- package/dist/goals/GoalNode.d.ts +18 -0
- package/dist/goals/GoalNode.js +91 -0
- package/dist/goals/GoalTreeView.d.ts +57 -0
- package/dist/goals/GoalTreeView.js +228 -0
- package/dist/goals/__tests__/GoalTreeView.test.d.ts +1 -0
- package/dist/goals/__tests__/GoalTreeView.test.js +296 -0
- package/dist/goals/index.d.ts +3 -0
- package/dist/goals/index.js +1 -0
- package/dist/goals/theme.d.ts +22 -0
- package/dist/goals/theme.js +32 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/webview/__tests__/render-blocks.test.js +48 -0
- package/dist/webview/notation-style.d.ts +11 -0
- package/dist/webview/notation-style.js +11 -0
- package/dist/webview/render-blocks.js +8 -4
- package/dist/webview/render-goals.js +2 -1
- package/package.json +4 -1
- package/src/activities/__tests__/gantt.test.ts +45 -0
- package/src/capability-map/CapabilityCardNode.tsx +146 -0
- package/src/capability-map/CapabilityMapView.tsx +331 -0
- package/src/capability-map/CapabilityRootNode.tsx +38 -0
- package/src/capability-map/__tests__/CapabilityMapView.test.tsx +232 -0
- package/src/capability-map/__tests__/address.test.ts +59 -0
- package/src/capability-map/__tests__/dsm-layout.test.ts +52 -0
- package/src/capability-map/__tests__/dsm-mutations.test.ts +141 -0
- package/src/capability-map/__tests__/dsm-validate.test.ts +65 -0
- package/src/capability-map/address.ts +48 -0
- package/src/capability-map/dsm-layout.ts +142 -0
- package/src/capability-map/dsm-mutations.ts +208 -0
- package/src/capability-map/dsm-schema.ts +80 -0
- package/src/capability-map/dsm-theme.ts +53 -0
- package/src/capability-map/dsm-validate.ts +103 -0
- package/src/capability-map/index.ts +35 -0
- package/src/compliance/__tests__/trace.test.ts +157 -0
- package/src/compliance/classify.ts +7 -1
- package/src/compliance/index.ts +6 -0
- package/src/compliance/reverse-index.ts +3 -1
- package/src/compliance/trace.ts +118 -0
- package/src/compliance/types.ts +101 -1
- package/src/goals/GoalNode.tsx +212 -0
- package/src/goals/GoalTreeView.tsx +350 -0
- package/src/goals/__tests__/GoalTreeView.test.tsx +313 -0
- package/src/goals/index.ts +4 -0
- package/src/goals/theme.ts +54 -0
- package/src/index.ts +29 -1
- package/src/webview/__tests__/render-blocks.test.ts +54 -0
- package/src/webview/notation-style.ts +12 -0
- package/src/webview/render-blocks.ts +8 -4
- package/src/webview/render-goals.ts +2 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme tokens for GoalTreeView — replaces DSM's Redux selectors
|
|
3
|
+
* (selectDiagramParams, selectGoalLevelColors) with a plain prop. Hosts
|
|
4
|
+
* that already carry Transitrix's --ts-* CSS custom properties (Studio)
|
|
5
|
+
* can pass those through; hosts with their own theme store (DSM) map
|
|
6
|
+
* their own values onto this shape.
|
|
7
|
+
*/
|
|
8
|
+
export interface ThemeTokens {
|
|
9
|
+
/** Card fill colour per goal level (0..7). Falls back to DEFAULT_GOAL_LEVEL_COLORS for missing levels. */
|
|
10
|
+
goalLevelColors?: Record<number, string>;
|
|
11
|
+
cardBorderColor?: string;
|
|
12
|
+
cardBorderWidth?: number;
|
|
13
|
+
cardTextColor?: string;
|
|
14
|
+
cardMetaColor?: string;
|
|
15
|
+
edgeColor?: string;
|
|
16
|
+
edgeWidth?: number;
|
|
17
|
+
/** Border colour a card takes on while it's a highlighted drop target during drag. */
|
|
18
|
+
dropTargetBorderColor?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const DEFAULT_GOAL_LEVEL_COLORS: Record<number, string> = {
|
|
22
|
+
0: '#e0e7ff',
|
|
23
|
+
1: '#c7d2fe',
|
|
24
|
+
2: '#a5b4fc',
|
|
25
|
+
3: '#93c5fd',
|
|
26
|
+
4: '#bae6fd',
|
|
27
|
+
5: '#bbf7d0',
|
|
28
|
+
6: '#fef08a',
|
|
29
|
+
7: '#fed7aa',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const DEFAULT_THEME: Required<ThemeTokens> = {
|
|
33
|
+
goalLevelColors: DEFAULT_GOAL_LEVEL_COLORS,
|
|
34
|
+
cardBorderColor: '#94a3b8',
|
|
35
|
+
cardBorderWidth: 1,
|
|
36
|
+
cardTextColor: '#0f172a',
|
|
37
|
+
cardMetaColor: '#64748b',
|
|
38
|
+
edgeColor: '#94a3b8',
|
|
39
|
+
edgeWidth: 1.5,
|
|
40
|
+
dropTargetBorderColor: '#2563eb',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export function resolveTheme(theme?: ThemeTokens): Required<ThemeTokens> {
|
|
44
|
+
return {
|
|
45
|
+
goalLevelColors: theme?.goalLevelColors ?? DEFAULT_THEME.goalLevelColors,
|
|
46
|
+
cardBorderColor: theme?.cardBorderColor ?? DEFAULT_THEME.cardBorderColor,
|
|
47
|
+
cardBorderWidth: theme?.cardBorderWidth ?? DEFAULT_THEME.cardBorderWidth,
|
|
48
|
+
cardTextColor: theme?.cardTextColor ?? DEFAULT_THEME.cardTextColor,
|
|
49
|
+
cardMetaColor: theme?.cardMetaColor ?? DEFAULT_THEME.cardMetaColor,
|
|
50
|
+
edgeColor: theme?.edgeColor ?? DEFAULT_THEME.edgeColor,
|
|
51
|
+
edgeWidth: theme?.edgeWidth ?? DEFAULT_THEME.edgeWidth,
|
|
52
|
+
dropTargetBorderColor: theme?.dropTargetBorderColor ?? DEFAULT_THEME.dropTargetBorderColor,
|
|
53
|
+
};
|
|
54
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -6,7 +6,35 @@ export * from "./geometry";
|
|
|
6
6
|
export * from "./applications/index";
|
|
7
7
|
export * from "./assertion/index";
|
|
8
8
|
export * from "./blocks/index";
|
|
9
|
-
|
|
9
|
+
// capability-map/index.js also exports a set of DSM-migration symbols
|
|
10
|
+
// (Capability, reparent, addChild, LayoutOptions, ThemeTokens, ...) that
|
|
11
|
+
// reuse the goals module's exact generic Layer-A/B naming convention by
|
|
12
|
+
// design (both follow the same extraction-spec template) — the wildcard
|
|
13
|
+
// re-export below would collide with goals' identically-named symbols.
|
|
14
|
+
// Neither spec's acceptance criteria asks for root-barrel access (only
|
|
15
|
+
// `@transitrix/diagrams/goals` / `@transitrix/diagrams/capability-map`
|
|
16
|
+
// subpath imports), so the colliding names are simply left off the root
|
|
17
|
+
// barrel; they're still importable via the capability-map subpath.
|
|
18
|
+
export type {
|
|
19
|
+
CapabilityType,
|
|
20
|
+
CapabilityNode,
|
|
21
|
+
CapabilityMapHeader,
|
|
22
|
+
CapabilityMapFile,
|
|
23
|
+
CapabilityTreeNode,
|
|
24
|
+
CapabilityTreeEdge,
|
|
25
|
+
CapabilityTreeLevelCounts,
|
|
26
|
+
CapabilityTreeLayout,
|
|
27
|
+
RenderCapabilityTreeOptions,
|
|
28
|
+
} from "./capability-map/index";
|
|
29
|
+
export {
|
|
30
|
+
validateCapabilityMap,
|
|
31
|
+
layoutCapabilityTree,
|
|
32
|
+
TREE_NODE_WIDTH,
|
|
33
|
+
TREE_NODE_HEIGHT,
|
|
34
|
+
TREE_RANK_SEP,
|
|
35
|
+
TREE_NODE_SEP,
|
|
36
|
+
renderCapabilityTreeSvg,
|
|
37
|
+
} from "./capability-map/index";
|
|
10
38
|
export * from "./change/index";
|
|
11
39
|
export * from "./compliance/index";
|
|
12
40
|
export * from "./compliance-matrix/index";
|
|
@@ -59,6 +59,15 @@ function extractLabels(svg: string): Array<{ cls: string; text: string }> {
|
|
|
59
59
|
return out;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/** Extract {class, y} for each text element in SVG document order. */
|
|
63
|
+
function extractTextY(svg: string): Array<{ cls: string; y: number }> {
|
|
64
|
+
const re = /<text class="(text-(?:primary|id))"[^>]*\by="([^"]+)"/g;
|
|
65
|
+
const out: Array<{ cls: string; y: number }> = [];
|
|
66
|
+
let m: RegExpExecArray | null;
|
|
67
|
+
while ((m = re.exec(svg))) out.push({ cls: m[1], y: parseFloat(m[2]) });
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
|
|
62
71
|
describe('renderBlocksSvg — leaf labels', () => {
|
|
63
72
|
it('wraps a long name to at most 3 lines and truncates with …', () => {
|
|
64
73
|
const longName =
|
|
@@ -153,3 +162,48 @@ describe('renderBlocksSvg — container headers', () => {
|
|
|
153
162
|
expect(primaries.some((l) => l.text.endsWith('…'))).toBe(true);
|
|
154
163
|
});
|
|
155
164
|
});
|
|
165
|
+
|
|
166
|
+
describe('renderBlocksSvg — name/ID vertical overlap regression (follows-up on #419)', () => {
|
|
167
|
+
// Root cause: NAME_ID_GAP was 6 px. text-primary is 12 px tall (±6 px around
|
|
168
|
+
// centre) and text-id is 10 px tall (±5 px around centre). The minimum gap
|
|
169
|
+
// between centres to avoid overlap is 11 px; a gap of 6 caused 5 px of overlap.
|
|
170
|
+
// Fix: NAME_ID_GAP = 14 (3 px visual buffer, matching the gap between name lines).
|
|
171
|
+
|
|
172
|
+
const NAME_HALF_H = 6; // half of text-primary font size (12 px)
|
|
173
|
+
const ID_HALF_H = 5; // half of text-id font size (10 px)
|
|
174
|
+
|
|
175
|
+
it('leaf node: last name line bottom does not overlap first ID line top (1 name + 1 ID)', () => {
|
|
176
|
+
const svg = renderBlocksSvg(leafDoc('Short', 'BLK-1'));
|
|
177
|
+
const positions = extractTextY(svg);
|
|
178
|
+
const nameY = positions.filter(p => p.cls === 'text-primary').map(p => p.y);
|
|
179
|
+
const idY = positions.filter(p => p.cls === 'text-id').map(p => p.y);
|
|
180
|
+
expect(nameY.length).toBeGreaterThan(0);
|
|
181
|
+
expect(idY.length).toBeGreaterThan(0);
|
|
182
|
+
const lastNameBottom = Math.max(...nameY) + NAME_HALF_H;
|
|
183
|
+
const firstIdTop = Math.min(...idY) - ID_HALF_H;
|
|
184
|
+
expect(firstIdTop).toBeGreaterThan(lastNameBottom);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('leaf node: no overlap with a 3-word name (2 name lines + 1 ID)', () => {
|
|
188
|
+
const svg = renderBlocksSvg(leafDoc('Three Word Name', 'BLK-001'));
|
|
189
|
+
const positions = extractTextY(svg);
|
|
190
|
+
const nameY = positions.filter(p => p.cls === 'text-primary').map(p => p.y);
|
|
191
|
+
const idY = positions.filter(p => p.cls === 'text-id').map(p => p.y);
|
|
192
|
+
const lastNameBottom = Math.max(...nameY) + NAME_HALF_H;
|
|
193
|
+
const firstIdTop = Math.min(...idY) - ID_HALF_H;
|
|
194
|
+
expect(firstIdTop).toBeGreaterThan(lastNameBottom);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('leaf node: no overlap with a long name (3 name lines) and wrapped ID (2 lines)', () => {
|
|
198
|
+
const longName =
|
|
199
|
+
'Personal data records that linger after consent withdrawal across many systems and archives';
|
|
200
|
+
const longId = 'VERY_LONG_BLOCK_IDENTIFIER_WITH_MANY_SEGMENTS_AND_MORE';
|
|
201
|
+
const svg = renderBlocksSvg(leafDoc(longName, longId));
|
|
202
|
+
const positions = extractTextY(svg);
|
|
203
|
+
const nameY = positions.filter(p => p.cls === 'text-primary').map(p => p.y);
|
|
204
|
+
const idY = positions.filter(p => p.cls === 'text-id').map(p => p.y);
|
|
205
|
+
const lastNameBottom = Math.max(...nameY) + NAME_HALF_H;
|
|
206
|
+
const firstIdTop = Math.min(...idY) - ID_HALF_H;
|
|
207
|
+
expect(firstIdTop).toBeGreaterThan(lastNameBottom);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared visual constants for entity-box (block-style) notation renderers.
|
|
3
|
+
*
|
|
4
|
+
* Goals, Nested Blocks, FGCA/DGCA, and any future notation using the same
|
|
5
|
+
* entity-box visual language import their shared style tokens from here.
|
|
6
|
+
* Centralising them prevents the per-renderer drift that caused the Goals/Blocks
|
|
7
|
+
* style mismatch (border radius diverged to rx=6 in Blocks vs rx=8 everywhere
|
|
8
|
+
* else). Change here and every notation updates together.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Border-radius (px) for entity and leaf-block node rectangles. */
|
|
12
|
+
export const ENTITY_NODE_RX = 8;
|
|
@@ -16,6 +16,7 @@ import { layoutNestedBlocks } from '../blocks/layout.js';
|
|
|
16
16
|
import type { BlocksFile, BlocksLayout, LaidOutBlock } from '../blocks/types.js';
|
|
17
17
|
import { generateSvgEmbedCss, type ThemeId } from '../theme/index.js';
|
|
18
18
|
import { escXml } from './render-util.js';
|
|
19
|
+
import { ENTITY_NODE_RX } from './notation-style.js';
|
|
19
20
|
|
|
20
21
|
const PAD = 24;
|
|
21
22
|
|
|
@@ -25,9 +26,12 @@ const CHAR_W = 7;
|
|
|
25
26
|
const CHAR_W_ID = 6;
|
|
26
27
|
|
|
27
28
|
// Vertical spacing between line centres.
|
|
28
|
-
const LINE_H = 14;
|
|
29
|
-
const LINE_H_ID = 12;
|
|
30
|
-
|
|
29
|
+
const LINE_H = 14; // name lines (text-primary, 12 px font)
|
|
30
|
+
const LINE_H_ID = 12; // id lines (text-id, 10 px font)
|
|
31
|
+
// Minimum gap to avoid overlap: half of name text height (6 px) + half of id
|
|
32
|
+
// text height (5 px) = 11 px. Use 14 px to match the name-line separation and
|
|
33
|
+
// leave a 3 px visual buffer — same as the gap between consecutive name lines.
|
|
34
|
+
const NAME_ID_GAP = 14;
|
|
31
35
|
|
|
32
36
|
/** Word-wrap `text` to at most `maxLines` lines of `maxChars` each. */
|
|
33
37
|
function wrapWords(text: string, maxChars: number, maxLines: number): string[] {
|
|
@@ -110,7 +114,7 @@ function emitBlockSvg(b: LaidOutBlock, ox: number, oy: number, parts: string[]):
|
|
|
110
114
|
const cls = levelClassForDepth(b.depth);
|
|
111
115
|
const cx = b.x + ox + b.width / 2;
|
|
112
116
|
parts.push(
|
|
113
|
-
`<rect class="diagram-node ${cls}" x="${b.x + ox}" y="${b.y + oy}" width="${b.width}" height="${b.height}" rx="
|
|
117
|
+
`<rect class="diagram-node ${cls}" x="${b.x + ox}" y="${b.y + oy}" width="${b.width}" height="${b.height}" rx="${ENTITY_NODE_RX}"/>`,
|
|
114
118
|
);
|
|
115
119
|
|
|
116
120
|
const innerW = Math.max(0, b.width - TEXT_MARGIN_X * 2);
|
|
@@ -15,6 +15,7 @@ import type { GoalTree, GoalTreeLayout, LaidOutEdge } from '../goals/types.js';
|
|
|
15
15
|
import { horizontalCubicEdgePath, DEFAULT_EDGE_CURVATURE } from '../edge-path.js';
|
|
16
16
|
import { generateSvgEmbedCss, type ThemeId } from '../theme/index.js';
|
|
17
17
|
import { escXml } from './render-util.js';
|
|
18
|
+
import { ENTITY_NODE_RX } from './notation-style.js';
|
|
18
19
|
|
|
19
20
|
const NODE_W = 250;
|
|
20
21
|
const NODE_H = 72;
|
|
@@ -130,7 +131,7 @@ export function renderGoalsLayoutSvg(layout: GoalTreeLayout, options: RenderGoal
|
|
|
130
131
|
const typeLabel = n.data.type ?? '';
|
|
131
132
|
const idText = String(n.data.id);
|
|
132
133
|
return `<g>
|
|
133
|
-
<rect class="diagram-node level-${level}" x="${x}" y="${y}" width="${n.width}" height="${n.height}" rx="
|
|
134
|
+
<rect class="diagram-node level-${level}" x="${x}" y="${y}" width="${n.width}" height="${n.height}" rx="${ENTITY_NODE_RX}"/>
|
|
134
135
|
<text class="text-primary" x="${x + n.width / 2}" y="${nameY1}" text-anchor="middle" dominant-baseline="central">${escXml(line1)}</text>${twoLines ? `
|
|
135
136
|
<text class="text-primary" x="${x + n.width / 2}" y="${nameY2}" text-anchor="middle" dominant-baseline="central">${escXml(line2)}</text>` : ''}${typeLabel ? `
|
|
136
137
|
<text class="text-secondary" x="${x + n.width / 2}" y="${typeY}" text-anchor="middle" dominant-baseline="central">${escXml(typeLabel)}</text>` : ''}
|