@diagrammo/dgmo 0.8.18 → 0.8.20

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 (42) hide show
  1. package/dist/cli.cjs +89 -130
  2. package/dist/index.cjs +1202 -993
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +216 -114
  5. package/dist/index.d.ts +216 -114
  6. package/dist/index.js +1211 -985
  7. package/dist/index.js.map +1 -1
  8. package/docs/language-reference.md +73 -0
  9. package/package.json +22 -9
  10. package/src/boxes-and-lines/parser.ts +8 -3
  11. package/src/c4/parser.ts +8 -7
  12. package/src/class/parser.ts +6 -0
  13. package/src/cli.ts +1 -9
  14. package/src/d3.ts +16 -234
  15. package/src/dgmo-router.ts +97 -5
  16. package/src/diagnostics.ts +16 -6
  17. package/src/echarts.ts +43 -10
  18. package/src/er/parser.ts +22 -2
  19. package/src/gantt/renderer.ts +153 -91
  20. package/src/graph/flowchart-parser.ts +89 -52
  21. package/src/graph/state-parser.ts +60 -35
  22. package/src/index.ts +23 -18
  23. package/src/infra/parser.ts +9 -2
  24. package/src/kanban/renderer.ts +2 -2
  25. package/src/palettes/color-utils.ts +4 -12
  26. package/src/palettes/index.ts +0 -4
  27. package/src/render.ts +30 -16
  28. package/src/sequence/collapse.ts +169 -0
  29. package/src/sequence/parser.ts +21 -4
  30. package/src/sequence/renderer.ts +198 -52
  31. package/src/sharing.ts +86 -49
  32. package/src/sitemap/renderer.ts +1 -6
  33. package/src/utils/arrows.ts +180 -11
  34. package/src/utils/d3-types.ts +4 -0
  35. package/src/utils/legend-constants.ts +11 -4
  36. package/src/utils/legend-d3.ts +171 -0
  37. package/src/utils/legend-layout.ts +140 -13
  38. package/src/utils/legend-types.ts +45 -0
  39. package/src/utils/time-ticks.ts +213 -0
  40. package/src/branding.ts +0 -67
  41. package/src/dgmo-mermaid.ts +0 -262
  42. package/src/palettes/mermaid-bridge.ts +0 -220
