@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
package/dist/index.js CHANGED
@@ -32,8 +32,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
32
32
  ));
33
33
 
34
34
  // src/diagnostics.ts
35
- function makeDgmoError(line10, message, severity = "error") {
36
- return { line: line10, message, severity };
35
+ function makeDgmoError(line10, message, severity = "error", code) {
36
+ return code !== void 0 ? { line: line10, message, severity, code } : { line: line10, message, severity };
37
37
  }
38
38
  function formatDgmoError(err) {
39
39
  return err.line > 0 ? `Line ${err.line}: ${err.message}` : err.message;
@@ -74,74 +74,227 @@ var init_diagnostics = __esm({
74
74
  }
75
75
  });
76
76
 
77
- // src/fonts.ts
78
- var FONT_FAMILY;
79
- var init_fonts = __esm({
80
- "src/fonts.ts"() {
77
+ // src/colors.ts
78
+ function isRecognizedColorName(name) {
79
+ return Object.prototype.hasOwnProperty.call(colorNames, name.toLowerCase());
80
+ }
81
+ function resolveColor(color, palette) {
82
+ if (!color) return null;
83
+ if (color.startsWith("#")) return null;
84
+ const lower = color.toLowerCase();
85
+ if (!isRecognizedColorName(lower)) return null;
86
+ if (palette) {
87
+ const named = palette.colors[lower];
88
+ if (named) return named;
89
+ }
90
+ return colorNames[lower];
91
+ }
92
+ function resolveColorWithDiagnostic(color, line10, diagnostics, palette) {
93
+ const resolved = resolveColor(color, palette);
94
+ if (resolved !== null) return resolved;
95
+ const hint = suggest(color, RECOGNIZED_COLOR_NAMES);
96
+ const suggestion = hint ? ` ${hint}` : "";
97
+ diagnostics.push(
98
+ makeDgmoError(
99
+ line10,
100
+ `Unknown color "${color}". Allowed: ${RECOGNIZED_COLOR_NAMES.join(", ")}.${suggestion}`,
101
+ "warning"
102
+ )
103
+ );
104
+ return void 0;
105
+ }
106
+ var nord, colorNames, RECOGNIZED_COLOR_NAMES, seriesColors;
107
+ var init_colors = __esm({
108
+ "src/colors.ts"() {
81
109
  "use strict";
82
- FONT_FAMILY = "Inter, system-ui, Avenir, Helvetica, Arial, sans-serif";
110
+ init_diagnostics();
111
+ nord = {
112
+ // Polar Night (dark)
113
+ nord0: "#2e3440",
114
+ nord1: "#3b4252",
115
+ nord2: "#434c5e",
116
+ nord3: "#4c566a",
117
+ // Snow Storm (light)
118
+ nord4: "#d8dee9",
119
+ nord5: "#e5e9f0",
120
+ nord6: "#eceff4",
121
+ // Frost (accent blues)
122
+ nord7: "#8fbcbb",
123
+ nord8: "#88c0d0",
124
+ nord9: "#81a1c1",
125
+ nord10: "#5e81ac",
126
+ // Aurora (colors)
127
+ nord11: "#bf616a",
128
+ // red
129
+ nord12: "#d08770",
130
+ // orange
131
+ nord13: "#ebcb8b",
132
+ // yellow
133
+ nord14: "#a3be8c",
134
+ // green
135
+ nord15: "#b48ead"
136
+ // purple
137
+ };
138
+ colorNames = {
139
+ red: nord.nord11,
140
+ orange: nord.nord12,
141
+ yellow: nord.nord13,
142
+ green: nord.nord14,
143
+ blue: nord.nord10,
144
+ purple: nord.nord15,
145
+ teal: nord.nord7,
146
+ cyan: nord.nord8,
147
+ gray: nord.nord3,
148
+ black: nord.nord0,
149
+ white: nord.nord6
150
+ };
151
+ RECOGNIZED_COLOR_NAMES = Object.freeze([
152
+ "red",
153
+ "orange",
154
+ "yellow",
155
+ "green",
156
+ "blue",
157
+ "purple",
158
+ "teal",
159
+ "cyan",
160
+ "gray",
161
+ "black",
162
+ "white"
163
+ ]);
164
+ seriesColors = [
165
+ nord.nord10,
166
+ // blue
167
+ nord.nord14,
168
+ // green
169
+ nord.nord13,
170
+ // yellow
171
+ nord.nord12,
172
+ // orange
173
+ nord.nord15,
174
+ // purple
175
+ nord.nord11,
176
+ // red
177
+ nord.nord7,
178
+ // teal
179
+ nord.nord8
180
+ // light blue
181
+ ];
83
182
  }
84
183
  });
85
184
 
86
- // src/branding.ts
87
- var branding_exports = {};
88
- __export(branding_exports, {
89
- injectBranding: () => injectBranding
90
- });
91
- function injectBranding(svgHtml, mutedColor) {
92
- if (!svgHtml) return svgHtml;
93
- 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>`;
94
- const vbMatch = svgHtml.match(/viewBox="([^"]+)"/);
95
- const heightAttrMatch = svgHtml.match(/height="([^"]+)"/);
96
- if (vbMatch) {
97
- const parts = vbMatch[1].split(/\s+/).map(Number);
98
- if (parts.length === 4) {
99
- const [vx, vy, vw, vh] = parts;
100
- const newVh = vh + BRANDING_HEIGHT;
101
- const textX = vx + vw - 4;
102
- const textY = vy + vh + BRANDING_HEIGHT - 6;
103
- const positioned = brandingText.replace('x="0" y="0"', `x="${textX}" y="${textY}"`);
104
- let result = svgHtml.replace(
105
- /viewBox="[^"]+"/,
106
- `viewBox="${vx} ${vy} ${vw} ${newVh}"`
107
- );
108
- if (heightAttrMatch) {
109
- const oldH = parseFloat(heightAttrMatch[1]);
110
- if (!isNaN(oldH)) {
111
- result = result.replace(
112
- `height="${heightAttrMatch[1]}"`,
113
- `height="${oldH + BRANDING_HEIGHT}"`
114
- );
115
- }
116
- }
117
- result = result.replace("</svg>", `${positioned}</svg>`);
118
- return result;
119
- }
185
+ // src/utils/arrows.ts
186
+ function validateLabelCharacters(label, lineNumber) {
187
+ const out = [];
188
+ if (label.includes("->") || label.includes("~>")) {
189
+ out.push(
190
+ makeDgmoError(
191
+ lineNumber,
192
+ 'Arrow symbols (-> or ~>) are not allowed inside a label. Move the label after the arrow: "A -> B: uses -> to chain". See "In-Arrow Message Labels" \u2192 Forbidden.',
193
+ "error",
194
+ ARROW_DIAGNOSTIC_CODES.ARROW_SUBSTRING_IN_LABEL
195
+ )
196
+ );
120
197
  }
121
- if (heightAttrMatch) {
122
- const widthMatch = svgHtml.match(/width="([^"]+)"/);
123
- const w = widthMatch ? parseFloat(widthMatch[1]) : 800;
124
- const h = parseFloat(heightAttrMatch[1]);
125
- if (!isNaN(h) && !isNaN(w)) {
126
- const textX = w - 4;
127
- const textY = h + BRANDING_HEIGHT - 6;
128
- const positioned = brandingText.replace('x="0" y="0"', `x="${textX}" y="${textY}"`);
129
- let result = svgHtml.replace(
130
- `height="${heightAttrMatch[1]}"`,
131
- `height="${h + BRANDING_HEIGHT}"`
198
+ for (const ch of label) {
199
+ const cp = ch.codePointAt(0);
200
+ const isC0 = cp >= 0 && cp <= 31 && cp !== 9;
201
+ const isDel = cp === 127;
202
+ if (isC0 || isDel) {
203
+ const hex = cp.toString(16).toUpperCase().padStart(4, "0");
204
+ out.push(
205
+ makeDgmoError(
206
+ lineNumber,
207
+ `Label contains a control character (U+${hex}). Remove it and use plain text.`,
208
+ "error",
209
+ ARROW_DIAGNOSTIC_CODES.CONTROL_CHAR_IN_LABEL
210
+ )
132
211
  );
133
- result = result.replace("</svg>", `${positioned}</svg>`);
134
- return result;
212
+ break;
135
213
  }
136
214
  }
137
- return svgHtml;
215
+ return out;
216
+ }
217
+ function parseInArrowLabel(rawLabel, lineNumber) {
218
+ const trimmed = rawLabel.trim();
219
+ if (trimmed.length === 0) {
220
+ return { label: void 0, diagnostics: [] };
221
+ }
222
+ const diagnostics = validateLabelCharacters(trimmed, lineNumber);
223
+ return { label: trimmed, diagnostics };
224
+ }
225
+ function matchColorParens(content) {
226
+ const m = content.match(/^\(([A-Za-z]+)\)$/);
227
+ if (!m) return null;
228
+ const candidate = m[1].toLowerCase();
229
+ if (RECOGNIZED_COLOR_NAMES.includes(candidate)) {
230
+ return candidate;
231
+ }
232
+ return null;
233
+ }
234
+ function parseArrow(line10) {
235
+ if (BIDI_SYNC_RE.test(line10) || BIDI_ASYNC_RE.test(line10)) {
236
+ return {
237
+ error: "Bidirectional arrows are no longer supported. Use two separate lines: 'A -msg-> B' and 'B -msg-> A'"
238
+ };
239
+ }
240
+ if (RETURN_SYNC_LABELED_RE.test(line10) || RETURN_ASYNC_LABELED_RE.test(line10)) {
241
+ const m = line10.match(RETURN_SYNC_LABELED_RE) ?? line10.match(RETURN_ASYNC_LABELED_RE);
242
+ const from = m[3];
243
+ const to = m[1];
244
+ const label = m[2].trim();
245
+ return {
246
+ error: `Left-pointing arrows are no longer supported. Write '${from} -${label}-> ${to}' instead`
247
+ };
248
+ }
249
+ const patterns = [
250
+ { re: SYNC_LABELED_RE, async: false },
251
+ { re: ASYNC_LABELED_RE, async: true }
252
+ ];
253
+ for (const { re, async: isAsync } of patterns) {
254
+ const m = line10.match(re);
255
+ if (!m) continue;
256
+ const label = m[2].trim();
257
+ if (!label) return null;
258
+ return {
259
+ from: m[1],
260
+ to: m[3],
261
+ label,
262
+ async: isAsync
263
+ };
264
+ }
265
+ return null;
138
266
  }
139
- var BRANDING_HEIGHT;
140
- var init_branding = __esm({
141
- "src/branding.ts"() {
267
+ var ARROW_DIAGNOSTIC_CODES, SYNC_LABELED_RE, ASYNC_LABELED_RE, RETURN_SYNC_LABELED_RE, RETURN_ASYNC_LABELED_RE, BIDI_SYNC_RE, BIDI_ASYNC_RE;
268
+ var init_arrows = __esm({
269
+ "src/utils/arrows.ts"() {
142
270
  "use strict";
143
- init_fonts();
144
- BRANDING_HEIGHT = 20;
271
+ init_diagnostics();
272
+ init_colors();
273
+ ARROW_DIAGNOSTIC_CODES = {
274
+ /** Active: label contains `->` or `~>` substring (TD-13). */
275
+ ARROW_SUBSTRING_IN_LABEL: "E_ARROW_SUBSTRING_IN_LABEL",
276
+ /** Active: label contains a forbidden control character (TD-14). */
277
+ CONTROL_CHAR_IN_LABEL: "E_CONTROL_CHAR_IN_LABEL",
278
+ /** Reserved: not currently emitted by any parser. See JSDoc above. */
279
+ TRAILING_ARROW_TEXT: "E_TRAILING_ARROW_TEXT",
280
+ /** Reserved: not currently emitted by any parser. See JSDoc above. */
281
+ MIXED_ARROW_DELIMITERS: "E_MIXED_ARROW_DELIMITERS"
282
+ };
283
+ SYNC_LABELED_RE = /^(.+?)\s*-(.+)->\s*(.+)$/;
284
+ ASYNC_LABELED_RE = /^(.+?)\s*~(.+)~>\s*(.+)$/;
285
+ RETURN_SYNC_LABELED_RE = /^(.+?)\s*<-(.+)-\s*(.+)$/;
286
+ RETURN_ASYNC_LABELED_RE = /^(.+?)\s*<~(.+)~\s*(.+)$/;
287
+ BIDI_SYNC_RE = /^(.+?)\s*<-(.+)->\s*(.+)$/;
288
+ BIDI_ASYNC_RE = /^(.+?)\s*<~(.+)~>\s*(.+)$/;
289
+ }
290
+ });
291
+
292
+ // src/fonts.ts
293
+ var FONT_FAMILY;
294
+ var init_fonts = __esm({
295
+ "src/fonts.ts"() {
296
+ "use strict";
297
+ FONT_FAMILY = "Inter, system-ui, Avenir, Helvetica, Arial, sans-serif";
145
298
  }
146
299
  });
147
300
 
@@ -322,110 +475,158 @@ var init_label_layout = __esm({
322
475
  }
323
476
  });
324
477
 
325
- // src/colors.ts
326
- function isRecognizedColorName(name) {
327
- return Object.prototype.hasOwnProperty.call(colorNames, name.toLowerCase());
478
+ // src/utils/time-ticks.ts
479
+ function fractionalYearToDate(frac) {
480
+ const year = Math.floor(frac);
481
+ const remainder = frac - year;
482
+ const monthFrac = remainder * 12;
483
+ const month = Math.floor(monthFrac);
484
+ const monthRemainder = remainder - month / 12;
485
+ const dayFrac = monthRemainder * 365;
486
+ const day = Math.floor(dayFrac) + 1;
487
+ const dayRemainder = dayFrac - Math.floor(dayFrac);
488
+ const hourFrac = dayRemainder * 24;
489
+ const hour = Math.floor(hourFrac);
490
+ const minute = Math.round((hourFrac - hour) * 60);
491
+ return new Date(year, month, day, hour, minute);
328
492
  }
329
- function resolveColor(color, palette) {
330
- if (!color) return null;
331
- if (color.startsWith("#")) return null;
332
- const lower = color.toLowerCase();
333
- if (!isRecognizedColorName(lower)) return null;
334
- if (palette) {
335
- const named = palette.colors[lower];
336
- if (named) return named;
337
- }
338
- return colorNames[lower];
493
+ function dateToFractionalYear(d) {
494
+ return d.getFullYear() + d.getMonth() / 12 + (d.getDate() - 1) / 365 + d.getHours() / 8760 + d.getMinutes() / 525600;
339
495
  }
340
- function resolveColorWithDiagnostic(color, line10, diagnostics, palette) {
341
- const resolved = resolveColor(color, palette);
342
- if (resolved !== null) return resolved;
343
- const hint = suggest(color, RECOGNIZED_COLOR_NAMES);
344
- const suggestion = hint ? ` ${hint}` : "";
345
- diagnostics.push(
346
- makeDgmoError(
347
- line10,
348
- `Unknown color "${color}". Allowed: ${RECOGNIZED_COLOR_NAMES.join(", ")}.${suggestion}`,
349
- "warning"
350
- )
351
- );
352
- return void 0;
496
+ function computeTimeTicks(domainMin, domainMax, scale, boundaryStart, boundaryEnd, boundaryStartLabel, boundaryEndLabel) {
497
+ const minYear = Math.floor(domainMin);
498
+ const maxYear = Math.floor(domainMax);
499
+ const span = domainMax - domainMin;
500
+ let ticks = [];
501
+ const firstYear = Math.ceil(domainMin);
502
+ const lastYear = Math.floor(domainMax);
503
+ if (lastYear >= firstYear + 1) {
504
+ const yearSpan = lastYear - firstYear;
505
+ let step = 1;
506
+ if (yearSpan > 80) step = 20;
507
+ else if (yearSpan > 40) step = 10;
508
+ else if (yearSpan > 20) step = 5;
509
+ else if (yearSpan > 10) step = 2;
510
+ const alignedFirst = Math.ceil(firstYear / step) * step;
511
+ for (let y = alignedFirst; y <= lastYear; y += step) {
512
+ ticks.push({ pos: scale(y), label: String(y) });
513
+ }
514
+ } else if (span > 0.25) {
515
+ const crossesYear = maxYear > minYear;
516
+ for (let y = minYear; y <= maxYear + 1; y++) {
517
+ for (let m = 1; m <= 12; m++) {
518
+ const val = y + (m - 1) / 12;
519
+ if (val > domainMax) break;
520
+ if (val >= domainMin) {
521
+ ticks.push({
522
+ pos: scale(val),
523
+ label: crossesYear ? `${MONTH_ABBR[m - 1]} '${String(y).slice(-2)}` : MONTH_ABBR[m - 1]
524
+ });
525
+ }
526
+ }
527
+ }
528
+ } else if (span <= 685e-6) {
529
+ let stepMin = 5;
530
+ const spanHours = span * 8760;
531
+ if (spanHours > 3) stepMin = 30;
532
+ else if (spanHours > 1) stepMin = 15;
533
+ else if (spanHours > 0.5) stepMin = 10;
534
+ const startDate = fractionalYearToDate(domainMin);
535
+ startDate.setMinutes(
536
+ Math.floor(startDate.getMinutes() / stepMin) * stepMin,
537
+ 0,
538
+ 0
539
+ );
540
+ while (true) {
541
+ const val = dateToFractionalYear(startDate);
542
+ if (val > domainMax) break;
543
+ if (val >= domainMin) {
544
+ const hh = String(startDate.getHours()).padStart(2, "0");
545
+ const mm = String(startDate.getMinutes()).padStart(2, "0");
546
+ ticks.push({ pos: scale(val), label: `${hh}:${mm}` });
547
+ }
548
+ startDate.setMinutes(startDate.getMinutes() + stepMin);
549
+ }
550
+ } else if (span <= 822e-5) {
551
+ let stepHour = 1;
552
+ const spanHours = span * 8760;
553
+ if (spanHours > 48) stepHour = 6;
554
+ else if (spanHours > 24) stepHour = 3;
555
+ else if (spanHours > 12) stepHour = 2;
556
+ const singleDay = spanHours <= 24;
557
+ const startDate = fractionalYearToDate(domainMin);
558
+ startDate.setHours(
559
+ Math.floor(startDate.getHours() / stepHour) * stepHour,
560
+ 0,
561
+ 0,
562
+ 0
563
+ );
564
+ while (true) {
565
+ const val = dateToFractionalYear(startDate);
566
+ if (val > domainMax) break;
567
+ if (val >= domainMin) {
568
+ const hh = String(startDate.getHours()).padStart(2, "0");
569
+ const mm = String(startDate.getMinutes()).padStart(2, "0");
570
+ if (singleDay) {
571
+ ticks.push({ pos: scale(val), label: `${hh}:${mm}` });
572
+ } else {
573
+ const mon = MONTH_ABBR[startDate.getMonth()];
574
+ const d = startDate.getDate();
575
+ ticks.push({ pos: scale(val), label: `${mon} ${d} ${hh}:${mm}` });
576
+ }
577
+ }
578
+ startDate.setHours(startDate.getHours() + stepHour);
579
+ }
580
+ } else {
581
+ for (let y = minYear; y <= maxYear + 1; y++) {
582
+ for (let m = 1; m <= 12; m++) {
583
+ for (const d of [1, 8, 15, 22]) {
584
+ const val = y + (m - 1) / 12 + (d - 1) / 365;
585
+ if (val > domainMax) break;
586
+ if (val >= domainMin) {
587
+ ticks.push({
588
+ pos: scale(val),
589
+ label: `${MONTH_ABBR[m - 1]} ${d}`
590
+ });
591
+ }
592
+ }
593
+ }
594
+ }
595
+ }
596
+ const collisionThreshold = 40;
597
+ if (boundaryStart !== void 0 && boundaryStartLabel) {
598
+ const boundaryPos = scale(boundaryStart);
599
+ ticks = ticks.filter(
600
+ (t) => Math.abs(t.pos - boundaryPos) >= collisionThreshold
601
+ );
602
+ ticks.unshift({ pos: boundaryPos, label: boundaryStartLabel });
603
+ }
604
+ if (boundaryEnd !== void 0 && boundaryEndLabel) {
605
+ const boundaryPos = scale(boundaryEnd);
606
+ ticks = ticks.filter(
607
+ (t) => Math.abs(t.pos - boundaryPos) >= collisionThreshold
608
+ );
609
+ ticks.push({ pos: boundaryPos, label: boundaryEndLabel });
610
+ }
611
+ return ticks;
353
612
  }
