@transitrix/diagrams 1.7.6 → 1.8.2

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 (64) hide show
  1. package/dist/__tests__/node-size-presets.test.d.ts +1 -0
  2. package/dist/__tests__/node-size-presets.test.js +30 -0
  3. package/dist/activities/layout.js +7 -7
  4. package/dist/activities/types.d.ts +4 -0
  5. package/dist/blocks/__tests__/layout.test.js +2 -2
  6. package/dist/blocks/layout.js +3 -2
  7. package/dist/capability-map/dsm-layout.js +1 -1
  8. package/dist/capability-map/dsm-schema.d.ts +1 -1
  9. package/dist/capability-map/dsm-schema.js +1 -1
  10. package/dist/capability-map/layout-tree.d.ts +6 -2
  11. package/dist/capability-map/layout-tree.js +12 -10
  12. package/dist/capability-map/render-capability-tree.d.ts +2 -9
  13. package/dist/capability-map/render-capability-tree.js +16 -28
  14. package/dist/fgca/preview-layout.d.ts +10 -6
  15. package/dist/fgca/preview-layout.js +16 -15
  16. package/dist/node-size-presets.d.ts +63 -0
  17. package/dist/node-size-presets.js +117 -0
  18. package/dist/process-blueprint/layout.js +4 -47
  19. package/dist/webview/__tests__/entity-text-layout.test.d.ts +1 -0
  20. package/dist/webview/__tests__/entity-text-layout.test.js +60 -0
  21. package/dist/webview/__tests__/entry.test.js +3 -3
  22. package/dist/webview/__tests__/render-blocks.test.js +10 -13
  23. package/dist/webview/__tests__/render-goals.test.js +41 -0
  24. package/dist/webview/entity-text-layout.d.ts +95 -0
  25. package/dist/webview/entity-text-layout.js +277 -0
  26. package/dist/webview/entry.js +7 -3
  27. package/dist/webview/notation-style.d.ts +1 -1
  28. package/dist/webview/notation-style.js +1 -1
  29. package/dist/webview/render-activities.d.ts +3 -1
  30. package/dist/webview/render-activities.js +29 -32
  31. package/dist/webview/render-blocks.d.ts +4 -1
  32. package/dist/webview/render-blocks.js +27 -108
  33. package/dist/webview/render-fgca.d.ts +15 -8
  34. package/dist/webview/render-fgca.js +41 -44
  35. package/dist/webview/render-goals.d.ts +2 -0
  36. package/dist/webview/render-goals.js +21 -36
  37. package/dist/webview/render-process-blueprint.d.ts +4 -10
  38. package/dist/webview/render-process-blueprint.js +18 -64
  39. package/package.json +1 -1
  40. package/src/__tests__/node-size-presets.test.ts +50 -0
  41. package/src/activities/layout.ts +12 -7
  42. package/src/activities/types.ts +4 -0
  43. package/src/blocks/__tests__/layout.test.ts +2 -2
  44. package/src/blocks/layout.ts +3 -2
  45. package/src/capability-map/dsm-layout.ts +1 -1
  46. package/src/capability-map/dsm-schema.ts +1 -1
  47. package/src/capability-map/index.ts +1 -1
  48. package/src/capability-map/layout-tree.ts +17 -9
  49. package/src/capability-map/render-capability-tree.ts +18 -30
  50. package/src/fgca/preview-layout.ts +24 -17
  51. package/src/node-size-presets.ts +180 -0
  52. package/src/process-blueprint/layout.ts +4 -48
  53. package/src/webview/__tests__/entity-text-layout.test.ts +73 -0
  54. package/src/webview/__tests__/entry.test.ts +3 -3
  55. package/src/webview/__tests__/render-blocks.test.ts +11 -14
  56. package/src/webview/__tests__/render-goals.test.ts +42 -0
  57. package/src/webview/entity-text-layout.ts +355 -0
  58. package/src/webview/entry.ts +8 -4
  59. package/src/webview/notation-style.ts +1 -1
  60. package/src/webview/render-activities.ts +38 -27
  61. package/src/webview/render-blocks.ts +35 -118
  62. package/src/webview/render-fgca.ts +59 -44
  63. package/src/webview/render-goals.ts +22 -34
  64. package/src/webview/render-process-blueprint.ts +30 -65
@@ -14,30 +14,28 @@ import type {
14
14
  ComplianceChip,
15
15
  ProcessBlueprintFile,
16
16
  ProcessBlueprintLayout,
17
+ ProcessBlueprintLayoutOptions,
17
18
  } from '../process-blueprint/types.js';