@@ -1,262 +0,0 @@
1
- // ============================================================
2
- // .dgmo → Mermaid Translation Layer
3
- // Parses dgmo quadrant syntax and generates valid Mermaid code.
4
- // ============================================================
5
-
6
- import { resolveColorWithDiagnostic } from './colors';
7
- import type { DgmoError } from './diagnostics';
8
- import { makeDgmoError, formatDgmoError } from './diagnostics';
9
-
10
- // ============================================================
11
- // Types
12
- // ============================================================
13
-
14
- interface QuadrantLabel {
15
- text: string;
16
- color: string | null;
17
- lineNumber: number;
18
- }
19
-
20
- export interface ParsedQuadrant {
21
- title: string | null;
22
- titleLineNumber: number | null;
23
- xAxis: [string, string] | null;
24
- xAxisLineNumber: number | null;
25
- yAxis: [string, string] | null;
26
- yAxisLineNumber: number | null;
27
- quadrants: {
28
- topRight: QuadrantLabel | null;
29
- topLeft: QuadrantLabel | null;
30
- bottomLeft: QuadrantLabel | null;
31
- bottomRight: QuadrantLabel | null;
32
- };
33
- points: { label: string; x: number; y: number; lineNumber: number }[];
34
- diagnostics: DgmoError[];
35
- error: string | null;
36
- }
37
-
38
- // ============================================================
39
- // Parser
40
- // ============================================================
41
-
42
- /** Regex for quadrant label lines: `top-right Promote (green)` */
43
- const QUADRANT_LABEL_RE = /^(.+?)(?:\s*\(([^)]+)\))?\s*$/;
44
-
45
- /** Regex for data point lines: `Label 0.9, 0.5` */
46
- const DATA_POINT_RE = /^(.+?)\s+([0-9]*\.?[0-9]+)\s*,\s*([0-9]*\.?[0-9]+)\s*$/;
47
-
48
- const QUADRANT_POSITIONS = new Set([
49
- 'top-right',
50
- 'top-left',
51
- 'bottom-left',
52
- 'bottom-right',
53
- ]);
54
-
55
- /**
56
- * Parses a .dgmo quadrant document into a structured object.
57
- * Lines are processed sequentially; unknown lines are silently skipped.
58
- */
59
- export function parseQuadrant(content: string): ParsedQuadrant {
60
- const result: ParsedQuadrant = {
61
- title: null,
62
- titleLineNumber: null,
63
- xAxis: null,
64
- xAxisLineNumber: null,
65
- yAxis: null,
66
- yAxisLineNumber: null,
67
- quadrants: {
68
- topRight: null,
69
- topLeft: null,
70
- bottomLeft: null,
71
- bottomRight: null,
72
- },
73
- points: [],
74
- diagnostics: [],
75
- error: null,
76
- };
77
-
78
- const lines = content.split('\n');
79
-
80
- for (let i = 0; i < lines.length; i++) {
81
- const line = lines[i].trim();
82
- const lineNumber = i + 1; // 1-indexed for editor
83
-
84
- // Skip empty lines and comments
85
- if (!line || line.startsWith('//')) continue;
86
-
87
- // Skip the chart: directive (already consumed by router)
88
- if (/^chart\s*:/i.test(line)) continue;
89
-
90
- // title <text>
91
- const titleMatch = line.match(/^title\s+(.+)/i);
92
- if (titleMatch) {
93
- result.title = titleMatch[1].trim();
94
- result.titleLineNumber = lineNumber;
95
- continue;
96
- }
97
-
98
- // x-label Low, High
99
- const xMatch = line.match(/^x-label\s+(.+)/i);
100
- if (xMatch) {
101
- const parts = xMatch[1].split(',').map((s) => s.trim());
102
- if (parts.length >= 2) {
103
- result.xAxis = [parts[0], parts[1]];
104
- result.xAxisLineNumber = lineNumber;
105
- }
106
- continue;
107
- }
108
-
109
- // y-label Low, High
110
- const yMatch = line.match(/^y-label\s+(.+)/i);
111
- if (yMatch) {
112
- const parts = yMatch[1].split(',').map((s) => s.trim());
113
- if (parts.length >= 2) {
114
- result.yAxis = [parts[0], parts[1]];
115
- result.yAxisLineNumber = lineNumber;
116
- }
117
- continue;
118
- }
119
-
120
- // Quadrant position labels: top-right Label (color)
121
- const posMatch = line.match(
122
- /^(top-right|top-left|bottom-left|bottom-right)\s+(.+)/i
123
- );
124
- if (posMatch) {
125
- const position = posMatch[1].toLowerCase();
126
- const labelMatch = posMatch[2].match(QUADRANT_LABEL_RE);
127
- if (labelMatch) {
128
- const label: QuadrantLabel = {
129
- text: labelMatch[1].trim(),
130
- color: labelMatch[2]
131
- ? (resolveColorWithDiagnostic(
132
- labelMatch[2].trim(),
133
- lineNumber,
134
- result.diagnostics
135
- ) ?? null)
136
- : null,
137
- lineNumber,
138
- };
139
- if (position === 'top-right') result.quadrants.topRight = label;
140
- else if (position === 'top-left') result.quadrants.topLeft = label;
141
- else if (position === 'bottom-left')
142
- result.quadrants.bottomLeft = label;
143
- else if (position === 'bottom-right')
144
- result.quadrants.bottomRight = label;
145
- }
146
- continue;
147
- }
148
-
149
- // Data points: Label x, y
150
- const pointMatch = line.match(DATA_POINT_RE);
151
- if (pointMatch) {
152
- // Make sure this isn't a quadrant position keyword
153
- const key = pointMatch[1].trim().toLowerCase();
154
- if (!QUADRANT_POSITIONS.has(key)) {
155
- result.points.push({
156
- label: pointMatch[1].trim(),
157
- x: parseFloat(pointMatch[2]),
158
- y: parseFloat(pointMatch[3]),
159
- lineNumber,
160
- });
161
- }
162
- continue;
163
- }
164
- }
165
-
166
- if (result.points.length === 0) {
167
- const diag = makeDgmoError(
168
- 1,
169
- 'No data points found. Add lines like: Label 0.5, 0.7'
170
- );
171
- result.diagnostics.push(diag);
172
- result.error = formatDgmoError(diag);
173
- }
174
-
175
- return result;
176
- }
177
-
178
- // ============================================================
179
- // Mermaid Builder
180
- // ============================================================
181
-
182
- /**
183
- * Generates valid Mermaid quadrantChart syntax from a parsed quadrant.
184
- * Returns a string ready for the Mermaid renderer.
185
- */
186
- export function buildMermaidQuadrant(
187
- parsed: ParsedQuadrant,
188
- options: {
189
- isDark?: boolean;
190
- textColor?: string;
191
- mutedTextColor?: string;
192
- } = {}
193
- ): string {
194
- const { isDark = false, textColor, mutedTextColor } = options;
195
- const lines: string[] = [];
196
-
197
- // %%{init}%% block — fill colors with reduced opacity + text color overrides
198
- const fillAlpha = isDark ? '30' : '55';
199
- const primaryText = textColor ?? (isDark ? '#d0d0d0' : '#333333');
200
- const quadrantLabelText = mutedTextColor ?? (isDark ? '#888888' : '#666666');
201
-
202
- const colorMap: Record<string, string> = {};
203
- if (parsed.quadrants.topRight?.color)
204
- colorMap.quadrant1Fill = parsed.quadrants.topRight.color + fillAlpha;
205
- if (parsed.quadrants.topLeft?.color)
206
- colorMap.quadrant2Fill = parsed.quadrants.topLeft.color + fillAlpha;
207
- if (parsed.quadrants.bottomLeft?.color)
208
- colorMap.quadrant3Fill = parsed.quadrants.bottomLeft.color + fillAlpha;
209
- if (parsed.quadrants.bottomRight?.color)
210
- colorMap.quadrant4Fill = parsed.quadrants.bottomRight.color + fillAlpha;
211
-
212
- // Quadrant labels use muted color, points use primary text color
213
- colorMap.quadrant1TextFill = quadrantLabelText;
214
- colorMap.quadrant2TextFill = quadrantLabelText;
215
- colorMap.quadrant3TextFill = quadrantLabelText;
216
- colorMap.quadrant4TextFill = quadrantLabelText;
217
- colorMap.quadrantPointTextFill = primaryText;
218
- colorMap.quadrantXAxisTextFill = primaryText;
219
- colorMap.quadrantYAxisTextFill = primaryText;
220
- colorMap.quadrantTitleFill = primaryText;
221
-
222
- const vars = JSON.stringify(colorMap);
223
- lines.push(`%%{init: {"themeVariables": ${vars}}}%%`);
224
-
225
- lines.push('quadrantChart');
226
-
227
- if (parsed.title) {
228
- lines.push(` title ${parsed.title}`);
229
- }
230
-
231
- if (parsed.xAxis) {
232
- lines.push(` x-axis ${parsed.xAxis[0]} --> ${parsed.xAxis[1]}`);
233
- }
234
-
235
- if (parsed.yAxis) {
236
- lines.push(` y-axis ${parsed.yAxis[0]} --> ${parsed.yAxis[1]}`);
237
- }
238
-
239
- // Helper to quote labels that need it (contain spaces or special chars)
240
- const quote = (s: string): string => (/[\s,:[\]]/.test(s) ? `"${s}"` : s);
241
-
242
- // Quadrant labels: 1=top-right, 2=top-left, 3=bottom-left, 4=bottom-right
243
- if (parsed.quadrants.topRight) {
244
- lines.push(` quadrant-1 ${quote(parsed.quadrants.topRight.text)}`);
245
- }
246
- if (parsed.quadrants.topLeft) {
247
- lines.push(` quadrant-2 ${quote(parsed.quadrants.topLeft.text)}`);
248
- }
249
- if (parsed.quadrants.bottomLeft) {
250
- lines.push(` quadrant-3 ${quote(parsed.quadrants.bottomLeft.text)}`);
251
- }
252
- if (parsed.quadrants.bottomRight) {
253
- lines.push(` quadrant-4 ${quote(parsed.quadrants.bottomRight.text)}`);
254
- }
255
-
256
- // Data points
257
- for (const point of parsed.points) {
258
- lines.push(` ${quote(point.label)}: [${point.x}, ${point.y}]`);
259
- }
260
-
261
- return lines.join('\n');
262
- }
@@ -1,220 +0,0 @@
1
- import type { PaletteColors } from './types';
2
- import { mute, tint, shade, contrastText } from './color-utils';
3
-
4
- // ============================================================
5
- // Mermaid Theme Variable Generator
6
- // ============================================================
7
-
8
- /**
9
- * Generates ~121 Mermaid theme variables from palette tokens.
10
- * Replaces the hardcoded lightThemeVars/darkThemeVars objects.
11
- *
12
- * Dark mode fills use `mute()` to derive desaturated variants
13
- * that are readable with light text.
14
- */
15
- export function buildMermaidThemeVars(
16
- colors: PaletteColors,
17
- isDark: boolean
18
- ): Record<string, string> {
19
- const c = colors.colors;
20
-
21
- // Ordered accent array for pie/cScale/fillType/actor slots
22
- const accentOrder = [
23
- c.blue,
24
- c.red,
25
- c.green,
26
- c.yellow,
27
- c.purple,
28
- c.orange,
29
- c.teal,
30
- c.cyan,
31
- colors.secondary,
32
- ];
33
-
34
- // Dark mode fills use muted variants for readability
35
- const fills = isDark ? accentOrder.map(mute) : accentOrder;
36
-
37
- return {
38
- // ── Backgrounds ──
39
- background: isDark ? colors.overlay : colors.border,
40
- mainBkg: colors.surface,
41
-
42
- // ── Primary/Secondary/Tertiary nodes ──
43
- primaryColor: isDark ? colors.primary : colors.surface,
44
- primaryTextColor: colors.text,
45
- primaryBorderColor: isDark ? colors.secondary : colors.border,
46
- secondaryColor: colors.secondary,
47
- secondaryTextColor: contrastText(colors.secondary, colors.text, colors.bg),
48
- secondaryBorderColor: colors.primary,
49
- tertiaryColor: colors.accent,
50
- tertiaryTextColor: contrastText(colors.accent, colors.text, colors.bg),
51
- tertiaryBorderColor: colors.border,
52
-
53
- // ── Lines & text ──
54
- lineColor: colors.textMuted,
55
- textColor: colors.text,
56
-
57
- // ── Clusters ──
58
- clusterBkg: colors.bg,
59
- clusterBorder: isDark ? colors.border : colors.textMuted,
60
- titleColor: colors.text,
61
-
62
- // ── Labels ──
63
- edgeLabelBackground: 'transparent',
64
-
65
- // ── Notes (sequence diagrams) ──
66
- noteBkgColor: colors.bg,
67
- noteTextColor: colors.text,
68
- noteBorderColor: isDark ? colors.border : colors.textMuted,
69
-
70
- // ── Actors (sequence diagrams) ──
71
- actorBkg: colors.surface,
72
- actorTextColor: colors.text,
73
- actorBorder: isDark ? colors.border : colors.textMuted,
74
- actorLineColor: colors.textMuted,
75
-
76
- // ── Signals (sequence diagrams) ──
77
- signalColor: colors.textMuted,
78
- signalTextColor: colors.text,
79
-
80
- // ── Labels ──
81
- labelColor: colors.text,
82
- labelTextColor: colors.text,
83
- labelBoxBkgColor: colors.surface,
84
- labelBoxBorderColor: isDark ? colors.border : colors.textMuted,
85
-
86
- // ── Loop boxes ──
87
- loopTextColor: colors.text,
88
-
89
- // ── Activation (sequence diagrams) ──
90
- activationBkgColor: isDark ? colors.overlay : colors.border,
91
- activationBorderColor: isDark ? colors.border : colors.textMuted,
92
-
93
- // ── Sequence numbers ──
94
- sequenceNumberColor: isDark ? colors.text : colors.bg,
95
-
96
- // ── State diagrams ──
97
- labelBackgroundColor: colors.surface,
98
-
99
- // ── Pie chart (9 slices) ──
100
- // Dark mode: use muted fills so light pieSectionTextColor stays readable
101
- ...Object.fromEntries(
102
- (isDark ? fills : accentOrder).map((col, i) => [`pie${i + 1}`, col])
103
- ),
104
- pieTitleTextColor: colors.text,
105
- pieSectionTextColor: isDark ? colors.text : colors.bg,
106
- pieLegendTextColor: colors.text,
107
- pieStrokeColor: 'transparent',
108
- pieOuterStrokeWidth: '0px',
109
- pieOuterStrokeColor: 'transparent',
110
-
111
- // ── cScale (9 tiers) — muted in dark mode ──
112
- ...Object.fromEntries(fills.map((f, i) => [`cScale${i}`, f])),
113
- ...Object.fromEntries(
114
- fills.map((_, i) => [
115
- `cScaleLabel${i}`,
116
- isDark ? colors.text : i < 2 || i > 6 ? colors.bg : colors.text,
117
- ])
118
- ),
119
-
120
- // ── fillType (8 slots) ──
121
- ...Object.fromEntries(
122
- [0, 1, 2, 3, 4, 5, 6, 7].map((i) => [
123
- `fillType${i}`,
124
- fills[i % fills.length],
125
- ])
126
- ),
127
-
128
- // ── Journey actors (6 slots) ──
129
- ...Object.fromEntries(
130
- [c.red, c.green, c.yellow, c.purple, c.orange, c.teal].map((color, i) => [
131
- `actor${i}`,
132
- color,
133
- ])
134
- ),
135
-
136
- // ── Flowchart ──
137
- nodeBorder: isDark ? colors.border : colors.textMuted,
138
- nodeTextColor: colors.text,
139
-
140
- // ── Gantt ──
141
- gridColor: isDark ? colors.textMuted : colors.border,
142
- doneTaskBkgColor: c.green,
143
- doneTaskBorderColor: isDark ? colors.border : colors.textMuted,
144
- activeTaskBkgColor: colors.secondary,
145
- activeTaskBorderColor: colors.primary,
146
- critBkgColor: c.orange,
147
- critBorderColor: c.red,
148
- taskBkgColor: colors.surface,
149
- taskBorderColor: isDark ? colors.border : colors.textMuted,
150
- taskTextColor: contrastText(colors.surface, colors.text, colors.bg),
151
- taskTextDarkColor: colors.bg,
152
- taskTextLightColor: colors.text,
153
- taskTextOutsideColor: colors.text,
154
- doneTaskTextColor: contrastText(c.green, colors.text, colors.bg),
155
- activeTaskTextColor: contrastText(colors.secondary, colors.text, colors.bg),
156
- critTaskTextColor: contrastText(c.orange, colors.text, colors.bg),
157
- sectionBkgColor: isDark
158
- ? shade(colors.primary, colors.bg, 0.6)
159
- : tint(colors.primary, 0.6),
160
- altSectionBkgColor: colors.bg,
161
- sectionBkgColor2: isDark
162
- ? shade(colors.primary, colors.bg, 0.6)
163
- : tint(colors.primary, 0.6),
164
- todayLineColor: c.yellow,
165
-
166
- // ── Quadrant ──
167
- quadrant1Fill: isDark
168
- ? shade(c.green, colors.bg, 0.75)
169
- : tint(c.green, 0.75),
170
- quadrant2Fill: isDark ? shade(c.blue, colors.bg, 0.75) : tint(c.blue, 0.75),
171
- quadrant3Fill: isDark ? shade(c.red, colors.bg, 0.75) : tint(c.red, 0.75),
172
- quadrant4Fill: isDark
173
- ? shade(c.yellow, colors.bg, 0.75)
174
- : tint(c.yellow, 0.75),
175
- quadrant1TextFill: colors.text,
176
- quadrant2TextFill: colors.text,
177
- quadrant3TextFill: colors.text,
178
- quadrant4TextFill: colors.text,
179
- quadrantPointFill: isDark ? c.cyan : c.blue,
180
- quadrantPointTextFill: colors.text,
181
- quadrantXAxisTextFill: colors.text,
182
- quadrantYAxisTextFill: colors.text,
183
- quadrantTitleFill: colors.text,
184
- quadrantInternalBorderStrokeFill: colors.border,
185
- quadrantExternalBorderStrokeFill: colors.border,
186
- };
187
- }
188
-
189
- // ============================================================
190
- // Mermaid Theme CSS Generator
191
- // ============================================================
192
-
193
- /**
194
- * Generates custom CSS overrides for Mermaid SVGs.
195
- * Handles git graph label backgrounds and dark-mode text readability.
196
- */
197
- export function buildThemeCSS(palette: PaletteColors, isDark: boolean): string {
198
- const base = `
199
- .branchLabelBkg { fill: transparent !important; stroke: transparent !important; }
200
- .commit-label-bkg { fill: transparent !important; stroke: transparent !important; }
201
- .tag-label-bkg { fill: transparent !important; stroke: transparent !important; }
202
-
203
- /* GitGraph: ensure commit and branch label text matches palette */
204
- .commit-label { fill: ${palette.text} !important; }
205
- .branch-label { fill: ${palette.text} !important; }
206
- .tag-label { fill: ${palette.text} !important; }
207
- `;
208
-
209
- if (!isDark) return base;
210
-
211
- return (
212
- base +
213
- `
214
- /* Flowchart: ensure node and edge label text is readable */
215
- .nodeLabel, .label { color: ${palette.text} !important; fill: ${palette.text} !important; }
216
- .edgeLabel { color: ${palette.text} !important; fill: ${palette.text} !important; }
217
- .edgeLabel .label { color: ${palette.text} !important; fill: ${palette.text} !important; }
218
- `
219
- );
220
- }