@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.
Files changed (74) hide show
  1. package/dist/cli.cjs +92 -131
  2. package/dist/editor.cjs +13 -1
  3. package/dist/editor.cjs.map +1 -1
  4. package/dist/editor.js +13 -1
  5. package/dist/editor.js.map +1 -1
  6. package/dist/highlight.cjs +13 -1
  7. package/dist/highlight.cjs.map +1 -1
  8. package/dist/highlight.js +13 -1
  9. package/dist/highlight.js.map +1 -1
  10. package/dist/index.cjs +4524 -1511
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +427 -186
  13. package/dist/index.d.ts +427 -186
  14. package/dist/index.js +4526 -1503
  15. package/dist/index.js.map +1 -1
  16. package/docs/guide/chart-mindmap.md +198 -0
  17. package/docs/guide/chart-sequence.md +23 -1
  18. package/docs/guide/chart-wireframe.md +100 -0
  19. package/docs/guide/index.md +8 -0
  20. package/docs/language-reference.md +210 -2
  21. package/package.json +22 -9
  22. package/src/boxes-and-lines/collapse.ts +21 -3
  23. package/src/boxes-and-lines/layout.ts +51 -9
  24. package/src/boxes-and-lines/parser.ts +16 -4
  25. package/src/boxes-and-lines/renderer.ts +121 -23
  26. package/src/boxes-and-lines/types.ts +1 -0
  27. package/src/c4/parser.ts +8 -7
  28. package/src/class/parser.ts +6 -0
  29. package/src/cli.ts +1 -9
  30. package/src/completion.ts +26 -0
  31. package/src/d3.ts +169 -266
  32. package/src/dgmo-router.ts +103 -5
  33. package/src/diagnostics.ts +16 -6
  34. package/src/echarts.ts +43 -10
  35. package/src/editor/keywords.ts +12 -0
  36. package/src/er/parser.ts +22 -2
  37. package/src/gantt/renderer.ts +2 -2
  38. package/src/graph/flowchart-parser.ts +89 -52
  39. package/src/graph/layout.ts +73 -9
  40. package/src/graph/state-collapse.ts +78 -0
  41. package/src/graph/state-parser.ts +60 -35
  42. package/src/graph/state-renderer.ts +139 -34
  43. package/src/index.ts +41 -16
  44. package/src/infra/parser.ts +9 -2
  45. package/src/kanban/renderer.ts +305 -59
  46. package/src/mindmap/collapse.ts +88 -0
  47. package/src/mindmap/layout.ts +605 -0
  48. package/src/mindmap/parser.ts +379 -0
  49. package/src/mindmap/renderer.ts +543 -0
  50. package/src/mindmap/text-wrap.ts +207 -0
  51. package/src/mindmap/types.ts +55 -0
  52. package/src/palettes/color-utils.ts +4 -12
  53. package/src/palettes/index.ts +0 -4
  54. package/src/render.ts +31 -20
  55. package/src/sequence/parser.ts +7 -2
  56. package/src/sequence/renderer.ts +141 -21
  57. package/src/sharing.ts +2 -0
  58. package/src/sitemap/layout.ts +35 -12
  59. package/src/sitemap/renderer.ts +1 -6
  60. package/src/utils/arrows.ts +180 -11
  61. package/src/utils/d3-types.ts +4 -0
  62. package/src/utils/export-container.ts +3 -2
  63. package/src/utils/legend-constants.ts +0 -4
  64. package/src/utils/legend-d3.ts +1 -0
  65. package/src/utils/legend-layout.ts +2 -2
  66. package/src/utils/parsing.ts +2 -0
  67. package/src/utils/time-ticks.ts +213 -0
  68. package/src/wireframe/layout.ts +460 -0
  69. package/src/wireframe/parser.ts +956 -0
  70. package/src/wireframe/renderer.ts +1293 -0
  71. package/src/wireframe/types.ts +110 -0
  72. package/src/branding.ts +0 -67
  73. package/src/dgmo-mermaid.ts +0 -262
  74. package/src/palettes/mermaid-bridge.ts +0 -220
