@diagrammo/dgmo 0.8.19 → 0.8.21
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/cli.cjs +92 -131
- package/dist/editor.cjs +13 -1
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.js +13 -1
- package/dist/editor.js.map +1 -1
- package/dist/highlight.cjs +13 -1
- package/dist/highlight.cjs.map +1 -1
- package/dist/highlight.js +13 -1
- package/dist/highlight.js.map +1 -1
- package/dist/index.cjs +4524 -1511
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +427 -186
- package/dist/index.d.ts +427 -186
- package/dist/index.js +4526 -1503
- package/dist/index.js.map +1 -1
- package/docs/guide/chart-mindmap.md +198 -0
- package/docs/guide/chart-sequence.md +23 -1
- package/docs/guide/chart-wireframe.md +100 -0
- package/docs/guide/index.md +8 -0
- package/docs/language-reference.md +210 -2
- package/package.json +22 -9
- package/src/boxes-and-lines/collapse.ts +21 -3
- package/src/boxes-and-lines/layout.ts +51 -9
- package/src/boxes-and-lines/parser.ts +16 -4
- package/src/boxes-and-lines/renderer.ts +121 -23
- package/src/boxes-and-lines/types.ts +1 -0
- package/src/c4/parser.ts +8 -7
- package/src/class/parser.ts +6 -0
- package/src/cli.ts +1 -9
- package/src/completion.ts +26 -0
- package/src/d3.ts +169 -266
- package/src/dgmo-router.ts +103 -5
- package/src/diagnostics.ts +16 -6
- package/src/echarts.ts +43 -10
- package/src/editor/keywords.ts +12 -0
- package/src/er/parser.ts +22 -2
- package/src/gantt/renderer.ts +2 -2
- package/src/graph/flowchart-parser.ts +89 -52
- package/src/graph/layout.ts +73 -9
- package/src/graph/state-collapse.ts +78 -0
- package/src/graph/state-parser.ts +60 -35
- package/src/graph/state-renderer.ts +139 -34
- package/src/index.ts +41 -16
- package/src/infra/parser.ts +9 -2
- package/src/kanban/renderer.ts +305 -59
- package/src/mindmap/collapse.ts +88 -0
- package/src/mindmap/layout.ts +605 -0
- package/src/mindmap/parser.ts +379 -0
- package/src/mindmap/renderer.ts +543 -0
- package/src/mindmap/text-wrap.ts +207 -0
- package/src/mindmap/types.ts +55 -0
- package/src/palettes/color-utils.ts +4 -12
- package/src/palettes/index.ts +0 -4
- package/src/render.ts +31 -20
- package/src/sequence/parser.ts +7 -2
- package/src/sequence/renderer.ts +141 -21
- package/src/sharing.ts +2 -0
- package/src/sitemap/layout.ts +35 -12
- package/src/sitemap/renderer.ts +1 -6
- package/src/utils/arrows.ts +180 -11
- package/src/utils/d3-types.ts +4 -0
- package/src/utils/export-container.ts +3 -2
- package/src/utils/legend-constants.ts +0 -4
- package/src/utils/legend-d3.ts +1 -0
- package/src/utils/legend-layout.ts +2 -2
- package/src/utils/parsing.ts +2 -0
- package/src/utils/time-ticks.ts +213 -0
- package/src/wireframe/layout.ts +460 -0
- package/src/wireframe/parser.ts +956 -0
- package/src/wireframe/renderer.ts +1293 -0
- package/src/wireframe/types.ts +110 -0
- package/src/branding.ts +0 -67
- package/src/dgmo-mermaid.ts +0 -262
- package/src/palettes/mermaid-bridge.ts +0 -220
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Mindmap Text Wrapping
|
|
3
|
+
// ============================================================
|
|
4
|
+
//
|
|
5
|
+
// Shared logic for wrapping node labels and descriptions into
|
|
6
|
+
// multiple lines. Used by both layout (for sizing) and renderer
|
|
7
|
+
// (for drawing). Ensures both agree on line breaks and font size.
|
|
8
|
+
|
|
9
|
+
const CHAR_WIDTH_RATIO = 0.58; // avg char width / fontSize for Helvetica
|
|
10
|
+
const H_PAD = 16; // 8px padding each side
|
|
11
|
+
const MAX_LABEL_LINES = 3;
|
|
12
|
+
const MAX_DESC_LINES = 2;
|
|
13
|
+
|
|
14
|
+
/** Split text into tokens, keeping hyphens attached to the left word. */
|
|
15
|
+
function tokenize(text: string): string[] {
|
|
16
|
+
const tokens: string[] = [];
|
|
17
|
+
// Split on spaces, and after hyphens (keep hyphen with left token)
|
|
18
|
+
const parts = text.split(/(\s+)/);
|
|
19
|
+
for (const part of parts) {
|
|
20
|
+
if (/^\s+$/.test(part)) continue; // skip whitespace tokens
|
|
21
|
+
// Further split on hyphens: "well-known" → ["well-", "known"]
|
|
22
|
+
const hyphenParts = part.split(/(?<=-)(?=\S)/);
|
|
23
|
+
tokens.push(...hyphenParts);
|
|
24
|
+
}
|
|
25
|
+
return tokens;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Wrap text into lines that fit within maxWidth at the given fontSize.
|
|
30
|
+
* Returns null if the text doesn't fit within maxLines.
|
|
31
|
+
*/
|
|
32
|
+
function tryWrap(
|
|
33
|
+
tokens: string[],
|
|
34
|
+
maxWidth: number,
|
|
35
|
+
fontSize: number,
|
|
36
|
+
maxLines: number
|
|
37
|
+
): string[] | null {
|
|
38
|
+
const availWidth = maxWidth - H_PAD;
|
|
39
|
+
const charWidth = fontSize * CHAR_WIDTH_RATIO;
|
|
40
|
+
const maxChars = Math.max(1, Math.floor(availWidth / charWidth));
|
|
41
|
+
|
|
42
|
+
const lines: string[] = [];
|
|
43
|
+
let currentLine = '';
|
|
44
|
+
|
|
45
|
+
for (const token of tokens) {
|
|
46
|
+
// After a hyphen-ending token, don't add a space
|
|
47
|
+
const sep = currentLine && !currentLine.endsWith('-') ? ' ' : '';
|
|
48
|
+
const candidate = currentLine + sep + token;
|
|
49
|
+
|
|
50
|
+
if (candidate.length <= maxChars) {
|
|
51
|
+
currentLine = candidate;
|
|
52
|
+
} else if (!currentLine) {
|
|
53
|
+
// Single token exceeds line — force it onto this line (will be truncated later if needed)
|
|
54
|
+
currentLine = token;
|
|
55
|
+
} else {
|
|
56
|
+
// Push current line, start new one
|
|
57
|
+
lines.push(currentLine);
|
|
58
|
+
if (lines.length >= maxLines) {
|
|
59
|
+
// Can't fit — return null to signal overflow
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
currentLine = token;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (currentLine) {
|
|
66
|
+
lines.push(currentLine);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (lines.length > maxLines) return null;
|
|
70
|
+
return lines;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Truncate the last line of a lines array with ellipsis to fit maxChars. */
|
|
74
|
+
function truncateLastLine(
|
|
75
|
+
lines: string[],
|
|
76
|
+
maxWidth: number,
|
|
77
|
+
fontSize: number
|
|
78
|
+
): string[] {
|
|
79
|
+
const availWidth = maxWidth - H_PAD;
|
|
80
|
+
const charWidth = fontSize * CHAR_WIDTH_RATIO;
|
|
81
|
+
const maxChars = Math.max(1, Math.floor(availWidth / charWidth));
|
|
82
|
+
|
|
83
|
+
const result = [...lines];
|
|
84
|
+
const last = result[result.length - 1];
|
|
85
|
+
if (last.length > maxChars) {
|
|
86
|
+
result[result.length - 1] = last.substring(0, maxChars - 1) + '\u2026';
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface WrapResult {
|
|
92
|
+
lines: string[];
|
|
93
|
+
fontSize: number;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Wrap text to fit within a node of the given maxWidth.
|
|
98
|
+
* Tries wrapping at baseFontSize first. If text doesn't fit within
|
|
99
|
+
* maxLines, reduces font size by 1px at a time down to minFontSize.
|
|
100
|
+
* As a last resort, truncates the final line with ellipsis.
|
|
101
|
+
*/
|
|
102
|
+
export function wrapText(
|
|
103
|
+
text: string,
|
|
104
|
+
maxWidth: number,
|
|
105
|
+
baseFontSize: number,
|
|
106
|
+
minFontSize: number,
|
|
107
|
+
maxLines: number = MAX_LABEL_LINES
|
|
108
|
+
): WrapResult {
|
|
109
|
+
if (!text) return { lines: [''], fontSize: baseFontSize };
|
|
110
|
+
|
|
111
|
+
const tokens = tokenize(text);
|
|
112
|
+
|
|
113
|
+
// Try at each font size from base down to min
|
|
114
|
+
for (let fs = baseFontSize; fs >= minFontSize; fs--) {
|
|
115
|
+
const lines = tryWrap(tokens, maxWidth, fs, maxLines);
|
|
116
|
+
if (lines) {
|
|
117
|
+
// Ensure each line fits (truncate overly long single tokens)
|
|
118
|
+
return { lines: truncateLastLine(lines, maxWidth, fs), fontSize: fs };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Last resort: wrap at minFontSize with unlimited lines, then take first maxLines
|
|
123
|
+
const allLines = tryWrap(tokens, maxWidth, minFontSize, 999) ?? [text];
|
|
124
|
+
const capped = allLines.slice(0, maxLines);
|
|
125
|
+
const truncated = truncateLastLine(capped, maxWidth, minFontSize);
|
|
126
|
+
// If we dropped lines, append ellipsis to indicate overflow
|
|
127
|
+
if (allLines.length > maxLines) {
|
|
128
|
+
const last = truncated[truncated.length - 1];
|
|
129
|
+
if (!last.endsWith('\u2026')) {
|
|
130
|
+
const availWidth = maxWidth - H_PAD;
|
|
131
|
+
const charWidth = minFontSize * CHAR_WIDTH_RATIO;
|
|
132
|
+
const maxChars = Math.max(1, Math.floor(availWidth / charWidth));
|
|
133
|
+
if (last.length >= maxChars - 1) {
|
|
134
|
+
truncated[truncated.length - 1] =
|
|
135
|
+
last.substring(0, maxChars - 1) + '\u2026';
|
|
136
|
+
} else {
|
|
137
|
+
truncated[truncated.length - 1] = last + '\u2026';
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
lines: truncated,
|
|
143
|
+
fontSize: minFontSize,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ============================================================
|
|
148
|
+
// Compute full node text layout (shared between layout + renderer)
|
|
149
|
+
// ============================================================
|
|
150
|
+
|
|
151
|
+
const ROOT_FONT_SIZE = 17;
|
|
152
|
+
const MIN_FONT_SIZE = 9;
|
|
153
|
+
const FONT_STEP = 3;
|
|
154
|
+
const DESC_FONT_SIZE = 10;
|
|
155
|
+
|
|
156
|
+
function labelFontSize(depth: number): number {
|
|
157
|
+
return Math.max(MIN_FONT_SIZE, ROOT_FONT_SIZE - depth * FONT_STEP);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
interface NodeTextLayout {
|
|
161
|
+
labelLines: string[];
|
|
162
|
+
labelFontSize: number;
|
|
163
|
+
descLines: string[];
|
|
164
|
+
descFontSize: number;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Compute wrapped text layout for a mindmap node.
|
|
169
|
+
* Called by both layout (for height) and renderer (for drawing).
|
|
170
|
+
*/
|
|
171
|
+
export function computeNodeText(
|
|
172
|
+
label: string,
|
|
173
|
+
description: string | undefined,
|
|
174
|
+
depth: number,
|
|
175
|
+
nodeWidth: number,
|
|
176
|
+
hideDescriptions: boolean
|
|
177
|
+
): NodeTextLayout {
|
|
178
|
+
const baseFontSize = labelFontSize(depth);
|
|
179
|
+
const labelResult = wrapText(
|
|
180
|
+
label,
|
|
181
|
+
nodeWidth,
|
|
182
|
+
baseFontSize,
|
|
183
|
+
MIN_FONT_SIZE,
|
|
184
|
+
MAX_LABEL_LINES
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
let descLines: string[] = [];
|
|
188
|
+
let descFontSize = DESC_FONT_SIZE;
|
|
189
|
+
if (!hideDescriptions && description) {
|
|
190
|
+
const descResult = wrapText(
|
|
191
|
+
description,
|
|
192
|
+
nodeWidth,
|
|
193
|
+
DESC_FONT_SIZE,
|
|
194
|
+
DESC_FONT_SIZE, // don't shrink descriptions
|
|
195
|
+
MAX_DESC_LINES
|
|
196
|
+
);
|
|
197
|
+
descLines = descResult.lines;
|
|
198
|
+
descFontSize = descResult.fontSize;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
labelLines: labelResult.lines,
|
|
203
|
+
labelFontSize: labelResult.fontSize,
|
|
204
|
+
descLines,
|
|
205
|
+
descFontSize,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { DgmoError } from '../diagnostics.js';
|
|
2
|
+
import type { TagGroup } from '../utils/tag-groups.js';
|
|
3
|
+
|
|
4
|
+
export interface MindmapNode {
|
|
5
|
+
id: string;
|
|
6
|
+
label: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
metadata: Record<string, string>;
|
|
9
|
+
children: MindmapNode[];
|
|
10
|
+
parentId: string | null;
|
|
11
|
+
lineNumber: number;
|
|
12
|
+
color?: string;
|
|
13
|
+
collapsed?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ParsedMindmap {
|
|
17
|
+
title: string | null;
|
|
18
|
+
titleLineNumber: number | null;
|
|
19
|
+
roots: MindmapNode[];
|
|
20
|
+
tagGroups: TagGroup[];
|
|
21
|
+
options: Record<string, string>;
|
|
22
|
+
diagnostics: DgmoError[];
|
|
23
|
+
error: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface MindmapLayoutNode {
|
|
27
|
+
id: string;
|
|
28
|
+
label: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
metadata: Record<string, string>;
|
|
31
|
+
lineNumber: number;
|
|
32
|
+
color?: string;
|
|
33
|
+
x: number;
|
|
34
|
+
y: number;
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
depth: number;
|
|
38
|
+
angle: number;
|
|
39
|
+
radius: number;
|
|
40
|
+
hiddenCount?: number;
|
|
41
|
+
hasChildren?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface MindmapLayoutEdge {
|
|
45
|
+
sourceId: string;
|
|
46
|
+
targetId: string;
|
|
47
|
+
path: string; // SVG path d attribute
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface MindmapLayoutResult {
|
|
51
|
+
nodes: MindmapLayoutNode[];
|
|
52
|
+
edges: MindmapLayoutEdge[];
|
|
53
|
+
width: number;
|
|
54
|
+
height: number;
|
|
55
|
+
}
|
|
@@ -84,17 +84,6 @@ export function hexToHSLString(hex: string): string {
|
|
|
84
84
|
// Color Manipulation
|
|
85
85
|
// ============================================================
|
|
86
86
|
|
|
87
|
-
/**
|
|
88
|
-
* Derive a muted (desaturated, darkened) variant of a color.
|
|
89
|
-
* Used by the Mermaid theme generator for dark-mode fills.
|
|
90
|
-
*
|
|
91
|
-
* Algorithm: cap saturation at 35% and lightness at 36%.
|
|
92
|
-
*/
|
|
93
|
-
export function mute(hex: string): string {
|
|
94
|
-
const { h, s, l } = hexToHSL(hex);
|
|
95
|
-
return hslToHex(h, Math.min(s, 35), Math.min(l, 36));
|
|
96
|
-
}
|
|
97
|
-
|
|
98
87
|
/**
|
|
99
88
|
* Blend a color toward white (light mode quadrant fills).
|
|
100
89
|
* amount: 0 = original, 1 = white
|
|
@@ -232,7 +221,10 @@ export function getSeriesColors(palette: PaletteColors): string[] {
|
|
|
232
221
|
* saturation and lightness, guaranteeing every segment gets a unique,
|
|
233
222
|
* perceptually distinct color regardless of segment count.
|
|
234
223
|
*/
|
|
235
|
-
export function getSegmentColors(
|
|
224
|
+
export function getSegmentColors(
|
|
225
|
+
palette: PaletteColors,
|
|
226
|
+
count: number
|
|
227
|
+
): string[] {
|
|
236
228
|
const base = getSeriesColors(palette);
|
|
237
229
|
const unique = [...new Set(base)];
|
|
238
230
|
const hsls = unique.map(hexToHSL);
|
package/src/palettes/index.ts
CHANGED
|
@@ -14,7 +14,6 @@ export {
|
|
|
14
14
|
hexToHSL,
|
|
15
15
|
hslToHex,
|
|
16
16
|
hexToHSLString,
|
|
17
|
-
mute,
|
|
18
17
|
tint,
|
|
19
18
|
shade,
|
|
20
19
|
getSeriesColors,
|
|
@@ -34,6 +33,3 @@ export { tokyoNightPalette } from './tokyo-night';
|
|
|
34
33
|
|
|
35
34
|
export { draculaPalette } from './dracula';
|
|
36
35
|
export { monokaiPalette } from './monokai';
|
|
37
|
-
|
|
38
|
-
// Re-export Mermaid bridge
|
|
39
|
-
export { buildMermaidThemeVars, buildThemeCSS } from './mermaid-bridge';
|
package/src/render.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { renderForExport } from './d3';
|
|
2
2
|
import { renderExtendedChartForExport } from './echarts';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
parseDgmoChartType,
|
|
5
|
+
getRenderCategory,
|
|
6
|
+
parseDgmo,
|
|
7
|
+
} from './dgmo-router';
|
|
8
|
+
import type { DgmoError } from './diagnostics';
|
|
4
9
|
import { getPalette } from './palettes/registry';
|
|
10
|
+
import type { CompactViewState } from './sharing';
|
|
5
11
|
|
|
6
12
|
/**
|
|
7
13
|
* Ensures DOM globals are available for D3 renderers.
|
|
@@ -45,13 +51,13 @@ async function ensureDom(): Promise<void> {
|
|
|
45
51
|
*
|
|
46
52
|
* @param content - DGMO source text
|
|
47
53
|
* @param options - Optional theme and palette settings
|
|
48
|
-
* @returns SVG string,
|
|
54
|
+
* @returns Object with `svg` (SVG string, empty on error) and `diagnostics` (parse errors/warnings)
|
|
49
55
|
*
|
|
50
56
|
* @example
|
|
51
57
|
* ```ts
|
|
52
58
|
* import { render } from '@diagrammo/dgmo';
|
|
53
59
|
*
|
|
54
|
-
* const svg = await render(`pie Languages
|
|
60
|
+
* const { svg, diagnostics } = await render(`pie Languages
|
|
55
61
|
* TypeScript: 45
|
|
56
62
|
* Python: 30
|
|
57
63
|
* Rust: 25`);
|
|
@@ -62,48 +68,53 @@ export async function render(
|
|
|
62
68
|
options?: {
|
|
63
69
|
theme?: 'light' | 'dark' | 'transparent';
|
|
64
70
|
palette?: string;
|
|
65
|
-
branding?: boolean;
|
|
66
71
|
c4Level?: 'context' | 'containers' | 'components' | 'deployment';
|
|
67
72
|
c4System?: string;
|
|
68
73
|
c4Container?: string;
|
|
69
74
|
tagGroup?: string;
|
|
70
75
|
/** Legend state for export — controls which tag group is shown in exported SVG. */
|
|
71
76
|
legendState?: { activeGroup?: string; hiddenAttributes?: string[] };
|
|
77
|
+
/** View state for export — controls interactive state (collapse, swimlanes, etc.) */
|
|
78
|
+
viewState?: CompactViewState;
|
|
72
79
|
}
|
|
73
|
-
): Promise<string> {
|
|
80
|
+
): Promise<{ svg: string; diagnostics: DgmoError[] }> {
|
|
74
81
|
const theme = options?.theme ?? 'light';
|
|
75
82
|
const paletteName = options?.palette ?? 'nord';
|
|
76
|
-
const branding = options?.branding ?? false;
|
|
77
83
|
|
|
78
84
|
const paletteColors =
|
|
79
85
|
getPalette(paletteName)[theme === 'dark' ? 'dark' : 'light'];
|
|
80
86
|
|
|
87
|
+
const { diagnostics } = parseDgmo(content);
|
|
88
|
+
|
|
81
89
|
const chartType = parseDgmoChartType(content);
|
|
82
90
|
const category = chartType ? getRenderCategory(chartType) : null;
|
|
83
91
|
|
|
84
|
-
// Build
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
:
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
// Build viewState from legendState (backwards compat) or use provided viewState
|
|
93
|
+
const viewState: CompactViewState | undefined =
|
|
94
|
+
options?.viewState ??
|
|
95
|
+
(options?.legendState
|
|
96
|
+
? {
|
|
97
|
+
tag: options.legendState.activeGroup ?? undefined,
|
|
98
|
+
ha: options.legendState.hiddenAttributes,
|
|
99
|
+
}
|
|
100
|
+
: undefined);
|
|
93
101
|
|
|
94
102
|
if (category === 'data-chart') {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
103
|
+
const svg = await renderExtendedChartForExport(
|
|
104
|
+
content,
|
|
105
|
+
theme,
|
|
106
|
+
paletteColors
|
|
107
|
+
);
|
|
108
|
+
return { svg, diagnostics };
|
|
98
109
|
}
|
|
99
110
|
|
|
100
111
|
// Visualization/diagram and unknown/null types all go through the unified renderer
|
|
101
112
|
await ensureDom();
|
|
102
|
-
|
|
103
|
-
branding,
|
|
113
|
+
const svg = await renderForExport(content, theme, paletteColors, viewState, {
|
|
104
114
|
c4Level: options?.c4Level,
|
|
105
115
|
c4System: options?.c4System,
|
|
106
116
|
c4Container: options?.c4Container,
|
|
107
117
|
tagGroup: options?.tagGroup,
|
|
108
118
|
});
|
|
119
|
+
return { svg, diagnostics };
|
|
109
120
|
}
|
package/src/sequence/parser.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { inferParticipantType } from './participant-inference';
|
|
6
6
|
import type { DgmoError } from '../diagnostics';
|
|
7
7
|
import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
|
|
8
|
-
import { parseArrow } from '../utils/arrows';
|
|
8
|
+
import { parseArrow, parseInArrowLabel } from '../utils/arrows';
|
|
9
9
|
import {
|
|
10
10
|
measureIndent,
|
|
11
11
|
extractColor,
|
|
@@ -945,9 +945,14 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
945
945
|
}
|
|
946
946
|
if (labeledArrow) {
|
|
947
947
|
contentStarted = true;
|
|
948
|
-
const { from, to, label, async: isAsync } = labeledArrow;
|
|
948
|
+
const { from, to, label: rawLabel, async: isAsync } = labeledArrow;
|
|
949
949
|
lastMsgFrom = from;
|
|
950
950
|
|
|
951
|
+
// TD-13/TD-14: validate in-arrow label characters
|
|
952
|
+
const labelResult = parseInArrowLabel(rawLabel, lineNumber);
|
|
953
|
+
labelResult.diagnostics.forEach((d) => result.diagnostics.push(d));
|
|
954
|
+
const label = labelResult.label ?? rawLabel;
|
|
955
|
+
|
|
951
956
|
const msg: SequenceMessage = {
|
|
952
957
|
from,
|
|
953
958
|
to,
|