354
- var nord, colorNames, RECOGNIZED_COLOR_NAMES, seriesColors;
355
- var init_colors = __esm({
356
- "src/colors.ts"() {
613
+ var MONTH_ABBR;
614
+ var init_time_ticks = __esm({
615
+ "src/utils/time-ticks.ts"() {
357
616
  "use strict";
358
- init_diagnostics();
359
- nord = {
360
- // Polar Night (dark)
361
- nord0: "#2e3440",
362
- nord1: "#3b4252",
363
- nord2: "#434c5e",
364
- nord3: "#4c566a",
365
- // Snow Storm (light)
366
- nord4: "#d8dee9",
367
- nord5: "#e5e9f0",
368
- nord6: "#eceff4",
369
- // Frost (accent blues)
370
- nord7: "#8fbcbb",
371
- nord8: "#88c0d0",
372
- nord9: "#81a1c1",
373
- nord10: "#5e81ac",
374
- // Aurora (colors)
375
- nord11: "#bf616a",
376
- // red
377
- nord12: "#d08770",
378
- // orange
379
- nord13: "#ebcb8b",
380
- // yellow
381
- nord14: "#a3be8c",
382
- // green
383
- nord15: "#b48ead"
384
- // purple
385
- };
386
- colorNames = {
387
- red: nord.nord11,
388
- orange: nord.nord12,
389
- yellow: nord.nord13,
390
- green: nord.nord14,
391
- blue: nord.nord10,
392
- purple: nord.nord15,
393
- teal: nord.nord7,
394
- cyan: nord.nord8,
395
- gray: nord.nord3,
396
- black: nord.nord0,
397
- white: nord.nord6
398
- };
399
- RECOGNIZED_COLOR_NAMES = Object.freeze([
400
- "red",
401
- "orange",
402
- "yellow",
403
- "green",
404
- "blue",
405
- "purple",
406
- "teal",
407
- "cyan",
408
- "gray",
409
- "black",
410
- "white"
411
- ]);
412
- seriesColors = [
413
- nord.nord10,
414
- // blue
415
- nord.nord14,
416
- // green
417
- nord.nord13,
418
- // yellow
419
- nord.nord12,
420
- // orange
421
- nord.nord15,
422
- // purple
423
- nord.nord11,
424
- // red
425
- nord.nord7,
426
- // teal
427
- nord.nord8
428
- // light blue
617
+ MONTH_ABBR = [
618
+ "Jan",
619
+ "Feb",
620
+ "Mar",
621
+ "Apr",
622
+ "May",
623
+ "Jun",
624
+ "Jul",
625
+ "Aug",
626
+ "Sep",
627
+ "Oct",
628
+ "Nov",
629
+ "Dec"
429
630
  ];
430
631
  }
431
632
  });
@@ -554,10 +755,6 @@ function hexToHSLString(hex) {
554
755
  const { h, s, l } = hexToHSL(hex);
555
756
  return `${h} ${s}% ${l}%`;
556
757
  }
557
- function mute(hex) {
558
- const { h, s, l } = hexToHSL(hex);
559
- return hslToHex(h, Math.min(s, 35), Math.min(l, 36));
560
- }
561
758
  function tint(hex, amount) {
562
759
  const raw = hex.replace("#", "");
563
760
  const full = raw.length === 3 ? raw[0] + raw[0] + raw[1] + raw[1] + raw[2] + raw[2] : raw;
@@ -1499,177 +1696,10 @@ var init_monokai = __esm({
1499
1696
  }
1500
1697
  });
1501
1698
 
1502
- // src/palettes/mermaid-bridge.ts
1503
- function buildMermaidThemeVars(colors, isDark) {
1504
- const c = colors.colors;
1505
- const accentOrder = [
1506
- c.blue,
1507
- c.red,
1508
- c.green,
1509
- c.yellow,
1510
- c.purple,
1511
- c.orange,
1512
- c.teal,
1513
- c.cyan,
1514
- colors.secondary
1515
- ];
1516
- const fills = isDark ? accentOrder.map(mute) : accentOrder;
1517
- return {
1518
- // ── Backgrounds ──
1519
- background: isDark ? colors.overlay : colors.border,
1520
- mainBkg: colors.surface,
1521
- // ── Primary/Secondary/Tertiary nodes ──
1522
- primaryColor: isDark ? colors.primary : colors.surface,
1523
- primaryTextColor: colors.text,
1524
- primaryBorderColor: isDark ? colors.secondary : colors.border,
1525
- secondaryColor: colors.secondary,
1526
- secondaryTextColor: contrastText(colors.secondary, colors.text, colors.bg),
1527
- secondaryBorderColor: colors.primary,
1528
- tertiaryColor: colors.accent,
1529
- tertiaryTextColor: contrastText(colors.accent, colors.text, colors.bg),
1530
- tertiaryBorderColor: colors.border,
1531
- // ── Lines & text ──
1532
- lineColor: colors.textMuted,
1533
- textColor: colors.text,
1534
- // ── Clusters ──
1535
- clusterBkg: colors.bg,
1536
- clusterBorder: isDark ? colors.border : colors.textMuted,
1537
- titleColor: colors.text,
1538
- // ── Labels ──
1539
- edgeLabelBackground: "transparent",
1540
- // ── Notes (sequence diagrams) ──
1541
- noteBkgColor: colors.bg,
1542
- noteTextColor: colors.text,
1543
- noteBorderColor: isDark ? colors.border : colors.textMuted,
1544
- // ── Actors (sequence diagrams) ──
1545
- actorBkg: colors.surface,
1546
- actorTextColor: colors.text,
1547
- actorBorder: isDark ? colors.border : colors.textMuted,
1548
- actorLineColor: colors.textMuted,
1549
- // ── Signals (sequence diagrams) ──
1550
- signalColor: colors.textMuted,
1551
- signalTextColor: colors.text,
1552
- // ── Labels ──
1553
- labelColor: colors.text,
1554
- labelTextColor: colors.text,
1555
- labelBoxBkgColor: colors.surface,
1556
- labelBoxBorderColor: isDark ? colors.border : colors.textMuted,
1557
- // ── Loop boxes ──
1558
- loopTextColor: colors.text,
1559
- // ── Activation (sequence diagrams) ──
1560
- activationBkgColor: isDark ? colors.overlay : colors.border,
1561
- activationBorderColor: isDark ? colors.border : colors.textMuted,
1562
- // ── Sequence numbers ──
1563
- sequenceNumberColor: isDark ? colors.text : colors.bg,
1564
- // ── State diagrams ──
1565
- labelBackgroundColor: colors.surface,
1566
- // ── Pie chart (9 slices) ──
1567
- // Dark mode: use muted fills so light pieSectionTextColor stays readable
1568
- ...Object.fromEntries(
1569
- (isDark ? fills : accentOrder).map((col, i) => [`pie${i + 1}`, col])
1570
- ),
1571
- pieTitleTextColor: colors.text,
1572
- pieSectionTextColor: isDark ? colors.text : colors.bg,
1573
- pieLegendTextColor: colors.text,
1574
- pieStrokeColor: "transparent",
1575
- pieOuterStrokeWidth: "0px",
1576
- pieOuterStrokeColor: "transparent",
1577
- // ── cScale (9 tiers) — muted in dark mode ──
1578
- ...Object.fromEntries(fills.map((f, i) => [`cScale${i}`, f])),
1579
- ...Object.fromEntries(
1580
- fills.map((_, i) => [
1581
- `cScaleLabel${i}`,
1582
- isDark ? colors.text : i < 2 || i > 6 ? colors.bg : colors.text
1583
- ])
1584
- ),
1585
- // ── fillType (8 slots) ──
1586
- ...Object.fromEntries(
1587
- [0, 1, 2, 3, 4, 5, 6, 7].map((i) => [
1588
- `fillType${i}`,
1589
- fills[i % fills.length]
1590
- ])
1591
- ),
1592
- // ── Journey actors (6 slots) ──
1593
- ...Object.fromEntries(
1594
- [c.red, c.green, c.yellow, c.purple, c.orange, c.teal].map((color, i) => [
1595
- `actor${i}`,
1596
- color
1597
- ])
1598
- ),
1599
- // ── Flowchart ──
1600
- nodeBorder: isDark ? colors.border : colors.textMuted,
1601
- nodeTextColor: colors.text,
1602
- // ── Gantt ──
1603
- gridColor: isDark ? colors.textMuted : colors.border,
1604
- doneTaskBkgColor: c.green,
1605
- doneTaskBorderColor: isDark ? colors.border : colors.textMuted,
1606
- activeTaskBkgColor: colors.secondary,
1607
- activeTaskBorderColor: colors.primary,
1608
- critBkgColor: c.orange,
1609
- critBorderColor: c.red,
1610
- taskBkgColor: colors.surface,
1611
- taskBorderColor: isDark ? colors.border : colors.textMuted,
1612
- taskTextColor: contrastText(colors.surface, colors.text, colors.bg),
1613
- taskTextDarkColor: colors.bg,
1614
- taskTextLightColor: colors.text,
1615
- taskTextOutsideColor: colors.text,
1616
- doneTaskTextColor: contrastText(c.green, colors.text, colors.bg),
1617
- activeTaskTextColor: contrastText(colors.secondary, colors.text, colors.bg),
1618
- critTaskTextColor: contrastText(c.orange, colors.text, colors.bg),
1619
- sectionBkgColor: isDark ? shade(colors.primary, colors.bg, 0.6) : tint(colors.primary, 0.6),
1620
- altSectionBkgColor: colors.bg,
1621
- sectionBkgColor2: isDark ? shade(colors.primary, colors.bg, 0.6) : tint(colors.primary, 0.6),
1622
- todayLineColor: c.yellow,
1623
- // ── Quadrant ──
1624
- quadrant1Fill: isDark ? shade(c.green, colors.bg, 0.75) : tint(c.green, 0.75),
1625
- quadrant2Fill: isDark ? shade(c.blue, colors.bg, 0.75) : tint(c.blue, 0.75),
1626
- quadrant3Fill: isDark ? shade(c.red, colors.bg, 0.75) : tint(c.red, 0.75),
1627
- quadrant4Fill: isDark ? shade(c.yellow, colors.bg, 0.75) : tint(c.yellow, 0.75),
1628
- quadrant1TextFill: colors.text,
1629
- quadrant2TextFill: colors.text,
1630
- quadrant3TextFill: colors.text,
1631
- quadrant4TextFill: colors.text,
1632
- quadrantPointFill: isDark ? c.cyan : c.blue,
1633
- quadrantPointTextFill: colors.text,
1634
- quadrantXAxisTextFill: colors.text,
1635
- quadrantYAxisTextFill: colors.text,
1636
- quadrantTitleFill: colors.text,
1637
- quadrantInternalBorderStrokeFill: colors.border,
1638
- quadrantExternalBorderStrokeFill: colors.border
1639
- };
1640
- }
1641
- function buildThemeCSS(palette, isDark) {
1642
- const base = `
1643
- .branchLabelBkg { fill: transparent !important; stroke: transparent !important; }
1644
- .commit-label-bkg { fill: transparent !important; stroke: transparent !important; }
1645
- .tag-label-bkg { fill: transparent !important; stroke: transparent !important; }
1646
-
1647
- /* GitGraph: ensure commit and branch label text matches palette */
1648
- .commit-label { fill: ${palette.text} !important; }
1649
- .branch-label { fill: ${palette.text} !important; }
1650
- .tag-label { fill: ${palette.text} !important; }
1651
- `;
1652
- if (!isDark) return base;
1653
- return base + `
1654
- /* Flowchart: ensure node and edge label text is readable */
1655
- .nodeLabel, .label { color: ${palette.text} !important; fill: ${palette.text} !important; }
1656
- .edgeLabel { color: ${palette.text} !important; fill: ${palette.text} !important; }
1657
- .edgeLabel .label { color: ${palette.text} !important; fill: ${palette.text} !important; }
1658
- `;
1659
- }
1660
- var init_mermaid_bridge = __esm({
1661
- "src/palettes/mermaid-bridge.ts"() {
1662
- "use strict";
1663
- init_color_utils();
1664
- }
1665
- });
1666
-
1667
1699
  // src/palettes/index.ts
1668
1700
  var palettes_exports = {};
1669
1701
  __export(palettes_exports, {
1670
1702
  boldPalette: () => boldPalette,
1671
- buildMermaidThemeVars: () => buildMermaidThemeVars,
1672
- buildThemeCSS: () => buildThemeCSS,
1673
1703
  catppuccinPalette: () => catppuccinPalette,
1674
1704
  contrastText: () => contrastText,
1675
1705
  draculaPalette: () => draculaPalette,
@@ -1683,7 +1713,6 @@ __export(palettes_exports, {
1683
1713
  hslToHex: () => hslToHex,
1684
1714
  isValidHex: () => isValidHex,
1685
1715
  monokaiPalette: () => monokaiPalette,
1686
- mute: () => mute,
1687
1716
  nordPalette: () => nordPalette,
1688
1717
  oneDarkPalette: () => oneDarkPalette,
1689
1718
  registerPalette: () => registerPalette,
@@ -1708,7 +1737,6 @@ var init_palettes = __esm({
1708
1737
  init_tokyo_night();
1709
1738
  init_dracula();
1710
1739
  init_monokai();
1711
- init_mermaid_bridge();
1712
1740
  }
1713
1741
  });
1714
1742
 
@@ -2100,7 +2128,7 @@ function measureLegendText(text, fontSize) {
2100
2128
  }
2101
2129
  return w;
2102
2130
  }
2103
- var LEGEND_HEIGHT, LEGEND_PILL_PAD, LEGEND_PILL_FONT_SIZE, LEGEND_CAPSULE_PAD, LEGEND_DOT_R, LEGEND_ENTRY_FONT_SIZE, LEGEND_ENTRY_DOT_GAP, LEGEND_ENTRY_TRAIL, LEGEND_GROUP_GAP, LEGEND_EYE_SIZE, LEGEND_EYE_GAP, LEGEND_ICON_W, LEGEND_MAX_ENTRY_ROWS, CHAR_W, DEFAULT_W, EYE_OPEN_PATH, EYE_CLOSED_PATH;
2131
+ var LEGEND_HEIGHT, LEGEND_PILL_PAD, LEGEND_PILL_FONT_SIZE, LEGEND_CAPSULE_PAD, LEGEND_DOT_R, LEGEND_ENTRY_FONT_SIZE, LEGEND_ENTRY_DOT_GAP, LEGEND_ENTRY_TRAIL, LEGEND_GROUP_GAP, LEGEND_EYE_SIZE, LEGEND_EYE_GAP, LEGEND_ICON_W, LEGEND_MAX_ENTRY_ROWS, CHAR_W, DEFAULT_W, EYE_OPEN_PATH, EYE_CLOSED_PATH, CONTROLS_ICON_PATH, LEGEND_TOGGLE_DOT_R, LEGEND_TOGGLE_OFF_OPACITY, LEGEND_GEAR_PILL_W;
2104
2132
  var init_legend_constants = __esm({
2105
2133
  "src/utils/legend-constants.ts"() {
2106
2134
  "use strict";
@@ -2207,6 +2235,10 @@ var init_legend_constants = __esm({
2207
2235
  DEFAULT_W = 0.56;
2208
2236
  EYE_OPEN_PATH = "M1 7s2.5-5 6-5 6 5 6 5-2.5 5-6 5-6-5-6-5z M7 9.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z";
2209
2237
  EYE_CLOSED_PATH = "M2.5 2.5l9 9 M1.5 7s2.2-4 5.5-4c1.2 0 2.2.5 3 1.1 M12.5 7s-2.2 4-5.5 4c-1.2 0-2.2-.5-3-1.1";
2238
+ CONTROLS_ICON_PATH = "M5.6 1.7L8.4 1.7L7.9 3.6L9.5 4.5L10.9 3.1L12.3 5.6L10.4 6.1L10.4 7.9L12.3 8.4L10.9 10.9L9.5 9.5L7.9 10.4L8.4 12.3L5.6 12.3L6.1 10.4L4.5 9.5L3.1 10.9L1.7 8.4L3.6 7.9L3.6 6.1L1.7 5.6L3.1 3.1L4.5 4.5L6.1 3.6ZM5 7a2 2 0 1 0 4 0a2 2 0 1 0-4 0Z";
2239
+ LEGEND_TOGGLE_DOT_R = LEGEND_DOT_R;
2240
+ LEGEND_TOGGLE_OFF_OPACITY = 0.4;
2241
+ LEGEND_GEAR_PILL_W = 14 + LEGEND_PILL_PAD;
2210
2242
  }
2211
2243
  });
2212
2244
 
@@ -2280,6 +2312,63 @@ function capsuleWidth(name, entries, containerWidth, addonWidth = 0) {
2280
2312
  visibleEntries: entries.length
2281
2313
  };
2282
2314
  }
2315
+ function controlsGroupCapsuleWidth(toggles) {
2316
+ let w = LEGEND_CAPSULE_PAD * 2 + LEGEND_GEAR_PILL_W + 4;
2317
+ for (const t of toggles) {
2318
+ w += LEGEND_TOGGLE_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + measureLegendText(t.label, LEGEND_ENTRY_FONT_SIZE) + LEGEND_ENTRY_TRAIL;
2319
+ }
2320
+ return w;
2321
+ }
2322
+ function buildControlsGroupLayout(config, state) {
2323
+ const cg = config.controlsGroup;
2324
+ if (!cg || cg.toggles.length === 0) return void 0;
2325
+ const expanded = !!state.controlsExpanded;
2326
+ const pillH = LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2;
2327
+ if (!expanded) {
2328
+ return {
2329
+ x: 0,
2330
+ y: 0,
2331
+ width: LEGEND_GEAR_PILL_W,
2332
+ height: LEGEND_HEIGHT,
2333
+ expanded: false,
2334
+ pill: { x: 0, y: 0, width: LEGEND_GEAR_PILL_W, height: LEGEND_HEIGHT },
2335
+ toggles: []
2336
+ };
2337
+ }
2338
+ const capsuleW = controlsGroupCapsuleWidth(cg.toggles);
2339
+ const toggleLayouts = [];
2340
+ let tx = LEGEND_CAPSULE_PAD + LEGEND_GEAR_PILL_W + 4;
2341
+ for (const toggle of cg.toggles) {
2342
+ const dotCx = tx + LEGEND_TOGGLE_DOT_R;
2343
+ const dotCy = LEGEND_HEIGHT / 2;
2344
+ const textX = tx + LEGEND_TOGGLE_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP;
2345
+ const textY = LEGEND_HEIGHT / 2;
2346
+ toggleLayouts.push({
2347
+ id: toggle.id,
2348
+ label: toggle.label,
2349
+ active: toggle.active,
2350
+ dotCx,
2351
+ dotCy,
2352
+ textX,
2353
+ textY
2354
+ });
2355
+ tx += LEGEND_TOGGLE_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + measureLegendText(toggle.label, LEGEND_ENTRY_FONT_SIZE) + LEGEND_ENTRY_TRAIL;
2356
+ }
2357
+ return {
2358
+ x: 0,
2359
+ y: 0,
2360
+ width: capsuleW,
2361
+ height: LEGEND_HEIGHT,
2362
+ expanded: true,
2363
+ pill: {
2364
+ x: LEGEND_CAPSULE_PAD,
2365
+ y: LEGEND_CAPSULE_PAD,
2366
+ width: LEGEND_GEAR_PILL_W - LEGEND_CAPSULE_PAD * 2,
2367
+ height: pillH
2368
+ },
2369
+ toggles: toggleLayouts
2370
+ };
2371
+ }
2283
2372
  function computeLegendLayout(config, state, containerWidth) {
2284
2373
  const { groups, controls: configControls, mode } = config;
2285
2374
  const isExport = mode === "inline";
@@ -2294,8 +2383,9 @@ function computeLegendLayout(config, state, containerWidth) {
2294
2383
  activeCapsule: void 0
2295
2384
  };
2296
2385
  }
2386
+ const controlsGroupLayout = isExport ? void 0 : buildControlsGroupLayout(config, state);
2297
2387
  const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0);
2298
- if (visibleGroups.length === 0 && (!configControls || configControls.length === 0)) {
2388
+ if (visibleGroups.length === 0 && (!configControls || configControls.length === 0) && !controlsGroupLayout) {
2299
2389
  return {
2300
2390
  height: 0,
2301
2391
  width: 0,
@@ -2359,7 +2449,8 @@ function computeLegendLayout(config, state, containerWidth) {
2359
2449
  if (totalControlsW > 0) totalControlsW -= CONTROL_GAP;
2360
2450
  }
2361
2451
  const controlsSpace = totalControlsW > 0 ? totalControlsW + LEGEND_GROUP_GAP * 2 : 0;
2362
- const groupAvailW = containerWidth - controlsSpace;
2452
+ const gearSpace = controlsGroupLayout ? controlsGroupLayout.width + LEGEND_GROUP_GAP : 0;
2453
+ const groupAvailW = containerWidth - controlsSpace - gearSpace;
2363
2454
  const pills = [];
2364
2455
  let activeCapsule;
2365
2456
  for (const g of visibleGroups) {
@@ -2368,7 +2459,7 @@ function computeLegendLayout(config, state, containerWidth) {
2368
2459
  if (isActive) {
2369
2460
  activeCapsule = buildCapsuleLayout(
2370
2461
  g,
2371
- containerWidth,
2462
+ groupAvailW,
2372
2463
  config.capsulePillAddonWidth ?? 0
2373
2464
  );
2374
2465
  } else {
@@ -2391,7 +2482,8 @@ function computeLegendLayout(config, state, containerWidth) {
2391
2482
  groupAvailW,
2392
2483
  containerWidth,
2393
2484
  totalControlsW,
2394
- alignLeft
2485
+ alignLeft,
2486
+ controlsGroupLayout
2395
2487
  );
2396
2488
  const height = rows.length * LEGEND_HEIGHT;
2397
2489
  const width = containerWidth;
@@ -2401,7 +2493,8 @@ function computeLegendLayout(config, state, containerWidth) {
2401
2493
  rows,
2402
2494
  activeCapsule,
2403
2495
  controls: controlLayouts,
2404
- pills
2496
+ pills,
2497
+ controlsGroup: controlsGroupLayout
2405
2498
  };
2406
2499
  }
2407
2500
  function buildCapsuleLayout(group, containerWidth, addonWidth = 0) {
@@ -2465,19 +2558,27 @@ function buildCapsuleLayout(group, containerWidth, addonWidth = 0) {
2465
2558
  addonX: addonWidth > 0 ? LEGEND_CAPSULE_PAD + pw + 4 : void 0
2466
2559
  };
2467
2560
  }
2468
- function layoutRows(activeCapsule, pills, controls, groupAvailW, containerWidth, totalControlsW, alignLeft = false) {
2561
+ function layoutRows(activeCapsule, pills, controls, groupAvailW, containerWidth, totalControlsW, alignLeft = false, controlsGroup) {
2469
2562
  const rows = [];
2470
2563
  const groupItems = [];
2471
2564
  if (activeCapsule) groupItems.push(activeCapsule);
2472
2565
  groupItems.push(...pills);
2566
+ const gearW = controlsGroup ? controlsGroup.width + LEGEND_GROUP_GAP : 0;
2473
2567
  let currentRowItems = [];
2474
2568
  let currentRowW = 0;
2475
2569
  let rowY = 0;
2476
2570
  for (const item of groupItems) {
2477
2571
  const itemW = item.width + LEGEND_GROUP_GAP;
2478
2572
  if (currentRowW + item.width > groupAvailW && currentRowItems.length > 0) {
2479
- if (!alignLeft)
2480
- centerRowItems(currentRowItems, containerWidth, totalControlsW);
2573
+ if (!alignLeft) {
2574
+ const rowGearW = rows.length === 0 ? gearW : 0;
2575
+ centerRowItems(
2576
+ currentRowItems,
2577
+ containerWidth,
2578
+ totalControlsW,
2579
+ rowGearW
2580
+ );
2581
+ }
2481
2582
  rows.push({ y: rowY, items: currentRowItems });
2482
2583
  rowY += LEGEND_HEIGHT;
2483
2584
  currentRowItems = [];
@@ -2505,19 +2606,32 @@ function layoutRows(activeCapsule, pills, controls, groupAvailW, containerWidth,
2505
2606
  }
2506
2607
  }
2507
2608
  if (currentRowItems.length > 0) {
2508
- centerRowItems(currentRowItems, containerWidth, totalControlsW);
2609
+ centerRowItems(currentRowItems, containerWidth, totalControlsW, gearW);
2509
2610
  rows.push({ y: rowY, items: currentRowItems });
2510
2611
  }
2612
+ if (controlsGroup) {
2613
+ const row0Items = rows[0]?.items ?? [];
2614
+ const groupItemsInRow0 = row0Items.filter(
2615
+ (it) => "groupName" in it
2616
+ );
2617
+ if (groupItemsInRow0.length > 0) {
2618
+ const last = groupItemsInRow0[groupItemsInRow0.length - 1];
2619
+ controlsGroup.x = last.x + last.width + LEGEND_GROUP_GAP;
2620
+ } else {
2621
+ controlsGroup.x = 0;
2622
+ }
2623
+ controlsGroup.y = 0;
2624
+ }
2511
2625
  if (rows.length === 0) {
2512
2626
  rows.push({ y: 0, items: [] });
2513
2627
  }
2514
2628
  return rows;
2515
2629
  }
2516
- function centerRowItems(items, containerWidth, totalControlsW) {
2630
+ function centerRowItems(items, containerWidth, totalControlsW, controlsGroupW = 0) {
2517
2631
  const groupItems = items.filter((it) => "groupName" in it);
2518
2632
  if (groupItems.length === 0) return;
2519
2633
  const totalGroupW = groupItems.reduce((s, it) => s + it.width, 0) + (groupItems.length - 1) * LEGEND_GROUP_GAP;
2520
- const availW = containerWidth - (totalControlsW > 0 ? totalControlsW + LEGEND_GROUP_GAP * 2 : 0);
2634
+ const availW = containerWidth - (totalControlsW > 0 ? totalControlsW + LEGEND_GROUP_GAP * 2 : 0) - controlsGroupW;
2521
2635
  const offset = Math.max(0, (availW - totalGroupW) / 2);
2522
2636
  let x = offset;
2523
2637
  for (const item of groupItems) {
@@ -2575,6 +2689,17 @@ function renderLegendD3(container, config, state, palette, isDark, callbacks, co
2575
2689
  for (const pill of currentLayout.pills) {
2576
2690
  renderPill(legendG, pill, palette, groupBg, callbacks);
2577
2691
  }
2692
+ if (currentLayout.controlsGroup) {
2693
+ renderControlsGroup(
2694
+ legendG,
2695
+ currentLayout.controlsGroup,
2696
+ palette,
2697
+ groupBg,
2698
+ pillBorder,
2699
+ callbacks,
2700
+ config
2701
+ );
2702
+ }
2578
2703
  for (const ctrl of currentLayout.controls) {
2579
2704
  renderControl(
2580
2705
  legendG,
@@ -2689,6 +2814,57 @@ function renderControl(parent, ctrl, palette, _groupBg, pillBorder, _isDark, con
2689
2814
  g.on("click", () => onClick());
2690
2815
  }
2691
2816
  }
2817
+ function renderControlsGroup(parent, layout, palette, groupBg, pillBorder, callbacks, config) {
2818
+ const g = parent.append("g").attr("transform", `translate(${layout.x},${layout.y})`).attr("data-legend-controls", layout.expanded ? "expanded" : "collapsed").attr("data-export-ignore", "true").style("cursor", "pointer");
2819
+ if (!layout.expanded) {
2820
+ g.append("rect").attr("width", layout.width).attr("height", layout.height).attr("rx", layout.height / 2).attr("fill", groupBg);
2821
+ const iconSize = 14;
2822
+ const iconX = (layout.width - iconSize) / 2;
2823
+ const iconY = (layout.height - iconSize) / 2;
2824
+ g.append("path").attr("d", CONTROLS_ICON_PATH).attr("transform", `translate(${iconX},${iconY})`).attr("fill", palette.textMuted).attr("fill-rule", "evenodd").attr("pointer-events", "none");
2825
+ if (callbacks?.onControlsExpand) {
2826
+ const cb = callbacks.onControlsExpand;
2827
+ g.on("click", () => cb());
2828
+ }
2829
+ } else {
2830
+ const pill = layout.pill;
2831
+ g.append("rect").attr("width", layout.width).attr("height", layout.height).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
2832
+ const pillG = g.append("g").attr("class", "controls-gear-pill").style("cursor", "pointer");
2833
+ pillG.append("rect").attr("x", pill.x).attr("y", pill.y).attr("width", pill.width).attr("height", pill.height).attr("rx", pill.height / 2).attr("fill", palette.bg);
2834
+ pillG.append("rect").attr("x", pill.x).attr("y", pill.y).attr("width", pill.width).attr("height", pill.height).attr("rx", pill.height / 2).attr("fill", "none").attr("stroke", pillBorder).attr("stroke-width", 0.75);
2835
+ const iconSize = 14;
2836
+ const iconX = pill.x + (pill.width - iconSize) / 2;
2837
+ const iconY = pill.y + (pill.height - iconSize) / 2;
2838
+ pillG.append("path").attr("d", CONTROLS_ICON_PATH).attr("transform", `translate(${iconX},${iconY})`).attr("fill", palette.text).attr("fill-rule", "evenodd").attr("pointer-events", "none");
2839
+ if (callbacks?.onControlsExpand) {
2840
+ const cb = callbacks.onControlsExpand;
2841
+ pillG.on("click", (event) => {
2842
+ event.stopPropagation();
2843
+ cb();
2844
+ });
2845
+ }
2846
+ const toggles = config?.controlsGroup?.toggles ?? [];
2847
+ for (const tl of layout.toggles) {
2848
+ const toggle = toggles.find((t) => t.id === tl.id);
2849
+ const entryG = g.append("g").attr("data-controls-toggle", tl.id).style("cursor", "pointer");
2850
+ if (tl.active) {
2851
+ entryG.append("circle").attr("cx", tl.dotCx).attr("cy", tl.dotCy).attr("r", LEGEND_TOGGLE_DOT_R).attr("fill", palette.primary ?? palette.text);
2852
+ } else {
2853
+ entryG.append("circle").attr("cx", tl.dotCx).attr("cy", tl.dotCy).attr("r", LEGEND_TOGGLE_DOT_R).attr("fill", "none").attr("stroke", palette.textMuted).attr("stroke-width", 1);
2854
+ }
2855
+ entryG.append("text").attr("x", tl.textX).attr("y", tl.textY).attr("dominant-baseline", "central").attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).attr("opacity", tl.active ? 1 : LEGEND_TOGGLE_OFF_OPACITY).attr("font-family", FONT_FAMILY).text(tl.label);
2856
+ if (callbacks?.onControlsToggle && toggle) {
2857
+ const cb = callbacks.onControlsToggle;
2858
+ const id = tl.id;
2859
+ const newActive = !tl.active;
2860
+ entryG.on("click", (event) => {
2861
+ event.stopPropagation();
2862
+ cb(id, newActive);
2863
+ });
2864
+ }
2865
+ }
2866
+ }
2867
+ }
2692
2868
  var init_legend_d3 = __esm({
2693
2869
  "src/utils/legend-d3.ts"() {
2694
2870
  "use strict";
@@ -2985,61 +3161,6 @@ var init_participant_inference = __esm({
2985
3161
  }
2986
3162
  });
2987
3163
 
2988
- // src/utils/arrows.ts
2989
- function parseArrow(line10) {
2990
- if (BIDI_SYNC_RE.test(line10) || BIDI_ASYNC_RE.test(line10)) {
2991
- return {
2992
- error: "Bidirectional arrows are no longer supported. Use two separate lines: 'A -msg-> B' and 'B -msg-> A'"
2993
- };
2994
- }
2995
- if (RETURN_SYNC_LABELED_RE.test(line10) || RETURN_ASYNC_LABELED_RE.test(line10)) {
2996
- const m = line10.match(RETURN_SYNC_LABELED_RE) ?? line10.match(RETURN_ASYNC_LABELED_RE);
2997
- const from = m[3];
2998
- const to = m[1];
2999
- const label = m[2].trim();
3000
- return {
3001
- error: `Left-pointing arrows are no longer supported. Write '${from} -${label}-> ${to}' instead`
3002
- };
3003
- }
3004
- const patterns = [
3005
- { re: SYNC_LABELED_RE, async: false },
3006
- { re: ASYNC_LABELED_RE, async: true }
3007
- ];
3008
- for (const { re, async: isAsync } of patterns) {
3009
- const m = line10.match(re);
3010
- if (!m) continue;
3011
- const label = m[2].trim();
3012
- if (!label) return null;
3013
- for (const arrow of ARROW_CHARS) {
3014
- if (label.includes(arrow)) {
3015
- return {
3016
- error: "Arrow characters (->, ~>) are not allowed inside labels"
3017
- };
3018
- }
3019
- }
3020
- return {
3021
- from: m[1],
3022
- to: m[3],
3023
- label,
3024
- async: isAsync
3025
- };
3026
- }
3027
- return null;
3028
- }
3029
- var SYNC_LABELED_RE, ASYNC_LABELED_RE, RETURN_SYNC_LABELED_RE, RETURN_ASYNC_LABELED_RE, BIDI_SYNC_RE, BIDI_ASYNC_RE, ARROW_CHARS;
3030
- var init_arrows = __esm({
3031
- "src/utils/arrows.ts"() {
3032
- "use strict";
3033
- SYNC_LABELED_RE = /^(.+?)\s*-(.+)->\s*(.+)$/;
3034
- ASYNC_LABELED_RE = /^(.+?)\s*~(.+)~>\s*(.+)$/;
3035
- RETURN_SYNC_LABELED_RE = /^(.+?)\s*<-(.+)-\s*(.+)$/;
3036
- RETURN_ASYNC_LABELED_RE = /^(.+?)\s*<~(.+)~\s*(.+)$/;
3037
- BIDI_SYNC_RE = /^(.+?)\s*<-(.+)->\s*(.+)$/;
3038
- BIDI_ASYNC_RE = /^(.+?)\s*<~(.+)~>\s*(.+)$/;
3039
- ARROW_CHARS = ["->", "~>"];
3040
- }
3041
- });
3042
-
3043
3164
  // src/sequence/parser.ts
3044
3165
  var parser_exports = {};
3045
3166
  __export(parser_exports, {
@@ -3242,7 +3363,13 @@ function parseSequenceDgmo(content) {
3242
3363
  const groupName = groupMatch[1].trim();
3243
3364
  const groupColor = groupMatch[2]?.trim();
3244
3365
  let groupMeta;
3245
- const afterBracket = groupMatch[3]?.trim() || "";
3366
+ let afterBracket = groupMatch[3]?.trim() || "";
3367
+ let isCollapsed = false;
3368
+ const collapseMatch = afterBracket.match(/^collapse\b/i);
3369
+ if (collapseMatch) {
3370
+ isCollapsed = true;
3371
+ afterBracket = afterBracket.slice(collapseMatch[0].length).trim();
3372
+ }
3246
3373
  if (afterBracket.startsWith("|")) {
3247
3374
  const segments = afterBracket.split("|");
3248
3375
  const meta = parsePipeMetadata(
@@ -3263,7 +3390,8 @@ function parseSequenceDgmo(content) {
3263
3390
  name: groupName,
3264
3391
  participantIds: [],
3265
3392
  lineNumber,
3266
- ...groupMeta ? { metadata: groupMeta } : {}
3393
+ ...groupMeta ? { metadata: groupMeta } : {},
3394
+ ...isCollapsed ? { collapsed: true } : {}
3267
3395
  };
3268
3396
  result.groups.push(activeGroup);
3269
3397
  continue;
@@ -3585,8 +3713,11 @@ function parseSequenceDgmo(content) {
3585
3713
  }
3586
3714
  if (labeledArrow) {
3587
3715
  contentStarted = true;
3588
- const { from, to, label, async: isAsync } = labeledArrow;
3716
+ const { from, to, label: rawLabel, async: isAsync } = labeledArrow;
3589
3717
  lastMsgFrom = from;
3718
+ const labelResult = parseInArrowLabel(rawLabel, lineNumber);
3719
+ labelResult.diagnostics.forEach((d) => result.diagnostics.push(d));
3720
+ const label = labelResult.label ?? rawLabel;
3590
3721
  const msg = {
3591
3722
  from,
3592
3723
  to,
@@ -3986,42 +4117,52 @@ function parseNodeRef(text, palette) {
3986
4117
  }
3987
4118
  function splitArrows(line10) {
3988
4119
  const segments = [];
3989
- let lastIndex = 0;
3990
4120
  const arrowPositions = [];
3991
4121
  let searchFrom = 0;
4122
+ let scanFloor = 0;
3992
4123
  while (searchFrom < line10.length) {
3993
4124
  const idx = line10.indexOf("->", searchFrom);
3994
4125
  if (idx === -1) break;
3995
- let arrowStart = idx;
4126
+ let runStart = idx;
4127
+ while (runStart > scanFloor && line10[runStart - 1] === "-") runStart--;
4128
+ const arrowEnd = idx + 2;
4129
+ let arrowStart;
3996
4130
  let label;
3997
4131
  let color;
3998
- if (idx > 0 && line10[idx - 1] !== " " && line10[idx - 1] !== " ") {
3999
- let scanBack = idx - 1;
4000
- while (scanBack > 0 && line10[scanBack] !== "-") {
4001
- scanBack--;
4002
- }
4003
- if (line10[scanBack] === "-" && (scanBack === 0 || /\s/.test(line10[scanBack - 1]))) {
4004
- let arrowContent = line10.substring(scanBack + 1, idx);
4005
- if (arrowContent.endsWith("-"))
4006
- arrowContent = arrowContent.slice(0, -1);
4007
- const colorMatch = arrowContent.match(/\(([^)]+)\)\s*$/);
4008
- if (colorMatch) {
4009
- color = colorMatch[1].trim();
4010
- const labelPart = arrowContent.substring(0, colorMatch.index).trim();
4011
- if (labelPart) label = labelPart;
4012
- } else {
4013
- const labelPart = arrowContent.trim();
4014
- if (labelPart) label = labelPart;
4015
- }
4016
- arrowStart = scanBack;
4132
+ let openingStart = -1;
4133
+ for (let i = scanFloor; i < runStart; i++) {
4134
+ if (line10[i] !== "-") continue;
4135
+ const prevIsWsOrFloor = i === 0 || i === scanFloor || /\s/.test(line10[i - 1]);
4136
+ if (prevIsWsOrFloor) {
4137
+ openingStart = i;
4138
+ break;
4017
4139
  }
4018
4140
  }
4019
- arrowPositions.push({ start: arrowStart, end: idx + 2, label, color });
4020
- searchFrom = idx + 2;
4141
+ if (openingStart !== -1) {
4142
+ let openingEnd = openingStart;
4143
+ while (openingEnd < runStart && line10[openingEnd] === "-") openingEnd++;
4144
+ const arrowContent = line10.substring(openingEnd, runStart);
4145
+ const colorMatch = arrowContent.match(/\(([^)]+)\)\s*$/);
4146
+ if (colorMatch) {
4147
+ color = colorMatch[1].trim();
4148
+ const labelPart = arrowContent.substring(0, colorMatch.index).trim();
4149
+ if (labelPart) label = labelPart;
4150
+ } else {
4151
+ const labelPart = arrowContent.trim();
4152
+ if (labelPart) label = labelPart;
4153
+ }
4154
+ arrowStart = openingStart;
4155
+ } else {
4156
+ arrowStart = runStart;
4157
+ }
4158
+ arrowPositions.push({ start: arrowStart, end: arrowEnd, label, color });
4159
+ searchFrom = arrowEnd;
4160
+ scanFloor = arrowEnd;
4021
4161
  }
4022
4162
  if (arrowPositions.length === 0) {
4023
4163
  return [line10];
4024
4164
  }
4165
+ let lastIndex = 0;
4025
4166
  for (let i = 0; i < arrowPositions.length; i++) {
4026
4167
  const arrow = arrowPositions[i];
4027
4168
  const beforeText = line10.substring(lastIndex, arrow.start).trim();
@@ -4044,20 +4185,26 @@ function splitArrows(line10) {
4044
4185
  }
4045
4186
  function parseArrowToken(token, palette, lineNumber, diagnostics) {
4046
4187
  if (token === "->") return {};
4047
- const colorOnly = token.match(/^-\(([^)]+)\)->$/);
4048
- if (colorOnly) {
4049
- return {
4050
- color: resolveColorWithDiagnostic(
4051
- colorOnly[1].trim(),
4052
- lineNumber,
4053
- diagnostics,
4054
- palette
4055
- )
4056
- };
4188
+ const bareParen = token.match(/^-(\([A-Za-z]+\))->$/);
4189
+ if (bareParen) {
4190
+ const colorName = matchColorParens(bareParen[1]);
4191
+ if (colorName) {
4192
+ return {
4193
+ color: resolveColorWithDiagnostic(
4194
+ colorName,
4195
+ lineNumber,
4196
+ diagnostics,
4197
+ palette
4198
+ )
4199
+ };
4200
+ }
4057
4201
  }
4058
4202
  const m = token.match(/^-(.+?)(?:\(([^)]+)\))?->$/);
4059
4203
  if (m) {
4060
- const label = m[1]?.trim() || void 0;
4204
+ const rawLabel = m[1] ?? "";
4205
+ const labelResult = parseInArrowLabel(rawLabel, lineNumber);
4206
+ diagnostics.push(...labelResult.diagnostics);
4207
+ const label = labelResult.label;
4061
4208
  let color = m[2] ? resolveColorWithDiagnostic(
4062
4209
  m[2].trim(),
4063
4210
  lineNumber,
@@ -4285,6 +4432,7 @@ var init_flowchart_parser = __esm({
4285
4432
  "use strict";
4286
4433
  init_colors();
4287
4434
  init_diagnostics();
4435
+ init_arrows();
4288
4436
  init_parsing();
4289
4437
  NODE_ID_RE = /^([a-zA-Z_][\w-]*)[\s([</{]/;
4290
4438
  }
@@ -4300,35 +4448,45 @@ function splitArrows2(line10) {
4300
4448
  const segments = [];
4301
4449
  const arrowPositions = [];
4302
4450
  let searchFrom = 0;
4451
+ let scanFloor = 0;
4303
4452
  while (searchFrom < line10.length) {
4304
4453
  const idx = line10.indexOf("->", searchFrom);
4305
4454
  if (idx === -1) break;
4306
- let arrowStart = idx;
4455
+ let runStart = idx;
4456
+ while (runStart > scanFloor && line10[runStart - 1] === "-") runStart--;
4457
+ const arrowEnd = idx + 2;
4458
+ let arrowStart;
4307
4459
  let label;
4308
4460
  let color;
4309
- if (idx > 0 && line10[idx - 1] !== " " && line10[idx - 1] !== " ") {
4310
- let scanBack = idx - 1;
4311
- while (scanBack > 0 && line10[scanBack] !== "-") {
4312
- scanBack--;
4313
- }
4314
- if (line10[scanBack] === "-" && (scanBack === 0 || /\s/.test(line10[scanBack - 1]))) {
4315
- let arrowContent = line10.substring(scanBack + 1, idx);
4316
- if (arrowContent.endsWith("-"))
4317
- arrowContent = arrowContent.slice(0, -1);
4318
- const colorMatch = arrowContent.match(/\(([^)]+)\)\s*$/);
4319
- if (colorMatch) {
4320
- color = colorMatch[1].trim();
4321
- const labelPart = arrowContent.substring(0, colorMatch.index).trim();
4322
- if (labelPart) label = labelPart;
4323
- } else {
4324
- const labelPart = arrowContent.trim();
4325
- if (labelPart) label = labelPart;
4326
- }
4327
- arrowStart = scanBack;
4461
+ let openingStart = -1;
4462
+ for (let i = scanFloor; i < runStart; i++) {
4463
+ if (line10[i] !== "-") continue;
4464
+ const prevIsWsOrFloor = i === 0 || i === scanFloor || /\s/.test(line10[i - 1]);
4465
+ if (prevIsWsOrFloor) {
4466
+ openingStart = i;
4467
+ break;
4328
4468
  }
4329
4469
  }
4330
- arrowPositions.push({ start: arrowStart, end: idx + 2, label, color });
4331
- searchFrom = idx + 2;
4470
+ if (openingStart !== -1) {
4471
+ let openingEnd = openingStart;
4472
+ while (openingEnd < runStart && line10[openingEnd] === "-") openingEnd++;
4473
+ const arrowContent = line10.substring(openingEnd, runStart);
4474
+ const colorMatch = arrowContent.match(/\(([^)]+)\)\s*$/);
4475
+ if (colorMatch) {
4476
+ color = colorMatch[1].trim();
4477
+ const labelPart = arrowContent.substring(0, colorMatch.index).trim();
4478
+ if (labelPart) label = labelPart;
4479
+ } else {
4480
+ const labelPart = arrowContent.trim();
4481
+ if (labelPart) label = labelPart;
4482
+ }
4483
+ arrowStart = openingStart;
4484
+ } else {
4485
+ arrowStart = runStart;
4486
+ }
4487
+ arrowPositions.push({ start: arrowStart, end: arrowEnd, label, color });
4488
+ searchFrom = arrowEnd;
4489
+ scanFloor = arrowEnd;
4332
4490
  }
4333
4491
  if (arrowPositions.length === 0) return [line10];
4334
4492
  let lastIndex = 0;
@@ -4350,19 +4508,26 @@ function splitArrows2(line10) {
4350
4508
  }
4351
4509
  function parseArrowToken2(token, palette, lineNumber, diagnostics) {
4352
4510
  if (token === "->") return {};
4353
- const colorOnly = token.match(/^-\(([^)]+)\)->$/);
4354
- if (colorOnly)
4355
- return {
4356
- color: resolveColorWithDiagnostic(
4357
- colorOnly[1].trim(),
4358
- lineNumber,
4359
- diagnostics,
4360
- palette
4361
- )
4362
- };
4511
+ const bareParen = token.match(/^-(\([A-Za-z]+\))->$/);
4512
+ if (bareParen) {
4513
+ const colorName = matchColorParens(bareParen[1]);
4514
+ if (colorName) {
4515
+ return {
4516
+ color: resolveColorWithDiagnostic(
4517
+ colorName,
4518
+ lineNumber,
4519
+ diagnostics,
4520
+ palette
4521
+ )
4522
+ };
4523
+ }
4524
+ }
4363
4525
  const m = token.match(/^-(.+?)(?:\(([^)]+)\))?->$/);
4364
4526
  if (m) {
4365
- const label = m[1]?.trim() || void 0;
4527
+ const rawLabel = m[1] ?? "";
4528
+ const labelResult = parseInArrowLabel(rawLabel, lineNumber);
4529
+ diagnostics.push(...labelResult.diagnostics);
4530
+ const label = labelResult.label;
4366
4531
  const color = m[2] ? resolveColorWithDiagnostic(
4367
4532
  m[2].trim(),
4368
4533
  lineNumber,
@@ -4603,6 +4768,7 @@ var init_state_parser = __esm({
4603
4768
  "use strict";
4604
4769
  init_colors();
4605
4770
  init_diagnostics();
4771
+ init_arrows();
4606
4772
  init_parsing();
4607
4773
  PSEUDOSTATE_ID = "pseudostate:[*]";
4608
4774
  PSEUDOSTATE_LABEL = "[*]";
@@ -4759,6 +4925,11 @@ function parseClassDiagram(content, palette) {
4759
4925
  const targetName = indentRel[2];
4760
4926
  const label = indentRel[3]?.trim();
4761
4927
  getOrCreateClass(targetName, lineNumber);
4928
+ if (label) {
4929
+ result.diagnostics.push(
4930
+ ...validateLabelCharacters(label, lineNumber)
4931
+ );
4932
+ }
4762
4933
  result.relationships.push({
4763
4934
  source: currentClass.id,
4764
4935
  target: classId(targetName),
@@ -4933,6 +5104,7 @@ var init_parser2 = __esm({
4933
5104
  "use strict";
4934
5105
  init_colors();
4935
5106
  init_diagnostics();
5107
+ init_arrows();
4936
5108
  init_parsing();
4937
5109
  CLASS_DECL_RE = /^(?:(abstract|interface|enum)\s+)?([A-Z][A-Za-z0-9_]*)(?:\s+(extends|implements)\s+([A-Z][A-Za-z0-9_]*))?(?:\s+\[(abstract|interface|enum)\])?(?:\s+\(([^)]+)\))?\s*$/;
4938
5110
  INDENT_REL_ARROW_RE = /^(--\|>|\.\.\|>|\*--|o--|\.\.>|->)\s*([A-Z][A-Za-z0-9_]*)(?:\s+:?\s*(.+))?$/;
@@ -4972,12 +5144,18 @@ function parseRelationship(trimmed, lineNumber, pushError) {
4972
5144
  const fromCard = parseCardSide(sym[2]);
4973
5145
  const toCard = parseCardSide(sym[3]);
4974
5146
  if (fromCard && toCard) {
5147
+ const label = sym[5]?.trim();
5148
+ if (label) {
5149
+ validateLabelCharacters(label, lineNumber).forEach(
5150
+ (d) => pushError(d.line, d.message)
5151
+ );
5152
+ }
4975
5153
  return {
4976
5154
  source: sym[1],
4977
5155
  target: sym[4],
4978
5156
  from: fromCard,
4979
5157
  to: toCard,
4980
- label: sym[5]?.trim()
5158
+ label
4981
5159
  };
4982
5160
  }
4983
5161
  }
@@ -5140,11 +5318,17 @@ function parseERDiagram(content, palette) {
5140
5318
  if (fromCard && toCard) {
5141
5319
  const targetName = indentRel[4];
5142
5320
  getOrCreateTable(targetName, lineNumber);
5321
+ const rawLabel = indentRel[2]?.trim();
5322
+ if (rawLabel) {
5323
+ result.diagnostics.push(
5324
+ ...validateLabelCharacters(rawLabel, lineNumber)
5325
+ );
5326
+ }
5143
5327
  result.relationships.push({
5144
5328
  source: currentTable.id,
5145
5329
  target: tableId(targetName),
5146
5330
  cardinality: { from: fromCard, to: toCard },
5147
- ...indentRel[2]?.trim() && { label: indentRel[2].trim() },
5331
+ ...rawLabel && { label: rawLabel },
5148
5332
  lineNumber
5149
5333
  });
5150
5334
  }
@@ -5308,6 +5492,7 @@ var init_parser3 = __esm({
5308
5492
  "use strict";
5309
5493
  init_colors();
5310
5494
  init_diagnostics();
5495
+ init_arrows();
5311
5496
  init_parsing();
5312
5497
  init_tag_groups();
5313
5498
  TABLE_DECL_RE = /^([a-zA-Z_]\w*)(?:\s*\(([^)]+)\))?(?:\s*\|(.+))?$/;
@@ -5810,7 +5995,28 @@ var init_legend_svg = __esm({
5810
5995
  });
5811
5996
 
5812
5997
  // src/echarts.ts
5813
- import * as echarts from "echarts";
5998
+ import * as echarts from "echarts/core";
5999
+ import {
6000
+ BarChart,
6001
+ LineChart,
6002
+ PieChart,
6003
+ ScatterChart,
6004
+ RadarChart,
6005
+ SankeyChart,
6006
+ GraphChart,
6007
+ HeatmapChart,
6008
+ FunnelChart
6009
+ } from "echarts/charts";
6010
+ import {
6011
+ GridComponent,
6012
+ TitleComponent,
6013
+ TooltipComponent,
6014
+ LegendComponent,
6015
+ RadarComponent,
6016
+ VisualMapComponent,
6017
+ GraphicComponent
6018
+ } from "echarts/components";
6019
+ import { SVGRenderer } from "echarts/renderers";
5814
6020
  function parseScatterRow(line10, palette, currentCategory, lineNumber) {
5815
6021
  const dataRow = parseDataRowValues(line10, { multiValue: true });
5816
6022
  if (!dataRow || dataRow.values.length < 2) return null;
@@ -7829,7 +8035,7 @@ function buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor,
7829
8035
  series
7830
8036
  };
7831
8037
  }
7832
- async function renderExtendedChartForExport(content, theme, palette, options) {
8038
+ async function renderExtendedChartForExport(content, theme, palette) {
7833
8039
  const isDark = theme === "dark";
7834
8040
  const { getPalette: getPalette2 } = await Promise.resolve().then(() => (init_palettes(), palettes_exports));
7835
8041
  const effectivePalette = palette ?? (isDark ? getPalette2("nord").dark : getPalette2("nord").light);
@@ -7900,10 +8106,6 @@ async function renderExtendedChartForExport(content, theme, palette, options) {
7900
8106
  `$1<g transform="translate(0,${legendY})">${legendSvgStr}</g>`
7901
8107
  );
7902
8108
  }
7903
- if (options?.branding !== false) {
7904
- const brandColor = theme === "transparent" ? "#888" : effectivePalette.textMuted;
7905
- result = injectBranding(result, brandColor);
7906
- }
7907
8109
  return result;
7908
8110
  } finally {
7909
8111
  chart.dispose();
@@ -7914,7 +8116,6 @@ var init_echarts = __esm({
7914
8116
  "src/echarts.ts"() {
7915
8117
  "use strict";
7916
8118
  init_fonts();
7917
- init_branding();
7918
8119
  init_legend_svg();
7919
8120
  init_label_layout();
7920
8121
  init_palettes();
@@ -7924,6 +8125,25 @@ var init_echarts = __esm({
7924
8125
  init_colors();
7925
8126
  init_parsing();
7926
8127
  init_chart();
8128
+ echarts.use([
8129
+ BarChart,
8130
+ LineChart,
8131
+ PieChart,
8132
+ ScatterChart,
8133
+ RadarChart,
8134
+ SankeyChart,
8135
+ GraphChart,
8136
+ HeatmapChart,
8137
+ FunnelChart,
8138
+ GridComponent,
8139
+ TitleComponent,
8140
+ TooltipComponent,
8141
+ LegendComponent,
8142
+ RadarComponent,
8143
+ VisualMapComponent,
8144
+ GraphicComponent,
8145
+ SVGRenderer
8146
+ ]);
7927
8147
  EMPHASIS_SELF = {
7928
8148
  focus: "self",
7929
8149
  blurScope: "global",
@@ -8886,13 +9106,10 @@ function parseC4(content, palette) {
8886
9106
  labeledHandled = true;
8887
9107
  break;
8888
9108
  }
8889
- let label = rawLabel;
9109
+ const labelResult = parseInArrowLabel(rawLabel, lineNumber);
9110
+ labelResult.diagnostics.forEach((d) => result.diagnostics.push(d));
9111
+ const label = labelResult.label;
8890
9112
  let technology;
8891
- const techMatch = rawLabel.match(/\[([^\]]+)\]\s*$/);
8892
- if (techMatch) {
8893
- label = rawLabel.substring(0, techMatch.index).trim() || void 0;
8894
- technology = techMatch[1].trim();
8895
- }
8896
9113
  let target = targetBody;
8897
9114
  const pipeIdx = targetBody.indexOf("|");
8898
9115
  if (pipeIdx !== -1) {
@@ -9228,6 +9445,7 @@ var init_parser6 = __esm({
9228
9445
  "src/c4/parser.ts"() {
9229
9446
  "use strict";
9230
9447
  init_diagnostics();
9448
+ init_arrows();
9231
9449
  init_tag_groups();
9232
9450
  init_participant_inference();
9233
9451
  init_parsing();
@@ -10028,7 +10246,10 @@ function parseInfra(content) {
10028
10246
  }
10029
10247
  const asyncConnMatch = trimmed.match(ASYNC_CONNECTION_RE);
10030
10248
  if (asyncConnMatch) {
10031
- const label = asyncConnMatch[1]?.trim() || "";
10249
+ const rawLabel = asyncConnMatch[1] ?? "";
10250
+ const labelResult = parseInArrowLabel(rawLabel, lineNumber);
10251
+ result.diagnostics.push(...labelResult.diagnostics);
10252
+ const label = labelResult.label ?? "";
10032
10253
  const targetRaw = asyncConnMatch[2].trim();
10033
10254
  const pipeMeta = extractPipeMetadata(targetRaw);
10034
10255
  const targetName = pipeMeta.clean || targetRaw;
@@ -10088,7 +10309,10 @@ function parseInfra(content) {
10088
10309
  }
10089
10310
  const connMatch = trimmed.match(CONNECTION_RE);
10090
10311
  if (connMatch) {
10091
- const label = connMatch[1]?.trim() || "";
10312
+ const rawLabel = connMatch[1] ?? "";
10313
+ const labelResult = parseInArrowLabel(rawLabel, lineNumber);
10314
+ result.diagnostics.push(...labelResult.diagnostics);
10315
+ const label = labelResult.label ?? "";
10092
10316
  const targetRaw = connMatch[2].trim();
10093
10317
  const pipeMeta = extractPipeMetadata(targetRaw);
10094
10318
  const targetName = pipeMeta.clean || targetRaw;
@@ -10287,6 +10511,7 @@ var init_parser8 = __esm({
10287
10511
  "use strict";
10288
10512
  init_diagnostics();
10289
10513
  init_colors();
10514
+ init_arrows();
10290
10515
  init_parsing();
10291
10516
  init_tag_groups();
10292
10517
  init_types();
@@ -11818,7 +12043,9 @@ function parseEdgeLine(trimmed, lineNum, aliasMap, diagnostics) {
11818
12043
  const biLabeledMatch = trimmed.match(/^(.+?)\s*<-(.+)->\s*(.+)$/);
11819
12044
  if (biLabeledMatch) {
11820
12045
  const source2 = resolveEndpoint(biLabeledMatch[1].trim());
11821
- const label = biLabeledMatch[2].trim();
12046
+ const labelResult = parseInArrowLabel(biLabeledMatch[2], lineNum);
12047
+ diagnostics.push(...labelResult.diagnostics);
12048
+ const label = labelResult.label;
11822
12049
  let rest2 = biLabeledMatch[3].trim();
11823
12050
  let metadata2 = {};
11824
12051
  const pipeIdx2 = rest2.indexOf("|");
@@ -11839,7 +12066,7 @@ function parseEdgeLine(trimmed, lineNum, aliasMap, diagnostics) {
11839
12066
  return {
11840
12067
  source: source2,
11841
12068
  target: resolveEndpoint(rest2),
11842
- label: label || void 0,
12069
+ label,
11843
12070
  bidirectional: true,
11844
12071
  lineNumber: lineNum,
11845
12072
  metadata: metadata2
@@ -11876,7 +12103,9 @@ function parseEdgeLine(trimmed, lineNum, aliasMap, diagnostics) {
11876
12103
  const labeledMatch = trimmed.match(/^(.+?)\s+-(.+)->\s*(.+)$/);
11877
12104
  if (labeledMatch) {
11878
12105
  const source2 = resolveEndpoint(labeledMatch[1].trim());
11879
- const label = labeledMatch[2].trim();
12106
+ const labelResult = parseInArrowLabel(labeledMatch[2], lineNum);
12107
+ diagnostics.push(...labelResult.diagnostics);
12108
+ const label = labelResult.label;
11880
12109
  let rest2 = labeledMatch[3].trim();
11881
12110
  if (label) {
11882
12111
  let metadata2 = {};
@@ -11939,6 +12168,7 @@ var init_parser10 = __esm({
11939
12168
  "src/boxes-and-lines/parser.ts"() {
11940
12169
  "use strict";
11941
12170
  init_diagnostics();
12171
+ init_arrows();
11942
12172
  init_tag_groups();
11943
12173
  init_parsing();
11944
12174
  MAX_GROUP_DEPTH = 1;
@@ -12014,19 +12244,78 @@ function getAllChartTypes() {
12014
12244
  function parseDgmo(content) {
12015
12245
  const chartType = parseDgmoChartType(content);
12016
12246
  if (!chartType) {
12247
+ const colonDiag = detectColonChartType(content);
12248
+ if (colonDiag) {
12249
+ const fallback = parseVisualization(content).diagnostics;
12250
+ return { diagnostics: [colonDiag, ...fallback] };
12251
+ }
12017
12252
  return { diagnostics: parseVisualization(content).diagnostics };
12018
12253
  }
12019
12254
  const directParser = PARSE_DISPATCH.get(chartType);
12020
- if (directParser) return { diagnostics: directParser(content).diagnostics };
12255
+ if (directParser) {
12256
+ const result2 = directParser(content);
12257
+ return {
12258
+ diagnostics: [...result2.diagnostics, ...detectEmptyContent(content)]
12259
+ };
12260
+ }
12021
12261
  if (STANDARD_CHART_TYPES2.has(chartType)) {
12022
- return { diagnostics: parseChart(content).diagnostics };
12262
+ const result2 = parseChart(content);
12263
+ return {
12264
+ diagnostics: [...result2.diagnostics, ...detectEmptyContent(content)]
12265
+ };
12023
12266
  }
12024
12267
  if (ECHART_TYPES.has(chartType)) {
12025
- return { diagnostics: parseExtendedChart(content).diagnostics };
12268
+ const result2 = parseExtendedChart(content);
12269
+ return {
12270
+ diagnostics: [...result2.diagnostics, ...detectEmptyContent(content)]
12271
+ };
12272
+ }
12273
+ const result = parseVisualization(content);
12274
+ return {
12275
+ diagnostics: [...result.diagnostics, ...detectEmptyContent(content)]
12276
+ };
12277
+ }
12278
+ function detectColonChartType(content) {
12279
+ const lines = content.split("\n");
12280
+ for (let i = 0; i < lines.length; i++) {
12281
+ const trimmed = lines[i].trim();
12282
+ if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("//"))
12283
+ continue;
12284
+ const match = trimmed.match(/^(\w[\w-]*)\s*:\s*(.*)$/);
12285
+ if (!match) return null;
12286
+ const word = match[1].toLowerCase();
12287
+ const rest = match[2].trim();
12288
+ if (ALL_KNOWN_TYPES.has(word)) {
12289
+ const example = rest ? `${word} ${rest}` : word;
12290
+ return makeDgmoError(
12291
+ i + 1,
12292
+ `Remove the colon \u2014 use '${example}' instead of '${trimmed}'. DGMO chart types don't use colons.`
12293
+ );
12294
+ }
12295
+ const hint = suggest(word, [...ALL_KNOWN_TYPES]);
12296
+ if (hint) {
12297
+ return makeDgmoError(
12298
+ i + 1,
12299
+ `Unknown chart type: ${word}. ${hint} Also, DGMO chart types don't use colons.`
12300
+ );
12301
+ }
12302
+ return null;
12303
+ }
12304
+ return null;
12305
+ }
12306
+ function detectEmptyContent(content) {
12307
+ const lines = content.split("\n");
12308
+ const nonEmpty = lines.filter(
12309
+ (l) => l.trim() && !l.trim().startsWith("#") && !l.trim().startsWith("//")
12310
+ );
12311
+ if (nonEmpty.length <= 1) {
12312
+ return [
12313
+ makeDgmoError(1, "No content after chart type declaration.", "warning")
12314
+ ];
12026
12315
  }
12027
- return { diagnostics: parseVisualization(content).diagnostics };
12316
+ return [];
12028
12317
  }
12029
- var GANTT_DURATION_RE, GANTT_DATE_RE, C4_TYPE_RE, DATA_CHART_TYPES, VISUALIZATION_TYPES, DIAGRAM_TYPES, EXTENDED_CHART_TYPES, STANDARD_CHART_TYPES2, ECHART_TYPES, PARSE_DISPATCH;
12318
+ var GANTT_DURATION_RE, GANTT_DATE_RE, C4_TYPE_RE, DATA_CHART_TYPES, VISUALIZATION_TYPES, DIAGRAM_TYPES, EXTENDED_CHART_TYPES, STANDARD_CHART_TYPES2, ECHART_TYPES, PARSE_DISPATCH, ALL_KNOWN_TYPES;
12030
12319
  var init_dgmo_router = __esm({
12031
12320
  "src/dgmo-router.ts"() {
12032
12321
  "use strict";
@@ -12046,6 +12335,7 @@ var init_dgmo_router = __esm({
12046
12335
  init_parser9();
12047
12336
  init_parser10();
12048
12337
  init_parsing();
12338
+ init_diagnostics();
12049
12339
  GANTT_DURATION_RE = /^\d+(?:\.\d+)?(?:min|bd|d|w|m|q|y|h)(?:\?)?\s+/;
12050
12340
  GANTT_DATE_RE = /^\d{4}-\d{2}-\d{2}(?:\s\d{2}:\d{2})?\s+/;
12051
12341
  C4_TYPE_RE = /\bis\s+an?\s+(person|system|container|component)\b/i;
@@ -12129,6 +12419,11 @@ var init_dgmo_router = __esm({
12129
12419
  ["gantt", (c) => parseGantt(c)],
12130
12420
  ["boxes-and-lines", (c) => parseBoxesAndLines(c)]
12131
12421
  ]);
12422
+ ALL_KNOWN_TYPES = /* @__PURE__ */ new Set([
12423
+ ...DATA_CHART_TYPES,
12424
+ ...VISUALIZATION_TYPES,
12425
+ ...DIAGRAM_TYPES
12426
+ ]);
12132
12427
  }
12133
12428
  });
12134
12429
 
@@ -14303,7 +14598,6 @@ async function renderSitemapForExport(content, theme, palette) {
14303
14598
  const { parseSitemap: parseSitemap2 } = await Promise.resolve().then(() => (init_parser7(), parser_exports7));
14304
14599
  const { layoutSitemap: layoutSitemap2 } = await Promise.resolve().then(() => (init_layout2(), layout_exports2));
14305
14600
  const { getPalette: getPalette2 } = await Promise.resolve().then(() => (init_palettes(), palettes_exports));
14306
- const { injectBranding: injectBranding2 } = await Promise.resolve().then(() => (init_branding(), branding_exports));
14307
14601
  const isDark = theme === "dark";
14308
14602
  const effectivePalette = palette ?? (isDark ? getPalette2("nord").dark : getPalette2("nord").light);
14309
14603
  const parsed = parseSitemap2(content, effectivePalette);
@@ -14345,8 +14639,7 @@ async function renderSitemapForExport(content, theme, palette) {
14345
14639
  svgEl.style.fontFamily = FONT_FAMILY;
14346
14640
  const svgHtml = svgEl.outerHTML;
14347
14641
  document.body.removeChild(container);
14348
- const brandColor = theme === "transparent" ? "#888" : effectivePalette.textMuted;
14349
- return injectBranding2(svgHtml, brandColor);
14642
+ return svgHtml;
14350
14643
  }
14351
14644
  var DIAGRAM_PADDING2, MAX_SCALE2, TITLE_HEIGHT2, LABEL_FONT_SIZE2, META_FONT_SIZE2, META_LINE_HEIGHT4, HEADER_HEIGHT4, SEPARATOR_GAP4, EDGE_STROKE_WIDTH2, NODE_STROKE_WIDTH2, CARD_RADIUS2, CONTAINER_RADIUS2, CONTAINER_LABEL_FONT_SIZE2, CONTAINER_META_FONT_SIZE2, CONTAINER_META_LINE_HEIGHT4, CONTAINER_HEADER_HEIGHT2, ARROWHEAD_W, ARROWHEAD_H, EDGE_LABEL_FONT_SIZE, COLLAPSE_BAR_HEIGHT2, LEGEND_FIXED_GAP2, lineGenerator, lineGeneratorLinear;
14352
14645
  var init_renderer2 = __esm({
@@ -14550,7 +14843,6 @@ var init_mutations = __esm({
14550
14843
  // src/kanban/renderer.ts
14551
14844
  var renderer_exports3 = {};
14552
14845
  __export(renderer_exports3, {
14553
- bucketCardsBySwimlane: () => bucketCardsBySwimlane,
14554
14846
  renderKanban: () => renderKanban,
14555
14847
  renderKanbanForExport: () => renderKanbanForExport
14556
14848
  });
@@ -23449,6 +23741,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23449
23741
  options?.currentActiveGroup
23450
23742
  );
23451
23743
  let criticalPathActive = false;
23744
+ let dependenciesActive = !!resolved.options.dependencies;
23745
+ let controlsExpanded = false;
23452
23746
  const tagRows = currentSwimlaneGroup ? buildTagLaneRowList(resolved, currentSwimlaneGroup, collapsedLanes) : null;
23453
23747
  const rows = tagRows ?? buildRowList(resolved, collapsedGroups);
23454
23748
  const isTagMode = tagRows !== null;
@@ -23465,9 +23759,11 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23465
23759
  const maxLabelLen = Math.max(...allLabels.map((l) => l.length), 10);
23466
23760
  const leftMargin = Math.max(MIN_LEFT_MARGIN, maxLabelLen * 7 + 30);
23467
23761
  const totalRows = rows.length;
23762
+ const hasCriticalPath = resolved.options.criticalPath && resolved.tasks.some((t) => t.isCriticalPath);
23763
+ const hasDependencies = resolved.options.dependencies && resolved.tasks.some((t) => t.task.dependencies.length > 0);
23468
23764
  const title = resolved.options.title;
23469
23765
  const titleHeight = title ? 50 : 20;
23470
- const tagLegendReserve = resolved.tagGroups.length > 0 ? LEGEND_HEIGHT + 8 : 0;
23766
+ const tagLegendReserve = resolved.tagGroups.length > 0 || hasCriticalPath || hasDependencies ? LEGEND_HEIGHT + 8 : 0;
23471
23767
  const topDateLabelReserve = 22;
23472
23768
  const hasOverheadLabels = resolved.markers.length > 0 || resolved.eras.length > 0;
23473
23769
  const markerLabelReserve = hasOverheadLabels ? 28 : 0;
@@ -23485,10 +23781,9 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23485
23781
  if (title) {
23486
23782
  svg.append("text").attr("x", containerWidth / 2).attr("y", TITLE_Y).attr("text-anchor", "middle").attr("font-size", TITLE_FONT_SIZE).attr("font-weight", TITLE_FONT_WEIGHT).attr("fill", palette.text).text(title);
23487
23783
  }
23488
- const hasCriticalPath = resolved.options.criticalPath && resolved.tasks.some((t) => t.isCriticalPath);
23489
23784
  function drawLegend() {
23490
23785
  svg.selectAll(".gantt-tag-legend-container").remove();
23491
- if (resolved.tagGroups.length > 0 || hasCriticalPath) {
23786
+ if (resolved.tagGroups.length > 0 || hasCriticalPath || hasDependencies) {
23492
23787
  const legendY = titleHeight;
23493
23788
  renderTagLegend(
23494
23789
  svg,
@@ -23510,16 +23805,38 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23510
23805
  recolorBars();
23511
23806
  },
23512
23807
  () => {
23513
- criticalPathActive = !criticalPathActive;
23808
+ controlsExpanded = !controlsExpanded;
23514
23809
  drawLegend();
23515
23810
  },
23516
23811
  currentSwimlaneGroup,
23517
23812
  onSwimlaneChange,
23518
23813
  viewMode,
23519
- resolved.tasks
23814
+ resolved.tasks,
23815
+ controlsExpanded,
23816
+ hasDependencies,
23817
+ dependenciesActive,
23818
+ (toggleId, active) => {
23819
+ if (toggleId === "critical-path") {
23820
+ criticalPathActive = active;
23821
+ } else if (toggleId === "dependencies") {
23822
+ dependenciesActive = active;
23823
+ g.selectAll(
23824
+ ".gantt-dep-arrow, .gantt-dep-arrowhead, .gantt-dep-label"
23825
+ ).attr("display", active ? null : "none");
23826
+ }
23827
+ drawLegend();
23828
+ }
23520
23829
  );
23521
23830
  }
23522
23831
  }
23832
+ function restoreHighlight() {
23833
+ if (criticalPathActive) {
23834
+ applyCriticalPathHighlight(svg, g);
23835
+ } else {
23836
+ svg.attr("data-critical-path-active", null);
23837
+ resetHighlight(g, svg);
23838
+ }
23839
+ }
23523
23840
  function recolorBars() {
23524
23841
  g.selectAll(".gantt-task").each(function() {
23525
23842
  const el = d3Selection10.select(this);
@@ -23538,8 +23855,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23538
23855
  });
23539
23856
  }
23540
23857
  drawLegend();
23541
- const startTime = dateToFractionalYear(resolved.startDate);
23542
- const endTime = dateToFractionalYear(resolved.endDate);
23858
+ const startTime = dateToFractionalYear2(resolved.startDate);
23859
+ const endTime = dateToFractionalYear2(resolved.endDate);
23543
23860
  const domainPad = Math.max((endTime - startTime) * 0.02, 0.01);
23544
23861
  const domainMin = startTime - domainPad;
23545
23862
  const domainMax = endTime + domainPad;
@@ -23570,7 +23887,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23570
23887
  } else {
23571
23888
  todayDate = /* @__PURE__ */ new Date(resolved.options.todayMarker + "T00:00:00");
23572
23889
  }
23573
- todayX = xScale(dateToFractionalYear(todayDate));
23890
+ todayX = xScale(dateToFractionalYear2(todayDate));
23574
23891
  if (todayX >= 0 && todayX <= innerWidth) {
23575
23892
  const todayLine = g.append("line").attr("class", "gantt-today").attr("x1", todayX).attr("y1", 0).attr("x2", todayX).attr("y2", innerHeight + 10).attr("stroke", todayColor).attr("stroke-width", 2).attr("stroke-dasharray", "6 4").attr("opacity", 0.7).attr("pointer-events", "none");
23576
23893
  if (todayMarkerLineNum)
@@ -23612,8 +23929,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23612
23929
  let lx2 = innerWidth;
23613
23930
  let laneBarWidth = innerWidth;
23614
23931
  if (row.laneStartDate && row.laneEndDate) {
23615
- lx1 = xScale(dateToFractionalYear(row.laneStartDate));
23616
- lx2 = xScale(dateToFractionalYear(row.laneEndDate));
23932
+ lx1 = xScale(dateToFractionalYear2(row.laneStartDate));
23933
+ lx2 = xScale(dateToFractionalYear2(row.laneEndDate));
23617
23934
  laneBarWidth = Math.max(lx2 - lx1, 2);
23618
23935
  }
23619
23936
  lanePositions.set(row.laneName, {
@@ -23645,7 +23962,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23645
23962
  );
23646
23963
  }
23647
23964
  }).on("mouseleave", () => {
23648
- resetHighlight(g, svg);
23965
+ restoreHighlight();
23649
23966
  hideGanttDateIndicators(g);
23650
23967
  });
23651
23968
  labelG.append("text").attr("x", labelX).attr("y", marginTop + yOffset + BAR_H / 2).attr("dy", "0.35em").attr("text-anchor", "start").attr("font-size", "11px").attr("font-weight", "bold").attr("fill", laneColor).text(
@@ -23719,8 +24036,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23719
24036
  labelG.append("text").attr("x", labelX).attr("y", marginTop + yOffset + BAR_H / 2).attr("dy", "0.35em").attr("text-anchor", "start").attr("font-size", "11px").attr("font-weight", "bold").attr("fill", palette.text).text(
23720
24037
  toggleIcon + " " + group.name + (group.progress !== null ? ` ${Math.round(group.progress)}%` : "")
23721
24038
  );
23722
- const gStart = dateToFractionalYear(group.startDate);
23723
- const gEnd = dateToFractionalYear(group.endDate);
24039
+ const gStart = dateToFractionalYear2(group.startDate);
24040
+ const gEnd = dateToFractionalYear2(group.endDate);
23724
24041
  const gx1 = xScale(gStart);
23725
24042
  const gx2 = xScale(gEnd);
23726
24043
  if (gx2 > gx1) {
@@ -23834,7 +24151,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23834
24151
  taskLabel.attr("data-critical-path", "true");
23835
24152
  }
23836
24153
  if (rt.isMilestone) {
23837
- const mx = xScale(dateToFractionalYear(rt.startDate));
24154
+ const mx = xScale(dateToFractionalYear2(rt.startDate));
23838
24155
  const my = yOffset + BAR_H / 2;
23839
24156
  g.append("polygon").attr("class", "gantt-milestone").attr("points", diamondPoints(mx, my, MILESTONE_SIZE)).attr("fill", barColor).attr("stroke", barColor).attr("stroke-width", 1.5).attr("data-line-number", String(task.lineNumber)).attr("data-task-name", task.label).attr("data-task-id", task.id).attr("data-group", topGroup).style("cursor", onClickItem ? "pointer" : "default").on("click", () => {
23840
24157
  if (onClickItem) onClickItem(task.lineNumber);
@@ -23850,14 +24167,14 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23850
24167
  );
23851
24168
  g.append("text").attr("class", "gantt-milestone-hover-label").attr("x", mx - MILESTONE_SIZE - 4).attr("y", my).attr("dy", "0.35em").attr("text-anchor", "end").attr("font-size", "10px").attr("fill", barColor).attr("font-weight", "600").text(task.label);
23852
24169
  }).on("mouseleave", () => {
23853
- resetHighlight(g, svg);
24170
+ restoreHighlight();
23854
24171
  hideGanttDateIndicators(g);
23855
24172
  g.selectAll(".gantt-milestone-hover-label").remove();
23856
24173
  });
23857
24174
  taskPositions.set(task.id, { x1: mx, x2: mx, y: my });
23858
24175
  } else {
23859
- const tStart = dateToFractionalYear(rt.startDate);
23860
- const tEnd = dateToFractionalYear(rt.endDate);
24176
+ const tStart = dateToFractionalYear2(rt.startDate);
24177
+ const tEnd = dateToFractionalYear2(rt.endDate);
23861
24178
  const x1 = xScale(tStart);
23862
24179
  const x2 = xScale(tEnd);
23863
24180
  const barWidth = Math.max(x2 - x1, 2);
@@ -23879,11 +24196,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23879
24196
  );
23880
24197
  }).on("mouseleave", () => {
23881
24198
  if (resolved.options.dependencies) {
23882
- if (criticalPathActive) {
23883
- applyCriticalPathHighlight(svg, g);
23884
- } else {
23885
- resetHighlight(g, svg);
23886
- }
24199
+ restoreHighlight();
23887
24200
  }
23888
24201
  resetTaskLabels(svg);
23889
24202
  hideGanttDateIndicators(g);
@@ -24096,14 +24409,14 @@ function renderHolidayBands(g, svg, resolved, xScale, innerHeight, palette, isDa
24096
24409
  }
24097
24410
  }
24098
24411
  function drawBand(g, xScale, start, end, height, palette, _isDark, className, opacity) {
24099
- const x1 = xScale(dateToFractionalYear(start));
24100
- const x2 = xScale(dateToFractionalYear(end));
24412
+ const x1 = xScale(dateToFractionalYear2(start));
24413
+ const x2 = xScale(dateToFractionalYear2(end));
24101
24414
  if (x2 <= x1) return;
24102
24415
  g.append("rect").attr("class", className).attr("x", x1).attr("y", 0).attr("width", x2 - x1).attr("height", height).attr("fill", palette.text).attr("opacity", opacity).attr("pointer-events", "none");
24103
24416
  }
24104
24417
  function drawHolidayBand(g, svg, xScale, start, end, height, palette, _isDark, label, lineNumber, headerY, chartLeftMargin, onClickItem) {
24105
- const x1 = xScale(dateToFractionalYear(start));
24106
- const x2 = xScale(dateToFractionalYear(end));
24418
+ const x1 = xScale(dateToFractionalYear2(start));
24419
+ const x2 = xScale(dateToFractionalYear2(end));
24107
24420
  if (x2 <= x1) return;
24108
24421
  const bandW = Math.max(x2 - x1, 4);
24109
24422
  const baseOpacity = 0.08;
@@ -24198,6 +24511,7 @@ function arrowheadPoints(x, y, size, angle) {
24198
24511
  return `${x},${y} ${x + size * Math.cos(a1)},${y + size * Math.sin(a1)} ${x + size * Math.cos(a2)},${y + size * Math.sin(a2)}`;
24199
24512
  }
24200
24513
  function applyCriticalPathHighlight(svg, chartG) {
24514
+ svg.attr("data-critical-path-active", "true");
24201
24515
  chartG.selectAll(".gantt-task").each(function() {
24202
24516
  const el = d3Selection10.select(this);
24203
24517
  el.attr(
@@ -24250,8 +24564,31 @@ function drawSwimlaneIcon2(parent, x, y, isActive, palette) {
24250
24564
  }
24251
24565
  return iconG;
24252
24566
  }
24253
- function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargin, chartInnerWidth, legendY, palette, isDark, hasCriticalPath, criticalPathActive, optionLineNumbers, onToggle, onToggleCriticalPath, currentSwimlaneGroup, onSwimlaneChange, legendViewMode, resolvedTasks) {
24254
- const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
24567
+ function buildControlsToggles(hasCriticalPath, criticalPathActive, hasDependencies, dependenciesActive) {
24568
+ const toggles = [];
24569
+ if (hasCriticalPath) {
24570
+ toggles.push({
24571
+ id: "critical-path",
24572
+ type: "toggle",
24573
+ label: "Critical Path",
24574
+ active: criticalPathActive,
24575
+ onToggle: () => {
24576
+ }
24577
+ });
24578
+ }
24579
+ if (hasDependencies) {
24580
+ toggles.push({
24581
+ id: "dependencies",
24582
+ type: "toggle",
24583
+ label: "Dependencies",
24584
+ active: dependenciesActive,
24585
+ onToggle: () => {
24586
+ }
24587
+ });
24588
+ }
24589
+ return toggles;
24590
+ }
24591
+ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargin, chartInnerWidth, legendY, palette, isDark, hasCriticalPath, criticalPathActive, optionLineNumbers, onToggle, onToggleControlsExpand, currentSwimlaneGroup, onSwimlaneChange, legendViewMode, resolvedTasks, controlsExpanded = false, hasDependencies = false, dependenciesActive = false, onControlsToggle) {
24255
24592
  let visibleGroups;
24256
24593
  if (activeGroupName) {
24257
24594
  const activeGroup = tagGroups.filter(
@@ -24312,16 +24649,17 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
24312
24649
  totalW += groupW;
24313
24650
  }
24314
24651
  totalW += Math.max(0, (visibleGroups.length - 1) * LEGEND_GROUP_GAP);
24315
- const cpLabel = "Critical Path";
24316
- const cpPillW = measureLegendText(cpLabel, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
24317
- if (hasCriticalPath) {
24652
+ const hasControls = hasCriticalPath || hasDependencies;
24653
+ const controlsToggleLabels = [];
24654
+ if (hasCriticalPath) controlsToggleLabels.push({ label: "Critical Path" });
24655
+ if (hasDependencies) controlsToggleLabels.push({ label: "Dependencies" });
24656
+ if (hasControls) {
24318
24657
  if (visibleGroups.length > 0) totalW += LEGEND_GROUP_GAP;
24319
- totalW += cpPillW;
24658
+ totalW += controlsExpanded ? controlsGroupCapsuleWidth(controlsToggleLabels) : LEGEND_GEAR_PILL_W;
24320
24659
  }
24321
24660
  const containerWidth = chartLeftMargin + chartInnerWidth + RIGHT_MARGIN;
24322
24661
  const legendX = (containerWidth - totalW) / 2;
24323
24662
  const legendRow = svg.append("g").attr("class", "gantt-tag-legend-container").attr("transform", `translate(${legendX}, ${legendY})`);
24324
- let cursorX = 0;
24325
24663
  if (visibleGroups.length > 0) {
24326
24664
  const showIcon = !legendViewMode && tagGroups.length > 0;
24327
24665
  const iconReserve = showIcon ? LEGEND_ICON_W : 0;
@@ -24333,6 +24671,12 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
24333
24671
  entries: entries.map((e) => ({ value: e.value, color: e.color }))
24334
24672
  };
24335
24673
  });
24674
+ const controlsToggles = buildControlsToggles(
24675
+ hasCriticalPath,
24676
+ criticalPathActive,
24677
+ hasDependencies,
24678
+ dependenciesActive
24679
+ );
24336
24680
  const legendConfig = {
24337
24681
  groups: legendGroups,
24338
24682
  position: {
@@ -24340,13 +24684,23 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
24340
24684
  titleRelation: "below-title"
24341
24685
  },
24342
24686
  mode: "fixed",
24343
- capsulePillAddonWidth: iconReserve
24687
+ capsulePillAddonWidth: iconReserve,
24688
+ controlsGroup: controlsToggles.length > 0 ? { toggles: controlsToggles } : void 0
24689
+ };
24690
+ const legendState = {
24691
+ activeGroup: activeGroupName,
24692
+ controlsExpanded
24344
24693
  };
24345
- const legendState = { activeGroup: activeGroupName };
24346
- const tagGroupsW = visibleGroups.reduce((s, _, i) => s + groupWidths[i], 0) + Math.max(0, (visibleGroups.length - 1) * LEGEND_GROUP_GAP);
24694
+ let tagGroupsW = visibleGroups.reduce((s, _, i) => s + groupWidths[i], 0) + Math.max(0, (visibleGroups.length - 1) * LEGEND_GROUP_GAP);
24695
+ if (hasControls) {
24696
+ if (visibleGroups.length > 0) tagGroupsW += LEGEND_GROUP_GAP;
24697
+ tagGroupsW += controlsExpanded ? controlsGroupCapsuleWidth(controlsToggleLabels) : LEGEND_GEAR_PILL_W;
24698
+ }
24347
24699
  const tagGroupG = legendRow.append("g");
24348
24700
  const legendCallbacks = {
24349
24701
  onGroupToggle: onToggle,
24702
+ onControlsExpand: onToggleControlsExpand,
24703
+ onControlsToggle,
24350
24704
  onEntryHover: (groupName, entryValue) => {
24351
24705
  const tagKey = groupName.toLowerCase();
24352
24706
  if (entryValue) {
@@ -24423,31 +24777,38 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
24423
24777
  legendCallbacks,
24424
24778
  tagGroupsW
24425
24779
  );
24426
- for (let i = 0; i < visibleGroups.length; i++) {
24427
- cursorX += groupWidths[i] + LEGEND_GROUP_GAP;
24428
- }
24780
+ } else if (hasControls) {
24781
+ const controlsToggles = buildControlsToggles(
24782
+ hasCriticalPath,
24783
+ criticalPathActive,
24784
+ hasDependencies,
24785
+ dependenciesActive
24786
+ );
24787
+ const legendConfig = {
24788
+ groups: [],
24789
+ position: {
24790
+ placement: "top-center",
24791
+ titleRelation: "below-title"
24792
+ },
24793
+ mode: "fixed",
24794
+ controlsGroup: { toggles: controlsToggles }
24795
+ };
24796
+ const tagGroupG = legendRow.append("g");
24797
+ renderLegendD3(
24798
+ tagGroupG,
24799
+ legendConfig,
24800
+ { activeGroup: null, controlsExpanded },
24801
+ palette,
24802
+ isDark,
24803
+ {
24804
+ onControlsExpand: onToggleControlsExpand,
24805
+ onControlsToggle
24806
+ },
24807
+ totalW
24808
+ );
24429
24809
  }
24430
- if (hasCriticalPath) {
24431
- const cpLineNum = optionLineNumbers["critical-path"];
24432
- const cpG = legendRow.append("g").attr("transform", `translate(${cursorX}, 0)`).attr("class", "gantt-legend-critical-path").style("cursor", "pointer").on("click", () => {
24433
- if (onToggleCriticalPath) onToggleCriticalPath();
24434
- });
24435
- if (cpLineNum) cpG.attr("data-line-number", String(cpLineNum));
24436
- cpG.append("rect").attr("width", cpPillW).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", criticalPathActive ? palette.bg : groupBg);
24437
- if (criticalPathActive) {
24438
- cpG.append("rect").attr("width", cpPillW).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", "none").attr("stroke", mix(palette.textMuted, palette.bg, 50)).attr("stroke-width", 0.75);
24439
- }
24440
- cpG.append("text").attr("x", cpPillW / 2).attr("y", LEGEND_HEIGHT / 2 + LEGEND_PILL_FONT_SIZE / 2 - 2).attr("text-anchor", "middle").attr("font-size", `${LEGEND_PILL_FONT_SIZE}px`).attr("font-weight", "500").attr("fill", criticalPathActive ? palette.text : palette.textMuted).text(cpLabel);
24441
- if (criticalPathActive) {
24442
- applyCriticalPathHighlight(svg, chartG);
24443
- }
24444
- cpG.on("mouseenter", () => {
24445
- applyCriticalPathHighlight(svg, chartG);
24446
- }).on("mouseleave", () => {
24447
- if (!criticalPathActive) {
24448
- resetHighlightAll(svg, chartG);
24449
- }
24450
- });
24810
+ if (criticalPathActive) {
24811
+ applyCriticalPathHighlight(svg, chartG);
24451
24812
  }
24452
24813
  }
24453
24814
  function renderErasAndMarkers(g, svg, resolved, xScale, innerHeight, palette) {
@@ -24577,8 +24938,8 @@ function renderSprintBands(g, svg, resolved, xScale, innerHeight, palette) {
24577
24938
  const chartMinX = 0;
24578
24939
  for (let i = 0; i < resolved.sprints.length; i++) {
24579
24940
  const sprint = resolved.sprints[i];
24580
- const rawSx = xScale(dateToFractionalYear(sprint.startDate));
24581
- const rawEx = xScale(dateToFractionalYear(sprint.endDate));
24941
+ const rawSx = xScale(dateToFractionalYear2(sprint.startDate));
24942
+ const rawEx = xScale(dateToFractionalYear2(sprint.endDate));
24582
24943
  if (rawEx <= rawSx) continue;
24583
24944
  const sx = Math.max(rawSx, chartMinX);
24584
24945
  const ex = rawEx;
@@ -24700,7 +25061,7 @@ function renderSprintBands(g, svg, resolved, xScale, innerHeight, palette) {
24700
25061
  });
24701
25062
  }
24702
25063
  const lastSprint = resolved.sprints[resolved.sprints.length - 1];
24703
- const lastEx = xScale(dateToFractionalYear(lastSprint.endDate));
25064
+ const lastEx = xScale(dateToFractionalYear2(lastSprint.endDate));
24704
25065
  g.append("line").attr("class", "gantt-sprint-boundary").attr("x1", lastEx).attr("y1", -6).attr("x2", lastEx).attr("y2", innerHeight).attr("stroke", bandColor).attr("stroke-width", 1).attr("stroke-dasharray", "3 3").attr("opacity", SPRINT_BOUNDARY_OPACITY);
24705
25066
  }
24706
25067
  function parseDateStringToDate(s) {
@@ -24711,7 +25072,7 @@ function parseDateStringToDate(s) {
24711
25072
  return new Date(year, month, day);
24712
25073
  }
24713
25074
  function parseDateToFractionalYear(s) {
24714
- return dateToFractionalYear(parseDateStringToDate(s));
25075
+ return dateToFractionalYear2(parseDateStringToDate(s));
24715
25076
  }
24716
25077
  function highlightDeps(g, svg, taskId, resolved) {
24717
25078
  const related = /* @__PURE__ */ new Set([taskId]);
@@ -24915,6 +25276,10 @@ function resetTaskLabels(svg) {
24915
25276
  svg.selectAll(".gantt-task-label").attr("opacity", 1);
24916
25277
  }
24917
25278
  function resetHighlight(g, svg) {
25279
+ if (svg.attr("data-critical-path-active") === "true") {
25280
+ applyCriticalPathHighlight(svg, g);
25281
+ return;
25282
+ }
24918
25283
  g.selectAll(".gantt-task, .gantt-milestone").attr(
24919
25284
  "opacity",
24920
25285
  1
@@ -25074,7 +25439,7 @@ function durationWeightedProgress(tasks) {
25074
25439
  }
25075
25440
  return hasProgress && totalDuration > 0 ? totalProgress / totalDuration : null;
25076
25441
  }
25077
- function dateToFractionalYear(d) {
25442
+ function dateToFractionalYear2(d) {
25078
25443
  const y = d.getFullYear();
25079
25444
  const startOfYear = new Date(y, 0, 1);
25080
25445
  const endOfYear = new Date(y + 1, 0, 1);
@@ -25086,7 +25451,7 @@ function diamondPoints(cx, cy, size) {
25086
25451
  return `${cx},${cy - half} ${cx + half},${cy} ${cx},${cy + half} ${cx - half},${cy}`;
25087
25452
  }
25088
25453
  function formatGanttDate(d) {
25089
- const base = `${MONTH_ABBR[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`;
25454
+ const base = `${MONTH_ABBR2[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`;
25090
25455
  if (d.getHours() === 0 && d.getMinutes() === 0) return base;
25091
25456
  const hh = String(d.getHours()).padStart(2, "0");
25092
25457
  const mm = String(d.getMinutes()).padStart(2, "0");
@@ -25097,7 +25462,7 @@ function showGanttDateIndicators(g, xScale, startDate, endDate, innerHeight, col
25097
25462
  g.selectAll(".gantt-today").attr("opacity", 0.05);
25098
25463
  const hg = g.append("g").attr("class", "gantt-hover-date").attr("pointer-events", "none");
25099
25464
  const tickLen = 6;
25100
- const startPos = xScale(dateToFractionalYear(startDate));
25465
+ const startPos = xScale(dateToFractionalYear2(startDate));
25101
25466
  const startLabel = formatGanttDate(startDate);
25102
25467
  if (!options?.skipStartLine) {
25103
25468
  hg.append("line").attr("class", "gantt-hover-date").attr("x1", startPos).attr("y1", -tickLen).attr("x2", startPos).attr("y2", innerHeight).attr("stroke", color).attr("stroke-width", 1.5).attr("stroke-dasharray", "4 4").attr("opacity", 0.6);
@@ -25105,7 +25470,7 @@ function showGanttDateIndicators(g, xScale, startDate, endDate, innerHeight, col
25105
25470
  hg.append("text").attr("class", "gantt-hover-date").attr("x", startPos).attr("y", -tickLen - 4).attr("text-anchor", "middle").attr("fill", color).attr("font-size", "10px").attr("font-weight", "600").text(startLabel);
25106
25471
  hg.append("text").attr("class", "gantt-hover-date").attr("x", startPos).attr("y", innerHeight + tickLen + 12).attr("text-anchor", "middle").attr("fill", color).attr("font-size", "10px").attr("font-weight", "600").text(startLabel);
25107
25472
  if (endDate && endDate.getTime() !== startDate.getTime()) {
25108
- const endPos = xScale(dateToFractionalYear(endDate));
25473
+ const endPos = xScale(dateToFractionalYear2(endDate));
25109
25474
  const endLabel = formatGanttDate(endDate);
25110
25475
  const minLabelGap = 90;
25111
25476
  const gap = endPos - startPos;
@@ -25175,7 +25540,7 @@ function renderTimeScaleHorizontal(g, scale, innerWidth, innerHeight, textColor)
25175
25540
  g.append("text").attr("class", "gantt-scale-tick").attr("x", tick.pos).attr("y", innerHeight + tickLen + 12).attr("text-anchor", "middle").attr("font-size", "10px").attr("fill", textColor).attr("opacity", opacity).text(tick.label);
25176
25541
  }
25177
25542
  }
25178
- var BAR_H, ROW_GAP, GROUP_GAP2, MILESTONE_SIZE, MIN_LEFT_MARGIN, BOTTOM_MARGIN, RIGHT_MARGIN, CHAR_W2, LABEL_PAD, LABEL_GAP, BAND_ACCENT_W, BAND_RADIUS, bandClipCounter, JS_DAY_TO_WEEKDAY2, ERA_COLORS, SPRINT_BAND_OPACITY, SPRINT_HOVER_OPACITY, SPRINT_BOUNDARY_OPACITY, FADE_OPACITY, MONTH_ABBR;
25543
+ var BAR_H, ROW_GAP, GROUP_GAP2, MILESTONE_SIZE, MIN_LEFT_MARGIN, BOTTOM_MARGIN, RIGHT_MARGIN, CHAR_W2, LABEL_PAD, LABEL_GAP, BAND_ACCENT_W, BAND_RADIUS, bandClipCounter, JS_DAY_TO_WEEKDAY2, ERA_COLORS, SPRINT_BAND_OPACITY, SPRINT_HOVER_OPACITY, SPRINT_BOUNDARY_OPACITY, FADE_OPACITY, MONTH_ABBR2;
25179
25544
  var init_renderer9 = __esm({
25180
25545
  "src/gantt/renderer.ts"() {
25181
25546
  "use strict";
@@ -25183,9 +25548,10 @@ var init_renderer9 = __esm({
25183
25548
  init_palettes();
25184
25549
  init_color_utils();
25185
25550
  init_tag_groups();
25186
- init_d3();
25551
+ init_time_ticks();
25187
25552
  init_legend_constants();
25188
25553
  init_legend_d3();
25554
+ init_legend_layout();
25189
25555
  init_title_constants();
25190
25556
  BAR_H = 22;
25191
25557
  ROW_GAP = 6;
@@ -25214,7 +25580,7 @@ var init_renderer9 = __esm({
25214
25580
  SPRINT_HOVER_OPACITY = 0.12;
25215
25581
  SPRINT_BOUNDARY_OPACITY = 0.3;
25216
25582
  FADE_OPACITY = 0.1;
25217
- MONTH_ABBR = [
25583
+ MONTH_ABBR2 = [
25218
25584
  "Jan",
25219
25585
  "Feb",
25220
25586
  "Mar",
@@ -25479,6 +25845,109 @@ var init_state_renderer = __esm({
25479
25845
  }
25480
25846
  });
25481
25847
 
25848
+ // src/sequence/collapse.ts
25849
+ function applyCollapseProjection(parsed, collapsedGroups) {
25850
+ if (collapsedGroups.size === 0) {
25851
+ return {
25852
+ participants: parsed.participants,
25853
+ messages: parsed.messages,
25854
+ elements: parsed.elements,
25855
+ groups: parsed.groups,
25856
+ collapsedGroupIds: /* @__PURE__ */ new Map()
25857
+ };
25858
+ }
25859
+ const memberToGroup = /* @__PURE__ */ new Map();
25860
+ const collapsedGroupNames = /* @__PURE__ */ new Set();
25861
+ for (const group of parsed.groups) {
25862
+ if (collapsedGroups.has(group.lineNumber)) {
25863
+ collapsedGroupNames.add(group.name);
25864
+ for (const memberId of group.participantIds) {
25865
+ memberToGroup.set(memberId, group.name);
25866
+ }
25867
+ }
25868
+ }
25869
+ const participants = [];
25870
+ const insertedGroups = /* @__PURE__ */ new Set();
25871
+ for (const p of parsed.participants) {
25872
+ const groupName = memberToGroup.get(p.id);
25873
+ if (groupName) {
25874
+ if (!insertedGroups.has(groupName)) {
25875
+ insertedGroups.add(groupName);
25876
+ const group = parsed.groups.find(
25877
+ (g) => g.name === groupName && collapsedGroups.has(g.lineNumber)
25878
+ );
25879
+ participants.push({
25880
+ id: groupName,
25881
+ label: groupName,
25882
+ type: "default",
25883
+ lineNumber: group.lineNumber
25884
+ });
25885
+ }
25886
+ } else if (collapsedGroupNames.has(p.id)) {
25887
+ } else {
25888
+ participants.push(p);
25889
+ }
25890
+ }
25891
+ const remap = (id) => memberToGroup.get(id) ?? id;
25892
+ const messages = parsed.messages.map((msg) => ({
25893
+ ...msg,
25894
+ from: remap(msg.from),
25895
+ to: remap(msg.to)
25896
+ }));
25897
+ const elements = remapElements(parsed.elements, memberToGroup);
25898
+ const groups = parsed.groups.filter(
25899
+ (g) => !collapsedGroups.has(g.lineNumber)
25900
+ );
25901
+ return {
25902
+ participants,
25903
+ messages,
25904
+ elements,
25905
+ groups,
25906
+ collapsedGroupIds: memberToGroup
25907
+ };
25908
+ }
25909
+ function remapElements(elements, memberToGroup) {
25910
+ const remap = (id) => memberToGroup.get(id) ?? id;
25911
+ const result = [];
25912
+ for (const el of elements) {
25913
+ if (isSequenceSection(el)) {
25914
+ result.push(el);
25915
+ } else if (isSequenceNote(el)) {
25916
+ result.push({
25917
+ ...el,
25918
+ participantId: remap(el.participantId)
25919
+ });
25920
+ } else if (isSequenceBlock(el)) {
25921
+ result.push({
25922
+ ...el,
25923
+ children: remapElements(el.children, memberToGroup),
25924
+ elseChildren: remapElements(el.elseChildren, memberToGroup),
25925
+ ...el.elseIfBranches ? {
25926
+ elseIfBranches: el.elseIfBranches.map((branch) => ({
25927
+ ...branch,
25928
+ children: remapElements(branch.children, memberToGroup)
25929
+ }))
25930
+ } : {}
25931
+ });
25932
+ } else {
25933
+ const msg = el;
25934
+ const from = remap(msg.from);
25935
+ const to = remap(msg.to);
25936
+ if (from === to && from !== msg.from && !msg.label) {
25937
+ continue;
25938
+ }
25939
+ result.push({ ...msg, from, to });
25940
+ }
25941
+ }
25942
+ return result;
25943
+ }
25944
+ var init_collapse3 = __esm({
25945
+ "src/sequence/collapse.ts"() {
25946
+ "use strict";
25947
+ init_parser();
25948
+ }
25949
+ });
25950
+
25482
25951
  // src/sequence/tag-resolution.ts
25483
25952
  function propagateGroupTags(participantMeta, groups) {
25484
25953
  for (const group of groups) {
@@ -25934,13 +26403,32 @@ function applyGroupOrdering(participants, groups, messages = []) {
25934
26403
  }
25935
26404
  function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateToLine, options) {
25936
26405
  d3Selection12.select(container).selectAll("*").remove();
25937
- const { title, messages, elements, groups, options: parsedOptions } = parsed;
26406
+ const { title, options: parsedOptions } = parsed;
26407
+ const effectiveCollapsedGroups = /* @__PURE__ */ new Set();
26408
+ for (const group of parsed.groups) {
26409
+ if (group.collapsed) effectiveCollapsedGroups.add(group.lineNumber);
26410
+ }
26411
+ if (options?.collapsedGroups) {
26412
+ for (const ln of options.collapsedGroups) {
26413
+ if (effectiveCollapsedGroups.has(ln)) {
26414
+ effectiveCollapsedGroups.delete(ln);
26415
+ } else {
26416
+ effectiveCollapsedGroups.add(ln);
26417
+ }
26418
+ }
26419
+ }
26420
+ const collapsed = effectiveCollapsedGroups.size > 0 ? applyCollapseProjection(parsed, effectiveCollapsedGroups) : null;
26421
+ const messages = collapsed ? collapsed.messages : parsed.messages;
26422
+ const elements = collapsed ? collapsed.elements : parsed.elements;
26423
+ const groups = collapsed ? collapsed.groups : parsed.groups;
26424
+ const collapsedGroupIds = collapsed?.collapsedGroupIds ?? /* @__PURE__ */ new Map();
25938
26425
  const collapsedSections = options?.collapsedSections;
25939
26426
  const expandedNoteLines = options?.expandedNoteLines;
25940
26427
  const collapseNotesDisabled = parsedOptions["collapse-notes"]?.toLowerCase() === "no";
25941
26428
  const isNoteExpanded = (note) => expandedNoteLines === void 0 || collapseNotesDisabled || expandedNoteLines.has(note.lineNumber);
26429
+ const sourceParticipants = collapsed ? collapsed.participants : parsed.participants;
25942
26430
  const participants = applyPositionOverrides(
25943
- applyGroupOrdering(parsed.participants, groups, messages)
26431
+ applyGroupOrdering(sourceParticipants, groups, messages)
25944
26432
  );
25945
26433
  if (participants.length === 0) return;
25946
26434
  const activationsOff = parsedOptions.activations?.toLowerCase() === "off";
@@ -26111,13 +26599,16 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26111
26599
  const preSectionMsgIndices = [];
26112
26600
  const sectionRegions = [];
26113
26601
  {
26602
+ const msgLineToIndex = /* @__PURE__ */ new Map();
26603
+ messages.forEach((m, i) => msgLineToIndex.set(m.lineNumber, i));
26604
+ const findMsgIndex = (child) => msgLineToIndex.get(child.lineNumber) ?? -1;
26114
26605
  const collectMsgIndicesFromBlock = (block) => {
26115
26606
  const indices = [];
26116
26607
  for (const child of block.children) {
26117
26608
  if (isSequenceBlock(child)) {
26118
26609
  indices.push(...collectMsgIndicesFromBlock(child));
26119
26610
  } else if (!isSequenceSection(child) && !isSequenceNote(child)) {
26120
- const idx = messages.indexOf(child);
26611
+ const idx = findMsgIndex(child);
26121
26612
  if (idx >= 0) indices.push(idx);
26122
26613
  }
26123
26614
  }
@@ -26127,7 +26618,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26127
26618
  if (isSequenceBlock(child)) {
26128
26619
  indices.push(...collectMsgIndicesFromBlock(child));
26129
26620
  } else if (!isSequenceSection(child) && !isSequenceNote(child)) {
26130
- const idx = messages.indexOf(child);
26621
+ const idx = findMsgIndex(child);
26131
26622
  if (idx >= 0) indices.push(idx);
26132
26623
  }
26133
26624
  }
@@ -26137,7 +26628,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26137
26628
  if (isSequenceBlock(child)) {
26138
26629
  indices.push(...collectMsgIndicesFromBlock(child));
26139
26630
  } else if (!isSequenceSection(child) && !isSequenceNote(child)) {
26140
- const idx = messages.indexOf(child);
26631
+ const idx = findMsgIndex(child);
26141
26632
  if (idx >= 0) indices.push(idx);
26142
26633
  }
26143
26634
  }
@@ -26152,7 +26643,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26152
26643
  } else if (isSequenceBlock(el)) {
26153
26644
  currentTarget.push(...collectMsgIndicesFromBlock(el));
26154
26645
  } else {
26155
- const idx = messages.indexOf(el);
26646
+ const idx = findMsgIndex(el);
26156
26647
  if (idx >= 0) currentTarget.push(idx);
26157
26648
  }
26158
26649
  }
@@ -26214,7 +26705,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26214
26705
  const titleOffset = title ? TITLE_HEIGHT5 : 0;
26215
26706
  const LEGEND_FIXED_GAP4 = 8;
26216
26707
  const legendTopSpace = parsed.tagGroups.length > 0 ? LEGEND_HEIGHT + LEGEND_FIXED_GAP4 : 0;
26217
- const groupOffset = groups.length > 0 ? GROUP_PADDING_TOP + GROUP_LABEL_SIZE : 0;
26708
+ const groupOffset = parsed.groups.length > 0 ? GROUP_PADDING_TOP + GROUP_LABEL_SIZE : 0;
26218
26709
  const participantStartY = TOP_MARGIN + titleOffset + legendTopSpace + PARTICIPANT_Y_OFFSET + groupOffset;
26219
26710
  const lifelineStartY0 = participantStartY + PARTICIPANT_BOX_HEIGHT;
26220
26711
  const hasActors = participants.some((p) => p.type === "actor");
@@ -26384,6 +26875,17 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26384
26875
  svgWidth
26385
26876
  );
26386
26877
  }
26878
+ const collapsedGroupNames = /* @__PURE__ */ new Set();
26879
+ const collapsedGroupMeta = /* @__PURE__ */ new Map();
26880
+ for (const group of parsed.groups) {
26881
+ if (effectiveCollapsedGroups.has(group.lineNumber)) {
26882
+ collapsedGroupNames.add(group.name);
26883
+ collapsedGroupMeta.set(group.name, {
26884
+ lineNumber: group.lineNumber,
26885
+ metadata: group.metadata
26886
+ });
26887
+ }
26888
+ }
26387
26889
  for (const group of groups) {
26388
26890
  if (group.participantIds.length === 0) continue;
26389
26891
  const memberXs = group.participantIds.map((id) => participantX.get(id)).filter((x) => x !== void 0);
@@ -26400,8 +26902,10 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26400
26902
  isDark ? 15 : 20
26401
26903
  ) : isDark ? palette.surface : palette.bg;
26402
26904
  const strokeColor = groupTagColor || palette.textMuted;
26403
- svg.append("rect").attr("x", minX).attr("y", boxY).attr("width", maxX - minX).attr("height", boxH).attr("rx", 6).attr("fill", fillColor).attr("stroke", strokeColor).attr("stroke-width", 1).attr("stroke-opacity", 0.5).attr("class", "group-box").attr("data-group-line", String(group.lineNumber));
26404
- svg.append("text").attr("x", minX + 8).attr("y", boxY + GROUP_LABEL_SIZE + 4).attr("fill", strokeColor).attr("font-size", GROUP_LABEL_SIZE).attr("font-weight", "bold").attr("opacity", 0.7).attr("class", "group-label").attr("data-group-line", String(group.lineNumber)).text(group.name);
26905
+ const groupG = svg.append("g").attr("class", "group-box-wrapper").attr("data-group-toggle", "").attr("data-group-line", String(group.lineNumber)).attr("cursor", "pointer");
26906
+ groupG.append("title").text("Click to collapse");
26907
+ groupG.append("rect").attr("x", minX).attr("y", boxY).attr("width", maxX - minX).attr("height", boxH).attr("rx", 6).attr("fill", fillColor).attr("stroke", strokeColor).attr("stroke-width", 1).attr("stroke-opacity", 0.5).attr("class", "group-box");
26908
+ groupG.append("text").attr("x", minX + 8).attr("y", boxY + GROUP_LABEL_SIZE + 4).attr("fill", strokeColor).attr("font-size", GROUP_LABEL_SIZE).attr("font-weight", "bold").attr("opacity", 0.7).attr("class", "group-label").text(group.name);
26405
26909
  }
26406
26910
  const lifelineStartY = lifelineStartY0;
26407
26911
  participants.forEach((participant, index) => {
@@ -26410,6 +26914,14 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26410
26914
  const pTagValue = tagMap?.participants.get(participant.id);
26411
26915
  const pTagColor = getTagColor(pTagValue);
26412
26916
  const pTagAttr = tagKey && pTagValue ? { key: tagKey, value: pTagValue.toLowerCase() } : void 0;
26917
+ const isCollapsedGroup = collapsedGroupNames.has(participant.id);
26918
+ let effectiveTagColor = pTagColor;
26919
+ if (isCollapsedGroup && !effectiveTagColor) {
26920
+ const meta = collapsedGroupMeta.get(participant.id);
26921
+ if (meta?.metadata && tagKey) {
26922
+ effectiveTagColor = getTagColor(meta.metadata[tagKey]);
26923
+ }
26924
+ }
26413
26925
  renderParticipant(
26414
26926
  svg,
26415
26927
  participant,
@@ -26417,10 +26929,35 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26417
26929
  cy,
26418
26930
  palette,
26419
26931
  isDark,
26420
- pTagColor,
26932
+ effectiveTagColor,
26421
26933
  pTagAttr
26422
26934
  );
26423
- const lifelineEl = svg.append("line").attr("x1", cx).attr("y1", lifelineStartY).attr("x2", cx).attr("y2", lifelineStartY + lifelineLength).attr("stroke", pTagColor || palette.textMuted).attr("stroke-width", 1).attr("stroke-dasharray", "6 4").attr("class", "lifeline").attr("data-participant-id", participant.id);
26935
+ if (isCollapsedGroup) {
26936
+ const meta = collapsedGroupMeta.get(participant.id);
26937
+ const drillColor = effectiveTagColor || palette.textMuted;
26938
+ const drillBarH = 6;
26939
+ const boxW = PARTICIPANT_BOX_WIDTH;
26940
+ const fullH = PARTICIPANT_BOX_HEIGHT + GROUP_PADDING_TOP + GROUP_PADDING_BOTTOM;
26941
+ const clipId = `clip-drill-group-${participant.id.replace(/[^a-zA-Z0-9-]/g, "-")}`;
26942
+ const participantG = svg.select(
26943
+ `.participant[data-participant-id="${participant.id}"]`
26944
+ );
26945
+ participantG.attr("data-group-toggle", "").attr("data-group-line", String(meta.lineNumber)).attr("cursor", "pointer");
26946
+ participantG.append("title").text("Click to expand");
26947
+ const pFill = effectiveTagColor ? mix(
26948
+ effectiveTagColor,
26949
+ isDark ? palette.surface : palette.bg,
26950
+ isDark ? 30 : 40
26951
+ ) : isDark ? mix(palette.overlay, palette.surface, 50) : mix(palette.bg, palette.surface, 50);
26952
+ const pStroke = effectiveTagColor || palette.border;
26953
+ participantG.append("rect").attr("x", -boxW / 2).attr("y", -GROUP_PADDING_TOP).attr("width", boxW).attr("height", fullH).attr("rx", 6).attr("fill", pFill).attr("stroke", pStroke).attr("stroke-width", 1.5);
26954
+ participantG.append("text").attr("x", 0).attr("y", -GROUP_PADDING_TOP + fullH / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("fill", palette.text).attr("font-size", 13).attr("font-weight", 500).text(participant.label);
26955
+ participantG.append("clipPath").attr("id", clipId).append("rect").attr("x", -boxW / 2).attr("y", -GROUP_PADDING_TOP).attr("width", boxW).attr("height", fullH).attr("rx", 6);
26956
+ participantG.append("rect").attr("class", "sequence-drill-bar").attr("x", -boxW / 2).attr("y", -GROUP_PADDING_TOP + fullH - drillBarH).attr("width", boxW).attr("height", drillBarH).attr("fill", drillColor).attr("clip-path", `url(#${clipId})`);
26957
+ }
26958
+ const llY = isCollapsedGroup ? lifelineStartY + GROUP_PADDING_BOTTOM : lifelineStartY;
26959
+ const llColor = isCollapsedGroup ? effectiveTagColor || palette.textMuted : pTagColor || palette.textMuted;
26960
+ const lifelineEl = svg.append("line").attr("x1", cx).attr("y1", llY).attr("x2", cx).attr("y2", lifelineStartY + lifelineLength).attr("stroke", llColor).attr("stroke-width", 1).attr("stroke-dasharray", "6 4").attr("class", "lifeline").attr("data-participant-id", participant.id);
26424
26961
  if (tagKey && pTagValue) {
26425
26962
  lifelineEl.attr(`data-tag-${tagKey}`, pTagValue.toLowerCase());
26426
26963
  }
@@ -26648,23 +27185,8 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26648
27185
  sectionG.append("rect").attr("x", bandX).attr("y", secY - BAND_HEIGHT / 2).attr("width", bandWidth).attr("height", BAND_HEIGHT).attr("fill", lineColor).attr("opacity", bandOpacity).attr("rx", 2).attr("class", "section-divider");
26649
27186
  const msgCount = sectionMsgCounts.get(sec.lineNumber) ?? 0;
26650
27187
  const labelText = isCollapsed ? `${sec.label} (${msgCount} ${msgCount === 1 ? "message" : "messages"})` : sec.label;
26651
- const labelColor = isCollapsed ? "#ffffff" : lineColor;
26652
- const chevronSpace = 14;
26653
27188
  const labelX = (sectionLineX1 + sectionLineX2) / 2;
26654
- const chevronX = labelX - (labelText.length * 3.5 + 8 + chevronSpace / 2);
26655
- const chevronY = secY;
26656
- if (isCollapsed) {
26657
- sectionG.append("path").attr(
26658
- "d",
26659
- `M ${chevronX} ${chevronY - 4} L ${chevronX + 6} ${chevronY} L ${chevronX} ${chevronY + 4} Z`
26660
- ).attr("fill", labelColor).attr("class", "section-chevron");
26661
- } else {
26662
- sectionG.append("path").attr(
26663
- "d",
26664
- `M ${chevronX - 1} ${chevronY - 3} L ${chevronX + 7} ${chevronY - 3} L ${chevronX + 3} ${chevronY + 3} Z`
26665
- ).attr("fill", labelColor).attr("class", "section-chevron");
26666
- }
26667
- sectionG.append("text").attr("x", labelX + chevronSpace / 2).attr("y", secY + 4).attr("text-anchor", "middle").attr("fill", labelColor).attr("font-size", 11).attr("font-weight", "bold").attr("class", "section-label").text(labelText);
27189
+ sectionG.append("text").attr("x", labelX).attr("y", secY + 4).attr("text-anchor", "middle").attr("fill", lineColor).attr("font-size", 11).attr("font-weight", "bold").attr("class", "section-label").text(labelText);
26668
27190
  }
26669
27191
  const SELF_CALL_WIDTH = 30;
26670
27192
  const SELF_CALL_HEIGHT = 25;
@@ -26703,7 +27225,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26703
27225
  if (tagKey && msgTagValue) {
26704
27226
  labelEl.attr(`data-tag-${tagKey}`, msgTagValue.toLowerCase());
26705
27227
  }
26706
- renderInlineText(labelEl, step.label, palette);
27228
+ labelEl.text(step.label);
26707
27229
  }
26708
27230
  } else {
26709
27231
  const goingRight = fromX < toX;
@@ -26730,7 +27252,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26730
27252
  if (tagKey && msgTagValue) {
26731
27253
  labelEl.attr(`data-tag-${tagKey}`, msgTagValue.toLowerCase());
26732
27254
  }
26733
- renderInlineText(labelEl, step.label, palette);
27255
+ labelEl.text(step.label);
26734
27256
  }
26735
27257
  }
26736
27258
  } else {
@@ -26761,7 +27283,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26761
27283
  if (tagKey && msgTagValue) {
26762
27284
  labelEl.attr(`data-tag-${tagKey}`, msgTagValue.toLowerCase());
26763
27285
  }
26764
- renderInlineText(labelEl, step.label, palette);
27286
+ labelEl.text(step.label);
26765
27287
  }
26766
27288
  }
26767
27289
  });
@@ -26945,6 +27467,7 @@ var init_renderer10 = __esm({
26945
27467
  init_inline_markdown();
26946
27468
  init_fonts();
26947
27469
  init_parser();
27470
+ init_collapse3();
26948
27471
  init_tag_resolution();
26949
27472
  init_tag_groups();
26950
27473
  init_legend_constants();
@@ -27037,23 +27560,6 @@ function parseTimelineDate(s) {
27037
27560
  const day = parts.length >= 3 ? parts[2] : 1;
27038
27561
  return year + (month - 1) / 12 + (day - 1) / 365 + hour / 8760 + minute / 525600;
27039
27562
  }
27040
- function fractionalYearToDate(frac) {
27041
- const year = Math.floor(frac);
27042
- const remainder = frac - year;
27043
- const monthFrac = remainder * 12;
27044
- const month = Math.floor(monthFrac);
27045
- const monthRemainder = remainder - month / 12;
27046
- const dayFrac = monthRemainder * 365;
27047
- const day = Math.floor(dayFrac) + 1;
27048
- const dayRemainder = dayFrac - Math.floor(dayFrac);
27049
- const hourFrac = dayRemainder * 24;
27050
- const hour = Math.floor(hourFrac);
27051
- const minute = Math.round((hourFrac - hour) * 60);
27052
- return new Date(year, month, day, hour, minute);
27053
- }
27054
- function dateToFractionalYear2(d) {
27055
- return d.getFullYear() + d.getMonth() / 12 + (d.getDate() - 1) / 365 + d.getHours() / 8760 + d.getMinutes() / 525600;
27056
- }
27057
27563
  function addDurationToDate(startDate, amount, unit) {
27058
27564
  const spaceIdx = startDate.indexOf(" ");
27059
27565
  let datePart = startDate;
@@ -28608,7 +29114,7 @@ function formatDateLabel(dateStr) {
28608
29114
  const parts = datePart.split("-");
28609
29115
  const year = parts[0];
28610
29116
  if (parts.length === 1) return year + timeSuffix;
28611
- const month = MONTH_ABBR2[parseInt(parts[1], 10) - 1];
29117
+ const month = MONTH_ABBR[parseInt(parts[1], 10) - 1];
28612
29118
  if (parts.length === 2) return `${month} ${year}${timeSuffix}`;
28613
29119
  const day = parseInt(parts[2], 10);
28614
29120
  return `${month} ${day}, ${year}${timeSuffix}`;
@@ -28625,123 +29131,6 @@ function formatBoundaryLabel(dateStr, otherDateStr) {
28625
29131
  }
28626
29132
  return formatDateLabel(dateStr);
28627
29133
  }
28628
- function computeTimeTicks(domainMin, domainMax, scale, boundaryStart, boundaryEnd, boundaryStartLabel, boundaryEndLabel) {
28629
- const minYear = Math.floor(domainMin);
28630
- const maxYear = Math.floor(domainMax);
28631
- const span = domainMax - domainMin;
28632
- let ticks = [];
28633
- const firstYear = Math.ceil(domainMin);
28634
- const lastYear = Math.floor(domainMax);
28635
- if (lastYear >= firstYear + 1) {
28636
- const yearSpan = lastYear - firstYear;
28637
- let step = 1;
28638
- if (yearSpan > 80) step = 20;
28639
- else if (yearSpan > 40) step = 10;
28640
- else if (yearSpan > 20) step = 5;
28641
- else if (yearSpan > 10) step = 2;
28642
- const alignedFirst = Math.ceil(firstYear / step) * step;
28643
- for (let y = alignedFirst; y <= lastYear; y += step) {
28644
- ticks.push({ pos: scale(y), label: String(y) });
28645
- }
28646
- } else if (span > 0.25) {
28647
- const crossesYear = maxYear > minYear;
28648
- for (let y = minYear; y <= maxYear + 1; y++) {
28649
- for (let m = 1; m <= 12; m++) {
28650
- const val = y + (m - 1) / 12;
28651
- if (val > domainMax) break;
28652
- if (val >= domainMin) {
28653
- ticks.push({
28654
- pos: scale(val),
28655
- label: crossesYear ? `${MONTH_ABBR2[m - 1]} '${String(y).slice(-2)}` : MONTH_ABBR2[m - 1]
28656
- });
28657
- }
28658
- }
28659
- }
28660
- } else if (span <= 685e-6) {
28661
- let stepMin = 5;
28662
- const spanHours = span * 8760;
28663
- if (spanHours > 3) stepMin = 30;
28664
- else if (spanHours > 1) stepMin = 15;
28665
- else if (spanHours > 0.5) stepMin = 10;
28666
- const startDate = fractionalYearToDate(domainMin);
28667
- startDate.setMinutes(
28668
- Math.floor(startDate.getMinutes() / stepMin) * stepMin,
28669
- 0,
28670
- 0
28671
- );
28672
- while (true) {
28673
- const val = dateToFractionalYear2(startDate);
28674
- if (val > domainMax) break;
28675
- if (val >= domainMin) {
28676
- const hh = String(startDate.getHours()).padStart(2, "0");
28677
- const mm = String(startDate.getMinutes()).padStart(2, "0");
28678
- ticks.push({ pos: scale(val), label: `${hh}:${mm}` });
28679
- }
28680
- startDate.setMinutes(startDate.getMinutes() + stepMin);
28681
- }
28682
- } else if (span <= 822e-5) {
28683
- let stepHour = 1;
28684
- const spanHours = span * 8760;
28685
- if (spanHours > 48) stepHour = 6;
28686
- else if (spanHours > 24) stepHour = 3;
28687
- else if (spanHours > 12) stepHour = 2;
28688
- const singleDay = spanHours <= 24;
28689
- const startDate = fractionalYearToDate(domainMin);
28690
- startDate.setHours(
28691
- Math.floor(startDate.getHours() / stepHour) * stepHour,
28692
- 0,
28693
- 0,
28694
- 0
28695
- );
28696
- while (true) {
28697
- const val = dateToFractionalYear2(startDate);
28698
- if (val > domainMax) break;
28699
- if (val >= domainMin) {
28700
- const hh = String(startDate.getHours()).padStart(2, "0");
28701
- const mm = String(startDate.getMinutes()).padStart(2, "0");
28702
- if (singleDay) {
28703
- ticks.push({ pos: scale(val), label: `${hh}:${mm}` });
28704
- } else {
28705
- const mon = MONTH_ABBR2[startDate.getMonth()];
28706
- const d = startDate.getDate();
28707
- ticks.push({ pos: scale(val), label: `${mon} ${d} ${hh}:${mm}` });
28708
- }
28709
- }
28710
- startDate.setHours(startDate.getHours() + stepHour);
28711
- }
28712
- } else {
28713
- for (let y = minYear; y <= maxYear + 1; y++) {
28714
- for (let m = 1; m <= 12; m++) {
28715
- for (const d of [1, 8, 15, 22]) {
28716
- const val = y + (m - 1) / 12 + (d - 1) / 365;
28717
- if (val > domainMax) break;
28718
- if (val >= domainMin) {
28719
- ticks.push({
28720
- pos: scale(val),
28721
- label: `${MONTH_ABBR2[m - 1]} ${d}`
28722
- });
28723
- }
28724
- }
28725
- }
28726
- }
28727
- }
28728
- const collisionThreshold = 40;
28729
- if (boundaryStart !== void 0 && boundaryStartLabel) {
28730
- const boundaryPos = scale(boundaryStart);
28731
- ticks = ticks.filter(
28732
- (t) => Math.abs(t.pos - boundaryPos) >= collisionThreshold
28733
- );
28734
- ticks.unshift({ pos: boundaryPos, label: boundaryStartLabel });
28735
- }
28736
- if (boundaryEnd !== void 0 && boundaryEndLabel) {
28737
- const boundaryPos = scale(boundaryEnd);
28738
- ticks = ticks.filter(
28739
- (t) => Math.abs(t.pos - boundaryPos) >= collisionThreshold
28740
- );
28741
- ticks.push({ pos: boundaryPos, label: boundaryEndLabel });
28742
- }
28743
- return ticks;
28744
- }
28745
29134
  function renderTimeScale(g, scale, isVertical, innerWidth, innerHeight, textColor, boundaryStart, boundaryEnd, boundaryStartLabel, boundaryEndLabel) {
28746
29135
  const [domainMin, domainMax] = scale.domain();
28747
29136
  const ticks = computeTimeTicks(
@@ -30669,7 +31058,7 @@ function createExportContainer(width, height) {
30669
31058
  document.body.appendChild(container);
30670
31059
  return container;
30671
31060
  }
30672
- function finalizeSvgExport(container, theme, palette, options) {
31061
+ function finalizeSvgExport(container, theme, palette) {
30673
31062
  const svgEl = container.querySelector("svg");
30674
31063
  if (!svgEl) return "";
30675
31064
  if (theme === "transparent") {
@@ -30682,10 +31071,6 @@ function finalizeSvgExport(container, theme, palette, options) {
30682
31071
  svgEl.querySelectorAll("[data-export-ignore]").forEach((el) => el.remove());
30683
31072
  const svgHtml = svgEl.outerHTML;
30684
31073
  document.body.removeChild(container);
30685
- if (options?.branding !== false) {
30686
- const brandColor = theme === "transparent" ? "#888" : palette.textMuted;
30687
- return injectBranding(svgHtml, brandColor);
30688
- }
30689
31074
  return svgHtml;
30690
31075
  }
30691
31076
  async function renderForExport(content, theme, palette, orgExportState, options) {
@@ -30732,7 +31117,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
30732
31117
  activeTagGroup,
30733
31118
  hiddenAttributes
30734
31119
  );
30735
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31120
+ return finalizeSvgExport(container2, theme, effectivePalette2);
30736
31121
  }
30737
31122
  if (detectedType === "sitemap") {
30738
31123
  const { parseSitemap: parseSitemap2 } = await Promise.resolve().then(() => (init_parser7(), parser_exports7));
@@ -30774,7 +31159,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
30774
31159
  activeTagGroup,
30775
31160
  hiddenAttributes
30776
31161
  );
30777
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31162
+ return finalizeSvgExport(container2, theme, effectivePalette2);
30778
31163
  }
30779
31164
  if (detectedType === "kanban") {
30780
31165
  const { parseKanban: parseKanban2 } = await Promise.resolve().then(() => (init_parser5(), parser_exports5));
@@ -30793,7 +31178,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
30793
31178
  options?.tagGroup
30794
31179
  )
30795
31180
  });
30796
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31181
+ return finalizeSvgExport(container2, theme, effectivePalette2);
30797
31182
  }
30798
31183
  if (detectedType === "class") {
30799
31184
  const { parseClassDiagram: parseClassDiagram2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports2));
@@ -30817,7 +31202,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
30817
31202
  void 0,
30818
31203
  { width: exportWidth, height: exportHeight }
30819
31204
  );
30820
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31205
+ return finalizeSvgExport(container2, theme, effectivePalette2);
30821
31206
  }
30822
31207
  if (detectedType === "er") {
30823
31208
  const { parseERDiagram: parseERDiagram2 } = await Promise.resolve().then(() => (init_parser3(), parser_exports3));
@@ -30846,7 +31231,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
30846
31231
  options?.tagGroup
30847
31232
  )
30848
31233
  );
30849
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31234
+ return finalizeSvgExport(container2, theme, effectivePalette2);
30850
31235
  }
30851
31236
  if (detectedType === "boxes-and-lines") {
30852
31237
  const { parseBoxesAndLines: parseBoxesAndLines2 } = await Promise.resolve().then(() => (init_parser10(), parser_exports10));
@@ -30872,7 +31257,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
30872
31257
  activeTagGroup: options?.tagGroup
30873
31258
  }
30874
31259
  );
30875
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31260
+ return finalizeSvgExport(container2, theme, effectivePalette2);
30876
31261
  }
30877
31262
  if (detectedType === "c4") {
30878
31263
  const { parseC4: parseC42 } = await Promise.resolve().then(() => (init_parser6(), parser_exports6));
@@ -30911,7 +31296,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
30911
31296
  options?.tagGroup
30912
31297
  )
30913
31298
  );
30914
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31299
+ return finalizeSvgExport(container2, theme, effectivePalette2);
30915
31300
  }
30916
31301
  if (detectedType === "flowchart") {
30917
31302
  const { parseFlowchart: parseFlowchart2 } = await Promise.resolve().then(() => (init_flowchart_parser(), flowchart_parser_exports));
@@ -30931,7 +31316,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
30931
31316
  void 0,
30932
31317
  { width: EXPORT_WIDTH, height: EXPORT_HEIGHT }
30933
31318
  );
30934
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31319
+ return finalizeSvgExport(container2, theme, effectivePalette2);
30935
31320
  }
30936
31321
  if (detectedType === "infra") {
30937
31322
  const { parseInfra: parseInfra2 } = await Promise.resolve().then(() => (init_parser8(), parser_exports8));
@@ -30977,7 +31362,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
30977
31362
  infraSvg.setAttribute("width", String(exportWidth));
30978
31363
  infraSvg.setAttribute("height", String(exportHeight));
30979
31364
  }
30980
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31365
+ return finalizeSvgExport(container2, theme, effectivePalette2);
30981
31366
  }
30982
31367
  if (detectedType === "gantt") {
30983
31368
  const { parseGantt: parseGantt2 } = await Promise.resolve().then(() => (init_parser9(), parser_exports9));
@@ -30998,7 +31383,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
30998
31383
  void 0,
30999
31384
  { width: EXPORT_W, height: EXPORT_H }
31000
31385
  );
31001
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31386
+ return finalizeSvgExport(container2, theme, effectivePalette2);
31002
31387
  }
31003
31388
  if (detectedType === "state") {
31004
31389
  const { parseState: parseState2 } = await Promise.resolve().then(() => (init_state_parser(), state_parser_exports));
@@ -31018,7 +31403,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
31018
31403
  void 0,
31019
31404
  { width: EXPORT_WIDTH, height: EXPORT_HEIGHT }
31020
31405
  );
31021
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31406
+ return finalizeSvgExport(container2, theme, effectivePalette2);
31022
31407
  }
31023
31408
  const parsed = parseVisualization(content, palette);
31024
31409
  if (parsed.error && parsed.type !== "sequence") {
@@ -31110,15 +31495,15 @@ async function renderForExport(content, theme, palette, orgExportState, options)
31110
31495
  dims
31111
31496
  );
31112
31497
  }
31113
- return finalizeSvgExport(container, theme, effectivePalette, options);
31498
+ return finalizeSvgExport(container, theme, effectivePalette);
31114
31499
  }
31115
- var DEFAULT_CLOUD_OPTIONS, STOP_WORDS, SLOPE_MARGIN, SLOPE_LABEL_FONT_SIZE, SLOPE_CHAR_WIDTH, ARC_MARGIN, MONTH_ABBR2, EXPORT_WIDTH, EXPORT_HEIGHT;
31500
+ var DEFAULT_CLOUD_OPTIONS, STOP_WORDS, SLOPE_MARGIN, SLOPE_LABEL_FONT_SIZE, SLOPE_CHAR_WIDTH, ARC_MARGIN, EXPORT_WIDTH, EXPORT_HEIGHT;
31116
31501
  var init_d3 = __esm({
31117
31502
  "src/d3.ts"() {
31118
31503
  "use strict";
31119
31504
  init_fonts();
31120
- init_branding();
31121
31505
  init_label_layout();
31506
+ init_time_ticks();
31122
31507
  init_colors();
31123
31508
  init_palettes();
31124
31509
  init_color_utils();
@@ -31249,20 +31634,6 @@ var init_d3 = __esm({
31249
31634
  SLOPE_LABEL_FONT_SIZE = 14;
31250
31635
  SLOPE_CHAR_WIDTH = 8;
31251
31636
  ARC_MARGIN = { top: 60, right: 40, bottom: 60, left: 40 };
31252
- MONTH_ABBR2 = [
31253
- "Jan",
31254
- "Feb",
31255
- "Mar",
31256
- "Apr",
31257
- "May",
31258
- "Jun",
31259
- "Jul",
31260
- "Aug",
31261
- "Sep",
31262
- "Oct",
31263
- "Nov",
31264
- "Dec"
31265
- ];
31266
31637
  EXPORT_WIDTH = 1200;
31267
31638
  EXPORT_HEIGHT = 800;
31268
31639
  }
@@ -31727,6 +32098,7 @@ var require_lz_string = __commonJS({
31727
32098
 
31728
32099
  // src/index.ts
31729
32100
  init_diagnostics();
32101
+ init_arrows();
31730
32102
 
31731
32103
  // src/render.ts
31732
32104
  init_d3();
@@ -31762,8 +32134,8 @@ async function ensureDom() {
31762
32134
  async function render(content, options) {
31763
32135
  const theme = options?.theme ?? "light";
31764
32136
  const paletteName = options?.palette ?? "nord";
31765
- const branding = options?.branding ?? false;
31766
32137
  const paletteColors = getPalette(paletteName)[theme === "dark" ? "dark" : "light"];
32138
+ const { diagnostics } = parseDgmo(content);
31767
32139
  const chartType = parseDgmoChartType(content);
31768
32140
  const category = chartType ? getRenderCategory(chartType) : null;
31769
32141
  const legendExportState = options?.legendState ? {
@@ -31771,18 +32143,27 @@ async function render(content, options) {
31771
32143
  hiddenAttributes: options.legendState.hiddenAttributes ? new Set(options.legendState.hiddenAttributes) : void 0
31772
32144
  } : void 0;
31773
32145
  if (category === "data-chart") {
31774
- return renderExtendedChartForExport(content, theme, paletteColors, {
31775
- branding
31776
- });
32146
+ const svg2 = await renderExtendedChartForExport(
32147
+ content,
32148
+ theme,
32149
+ paletteColors
32150
+ );
32151
+ return { svg: svg2, diagnostics };
31777
32152
  }
31778
32153
  await ensureDom();
31779
- return renderForExport(content, theme, paletteColors, legendExportState, {
31780
- branding,
31781
- c4Level: options?.c4Level,
31782
- c4System: options?.c4System,
31783
- c4Container: options?.c4Container,
31784
- tagGroup: options?.tagGroup
31785
- });
32154
+ const svg = await renderForExport(
32155
+ content,
32156
+ theme,
32157
+ paletteColors,
32158
+ legendExportState,
32159
+ {
32160
+ c4Level: options?.c4Level,
32161
+ c4System: options?.c4System,
32162
+ c4Container: options?.c4Container,
32163
+ tagGroup: options?.tagGroup
32164
+ }
32165
+ );
32166
+ return { svg, diagnostics };
31786
32167
  }
31787
32168
 
31788
32169
  // src/index.ts
@@ -31790,172 +32171,9 @@ init_dgmo_router();
31790
32171
  init_chart();
31791
32172
  init_echarts();
31792
32173
  init_d3();
32174
+ init_time_ticks();
31793
32175
  init_parser();
31794
32176
  init_participant_inference();
31795
-
31796
- // src/dgmo-mermaid.ts
31797
- init_colors();
31798
- init_diagnostics();
31799
- var QUADRANT_LABEL_RE = /^(.+?)(?:\s*\(([^)]+)\))?\s*$/;
31800
- var DATA_POINT_RE = /^(.+?)\s+([0-9]*\.?[0-9]+)\s*,\s*([0-9]*\.?[0-9]+)\s*$/;
31801
- var QUADRANT_POSITIONS = /* @__PURE__ */ new Set([
31802
- "top-right",
31803
- "top-left",
31804
- "bottom-left",
31805
- "bottom-right"
31806
- ]);
31807
- function parseQuadrant(content) {
31808
- const result = {
31809
- title: null,
31810
- titleLineNumber: null,
31811
- xAxis: null,
31812
- xAxisLineNumber: null,
31813
- yAxis: null,
31814
- yAxisLineNumber: null,
31815
- quadrants: {
31816
- topRight: null,
31817
- topLeft: null,
31818
- bottomLeft: null,
31819
- bottomRight: null
31820
- },
31821
- points: [],
31822
- diagnostics: [],
31823
- error: null
31824
- };
31825
- const lines = content.split("\n");
31826
- for (let i = 0; i < lines.length; i++) {
31827
- const line10 = lines[i].trim();
31828
- const lineNumber = i + 1;
31829
- if (!line10 || line10.startsWith("//")) continue;
31830
- if (/^chart\s*:/i.test(line10)) continue;
31831
- const titleMatch = line10.match(/^title\s+(.+)/i);
31832
- if (titleMatch) {
31833
- result.title = titleMatch[1].trim();
31834
- result.titleLineNumber = lineNumber;
31835
- continue;
31836
- }
31837
- const xMatch = line10.match(/^x-label\s+(.+)/i);
31838
- if (xMatch) {
31839
- const parts = xMatch[1].split(",").map((s) => s.trim());
31840
- if (parts.length >= 2) {
31841
- result.xAxis = [parts[0], parts[1]];
31842
- result.xAxisLineNumber = lineNumber;
31843
- }
31844
- continue;
31845
- }
31846
- const yMatch = line10.match(/^y-label\s+(.+)/i);
31847
- if (yMatch) {
31848
- const parts = yMatch[1].split(",").map((s) => s.trim());
31849
- if (parts.length >= 2) {
31850
- result.yAxis = [parts[0], parts[1]];
31851
- result.yAxisLineNumber = lineNumber;
31852
- }
31853
- continue;
31854
- }
31855
- const posMatch = line10.match(
31856
- /^(top-right|top-left|bottom-left|bottom-right)\s+(.+)/i
31857
- );
31858
- if (posMatch) {
31859
- const position = posMatch[1].toLowerCase();
31860
- const labelMatch = posMatch[2].match(QUADRANT_LABEL_RE);
31861
- if (labelMatch) {
31862
- const label = {
31863
- text: labelMatch[1].trim(),
31864
- color: labelMatch[2] ? resolveColorWithDiagnostic(
31865
- labelMatch[2].trim(),
31866
- lineNumber,
31867
- result.diagnostics
31868
- ) ?? null : null,
31869
- lineNumber
31870
- };
31871
- if (position === "top-right") result.quadrants.topRight = label;
31872
- else if (position === "top-left") result.quadrants.topLeft = label;
31873
- else if (position === "bottom-left")
31874
- result.quadrants.bottomLeft = label;
31875
- else if (position === "bottom-right")
31876
- result.quadrants.bottomRight = label;
31877
- }
31878
- continue;
31879
- }
31880
- const pointMatch = line10.match(DATA_POINT_RE);
31881
- if (pointMatch) {
31882
- const key = pointMatch[1].trim().toLowerCase();
31883
- if (!QUADRANT_POSITIONS.has(key)) {
31884
- result.points.push({
31885
- label: pointMatch[1].trim(),
31886
- x: parseFloat(pointMatch[2]),
31887
- y: parseFloat(pointMatch[3]),
31888
- lineNumber
31889
- });
31890
- }
31891
- continue;
31892
- }
31893
- }
31894
- if (result.points.length === 0) {
31895
- const diag = makeDgmoError(
31896
- 1,
31897
- "No data points found. Add lines like: Label 0.5, 0.7"
31898
- );
31899
- result.diagnostics.push(diag);
31900
- result.error = formatDgmoError(diag);
31901
- }
31902
- return result;
31903
- }
31904
- function buildMermaidQuadrant(parsed, options = {}) {
31905
- const { isDark = false, textColor, mutedTextColor } = options;
31906
- const lines = [];
31907
- const fillAlpha = isDark ? "30" : "55";
31908
- const primaryText = textColor ?? (isDark ? "#d0d0d0" : "#333333");
31909
- const quadrantLabelText = mutedTextColor ?? (isDark ? "#888888" : "#666666");
31910
- const colorMap = {};
31911
- if (parsed.quadrants.topRight?.color)
31912
- colorMap.quadrant1Fill = parsed.quadrants.topRight.color + fillAlpha;
31913
- if (parsed.quadrants.topLeft?.color)
31914
- colorMap.quadrant2Fill = parsed.quadrants.topLeft.color + fillAlpha;
31915
- if (parsed.quadrants.bottomLeft?.color)
31916
- colorMap.quadrant3Fill = parsed.quadrants.bottomLeft.color + fillAlpha;
31917
- if (parsed.quadrants.bottomRight?.color)
31918
- colorMap.quadrant4Fill = parsed.quadrants.bottomRight.color + fillAlpha;
31919
- colorMap.quadrant1TextFill = quadrantLabelText;
31920
- colorMap.quadrant2TextFill = quadrantLabelText;
31921
- colorMap.quadrant3TextFill = quadrantLabelText;
31922
- colorMap.quadrant4TextFill = quadrantLabelText;
31923
- colorMap.quadrantPointTextFill = primaryText;
31924
- colorMap.quadrantXAxisTextFill = primaryText;
31925
- colorMap.quadrantYAxisTextFill = primaryText;
31926
- colorMap.quadrantTitleFill = primaryText;
31927
- const vars = JSON.stringify(colorMap);
31928
- lines.push(`%%{init: {"themeVariables": ${vars}}}%%`);
31929
- lines.push("quadrantChart");
31930
- if (parsed.title) {
31931
- lines.push(` title ${parsed.title}`);
31932
- }
31933
- if (parsed.xAxis) {
31934
- lines.push(` x-axis ${parsed.xAxis[0]} --> ${parsed.xAxis[1]}`);
31935
- }
31936
- if (parsed.yAxis) {
31937
- lines.push(` y-axis ${parsed.yAxis[0]} --> ${parsed.yAxis[1]}`);
31938
- }
31939
- const quote = (s) => /[\s,:[\]]/.test(s) ? `"${s}"` : s;
31940
- if (parsed.quadrants.topRight) {
31941
- lines.push(` quadrant-1 ${quote(parsed.quadrants.topRight.text)}`);
31942
- }
31943
- if (parsed.quadrants.topLeft) {
31944
- lines.push(` quadrant-2 ${quote(parsed.quadrants.topLeft.text)}`);
31945
- }
31946
- if (parsed.quadrants.bottomLeft) {
31947
- lines.push(` quadrant-3 ${quote(parsed.quadrants.bottomLeft.text)}`);
31948
- }
31949
- if (parsed.quadrants.bottomRight) {
31950
- lines.push(` quadrant-4 ${quote(parsed.quadrants.bottomRight.text)}`);
31951
- }
31952
- for (const point of parsed.points) {
31953
- lines.push(` ${quote(point.label)}: [${point.x}, ${point.y}]`);
31954
- }
31955
- return lines.join("\n");
31956
- }
31957
-
31958
- // src/index.ts
31959
32177
  init_flowchart_parser();
31960
32178
  init_state_parser();
31961
32179
  init_state_renderer();
@@ -32507,6 +32725,7 @@ init_legend_d3();
32507
32725
  init_legend_layout();
32508
32726
  init_d3();
32509
32727
  init_renderer10();
32728
+ init_collapse3();
32510
32729
  init_colors();
32511
32730
  init_palettes();
32512
32731
 
@@ -32514,6 +32733,24 @@ init_palettes();
32514
32733
  var import_lz_string = __toESM(require_lz_string(), 1);
32515
32734
  var DEFAULT_BASE_URL = "https://online.diagrammo.app";
32516
32735
  var COMPRESSED_SIZE_LIMIT = 8192;
32736
+ function encodeViewState(state) {
32737
+ const keys = Object.keys(state);
32738
+ if (keys.length === 0) return "";
32739
+ return (0, import_lz_string.compressToEncodedURIComponent)(JSON.stringify(state));
32740
+ }
32741
+ function decodeViewState(encoded) {
32742
+ if (!encoded) return {};
32743
+ try {
32744
+ const json = (0, import_lz_string.decompressFromEncodedURIComponent)(encoded);
32745
+ if (!json) return {};
32746
+ const parsed = JSON.parse(json);
32747
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
32748
+ return {};
32749
+ return parsed;
32750
+ } catch {
32751
+ return {};
32752
+ }
32753
+ }
32517
32754
  function encodeDiagramUrl(dsl, options) {
32518
32755
  const baseUrl = options?.baseUrl ?? DEFAULT_BASE_URL;
32519
32756
  const compressed = (0, import_lz_string.compressToEncodedURIComponent)(dsl);
@@ -32526,23 +32763,17 @@ function encodeDiagramUrl(dsl, options) {
32526
32763
  };
32527
32764
  }
32528
32765
  let hash = `dgmo=${compressed}`;
32529
- if (options?.viewState?.activeTagGroup) {
32530
- hash += `&tag=${encodeURIComponent(options.viewState.activeTagGroup)}`;
32531
- }
32532
- if (options?.viewState?.collapsedGroups?.length) {
32533
- hash += `&cg=${encodeURIComponent(options.viewState.collapsedGroups.join(","))}`;
32534
- }
32535
- if (options?.viewState?.swimlaneTagGroup) {
32536
- hash += `&swim=${encodeURIComponent(options.viewState.swimlaneTagGroup)}`;
32537
- }
32538
- if (options?.viewState?.collapsedLanes?.length) {
32539
- hash += `&cl=${encodeURIComponent(options.viewState.collapsedLanes.join(","))}`;
32766
+ if (options?.viewState) {
32767
+ const vsEncoded = encodeViewState(options.viewState);
32768
+ if (vsEncoded) {
32769
+ hash += `&vs=${vsEncoded}`;
32770
+ }
32540
32771
  }
32541
- if (options?.viewState?.palette && options.viewState.palette !== "nord") {
32542
- hash += `&pal=${encodeURIComponent(options.viewState.palette)}`;
32772
+ if (options?.palette && options.palette !== "nord") {
32773
+ hash += `&pal=${encodeURIComponent(options.palette)}`;
32543
32774
  }
32544
- if (options?.viewState?.theme && options.viewState.theme !== "dark") {
32545
- hash += `&th=${encodeURIComponent(options.viewState.theme)}`;
32775
+ if (options?.theme && options.theme !== "dark") {
32776
+ hash += `&th=${encodeURIComponent(options.theme)}`;
32546
32777
  }
32547
32778
  if (options?.filename) {
32548
32779
  hash += `&fn=${encodeURIComponent(options.filename)}`;
@@ -32552,6 +32783,8 @@ function encodeDiagramUrl(dsl, options) {
32552
32783
  function decodeDiagramUrl(hash) {
32553
32784
  const empty = { dsl: "", viewState: {} };
32554
32785
  let filename;
32786
+ let palette;
32787
+ let theme;
32555
32788
  if (!hash) return empty;
32556
32789
  let raw = hash;
32557
32790
  if (raw.startsWith("#") || raw.startsWith("?")) {
@@ -32559,38 +32792,31 @@ function decodeDiagramUrl(hash) {
32559
32792
  }
32560
32793
  const parts = raw.split("&");
32561
32794
  let payload = parts[0];
32562
- const viewState = {};
32795
+ let viewState = {};
32563
32796
  for (let i = 1; i < parts.length; i++) {
32564
32797
  const eq = parts[i].indexOf("=");
32565
32798
  if (eq === -1) continue;
32566
32799
  const key = parts[i].slice(0, eq);
32567
- const val = decodeURIComponent(parts[i].slice(eq + 1));
32568
- if (key === "tag" && val) {
32569
- viewState.activeTagGroup = val;
32570
- }
32571
- if (key === "cg" && val) {
32572
- viewState.collapsedGroups = val.split(",").filter(Boolean);
32573
- }
32574
- if (key === "swim" && val) {
32575
- viewState.swimlaneTagGroup = val;
32800
+ const val = parts[i].slice(eq + 1);
32801
+ if (key === "vs" && val) {
32802
+ viewState = decodeViewState(val);
32576
32803
  }
32577
- if (key === "cl" && val) {
32578
- viewState.collapsedLanes = val.split(",").filter(Boolean);
32804
+ if (key === "pal" && val) palette = decodeURIComponent(val);
32805
+ if (key === "th") {
32806
+ const decoded = decodeURIComponent(val);
32807
+ if (decoded === "light" || decoded === "dark") theme = decoded;
32579
32808
  }
32580
- if (key === "pal" && val) viewState.palette = val;
32581
- if (key === "th" && (val === "light" || val === "dark"))
32582
- viewState.theme = val;
32583
- if (key === "fn" && val) filename = val;
32809
+ if (key === "fn" && val) filename = decodeURIComponent(val);
32584
32810
  }
32585
32811
  if (payload.startsWith("dgmo=")) {
32586
32812
  payload = payload.slice(5);
32587
32813
  }
32588
- if (!payload) return { dsl: "", viewState, filename };
32814
+ if (!payload) return { dsl: "", viewState, palette, theme, filename };
32589
32815
  try {
32590
32816
  const result = (0, import_lz_string.decompressFromEncodedURIComponent)(payload);
32591
- return { dsl: result ?? "", viewState, filename };
32817
+ return { dsl: result ?? "", viewState, palette, theme, filename };
32592
32818
  } catch {
32593
- return { dsl: "", viewState, filename };
32819
+ return { dsl: "", viewState, palette, theme, filename };
32594
32820
  }
32595
32821
  }
32596
32822
 
@@ -33337,9 +33563,9 @@ registerExtractor("boxes-and-lines", extractBoxesAndLinesSymbols);
33337
33563
 
33338
33564
  // src/index.ts
33339
33565
  init_parsing();
33340
- init_branding();
33341
33566
  export {
33342
33567
  ALL_CHART_TYPES,
33568
+ ARROW_DIAGNOSTIC_CODES,
33343
33569
  CHART_TYPES,
33344
33570
  COMPLETION_REGISTRY,
33345
33571
  ENTITY_TYPES,
@@ -33350,17 +33576,15 @@ export {
33350
33576
  RECOGNIZED_COLOR_NAMES,
33351
33577
  RULE_COUNT,
33352
33578
  addDurationToDate,
33579
+ applyCollapseProjection,
33353
33580
  applyGroupOrdering,
33354
33581
  applyPositionOverrides,
33355
33582
  boldPalette,
33356
33583
  buildExtendedChartOption,
33357
- buildMermaidQuadrant,
33358
- buildMermaidThemeVars,
33359
33584
  buildNoteMessageMap,
33360
33585
  buildRenderSequence,
33361
33586
  buildSimpleChartOption,
33362
33587
  buildTagLaneRowList,
33363
- buildThemeCSS,
33364
33588
  calculateSchedule,
33365
33589
  catppuccinPalette,
33366
33590
  collapseBoxesAndLines,
@@ -33379,8 +33603,10 @@ export {
33379
33603
  computeTimeTicks,
33380
33604
  contrastText,
33381
33605
  decodeDiagramUrl,
33606
+ decodeViewState,
33382
33607
  draculaPalette,
33383
33608
  encodeDiagramUrl,
33609
+ encodeViewState,
33384
33610
  extractDiagramSymbols,
33385
33611
  extractTagDeclarations,
33386
33612
  formatDateLabel,
@@ -33399,7 +33625,6 @@ export {
33399
33625
  hslToHex,
33400
33626
  inferParticipantType,
33401
33627
  inferRoles,
33402
- injectBranding,
33403
33628
  isArchiveColumn,
33404
33629
  isExtendedChartType,
33405
33630
  isRecognizedColorName,
@@ -33424,8 +33649,8 @@ export {
33424
33649
  looksLikeSitemap,
33425
33650
  looksLikeState,
33426
33651
  makeDgmoError,
33652
+ matchColorParens,
33427
33653
  monokaiPalette,
33428
- mute,
33429
33654
  nord,
33430
33655
  nordPalette,
33431
33656
  oneDarkPalette,
@@ -33443,11 +33668,11 @@ export {
33443
33668
  parseFirstLine,
33444
33669
  parseFlowchart,
33445
33670
  parseGantt,
33671
+ parseInArrowLabel,
33446
33672
  parseInfra,
33447
33673
  parseInlineMarkdown,
33448
33674
  parseKanban,
33449
33675
  parseOrg,
33450
- parseQuadrant,
33451
33676
  parseSequenceDgmo,
33452
33677
  parseSitemap,
33453
33678
  parseState,
@@ -33506,6 +33731,7 @@ export {
33506
33731
  tokyoNightPalette,
33507
33732
  truncateBareUrl,
33508
33733
  validateComputed,
33509
- validateInfra
33734
+ validateInfra,
33735
+ validateLabelCharacters
33510
33736
  };
33511
33737
  //# sourceMappingURL=index.js.map