@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.cjs CHANGED
@@ -34,8 +34,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
34
34
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
35
35
 
36
36
  // src/diagnostics.ts
37
- function makeDgmoError(line10, message, severity = "error") {
38
- return { line: line10, message, severity };
37
+ function makeDgmoError(line10, message, severity = "error", code) {
38
+ return code !== void 0 ? { line: line10, message, severity, code } : { line: line10, message, severity };
39
39
  }
40
40
  function formatDgmoError(err) {
41
41
  return err.line > 0 ? `Line ${err.line}: ${err.message}` : err.message;
@@ -76,74 +76,227 @@ var init_diagnostics = __esm({
76
76
  }
77
77
  });
78
78
 
79
- // src/fonts.ts
80
- var FONT_FAMILY;
81
- var init_fonts = __esm({
82
- "src/fonts.ts"() {
79
+ // src/colors.ts
80
+ function isRecognizedColorName(name) {
81
+ return Object.prototype.hasOwnProperty.call(colorNames, name.toLowerCase());
82
+ }
83
+ function resolveColor(color, palette) {
84
+ if (!color) return null;
85
+ if (color.startsWith("#")) return null;
86
+ const lower = color.toLowerCase();
87
+ if (!isRecognizedColorName(lower)) return null;
88
+ if (palette) {
89
+ const named = palette.colors[lower];
90
+ if (named) return named;
91
+ }
92
+ return colorNames[lower];
93
+ }
94
+ function resolveColorWithDiagnostic(color, line10, diagnostics, palette) {
95
+ const resolved = resolveColor(color, palette);
96
+ if (resolved !== null) return resolved;
97
+ const hint = suggest(color, RECOGNIZED_COLOR_NAMES);
98
+ const suggestion = hint ? ` ${hint}` : "";
99
+ diagnostics.push(
100
+ makeDgmoError(
101
+ line10,
102
+ `Unknown color "${color}". Allowed: ${RECOGNIZED_COLOR_NAMES.join(", ")}.${suggestion}`,
103
+ "warning"
104
+ )
105
+ );
106
+ return void 0;
107
+ }
108
+ var nord, colorNames, RECOGNIZED_COLOR_NAMES, seriesColors;
109
+ var init_colors = __esm({
110
+ "src/colors.ts"() {
83
111
  "use strict";
84
- FONT_FAMILY = "Inter, system-ui, Avenir, Helvetica, Arial, sans-serif";
112
+ init_diagnostics();
113
+ nord = {
114
+ // Polar Night (dark)
115
+ nord0: "#2e3440",
116
+ nord1: "#3b4252",
117
+ nord2: "#434c5e",
118
+ nord3: "#4c566a",
119
+ // Snow Storm (light)
120
+ nord4: "#d8dee9",
121
+ nord5: "#e5e9f0",
122
+ nord6: "#eceff4",
123
+ // Frost (accent blues)
124
+ nord7: "#8fbcbb",
125
+ nord8: "#88c0d0",
126
+ nord9: "#81a1c1",
127
+ nord10: "#5e81ac",
128
+ // Aurora (colors)
129
+ nord11: "#bf616a",
130
+ // red
131
+ nord12: "#d08770",
132
+ // orange
133
+ nord13: "#ebcb8b",
134
+ // yellow
135
+ nord14: "#a3be8c",
136
+ // green
137
+ nord15: "#b48ead"
138
+ // purple
139
+ };
140
+ colorNames = {
141
+ red: nord.nord11,
142
+ orange: nord.nord12,
143
+ yellow: nord.nord13,
144
+ green: nord.nord14,
145
+ blue: nord.nord10,
146
+ purple: nord.nord15,
147
+ teal: nord.nord7,
148
+ cyan: nord.nord8,
149
+ gray: nord.nord3,
150
+ black: nord.nord0,
151
+ white: nord.nord6
152
+ };
153
+ RECOGNIZED_COLOR_NAMES = Object.freeze([
154
+ "red",
155
+ "orange",
156
+ "yellow",
157
+ "green",
158
+ "blue",
159
+ "purple",
160
+ "teal",
161
+ "cyan",
162
+ "gray",
163
+ "black",
164
+ "white"
165
+ ]);
166
+ seriesColors = [
167
+ nord.nord10,
168
+ // blue
169
+ nord.nord14,
170
+ // green
171
+ nord.nord13,
172
+ // yellow
173
+ nord.nord12,
174
+ // orange
175
+ nord.nord15,
176
+ // purple
177
+ nord.nord11,
178
+ // red
179
+ nord.nord7,
180
+ // teal
181
+ nord.nord8
182
+ // light blue
183
+ ];
85
184
  }
86
185
  });
87
186
 
88
- // src/branding.ts
89
- var branding_exports = {};
90
- __export(branding_exports, {
91
- injectBranding: () => injectBranding
92
- });
93
- function injectBranding(svgHtml, mutedColor) {
94
- if (!svgHtml) return svgHtml;
95
- 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>`;
96
- const vbMatch = svgHtml.match(/viewBox="([^"]+)"/);
97
- const heightAttrMatch = svgHtml.match(/height="([^"]+)"/);
98
- if (vbMatch) {
99
- const parts = vbMatch[1].split(/\s+/).map(Number);
100
- if (parts.length === 4) {
101
- const [vx, vy, vw, vh] = parts;
102
- const newVh = vh + BRANDING_HEIGHT;
103
- const textX = vx + vw - 4;
104
- const textY = vy + vh + BRANDING_HEIGHT - 6;
105
- const positioned = brandingText.replace('x="0" y="0"', `x="${textX}" y="${textY}"`);
106
- let result = svgHtml.replace(
107
- /viewBox="[^"]+"/,
108
- `viewBox="${vx} ${vy} ${vw} ${newVh}"`
109
- );
110
- if (heightAttrMatch) {
111
- const oldH = parseFloat(heightAttrMatch[1]);
112
- if (!isNaN(oldH)) {
113
- result = result.replace(
114
- `height="${heightAttrMatch[1]}"`,
115
- `height="${oldH + BRANDING_HEIGHT}"`
116
- );
117
- }
118
- }
119
- result = result.replace("</svg>", `${positioned}</svg>`);
120
- return result;
121
- }
187
+ // src/utils/arrows.ts
188
+ function validateLabelCharacters(label, lineNumber) {
189
+ const out = [];
190
+ if (label.includes("->") || label.includes("~>")) {
191
+ out.push(
192
+ makeDgmoError(
193
+ lineNumber,
194
+ '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.',
195
+ "error",
196
+ ARROW_DIAGNOSTIC_CODES.ARROW_SUBSTRING_IN_LABEL
197
+ )
198
+ );
122
199
  }
123
- if (heightAttrMatch) {
124
- const widthMatch = svgHtml.match(/width="([^"]+)"/);
125
- const w = widthMatch ? parseFloat(widthMatch[1]) : 800;
126
- const h = parseFloat(heightAttrMatch[1]);
127
- if (!isNaN(h) && !isNaN(w)) {
128
- const textX = w - 4;
129
- const textY = h + BRANDING_HEIGHT - 6;
130
- const positioned = brandingText.replace('x="0" y="0"', `x="${textX}" y="${textY}"`);
131
- let result = svgHtml.replace(
132
- `height="${heightAttrMatch[1]}"`,
133
- `height="${h + BRANDING_HEIGHT}"`
200
+ for (const ch of label) {
201
+ const cp = ch.codePointAt(0);
202
+ const isC0 = cp >= 0 && cp <= 31 && cp !== 9;
203
+ const isDel = cp === 127;
204
+ if (isC0 || isDel) {
205
+ const hex = cp.toString(16).toUpperCase().padStart(4, "0");
206
+ out.push(
207
+ makeDgmoError(
208
+ lineNumber,
209
+ `Label contains a control character (U+${hex}). Remove it and use plain text.`,
210
+ "error",
211
+ ARROW_DIAGNOSTIC_CODES.CONTROL_CHAR_IN_LABEL
212
+ )
134
213
  );
135
- result = result.replace("</svg>", `${positioned}</svg>`);
136
- return result;
214
+ break;
137
215
  }
138
216
  }
139
- return svgHtml;
217
+ return out;
218
+ }
219
+ function parseInArrowLabel(rawLabel, lineNumber) {
220
+ const trimmed = rawLabel.trim();
221
+ if (trimmed.length === 0) {
222
+ return { label: void 0, diagnostics: [] };
223
+ }
224
+ const diagnostics = validateLabelCharacters(trimmed, lineNumber);
225
+ return { label: trimmed, diagnostics };
226
+ }
227
+ function matchColorParens(content) {
228
+ const m = content.match(/^\(([A-Za-z]+)\)$/);
229
+ if (!m) return null;
230
+ const candidate = m[1].toLowerCase();
231
+ if (RECOGNIZED_COLOR_NAMES.includes(candidate)) {
232
+ return candidate;
233
+ }
234
+ return null;
235
+ }
236
+ function parseArrow(line10) {
237
+ if (BIDI_SYNC_RE.test(line10) || BIDI_ASYNC_RE.test(line10)) {
238
+ return {
239
+ error: "Bidirectional arrows are no longer supported. Use two separate lines: 'A -msg-> B' and 'B -msg-> A'"
240
+ };
241
+ }
242
+ if (RETURN_SYNC_LABELED_RE.test(line10) || RETURN_ASYNC_LABELED_RE.test(line10)) {
243
+ const m = line10.match(RETURN_SYNC_LABELED_RE) ?? line10.match(RETURN_ASYNC_LABELED_RE);
244
+ const from = m[3];
245
+ const to = m[1];
246
+ const label = m[2].trim();
247
+ return {
248
+ error: `Left-pointing arrows are no longer supported. Write '${from} -${label}-> ${to}' instead`
249
+ };
250
+ }
251
+ const patterns = [
252
+ { re: SYNC_LABELED_RE, async: false },
253
+ { re: ASYNC_LABELED_RE, async: true }
254
+ ];
255
+ for (const { re, async: isAsync } of patterns) {
256
+ const m = line10.match(re);
257
+ if (!m) continue;
258
+ const label = m[2].trim();
259
+ if (!label) return null;
260
+ return {
261
+ from: m[1],
262
+ to: m[3],
263
+ label,
264
+ async: isAsync
265
+ };
266
+ }
267
+ return null;
140
268
  }
141
- var BRANDING_HEIGHT;
142
- var init_branding = __esm({
143
- "src/branding.ts"() {
269
+ var ARROW_DIAGNOSTIC_CODES, SYNC_LABELED_RE, ASYNC_LABELED_RE, RETURN_SYNC_LABELED_RE, RETURN_ASYNC_LABELED_RE, BIDI_SYNC_RE, BIDI_ASYNC_RE;
270
+ var init_arrows = __esm({
271
+ "src/utils/arrows.ts"() {
144
272
  "use strict";
145
- init_fonts();
146
- BRANDING_HEIGHT = 20;
273
+ init_diagnostics();
274
+ init_colors();
275
+ ARROW_DIAGNOSTIC_CODES = {
276
+ /** Active: label contains `->` or `~>` substring (TD-13). */
277
+ ARROW_SUBSTRING_IN_LABEL: "E_ARROW_SUBSTRING_IN_LABEL",
278
+ /** Active: label contains a forbidden control character (TD-14). */
279
+ CONTROL_CHAR_IN_LABEL: "E_CONTROL_CHAR_IN_LABEL",
280
+ /** Reserved: not currently emitted by any parser. See JSDoc above. */
281
+ TRAILING_ARROW_TEXT: "E_TRAILING_ARROW_TEXT",
282
+ /** Reserved: not currently emitted by any parser. See JSDoc above. */
283
+ MIXED_ARROW_DELIMITERS: "E_MIXED_ARROW_DELIMITERS"
284
+ };
285
+ SYNC_LABELED_RE = /^(.+?)\s*-(.+)->\s*(.+)$/;
286
+ ASYNC_LABELED_RE = /^(.+?)\s*~(.+)~>\s*(.+)$/;
287
+ RETURN_SYNC_LABELED_RE = /^(.+?)\s*<-(.+)-\s*(.+)$/;
288
+ RETURN_ASYNC_LABELED_RE = /^(.+?)\s*<~(.+)~\s*(.+)$/;
289
+ BIDI_SYNC_RE = /^(.+?)\s*<-(.+)->\s*(.+)$/;
290
+ BIDI_ASYNC_RE = /^(.+?)\s*<~(.+)~>\s*(.+)$/;
291
+ }
292
+ });
293
+
294
+ // src/fonts.ts
295
+ var FONT_FAMILY;
296
+ var init_fonts = __esm({
297
+ "src/fonts.ts"() {
298
+ "use strict";
299
+ FONT_FAMILY = "Inter, system-ui, Avenir, Helvetica, Arial, sans-serif";
147
300
  }
148
301
  });
149
302
 
@@ -324,110 +477,158 @@ var init_label_layout = __esm({
324
477
  }
325
478
  });
326
479
 
327
- // src/colors.ts
328
- function isRecognizedColorName(name) {
329
- return Object.prototype.hasOwnProperty.call(colorNames, name.toLowerCase());
480
+ // src/utils/time-ticks.ts
481
+ function fractionalYearToDate(frac) {
482
+ const year = Math.floor(frac);
483
+ const remainder = frac - year;
484
+ const monthFrac = remainder * 12;
485
+ const month = Math.floor(monthFrac);
486
+ const monthRemainder = remainder - month / 12;
487
+ const dayFrac = monthRemainder * 365;
488
+ const day = Math.floor(dayFrac) + 1;
489
+ const dayRemainder = dayFrac - Math.floor(dayFrac);
490
+ const hourFrac = dayRemainder * 24;
491
+ const hour = Math.floor(hourFrac);
492
+ const minute = Math.round((hourFrac - hour) * 60);
493
+ return new Date(year, month, day, hour, minute);
330
494
  }
331
- function resolveColor(color, palette) {
332
- if (!color) return null;
333
- if (color.startsWith("#")) return null;
334
- const lower = color.toLowerCase();
335
- if (!isRecognizedColorName(lower)) return null;
336
- if (palette) {
337
- const named = palette.colors[lower];
338
- if (named) return named;
339
- }
340
- return colorNames[lower];
495
+ function dateToFractionalYear(d) {
496
+ return d.getFullYear() + d.getMonth() / 12 + (d.getDate() - 1) / 365 + d.getHours() / 8760 + d.getMinutes() / 525600;
341
497
  }
342
- function resolveColorWithDiagnostic(color, line10, diagnostics, palette) {
343
- const resolved = resolveColor(color, palette);
344
- if (resolved !== null) return resolved;
345
- const hint = suggest(color, RECOGNIZED_COLOR_NAMES);
346
- const suggestion = hint ? ` ${hint}` : "";
347
- diagnostics.push(
348
- makeDgmoError(
349
- line10,
350
- `Unknown color "${color}". Allowed: ${RECOGNIZED_COLOR_NAMES.join(", ")}.${suggestion}`,
351
- "warning"
352
- )
353
- );
354
- return void 0;
498
+ function computeTimeTicks(domainMin, domainMax, scale, boundaryStart, boundaryEnd, boundaryStartLabel, boundaryEndLabel) {
499
+ const minYear = Math.floor(domainMin);
500
+ const maxYear = Math.floor(domainMax);
501
+ const span = domainMax - domainMin;
502
+ let ticks = [];
503
+ const firstYear = Math.ceil(domainMin);
504
+ const lastYear = Math.floor(domainMax);
505
+ if (lastYear >= firstYear + 1) {
506
+ const yearSpan = lastYear - firstYear;
507
+ let step = 1;
508
+ if (yearSpan > 80) step = 20;
509
+ else if (yearSpan > 40) step = 10;
510
+ else if (yearSpan > 20) step = 5;
511
+ else if (yearSpan > 10) step = 2;
512
+ const alignedFirst = Math.ceil(firstYear / step) * step;
513
+ for (let y = alignedFirst; y <= lastYear; y += step) {
514
+ ticks.push({ pos: scale(y), label: String(y) });
515
+ }
516
+ } else if (span > 0.25) {
517
+ const crossesYear = maxYear > minYear;
518
+ for (let y = minYear; y <= maxYear + 1; y++) {
519
+ for (let m = 1; m <= 12; m++) {
520
+ const val = y + (m - 1) / 12;
521
+ if (val > domainMax) break;
522
+ if (val >= domainMin) {
523
+ ticks.push({
524
+ pos: scale(val),
525
+ label: crossesYear ? `${MONTH_ABBR[m - 1]} '${String(y).slice(-2)}` : MONTH_ABBR[m - 1]
526
+ });
527
+ }
528
+ }
529
+ }
530
+ } else if (span <= 685e-6) {
531
+ let stepMin = 5;
532
+ const spanHours = span * 8760;
533
+ if (spanHours > 3) stepMin = 30;
534
+ else if (spanHours > 1) stepMin = 15;
535
+ else if (spanHours > 0.5) stepMin = 10;
536
+ const startDate = fractionalYearToDate(domainMin);
537
+ startDate.setMinutes(
538
+ Math.floor(startDate.getMinutes() / stepMin) * stepMin,
539
+ 0,
540
+ 0
541
+ );
542
+ while (true) {
543
+ const val = dateToFractionalYear(startDate);
544
+ if (val > domainMax) break;
545
+ if (val >= domainMin) {
546
+ const hh = String(startDate.getHours()).padStart(2, "0");
547
+ const mm = String(startDate.getMinutes()).padStart(2, "0");
548
+ ticks.push({ pos: scale(val), label: `${hh}:${mm}` });
549
+ }
550
+ startDate.setMinutes(startDate.getMinutes() + stepMin);
551
+ }
552
+ } else if (span <= 822e-5) {
553
+ let stepHour = 1;
554
+ const spanHours = span * 8760;
555
+ if (spanHours > 48) stepHour = 6;
556
+ else if (spanHours > 24) stepHour = 3;
557
+ else if (spanHours > 12) stepHour = 2;
558
+ const singleDay = spanHours <= 24;
559
+ const startDate = fractionalYearToDate(domainMin);
560
+ startDate.setHours(
561
+ Math.floor(startDate.getHours() / stepHour) * stepHour,
562
+ 0,
563
+ 0,
564
+ 0
565
+ );
566
+ while (true) {
567
+ const val = dateToFractionalYear(startDate);
568
+ if (val > domainMax) break;
569
+ if (val >= domainMin) {
570
+ const hh = String(startDate.getHours()).padStart(2, "0");
571
+ const mm = String(startDate.getMinutes()).padStart(2, "0");
572
+ if (singleDay) {
573
+ ticks.push({ pos: scale(val), label: `${hh}:${mm}` });
574
+ } else {
575
+ const mon = MONTH_ABBR[startDate.getMonth()];
576
+ const d = startDate.getDate();
577
+ ticks.push({ pos: scale(val), label: `${mon} ${d} ${hh}:${mm}` });
578
+ }
579
+ }
580
+ startDate.setHours(startDate.getHours() + stepHour);
581
+ }
582
+ } else {
583
+ for (let y = minYear; y <= maxYear + 1; y++) {
584
+ for (let m = 1; m <= 12; m++) {
585
+ for (const d of [1, 8, 15, 22]) {
586
+ const val = y + (m - 1) / 12 + (d - 1) / 365;
587
+ if (val > domainMax) break;
588
+ if (val >= domainMin) {
589
+ ticks.push({
590
+ pos: scale(val),
591
+ label: `${MONTH_ABBR[m - 1]} ${d}`
592
+ });
593
+ }
594
+ }
595
+ }
596
+ }
597
+ }
598
+ const collisionThreshold = 40;
599
+ if (boundaryStart !== void 0 && boundaryStartLabel) {
600
+ const boundaryPos = scale(boundaryStart);
601
+ ticks = ticks.filter(
602
+ (t) => Math.abs(t.pos - boundaryPos) >= collisionThreshold
603
+ );
604
+ ticks.unshift({ pos: boundaryPos, label: boundaryStartLabel });
605
+ }
606
+ if (boundaryEnd !== void 0 && boundaryEndLabel) {
607
+ const boundaryPos = scale(boundaryEnd);
608
+ ticks = ticks.filter(
609
+ (t) => Math.abs(t.pos - boundaryPos) >= collisionThreshold
610
+ );
611
+ ticks.push({ pos: boundaryPos, label: boundaryEndLabel });
612
+ }
613
+ return ticks;
355
614
  }