19
+ import {
20
+ parseNodeSizePreset,
21
+ resolveProcessBlueprintSize,
22
+ type NodeSizePreset,
23
+ } from '../node-size-presets.js';
18
24
  import { generateSvgEmbedCss } from '../theme/index.js';
25
+ import {
26
+ layoutCenteredPillText,
27
+ layoutLeftCellLines,
28
+ maxCharsForInnerWidth,
29
+ truncateLine,
30
+ } from './entity-text-layout.js';
19
31
  import { escXml } from './render-util.js';
20
32
 
21
33
  const PAD = 24;
22
34
 
23
- // Blueprint-specific rules not carried by the shared theme CSS. The outer
24
- // border is drawn last on top of the clipped inner content; the row
25
- // backgrounds are transparent (pills/chips carry the colour). Mirrors
26
- // `BLUEPRINT_CSS` from the VS Code preview so the self-contained JCEF output
27
- // matches the editor.
28
35
  const BP_EMBED_CSS = `
29
36
  .bp-row-bg { fill: none; }
30
37
  .bp-border { fill: none; stroke: var(--ts-border, #cbd5e1); stroke-width: 1; }`;
31
38
 
32
- function truncate(text: string, maxChars: number): string {
33
- if (text.length <= maxChars) return text;
34
- return text.slice(0, Math.max(0, maxChars - 1)) + '…';
35
- }
36
-
37
- /**
38
- * Render a left-anchored, vertically centred multi-line text cell. `lines` are
39
- * pre-wrapped by the layout; the block is centred within the cell height.
40
- */
41
39
  function textCellSvg(
42
40
  lines: string[],
43
41
  cls: string,
@@ -46,25 +44,16 @@ function textCellSvg(
46
44
  cellHeight: number,
47
45
  lineHeight: number,
48
46
  ): string {
47
+ const specs = layoutLeftCellLines(lines, x, cellTop, cellHeight, lineHeight, cls);
49
48
  const ls = lines.length > 0 ? lines : [''];
50
49
  const first = cellTop + cellHeight / 2 - ((ls.length - 1) / 2) * lineHeight;
51
50
  const tspans = ls
52
51
  .map((ln, i) => `<tspan x="${x}" y="${first + i * lineHeight}">${escXml(ln)}</tspan>`)
53
52
  .join('');
53
+ void specs;
54
54
  return `<text class="${cls}" dominant-baseline="central">${tspans}</text>`;
55
55
  }
56
56
 
57
- /**
58
- * Render a single compliance law chip wrapped in a `<g data-chip-law…>` so the
59
- * VS Code host can wire click-to-drill-down. Other hosts (JCEF) simply ignore
60
- * the data attributes.
61
- *
62
- * Three orthogonal decorations are expressed as SVG overrides:
63
- * - `new` — dashed stroke border (`stroke-dasharray="4 2"`)
64
- * - `gap` — warning-level fill (`class="…compliance-gap"`)
65
- * - `deadline` — urgent fill override (`class="…compliance-deadline"`) + a small
66
- * badge circle in the top-right corner
67
- */
68
57
  function complianceChipSvg(
69
58
  chip: ComplianceChip,
70
59
  ox: number,
@@ -81,12 +70,13 @@ function complianceChipSvg(
81
70
  if (hasDeadline) rectClass += ' compliance-deadline';
82
71
  else if (hasGap) rectClass += ' compliance-gap';
83
72
  const strokeDash = hasNew ? ' stroke-dasharray="4 2"' : '';
73
+ const maxChars = maxCharsForInnerWidth(Math.max(0, width - 8), 8);
84
74
  const parts: string[] = [];
85
75
  parts.push(
86
76
  `<rect class="${rectClass}" x="${ax}" y="${ay}" width="${width}" height="${height}" rx="6"${strokeDash}/>`,
87
77
  );
88
78
  parts.push(
89
- `<text class="text-pill" x="${ax + width / 2}" y="${ay + height / 2}" text-anchor="middle" dominant-baseline="central">${escXml(truncate(lawId, Math.floor(width / 8)))}</text>`,
79
+ `<text class="text-pill" x="${ax + width / 2}" y="${ay + height / 2}" text-anchor="middle" dominant-baseline="central">${escXml(truncateLine(lawId, maxChars))}</text>`,
90
80
  );
91
81
  if (hasDeadline) {
92
82
  const br = 5;
@@ -100,15 +90,6 @@ function complianceChipSvg(
100
90
  return `<g data-chip-law="${escXml(lawId)}" data-chip-stage="${escXml(stageId)}">\n${parts.join('\n')}\n</g>`;
101
91
  }
102
92
 
103
- /**
104
- * Canonical Process Blueprint body — everything inside the `<svg>` except the
105
- * host-specific title block. Shared verbatim by the VS Code preview and the
106
- * host-neutral wrapper below so both surfaces stay pixel-identical.
107
- *
108
- * Layering: a clipped group holds the row fills and grid lines; pills and
109
- * compliance chips render on top (so grid lines sit under pill content); the
110
- * outer rounded border is drawn last so corner cells never overflow it.
111
- */
112
93
  export function renderProcessBlueprintBody(
113
94
  layout: ProcessBlueprintLayout,
114
95
  ox: number,
@@ -119,31 +100,26 @@ export function renderProcessBlueprintBody(
119
100
  const th = layout.bounds.height;
120
101
 
121
102
  const parts: string[] = [];
122
- // Pills and chips collected separately so they render after (on top of) grid lines.
123
103
  const topParts: string[] = [];
124
104
 
125
- // Outer background (fill only; border drawn last on top).
126
105
  parts.push(
127
106
  `<rect class="diagram-node level-0 bp-row-bg" x="${ox}" y="${oy}" width="${tw}" height="${th}" rx="6" stroke="none"/>`,
128
107
  );
129
108
 
130
- // ClipPath — clips all inner content to the outer rounded rect so corner cells
131
- // don't visually overflow the rx=6 boundary.
132
109
  parts.push(`<defs><clipPath id="${clipId}"><rect x="${ox}" y="${oy}" width="${tw}" height="${th}" rx="6"/></clipPath></defs>`);
133
110
 
134
111
  const inner: string[] = [];
112
+ const headerMaxChars = Math.max(8, Math.floor((layout.stageColumnWidth - 16) / 7));
135
113
 
136
- // Stage headers (top row).
137
114
  for (const s of layout.stageHeaders) {
138
115
  inner.push(
139
116
  `<rect class="diagram-node level-1" x="${s.x + ox}" y="${s.y + oy}" width="${s.width}" height="${s.height}" stroke="none"/>`,
140
117
  );
141
118
  inner.push(
142
- `<text class="text-header" x="${s.x + ox + s.width / 2}" y="${s.y + oy + s.height / 2}" text-anchor="middle" dominant-baseline="central">${escXml(truncate(s.name, 28))}</text>`,
119
+ `<text class="text-header" x="${s.x + ox + s.width / 2}" y="${s.y + oy + s.height / 2}" text-anchor="middle" dominant-baseline="central">${escXml(truncateLine(s.name, headerMaxChars))}</text>`,
143
120
  );
144
121
  }
145
122
 
146
- // Legend column (row labels).
147
123
  for (const l of layout.legend) {
148
124
  inner.push(
149
125
  `<rect class="diagram-node level-2 bp-row-bg" x="${ox}" y="${l.y + oy}" width="${layout.legendColumnWidth}" height="${l.height}" stroke="none"/>`,
@@ -153,7 +129,6 @@ export function renderProcessBlueprintBody(
153
129
  );
154
130
  }
155
131
 
156
- // Goal and result cells.
157
132
  const textX = layout.cellTextPadX;
158
133
  for (const c of layout.goalCells) {
159
134
  inner.push(
@@ -168,7 +143,6 @@ export function renderProcessBlueprintBody(
168
143
  inner.push(textCellSvg(c.lines, 'text-secondary', c.x + ox + textX, c.y + oy, c.height, layout.textLineHeight));
169
144
  }
170
145
 
171
- // Aspect rows — transparent background; pills carry the colour.
172
146
  for (let r = 0; r < layout.aspectRows.length; r++) {
173
147
  const row = layout.aspectRows[r];
174
148
  const level = 5 + (r % 3);
@@ -176,31 +150,22 @@ export function renderProcessBlueprintBody(
176
150
  `<rect class="diagram-node level-${level} bp-row-bg" x="${layout.legendColumnWidth + ox}" y="${row.y + oy}" width="${tw - layout.legendColumnWidth}" height="${row.height}" stroke="none"/>`,
177
151
  );
178
152
  for (const p of row.pills) {
179
- const cx = p.x + ox + p.width / 2;
180
- const pillLH = 15;
181
- // p.lines: name lines (possibly wrapped), optionally with id as last line.
182
153
  const hasIdLine = !!(p.id && p.lines.length > 0 && p.lines[p.lines.length - 1] === p.id);
183
154
  const nameLines = hasIdLine ? p.lines.slice(0, -1) : p.lines;
184
155
  const idLine = hasIdLine ? p.id : undefined;
185
- const pillFirstY = p.y + oy + p.height / 2 - ((p.lines.length - 1) / 2) * pillLH;
156
+ const pillSpecs = layoutCenteredPillText(p.x + ox, p.y + oy, p.width, p.height, nameLines, idLine);
186
157
  topParts.push(
187
158
  `<rect class="diagram-node level-${level}" x="${p.x + ox}" y="${p.y + oy}" width="${p.width}" height="${p.height}" rx="6"/>`,
188
159
  );
189
- if (nameLines.length > 0) {
190
- const tspans = nameLines
191
- .map((ln, i) => `<tspan x="${cx}" y="${pillFirstY + i * pillLH}">${escXml(ln)}</tspan>`)
192
- .join('');
193
- topParts.push(`<text class="text-pill" text-anchor="middle" dominant-baseline="central">${tspans}</text>`);
194
- }
195
- if (idLine) {
160
+ for (const spec of pillSpecs) {
161
+ const anchor = spec.cls === 'text-pill' || spec.cls === 'text-secondary' ? ' text-anchor="middle"' : '';
196
162
  topParts.push(
197
- `<text class="text-secondary" x="${cx}" y="${pillFirstY + nameLines.length * pillLH}" text-anchor="middle" dominant-baseline="central">${escXml(idLine)}</text>`,
163
+ `<text class="${spec.cls}" x="${spec.x}" y="${spec.y}"${anchor} dominant-baseline="central">${escXml(spec.text)}</text>`,
198
164
  );
199
165
  }
200
166
  }
201
167
  }
202
168
 
203
- // Compliance row (optional).
204
169
  if (layout.complianceRow) {
205
170
  const row = layout.complianceRow;
206
171
  inner.push(
@@ -212,7 +177,6 @@ export function renderProcessBlueprintBody(
212
177
  }
213
178
  }
214
179
 
215
- // Grid lines — drawn before pills so they appear under pill content.
216
180
  const gridX1 = ox;
217
181
  const gridX2 = ox + tw;
218
182
  const gridY1 = oy;
@@ -232,10 +196,7 @@ export function renderProcessBlueprintBody(
232
196
  inner.push(`<line class="diagram-edge" x1="${stageLineX}" y1="${gridY1}" x2="${stageLineX}" y2="${gridY2}"/>`);
233
197
  }
234
198
 
235
- // Inner content (fills + grid lines) clipped to rounded outer rect.
236
199
  parts.push(`<g clip-path="url(#${clipId})">${inner.join('\n')}${topParts.join('\n')}</g>`);
237
-
238
- // Outer border on top — fill="none" as attribute so PNG export (no external CSS) never shows black.
239
200
  parts.push(`<rect class="bp-border" x="${ox}" y="${oy}" width="${tw}" height="${th}" rx="6" fill="none"/>`);
240
201
 
241
202
  return parts.join('\n');
@@ -243,15 +204,21 @@ export function renderProcessBlueprintBody(
243
204
 
244
205
  export interface RenderProcessBlueprintOptions {
245
206
  title?: string;
207
+ nodeSizePreset?: NodeSizePreset;
208
+ layoutOptions?: ProcessBlueprintLayoutOptions;
246
209
  }
247
210
 
248
211
  export function renderProcessBlueprintSvg(
249
212
  doc: ProcessBlueprintFile,
250
213
  options: RenderProcessBlueprintOptions = {},
251
214
  ): string {
252
- const { title = '' } = options;
215
+ const { title = '', nodeSizePreset = 'normal', layoutOptions } = options;
216
+ const sizing = resolveProcessBlueprintSize(parseNodeSizePreset(nodeSizePreset));
253
217
 
254
- const layout: ProcessBlueprintLayout = layoutProcessBlueprint(doc);
218
+ const layout: ProcessBlueprintLayout = layoutProcessBlueprint(doc, {
219
+ ...sizing,
220
+ ...layoutOptions,
221
+ });
255
222
 
256
223
  if (layout.bounds.width === 0 || layout.bounds.height === 0) {
257
224
  return `<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 0 0"></svg>`;
@@ -269,8 +236,6 @@ export function renderProcessBlueprintSvg(
269
236
  ? `<text class="text-header" x="${PAD}" y="${PAD - 6}">${escXml(title)}</text>`
270
237
  : '';
271
238
 
272
- // Embed the shared theme CSS plus the blueprint-specific rules inside the SVG
273
- // so the rendered output is self-contained for the JCEF host.
274
239
  const embedCss = generateSvgEmbedCss('transitrix') + BP_EMBED_CSS;
275
240
 
276
241
  return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">