@transitrix/diagrams 1.8.1 → 1.8.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/__tests__/node-size-presets.test.d.ts +1 -0
- package/dist/__tests__/node-size-presets.test.js +30 -0
- package/dist/activities/layout.js +1 -1
- package/dist/blocks/__tests__/layout.test.js +2 -2
- package/dist/blocks/layout.js +4 -3
- package/dist/capability-map/dsm-layout.js +1 -1
- package/dist/capability-map/layout-tree.d.ts +1 -1
- package/dist/capability-map/layout-tree.js +1 -1
- package/dist/fgca/preview-layout.d.ts +2 -2
- package/dist/fgca/preview-layout.js +3 -2
- package/dist/node-size-presets.d.ts +17 -4
- package/dist/node-size-presets.js +86 -72
- package/dist/webview/__tests__/entity-text-layout.test.js +115 -3
- package/dist/webview/__tests__/render-blocks.test.js +9 -6
- package/dist/webview/__tests__/render-goals.test.js +41 -0
- package/dist/webview/entity-text-layout.d.ts +31 -3
- package/dist/webview/entity-text-layout.js +123 -57
- package/package.json +1 -1
- package/src/__tests__/node-size-presets.test.ts +50 -0
- package/src/activities/layout.ts +1 -1
- package/src/blocks/__tests__/layout.test.ts +2 -2
- package/src/blocks/layout.ts +4 -3
- package/src/capability-map/dsm-layout.ts +1 -1
- package/src/capability-map/layout-tree.ts +1 -1
- package/src/fgca/preview-layout.ts +3 -2
- package/src/node-size-presets.ts +95 -72
- package/src/webview/__tests__/entity-text-layout.test.ts +132 -2
- package/src/webview/__tests__/render-blocks.test.ts +11 -6
- package/src/webview/__tests__/render-goals.test.ts +42 -0
- package/src/webview/entity-text-layout.ts +140 -54
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
* Capability Map renderers.
|
|
7
7
|
*/
|
|
8
8
|
export declare const TEXT_MARGIN_X = 8;
|
|
9
|
+
/**
|
|
10
|
+
* Minimum reserved vertical padding (px) between the box's top/bottom edge and
|
|
11
|
+
* the nearest line's outer extent. Always enforced — see
|
|
12
|
+
* {@link layoutCenteredEntityText}'s adaptive line-count reduction, which
|
|
13
|
+
* trades name-line count for this padding rather than letting text touch or
|
|
14
|
+
* cross the box border.
|
|
15
|
+
*/
|
|
16
|
+
export declare const TEXT_MARGIN_Y = 4;
|
|
9
17
|
/** Approximate character width (px) at text-primary size (12px/600). */
|
|
10
18
|
export declare const CHAR_W_PRIMARY = 7;
|
|
11
19
|
/** Approximate character width (px) at text-id size (10px/600). */
|
|
@@ -18,10 +26,15 @@ export declare const LINE_H_PRIMARY = 14;
|
|
|
18
26
|
export declare const LINE_H_SECONDARY = 14;
|
|
19
27
|
/** Vertical advance between id line centres (text-id). */
|
|
20
28
|
export declare const LINE_H_ID = 12;
|
|
21
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Gap between the name block and the id block in leaf-style layouts — used as
|
|
31
|
+
* a direct centre-to-centre distance there, so keep it >= ~11px (half of
|
|
32
|
+
* text-primary's 12px font + half of text-id's 10px font) to avoid the
|
|
33
|
+
* overlap regressed in #419. Do not tighten without re-checking that test.
|
|
34
|
+
*/
|
|
22
35
|
export declare const NAME_ID_GAP = 14;
|
|
23
36
|
/** Gap between stacked row groups (name → type → id). */
|
|
24
|
-
export declare const ROW_GROUP_GAP =
|
|
37
|
+
export declare const ROW_GROUP_GAP = 4;
|
|
25
38
|
export declare function maxCharsForInnerWidth(innerWidth: number, charWidth: number): number;
|
|
26
39
|
/** Word-wrap `text` to at most `maxLines` lines of `maxChars` each. */
|
|
27
40
|
export declare function wrapWords(text: string, maxChars: number, maxLines: number): string[];
|
|
@@ -55,10 +68,20 @@ export interface LayoutCenteredEntityOptions {
|
|
|
55
68
|
nameMaxLines?: number;
|
|
56
69
|
idMaxLines?: number;
|
|
57
70
|
marginX?: number;
|
|
71
|
+
marginY?: number;
|
|
58
72
|
}
|
|
59
73
|
/**
|
|
60
74
|
* Layout centred entity text (name → optional type → id) inside a fixed box.
|
|
61
75
|
* Returns absolute SVG y-positions (line centres) for each rendered row.
|
|
76
|
+
*
|
|
77
|
+
* Guarantees, in order of priority:
|
|
78
|
+
* 1. Content never crosses the box's top/bottom edge.
|
|
79
|
+
* 2. At least `marginY` (default {@link TEXT_MARGIN_Y}) of padding is kept
|
|
80
|
+
* above the first line's glyphs and below the last line's glyphs.
|
|
81
|
+
* 3. The name gets as many lines as `nameMaxLines` allows (2 by default)
|
|
82
|
+
* when the box is tall enough; on short/compact boxes (e.g. with a type
|
|
83
|
+
* row present) it degrades to fewer name lines — trading line count,
|
|
84
|
+
* not padding — rather than letting text overflow.
|
|
62
85
|
*/
|
|
63
86
|
export declare function layoutCenteredEntityText(opts: LayoutCenteredEntityOptions): TextLineSpec[];
|
|
64
87
|
/** Emit centred `<text>` elements from {@link layoutCenteredEntityText} specs. */
|
|
@@ -73,7 +96,12 @@ export interface LayoutLeafBlockTextOptions {
|
|
|
73
96
|
nameMaxLines?: number;
|
|
74
97
|
marginX?: number;
|
|
75
98
|
}
|
|
76
|
-
/**
|
|
99
|
+
/**
|
|
100
|
+
* Leaf nested-block layout: name (up to 3 lines) + id (up to 2 lines), centred.
|
|
101
|
+
* Degrades name line count (never below 1) when the box is too short to hold
|
|
102
|
+
* the requested max plus {@link TEXT_MARGIN_Y} padding on both edges, so text
|
|
103
|
+
* never crosses the box border.
|
|
104
|
+
*/
|
|
77
105
|
export declare function layoutLeafBlockText(opts: LayoutLeafBlockTextOptions): TextLineSpec[];
|
|
78
106
|
export interface LayoutHeaderBlockTextOptions {
|
|
79
107
|
boxX: number;
|
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
* Capability Map renderers.
|
|
7
7
|
*/
|
|
8
8
|
export const TEXT_MARGIN_X = 8;
|
|
9
|
+
/**
|
|
10
|
+
* Minimum reserved vertical padding (px) between the box's top/bottom edge and
|
|
11
|
+
* the nearest line's outer extent. Always enforced — see
|
|
12
|
+
* {@link layoutCenteredEntityText}'s adaptive line-count reduction, which
|
|
13
|
+
* trades name-line count for this padding rather than letting text touch or
|
|
14
|
+
* cross the box border.
|
|
15
|
+
*/
|
|
16
|
+
export const TEXT_MARGIN_Y = 4;
|
|
9
17
|
/** Approximate character width (px) at text-primary size (12px/600). */
|
|
10
18
|
export const CHAR_W_PRIMARY = 7;
|
|
11
19
|
/** Approximate character width (px) at text-id size (10px/600). */
|
|
@@ -18,10 +26,15 @@ export const LINE_H_PRIMARY = 14;
|
|
|
18
26
|
export const LINE_H_SECONDARY = 14;
|
|
19
27
|
/** Vertical advance between id line centres (text-id). */
|
|
20
28
|
export const LINE_H_ID = 12;
|
|
21
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Gap between the name block and the id block in leaf-style layouts — used as
|
|
31
|
+
* a direct centre-to-centre distance there, so keep it >= ~11px (half of
|
|
32
|
+
* text-primary's 12px font + half of text-id's 10px font) to avoid the
|
|
33
|
+
* overlap regressed in #419. Do not tighten without re-checking that test.
|
|
34
|
+
*/
|
|
22
35
|
export const NAME_ID_GAP = 14;
|
|
23
36
|
/** Gap between stacked row groups (name → type → id). */
|
|
24
|
-
export const ROW_GROUP_GAP =
|
|
37
|
+
export const ROW_GROUP_GAP = 4;
|
|
25
38
|
export function maxCharsForInnerWidth(innerWidth, charWidth) {
|
|
26
39
|
return Math.max(4, Math.floor(Math.max(0, innerWidth) / charWidth));
|
|
27
40
|
}
|
|
@@ -136,69 +149,100 @@ export function truncateId(id, maxChars) {
|
|
|
136
149
|
const sepIdx = Math.max(cut.lastIndexOf('_'), cut.lastIndexOf('-'));
|
|
137
150
|
return `${sepIdx > safeMax / 3 ? cut.slice(0, sepIdx) : cut}…`;
|
|
138
151
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
152
|
+
/**
|
|
153
|
+
* Half-extent (px) of rendered glyphs around their centre y — approximately
|
|
154
|
+
* font-size / 2, the same heuristic the #419 non-overlap regression test
|
|
155
|
+
* uses. Used only to keep the *outermost* line's visual extent (not just its
|
|
156
|
+
* centre) inside the box when computing edge padding; inter-line spacing
|
|
157
|
+
* between adjacent rows is governed separately by the LINE_H_ constants,
|
|
158
|
+
* NAME_ID_GAP and ROW_GROUP_GAP, which already keep centres far enough apart
|
|
159
|
+
* to avoid overlap.
|
|
160
|
+
*/
|
|
161
|
+
const HALF_EXTENT_PRIMARY = 6; // text-primary, 12px font
|
|
162
|
+
const HALF_EXTENT_ID = 5; // text-id, 10px font
|
|
163
|
+
/** Place `groups`' lines starting at `startY`, advancing by each line's own height and gap. */
|
|
164
|
+
function placeLines(groups, startY) {
|
|
165
|
+
const out = [];
|
|
166
|
+
let y = startY;
|
|
167
|
+
for (let gi = 0; gi < groups.length; gi++) {
|
|
168
|
+
const g = groups[gi];
|
|
169
|
+
for (let li = 0; li < g.lines.length; li++) {
|
|
170
|
+
out.push({ cls: g.cls, text: g.lines[li], y });
|
|
171
|
+
y += g.lineHeight;
|
|
172
|
+
}
|
|
173
|
+
if (gi < groups.length - 1)
|
|
174
|
+
y += g.gapAfter;
|
|
147
175
|
}
|
|
148
|
-
return
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
/** Centre-to-centre distance from the first to the last placed line. */
|
|
179
|
+
function centerSpan(groups) {
|
|
180
|
+
const ys = placeLines(groups, 0).map((s) => s.y);
|
|
181
|
+
return ys[ys.length - 1] - ys[0];
|
|
149
182
|
}
|
|
150
183
|
/**
|
|
151
184
|
* Layout centred entity text (name → optional type → id) inside a fixed box.
|
|
152
185
|
* Returns absolute SVG y-positions (line centres) for each rendered row.
|
|
186
|
+
*
|
|
187
|
+
* Guarantees, in order of priority:
|
|
188
|
+
* 1. Content never crosses the box's top/bottom edge.
|
|
189
|
+
* 2. At least `marginY` (default {@link TEXT_MARGIN_Y}) of padding is kept
|
|
190
|
+
* above the first line's glyphs and below the last line's glyphs.
|
|
191
|
+
* 3. The name gets as many lines as `nameMaxLines` allows (2 by default)
|
|
192
|
+
* when the box is tall enough; on short/compact boxes (e.g. with a type
|
|
193
|
+
* row present) it degrades to fewer name lines — trading line count,
|
|
194
|
+
* not padding — rather than letting text overflow.
|
|
153
195
|
*/
|
|
154
196
|
export function layoutCenteredEntityText(opts) {
|
|
155
197
|
const marginX = opts.marginX ?? TEXT_MARGIN_X;
|
|
198
|
+
const marginY = opts.marginY ?? TEXT_MARGIN_Y;
|
|
156
199
|
const innerW = Math.max(0, opts.boxWidth - marginX * 2);
|
|
157
|
-
const cx = opts.boxX + opts.boxWidth / 2;
|
|
158
200
|
const maxCharsName = maxCharsForInnerWidth(innerW, CHAR_W_PRIMARY);
|
|
159
201
|
const maxCharsType = maxCharsForInnerWidth(innerW, CHAR_W_SECONDARY);
|
|
160
202
|
const maxCharsId = maxCharsForInnerWidth(innerW, CHAR_W_ID);
|
|
161
|
-
const nameLines = wrapWords(opts.name, maxCharsName, opts.nameMaxLines ?? 2);
|
|
162
203
|
const typeLine = opts.type?.trim() ? truncateLine(opts.type, maxCharsType) : undefined;
|
|
163
204
|
const idMaxLines = opts.idMaxLines ?? 1;
|
|
164
205
|
const idLines = idMaxLines <= 1 ? [truncateId(opts.id, maxCharsId)] : wrapId(opts.id, maxCharsId, idMaxLines);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
206
|
+
function buildGroups(nameLines) {
|
|
207
|
+
const groups = [
|
|
208
|
+
{
|
|
209
|
+
cls: 'text-primary',
|
|
210
|
+
lines: nameLines,
|
|
211
|
+
lineHeight: LINE_H_PRIMARY,
|
|
212
|
+
gapAfter: typeLine ? ROW_GROUP_GAP : NAME_ID_GAP,
|
|
213
|
+
},
|
|
214
|
+
];
|
|
215
|
+
if (typeLine) {
|
|
216
|
+
groups.push({
|
|
217
|
+
cls: 'text-secondary',
|
|
218
|
+
lines: [typeLine],
|
|
219
|
+
lineHeight: LINE_H_SECONDARY,
|
|
220
|
+
gapAfter: NAME_ID_GAP,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
174
223
|
groups.push({
|
|
175
|
-
cls: 'text-
|
|
176
|
-
lines:
|
|
177
|
-
lineHeight:
|
|
178
|
-
gapAfter:
|
|
224
|
+
cls: 'text-id',
|
|
225
|
+
lines: idLines,
|
|
226
|
+
lineHeight: LINE_H_ID,
|
|
227
|
+
gapAfter: 0,
|
|
179
228
|
});
|
|
229
|
+
return groups;
|
|
180
230
|
}
|
|
181
|
-
groups
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const g = groups[gi];
|
|
193
|
-
for (let li = 0; li < g.lines.length; li++) {
|
|
194
|
-
out.push({ cls: g.cls, text: g.lines[li], y });
|
|
195
|
-
if (li < g.lines.length - 1)
|
|
196
|
-
y += g.lineHeight;
|
|
197
|
-
}
|
|
198
|
-
if (gi < groups.length - 1)
|
|
199
|
-
y += g.gapAfter;
|
|
231
|
+
// groups[0] is always the name (text-primary) and the last group is always
|
|
232
|
+
// the id (text-id), so the edge-padding half-extents below are fixed.
|
|
233
|
+
const requestedNameMaxLines = Math.max(1, opts.nameMaxLines ?? 2);
|
|
234
|
+
const availableHeight = Math.max(0, opts.boxHeight - marginY * 2);
|
|
235
|
+
let nameLines = wrapWords(opts.name, maxCharsName, requestedNameMaxLines);
|
|
236
|
+
let groups = buildGroups(nameLines);
|
|
237
|
+
let contentSpan = centerSpan(groups) + HALF_EXTENT_PRIMARY + HALF_EXTENT_ID;
|
|
238
|
+
for (let cap = requestedNameMaxLines - 1; cap >= 1 && contentSpan > availableHeight; cap--) {
|
|
239
|
+
nameLines = wrapWords(opts.name, maxCharsName, cap);
|
|
240
|
+
groups = buildGroups(nameLines);
|
|
241
|
+
contentSpan = centerSpan(groups) + HALF_EXTENT_PRIMARY + HALF_EXTENT_ID;
|
|
200
242
|
}
|
|
201
|
-
|
|
243
|
+
const margin = Math.max(0, (opts.boxHeight - contentSpan) / 2);
|
|
244
|
+
const firstY = Math.round(opts.boxY + margin + HALF_EXTENT_PRIMARY);
|
|
245
|
+
return placeLines(groups, firstY);
|
|
202
246
|
}
|
|
203
247
|
/** Emit centred `<text>` elements from {@link layoutCenteredEntityText} specs. */
|
|
204
248
|
export function emitCenteredTextSvg(specs, centerX, esc) {
|
|
@@ -210,28 +254,48 @@ export function emitCenteredTextSvg(specs, centerX, esc) {
|
|
|
210
254
|
})
|
|
211
255
|
.join('\n');
|
|
212
256
|
}
|
|
213
|
-
/**
|
|
257
|
+
/**
|
|
258
|
+
* Leaf nested-block layout: name (up to 3 lines) + id (up to 2 lines), centred.
|
|
259
|
+
* Degrades name line count (never below 1) when the box is too short to hold
|
|
260
|
+
* the requested max plus {@link TEXT_MARGIN_Y} padding on both edges, so text
|
|
261
|
+
* never crosses the box border.
|
|
262
|
+
*/
|
|
214
263
|
export function layoutLeafBlockText(opts) {
|
|
215
264
|
const marginX = opts.marginX ?? TEXT_MARGIN_X;
|
|
216
265
|
const innerW = Math.max(0, opts.boxWidth - marginX * 2);
|
|
217
266
|
const maxCharsName = maxCharsForInnerWidth(innerW, CHAR_W_PRIMARY);
|
|
218
267
|
const maxCharsId = maxCharsForInnerWidth(innerW, CHAR_W_ID);
|
|
219
|
-
const nameLines = wrapWords(opts.name, maxCharsName, opts.nameMaxLines ?? 3);
|
|
220
268
|
const idLines = wrapId(opts.id, maxCharsId, 2);
|
|
221
|
-
const
|
|
222
|
-
const
|
|
223
|
-
const
|
|
224
|
-
|
|
269
|
+
const idCenterSpan = (idLines.length - 1) * LINE_H_ID;
|
|
270
|
+
const requestedNameMaxLines = Math.max(1, opts.nameMaxLines ?? 3);
|
|
271
|
+
const availableHeight = Math.max(0, opts.boxHeight - TEXT_MARGIN_Y * 2);
|
|
272
|
+
let nameLines = wrapWords(opts.name, maxCharsName, requestedNameMaxLines);
|
|
273
|
+
let nameCenterSpan = (nameLines.length - 1) * LINE_H_PRIMARY;
|
|
274
|
+
let contentSpan = nameCenterSpan + NAME_ID_GAP + idCenterSpan + HALF_EXTENT_PRIMARY + HALF_EXTENT_ID;
|
|
275
|
+
for (let cap = requestedNameMaxLines - 1; cap >= 1 && contentSpan > availableHeight; cap--) {
|
|
276
|
+
nameLines = wrapWords(opts.name, maxCharsName, cap);
|
|
277
|
+
nameCenterSpan = (nameLines.length - 1) * LINE_H_PRIMARY;
|
|
278
|
+
contentSpan = nameCenterSpan + NAME_ID_GAP + idCenterSpan + HALF_EXTENT_PRIMARY + HALF_EXTENT_ID;
|
|
279
|
+
}
|
|
280
|
+
const margin = Math.max(0, (opts.boxHeight - contentSpan) / 2);
|
|
281
|
+
const firstY = Math.round(opts.boxY + margin + HALF_EXTENT_PRIMARY);
|
|
225
282
|
const out = [];
|
|
226
283
|
for (let i = 0; i < nameLines.length; i++) {
|
|
227
284
|
out.push({ cls: 'text-primary', text: nameLines[i], y: firstY + i * LINE_H_PRIMARY });
|
|
228
285
|
}
|
|
229
|
-
const idFirstY = firstY +
|
|
286
|
+
const idFirstY = firstY + nameCenterSpan + NAME_ID_GAP;
|
|
230
287
|
for (let i = 0; i < idLines.length; i++) {
|
|
231
288
|
out.push({ cls: 'text-id', text: idLines[i], y: idFirstY + i * LINE_H_ID });
|
|
232
289
|
}
|
|
233
290
|
return out;
|
|
234
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* Direct centre-to-centre gap between the header's name and id lines.
|
|
294
|
+
* Safely above the ~11px non-overlap floor (half of text-primary's 12px font
|
|
295
|
+
* + half of text-id's 10px font, per the #419 regression) but tighter than
|
|
296
|
+
* {@link NAME_ID_GAP} to fit the compact header strip.
|
|
297
|
+
*/
|
|
298
|
+
const HEADER_NAME_ID_GAP = 12;
|
|
235
299
|
/** Container header: single-line name + id stacked in the header strip. */
|
|
236
300
|
export function layoutHeaderBlockText(opts) {
|
|
237
301
|
const marginX = opts.marginX ?? TEXT_MARGIN_X;
|
|
@@ -240,11 +304,13 @@ export function layoutHeaderBlockText(opts) {
|
|
|
240
304
|
const maxCharsId = maxCharsForInnerWidth(innerW, CHAR_W_ID);
|
|
241
305
|
const nameText = truncateLine(opts.name, maxCharsName);
|
|
242
306
|
const idText = truncateId(opts.id, maxCharsId);
|
|
243
|
-
const
|
|
244
|
-
const
|
|
307
|
+
const contentSpan = HEADER_NAME_ID_GAP + HALF_EXTENT_PRIMARY + HALF_EXTENT_ID;
|
|
308
|
+
const margin = Math.max(0, (opts.headerHeight - contentSpan) / 2);
|
|
309
|
+
const nameY = Math.round(opts.boxY + margin + HALF_EXTENT_PRIMARY);
|
|
310
|
+
const idY = nameY + HEADER_NAME_ID_GAP;
|
|
245
311
|
return [
|
|
246
|
-
{ cls: 'text-primary', text: nameText, y:
|
|
247
|
-
{ cls: 'text-id', text: idText, y:
|
|
312
|
+
{ cls: 'text-primary', text: nameText, y: nameY },
|
|
313
|
+
{ cls: 'text-id', text: idText, y: idY },
|
|
248
314
|
];
|
|
249
315
|
}
|
|
250
316
|
/**
|
package/package.json
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ACTION_NODE_SIZE,
|
|
5
|
+
BLOCKS_LEAF_SIZE,
|
|
6
|
+
CAPABILITY_MAP_NODE_SIZE,
|
|
7
|
+
DGCA_NODE_SIZE,
|
|
8
|
+
ENTITY_NODE_HEIGHT,
|
|
9
|
+
ENTITY_NODE_SIZE,
|
|
10
|
+
ENTITY_NODE_WIDTH,
|
|
11
|
+
GOALS_NODE_SIZE,
|
|
12
|
+
PROCESS_BLUEPRINT_SIZE,
|
|
13
|
+
resolveGoalsNodeSize,
|
|
14
|
+
} from '../node-size-presets.js';
|
|
15
|
+
|
|
16
|
+
describe('node-size-presets', () => {
|
|
17
|
+
it('uses shared width and height tiers for entity nodes', () => {
|
|
18
|
+
expect(ENTITY_NODE_WIDTH).toEqual({ compact: 200, normal: 250, wide: 320 });
|
|
19
|
+
expect(ENTITY_NODE_HEIGHT).toEqual({ compact: 72, normal: 80, wide: 96 });
|
|
20
|
+
expect(ENTITY_NODE_SIZE.normal).toEqual({ width: 250, height: 80 });
|
|
21
|
+
expect(ENTITY_NODE_SIZE.compact).toEqual({ width: 200, height: 72 });
|
|
22
|
+
expect(ENTITY_NODE_SIZE.wide).toEqual({ width: 320, height: 96 });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('exposes the same entity sizes for every box-based notation', () => {
|
|
26
|
+
for (const table of [GOALS_NODE_SIZE, DGCA_NODE_SIZE, BLOCKS_LEAF_SIZE, ACTION_NODE_SIZE]) {
|
|
27
|
+
expect(table).toBe(ENTITY_NODE_SIZE);
|
|
28
|
+
expect(table.normal).toEqual(ENTITY_NODE_SIZE.normal);
|
|
29
|
+
}
|
|
30
|
+
expect(CAPABILITY_MAP_NODE_SIZE.normal).toEqual({
|
|
31
|
+
nodeWidth: ENTITY_NODE_WIDTH.normal,
|
|
32
|
+
nodeHeight: ENTITY_NODE_HEIGHT.normal,
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('scales wide above normal and compact below', () => {
|
|
37
|
+
expect(resolveGoalsNodeSize('wide').height).toBeGreaterThan(resolveGoalsNodeSize('normal').height);
|
|
38
|
+
expect(resolveGoalsNodeSize('compact').height).toBeLessThan(resolveGoalsNodeSize('normal').height);
|
|
39
|
+
expect(resolveGoalsNodeSize('wide').width).toBeGreaterThan(resolveGoalsNodeSize('normal').width);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('scales process blueprint from the same tier ratios', () => {
|
|
43
|
+
expect(PROCESS_BLUEPRINT_SIZE.wide.stageColumnWidth).toBeGreaterThan(
|
|
44
|
+
PROCESS_BLUEPRINT_SIZE.normal.stageColumnWidth,
|
|
45
|
+
);
|
|
46
|
+
expect(PROCESS_BLUEPRINT_SIZE.compact.goalRowHeight).toBeLessThan(
|
|
47
|
+
PROCESS_BLUEPRINT_SIZE.normal.goalRowHeight,
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
});
|
package/src/activities/layout.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ActivityDoc, Activity, ActivitiesLayout, ActivitiesLayoutOptions, LayoutNode, LayoutEdge } from './types.js';
|
|
2
2
|
import { computeCpm } from './cpm.js';
|
|
3
3
|
|
|
4
|
-
const DEFAULT_NODE_W =
|
|
4
|
+
const DEFAULT_NODE_W = 250;
|
|
5
5
|
const DEFAULT_NODE_H = 80;
|
|
6
6
|
const H_GAP = 80;
|
|
7
7
|
const V_GAP = 24;
|
|
@@ -118,8 +118,8 @@ describe('layoutNestedBlocks', () => {
|
|
|
118
118
|
},
|
|
119
119
|
});
|
|
120
120
|
const a = layout.blocks[0];
|
|
121
|
-
expect(a.width).toBe(
|
|
122
|
-
expect(a.height).toBe(
|
|
121
|
+
expect(a.width).toBe(250);
|
|
122
|
+
expect(a.height).toBe(80);
|
|
123
123
|
});
|
|
124
124
|
|
|
125
125
|
it('honours custom layout options', () => {
|
package/src/blocks/layout.ts
CHANGED
|
@@ -5,12 +5,13 @@ import type {
|
|
|
5
5
|
BlocksLayoutOptions,
|
|
6
6
|
LaidOutBlock,
|
|
7
7
|
} from './types.js';
|
|
8
|
+
import { ENTITY_NODE_SIZE } from '../node-size-presets.js';
|
|
8
9
|
|
|
9
10
|
const DEFAULTS: Required<BlocksLayoutOptions> = {
|
|
10
|
-
leafWidth:
|
|
11
|
-
leafHeight:
|
|
11
|
+
leafWidth: ENTITY_NODE_SIZE.normal.width,
|
|
12
|
+
leafHeight: ENTITY_NODE_SIZE.normal.height,
|
|
12
13
|
padding: 12,
|
|
13
|
-
headerHeight:
|
|
14
|
+
headerHeight: 32,
|
|
14
15
|
childGap: 12,
|
|
15
16
|
topLevelGap: 24,
|
|
16
17
|
};
|
|
@@ -2,7 +2,7 @@ import type { CapabilityMap, LayoutOptions, CapabilityMapLayout, LaidOutNode, La
|
|
|
2
2
|
import { getLevel, getParentAddress } from './address.js';
|
|
3
3
|
|
|
4
4
|
const DEFAULT_NODE_WIDTH = 250;
|
|
5
|
-
const DEFAULT_NODE_HEIGHT =
|
|
5
|
+
const DEFAULT_NODE_HEIGHT = 80;
|
|
6
6
|
const DEFAULT_RANK_SEP = 80;
|
|
7
7
|
const DEFAULT_NODE_SEP = 24;
|
|
8
8
|
/** Width of the virtual organisation-root node — narrower than a capability
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CapabilityNode, CapabilityMapHeader } from './types.js';
|
|
2
2
|
|
|
3
3
|
export const TREE_NODE_WIDTH = 250;
|
|
4
|
-
export const TREE_NODE_HEIGHT =
|
|
4
|
+
export const TREE_NODE_HEIGHT = 80;
|
|
5
5
|
/** Gap between sibling columns (node width + rank sep = 330px, matching DSM). */
|
|
6
6
|
export const TREE_RANK_SEP = 80;
|
|
7
7
|
/** Vertical gap between sibling nodes. */
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// No React, no DOM, no vscode. Safe to call in Node.js or a test environment.
|
|
9
9
|
|
|
10
10
|
import type { Scope } from '../scope.js';
|
|
11
|
+
import { ENTITY_NODE_SIZE } from '../node-size-presets.js';
|
|
11
12
|
|
|
12
13
|
export type FGCAPreviewColumn = 'driver' | 'goal' | 'change' | 'activity';
|
|
13
14
|
|
|
@@ -125,8 +126,8 @@ export interface FGCAPreviewLayout {
|
|
|
125
126
|
|
|
126
127
|
// Default node + frame geometry. Inter-node gaps are user-configurable via
|
|
127
128
|
// Inter-node gaps are user-configurable via spacing settings; node size uses presets.
|
|
128
|
-
export const FGCA_NODE_W =
|
|
129
|
-
export const FGCA_NODE_H =
|
|
129
|
+
export const FGCA_NODE_W = ENTITY_NODE_SIZE.normal.width;
|
|
130
|
+
export const FGCA_NODE_H = ENTITY_NODE_SIZE.normal.height;
|
|
130
131
|
export const FGCA_HEADER_H = 32;
|
|
131
132
|
export const FGCA_PAD = 20;
|
|
132
133
|
export const FGCA_DEFAULT_COL_GAP = 160;
|