@@ -0,0 +1,110 @@
1
+ // ============================================================
2
+ // Wireframe Diagram Types
3
+ // ============================================================
4
+
5
+ import type { DgmoError } from '../diagnostics';
6
+ import type { TagGroup } from '../utils/tag-groups';
7
+
8
+ /**
9
+ * All wireframe element types.
10
+ * Visual-mnemonic elements are inferred from bracket syntax;
11
+ * keyword elements use a small vocabulary (9 keywords).
12
+ */
13
+ export type WireframeElementType =
14
+ | 'group'
15
+ | 'textInput'
16
+ | 'button'
17
+ | 'dropdown'
18
+ | 'checkbox'
19
+ | 'radio'
20
+ | 'heading'
21
+ | 'divider'
22
+ | 'text'
23
+ | 'listItem'
24
+ | 'nav'
25
+ | 'tabs'
26
+ | 'table'
27
+ | 'image'
28
+ | 'modal'
29
+ | 'skeleton'
30
+ | 'alert'
31
+ | 'progress'
32
+ | 'chart';
33
+
34
+ /**
35
+ * Single flat interface for all wireframe elements (ADR-8).
36
+ * No separate WireframeGroup — all elements carry group fields
37
+ * with sensible defaults (isContainer=false, orientation='vertical', isSkeleton=false).
38
+ */
39
+ export interface WireframeElement {
40
+ id: string;
41
+ type: WireframeElementType;
42
+ /** Display label / placeholder text / heading text */
43
+ label: string;
44
+ /** Child elements (non-empty only when isContainer=true) */
45
+ children: WireframeElement[];
46
+ /** Pipe metadata key-value pairs */
47
+ metadata: Record<string, string>;
48
+ /** State keywords: disabled, active, ghost, destructive, etc. */
49
+ states: string[];
50
+ /** Free-text annotations from pipe metadata */
51
+ annotations: string[];
52
+ /** 1-based line number in source */
53
+ lineNumber: number;
54
+ /** Measured indentation (column) */
55
+ indent: number;
56
+ /** True when element has children (set during parse via indent stack) */
57
+ isContainer: boolean;
58
+ /** Stacking direction for group children */
59
+ orientation: 'vertical' | 'horizontal';
60
+ /** True when inside a skeleton block */
61
+ isSkeleton: boolean;
62
+
63
+ // ── Type-specific fields ─────────────────────────────────
64
+ /** Heading level: 1 for `#`, 2 for `##` */
65
+ headingLevel?: number;
66
+ /** Dropdown options (for type='dropdown') */
67
+ options?: string[];
68
+ /** Checked state (for type='checkbox') */
69
+ checked?: boolean;
70
+ /** Selected state (for type='radio') */
71
+ selected?: boolean;
72
+ /** Image hint: 'default' | 'round' | 'wide' */
73
+ imageHint?: 'default' | 'round' | 'wide';
74
+ /** Progress value 0-100 (for type='progress') */
75
+ progressValue?: number;
76
+ /** Chart hint: 'line' | 'bar' | 'pie' */
77
+ chartHint?: 'line' | 'bar' | 'pie';
78
+ /** Table dimensions for skeleton shorthand (for type='table') */
79
+ tableRows?: number;
80
+ tableCols?: number;
81
+ /** Table header row labels (for type='table') */
82
+ tableHeaders?: string[];
83
+ /** Table data rows — each row is an array of cell content strings (for type='table') */
84
+ tableData?: string[][];
85
+ /** Inline elements on the same line (multi-element line) */
86
+ inlineElements?: WireframeElement[];
87
+ /** Label element for label-field pairing */
88
+ labelFor?: WireframeElement;
89
+ /** Color from tag system */
90
+ color?: string;
91
+ /** Field variant: password, textarea */
92
+ fieldVariant?: 'password' | 'textarea';
93
+ }
94
+
95
+ /** Form factor / layout mode */
96
+ export type WireframeFormFactor = 'desktop' | 'mobile';
97
+
98
+ export interface ParsedWireframe {
99
+ title: string | null;
100
+ titleLineNumber: number | null;
101
+ formFactor: WireframeFormFactor;
102
+ /** Top-level elements (roots of the hierarchy) */
103
+ roots: WireframeElement[];
104
+ /** Modal elements (rendered separately below main) */
105
+ modals: WireframeElement[];
106
+ tagGroups: TagGroup[];
107
+ options: Record<string, string>;
108
+ diagnostics: DgmoError[];
109
+ error: string | null;
110
+ }
package/src/branding.ts DELETED
@@ -1,67 +0,0 @@
1
- import { FONT_FAMILY } from './fonts';
2
-
3
- const BRANDING_HEIGHT = 20;
4
-
5
- /**
6
- * Injects `diagrammo.app` branding text into an SVG string.
7
- * Extends the SVG height by 20px and places the text at the bottom-right.
8
- */
9
- export function injectBranding(svgHtml: string, mutedColor: string): string {
10
- if (!svgHtml) return svgHtml;
11
-
12
- const brandingText = `<text x="0" y="0" font-size="10" font-family="${FONT_FAMILY}" fill="${mutedColor}" opacity="0.5" text-anchor="end">diagrammo.app</text>`;
13
-
14
- // Parse viewBox
15
- const vbMatch = svgHtml.match(/viewBox="([^"]+)"/);
16
- const heightAttrMatch = svgHtml.match(/height="([^"]+)"/);
17
-
18
- if (vbMatch) {
19
- const parts = vbMatch[1].split(/\s+/).map(Number);
20
- if (parts.length === 4) {
21
- const [vx, vy, vw, vh] = parts;
22
- const newVh = vh + BRANDING_HEIGHT;
23
- const textX = vx + vw - 4;
24
- const textY = vy + vh + BRANDING_HEIGHT - 6;
25
- const positioned = brandingText.replace('x="0" y="0"', `x="${textX}" y="${textY}"`);
26
-
27
- let result = svgHtml.replace(
28
- /viewBox="[^"]+"/,
29
- `viewBox="${vx} ${vy} ${vw} ${newVh}"`
30
- );
31
-
32
- // Update height attribute if present
33
- if (heightAttrMatch) {
34
- const oldH = parseFloat(heightAttrMatch[1]);
35
- if (!isNaN(oldH)) {
36
- result = result.replace(
37
- `height="${heightAttrMatch[1]}"`,
38
- `height="${oldH + BRANDING_HEIGHT}"`
39
- );
40
- }
41
- }
42
-
43
- result = result.replace('</svg>', `${positioned}</svg>`);
44
- return result;
45
- }
46
- }
47
-
48
- // Fallback: no viewBox, try width/height attributes
49
- if (heightAttrMatch) {
50
- const widthMatch = svgHtml.match(/width="([^"]+)"/);
51
- const w = widthMatch ? parseFloat(widthMatch[1]) : 800;
52
- const h = parseFloat(heightAttrMatch[1]);
53
- if (!isNaN(h) && !isNaN(w)) {
54
- const textX = w - 4;
55
- const textY = h + BRANDING_HEIGHT - 6;
56
- const positioned = brandingText.replace('x="0" y="0"', `x="${textX}" y="${textY}"`);
57
- let result = svgHtml.replace(
58
- `height="${heightAttrMatch[1]}"`,
59
- `height="${h + BRANDING_HEIGHT}"`
60
- );
61
- result = result.replace('</svg>', `${positioned}</svg>`);
62
- return result;
63
- }
64
- }
65
-
66
- return svgHtml;
67
- }
@@ -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
- }