356
- var nord, colorNames, RECOGNIZED_COLOR_NAMES, seriesColors;
357
- var init_colors = __esm({
358
- "src/colors.ts"() {
615
+ var MONTH_ABBR;
616
+ var init_time_ticks = __esm({
617
+ "src/utils/time-ticks.ts"() {
359
618
  "use strict";
360
- init_diagnostics();
361
- nord = {
362
- // Polar Night (dark)
363
- nord0: "#2e3440",
364
- nord1: "#3b4252",
365
- nord2: "#434c5e",
366
- nord3: "#4c566a",
367
- // Snow Storm (light)
368
- nord4: "#d8dee9",
369
- nord5: "#e5e9f0",
370
- nord6: "#eceff4",
371
- // Frost (accent blues)
372
- nord7: "#8fbcbb",
373
- nord8: "#88c0d0",
374
- nord9: "#81a1c1",
375
- nord10: "#5e81ac",
376
- // Aurora (colors)
377
- nord11: "#bf616a",
378
- // red
379
- nord12: "#d08770",
380
- // orange
381
- nord13: "#ebcb8b",
382
- // yellow
383
- nord14: "#a3be8c",
384
- // green
385
- nord15: "#b48ead"
386
- // purple
387
- };
388
- colorNames = {
389
- red: nord.nord11,
390
- orange: nord.nord12,
391
- yellow: nord.nord13,
392
- green: nord.nord14,
393
- blue: nord.nord10,
394
- purple: nord.nord15,
395
- teal: nord.nord7,
396
- cyan: nord.nord8,
397
- gray: nord.nord3,
398
- black: nord.nord0,
399
- white: nord.nord6
400
- };
401
- RECOGNIZED_COLOR_NAMES = Object.freeze([
402
- "red",
403
- "orange",
404
- "yellow",
405
- "green",
406
- "blue",
407
- "purple",
408
- "teal",
409
- "cyan",
410
- "gray",
411
- "black",
412
- "white"
413
- ]);
414
- seriesColors = [
415
- nord.nord10,
416
- // blue
417
- nord.nord14,
418
- // green
419
- nord.nord13,
420
- // yellow
421
- nord.nord12,
422
- // orange
423
- nord.nord15,
424
- // purple
425
- nord.nord11,
426
- // red
427
- nord.nord7,
428
- // teal
429
- nord.nord8
430
- // light blue
619
+ MONTH_ABBR = [
620
+ "Jan",
621
+ "Feb",
622
+ "Mar",
623
+ "Apr",
624
+ "May",
625
+ "Jun",
626
+ "Jul",
627
+ "Aug",
628
+ "Sep",
629
+ "Oct",
630
+ "Nov",
631
+ "Dec"
431
632
  ];
432
633
  }
433
634
  });
@@ -556,10 +757,6 @@ function hexToHSLString(hex) {
556
757
  const { h, s, l } = hexToHSL(hex);
557
758
  return `${h} ${s}% ${l}%`;
558
759
  }
559
- function mute(hex) {
560
- const { h, s, l } = hexToHSL(hex);
561
- return hslToHex(h, Math.min(s, 35), Math.min(l, 36));
562
- }
563
760
  function tint(hex, amount) {
564
761
  const raw = hex.replace("#", "");
565
762
  const full = raw.length === 3 ? raw[0] + raw[0] + raw[1] + raw[1] + raw[2] + raw[2] : raw;
@@ -1501,177 +1698,10 @@ var init_monokai = __esm({
1501
1698
  }
1502
1699
  });
1503
1700
 
1504
- // src/palettes/mermaid-bridge.ts
1505
- function buildMermaidThemeVars(colors, isDark) {
1506
- const c = colors.colors;
1507
- const accentOrder = [
1508
- c.blue,
1509
- c.red,
1510
- c.green,
1511
- c.yellow,
1512
- c.purple,
1513
- c.orange,
1514
- c.teal,
1515
- c.cyan,
1516
- colors.secondary
1517
- ];
1518
- const fills = isDark ? accentOrder.map(mute) : accentOrder;
1519
- return {
1520
- // ── Backgrounds ──
1521
- background: isDark ? colors.overlay : colors.border,
1522
- mainBkg: colors.surface,
1523
- // ── Primary/Secondary/Tertiary nodes ──
1524
- primaryColor: isDark ? colors.primary : colors.surface,
1525
- primaryTextColor: colors.text,
1526
- primaryBorderColor: isDark ? colors.secondary : colors.border,
1527
- secondaryColor: colors.secondary,
1528
- secondaryTextColor: contrastText(colors.secondary, colors.text, colors.bg),
1529
- secondaryBorderColor: colors.primary,
1530
- tertiaryColor: colors.accent,
1531
- tertiaryTextColor: contrastText(colors.accent, colors.text, colors.bg),
1532
- tertiaryBorderColor: colors.border,
1533
- // ── Lines & text ──
1534
- lineColor: colors.textMuted,
1535
- textColor: colors.text,
1536
- // ── Clusters ──
1537
- clusterBkg: colors.bg,
1538
- clusterBorder: isDark ? colors.border : colors.textMuted,
1539
- titleColor: colors.text,
1540
- // ── Labels ──
1541
- edgeLabelBackground: "transparent",
1542
- // ── Notes (sequence diagrams) ──
1543
- noteBkgColor: colors.bg,
1544
- noteTextColor: colors.text,
1545
- noteBorderColor: isDark ? colors.border : colors.textMuted,
1546
- // ── Actors (sequence diagrams) ──
1547
- actorBkg: colors.surface,
1548
- actorTextColor: colors.text,
1549
- actorBorder: isDark ? colors.border : colors.textMuted,
1550
- actorLineColor: colors.textMuted,
1551
- // ── Signals (sequence diagrams) ──
1552
- signalColor: colors.textMuted,
1553
- signalTextColor: colors.text,
1554
- // ── Labels ──
1555
- labelColor: colors.text,
1556
- labelTextColor: colors.text,
1557
- labelBoxBkgColor: colors.surface,
1558
- labelBoxBorderColor: isDark ? colors.border : colors.textMuted,
1559
- // ── Loop boxes ──
1560
- loopTextColor: colors.text,
1561
- // ── Activation (sequence diagrams) ──
1562
- activationBkgColor: isDark ? colors.overlay : colors.border,
1563
- activationBorderColor: isDark ? colors.border : colors.textMuted,
1564
- // ── Sequence numbers ──
1565
- sequenceNumberColor: isDark ? colors.text : colors.bg,
1566
- // ── State diagrams ──
1567
- labelBackgroundColor: colors.surface,
1568
- // ── Pie chart (9 slices) ──
1569
- // Dark mode: use muted fills so light pieSectionTextColor stays readable
1570
- ...Object.fromEntries(
1571
- (isDark ? fills : accentOrder).map((col, i) => [`pie${i + 1}`, col])
1572
- ),
1573
- pieTitleTextColor: colors.text,
1574
- pieSectionTextColor: isDark ? colors.text : colors.bg,
1575
- pieLegendTextColor: colors.text,
1576
- pieStrokeColor: "transparent",
1577
- pieOuterStrokeWidth: "0px",
1578
- pieOuterStrokeColor: "transparent",
1579
- // ── cScale (9 tiers) — muted in dark mode ──
1580
- ...Object.fromEntries(fills.map((f, i) => [`cScale${i}`, f])),
1581
- ...Object.fromEntries(
1582
- fills.map((_, i) => [
1583
- `cScaleLabel${i}`,
1584
- isDark ? colors.text : i < 2 || i > 6 ? colors.bg : colors.text
1585
- ])
1586
- ),
1587
- // ── fillType (8 slots) ──
1588
- ...Object.fromEntries(
1589
- [0, 1, 2, 3, 4, 5, 6, 7].map((i) => [
1590
- `fillType${i}`,
1591
- fills[i % fills.length]
1592
- ])
1593
- ),
1594
- // ── Journey actors (6 slots) ──
1595
- ...Object.fromEntries(
1596
- [c.red, c.green, c.yellow, c.purple, c.orange, c.teal].map((color, i) => [
1597
- `actor${i}`,
1598
- color
1599
- ])
1600
- ),
1601
- // ── Flowchart ──
1602
- nodeBorder: isDark ? colors.border : colors.textMuted,
1603
- nodeTextColor: colors.text,
1604
- // ── Gantt ──
1605
- gridColor: isDark ? colors.textMuted : colors.border,
1606
- doneTaskBkgColor: c.green,
1607
- doneTaskBorderColor: isDark ? colors.border : colors.textMuted,
1608
- activeTaskBkgColor: colors.secondary,
1609
- activeTaskBorderColor: colors.primary,
1610
- critBkgColor: c.orange,
1611
- critBorderColor: c.red,
1612
- taskBkgColor: colors.surface,
1613
- taskBorderColor: isDark ? colors.border : colors.textMuted,
1614
- taskTextColor: contrastText(colors.surface, colors.text, colors.bg),
1615
- taskTextDarkColor: colors.bg,
1616
- taskTextLightColor: colors.text,
1617
- taskTextOutsideColor: colors.text,
1618
- doneTaskTextColor: contrastText(c.green, colors.text, colors.bg),
1619
- activeTaskTextColor: contrastText(colors.secondary, colors.text, colors.bg),
1620
- critTaskTextColor: contrastText(c.orange, colors.text, colors.bg),
1621
- sectionBkgColor: isDark ? shade(colors.primary, colors.bg, 0.6) : tint(colors.primary, 0.6),
1622
- altSectionBkgColor: colors.bg,
1623
- sectionBkgColor2: isDark ? shade(colors.primary, colors.bg, 0.6) : tint(colors.primary, 0.6),
1624
- todayLineColor: c.yellow,
1625
- // ── Quadrant ──
1626
- quadrant1Fill: isDark ? shade(c.green, colors.bg, 0.75) : tint(c.green, 0.75),
1627
- quadrant2Fill: isDark ? shade(c.blue, colors.bg, 0.75) : tint(c.blue, 0.75),
1628
- quadrant3Fill: isDark ? shade(c.red, colors.bg, 0.75) : tint(c.red, 0.75),
1629
- quadrant4Fill: isDark ? shade(c.yellow, colors.bg, 0.75) : tint(c.yellow, 0.75),
1630
- quadrant1TextFill: colors.text,
1631
- quadrant2TextFill: colors.text,
1632
- quadrant3TextFill: colors.text,
1633
- quadrant4TextFill: colors.text,
1634
- quadrantPointFill: isDark ? c.cyan : c.blue,
1635
- quadrantPointTextFill: colors.text,
1636
- quadrantXAxisTextFill: colors.text,
1637
- quadrantYAxisTextFill: colors.text,
1638
- quadrantTitleFill: colors.text,
1639
- quadrantInternalBorderStrokeFill: colors.border,
1640
- quadrantExternalBorderStrokeFill: colors.border
1641
- };
1642
- }
1643
- function buildThemeCSS(palette, isDark) {
1644
- const base = `
1645
- .branchLabelBkg { fill: transparent !important; stroke: transparent !important; }
1646
- .commit-label-bkg { fill: transparent !important; stroke: transparent !important; }
1647
- .tag-label-bkg { fill: transparent !important; stroke: transparent !important; }
1648
-
1649
- /* GitGraph: ensure commit and branch label text matches palette */
1650
- .commit-label { fill: ${palette.text} !important; }
1651
- .branch-label { fill: ${palette.text} !important; }
1652
- .tag-label { fill: ${palette.text} !important; }
1653
- `;
1654
- if (!isDark) return base;
1655
- return base + `
1656
- /* Flowchart: ensure node and edge label text is readable */
1657
- .nodeLabel, .label { color: ${palette.text} !important; fill: ${palette.text} !important; }
1658
- .edgeLabel { color: ${palette.text} !important; fill: ${palette.text} !important; }
1659
- .edgeLabel .label { color: ${palette.text} !important; fill: ${palette.text} !important; }
1660
- `;
1661
- }
1662
- var init_mermaid_bridge = __esm({
1663
- "src/palettes/mermaid-bridge.ts"() {
1664
- "use strict";
1665
- init_color_utils();
1666
- }
1667
- });
1668
-
1669
1701
  // src/palettes/index.ts
1670
1702
  var palettes_exports = {};
1671
1703
  __export(palettes_exports, {
1672
1704
  boldPalette: () => boldPalette,
1673
- buildMermaidThemeVars: () => buildMermaidThemeVars,
1674
- buildThemeCSS: () => buildThemeCSS,
1675
1705
  catppuccinPalette: () => catppuccinPalette,
1676
1706
  contrastText: () => contrastText,
1677
1707
  draculaPalette: () => draculaPalette,
@@ -1685,7 +1715,6 @@ __export(palettes_exports, {
1685
1715
  hslToHex: () => hslToHex,
1686
1716
  isValidHex: () => isValidHex,
1687
1717
  monokaiPalette: () => monokaiPalette,
1688
- mute: () => mute,
1689
1718
  nordPalette: () => nordPalette,
1690
1719
  oneDarkPalette: () => oneDarkPalette,
1691
1720
  registerPalette: () => registerPalette,
@@ -1710,7 +1739,6 @@ var init_palettes = __esm({
1710
1739
  init_tokyo_night();
1711
1740
  init_dracula();
1712
1741
  init_monokai();
1713
- init_mermaid_bridge();
1714
1742
  }
1715
1743
  });
1716
1744
 
@@ -2102,7 +2130,7 @@ function measureLegendText(text, fontSize) {
2102
2130
  }
2103
2131
  return w;
2104
2132
  }
2105
- 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;
2133
+ 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;
2106
2134
  var init_legend_constants = __esm({
2107
2135
  "src/utils/legend-constants.ts"() {
2108
2136
  "use strict";
@@ -2209,6 +2237,10 @@ var init_legend_constants = __esm({
2209
2237
  DEFAULT_W = 0.56;
2210
2238
  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";
2211
2239
  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";
2240
+ 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";
2241
+ LEGEND_TOGGLE_DOT_R = LEGEND_DOT_R;
2242
+ LEGEND_TOGGLE_OFF_OPACITY = 0.4;
2243
+ LEGEND_GEAR_PILL_W = 14 + LEGEND_PILL_PAD;
2212
2244
  }
2213
2245
  });
2214
2246
 
@@ -2282,6 +2314,63 @@ function capsuleWidth(name, entries, containerWidth, addonWidth = 0) {
2282
2314
  visibleEntries: entries.length
2283
2315
  };
2284
2316
  }
2317
+ function controlsGroupCapsuleWidth(toggles) {
2318
+ let w = LEGEND_CAPSULE_PAD * 2 + LEGEND_GEAR_PILL_W + 4;
2319
+ for (const t of toggles) {
2320
+ w += LEGEND_TOGGLE_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + measureLegendText(t.label, LEGEND_ENTRY_FONT_SIZE) + LEGEND_ENTRY_TRAIL;
2321
+ }
2322
+ return w;
2323
+ }
2324
+ function buildControlsGroupLayout(config, state) {
2325
+ const cg = config.controlsGroup;
2326
+ if (!cg || cg.toggles.length === 0) return void 0;
2327
+ const expanded = !!state.controlsExpanded;
2328
+ const pillH = LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2;
2329
+ if (!expanded) {
2330
+ return {
2331
+ x: 0,
2332
+ y: 0,
2333
+ width: LEGEND_GEAR_PILL_W,
2334
+ height: LEGEND_HEIGHT,
2335
+ expanded: false,
2336
+ pill: { x: 0, y: 0, width: LEGEND_GEAR_PILL_W, height: LEGEND_HEIGHT },
2337
+ toggles: []
2338
+ };
2339
+ }
2340
+ const capsuleW = controlsGroupCapsuleWidth(cg.toggles);
2341
+ const toggleLayouts = [];
2342
+ let tx = LEGEND_CAPSULE_PAD + LEGEND_GEAR_PILL_W + 4;
2343
+ for (const toggle of cg.toggles) {
2344
+ const dotCx = tx + LEGEND_TOGGLE_DOT_R;
2345
+ const dotCy = LEGEND_HEIGHT / 2;
2346
+ const textX = tx + LEGEND_TOGGLE_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP;
2347
+ const textY = LEGEND_HEIGHT / 2;
2348
+ toggleLayouts.push({
2349
+ id: toggle.id,
2350
+ label: toggle.label,
2351
+ active: toggle.active,
2352
+ dotCx,
2353
+ dotCy,
2354
+ textX,
2355
+ textY
2356
+ });
2357
+ tx += LEGEND_TOGGLE_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + measureLegendText(toggle.label, LEGEND_ENTRY_FONT_SIZE) + LEGEND_ENTRY_TRAIL;
2358
+ }
2359
+ return {
2360
+ x: 0,
2361
+ y: 0,
2362
+ width: capsuleW,
2363
+ height: LEGEND_HEIGHT,
2364
+ expanded: true,
2365
+ pill: {
2366
+ x: LEGEND_CAPSULE_PAD,
2367
+ y: LEGEND_CAPSULE_PAD,
2368
+ width: LEGEND_GEAR_PILL_W - LEGEND_CAPSULE_PAD * 2,
2369
+ height: pillH
2370
+ },
2371
+ toggles: toggleLayouts
2372
+ };
2373
+ }
2285
2374
  function computeLegendLayout(config, state, containerWidth) {
2286
2375
  const { groups, controls: configControls, mode } = config;
2287
2376
  const isExport = mode === "inline";
@@ -2296,8 +2385,9 @@ function computeLegendLayout(config, state, containerWidth) {
2296
2385
  activeCapsule: void 0
2297
2386
  };
2298
2387
  }
2388
+ const controlsGroupLayout = isExport ? void 0 : buildControlsGroupLayout(config, state);
2299
2389
  const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0);
2300
- if (visibleGroups.length === 0 && (!configControls || configControls.length === 0)) {
2390
+ if (visibleGroups.length === 0 && (!configControls || configControls.length === 0) && !controlsGroupLayout) {
2301
2391
  return {
2302
2392
  height: 0,
2303
2393
  width: 0,
@@ -2361,7 +2451,8 @@ function computeLegendLayout(config, state, containerWidth) {
2361
2451
  if (totalControlsW > 0) totalControlsW -= CONTROL_GAP;
2362
2452
  }
2363
2453
  const controlsSpace = totalControlsW > 0 ? totalControlsW + LEGEND_GROUP_GAP * 2 : 0;
2364
- const groupAvailW = containerWidth - controlsSpace;
2454
+ const gearSpace = controlsGroupLayout ? controlsGroupLayout.width + LEGEND_GROUP_GAP : 0;
2455
+ const groupAvailW = containerWidth - controlsSpace - gearSpace;
2365
2456
  const pills = [];
2366
2457
  let activeCapsule;
2367
2458
  for (const g of visibleGroups) {
@@ -2370,7 +2461,7 @@ function computeLegendLayout(config, state, containerWidth) {
2370
2461
  if (isActive) {
2371
2462
  activeCapsule = buildCapsuleLayout(
2372
2463
  g,
2373
- containerWidth,
2464
+ groupAvailW,
2374
2465
  config.capsulePillAddonWidth ?? 0
2375
2466
  );
2376
2467
  } else {
@@ -2393,7 +2484,8 @@ function computeLegendLayout(config, state, containerWidth) {
2393
2484
  groupAvailW,
2394
2485
  containerWidth,
2395
2486
  totalControlsW,
2396
- alignLeft
2487
+ alignLeft,
2488
+ controlsGroupLayout
2397
2489
  );
2398
2490
  const height = rows.length * LEGEND_HEIGHT;
2399
2491
  const width = containerWidth;
@@ -2403,7 +2495,8 @@ function computeLegendLayout(config, state, containerWidth) {
2403
2495
  rows,
2404
2496
  activeCapsule,
2405
2497
  controls: controlLayouts,
2406
- pills
2498
+ pills,
2499
+ controlsGroup: controlsGroupLayout
2407
2500
  };
2408
2501
  }
2409
2502
  function buildCapsuleLayout(group, containerWidth, addonWidth = 0) {
@@ -2467,19 +2560,27 @@ function buildCapsuleLayout(group, containerWidth, addonWidth = 0) {
2467
2560
  addonX: addonWidth > 0 ? LEGEND_CAPSULE_PAD + pw + 4 : void 0
2468
2561
  };
2469
2562
  }
2470
- function layoutRows(activeCapsule, pills, controls, groupAvailW, containerWidth, totalControlsW, alignLeft = false) {
2563
+ function layoutRows(activeCapsule, pills, controls, groupAvailW, containerWidth, totalControlsW, alignLeft = false, controlsGroup) {
2471
2564
  const rows = [];
2472
2565
  const groupItems = [];
2473
2566
  if (activeCapsule) groupItems.push(activeCapsule);
2474
2567
  groupItems.push(...pills);
2568
+ const gearW = controlsGroup ? controlsGroup.width + LEGEND_GROUP_GAP : 0;
2475
2569
  let currentRowItems = [];
2476
2570
  let currentRowW = 0;
2477
2571
  let rowY = 0;
2478
2572
  for (const item of groupItems) {
2479
2573
  const itemW = item.width + LEGEND_GROUP_GAP;
2480
2574
  if (currentRowW + item.width > groupAvailW && currentRowItems.length > 0) {
2481
- if (!alignLeft)
2482
- centerRowItems(currentRowItems, containerWidth, totalControlsW);
2575
+ if (!alignLeft) {
2576
+ const rowGearW = rows.length === 0 ? gearW : 0;
2577
+ centerRowItems(
2578
+ currentRowItems,
2579
+ containerWidth,
2580
+ totalControlsW,
2581
+ rowGearW
2582
+ );
2583
+ }
2483
2584
  rows.push({ y: rowY, items: currentRowItems });
2484
2585
  rowY += LEGEND_HEIGHT;
2485
2586
  currentRowItems = [];
@@ -2507,19 +2608,32 @@ function layoutRows(activeCapsule, pills, controls, groupAvailW, containerWidth,
2507
2608
  }
2508
2609
  }
2509
2610
  if (currentRowItems.length > 0) {
2510
- centerRowItems(currentRowItems, containerWidth, totalControlsW);
2611
+ centerRowItems(currentRowItems, containerWidth, totalControlsW, gearW);
2511
2612
  rows.push({ y: rowY, items: currentRowItems });
2512
2613
  }
2614
+ if (controlsGroup) {
2615
+ const row0Items = rows[0]?.items ?? [];
2616
+ const groupItemsInRow0 = row0Items.filter(
2617
+ (it) => "groupName" in it
2618
+ );
2619
+ if (groupItemsInRow0.length > 0) {
2620
+ const last = groupItemsInRow0[groupItemsInRow0.length - 1];
2621
+ controlsGroup.x = last.x + last.width + LEGEND_GROUP_GAP;
2622
+ } else {
2623
+ controlsGroup.x = 0;
2624
+ }
2625
+ controlsGroup.y = 0;
2626
+ }
2513
2627
  if (rows.length === 0) {
2514
2628
  rows.push({ y: 0, items: [] });
2515
2629
  }
2516
2630
  return rows;
2517
2631
  }
2518
- function centerRowItems(items, containerWidth, totalControlsW) {
2632
+ function centerRowItems(items, containerWidth, totalControlsW, controlsGroupW = 0) {
2519
2633
  const groupItems = items.filter((it) => "groupName" in it);
2520
2634
  if (groupItems.length === 0) return;
2521
2635
  const totalGroupW = groupItems.reduce((s, it) => s + it.width, 0) + (groupItems.length - 1) * LEGEND_GROUP_GAP;
2522
- const availW = containerWidth - (totalControlsW > 0 ? totalControlsW + LEGEND_GROUP_GAP * 2 : 0);
2636
+ const availW = containerWidth - (totalControlsW > 0 ? totalControlsW + LEGEND_GROUP_GAP * 2 : 0) - controlsGroupW;
2523
2637
  const offset = Math.max(0, (availW - totalGroupW) / 2);
2524
2638
  let x = offset;
2525
2639
  for (const item of groupItems) {
@@ -2577,6 +2691,17 @@ function renderLegendD3(container, config, state, palette, isDark, callbacks, co
2577
2691
  for (const pill of currentLayout.pills) {
2578
2692
  renderPill(legendG, pill, palette, groupBg, callbacks);
2579
2693
  }
2694
+ if (currentLayout.controlsGroup) {
2695
+ renderControlsGroup(
2696
+ legendG,
2697
+ currentLayout.controlsGroup,
2698
+ palette,
2699
+ groupBg,
2700
+ pillBorder,
2701
+ callbacks,
2702
+ config
2703
+ );
2704
+ }
2580
2705
  for (const ctrl of currentLayout.controls) {
2581
2706
  renderControl(
2582
2707
  legendG,
@@ -2691,6 +2816,57 @@ function renderControl(parent, ctrl, palette, _groupBg, pillBorder, _isDark, con
2691
2816
  g.on("click", () => onClick());
2692
2817
  }
2693
2818
  }
2819
+ function renderControlsGroup(parent, layout, palette, groupBg, pillBorder, callbacks, config) {
2820
+ 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");
2821
+ if (!layout.expanded) {
2822
+ g.append("rect").attr("width", layout.width).attr("height", layout.height).attr("rx", layout.height / 2).attr("fill", groupBg);
2823
+ const iconSize = 14;
2824
+ const iconX = (layout.width - iconSize) / 2;
2825
+ const iconY = (layout.height - iconSize) / 2;
2826
+ 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");
2827
+ if (callbacks?.onControlsExpand) {
2828
+ const cb = callbacks.onControlsExpand;
2829
+ g.on("click", () => cb());
2830
+ }
2831
+ } else {
2832
+ const pill = layout.pill;
2833
+ g.append("rect").attr("width", layout.width).attr("height", layout.height).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
2834
+ const pillG = g.append("g").attr("class", "controls-gear-pill").style("cursor", "pointer");
2835
+ 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);
2836
+ 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);
2837
+ const iconSize = 14;
2838
+ const iconX = pill.x + (pill.width - iconSize) / 2;
2839
+ const iconY = pill.y + (pill.height - iconSize) / 2;
2840
+ 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");
2841
+ if (callbacks?.onControlsExpand) {
2842
+ const cb = callbacks.onControlsExpand;
2843
+ pillG.on("click", (event) => {
2844
+ event.stopPropagation();
2845
+ cb();
2846
+ });
2847
+ }
2848
+ const toggles = config?.controlsGroup?.toggles ?? [];
2849
+ for (const tl of layout.toggles) {
2850
+ const toggle = toggles.find((t) => t.id === tl.id);
2851
+ const entryG = g.append("g").attr("data-controls-toggle", tl.id).style("cursor", "pointer");
2852
+ if (tl.active) {
2853
+ entryG.append("circle").attr("cx", tl.dotCx).attr("cy", tl.dotCy).attr("r", LEGEND_TOGGLE_DOT_R).attr("fill", palette.primary ?? palette.text);
2854
+ } else {
2855
+ 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);
2856
+ }
2857
+ 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);
2858
+ if (callbacks?.onControlsToggle && toggle) {
2859
+ const cb = callbacks.onControlsToggle;
2860
+ const id = tl.id;
2861
+ const newActive = !tl.active;
2862
+ entryG.on("click", (event) => {
2863
+ event.stopPropagation();
2864
+ cb(id, newActive);
2865
+ });
2866
+ }
2867
+ }
2868
+ }
2869
+ }
2694
2870
  var init_legend_d3 = __esm({
2695
2871
  "src/utils/legend-d3.ts"() {
2696
2872
  "use strict";
@@ -2987,61 +3163,6 @@ var init_participant_inference = __esm({
2987
3163
  }
2988
3164
  });
2989
3165
 
2990
- // src/utils/arrows.ts
2991
- function parseArrow(line10) {
2992
- if (BIDI_SYNC_RE.test(line10) || BIDI_ASYNC_RE.test(line10)) {
2993
- return {
2994
- error: "Bidirectional arrows are no longer supported. Use two separate lines: 'A -msg-> B' and 'B -msg-> A'"
2995
- };
2996
- }
2997
- if (RETURN_SYNC_LABELED_RE.test(line10) || RETURN_ASYNC_LABELED_RE.test(line10)) {
2998
- const m = line10.match(RETURN_SYNC_LABELED_RE) ?? line10.match(RETURN_ASYNC_LABELED_RE);
2999
- const from = m[3];
3000
- const to = m[1];
3001
- const label = m[2].trim();
3002
- return {
3003
- error: `Left-pointing arrows are no longer supported. Write '${from} -${label}-> ${to}' instead`
3004
- };
3005
- }
3006
- const patterns = [
3007
- { re: SYNC_LABELED_RE, async: false },
3008
- { re: ASYNC_LABELED_RE, async: true }
3009
- ];
3010
- for (const { re, async: isAsync } of patterns) {
3011
- const m = line10.match(re);
3012
- if (!m) continue;
3013
- const label = m[2].trim();
3014
- if (!label) return null;
3015
- for (const arrow of ARROW_CHARS) {
3016
- if (label.includes(arrow)) {
3017
- return {
3018
- error: "Arrow characters (->, ~>) are not allowed inside labels"
3019
- };
3020
- }
3021
- }
3022
- return {
3023
- from: m[1],
3024
- to: m[3],
3025
- label,
3026
- async: isAsync
3027
- };
3028
- }
3029
- return null;
3030
- }
3031
- var SYNC_LABELED_RE, ASYNC_LABELED_RE, RETURN_SYNC_LABELED_RE, RETURN_ASYNC_LABELED_RE, BIDI_SYNC_RE, BIDI_ASYNC_RE, ARROW_CHARS;
3032
- var init_arrows = __esm({
3033
- "src/utils/arrows.ts"() {
3034
- "use strict";
3035
- SYNC_LABELED_RE = /^(.+?)\s*-(.+)->\s*(.+)$/;
3036
- ASYNC_LABELED_RE = /^(.+?)\s*~(.+)~>\s*(.+)$/;
3037
- RETURN_SYNC_LABELED_RE = /^(.+?)\s*<-(.+)-\s*(.+)$/;
3038
- RETURN_ASYNC_LABELED_RE = /^(.+?)\s*<~(.+)~\s*(.+)$/;
3039
- BIDI_SYNC_RE = /^(.+?)\s*<-(.+)->\s*(.+)$/;
3040
- BIDI_ASYNC_RE = /^(.+?)\s*<~(.+)~>\s*(.+)$/;
3041
- ARROW_CHARS = ["->", "~>"];
3042
- }
3043
- });
3044
-
3045
3166
  // src/sequence/parser.ts
3046
3167
  var parser_exports = {};
3047
3168
  __export(parser_exports, {
@@ -3244,7 +3365,13 @@ function parseSequenceDgmo(content) {
3244
3365
  const groupName = groupMatch[1].trim();
3245
3366
  const groupColor = groupMatch[2]?.trim();
3246
3367
  let groupMeta;
3247
- const afterBracket = groupMatch[3]?.trim() || "";
3368
+ let afterBracket = groupMatch[3]?.trim() || "";
3369
+ let isCollapsed = false;
3370
+ const collapseMatch = afterBracket.match(/^collapse\b/i);
3371
+ if (collapseMatch) {
3372
+ isCollapsed = true;
3373
+ afterBracket = afterBracket.slice(collapseMatch[0].length).trim();
3374
+ }
3248
3375
  if (afterBracket.startsWith("|")) {
3249
3376
  const segments = afterBracket.split("|");
3250
3377
  const meta = parsePipeMetadata(
@@ -3265,7 +3392,8 @@ function parseSequenceDgmo(content) {
3265
3392
  name: groupName,
3266
3393
  participantIds: [],
3267
3394
  lineNumber,
3268
- ...groupMeta ? { metadata: groupMeta } : {}
3395
+ ...groupMeta ? { metadata: groupMeta } : {},
3396
+ ...isCollapsed ? { collapsed: true } : {}
3269
3397
  };
3270
3398
  result.groups.push(activeGroup);
3271
3399
  continue;
@@ -3587,8 +3715,11 @@ function parseSequenceDgmo(content) {
3587
3715
  }
3588
3716
  if (labeledArrow) {
3589
3717
  contentStarted = true;
3590
- const { from, to, label, async: isAsync } = labeledArrow;
3718
+ const { from, to, label: rawLabel, async: isAsync } = labeledArrow;
3591
3719
  lastMsgFrom = from;
3720
+ const labelResult = parseInArrowLabel(rawLabel, lineNumber);
3721
+ labelResult.diagnostics.forEach((d) => result.diagnostics.push(d));
3722
+ const label = labelResult.label ?? rawLabel;
3592
3723
  const msg = {
3593
3724
  from,
3594
3725
  to,
@@ -3988,42 +4119,52 @@ function parseNodeRef(text, palette) {
3988
4119
  }
3989
4120
  function splitArrows(line10) {
3990
4121
  const segments = [];
3991
- let lastIndex = 0;
3992
4122
  const arrowPositions = [];
3993
4123
  let searchFrom = 0;
4124
+ let scanFloor = 0;
3994
4125
  while (searchFrom < line10.length) {
3995
4126
  const idx = line10.indexOf("->", searchFrom);
3996
4127
  if (idx === -1) break;
3997
- let arrowStart = idx;
4128
+ let runStart = idx;
4129
+ while (runStart > scanFloor && line10[runStart - 1] === "-") runStart--;
4130
+ const arrowEnd = idx + 2;
4131
+ let arrowStart;
3998
4132
  let label;
3999
4133
  let color;
4000
- if (idx > 0 && line10[idx - 1] !== " " && line10[idx - 1] !== " ") {
4001
- let scanBack = idx - 1;
4002
- while (scanBack > 0 && line10[scanBack] !== "-") {
4003
- scanBack--;
4004
- }
4005
- if (line10[scanBack] === "-" && (scanBack === 0 || /\s/.test(line10[scanBack - 1]))) {
4006
- let arrowContent = line10.substring(scanBack + 1, idx);
4007
- if (arrowContent.endsWith("-"))
4008
- arrowContent = arrowContent.slice(0, -1);
4009
- const colorMatch = arrowContent.match(/\(([^)]+)\)\s*$/);
4010
- if (colorMatch) {
4011
- color = colorMatch[1].trim();
4012
- const labelPart = arrowContent.substring(0, colorMatch.index).trim();
4013
- if (labelPart) label = labelPart;
4014
- } else {
4015
- const labelPart = arrowContent.trim();
4016
- if (labelPart) label = labelPart;
4017
- }
4018
- arrowStart = scanBack;
4134
+ let openingStart = -1;
4135
+ for (let i = scanFloor; i < runStart; i++) {
4136
+ if (line10[i] !== "-") continue;
4137
+ const prevIsWsOrFloor = i === 0 || i === scanFloor || /\s/.test(line10[i - 1]);
4138
+ if (prevIsWsOrFloor) {
4139
+ openingStart = i;
4140
+ break;
4019
4141
  }
4020
4142
  }
4021
- arrowPositions.push({ start: arrowStart, end: idx + 2, label, color });
4022
- searchFrom = idx + 2;
4143
+ if (openingStart !== -1) {
4144
+ let openingEnd = openingStart;
4145
+ while (openingEnd < runStart && line10[openingEnd] === "-") openingEnd++;
4146
+ const arrowContent = line10.substring(openingEnd, runStart);
4147
+ const colorMatch = arrowContent.match(/\(([^)]+)\)\s*$/);
4148
+ if (colorMatch) {
4149
+ color = colorMatch[1].trim();
4150
+ const labelPart = arrowContent.substring(0, colorMatch.index).trim();
4151
+ if (labelPart) label = labelPart;
4152
+ } else {
4153
+ const labelPart = arrowContent.trim();
4154
+ if (labelPart) label = labelPart;
4155
+ }
4156
+ arrowStart = openingStart;
4157
+ } else {
4158
+ arrowStart = runStart;
4159
+ }
4160
+ arrowPositions.push({ start: arrowStart, end: arrowEnd, label, color });
4161
+ searchFrom = arrowEnd;
4162
+ scanFloor = arrowEnd;
4023
4163
  }
4024
4164
  if (arrowPositions.length === 0) {
4025
4165
  return [line10];
4026
4166
  }
4167
+ let lastIndex = 0;
4027
4168
  for (let i = 0; i < arrowPositions.length; i++) {
4028
4169
  const arrow = arrowPositions[i];
4029
4170
  const beforeText = line10.substring(lastIndex, arrow.start).trim();
@@ -4046,20 +4187,26 @@ function splitArrows(line10) {
4046
4187
  }
4047
4188
  function parseArrowToken(token, palette, lineNumber, diagnostics) {
4048
4189
  if (token === "->") return {};
4049
- const colorOnly = token.match(/^-\(([^)]+)\)->$/);
4050
- if (colorOnly) {
4051
- return {
4052
- color: resolveColorWithDiagnostic(
4053
- colorOnly[1].trim(),
4054
- lineNumber,
4055
- diagnostics,
4056
- palette
4057
- )
4058
- };
4190
+ const bareParen = token.match(/^-(\([A-Za-z]+\))->$/);
4191
+ if (bareParen) {
4192
+ const colorName = matchColorParens(bareParen[1]);
4193
+ if (colorName) {
4194
+ return {
4195
+ color: resolveColorWithDiagnostic(
4196
+ colorName,
4197
+ lineNumber,
4198
+ diagnostics,
4199
+ palette
4200
+ )
4201
+ };
4202
+ }
4059
4203
  }
4060
4204
  const m = token.match(/^-(.+?)(?:\(([^)]+)\))?->$/);
4061
4205
  if (m) {
4062
- const label = m[1]?.trim() || void 0;
4206
+ const rawLabel = m[1] ?? "";
4207
+ const labelResult = parseInArrowLabel(rawLabel, lineNumber);
4208
+ diagnostics.push(...labelResult.diagnostics);
4209
+ const label = labelResult.label;
4063
4210
  let color = m[2] ? resolveColorWithDiagnostic(
4064
4211
  m[2].trim(),
4065
4212
  lineNumber,
@@ -4287,6 +4434,7 @@ var init_flowchart_parser = __esm({
4287
4434
  "use strict";
4288
4435
  init_colors();
4289
4436
  init_diagnostics();
4437
+ init_arrows();
4290
4438
  init_parsing();
4291
4439
  NODE_ID_RE = /^([a-zA-Z_][\w-]*)[\s([</{]/;
4292
4440
  }
@@ -4302,35 +4450,45 @@ function splitArrows2(line10) {
4302
4450
  const segments = [];
4303
4451
  const arrowPositions = [];
4304
4452
  let searchFrom = 0;
4453
+ let scanFloor = 0;
4305
4454
  while (searchFrom < line10.length) {
4306
4455
  const idx = line10.indexOf("->", searchFrom);
4307
4456
  if (idx === -1) break;
4308
- let arrowStart = idx;
4457
+ let runStart = idx;
4458
+ while (runStart > scanFloor && line10[runStart - 1] === "-") runStart--;
4459
+ const arrowEnd = idx + 2;
4460
+ let arrowStart;
4309
4461
  let label;
4310
4462
  let color;
4311
- if (idx > 0 && line10[idx - 1] !== " " && line10[idx - 1] !== " ") {
4312
- let scanBack = idx - 1;
4313
- while (scanBack > 0 && line10[scanBack] !== "-") {
4314
- scanBack--;
4315
- }
4316
- if (line10[scanBack] === "-" && (scanBack === 0 || /\s/.test(line10[scanBack - 1]))) {
4317
- let arrowContent = line10.substring(scanBack + 1, idx);
4318
- if (arrowContent.endsWith("-"))
4319
- arrowContent = arrowContent.slice(0, -1);
4320
- const colorMatch = arrowContent.match(/\(([^)]+)\)\s*$/);
4321
- if (colorMatch) {
4322
- color = colorMatch[1].trim();
4323
- const labelPart = arrowContent.substring(0, colorMatch.index).trim();
4324
- if (labelPart) label = labelPart;
4325
- } else {
4326
- const labelPart = arrowContent.trim();
4327
- if (labelPart) label = labelPart;
4328
- }
4329
- arrowStart = scanBack;
4463
+ let openingStart = -1;
4464
+ for (let i = scanFloor; i < runStart; i++) {
4465
+ if (line10[i] !== "-") continue;
4466
+ const prevIsWsOrFloor = i === 0 || i === scanFloor || /\s/.test(line10[i - 1]);
4467
+ if (prevIsWsOrFloor) {
4468
+ openingStart = i;
4469
+ break;
4330
4470
  }
4331
4471
  }
4332
- arrowPositions.push({ start: arrowStart, end: idx + 2, label, color });
4333
- searchFrom = idx + 2;
4472
+ if (openingStart !== -1) {
4473
+ let openingEnd = openingStart;
4474
+ while (openingEnd < runStart && line10[openingEnd] === "-") openingEnd++;
4475
+ const arrowContent = line10.substring(openingEnd, runStart);
4476
+ const colorMatch = arrowContent.match(/\(([^)]+)\)\s*$/);
4477
+ if (colorMatch) {
4478
+ color = colorMatch[1].trim();
4479
+ const labelPart = arrowContent.substring(0, colorMatch.index).trim();
4480
+ if (labelPart) label = labelPart;
4481
+ } else {
4482
+ const labelPart = arrowContent.trim();
4483
+ if (labelPart) label = labelPart;
4484
+ }
4485
+ arrowStart = openingStart;
4486
+ } else {
4487
+ arrowStart = runStart;
4488
+ }
4489
+ arrowPositions.push({ start: arrowStart, end: arrowEnd, label, color });
4490
+ searchFrom = arrowEnd;
4491
+ scanFloor = arrowEnd;
4334
4492
  }
4335
4493
  if (arrowPositions.length === 0) return [line10];
4336
4494
  let lastIndex = 0;
@@ -4352,19 +4510,26 @@ function splitArrows2(line10) {
4352
4510
  }
4353
4511
  function parseArrowToken2(token, palette, lineNumber, diagnostics) {
4354
4512
  if (token === "->") return {};
4355
- const colorOnly = token.match(/^-\(([^)]+)\)->$/);
4356
- if (colorOnly)
4357
- return {
4358
- color: resolveColorWithDiagnostic(
4359
- colorOnly[1].trim(),
4360
- lineNumber,
4361
- diagnostics,
4362
- palette
4363
- )
4364
- };
4513
+ const bareParen = token.match(/^-(\([A-Za-z]+\))->$/);
4514
+ if (bareParen) {
4515
+ const colorName = matchColorParens(bareParen[1]);
4516
+ if (colorName) {
4517
+ return {
4518
+ color: resolveColorWithDiagnostic(
4519
+ colorName,
4520
+ lineNumber,
4521
+ diagnostics,
4522
+ palette
4523
+ )
4524
+ };
4525
+ }
4526
+ }
4365
4527
  const m = token.match(/^-(.+?)(?:\(([^)]+)\))?->$/);
4366
4528
  if (m) {
4367
- const label = m[1]?.trim() || void 0;
4529
+ const rawLabel = m[1] ?? "";
4530
+ const labelResult = parseInArrowLabel(rawLabel, lineNumber);
4531
+ diagnostics.push(...labelResult.diagnostics);
4532
+ const label = labelResult.label;
4368
4533
  const color = m[2] ? resolveColorWithDiagnostic(
4369
4534
  m[2].trim(),
4370
4535
  lineNumber,
@@ -4605,6 +4770,7 @@ var init_state_parser = __esm({
4605
4770
  "use strict";
4606
4771
  init_colors();
4607
4772
  init_diagnostics();
4773
+ init_arrows();
4608
4774
  init_parsing();
4609
4775
  PSEUDOSTATE_ID = "pseudostate:[*]";
4610
4776
  PSEUDOSTATE_LABEL = "[*]";
@@ -4761,6 +4927,11 @@ function parseClassDiagram(content, palette) {
4761
4927
  const targetName = indentRel[2];
4762
4928
  const label = indentRel[3]?.trim();
4763
4929
  getOrCreateClass(targetName, lineNumber);
4930
+ if (label) {
4931
+ result.diagnostics.push(
4932
+ ...validateLabelCharacters(label, lineNumber)
4933
+ );
4934
+ }
4764
4935
  result.relationships.push({
4765
4936
  source: currentClass.id,
4766
4937
  target: classId(targetName),
@@ -4935,6 +5106,7 @@ var init_parser2 = __esm({
4935
5106
  "use strict";
4936
5107
  init_colors();
4937
5108
  init_diagnostics();
5109
+ init_arrows();
4938
5110
  init_parsing();
4939
5111
  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*$/;
4940
5112
  INDENT_REL_ARROW_RE = /^(--\|>|\.\.\|>|\*--|o--|\.\.>|->)\s*([A-Z][A-Za-z0-9_]*)(?:\s+:?\s*(.+))?$/;
@@ -4974,12 +5146,18 @@ function parseRelationship(trimmed, lineNumber, pushError) {
4974
5146
  const fromCard = parseCardSide(sym[2]);
4975
5147
  const toCard = parseCardSide(sym[3]);
4976
5148
  if (fromCard && toCard) {
5149
+ const label = sym[5]?.trim();
5150
+ if (label) {
5151
+ validateLabelCharacters(label, lineNumber).forEach(
5152
+ (d) => pushError(d.line, d.message)
5153
+ );
5154
+ }
4977
5155
  return {
4978
5156
  source: sym[1],
4979
5157
  target: sym[4],
4980
5158
  from: fromCard,
4981
5159
  to: toCard,
4982
- label: sym[5]?.trim()
5160
+ label
4983
5161
  };
4984
5162
  }
4985
5163
  }
@@ -5142,11 +5320,17 @@ function parseERDiagram(content, palette) {
5142
5320
  if (fromCard && toCard) {
5143
5321
  const targetName = indentRel[4];
5144
5322
  getOrCreateTable(targetName, lineNumber);
5323
+ const rawLabel = indentRel[2]?.trim();
5324
+ if (rawLabel) {
5325
+ result.diagnostics.push(
5326
+ ...validateLabelCharacters(rawLabel, lineNumber)
5327
+ );
5328
+ }
5145
5329
  result.relationships.push({
5146
5330
  source: currentTable.id,
5147
5331
  target: tableId(targetName),
5148
5332
  cardinality: { from: fromCard, to: toCard },
5149
- ...indentRel[2]?.trim() && { label: indentRel[2].trim() },
5333
+ ...rawLabel && { label: rawLabel },
5150
5334
  lineNumber
5151
5335
  });
5152
5336
  }
@@ -5310,6 +5494,7 @@ var init_parser3 = __esm({
5310
5494
  "use strict";
5311
5495
  init_colors();
5312
5496
  init_diagnostics();
5497
+ init_arrows();
5313
5498
  init_parsing();
5314
5499
  init_tag_groups();
5315
5500
  TABLE_DECL_RE = /^([a-zA-Z_]\w*)(?:\s*\(([^)]+)\))?(?:\s*\|(.+))?$/;
@@ -7830,7 +8015,7 @@ function buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor,
7830
8015
  series
7831
8016
  };
7832
8017
  }
7833
- async function renderExtendedChartForExport(content, theme, palette, options) {
8018
+ async function renderExtendedChartForExport(content, theme, palette) {
7834
8019
  const isDark = theme === "dark";
7835
8020
  const { getPalette: getPalette2 } = await Promise.resolve().then(() => (init_palettes(), palettes_exports));
7836
8021
  const effectivePalette = palette ?? (isDark ? getPalette2("nord").dark : getPalette2("nord").light);
@@ -7901,22 +8086,20 @@ async function renderExtendedChartForExport(content, theme, palette, options) {
7901
8086
  `$1<g transform="translate(0,${legendY})">${legendSvgStr}</g>`
7902
8087
  );
7903
8088
  }
7904
- if (options?.branding !== false) {
7905
- const brandColor = theme === "transparent" ? "#888" : effectivePalette.textMuted;
7906
- result = injectBranding(result, brandColor);
7907
- }
7908
8089
  return result;
7909
8090
  } finally {
7910
8091
  chart.dispose();
7911
8092
  }
7912
8093
  }
7913
- var echarts, EMPHASIS_SELF, EMPHASIS_SERIES, BLUR_DIM, EMPHASIS_LINE, CHART_BASE, CHART_BORDER_WIDTH, VALID_EXTENDED_TYPES, KNOWN_EXTENDED_OPTIONS, ECHART_EXPORT_WIDTH, ECHART_EXPORT_HEIGHT, STANDARD_CHART_TYPES;
8094
+ var echarts, import_charts, import_components, import_renderers, EMPHASIS_SELF, EMPHASIS_SERIES, BLUR_DIM, EMPHASIS_LINE, CHART_BASE, CHART_BORDER_WIDTH, VALID_EXTENDED_TYPES, KNOWN_EXTENDED_OPTIONS, ECHART_EXPORT_WIDTH, ECHART_EXPORT_HEIGHT, STANDARD_CHART_TYPES;
7914
8095
  var init_echarts = __esm({
7915
8096
  "src/echarts.ts"() {
7916
8097
  "use strict";
7917
- echarts = __toESM(require("echarts"), 1);
8098
+ echarts = __toESM(require("echarts/core"), 1);
8099
+ import_charts = require("echarts/charts");
8100
+ import_components = require("echarts/components");
8101
+ import_renderers = require("echarts/renderers");
7918
8102
  init_fonts();
7919
- init_branding();
7920
8103
  init_legend_svg();
7921
8104
  init_label_layout();
7922
8105
  init_palettes();
@@ -7926,6 +8109,25 @@ var init_echarts = __esm({
7926
8109
  init_colors();
7927
8110
  init_parsing();
7928
8111
  init_chart();
8112
+ echarts.use([
8113
+ import_charts.BarChart,
8114
+ import_charts.LineChart,
8115
+ import_charts.PieChart,
8116
+ import_charts.ScatterChart,
8117
+ import_charts.RadarChart,
8118
+ import_charts.SankeyChart,
8119
+ import_charts.GraphChart,
8120
+ import_charts.HeatmapChart,
8121
+ import_charts.FunnelChart,
8122
+ import_components.GridComponent,
8123
+ import_components.TitleComponent,
8124
+ import_components.TooltipComponent,
8125
+ import_components.LegendComponent,
8126
+ import_components.RadarComponent,
8127
+ import_components.VisualMapComponent,
8128
+ import_components.GraphicComponent,
8129
+ import_renderers.SVGRenderer
8130
+ ]);
7929
8131
  EMPHASIS_SELF = {
7930
8132
  focus: "self",
7931
8133
  blurScope: "global",
@@ -8888,13 +9090,10 @@ function parseC4(content, palette) {
8888
9090
  labeledHandled = true;
8889
9091
  break;
8890
9092
  }
8891
- let label = rawLabel;
9093
+ const labelResult = parseInArrowLabel(rawLabel, lineNumber);
9094
+ labelResult.diagnostics.forEach((d) => result.diagnostics.push(d));
9095
+ const label = labelResult.label;
8892
9096
  let technology;
8893
- const techMatch = rawLabel.match(/\[([^\]]+)\]\s*$/);
8894
- if (techMatch) {
8895
- label = rawLabel.substring(0, techMatch.index).trim() || void 0;
8896
- technology = techMatch[1].trim();
8897
- }
8898
9097
  let target = targetBody;
8899
9098
  const pipeIdx = targetBody.indexOf("|");
8900
9099
  if (pipeIdx !== -1) {
@@ -9230,6 +9429,7 @@ var init_parser6 = __esm({
9230
9429
  "src/c4/parser.ts"() {
9231
9430
  "use strict";
9232
9431
  init_diagnostics();
9432
+ init_arrows();
9233
9433
  init_tag_groups();
9234
9434
  init_participant_inference();
9235
9435
  init_parsing();
@@ -10030,7 +10230,10 @@ function parseInfra(content) {
10030
10230
  }
10031
10231
  const asyncConnMatch = trimmed.match(ASYNC_CONNECTION_RE);
10032
10232
  if (asyncConnMatch) {
10033
- const label = asyncConnMatch[1]?.trim() || "";
10233
+ const rawLabel = asyncConnMatch[1] ?? "";
10234
+ const labelResult = parseInArrowLabel(rawLabel, lineNumber);
10235
+ result.diagnostics.push(...labelResult.diagnostics);
10236
+ const label = labelResult.label ?? "";
10034
10237
  const targetRaw = asyncConnMatch[2].trim();
10035
10238
  const pipeMeta = extractPipeMetadata(targetRaw);
10036
10239
  const targetName = pipeMeta.clean || targetRaw;
@@ -10090,7 +10293,10 @@ function parseInfra(content) {
10090
10293
  }
10091
10294
  const connMatch = trimmed.match(CONNECTION_RE);
10092
10295
  if (connMatch) {
10093
- const label = connMatch[1]?.trim() || "";
10296
+ const rawLabel = connMatch[1] ?? "";
10297
+ const labelResult = parseInArrowLabel(rawLabel, lineNumber);
10298
+ result.diagnostics.push(...labelResult.diagnostics);
10299
+ const label = labelResult.label ?? "";
10094
10300
  const targetRaw = connMatch[2].trim();
10095
10301
  const pipeMeta = extractPipeMetadata(targetRaw);
10096
10302
  const targetName = pipeMeta.clean || targetRaw;
@@ -10289,6 +10495,7 @@ var init_parser8 = __esm({
10289
10495
  "use strict";
10290
10496
  init_diagnostics();
10291
10497
  init_colors();
10498
+ init_arrows();
10292
10499
  init_parsing();
10293
10500
  init_tag_groups();
10294
10501
  init_types();
@@ -11820,7 +12027,9 @@ function parseEdgeLine(trimmed, lineNum, aliasMap, diagnostics) {
11820
12027
  const biLabeledMatch = trimmed.match(/^(.+?)\s*<-(.+)->\s*(.+)$/);
11821
12028
  if (biLabeledMatch) {
11822
12029
  const source2 = resolveEndpoint(biLabeledMatch[1].trim());
11823
- const label = biLabeledMatch[2].trim();
12030
+ const labelResult = parseInArrowLabel(biLabeledMatch[2], lineNum);
12031
+ diagnostics.push(...labelResult.diagnostics);
12032
+ const label = labelResult.label;
11824
12033
  let rest2 = biLabeledMatch[3].trim();
11825
12034
  let metadata2 = {};
11826
12035
  const pipeIdx2 = rest2.indexOf("|");
@@ -11841,7 +12050,7 @@ function parseEdgeLine(trimmed, lineNum, aliasMap, diagnostics) {
11841
12050
  return {
11842
12051
  source: source2,
11843
12052
  target: resolveEndpoint(rest2),
11844
- label: label || void 0,
12053
+ label,
11845
12054
  bidirectional: true,
11846
12055
  lineNumber: lineNum,
11847
12056
  metadata: metadata2
@@ -11878,7 +12087,9 @@ function parseEdgeLine(trimmed, lineNum, aliasMap, diagnostics) {
11878
12087
  const labeledMatch = trimmed.match(/^(.+?)\s+-(.+)->\s*(.+)$/);
11879
12088
  if (labeledMatch) {
11880
12089
  const source2 = resolveEndpoint(labeledMatch[1].trim());
11881
- const label = labeledMatch[2].trim();
12090
+ const labelResult = parseInArrowLabel(labeledMatch[2], lineNum);
12091
+ diagnostics.push(...labelResult.diagnostics);
12092
+ const label = labelResult.label;
11882
12093
  let rest2 = labeledMatch[3].trim();
11883
12094
  if (label) {
11884
12095
  let metadata2 = {};
@@ -11941,6 +12152,7 @@ var init_parser10 = __esm({
11941
12152
  "src/boxes-and-lines/parser.ts"() {
11942
12153
  "use strict";
11943
12154
  init_diagnostics();
12155
+ init_arrows();
11944
12156
  init_tag_groups();
11945
12157
  init_parsing();
11946
12158
  MAX_GROUP_DEPTH = 1;
@@ -12016,19 +12228,78 @@ function getAllChartTypes() {
12016
12228
  function parseDgmo(content) {
12017
12229
  const chartType = parseDgmoChartType(content);
12018
12230
  if (!chartType) {
12231
+ const colonDiag = detectColonChartType(content);
12232
+ if (colonDiag) {
12233
+ const fallback = parseVisualization(content).diagnostics;
12234
+ return { diagnostics: [colonDiag, ...fallback] };
12235
+ }
12019
12236
  return { diagnostics: parseVisualization(content).diagnostics };
12020
12237
  }
12021
12238
  const directParser = PARSE_DISPATCH.get(chartType);
12022
- if (directParser) return { diagnostics: directParser(content).diagnostics };
12239
+ if (directParser) {
12240
+ const result2 = directParser(content);
12241
+ return {
12242
+ diagnostics: [...result2.diagnostics, ...detectEmptyContent(content)]
12243
+ };
12244
+ }
12023
12245
  if (STANDARD_CHART_TYPES2.has(chartType)) {
12024
- return { diagnostics: parseChart(content).diagnostics };
12246
+ const result2 = parseChart(content);
12247
+ return {
12248
+ diagnostics: [...result2.diagnostics, ...detectEmptyContent(content)]
12249
+ };
12025
12250
  }
12026
12251
  if (ECHART_TYPES.has(chartType)) {
12027
- return { diagnostics: parseExtendedChart(content).diagnostics };
12252
+ const result2 = parseExtendedChart(content);
12253
+ return {
12254
+ diagnostics: [...result2.diagnostics, ...detectEmptyContent(content)]
12255
+ };
12256
+ }
12257
+ const result = parseVisualization(content);
12258
+ return {
12259
+ diagnostics: [...result.diagnostics, ...detectEmptyContent(content)]
12260
+ };
12261
+ }
12262
+ function detectColonChartType(content) {
12263
+ const lines = content.split("\n");
12264
+ for (let i = 0; i < lines.length; i++) {
12265
+ const trimmed = lines[i].trim();
12266
+ if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("//"))
12267
+ continue;
12268
+ const match = trimmed.match(/^(\w[\w-]*)\s*:\s*(.*)$/);
12269
+ if (!match) return null;
12270
+ const word = match[1].toLowerCase();
12271
+ const rest = match[2].trim();
12272
+ if (ALL_KNOWN_TYPES.has(word)) {
12273
+ const example = rest ? `${word} ${rest}` : word;
12274
+ return makeDgmoError(
12275
+ i + 1,
12276
+ `Remove the colon \u2014 use '${example}' instead of '${trimmed}'. DGMO chart types don't use colons.`
12277
+ );
12278
+ }
12279
+ const hint = suggest(word, [...ALL_KNOWN_TYPES]);
12280
+ if (hint) {
12281
+ return makeDgmoError(
12282
+ i + 1,
12283
+ `Unknown chart type: ${word}. ${hint} Also, DGMO chart types don't use colons.`
12284
+ );
12285
+ }
12286
+ return null;
12287
+ }
12288
+ return null;
12289
+ }
12290
+ function detectEmptyContent(content) {
12291
+ const lines = content.split("\n");
12292
+ const nonEmpty = lines.filter(
12293
+ (l) => l.trim() && !l.trim().startsWith("#") && !l.trim().startsWith("//")
12294
+ );
12295
+ if (nonEmpty.length <= 1) {
12296
+ return [
12297
+ makeDgmoError(1, "No content after chart type declaration.", "warning")
12298
+ ];
12028
12299
  }
12029
- return { diagnostics: parseVisualization(content).diagnostics };
12300
+ return [];
12030
12301
  }
12031
- 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;
12302
+ 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;
12032
12303
  var init_dgmo_router = __esm({
12033
12304
  "src/dgmo-router.ts"() {
12034
12305
  "use strict";
@@ -12048,6 +12319,7 @@ var init_dgmo_router = __esm({
12048
12319
  init_parser9();
12049
12320
  init_parser10();
12050
12321
  init_parsing();
12322
+ init_diagnostics();
12051
12323
  GANTT_DURATION_RE = /^\d+(?:\.\d+)?(?:min|bd|d|w|m|q|y|h)(?:\?)?\s+/;
12052
12324
  GANTT_DATE_RE = /^\d{4}-\d{2}-\d{2}(?:\s\d{2}:\d{2})?\s+/;
12053
12325
  C4_TYPE_RE = /\bis\s+an?\s+(person|system|container|component)\b/i;
@@ -12131,6 +12403,11 @@ var init_dgmo_router = __esm({
12131
12403
  ["gantt", (c) => parseGantt(c)],
12132
12404
  ["boxes-and-lines", (c) => parseBoxesAndLines(c)]
12133
12405
  ]);
12406
+ ALL_KNOWN_TYPES = /* @__PURE__ */ new Set([
12407
+ ...DATA_CHART_TYPES,
12408
+ ...VISUALIZATION_TYPES,
12409
+ ...DIAGRAM_TYPES
12410
+ ]);
12134
12411
  }
12135
12412
  });
12136
12413
 
@@ -14303,7 +14580,6 @@ async function renderSitemapForExport(content, theme, palette) {
14303
14580
  const { parseSitemap: parseSitemap2 } = await Promise.resolve().then(() => (init_parser7(), parser_exports7));
14304
14581
  const { layoutSitemap: layoutSitemap2 } = await Promise.resolve().then(() => (init_layout2(), layout_exports2));
14305
14582
  const { getPalette: getPalette2 } = await Promise.resolve().then(() => (init_palettes(), palettes_exports));
14306
- const { injectBranding: injectBranding2 } = await Promise.resolve().then(() => (init_branding(), branding_exports));
14307
14583
  const isDark = theme === "dark";
14308
14584
  const effectivePalette = palette ?? (isDark ? getPalette2("nord").dark : getPalette2("nord").light);
14309
14585
  const parsed = parseSitemap2(content, effectivePalette);
@@ -14345,8 +14621,7 @@ async function renderSitemapForExport(content, theme, palette) {
14345
14621
  svgEl.style.fontFamily = FONT_FAMILY;
14346
14622
  const svgHtml = svgEl.outerHTML;
14347
14623
  document.body.removeChild(container);
14348
- const brandColor = theme === "transparent" ? "#888" : effectivePalette.textMuted;
14349
- return injectBranding2(svgHtml, brandColor);
14624
+ return svgHtml;
14350
14625
  }
14351
14626
  var d3Selection2, d3Shape, 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
14627
  var init_renderer2 = __esm({
@@ -14552,7 +14827,6 @@ var init_mutations = __esm({
14552
14827
  // src/kanban/renderer.ts
14553
14828
  var renderer_exports3 = {};
14554
14829
  __export(renderer_exports3, {
14555
- bucketCardsBySwimlane: () => bucketCardsBySwimlane,
14556
14830
  renderKanban: () => renderKanban,
14557
14831
  renderKanbanForExport: () => renderKanbanForExport
14558
14832
  });
@@ -23449,6 +23723,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23449
23723
  options?.currentActiveGroup
23450
23724
  );
23451
23725
  let criticalPathActive = false;
23726
+ let dependenciesActive = !!resolved.options.dependencies;
23727
+ let controlsExpanded = false;
23452
23728
  const tagRows = currentSwimlaneGroup ? buildTagLaneRowList(resolved, currentSwimlaneGroup, collapsedLanes) : null;
23453
23729
  const rows = tagRows ?? buildRowList(resolved, collapsedGroups);
23454
23730
  const isTagMode = tagRows !== null;
@@ -23465,9 +23741,11 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23465
23741
  const maxLabelLen = Math.max(...allLabels.map((l) => l.length), 10);
23466
23742
  const leftMargin = Math.max(MIN_LEFT_MARGIN, maxLabelLen * 7 + 30);
23467
23743
  const totalRows = rows.length;
23744
+ const hasCriticalPath = resolved.options.criticalPath && resolved.tasks.some((t) => t.isCriticalPath);
23745
+ const hasDependencies = resolved.options.dependencies && resolved.tasks.some((t) => t.task.dependencies.length > 0);
23468
23746
  const title = resolved.options.title;
23469
23747
  const titleHeight = title ? 50 : 20;
23470
- const tagLegendReserve = resolved.tagGroups.length > 0 ? LEGEND_HEIGHT + 8 : 0;
23748
+ const tagLegendReserve = resolved.tagGroups.length > 0 || hasCriticalPath || hasDependencies ? LEGEND_HEIGHT + 8 : 0;
23471
23749
  const topDateLabelReserve = 22;
23472
23750
  const hasOverheadLabels = resolved.markers.length > 0 || resolved.eras.length > 0;
23473
23751
  const markerLabelReserve = hasOverheadLabels ? 28 : 0;
@@ -23485,10 +23763,9 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23485
23763
  if (title) {
23486
23764
  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
23765
  }
23488
- const hasCriticalPath = resolved.options.criticalPath && resolved.tasks.some((t) => t.isCriticalPath);
23489
23766
  function drawLegend() {
23490
23767
  svg.selectAll(".gantt-tag-legend-container").remove();
23491
- if (resolved.tagGroups.length > 0 || hasCriticalPath) {
23768
+ if (resolved.tagGroups.length > 0 || hasCriticalPath || hasDependencies) {
23492
23769
  const legendY = titleHeight;
23493
23770
  renderTagLegend(
23494
23771
  svg,
@@ -23510,16 +23787,38 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23510
23787
  recolorBars();
23511
23788
  },
23512
23789
  () => {
23513
- criticalPathActive = !criticalPathActive;
23790
+ controlsExpanded = !controlsExpanded;
23514
23791
  drawLegend();
23515
23792
  },
23516
23793
  currentSwimlaneGroup,
23517
23794
  onSwimlaneChange,
23518
23795
  viewMode,
23519
- resolved.tasks
23796
+ resolved.tasks,
23797
+ controlsExpanded,
23798
+ hasDependencies,
23799
+ dependenciesActive,
23800
+ (toggleId, active) => {
23801
+ if (toggleId === "critical-path") {
23802
+ criticalPathActive = active;
23803
+ } else if (toggleId === "dependencies") {
23804
+ dependenciesActive = active;
23805
+ g.selectAll(
23806
+ ".gantt-dep-arrow, .gantt-dep-arrowhead, .gantt-dep-label"
23807
+ ).attr("display", active ? null : "none");
23808
+ }
23809
+ drawLegend();
23810
+ }
23520
23811
  );
23521
23812
  }
23522
23813
  }
23814
+ function restoreHighlight() {
23815
+ if (criticalPathActive) {
23816
+ applyCriticalPathHighlight(svg, g);
23817
+ } else {
23818
+ svg.attr("data-critical-path-active", null);
23819
+ resetHighlight(g, svg);
23820
+ }
23821
+ }
23523
23822
  function recolorBars() {
23524
23823
  g.selectAll(".gantt-task").each(function() {
23525
23824
  const el = d3Selection10.select(this);
@@ -23538,8 +23837,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23538
23837
  });
23539
23838
  }
23540
23839
  drawLegend();
23541
- const startTime = dateToFractionalYear(resolved.startDate);
23542
- const endTime = dateToFractionalYear(resolved.endDate);
23840
+ const startTime = dateToFractionalYear2(resolved.startDate);
23841
+ const endTime = dateToFractionalYear2(resolved.endDate);
23543
23842
  const domainPad = Math.max((endTime - startTime) * 0.02, 0.01);
23544
23843
  const domainMin = startTime - domainPad;
23545
23844
  const domainMax = endTime + domainPad;
@@ -23570,7 +23869,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23570
23869
  } else {
23571
23870
  todayDate = /* @__PURE__ */ new Date(resolved.options.todayMarker + "T00:00:00");
23572
23871
  }
23573
- todayX = xScale(dateToFractionalYear(todayDate));
23872
+ todayX = xScale(dateToFractionalYear2(todayDate));
23574
23873
  if (todayX >= 0 && todayX <= innerWidth) {
23575
23874
  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
23875
  if (todayMarkerLineNum)
@@ -23612,8 +23911,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23612
23911
  let lx2 = innerWidth;
23613
23912
  let laneBarWidth = innerWidth;
23614
23913
  if (row.laneStartDate && row.laneEndDate) {
23615
- lx1 = xScale(dateToFractionalYear(row.laneStartDate));
23616
- lx2 = xScale(dateToFractionalYear(row.laneEndDate));
23914
+ lx1 = xScale(dateToFractionalYear2(row.laneStartDate));
23915
+ lx2 = xScale(dateToFractionalYear2(row.laneEndDate));
23617
23916
  laneBarWidth = Math.max(lx2 - lx1, 2);
23618
23917
  }
23619
23918
  lanePositions.set(row.laneName, {
@@ -23645,7 +23944,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23645
23944
  );
23646
23945
  }
23647
23946
  }).on("mouseleave", () => {
23648
- resetHighlight(g, svg);
23947
+ restoreHighlight();
23649
23948
  hideGanttDateIndicators(g);
23650
23949
  });
23651
23950
  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 +24018,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23719
24018
  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
24019
  toggleIcon + " " + group.name + (group.progress !== null ? ` ${Math.round(group.progress)}%` : "")
23721
24020
  );
23722
- const gStart = dateToFractionalYear(group.startDate);
23723
- const gEnd = dateToFractionalYear(group.endDate);
24021
+ const gStart = dateToFractionalYear2(group.startDate);
24022
+ const gEnd = dateToFractionalYear2(group.endDate);
23724
24023
  const gx1 = xScale(gStart);
23725
24024
  const gx2 = xScale(gEnd);
23726
24025
  if (gx2 > gx1) {
@@ -23834,7 +24133,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23834
24133
  taskLabel.attr("data-critical-path", "true");
23835
24134
  }
23836
24135
  if (rt.isMilestone) {
23837
- const mx = xScale(dateToFractionalYear(rt.startDate));
24136
+ const mx = xScale(dateToFractionalYear2(rt.startDate));
23838
24137
  const my = yOffset + BAR_H / 2;
23839
24138
  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
24139
  if (onClickItem) onClickItem(task.lineNumber);
@@ -23850,14 +24149,14 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23850
24149
  );
23851
24150
  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
24151
  }).on("mouseleave", () => {
23853
- resetHighlight(g, svg);
24152
+ restoreHighlight();
23854
24153
  hideGanttDateIndicators(g);
23855
24154
  g.selectAll(".gantt-milestone-hover-label").remove();
23856
24155
  });
23857
24156
  taskPositions.set(task.id, { x1: mx, x2: mx, y: my });
23858
24157
  } else {
23859
- const tStart = dateToFractionalYear(rt.startDate);
23860
- const tEnd = dateToFractionalYear(rt.endDate);
24158
+ const tStart = dateToFractionalYear2(rt.startDate);
24159
+ const tEnd = dateToFractionalYear2(rt.endDate);
23861
24160
  const x1 = xScale(tStart);
23862
24161
  const x2 = xScale(tEnd);
23863
24162
  const barWidth = Math.max(x2 - x1, 2);
@@ -23879,11 +24178,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23879
24178
  );
23880
24179
  }).on("mouseleave", () => {
23881
24180
  if (resolved.options.dependencies) {
23882
- if (criticalPathActive) {
23883
- applyCriticalPathHighlight(svg, g);
23884
- } else {
23885
- resetHighlight(g, svg);
23886
- }
24181
+ restoreHighlight();
23887
24182
  }
23888
24183
  resetTaskLabels(svg);
23889
24184
  hideGanttDateIndicators(g);
@@ -24096,14 +24391,14 @@ function renderHolidayBands(g, svg, resolved, xScale, innerHeight, palette, isDa
24096
24391
  }
24097
24392
  }
24098
24393
  function drawBand(g, xScale, start, end, height, palette, _isDark, className, opacity) {
24099
- const x1 = xScale(dateToFractionalYear(start));
24100
- const x2 = xScale(dateToFractionalYear(end));
24394
+ const x1 = xScale(dateToFractionalYear2(start));
24395
+ const x2 = xScale(dateToFractionalYear2(end));
24101
24396
  if (x2 <= x1) return;
24102
24397
  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
24398
  }
24104
24399
  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));
24400
+ const x1 = xScale(dateToFractionalYear2(start));
24401
+ const x2 = xScale(dateToFractionalYear2(end));
24107
24402
  if (x2 <= x1) return;
24108
24403
  const bandW = Math.max(x2 - x1, 4);
24109
24404
  const baseOpacity = 0.08;
@@ -24198,6 +24493,7 @@ function arrowheadPoints(x, y, size, angle) {
24198
24493
  return `${x},${y} ${x + size * Math.cos(a1)},${y + size * Math.sin(a1)} ${x + size * Math.cos(a2)},${y + size * Math.sin(a2)}`;
24199
24494
  }
24200
24495
  function applyCriticalPathHighlight(svg, chartG) {
24496
+ svg.attr("data-critical-path-active", "true");
24201
24497
  chartG.selectAll(".gantt-task").each(function() {
24202
24498
  const el = d3Selection10.select(this);
24203
24499
  el.attr(
@@ -24250,8 +24546,31 @@ function drawSwimlaneIcon2(parent, x, y, isActive, palette) {
24250
24546
  }
24251
24547
  return iconG;
24252
24548
  }
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);
24549
+ function buildControlsToggles(hasCriticalPath, criticalPathActive, hasDependencies, dependenciesActive) {
24550
+ const toggles = [];
24551
+ if (hasCriticalPath) {
24552
+ toggles.push({
24553
+ id: "critical-path",
24554
+ type: "toggle",
24555
+ label: "Critical Path",
24556
+ active: criticalPathActive,
24557
+ onToggle: () => {
24558
+ }
24559
+ });
24560
+ }
24561
+ if (hasDependencies) {
24562
+ toggles.push({
24563
+ id: "dependencies",
24564
+ type: "toggle",
24565
+ label: "Dependencies",
24566
+ active: dependenciesActive,
24567
+ onToggle: () => {
24568
+ }
24569
+ });
24570
+ }
24571
+ return toggles;
24572
+ }
24573
+ 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
24574
  let visibleGroups;
24256
24575
  if (activeGroupName) {
24257
24576
  const activeGroup = tagGroups.filter(
@@ -24312,16 +24631,17 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
24312
24631
  totalW += groupW;
24313
24632
  }
24314
24633
  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) {
24634
+ const hasControls = hasCriticalPath || hasDependencies;
24635
+ const controlsToggleLabels = [];
24636
+ if (hasCriticalPath) controlsToggleLabels.push({ label: "Critical Path" });
24637
+ if (hasDependencies) controlsToggleLabels.push({ label: "Dependencies" });
24638
+ if (hasControls) {
24318
24639
  if (visibleGroups.length > 0) totalW += LEGEND_GROUP_GAP;
24319
- totalW += cpPillW;
24640
+ totalW += controlsExpanded ? controlsGroupCapsuleWidth(controlsToggleLabels) : LEGEND_GEAR_PILL_W;
24320
24641
  }
24321
24642
  const containerWidth = chartLeftMargin + chartInnerWidth + RIGHT_MARGIN;
24322
24643
  const legendX = (containerWidth - totalW) / 2;
24323
24644
  const legendRow = svg.append("g").attr("class", "gantt-tag-legend-container").attr("transform", `translate(${legendX}, ${legendY})`);
24324
- let cursorX = 0;
24325
24645
  if (visibleGroups.length > 0) {
24326
24646
  const showIcon = !legendViewMode && tagGroups.length > 0;
24327
24647
  const iconReserve = showIcon ? LEGEND_ICON_W : 0;
@@ -24333,6 +24653,12 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
24333
24653
  entries: entries.map((e) => ({ value: e.value, color: e.color }))
24334
24654
  };
24335
24655
  });
24656
+ const controlsToggles = buildControlsToggles(
24657
+ hasCriticalPath,
24658
+ criticalPathActive,
24659
+ hasDependencies,
24660
+ dependenciesActive
24661
+ );
24336
24662
  const legendConfig = {
24337
24663
  groups: legendGroups,
24338
24664
  position: {
@@ -24340,13 +24666,23 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
24340
24666
  titleRelation: "below-title"
24341
24667
  },
24342
24668
  mode: "fixed",
24343
- capsulePillAddonWidth: iconReserve
24669
+ capsulePillAddonWidth: iconReserve,
24670
+ controlsGroup: controlsToggles.length > 0 ? { toggles: controlsToggles } : void 0
24671
+ };
24672
+ const legendState = {
24673
+ activeGroup: activeGroupName,
24674
+ controlsExpanded
24344
24675
  };
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);
24676
+ let tagGroupsW = visibleGroups.reduce((s, _, i) => s + groupWidths[i], 0) + Math.max(0, (visibleGroups.length - 1) * LEGEND_GROUP_GAP);
24677
+ if (hasControls) {
24678
+ if (visibleGroups.length > 0) tagGroupsW += LEGEND_GROUP_GAP;
24679
+ tagGroupsW += controlsExpanded ? controlsGroupCapsuleWidth(controlsToggleLabels) : LEGEND_GEAR_PILL_W;
24680
+ }
24347
24681
  const tagGroupG = legendRow.append("g");
24348
24682
  const legendCallbacks = {
24349
24683
  onGroupToggle: onToggle,
24684
+ onControlsExpand: onToggleControlsExpand,
24685
+ onControlsToggle,
24350
24686
  onEntryHover: (groupName, entryValue) => {
24351
24687
  const tagKey = groupName.toLowerCase();
24352
24688
  if (entryValue) {
@@ -24423,31 +24759,38 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
24423
24759
  legendCallbacks,
24424
24760
  tagGroupsW
24425
24761
  );
24426
- for (let i = 0; i < visibleGroups.length; i++) {
24427
- cursorX += groupWidths[i] + LEGEND_GROUP_GAP;
24428
- }
24762
+ } else if (hasControls) {
24763
+ const controlsToggles = buildControlsToggles(
24764
+ hasCriticalPath,
24765
+ criticalPathActive,
24766
+ hasDependencies,
24767
+ dependenciesActive
24768
+ );
24769
+ const legendConfig = {
24770
+ groups: [],
24771
+ position: {
24772
+ placement: "top-center",
24773
+ titleRelation: "below-title"
24774
+ },
24775
+ mode: "fixed",
24776
+ controlsGroup: { toggles: controlsToggles }
24777
+ };
24778
+ const tagGroupG = legendRow.append("g");
24779
+ renderLegendD3(
24780
+ tagGroupG,
24781
+ legendConfig,
24782
+ { activeGroup: null, controlsExpanded },
24783
+ palette,
24784
+ isDark,
24785
+ {
24786
+ onControlsExpand: onToggleControlsExpand,
24787
+ onControlsToggle
24788
+ },
24789
+ totalW
24790
+ );
24429
24791
  }
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
- });
24792
+ if (criticalPathActive) {
24793
+ applyCriticalPathHighlight(svg, chartG);
24451
24794
  }
24452
24795
  }
24453
24796
  function renderErasAndMarkers(g, svg, resolved, xScale, innerHeight, palette) {
@@ -24577,8 +24920,8 @@ function renderSprintBands(g, svg, resolved, xScale, innerHeight, palette) {
24577
24920
  const chartMinX = 0;
24578
24921
  for (let i = 0; i < resolved.sprints.length; i++) {
24579
24922
  const sprint = resolved.sprints[i];
24580
- const rawSx = xScale(dateToFractionalYear(sprint.startDate));
24581
- const rawEx = xScale(dateToFractionalYear(sprint.endDate));
24923
+ const rawSx = xScale(dateToFractionalYear2(sprint.startDate));
24924
+ const rawEx = xScale(dateToFractionalYear2(sprint.endDate));
24582
24925
  if (rawEx <= rawSx) continue;
24583
24926
  const sx = Math.max(rawSx, chartMinX);
24584
24927
  const ex = rawEx;
@@ -24700,7 +25043,7 @@ function renderSprintBands(g, svg, resolved, xScale, innerHeight, palette) {
24700
25043
  });
24701
25044
  }
24702
25045
  const lastSprint = resolved.sprints[resolved.sprints.length - 1];
24703
- const lastEx = xScale(dateToFractionalYear(lastSprint.endDate));
25046
+ const lastEx = xScale(dateToFractionalYear2(lastSprint.endDate));
24704
25047
  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
25048
  }
24706
25049
  function parseDateStringToDate(s) {
@@ -24711,7 +25054,7 @@ function parseDateStringToDate(s) {
24711
25054
  return new Date(year, month, day);
24712
25055
  }
24713
25056
  function parseDateToFractionalYear(s) {
24714
- return dateToFractionalYear(parseDateStringToDate(s));
25057
+ return dateToFractionalYear2(parseDateStringToDate(s));
24715
25058
  }
24716
25059
  function highlightDeps(g, svg, taskId, resolved) {
24717
25060
  const related = /* @__PURE__ */ new Set([taskId]);
@@ -24915,6 +25258,10 @@ function resetTaskLabels(svg) {
24915
25258
  svg.selectAll(".gantt-task-label").attr("opacity", 1);
24916
25259
  }
24917
25260
  function resetHighlight(g, svg) {
25261
+ if (svg.attr("data-critical-path-active") === "true") {
25262
+ applyCriticalPathHighlight(svg, g);
25263
+ return;
25264
+ }
24918
25265
  g.selectAll(".gantt-task, .gantt-milestone").attr(
24919
25266
  "opacity",
24920
25267
  1
@@ -25074,7 +25421,7 @@ function durationWeightedProgress(tasks) {
25074
25421
  }
25075
25422
  return hasProgress && totalDuration > 0 ? totalProgress / totalDuration : null;
25076
25423
  }
25077
- function dateToFractionalYear(d) {
25424
+ function dateToFractionalYear2(d) {
25078
25425
  const y = d.getFullYear();
25079
25426
  const startOfYear = new Date(y, 0, 1);
25080
25427
  const endOfYear = new Date(y + 1, 0, 1);
@@ -25086,7 +25433,7 @@ function diamondPoints(cx, cy, size) {
25086
25433
  return `${cx},${cy - half} ${cx + half},${cy} ${cx},${cy + half} ${cx - half},${cy}`;
25087
25434
  }
25088
25435
  function formatGanttDate(d) {
25089
- const base = `${MONTH_ABBR[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`;
25436
+ const base = `${MONTH_ABBR2[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`;
25090
25437
  if (d.getHours() === 0 && d.getMinutes() === 0) return base;
25091
25438
  const hh = String(d.getHours()).padStart(2, "0");
25092
25439
  const mm = String(d.getMinutes()).padStart(2, "0");
@@ -25097,7 +25444,7 @@ function showGanttDateIndicators(g, xScale, startDate, endDate, innerHeight, col
25097
25444
  g.selectAll(".gantt-today").attr("opacity", 0.05);
25098
25445
  const hg = g.append("g").attr("class", "gantt-hover-date").attr("pointer-events", "none");
25099
25446
  const tickLen = 6;
25100
- const startPos = xScale(dateToFractionalYear(startDate));
25447
+ const startPos = xScale(dateToFractionalYear2(startDate));
25101
25448
  const startLabel = formatGanttDate(startDate);
25102
25449
  if (!options?.skipStartLine) {
25103
25450
  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 +25452,7 @@ function showGanttDateIndicators(g, xScale, startDate, endDate, innerHeight, col
25105
25452
  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
25453
  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
25454
  if (endDate && endDate.getTime() !== startDate.getTime()) {
25108
- const endPos = xScale(dateToFractionalYear(endDate));
25455
+ const endPos = xScale(dateToFractionalYear2(endDate));
25109
25456
  const endLabel = formatGanttDate(endDate);
25110
25457
  const minLabelGap = 90;
25111
25458
  const gap = endPos - startPos;
@@ -25175,7 +25522,7 @@ function renderTimeScaleHorizontal(g, scale, innerWidth, innerHeight, textColor)
25175
25522
  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
25523
  }
25177
25524
  }
25178
- var d3Scale, d3Selection10, 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;
25525
+ var d3Scale, d3Selection10, 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
25526
  var init_renderer9 = __esm({
25180
25527
  "src/gantt/renderer.ts"() {
25181
25528
  "use strict";
@@ -25185,9 +25532,10 @@ var init_renderer9 = __esm({
25185
25532
  init_palettes();
25186
25533
  init_color_utils();
25187
25534
  init_tag_groups();
25188
- init_d3();
25535
+ init_time_ticks();
25189
25536
  init_legend_constants();
25190
25537
  init_legend_d3();
25538
+ init_legend_layout();
25191
25539
  init_title_constants();
25192
25540
  BAR_H = 22;
25193
25541
  ROW_GAP = 6;
@@ -25216,7 +25564,7 @@ var init_renderer9 = __esm({
25216
25564
  SPRINT_HOVER_OPACITY = 0.12;
25217
25565
  SPRINT_BOUNDARY_OPACITY = 0.3;
25218
25566
  FADE_OPACITY = 0.1;
25219
- MONTH_ABBR = [
25567
+ MONTH_ABBR2 = [
25220
25568
  "Jan",
25221
25569
  "Feb",
25222
25570
  "Mar",
@@ -25481,6 +25829,109 @@ var init_state_renderer = __esm({
25481
25829
  }
25482
25830
  });
25483
25831
 
25832
+ // src/sequence/collapse.ts
25833
+ function applyCollapseProjection(parsed, collapsedGroups) {
25834
+ if (collapsedGroups.size === 0) {
25835
+ return {
25836
+ participants: parsed.participants,
25837
+ messages: parsed.messages,
25838
+ elements: parsed.elements,
25839
+ groups: parsed.groups,
25840
+ collapsedGroupIds: /* @__PURE__ */ new Map()
25841
+ };
25842
+ }
25843
+ const memberToGroup = /* @__PURE__ */ new Map();
25844
+ const collapsedGroupNames = /* @__PURE__ */ new Set();
25845
+ for (const group of parsed.groups) {
25846
+ if (collapsedGroups.has(group.lineNumber)) {
25847
+ collapsedGroupNames.add(group.name);
25848
+ for (const memberId of group.participantIds) {
25849
+ memberToGroup.set(memberId, group.name);
25850
+ }
25851
+ }
25852
+ }
25853
+ const participants = [];
25854
+ const insertedGroups = /* @__PURE__ */ new Set();
25855
+ for (const p of parsed.participants) {
25856
+ const groupName = memberToGroup.get(p.id);
25857
+ if (groupName) {
25858
+ if (!insertedGroups.has(groupName)) {
25859
+ insertedGroups.add(groupName);
25860
+ const group = parsed.groups.find(
25861
+ (g) => g.name === groupName && collapsedGroups.has(g.lineNumber)
25862
+ );
25863
+ participants.push({
25864
+ id: groupName,
25865
+ label: groupName,
25866
+ type: "default",
25867
+ lineNumber: group.lineNumber
25868
+ });
25869
+ }
25870
+ } else if (collapsedGroupNames.has(p.id)) {
25871
+ } else {
25872
+ participants.push(p);
25873
+ }
25874
+ }
25875
+ const remap = (id) => memberToGroup.get(id) ?? id;
25876
+ const messages = parsed.messages.map((msg) => ({
25877
+ ...msg,
25878
+ from: remap(msg.from),
25879
+ to: remap(msg.to)
25880
+ }));
25881
+ const elements = remapElements(parsed.elements, memberToGroup);
25882
+ const groups = parsed.groups.filter(
25883
+ (g) => !collapsedGroups.has(g.lineNumber)
25884
+ );
25885
+ return {
25886
+ participants,
25887
+ messages,
25888
+ elements,
25889
+ groups,
25890
+ collapsedGroupIds: memberToGroup
25891
+ };
25892
+ }
25893
+ function remapElements(elements, memberToGroup) {
25894
+ const remap = (id) => memberToGroup.get(id) ?? id;
25895
+ const result = [];
25896
+ for (const el of elements) {
25897
+ if (isSequenceSection(el)) {
25898
+ result.push(el);
25899
+ } else if (isSequenceNote(el)) {
25900
+ result.push({
25901
+ ...el,
25902
+ participantId: remap(el.participantId)
25903
+ });
25904
+ } else if (isSequenceBlock(el)) {
25905
+ result.push({
25906
+ ...el,
25907
+ children: remapElements(el.children, memberToGroup),
25908
+ elseChildren: remapElements(el.elseChildren, memberToGroup),
25909
+ ...el.elseIfBranches ? {
25910
+ elseIfBranches: el.elseIfBranches.map((branch) => ({
25911
+ ...branch,
25912
+ children: remapElements(branch.children, memberToGroup)
25913
+ }))
25914
+ } : {}
25915
+ });
25916
+ } else {
25917
+ const msg = el;
25918
+ const from = remap(msg.from);
25919
+ const to = remap(msg.to);
25920
+ if (from === to && from !== msg.from && !msg.label) {
25921
+ continue;
25922
+ }
25923
+ result.push({ ...msg, from, to });
25924
+ }
25925
+ }
25926
+ return result;
25927
+ }
25928
+ var init_collapse3 = __esm({
25929
+ "src/sequence/collapse.ts"() {
25930
+ "use strict";
25931
+ init_parser();
25932
+ }
25933
+ });
25934
+
25484
25935
  // src/sequence/tag-resolution.ts
25485
25936
  function propagateGroupTags(participantMeta, groups) {
25486
25937
  for (const group of groups) {
@@ -25935,13 +26386,32 @@ function applyGroupOrdering(participants, groups, messages = []) {
25935
26386
  }
25936
26387
  function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateToLine, options) {
25937
26388
  d3Selection12.select(container).selectAll("*").remove();
25938
- const { title, messages, elements, groups, options: parsedOptions } = parsed;
26389
+ const { title, options: parsedOptions } = parsed;
26390
+ const effectiveCollapsedGroups = /* @__PURE__ */ new Set();
26391
+ for (const group of parsed.groups) {
26392
+ if (group.collapsed) effectiveCollapsedGroups.add(group.lineNumber);
26393
+ }
26394
+ if (options?.collapsedGroups) {
26395
+ for (const ln of options.collapsedGroups) {
26396
+ if (effectiveCollapsedGroups.has(ln)) {
26397
+ effectiveCollapsedGroups.delete(ln);
26398
+ } else {
26399
+ effectiveCollapsedGroups.add(ln);
26400
+ }
26401
+ }
26402
+ }
26403
+ const collapsed = effectiveCollapsedGroups.size > 0 ? applyCollapseProjection(parsed, effectiveCollapsedGroups) : null;
26404
+ const messages = collapsed ? collapsed.messages : parsed.messages;
26405
+ const elements = collapsed ? collapsed.elements : parsed.elements;
26406
+ const groups = collapsed ? collapsed.groups : parsed.groups;
26407
+ const collapsedGroupIds = collapsed?.collapsedGroupIds ?? /* @__PURE__ */ new Map();
25939
26408
  const collapsedSections = options?.collapsedSections;
25940
26409
  const expandedNoteLines = options?.expandedNoteLines;
25941
26410
  const collapseNotesDisabled = parsedOptions["collapse-notes"]?.toLowerCase() === "no";
25942
26411
  const isNoteExpanded = (note) => expandedNoteLines === void 0 || collapseNotesDisabled || expandedNoteLines.has(note.lineNumber);
26412
+ const sourceParticipants = collapsed ? collapsed.participants : parsed.participants;
25943
26413
  const participants = applyPositionOverrides(
25944
- applyGroupOrdering(parsed.participants, groups, messages)
26414
+ applyGroupOrdering(sourceParticipants, groups, messages)
25945
26415
  );
25946
26416
  if (participants.length === 0) return;
25947
26417
  const activationsOff = parsedOptions.activations?.toLowerCase() === "off";
@@ -26112,13 +26582,16 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26112
26582
  const preSectionMsgIndices = [];
26113
26583
  const sectionRegions = [];
26114
26584
  {
26585
+ const msgLineToIndex = /* @__PURE__ */ new Map();
26586
+ messages.forEach((m, i) => msgLineToIndex.set(m.lineNumber, i));
26587
+ const findMsgIndex = (child) => msgLineToIndex.get(child.lineNumber) ?? -1;
26115
26588
  const collectMsgIndicesFromBlock = (block) => {
26116
26589
  const indices = [];
26117
26590
  for (const child of block.children) {
26118
26591
  if (isSequenceBlock(child)) {
26119
26592
  indices.push(...collectMsgIndicesFromBlock(child));
26120
26593
  } else if (!isSequenceSection(child) && !isSequenceNote(child)) {
26121
- const idx = messages.indexOf(child);
26594
+ const idx = findMsgIndex(child);
26122
26595
  if (idx >= 0) indices.push(idx);
26123
26596
  }
26124
26597
  }
@@ -26128,7 +26601,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26128
26601
  if (isSequenceBlock(child)) {
26129
26602
  indices.push(...collectMsgIndicesFromBlock(child));
26130
26603
  } else if (!isSequenceSection(child) && !isSequenceNote(child)) {
26131
- const idx = messages.indexOf(child);
26604
+ const idx = findMsgIndex(child);
26132
26605
  if (idx >= 0) indices.push(idx);
26133
26606
  }
26134
26607
  }
@@ -26138,7 +26611,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26138
26611
  if (isSequenceBlock(child)) {
26139
26612
  indices.push(...collectMsgIndicesFromBlock(child));
26140
26613
  } else if (!isSequenceSection(child) && !isSequenceNote(child)) {
26141
- const idx = messages.indexOf(child);
26614
+ const idx = findMsgIndex(child);
26142
26615
  if (idx >= 0) indices.push(idx);
26143
26616
  }
26144
26617
  }
@@ -26153,7 +26626,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26153
26626
  } else if (isSequenceBlock(el)) {
26154
26627
  currentTarget.push(...collectMsgIndicesFromBlock(el));
26155
26628
  } else {
26156
- const idx = messages.indexOf(el);
26629
+ const idx = findMsgIndex(el);
26157
26630
  if (idx >= 0) currentTarget.push(idx);
26158
26631
  }
26159
26632
  }
@@ -26215,7 +26688,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26215
26688
  const titleOffset = title ? TITLE_HEIGHT5 : 0;
26216
26689
  const LEGEND_FIXED_GAP4 = 8;
26217
26690
  const legendTopSpace = parsed.tagGroups.length > 0 ? LEGEND_HEIGHT + LEGEND_FIXED_GAP4 : 0;
26218
- const groupOffset = groups.length > 0 ? GROUP_PADDING_TOP + GROUP_LABEL_SIZE : 0;
26691
+ const groupOffset = parsed.groups.length > 0 ? GROUP_PADDING_TOP + GROUP_LABEL_SIZE : 0;
26219
26692
  const participantStartY = TOP_MARGIN + titleOffset + legendTopSpace + PARTICIPANT_Y_OFFSET + groupOffset;
26220
26693
  const lifelineStartY0 = participantStartY + PARTICIPANT_BOX_HEIGHT;
26221
26694
  const hasActors = participants.some((p) => p.type === "actor");
@@ -26385,6 +26858,17 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26385
26858
  svgWidth
26386
26859
  );
26387
26860
  }
26861
+ const collapsedGroupNames = /* @__PURE__ */ new Set();
26862
+ const collapsedGroupMeta = /* @__PURE__ */ new Map();
26863
+ for (const group of parsed.groups) {
26864
+ if (effectiveCollapsedGroups.has(group.lineNumber)) {
26865
+ collapsedGroupNames.add(group.name);
26866
+ collapsedGroupMeta.set(group.name, {
26867
+ lineNumber: group.lineNumber,
26868
+ metadata: group.metadata
26869
+ });
26870
+ }
26871
+ }
26388
26872
  for (const group of groups) {
26389
26873
  if (group.participantIds.length === 0) continue;
26390
26874
  const memberXs = group.participantIds.map((id) => participantX.get(id)).filter((x) => x !== void 0);
@@ -26401,8 +26885,10 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26401
26885
  isDark ? 15 : 20
26402
26886
  ) : isDark ? palette.surface : palette.bg;
26403
26887
  const strokeColor = groupTagColor || palette.textMuted;
26404
- 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));
26405
- 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);
26888
+ const groupG = svg.append("g").attr("class", "group-box-wrapper").attr("data-group-toggle", "").attr("data-group-line", String(group.lineNumber)).attr("cursor", "pointer");
26889
+ groupG.append("title").text("Click to collapse");
26890
+ 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");
26891
+ 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);
26406
26892
  }
26407
26893
  const lifelineStartY = lifelineStartY0;
26408
26894
  participants.forEach((participant, index) => {
@@ -26411,6 +26897,14 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26411
26897
  const pTagValue = tagMap?.participants.get(participant.id);
26412
26898
  const pTagColor = getTagColor(pTagValue);
26413
26899
  const pTagAttr = tagKey && pTagValue ? { key: tagKey, value: pTagValue.toLowerCase() } : void 0;
26900
+ const isCollapsedGroup = collapsedGroupNames.has(participant.id);
26901
+ let effectiveTagColor = pTagColor;
26902
+ if (isCollapsedGroup && !effectiveTagColor) {
26903
+ const meta = collapsedGroupMeta.get(participant.id);
26904
+ if (meta?.metadata && tagKey) {
26905
+ effectiveTagColor = getTagColor(meta.metadata[tagKey]);
26906
+ }
26907
+ }
26414
26908
  renderParticipant(
26415
26909
  svg,
26416
26910
  participant,
@@ -26418,10 +26912,35 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26418
26912
  cy,
26419
26913
  palette,
26420
26914
  isDark,
26421
- pTagColor,
26915
+ effectiveTagColor,
26422
26916
  pTagAttr
26423
26917
  );
26424
- 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);
26918
+ if (isCollapsedGroup) {
26919
+ const meta = collapsedGroupMeta.get(participant.id);
26920
+ const drillColor = effectiveTagColor || palette.textMuted;
26921
+ const drillBarH = 6;
26922
+ const boxW = PARTICIPANT_BOX_WIDTH;
26923
+ const fullH = PARTICIPANT_BOX_HEIGHT + GROUP_PADDING_TOP + GROUP_PADDING_BOTTOM;
26924
+ const clipId = `clip-drill-group-${participant.id.replace(/[^a-zA-Z0-9-]/g, "-")}`;
26925
+ const participantG = svg.select(
26926
+ `.participant[data-participant-id="${participant.id}"]`
26927
+ );
26928
+ participantG.attr("data-group-toggle", "").attr("data-group-line", String(meta.lineNumber)).attr("cursor", "pointer");
26929
+ participantG.append("title").text("Click to expand");
26930
+ const pFill = effectiveTagColor ? mix(
26931
+ effectiveTagColor,
26932
+ isDark ? palette.surface : palette.bg,
26933
+ isDark ? 30 : 40
26934
+ ) : isDark ? mix(palette.overlay, palette.surface, 50) : mix(palette.bg, palette.surface, 50);
26935
+ const pStroke = effectiveTagColor || palette.border;
26936
+ 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);
26937
+ 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);
26938
+ 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);
26939
+ 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})`);
26940
+ }
26941
+ const llY = isCollapsedGroup ? lifelineStartY + GROUP_PADDING_BOTTOM : lifelineStartY;
26942
+ const llColor = isCollapsedGroup ? effectiveTagColor || palette.textMuted : pTagColor || palette.textMuted;
26943
+ 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);
26425
26944
  if (tagKey && pTagValue) {
26426
26945
  lifelineEl.attr(`data-tag-${tagKey}`, pTagValue.toLowerCase());
26427
26946
  }
@@ -26649,23 +27168,8 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26649
27168
  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");
26650
27169
  const msgCount = sectionMsgCounts.get(sec.lineNumber) ?? 0;
26651
27170
  const labelText = isCollapsed ? `${sec.label} (${msgCount} ${msgCount === 1 ? "message" : "messages"})` : sec.label;
26652
- const labelColor = isCollapsed ? "#ffffff" : lineColor;
26653
- const chevronSpace = 14;
26654
27171
  const labelX = (sectionLineX1 + sectionLineX2) / 2;
26655
- const chevronX = labelX - (labelText.length * 3.5 + 8 + chevronSpace / 2);
26656
- const chevronY = secY;
26657
- if (isCollapsed) {
26658
- sectionG.append("path").attr(
26659
- "d",
26660
- `M ${chevronX} ${chevronY - 4} L ${chevronX + 6} ${chevronY} L ${chevronX} ${chevronY + 4} Z`
26661
- ).attr("fill", labelColor).attr("class", "section-chevron");
26662
- } else {
26663
- sectionG.append("path").attr(
26664
- "d",
26665
- `M ${chevronX - 1} ${chevronY - 3} L ${chevronX + 7} ${chevronY - 3} L ${chevronX + 3} ${chevronY + 3} Z`
26666
- ).attr("fill", labelColor).attr("class", "section-chevron");
26667
- }
26668
- 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);
27172
+ 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);
26669
27173
  }
26670
27174
  const SELF_CALL_WIDTH = 30;
26671
27175
  const SELF_CALL_HEIGHT = 25;
@@ -26704,7 +27208,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26704
27208
  if (tagKey && msgTagValue) {
26705
27209
  labelEl.attr(`data-tag-${tagKey}`, msgTagValue.toLowerCase());
26706
27210
  }
26707
- renderInlineText(labelEl, step.label, palette);
27211
+ labelEl.text(step.label);
26708
27212
  }
26709
27213
  } else {
26710
27214
  const goingRight = fromX < toX;
@@ -26731,7 +27235,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26731
27235
  if (tagKey && msgTagValue) {
26732
27236
  labelEl.attr(`data-tag-${tagKey}`, msgTagValue.toLowerCase());
26733
27237
  }
26734
- renderInlineText(labelEl, step.label, palette);
27238
+ labelEl.text(step.label);
26735
27239
  }
26736
27240
  }
26737
27241
  } else {
@@ -26762,7 +27266,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26762
27266
  if (tagKey && msgTagValue) {
26763
27267
  labelEl.attr(`data-tag-${tagKey}`, msgTagValue.toLowerCase());
26764
27268
  }
26765
- renderInlineText(labelEl, step.label, palette);
27269
+ labelEl.text(step.label);
26766
27270
  }
26767
27271
  }
26768
27272
  });
@@ -26947,6 +27451,7 @@ var init_renderer10 = __esm({
26947
27451
  init_inline_markdown();
26948
27452
  init_fonts();
26949
27453
  init_parser();
27454
+ init_collapse3();
26950
27455
  init_tag_resolution();
26951
27456
  init_tag_groups();
26952
27457
  init_legend_constants();
@@ -27034,23 +27539,6 @@ function parseTimelineDate(s) {
27034
27539
  const day = parts.length >= 3 ? parts[2] : 1;
27035
27540
  return year + (month - 1) / 12 + (day - 1) / 365 + hour / 8760 + minute / 525600;
27036
27541
  }
27037
- function fractionalYearToDate(frac) {
27038
- const year = Math.floor(frac);
27039
- const remainder = frac - year;
27040
- const monthFrac = remainder * 12;
27041
- const month = Math.floor(monthFrac);
27042
- const monthRemainder = remainder - month / 12;
27043
- const dayFrac = monthRemainder * 365;
27044
- const day = Math.floor(dayFrac) + 1;
27045
- const dayRemainder = dayFrac - Math.floor(dayFrac);
27046
- const hourFrac = dayRemainder * 24;
27047
- const hour = Math.floor(hourFrac);
27048
- const minute = Math.round((hourFrac - hour) * 60);
27049
- return new Date(year, month, day, hour, minute);
27050
- }
27051
- function dateToFractionalYear2(d) {
27052
- return d.getFullYear() + d.getMonth() / 12 + (d.getDate() - 1) / 365 + d.getHours() / 8760 + d.getMinutes() / 525600;
27053
- }
27054
27542
  function addDurationToDate(startDate, amount, unit) {
27055
27543
  const spaceIdx = startDate.indexOf(" ");
27056
27544
  let datePart = startDate;
@@ -28605,7 +29093,7 @@ function formatDateLabel(dateStr) {
28605
29093
  const parts = datePart.split("-");
28606
29094
  const year = parts[0];
28607
29095
  if (parts.length === 1) return year + timeSuffix;
28608
- const month = MONTH_ABBR2[parseInt(parts[1], 10) - 1];
29096
+ const month = MONTH_ABBR[parseInt(parts[1], 10) - 1];
28609
29097
  if (parts.length === 2) return `${month} ${year}${timeSuffix}`;
28610
29098
  const day = parseInt(parts[2], 10);
28611
29099
  return `${month} ${day}, ${year}${timeSuffix}`;
@@ -28622,123 +29110,6 @@ function formatBoundaryLabel(dateStr, otherDateStr) {
28622
29110
  }
28623
29111
  return formatDateLabel(dateStr);
28624
29112
  }
28625
- function computeTimeTicks(domainMin, domainMax, scale, boundaryStart, boundaryEnd, boundaryStartLabel, boundaryEndLabel) {
28626
- const minYear = Math.floor(domainMin);
28627
- const maxYear = Math.floor(domainMax);
28628
- const span = domainMax - domainMin;
28629
- let ticks = [];
28630
- const firstYear = Math.ceil(domainMin);
28631
- const lastYear = Math.floor(domainMax);
28632
- if (lastYear >= firstYear + 1) {
28633
- const yearSpan = lastYear - firstYear;
28634
- let step = 1;
28635
- if (yearSpan > 80) step = 20;
28636
- else if (yearSpan > 40) step = 10;
28637
- else if (yearSpan > 20) step = 5;
28638
- else if (yearSpan > 10) step = 2;
28639
- const alignedFirst = Math.ceil(firstYear / step) * step;
28640
- for (let y = alignedFirst; y <= lastYear; y += step) {
28641
- ticks.push({ pos: scale(y), label: String(y) });
28642
- }
28643
- } else if (span > 0.25) {
28644
- const crossesYear = maxYear > minYear;
28645
- for (let y = minYear; y <= maxYear + 1; y++) {
28646
- for (let m = 1; m <= 12; m++) {
28647
- const val = y + (m - 1) / 12;
28648
- if (val > domainMax) break;
28649
- if (val >= domainMin) {
28650
- ticks.push({
28651
- pos: scale(val),
28652
- label: crossesYear ? `${MONTH_ABBR2[m - 1]} '${String(y).slice(-2)}` : MONTH_ABBR2[m - 1]
28653
- });
28654
- }
28655
- }
28656
- }
28657
- } else if (span <= 685e-6) {
28658
- let stepMin = 5;
28659
- const spanHours = span * 8760;
28660
- if (spanHours > 3) stepMin = 30;
28661
- else if (spanHours > 1) stepMin = 15;
28662
- else if (spanHours > 0.5) stepMin = 10;
28663
- const startDate = fractionalYearToDate(domainMin);
28664
- startDate.setMinutes(
28665
- Math.floor(startDate.getMinutes() / stepMin) * stepMin,
28666
- 0,
28667
- 0
28668
- );
28669
- while (true) {
28670
- const val = dateToFractionalYear2(startDate);
28671
- if (val > domainMax) break;
28672
- if (val >= domainMin) {
28673
- const hh = String(startDate.getHours()).padStart(2, "0");
28674
- const mm = String(startDate.getMinutes()).padStart(2, "0");
28675
- ticks.push({ pos: scale(val), label: `${hh}:${mm}` });
28676
- }
28677
- startDate.setMinutes(startDate.getMinutes() + stepMin);
28678
- }
28679
- } else if (span <= 822e-5) {
28680
- let stepHour = 1;
28681
- const spanHours = span * 8760;
28682
- if (spanHours > 48) stepHour = 6;
28683
- else if (spanHours > 24) stepHour = 3;
28684
- else if (spanHours > 12) stepHour = 2;
28685
- const singleDay = spanHours <= 24;
28686
- const startDate = fractionalYearToDate(domainMin);
28687
- startDate.setHours(
28688
- Math.floor(startDate.getHours() / stepHour) * stepHour,
28689
- 0,
28690
- 0,
28691
- 0
28692
- );
28693
- while (true) {
28694
- const val = dateToFractionalYear2(startDate);
28695
- if (val > domainMax) break;
28696
- if (val >= domainMin) {
28697
- const hh = String(startDate.getHours()).padStart(2, "0");
28698
- const mm = String(startDate.getMinutes()).padStart(2, "0");
28699
- if (singleDay) {
28700
- ticks.push({ pos: scale(val), label: `${hh}:${mm}` });
28701
- } else {
28702
- const mon = MONTH_ABBR2[startDate.getMonth()];
28703
- const d = startDate.getDate();
28704
- ticks.push({ pos: scale(val), label: `${mon} ${d} ${hh}:${mm}` });
28705
- }
28706
- }
28707
- startDate.setHours(startDate.getHours() + stepHour);
28708
- }
28709
- } else {
28710
- for (let y = minYear; y <= maxYear + 1; y++) {
28711
- for (let m = 1; m <= 12; m++) {
28712
- for (const d of [1, 8, 15, 22]) {
28713
- const val = y + (m - 1) / 12 + (d - 1) / 365;
28714
- if (val > domainMax) break;
28715
- if (val >= domainMin) {
28716
- ticks.push({
28717
- pos: scale(val),
28718
- label: `${MONTH_ABBR2[m - 1]} ${d}`
28719
- });
28720
- }
28721
- }
28722
- }
28723
- }
28724
- }
28725
- const collisionThreshold = 40;
28726
- if (boundaryStart !== void 0 && boundaryStartLabel) {
28727
- const boundaryPos = scale(boundaryStart);
28728
- ticks = ticks.filter(
28729
- (t) => Math.abs(t.pos - boundaryPos) >= collisionThreshold
28730
- );
28731
- ticks.unshift({ pos: boundaryPos, label: boundaryStartLabel });
28732
- }
28733
- if (boundaryEnd !== void 0 && boundaryEndLabel) {
28734
- const boundaryPos = scale(boundaryEnd);
28735
- ticks = ticks.filter(
28736
- (t) => Math.abs(t.pos - boundaryPos) >= collisionThreshold
28737
- );
28738
- ticks.push({ pos: boundaryPos, label: boundaryEndLabel });
28739
- }
28740
- return ticks;
28741
- }
28742
29113
  function renderTimeScale(g, scale, isVertical, innerWidth, innerHeight, textColor, boundaryStart, boundaryEnd, boundaryStartLabel, boundaryEndLabel) {
28743
29114
  const [domainMin, domainMax] = scale.domain();
28744
29115
  const ticks = computeTimeTicks(
@@ -30666,7 +31037,7 @@ function createExportContainer(width, height) {
30666
31037
  document.body.appendChild(container);
30667
31038
  return container;
30668
31039
  }
30669
- function finalizeSvgExport(container, theme, palette, options) {
31040
+ function finalizeSvgExport(container, theme, palette) {
30670
31041
  const svgEl = container.querySelector("svg");
30671
31042
  if (!svgEl) return "";
30672
31043
  if (theme === "transparent") {
@@ -30679,10 +31050,6 @@ function finalizeSvgExport(container, theme, palette, options) {
30679
31050
  svgEl.querySelectorAll("[data-export-ignore]").forEach((el) => el.remove());
30680
31051
  const svgHtml = svgEl.outerHTML;
30681
31052
  document.body.removeChild(container);
30682
- if (options?.branding !== false) {
30683
- const brandColor = theme === "transparent" ? "#888" : palette.textMuted;
30684
- return injectBranding(svgHtml, brandColor);
30685
- }
30686
31053
  return svgHtml;
30687
31054
  }
30688
31055
  async function renderForExport(content, theme, palette, orgExportState, options) {
@@ -30729,7 +31096,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
30729
31096
  activeTagGroup,
30730
31097
  hiddenAttributes
30731
31098
  );
30732
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31099
+ return finalizeSvgExport(container2, theme, effectivePalette2);
30733
31100
  }
30734
31101
  if (detectedType === "sitemap") {
30735
31102
  const { parseSitemap: parseSitemap2 } = await Promise.resolve().then(() => (init_parser7(), parser_exports7));
@@ -30771,7 +31138,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
30771
31138
  activeTagGroup,
30772
31139
  hiddenAttributes
30773
31140
  );
30774
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31141
+ return finalizeSvgExport(container2, theme, effectivePalette2);
30775
31142
  }
30776
31143
  if (detectedType === "kanban") {
30777
31144
  const { parseKanban: parseKanban2 } = await Promise.resolve().then(() => (init_parser5(), parser_exports5));
@@ -30790,7 +31157,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
30790
31157
  options?.tagGroup
30791
31158
  )
30792
31159
  });
30793
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31160
+ return finalizeSvgExport(container2, theme, effectivePalette2);
30794
31161
  }
30795
31162
  if (detectedType === "class") {
30796
31163
  const { parseClassDiagram: parseClassDiagram2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports2));
@@ -30814,7 +31181,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
30814
31181
  void 0,
30815
31182
  { width: exportWidth, height: exportHeight }
30816
31183
  );
30817
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31184
+ return finalizeSvgExport(container2, theme, effectivePalette2);
30818
31185
  }
30819
31186
  if (detectedType === "er") {
30820
31187
  const { parseERDiagram: parseERDiagram2 } = await Promise.resolve().then(() => (init_parser3(), parser_exports3));
@@ -30843,7 +31210,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
30843
31210
  options?.tagGroup
30844
31211
  )
30845
31212
  );
30846
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31213
+ return finalizeSvgExport(container2, theme, effectivePalette2);
30847
31214
  }
30848
31215
  if (detectedType === "boxes-and-lines") {
30849
31216
  const { parseBoxesAndLines: parseBoxesAndLines2 } = await Promise.resolve().then(() => (init_parser10(), parser_exports10));
@@ -30869,7 +31236,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
30869
31236
  activeTagGroup: options?.tagGroup
30870
31237
  }
30871
31238
  );
30872
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31239
+ return finalizeSvgExport(container2, theme, effectivePalette2);
30873
31240
  }
30874
31241
  if (detectedType === "c4") {
30875
31242
  const { parseC4: parseC42 } = await Promise.resolve().then(() => (init_parser6(), parser_exports6));
@@ -30908,7 +31275,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
30908
31275
  options?.tagGroup
30909
31276
  )
30910
31277
  );
30911
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31278
+ return finalizeSvgExport(container2, theme, effectivePalette2);
30912
31279
  }
30913
31280
  if (detectedType === "flowchart") {
30914
31281
  const { parseFlowchart: parseFlowchart2 } = await Promise.resolve().then(() => (init_flowchart_parser(), flowchart_parser_exports));
@@ -30928,7 +31295,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
30928
31295
  void 0,
30929
31296
  { width: EXPORT_WIDTH, height: EXPORT_HEIGHT }
30930
31297
  );
30931
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31298
+ return finalizeSvgExport(container2, theme, effectivePalette2);
30932
31299
  }
30933
31300
  if (detectedType === "infra") {
30934
31301
  const { parseInfra: parseInfra2 } = await Promise.resolve().then(() => (init_parser8(), parser_exports8));
@@ -30974,7 +31341,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
30974
31341
  infraSvg.setAttribute("width", String(exportWidth));
30975
31342
  infraSvg.setAttribute("height", String(exportHeight));
30976
31343
  }
30977
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31344
+ return finalizeSvgExport(container2, theme, effectivePalette2);
30978
31345
  }
30979
31346
  if (detectedType === "gantt") {
30980
31347
  const { parseGantt: parseGantt2 } = await Promise.resolve().then(() => (init_parser9(), parser_exports9));
@@ -30995,7 +31362,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
30995
31362
  void 0,
30996
31363
  { width: EXPORT_W, height: EXPORT_H }
30997
31364
  );
30998
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31365
+ return finalizeSvgExport(container2, theme, effectivePalette2);
30999
31366
  }
31000
31367
  if (detectedType === "state") {
31001
31368
  const { parseState: parseState2 } = await Promise.resolve().then(() => (init_state_parser(), state_parser_exports));
@@ -31015,7 +31382,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
31015
31382
  void 0,
31016
31383
  { width: EXPORT_WIDTH, height: EXPORT_HEIGHT }
31017
31384
  );
31018
- return finalizeSvgExport(container2, theme, effectivePalette2, options);
31385
+ return finalizeSvgExport(container2, theme, effectivePalette2);
31019
31386
  }
31020
31387
  const parsed = parseVisualization(content, palette);
31021
31388
  if (parsed.error && parsed.type !== "sequence") {
@@ -31107,9 +31474,9 @@ async function renderForExport(content, theme, palette, orgExportState, options)
31107
31474
  dims
31108
31475
  );
31109
31476
  }
31110
- return finalizeSvgExport(container, theme, effectivePalette, options);
31477
+ return finalizeSvgExport(container, theme, effectivePalette);
31111
31478
  }
31112
- var d3Scale2, d3Selection13, d3Shape9, d3Array, import_d3_cloud, DEFAULT_CLOUD_OPTIONS, STOP_WORDS, SLOPE_MARGIN, SLOPE_LABEL_FONT_SIZE, SLOPE_CHAR_WIDTH, ARC_MARGIN, MONTH_ABBR2, EXPORT_WIDTH, EXPORT_HEIGHT;
31479
+ var d3Scale2, d3Selection13, d3Shape9, d3Array, import_d3_cloud, DEFAULT_CLOUD_OPTIONS, STOP_WORDS, SLOPE_MARGIN, SLOPE_LABEL_FONT_SIZE, SLOPE_CHAR_WIDTH, ARC_MARGIN, EXPORT_WIDTH, EXPORT_HEIGHT;
31113
31480
  var init_d3 = __esm({
31114
31481
  "src/d3.ts"() {
31115
31482
  "use strict";
@@ -31119,8 +31486,8 @@ var init_d3 = __esm({
31119
31486
  d3Array = __toESM(require("d3-array"), 1);
31120
31487
  import_d3_cloud = __toESM(require("d3-cloud"), 1);
31121
31488
  init_fonts();
31122
- init_branding();
31123
31489
  init_label_layout();
31490
+ init_time_ticks();
31124
31491
  init_colors();
31125
31492
  init_palettes();
31126
31493
  init_color_utils();
@@ -31251,20 +31618,6 @@ var init_d3 = __esm({
31251
31618
  SLOPE_LABEL_FONT_SIZE = 14;
31252
31619
  SLOPE_CHAR_WIDTH = 8;
31253
31620
  ARC_MARGIN = { top: 60, right: 40, bottom: 60, left: 40 };
31254
- MONTH_ABBR2 = [
31255
- "Jan",
31256
- "Feb",
31257
- "Mar",
31258
- "Apr",
31259
- "May",
31260
- "Jun",
31261
- "Jul",
31262
- "Aug",
31263
- "Sep",
31264
- "Oct",
31265
- "Nov",
31266
- "Dec"
31267
- ];
31268
31621
  EXPORT_WIDTH = 1200;
31269
31622
  EXPORT_HEIGHT = 800;
31270
31623
  }
@@ -31731,6 +32084,7 @@ var require_lz_string = __commonJS({
31731
32084
  var index_exports = {};
31732
32085
  __export(index_exports, {
31733
32086
  ALL_CHART_TYPES: () => ALL_CHART_TYPES,
32087
+ ARROW_DIAGNOSTIC_CODES: () => ARROW_DIAGNOSTIC_CODES,
31734
32088
  CHART_TYPES: () => CHART_TYPES,
31735
32089
  COMPLETION_REGISTRY: () => COMPLETION_REGISTRY,
31736
32090
  ENTITY_TYPES: () => ENTITY_TYPES,
@@ -31741,17 +32095,15 @@ __export(index_exports, {
31741
32095
  RECOGNIZED_COLOR_NAMES: () => RECOGNIZED_COLOR_NAMES,
31742
32096
  RULE_COUNT: () => RULE_COUNT,
31743
32097
  addDurationToDate: () => addDurationToDate,
32098
+ applyCollapseProjection: () => applyCollapseProjection,
31744
32099
  applyGroupOrdering: () => applyGroupOrdering,
31745
32100
  applyPositionOverrides: () => applyPositionOverrides,
31746
32101
  boldPalette: () => boldPalette,
31747
32102
  buildExtendedChartOption: () => buildExtendedChartOption,
31748
- buildMermaidQuadrant: () => buildMermaidQuadrant,
31749
- buildMermaidThemeVars: () => buildMermaidThemeVars,
31750
32103
  buildNoteMessageMap: () => buildNoteMessageMap,
31751
32104
  buildRenderSequence: () => buildRenderSequence,
31752
32105
  buildSimpleChartOption: () => buildSimpleChartOption,
31753
32106
  buildTagLaneRowList: () => buildTagLaneRowList,
31754
- buildThemeCSS: () => buildThemeCSS,
31755
32107
  calculateSchedule: () => calculateSchedule,
31756
32108
  catppuccinPalette: () => catppuccinPalette,
31757
32109
  collapseBoxesAndLines: () => collapseBoxesAndLines,
@@ -31770,8 +32122,10 @@ __export(index_exports, {
31770
32122
  computeTimeTicks: () => computeTimeTicks,
31771
32123
  contrastText: () => contrastText,
31772
32124
  decodeDiagramUrl: () => decodeDiagramUrl,
32125
+ decodeViewState: () => decodeViewState,
31773
32126
  draculaPalette: () => draculaPalette,
31774
32127
  encodeDiagramUrl: () => encodeDiagramUrl,
32128
+ encodeViewState: () => encodeViewState,
31775
32129
  extractDiagramSymbols: () => extractDiagramSymbols,
31776
32130
  extractTagDeclarations: () => extractTagDeclarations,
31777
32131
  formatDateLabel: () => formatDateLabel,
@@ -31790,7 +32144,6 @@ __export(index_exports, {
31790
32144
  hslToHex: () => hslToHex,
31791
32145
  inferParticipantType: () => inferParticipantType,
31792
32146
  inferRoles: () => inferRoles,
31793
- injectBranding: () => injectBranding,
31794
32147
  isArchiveColumn: () => isArchiveColumn,
31795
32148
  isExtendedChartType: () => isExtendedChartType,
31796
32149
  isRecognizedColorName: () => isRecognizedColorName,
@@ -31815,8 +32168,8 @@ __export(index_exports, {
31815
32168
  looksLikeSitemap: () => looksLikeSitemap,
31816
32169
  looksLikeState: () => looksLikeState,
31817
32170
  makeDgmoError: () => makeDgmoError,
32171
+ matchColorParens: () => matchColorParens,
31818
32172
  monokaiPalette: () => monokaiPalette,
31819
- mute: () => mute,
31820
32173
  nord: () => nord,
31821
32174
  nordPalette: () => nordPalette,
31822
32175
  oneDarkPalette: () => oneDarkPalette,
@@ -31834,11 +32187,11 @@ __export(index_exports, {
31834
32187
  parseFirstLine: () => parseFirstLine,
31835
32188
  parseFlowchart: () => parseFlowchart,
31836
32189
  parseGantt: () => parseGantt,
32190
+ parseInArrowLabel: () => parseInArrowLabel,
31837
32191
  parseInfra: () => parseInfra,
31838
32192
  parseInlineMarkdown: () => parseInlineMarkdown,
31839
32193
  parseKanban: () => parseKanban,
31840
32194
  parseOrg: () => parseOrg,
31841
- parseQuadrant: () => parseQuadrant,
31842
32195
  parseSequenceDgmo: () => parseSequenceDgmo,
31843
32196
  parseSitemap: () => parseSitemap,
31844
32197
  parseState: () => parseState,
@@ -31897,10 +32250,12 @@ __export(index_exports, {
31897
32250
  tokyoNightPalette: () => tokyoNightPalette,
31898
32251
  truncateBareUrl: () => truncateBareUrl,
31899
32252
  validateComputed: () => validateComputed,
31900
- validateInfra: () => validateInfra
32253
+ validateInfra: () => validateInfra,
32254
+ validateLabelCharacters: () => validateLabelCharacters
31901
32255
  });
31902
32256
  module.exports = __toCommonJS(index_exports);
31903
32257
  init_diagnostics();
32258
+ init_arrows();
31904
32259
 
31905
32260
  // src/render.ts
31906
32261
  init_d3();
@@ -31936,8 +32291,8 @@ async function ensureDom() {
31936
32291
  async function render(content, options) {
31937
32292
  const theme = options?.theme ?? "light";
31938
32293
  const paletteName = options?.palette ?? "nord";
31939
- const branding = options?.branding ?? false;
31940
32294
  const paletteColors = getPalette(paletteName)[theme === "dark" ? "dark" : "light"];
32295
+ const { diagnostics } = parseDgmo(content);
31941
32296
  const chartType = parseDgmoChartType(content);
31942
32297
  const category = chartType ? getRenderCategory(chartType) : null;
31943
32298
  const legendExportState = options?.legendState ? {
@@ -31945,18 +32300,27 @@ async function render(content, options) {
31945
32300
  hiddenAttributes: options.legendState.hiddenAttributes ? new Set(options.legendState.hiddenAttributes) : void 0
31946
32301
  } : void 0;
31947
32302
  if (category === "data-chart") {
31948
- return renderExtendedChartForExport(content, theme, paletteColors, {
31949
- branding
31950
- });
32303
+ const svg2 = await renderExtendedChartForExport(
32304
+ content,
32305
+ theme,
32306
+ paletteColors
32307
+ );
32308
+ return { svg: svg2, diagnostics };
31951
32309
  }
31952
32310
  await ensureDom();
31953
- return renderForExport(content, theme, paletteColors, legendExportState, {
31954
- branding,
31955
- c4Level: options?.c4Level,
31956
- c4System: options?.c4System,
31957
- c4Container: options?.c4Container,
31958
- tagGroup: options?.tagGroup
31959
- });
32311
+ const svg = await renderForExport(
32312
+ content,
32313
+ theme,
32314
+ paletteColors,
32315
+ legendExportState,
32316
+ {
32317
+ c4Level: options?.c4Level,
32318
+ c4System: options?.c4System,
32319
+ c4Container: options?.c4Container,
32320
+ tagGroup: options?.tagGroup
32321
+ }
32322
+ );
32323
+ return { svg, diagnostics };
31960
32324
  }
31961
32325
 
31962
32326
  // src/index.ts
@@ -31964,172 +32328,9 @@ init_dgmo_router();
31964
32328
  init_chart();
31965
32329
  init_echarts();
31966
32330
  init_d3();
32331
+ init_time_ticks();
31967
32332
  init_parser();
31968
32333
  init_participant_inference();
31969
-
31970
- // src/dgmo-mermaid.ts
31971
- init_colors();
31972
- init_diagnostics();
31973
- var QUADRANT_LABEL_RE = /^(.+?)(?:\s*\(([^)]+)\))?\s*$/;
31974
- var DATA_POINT_RE = /^(.+?)\s+([0-9]*\.?[0-9]+)\s*,\s*([0-9]*\.?[0-9]+)\s*$/;
31975
- var QUADRANT_POSITIONS = /* @__PURE__ */ new Set([
31976
- "top-right",
31977
- "top-left",
31978
- "bottom-left",
31979
- "bottom-right"
31980
- ]);
31981
- function parseQuadrant(content) {
31982
- const result = {
31983
- title: null,
31984
- titleLineNumber: null,
31985
- xAxis: null,
31986
- xAxisLineNumber: null,
31987
- yAxis: null,
31988
- yAxisLineNumber: null,
31989
- quadrants: {
31990
- topRight: null,
31991
- topLeft: null,
31992
- bottomLeft: null,
31993
- bottomRight: null
31994
- },
31995
- points: [],
31996
- diagnostics: [],
31997
- error: null
31998
- };
31999
- const lines = content.split("\n");
32000
- for (let i = 0; i < lines.length; i++) {
32001
- const line10 = lines[i].trim();
32002
- const lineNumber = i + 1;
32003
- if (!line10 || line10.startsWith("//")) continue;
32004
- if (/^chart\s*:/i.test(line10)) continue;
32005
- const titleMatch = line10.match(/^title\s+(.+)/i);
32006
- if (titleMatch) {
32007
- result.title = titleMatch[1].trim();
32008
- result.titleLineNumber = lineNumber;
32009
- continue;
32010
- }
32011
- const xMatch = line10.match(/^x-label\s+(.+)/i);
32012
- if (xMatch) {
32013
- const parts = xMatch[1].split(",").map((s) => s.trim());
32014
- if (parts.length >= 2) {
32015
- result.xAxis = [parts[0], parts[1]];
32016
- result.xAxisLineNumber = lineNumber;
32017
- }
32018
- continue;
32019
- }
32020
- const yMatch = line10.match(/^y-label\s+(.+)/i);
32021
- if (yMatch) {
32022
- const parts = yMatch[1].split(",").map((s) => s.trim());
32023
- if (parts.length >= 2) {
32024
- result.yAxis = [parts[0], parts[1]];
32025
- result.yAxisLineNumber = lineNumber;
32026
- }
32027
- continue;
32028
- }
32029
- const posMatch = line10.match(
32030
- /^(top-right|top-left|bottom-left|bottom-right)\s+(.+)/i
32031
- );
32032
- if (posMatch) {
32033
- const position = posMatch[1].toLowerCase();
32034
- const labelMatch = posMatch[2].match(QUADRANT_LABEL_RE);
32035
- if (labelMatch) {
32036
- const label = {
32037
- text: labelMatch[1].trim(),
32038
- color: labelMatch[2] ? resolveColorWithDiagnostic(
32039
- labelMatch[2].trim(),
32040
- lineNumber,
32041
- result.diagnostics
32042
- ) ?? null : null,
32043
- lineNumber
32044
- };
32045
- if (position === "top-right") result.quadrants.topRight = label;
32046
- else if (position === "top-left") result.quadrants.topLeft = label;
32047
- else if (position === "bottom-left")
32048
- result.quadrants.bottomLeft = label;
32049
- else if (position === "bottom-right")
32050
- result.quadrants.bottomRight = label;
32051
- }
32052
- continue;
32053
- }
32054
- const pointMatch = line10.match(DATA_POINT_RE);
32055
- if (pointMatch) {
32056
- const key = pointMatch[1].trim().toLowerCase();
32057
- if (!QUADRANT_POSITIONS.has(key)) {
32058
- result.points.push({
32059
- label: pointMatch[1].trim(),
32060
- x: parseFloat(pointMatch[2]),
32061
- y: parseFloat(pointMatch[3]),
32062
- lineNumber
32063
- });
32064
- }
32065
- continue;
32066
- }
32067
- }
32068
- if (result.points.length === 0) {
32069
- const diag = makeDgmoError(
32070
- 1,
32071
- "No data points found. Add lines like: Label 0.5, 0.7"
32072
- );
32073
- result.diagnostics.push(diag);
32074
- result.error = formatDgmoError(diag);
32075
- }
32076
- return result;
32077
- }
32078
- function buildMermaidQuadrant(parsed, options = {}) {
32079
- const { isDark = false, textColor, mutedTextColor } = options;
32080
- const lines = [];
32081
- const fillAlpha = isDark ? "30" : "55";
32082
- const primaryText = textColor ?? (isDark ? "#d0d0d0" : "#333333");
32083
- const quadrantLabelText = mutedTextColor ?? (isDark ? "#888888" : "#666666");
32084
- const colorMap = {};
32085
- if (parsed.quadrants.topRight?.color)
32086
- colorMap.quadrant1Fill = parsed.quadrants.topRight.color + fillAlpha;
32087
- if (parsed.quadrants.topLeft?.color)
32088
- colorMap.quadrant2Fill = parsed.quadrants.topLeft.color + fillAlpha;
32089
- if (parsed.quadrants.bottomLeft?.color)
32090
- colorMap.quadrant3Fill = parsed.quadrants.bottomLeft.color + fillAlpha;
32091
- if (parsed.quadrants.bottomRight?.color)
32092
- colorMap.quadrant4Fill = parsed.quadrants.bottomRight.color + fillAlpha;
32093
- colorMap.quadrant1TextFill = quadrantLabelText;
32094
- colorMap.quadrant2TextFill = quadrantLabelText;
32095
- colorMap.quadrant3TextFill = quadrantLabelText;
32096
- colorMap.quadrant4TextFill = quadrantLabelText;
32097
- colorMap.quadrantPointTextFill = primaryText;
32098
- colorMap.quadrantXAxisTextFill = primaryText;
32099
- colorMap.quadrantYAxisTextFill = primaryText;
32100
- colorMap.quadrantTitleFill = primaryText;
32101
- const vars = JSON.stringify(colorMap);
32102
- lines.push(`%%{init: {"themeVariables": ${vars}}}%%`);
32103
- lines.push("quadrantChart");
32104
- if (parsed.title) {
32105
- lines.push(` title ${parsed.title}`);
32106
- }
32107
- if (parsed.xAxis) {
32108
- lines.push(` x-axis ${parsed.xAxis[0]} --> ${parsed.xAxis[1]}`);
32109
- }
32110
- if (parsed.yAxis) {
32111
- lines.push(` y-axis ${parsed.yAxis[0]} --> ${parsed.yAxis[1]}`);
32112
- }
32113
- const quote = (s) => /[\s,:[\]]/.test(s) ? `"${s}"` : s;
32114
- if (parsed.quadrants.topRight) {
32115
- lines.push(` quadrant-1 ${quote(parsed.quadrants.topRight.text)}`);
32116
- }
32117
- if (parsed.quadrants.topLeft) {
32118
- lines.push(` quadrant-2 ${quote(parsed.quadrants.topLeft.text)}`);
32119
- }
32120
- if (parsed.quadrants.bottomLeft) {
32121
- lines.push(` quadrant-3 ${quote(parsed.quadrants.bottomLeft.text)}`);
32122
- }
32123
- if (parsed.quadrants.bottomRight) {
32124
- lines.push(` quadrant-4 ${quote(parsed.quadrants.bottomRight.text)}`);
32125
- }
32126
- for (const point of parsed.points) {
32127
- lines.push(` ${quote(point.label)}: [${point.x}, ${point.y}]`);
32128
- }
32129
- return lines.join("\n");
32130
- }
32131
-
32132
- // src/index.ts
32133
32334
  init_flowchart_parser();
32134
32335
  init_state_parser();
32135
32336
  init_state_renderer();
@@ -32681,6 +32882,7 @@ init_legend_d3();
32681
32882
  init_legend_layout();
32682
32883
  init_d3();
32683
32884
  init_renderer10();
32885
+ init_collapse3();
32684
32886
  init_colors();
32685
32887
  init_palettes();
32686
32888
 
@@ -32688,6 +32890,24 @@ init_palettes();
32688
32890
  var import_lz_string = __toESM(require_lz_string(), 1);
32689
32891
  var DEFAULT_BASE_URL = "https://online.diagrammo.app";
32690
32892
  var COMPRESSED_SIZE_LIMIT = 8192;
32893
+ function encodeViewState(state) {
32894
+ const keys = Object.keys(state);
32895
+ if (keys.length === 0) return "";
32896
+ return (0, import_lz_string.compressToEncodedURIComponent)(JSON.stringify(state));
32897
+ }
32898
+ function decodeViewState(encoded) {
32899
+ if (!encoded) return {};
32900
+ try {
32901
+ const json = (0, import_lz_string.decompressFromEncodedURIComponent)(encoded);
32902
+ if (!json) return {};
32903
+ const parsed = JSON.parse(json);
32904
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
32905
+ return {};
32906
+ return parsed;
32907
+ } catch {
32908
+ return {};
32909
+ }
32910
+ }
32691
32911
  function encodeDiagramUrl(dsl, options) {
32692
32912
  const baseUrl = options?.baseUrl ?? DEFAULT_BASE_URL;
32693
32913
  const compressed = (0, import_lz_string.compressToEncodedURIComponent)(dsl);
@@ -32700,23 +32920,17 @@ function encodeDiagramUrl(dsl, options) {
32700
32920
  };
32701
32921
  }
32702
32922
  let hash = `dgmo=${compressed}`;
32703
- if (options?.viewState?.activeTagGroup) {
32704
- hash += `&tag=${encodeURIComponent(options.viewState.activeTagGroup)}`;
32705
- }
32706
- if (options?.viewState?.collapsedGroups?.length) {
32707
- hash += `&cg=${encodeURIComponent(options.viewState.collapsedGroups.join(","))}`;
32708
- }
32709
- if (options?.viewState?.swimlaneTagGroup) {
32710
- hash += `&swim=${encodeURIComponent(options.viewState.swimlaneTagGroup)}`;
32711
- }
32712
- if (options?.viewState?.collapsedLanes?.length) {
32713
- hash += `&cl=${encodeURIComponent(options.viewState.collapsedLanes.join(","))}`;
32923
+ if (options?.viewState) {
32924
+ const vsEncoded = encodeViewState(options.viewState);
32925
+ if (vsEncoded) {
32926
+ hash += `&vs=${vsEncoded}`;
32927
+ }
32714
32928
  }
32715
- if (options?.viewState?.palette && options.viewState.palette !== "nord") {
32716
- hash += `&pal=${encodeURIComponent(options.viewState.palette)}`;
32929
+ if (options?.palette && options.palette !== "nord") {
32930
+ hash += `&pal=${encodeURIComponent(options.palette)}`;
32717
32931
  }
32718
- if (options?.viewState?.theme && options.viewState.theme !== "dark") {
32719
- hash += `&th=${encodeURIComponent(options.viewState.theme)}`;
32932
+ if (options?.theme && options.theme !== "dark") {
32933
+ hash += `&th=${encodeURIComponent(options.theme)}`;
32720
32934
  }
32721
32935
  if (options?.filename) {
32722
32936
  hash += `&fn=${encodeURIComponent(options.filename)}`;
@@ -32726,6 +32940,8 @@ function encodeDiagramUrl(dsl, options) {
32726
32940
  function decodeDiagramUrl(hash) {
32727
32941
  const empty = { dsl: "", viewState: {} };
32728
32942
  let filename;
32943
+ let palette;
32944
+ let theme;
32729
32945
  if (!hash) return empty;
32730
32946
  let raw = hash;
32731
32947
  if (raw.startsWith("#") || raw.startsWith("?")) {
@@ -32733,38 +32949,31 @@ function decodeDiagramUrl(hash) {
32733
32949
  }
32734
32950
  const parts = raw.split("&");
32735
32951
  let payload = parts[0];
32736
- const viewState = {};
32952
+ let viewState = {};
32737
32953
  for (let i = 1; i < parts.length; i++) {
32738
32954
  const eq = parts[i].indexOf("=");
32739
32955
  if (eq === -1) continue;
32740
32956
  const key = parts[i].slice(0, eq);
32741
- const val = decodeURIComponent(parts[i].slice(eq + 1));
32742
- if (key === "tag" && val) {
32743
- viewState.activeTagGroup = val;
32744
- }
32745
- if (key === "cg" && val) {
32746
- viewState.collapsedGroups = val.split(",").filter(Boolean);
32747
- }
32748
- if (key === "swim" && val) {
32749
- viewState.swimlaneTagGroup = val;
32957
+ const val = parts[i].slice(eq + 1);
32958
+ if (key === "vs" && val) {
32959
+ viewState = decodeViewState(val);
32750
32960
  }
32751
- if (key === "cl" && val) {
32752
- viewState.collapsedLanes = val.split(",").filter(Boolean);
32961
+ if (key === "pal" && val) palette = decodeURIComponent(val);
32962
+ if (key === "th") {
32963
+ const decoded = decodeURIComponent(val);
32964
+ if (decoded === "light" || decoded === "dark") theme = decoded;
32753
32965
  }
32754
- if (key === "pal" && val) viewState.palette = val;
32755
- if (key === "th" && (val === "light" || val === "dark"))
32756
- viewState.theme = val;
32757
- if (key === "fn" && val) filename = val;
32966
+ if (key === "fn" && val) filename = decodeURIComponent(val);
32758
32967
  }
32759
32968
  if (payload.startsWith("dgmo=")) {
32760
32969
  payload = payload.slice(5);
32761
32970
  }
32762
- if (!payload) return { dsl: "", viewState, filename };
32971
+ if (!payload) return { dsl: "", viewState, palette, theme, filename };
32763
32972
  try {
32764
32973
  const result = (0, import_lz_string.decompressFromEncodedURIComponent)(payload);
32765
- return { dsl: result ?? "", viewState, filename };
32974
+ return { dsl: result ?? "", viewState, palette, theme, filename };
32766
32975
  } catch {
32767
- return { dsl: "", viewState, filename };
32976
+ return { dsl: "", viewState, palette, theme, filename };
32768
32977
  }
32769
32978
  }
32770
32979
 
@@ -33511,10 +33720,10 @@ registerExtractor("boxes-and-lines", extractBoxesAndLinesSymbols);
33511
33720
 
33512
33721
  // src/index.ts
33513
33722
  init_parsing();
33514
- init_branding();
33515
33723
  // Annotate the CommonJS export names for ESM import in node:
33516
33724
  0 && (module.exports = {
33517
33725
  ALL_CHART_TYPES,
33726
+ ARROW_DIAGNOSTIC_CODES,
33518
33727
  CHART_TYPES,
33519
33728
  COMPLETION_REGISTRY,
33520
33729
  ENTITY_TYPES,
@@ -33525,17 +33734,15 @@ init_branding();
33525
33734
  RECOGNIZED_COLOR_NAMES,
33526
33735
  RULE_COUNT,
33527
33736
  addDurationToDate,
33737
+ applyCollapseProjection,
33528
33738
  applyGroupOrdering,
33529
33739
  applyPositionOverrides,
33530
33740
  boldPalette,
33531
33741
  buildExtendedChartOption,
33532
- buildMermaidQuadrant,
33533
- buildMermaidThemeVars,
33534
33742
  buildNoteMessageMap,
33535
33743
  buildRenderSequence,
33536
33744
  buildSimpleChartOption,
33537
33745
  buildTagLaneRowList,
33538
- buildThemeCSS,
33539
33746
  calculateSchedule,
33540
33747
  catppuccinPalette,
33541
33748
  collapseBoxesAndLines,
@@ -33554,8 +33761,10 @@ init_branding();
33554
33761
  computeTimeTicks,
33555
33762
  contrastText,
33556
33763
  decodeDiagramUrl,
33764
+ decodeViewState,
33557
33765
  draculaPalette,
33558
33766
  encodeDiagramUrl,
33767
+ encodeViewState,
33559
33768
  extractDiagramSymbols,
33560
33769
  extractTagDeclarations,
33561
33770
  formatDateLabel,
@@ -33574,7 +33783,6 @@ init_branding();
33574
33783
  hslToHex,
33575
33784
  inferParticipantType,
33576
33785
  inferRoles,
33577
- injectBranding,
33578
33786
  isArchiveColumn,
33579
33787
  isExtendedChartType,
33580
33788
  isRecognizedColorName,
@@ -33599,8 +33807,8 @@ init_branding();
33599
33807
  looksLikeSitemap,
33600
33808
  looksLikeState,
33601
33809
  makeDgmoError,
33810
+ matchColorParens,
33602
33811
  monokaiPalette,
33603
- mute,
33604
33812
  nord,
33605
33813
  nordPalette,
33606
33814
  oneDarkPalette,
@@ -33618,11 +33826,11 @@ init_branding();
33618
33826
  parseFirstLine,
33619
33827
  parseFlowchart,
33620
33828
  parseGantt,
33829
+ parseInArrowLabel,
33621
33830
  parseInfra,
33622
33831
  parseInlineMarkdown,
33623
33832
  parseKanban,
33624
33833
  parseOrg,
33625
- parseQuadrant,
33626
33834
  parseSequenceDgmo,
33627
33835
  parseSitemap,
33628
33836
  parseState,
@@ -33681,6 +33889,7 @@ init_branding();
33681
33889
  tokyoNightPalette,
33682
33890
  truncateBareUrl,
33683
33891
  validateComputed,
33684
- validateInfra
33892
+ validateInfra,
33893
+ validateLabelCharacters
33685
33894
  });
33686
33895
  //# sourceMappingURL=index.cjs.map