@diagrammo/dgmo 0.8.17 → 0.8.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -8,6 +8,9 @@ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
8
  var __esm = (fn, res) => function __init() {
9
9
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
10
  };
11
+ var __commonJS = (cb, mod) => function __require() {
12
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
13
+ };
11
14
  var __export = (target, all) => {
12
15
  for (var name in all)
13
16
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -2099,7 +2102,7 @@ function measureLegendText(text, fontSize) {
2099
2102
  }
2100
2103
  return w;
2101
2104
  }
2102
- 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;
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, CONTROLS_ICON_PATH, LEGEND_TOGGLE_DOT_R, LEGEND_TOGGLE_OFF_OPACITY, LEGEND_GEAR_PILL_W;
2103
2106
  var init_legend_constants = __esm({
2104
2107
  "src/utils/legend-constants.ts"() {
2105
2108
  "use strict";
@@ -2206,6 +2209,10 @@ var init_legend_constants = __esm({
2206
2209
  DEFAULT_W = 0.56;
2207
2210
  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";
2208
2211
  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";
2212
+ 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";
2213
+ LEGEND_TOGGLE_DOT_R = LEGEND_DOT_R;
2214
+ LEGEND_TOGGLE_OFF_OPACITY = 0.4;
2215
+ LEGEND_GEAR_PILL_W = 14 + LEGEND_PILL_PAD;
2209
2216
  }
2210
2217
  });
2211
2218
 
@@ -2251,13 +2258,13 @@ function capsuleWidth(name, entries, containerWidth, addonWidth = 0) {
2251
2258
  visibleEntries: entries.length
2252
2259
  };
2253
2260
  }
2254
- const rowWidth = maxCapsuleW - LEGEND_CAPSULE_PAD * 2;
2261
+ const rowWidth = maxCapsuleW - LEGEND_CAPSULE_PAD;
2255
2262
  let row = 1;
2256
- let rowX = pw + 4;
2263
+ let rowX = LEGEND_CAPSULE_PAD + pw + 4 + addonWidth;
2257
2264
  let visible = 0;
2258
2265
  for (let i = 0; i < entries.length; i++) {
2259
2266
  const ew2 = entryWidth(entries[i].value);
2260
- if (rowX + ew2 > rowWidth && rowX > pw + 4) {
2267
+ if (rowX + ew2 > rowWidth && i > 0) {
2261
2268
  row++;
2262
2269
  rowX = 0;
2263
2270
  if (row > LEGEND_MAX_ENTRY_ROWS) {
@@ -2279,6 +2286,63 @@ function capsuleWidth(name, entries, containerWidth, addonWidth = 0) {
2279
2286
  visibleEntries: entries.length
2280
2287
  };
2281
2288
  }
2289
+ function controlsGroupCapsuleWidth(toggles) {
2290
+ let w = LEGEND_CAPSULE_PAD * 2 + LEGEND_GEAR_PILL_W + 4;
2291
+ for (const t of toggles) {
2292
+ w += LEGEND_TOGGLE_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + measureLegendText(t.label, LEGEND_ENTRY_FONT_SIZE) + LEGEND_ENTRY_TRAIL;
2293
+ }
2294
+ return w;
2295
+ }
2296
+ function buildControlsGroupLayout(config, state) {
2297
+ const cg = config.controlsGroup;
2298
+ if (!cg || cg.toggles.length === 0) return void 0;
2299
+ const expanded = !!state.controlsExpanded;
2300
+ const pillH = LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2;
2301
+ if (!expanded) {
2302
+ return {
2303
+ x: 0,
2304
+ y: 0,
2305
+ width: LEGEND_GEAR_PILL_W,
2306
+ height: LEGEND_HEIGHT,
2307
+ expanded: false,
2308
+ pill: { x: 0, y: 0, width: LEGEND_GEAR_PILL_W, height: LEGEND_HEIGHT },
2309
+ toggles: []
2310
+ };
2311
+ }
2312
+ const capsuleW = controlsGroupCapsuleWidth(cg.toggles);
2313
+ const toggleLayouts = [];
2314
+ let tx = LEGEND_CAPSULE_PAD + LEGEND_GEAR_PILL_W + 4;
2315
+ for (const toggle of cg.toggles) {
2316
+ const dotCx = tx + LEGEND_TOGGLE_DOT_R;
2317
+ const dotCy = LEGEND_HEIGHT / 2;
2318
+ const textX = tx + LEGEND_TOGGLE_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP;
2319
+ const textY = LEGEND_HEIGHT / 2;
2320
+ toggleLayouts.push({
2321
+ id: toggle.id,
2322
+ label: toggle.label,
2323
+ active: toggle.active,
2324
+ dotCx,
2325
+ dotCy,
2326
+ textX,
2327
+ textY
2328
+ });
2329
+ tx += LEGEND_TOGGLE_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + measureLegendText(toggle.label, LEGEND_ENTRY_FONT_SIZE) + LEGEND_ENTRY_TRAIL;
2330
+ }
2331
+ return {
2332
+ x: 0,
2333
+ y: 0,
2334
+ width: capsuleW,
2335
+ height: LEGEND_HEIGHT,
2336
+ expanded: true,
2337
+ pill: {
2338
+ x: LEGEND_CAPSULE_PAD,
2339
+ y: LEGEND_CAPSULE_PAD,
2340
+ width: LEGEND_GEAR_PILL_W - LEGEND_CAPSULE_PAD * 2,
2341
+ height: pillH
2342
+ },
2343
+ toggles: toggleLayouts
2344
+ };
2345
+ }
2282
2346
  function computeLegendLayout(config, state, containerWidth) {
2283
2347
  const { groups, controls: configControls, mode } = config;
2284
2348
  const isExport = mode === "inline";
@@ -2293,8 +2357,9 @@ function computeLegendLayout(config, state, containerWidth) {
2293
2357
  activeCapsule: void 0
2294
2358
  };
2295
2359
  }
2360
+ const controlsGroupLayout = isExport ? void 0 : buildControlsGroupLayout(config, state);
2296
2361
  const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0);
2297
- if (visibleGroups.length === 0 && (!configControls || configControls.length === 0)) {
2362
+ if (visibleGroups.length === 0 && (!configControls || configControls.length === 0) && !controlsGroupLayout) {
2298
2363
  return {
2299
2364
  height: 0,
2300
2365
  width: 0,
@@ -2358,7 +2423,8 @@ function computeLegendLayout(config, state, containerWidth) {
2358
2423
  if (totalControlsW > 0) totalControlsW -= CONTROL_GAP;
2359
2424
  }
2360
2425
  const controlsSpace = totalControlsW > 0 ? totalControlsW + LEGEND_GROUP_GAP * 2 : 0;
2361
- const groupAvailW = containerWidth - controlsSpace;
2426
+ const gearSpace = controlsGroupLayout ? controlsGroupLayout.width + LEGEND_GROUP_GAP : 0;
2427
+ const groupAvailW = containerWidth - controlsSpace - gearSpace;
2362
2428
  const pills = [];
2363
2429
  let activeCapsule;
2364
2430
  for (const g of visibleGroups) {
@@ -2367,7 +2433,7 @@ function computeLegendLayout(config, state, containerWidth) {
2367
2433
  if (isActive) {
2368
2434
  activeCapsule = buildCapsuleLayout(
2369
2435
  g,
2370
- containerWidth,
2436
+ groupAvailW,
2371
2437
  config.capsulePillAddonWidth ?? 0
2372
2438
  );
2373
2439
  } else {
@@ -2390,7 +2456,8 @@ function computeLegendLayout(config, state, containerWidth) {
2390
2456
  groupAvailW,
2391
2457
  containerWidth,
2392
2458
  totalControlsW,
2393
- alignLeft
2459
+ alignLeft,
2460
+ controlsGroupLayout
2394
2461
  );
2395
2462
  const height = rows.length * LEGEND_HEIGHT;
2396
2463
  const width = containerWidth;
@@ -2400,7 +2467,8 @@ function computeLegendLayout(config, state, containerWidth) {
2400
2467
  rows,
2401
2468
  activeCapsule,
2402
2469
  controls: controlLayouts,
2403
- pills
2470
+ pills,
2471
+ controlsGroup: controlsGroupLayout
2404
2472
  };
2405
2473
  }
2406
2474
  function buildCapsuleLayout(group, containerWidth, addonWidth = 0) {
@@ -2423,7 +2491,7 @@ function buildCapsuleLayout(group, containerWidth, addonWidth = 0) {
2423
2491
  let ex = LEGEND_CAPSULE_PAD + pw + 4 + addonWidth;
2424
2492
  let ey = 0;
2425
2493
  let rowX = ex;
2426
- const maxRowW = containerWidth - LEGEND_CAPSULE_PAD * 2;
2494
+ const maxRowW = containerWidth - LEGEND_CAPSULE_PAD;
2427
2495
  let currentRow = 0;
2428
2496
  for (let i = 0; i < info.visibleEntries; i++) {
2429
2497
  const entry = group.entries[i];
@@ -2464,19 +2532,27 @@ function buildCapsuleLayout(group, containerWidth, addonWidth = 0) {
2464
2532
  addonX: addonWidth > 0 ? LEGEND_CAPSULE_PAD + pw + 4 : void 0
2465
2533
  };
2466
2534
  }
2467
- function layoutRows(activeCapsule, pills, controls, groupAvailW, containerWidth, totalControlsW, alignLeft = false) {
2535
+ function layoutRows(activeCapsule, pills, controls, groupAvailW, containerWidth, totalControlsW, alignLeft = false, controlsGroup) {
2468
2536
  const rows = [];
2469
2537
  const groupItems = [];
2470
2538
  if (activeCapsule) groupItems.push(activeCapsule);
2471
2539
  groupItems.push(...pills);
2540
+ const gearW = controlsGroup ? controlsGroup.width + LEGEND_GROUP_GAP : 0;
2472
2541
  let currentRowItems = [];
2473
2542
  let currentRowW = 0;
2474
2543
  let rowY = 0;
2475
2544
  for (const item of groupItems) {
2476
2545
  const itemW = item.width + LEGEND_GROUP_GAP;
2477
2546
  if (currentRowW + item.width > groupAvailW && currentRowItems.length > 0) {
2478
- if (!alignLeft)
2479
- centerRowItems(currentRowItems, containerWidth, totalControlsW);
2547
+ if (!alignLeft) {
2548
+ const rowGearW = rows.length === 0 ? gearW : 0;
2549
+ centerRowItems(
2550
+ currentRowItems,
2551
+ containerWidth,
2552
+ totalControlsW,
2553
+ rowGearW
2554
+ );
2555
+ }
2480
2556
  rows.push({ y: rowY, items: currentRowItems });
2481
2557
  rowY += LEGEND_HEIGHT;
2482
2558
  currentRowItems = [];
@@ -2504,19 +2580,32 @@ function layoutRows(activeCapsule, pills, controls, groupAvailW, containerWidth,
2504
2580
  }
2505
2581
  }
2506
2582
  if (currentRowItems.length > 0) {
2507
- centerRowItems(currentRowItems, containerWidth, totalControlsW);
2583
+ centerRowItems(currentRowItems, containerWidth, totalControlsW, gearW);
2508
2584
  rows.push({ y: rowY, items: currentRowItems });
2509
2585
  }
2586
+ if (controlsGroup) {
2587
+ const row0Items = rows[0]?.items ?? [];
2588
+ const groupItemsInRow0 = row0Items.filter(
2589
+ (it) => "groupName" in it
2590
+ );
2591
+ if (groupItemsInRow0.length > 0) {
2592
+ const last = groupItemsInRow0[groupItemsInRow0.length - 1];
2593
+ controlsGroup.x = last.x + last.width + LEGEND_GROUP_GAP;
2594
+ } else {
2595
+ controlsGroup.x = 0;
2596
+ }
2597
+ controlsGroup.y = 0;
2598
+ }
2510
2599
  if (rows.length === 0) {
2511
2600
  rows.push({ y: 0, items: [] });
2512
2601
  }
2513
2602
  return rows;
2514
2603
  }
2515
- function centerRowItems(items, containerWidth, totalControlsW) {
2604
+ function centerRowItems(items, containerWidth, totalControlsW, controlsGroupW = 0) {
2516
2605
  const groupItems = items.filter((it) => "groupName" in it);
2517
2606
  if (groupItems.length === 0) return;
2518
2607
  const totalGroupW = groupItems.reduce((s, it) => s + it.width, 0) + (groupItems.length - 1) * LEGEND_GROUP_GAP;
2519
- const availW = containerWidth - (totalControlsW > 0 ? totalControlsW + LEGEND_GROUP_GAP * 2 : 0);
2608
+ const availW = containerWidth - (totalControlsW > 0 ? totalControlsW + LEGEND_GROUP_GAP * 2 : 0) - controlsGroupW;
2520
2609
  const offset = Math.max(0, (availW - totalGroupW) / 2);
2521
2610
  let x = offset;
2522
2611
  for (const item of groupItems) {
@@ -2574,6 +2663,17 @@ function renderLegendD3(container, config, state, palette, isDark, callbacks, co
2574
2663
  for (const pill of currentLayout.pills) {
2575
2664
  renderPill(legendG, pill, palette, groupBg, callbacks);
2576
2665
  }
2666
+ if (currentLayout.controlsGroup) {
2667
+ renderControlsGroup(
2668
+ legendG,
2669
+ currentLayout.controlsGroup,
2670
+ palette,
2671
+ groupBg,
2672
+ pillBorder,
2673
+ callbacks,
2674
+ config
2675
+ );
2676
+ }
2577
2677
  for (const ctrl of currentLayout.controls) {
2578
2678
  renderControl(
2579
2679
  legendG,
@@ -2688,6 +2788,57 @@ function renderControl(parent, ctrl, palette, _groupBg, pillBorder, _isDark, con
2688
2788
  g.on("click", () => onClick());
2689
2789
  }
2690
2790
  }
2791
+ function renderControlsGroup(parent, layout, palette, groupBg, pillBorder, callbacks, config) {
2792
+ 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");
2793
+ if (!layout.expanded) {
2794
+ g.append("rect").attr("width", layout.width).attr("height", layout.height).attr("rx", layout.height / 2).attr("fill", groupBg);
2795
+ const iconSize = 14;
2796
+ const iconX = (layout.width - iconSize) / 2;
2797
+ const iconY = (layout.height - iconSize) / 2;
2798
+ 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");
2799
+ if (callbacks?.onControlsExpand) {
2800
+ const cb = callbacks.onControlsExpand;
2801
+ g.on("click", () => cb());
2802
+ }
2803
+ } else {
2804
+ const pill = layout.pill;
2805
+ g.append("rect").attr("width", layout.width).attr("height", layout.height).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
2806
+ const pillG = g.append("g").attr("class", "controls-gear-pill").style("cursor", "pointer");
2807
+ 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);
2808
+ 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);
2809
+ const iconSize = 14;
2810
+ const iconX = pill.x + (pill.width - iconSize) / 2;
2811
+ const iconY = pill.y + (pill.height - iconSize) / 2;
2812
+ 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");
2813
+ if (callbacks?.onControlsExpand) {
2814
+ const cb = callbacks.onControlsExpand;
2815
+ pillG.on("click", (event) => {
2816
+ event.stopPropagation();
2817
+ cb();
2818
+ });
2819
+ }
2820
+ const toggles = config?.controlsGroup?.toggles ?? [];
2821
+ for (const tl of layout.toggles) {
2822
+ const toggle = toggles.find((t) => t.id === tl.id);
2823
+ const entryG = g.append("g").attr("data-controls-toggle", tl.id).style("cursor", "pointer");
2824
+ if (tl.active) {
2825
+ entryG.append("circle").attr("cx", tl.dotCx).attr("cy", tl.dotCy).attr("r", LEGEND_TOGGLE_DOT_R).attr("fill", palette.primary ?? palette.text);
2826
+ } else {
2827
+ 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);
2828
+ }
2829
+ 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);
2830
+ if (callbacks?.onControlsToggle && toggle) {
2831
+ const cb = callbacks.onControlsToggle;
2832
+ const id = tl.id;
2833
+ const newActive = !tl.active;
2834
+ entryG.on("click", (event) => {
2835
+ event.stopPropagation();
2836
+ cb(id, newActive);
2837
+ });
2838
+ }
2839
+ }
2840
+ }
2841
+ }
2691
2842
  var init_legend_d3 = __esm({
2692
2843
  "src/utils/legend-d3.ts"() {
2693
2844
  "use strict";
@@ -3241,7 +3392,13 @@ function parseSequenceDgmo(content) {
3241
3392
  const groupName = groupMatch[1].trim();
3242
3393
  const groupColor = groupMatch[2]?.trim();
3243
3394
  let groupMeta;
3244
- const afterBracket = groupMatch[3]?.trim() || "";
3395
+ let afterBracket = groupMatch[3]?.trim() || "";
3396
+ let isCollapsed = false;
3397
+ const collapseMatch = afterBracket.match(/^collapse\b/i);
3398
+ if (collapseMatch) {
3399
+ isCollapsed = true;
3400
+ afterBracket = afterBracket.slice(collapseMatch[0].length).trim();
3401
+ }
3245
3402
  if (afterBracket.startsWith("|")) {
3246
3403
  const segments = afterBracket.split("|");
3247
3404
  const meta = parsePipeMetadata(
@@ -3262,7 +3419,8 @@ function parseSequenceDgmo(content) {
3262
3419
  name: groupName,
3263
3420
  participantIds: [],
3264
3421
  lineNumber,
3265
- ...groupMeta ? { metadata: groupMeta } : {}
3422
+ ...groupMeta ? { metadata: groupMeta } : {},
3423
+ ...isCollapsed ? { collapsed: true } : {}
3266
3424
  };
3267
3425
  result.groups.push(activeGroup);
3268
3426
  continue;
@@ -10359,7 +10517,7 @@ function addBusinessDays(startDate, count, workweek, holidaySet, direction = 1)
10359
10517
  }
10360
10518
  return current;
10361
10519
  }
10362
- function addGanttDuration(startDate, duration, holidays, holidaySet, direction = 1) {
10520
+ function addGanttDuration(startDate, duration, holidays, holidaySet, direction = 1, opts) {
10363
10521
  const { amount, unit } = duration;
10364
10522
  switch (unit) {
10365
10523
  case "bd":
@@ -10421,10 +10579,22 @@ function addGanttDuration(startDate, duration, holidays, holidaySet, direction =
10421
10579
  result.setTime(result.getTime() + amount * 6e4 * direction);
10422
10580
  return result;
10423
10581
  }
10582
+ case "s": {
10583
+ if (!opts?.sprintLength) {
10584
+ throw new Error(
10585
+ 'Sprint duration unit "s" requires sprintLength configuration'
10586
+ );
10587
+ }
10588
+ const sl = opts.sprintLength;
10589
+ const totalDays = amount * sl.amount * (sl.unit === "w" ? 7 : 1);
10590
+ const result = new Date(startDate);
10591
+ result.setDate(result.getDate() + Math.round(totalDays) * direction);
10592
+ return result;
10593
+ }
10424
10594
  }
10425
10595
  }
10426
10596
  function parseDuration(s) {
10427
- const match = s.trim().match(/^(\d+(?:\.\d+)?)(min|bd|d|w|m|q|y|h)$/);
10597
+ const match = s.trim().match(/^(\d+(?:\.\d+)?)(min|bd|d|w|m|q|y|h|s)$/);
10428
10598
  if (!match) return null;
10429
10599
  return { amount: parseFloat(match[1]), unit: match[2] };
10430
10600
  }
@@ -10511,7 +10681,11 @@ function parseGantt(content, palette) {
10511
10681
  defaultSwimlaneGroup: null,
10512
10682
  activeTag: null,
10513
10683
  optionLineNumbers: {},
10514
- holidaysLineNumber: null
10684
+ holidaysLineNumber: null,
10685
+ sprintLength: null,
10686
+ sprintNumber: null,
10687
+ sprintStart: null,
10688
+ sprintMode: null
10515
10689
  },
10516
10690
  diagnostics,
10517
10691
  error: null
@@ -10927,6 +11101,55 @@ function parseGantt(content, palette) {
10927
11101
  case "active-tag":
10928
11102
  result.options.activeTag = value;
10929
11103
  break;
11104
+ case "sprint-length": {
11105
+ const dur = parseDuration(value);
11106
+ if (!dur) {
11107
+ warn(
11108
+ lineNumber,
11109
+ `Invalid sprint-length value: "${value}". Expected a duration like "2w" or "10d".`
11110
+ );
11111
+ } else if (dur.unit !== "d" && dur.unit !== "w") {
11112
+ warn(
11113
+ lineNumber,
11114
+ `sprint-length only accepts "d" or "w" units, got "${dur.unit}".`
11115
+ );
11116
+ } else if (dur.amount <= 0) {
11117
+ warn(lineNumber, `sprint-length must be greater than 0.`);
11118
+ } else if (!Number.isInteger(dur.amount * (dur.unit === "w" ? 7 : 1))) {
11119
+ warn(
11120
+ lineNumber,
11121
+ `sprint-length must resolve to a whole number of days.`
11122
+ );
11123
+ } else {
11124
+ result.options.sprintLength = dur;
11125
+ }
11126
+ break;
11127
+ }
11128
+ case "sprint-number": {
11129
+ const n = Number(value);
11130
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
11131
+ warn(
11132
+ lineNumber,
11133
+ `sprint-number must be a positive integer, got "${value}".`
11134
+ );
11135
+ } else {
11136
+ result.options.sprintNumber = n;
11137
+ }
11138
+ break;
11139
+ }
11140
+ case "sprint-start": {
11141
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
11142
+ warn(
11143
+ lineNumber,
11144
+ `sprint-start requires a full date (YYYY-MM-DD), got "${value}".`
11145
+ );
11146
+ } else if (Number.isNaN((/* @__PURE__ */ new Date(value + "T00:00:00")).getTime())) {
11147
+ warn(lineNumber, `sprint-start is not a valid date: "${value}".`);
11148
+ } else {
11149
+ result.options.sprintStart = value;
11150
+ }
11151
+ break;
11152
+ }
10930
11153
  }
10931
11154
  continue;
10932
11155
  }
@@ -11107,6 +11330,21 @@ function parseGantt(content, palette) {
11107
11330
  result.options.sort = "default";
11108
11331
  }
11109
11332
  validateTagGroupNames(result.tagGroups, warn);
11333
+ const hasSprintOption = result.options.sprintLength !== null || result.options.sprintNumber !== null || result.options.sprintStart !== null;
11334
+ const hasSprintUnit = hasSprintDurationUnit(result.nodes);
11335
+ if (hasSprintOption) {
11336
+ result.options.sprintMode = "explicit";
11337
+ } else if (hasSprintUnit) {
11338
+ result.options.sprintMode = "auto";
11339
+ }
11340
+ if (result.options.sprintMode) {
11341
+ if (!result.options.sprintLength) {
11342
+ result.options.sprintLength = { amount: 2, unit: "w" };
11343
+ }
11344
+ if (result.options.sprintNumber === null) {
11345
+ result.options.sprintNumber = 1;
11346
+ }
11347
+ }
11110
11348
  return result;
11111
11349
  function makeTask(labelRaw, duration, uncertain, ln, explicitStart) {
11112
11350
  const segments = labelRaw.split("|");
@@ -11224,6 +11462,16 @@ function parseWorkweek(s) {
11224
11462
  function isKnownOption(key) {
11225
11463
  return KNOWN_OPTIONS5.has(key);
11226
11464
  }
11465
+ function hasSprintDurationUnit(nodes) {
11466
+ for (const node of nodes) {
11467
+ if (node.kind === "task") {
11468
+ if (node.duration?.unit === "s") return true;
11469
+ } else if (node.kind === "group" || node.kind === "parallel") {
11470
+ if (hasSprintDurationUnit(node.children)) return true;
11471
+ }
11472
+ }
11473
+ return false;
11474
+ }
11227
11475
  var DURATION_RE, EXPLICIT_DATE_RE, TIMELINE_DURATION_RE, GROUP_RE2, DEPENDENCY_RE, COMMENT_RE, ERA_RE, MARKER_RE, HOLIDAY_DATE_RE, HOLIDAY_RANGE_RE, WORKWEEK_RE, ERA_ENTRY_RE, MARKER_ENTRY_RE, WEEKDAY_MAP, KNOWN_OPTIONS5, KNOWN_BOOLEANS4;
11228
11476
  var init_parser9 = __esm({
11229
11477
  "src/gantt/parser.ts"() {
@@ -11233,9 +11481,9 @@ var init_parser9 = __esm({
11233
11481
  init_parsing();
11234
11482
  init_duration();
11235
11483
  init_palettes();
11236
- DURATION_RE = /^(\d+(?:\.\d+)?)(min|bd|d|w|m|q|y|h)(\?)?\s+(.+)$/;
11484
+ DURATION_RE = /^(\d+(?:\.\d+)?)(min|bd|d|w|m|q|y|h|s)(\?)?\s+(.+)$/;
11237
11485
  EXPLICIT_DATE_RE = /^(\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2})?)\s+(.+)$/;
11238
- TIMELINE_DURATION_RE = /^(\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2})?)\s*(?:->|\u2013>)\s*(\d+(?:\.\d+)?)(min|bd|d|w|m|q|y|h)(\?)?\s+(.+)$/;
11486
+ TIMELINE_DURATION_RE = /^(\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2})?)\s*(?:->|\u2013>)\s*(\d+(?:\.\d+)?)(min|bd|d|w|m|q|y|h|s)(\?)?\s+(.+)$/;
11239
11487
  GROUP_RE2 = /^\[(.+?)\]\s*(.*)$/;
11240
11488
  DEPENDENCY_RE = /^(?:-(.+?))?->\s*(.+)$/;
11241
11489
  COMMENT_RE = /^\/\//;
@@ -11270,7 +11518,10 @@ var init_parser9 = __esm({
11270
11518
  "dependencies",
11271
11519
  "chart",
11272
11520
  "sort",
11273
- "active-tag"
11521
+ "active-tag",
11522
+ "sprint-length",
11523
+ "sprint-number",
11524
+ "sprint-start"
11274
11525
  ]);
11275
11526
  KNOWN_BOOLEANS4 = /* @__PURE__ */ new Set([
11276
11527
  "critical-path",
@@ -22667,6 +22918,7 @@ function calculateSchedule(parsed) {
22667
22918
  tagGroups: parsed.tagGroups,
22668
22919
  eras: parsed.eras,
22669
22920
  markers: parsed.markers,
22921
+ sprints: [],
22670
22922
  options: parsed.options,
22671
22923
  diagnostics,
22672
22924
  error: parsed.error
@@ -22688,6 +22940,7 @@ function calculateSchedule(parsed) {
22688
22940
  } else {
22689
22941
  projectStart = new Date(2e3, 0, 1);
22690
22942
  }
22943
+ const sprintOpts = parsed.options.sprintLength ? { sprintLength: parsed.options.sprintLength } : void 0;
22691
22944
  const depOffsetMap = /* @__PURE__ */ new Map();
22692
22945
  const allTasks = collectTasks(parsed.nodes);
22693
22946
  if (allTasks.length === 0) {
@@ -22769,7 +23022,8 @@ function calculateSchedule(parsed) {
22769
23022
  depOffset.duration,
22770
23023
  parsed.holidays,
22771
23024
  holidaySet,
22772
- depOffset.direction
23025
+ depOffset.direction,
23026
+ sprintOpts
22773
23027
  );
22774
23028
  }
22775
23029
  if (predEnd.getTime() > start.getTime()) {
@@ -22783,7 +23037,8 @@ function calculateSchedule(parsed) {
22783
23037
  task.offset.duration,
22784
23038
  parsed.holidays,
22785
23039
  holidaySet,
22786
- task.offset.direction
23040
+ task.offset.direction,
23041
+ sprintOpts
22787
23042
  );
22788
23043
  if (start.getTime() < projectStart.getTime()) {
22789
23044
  warn(
@@ -22823,7 +23078,9 @@ function calculateSchedule(parsed) {
22823
23078
  start,
22824
23079
  task.duration,
22825
23080
  parsed.holidays,
22826
- holidaySet
23081
+ holidaySet,
23082
+ 1,
23083
+ sprintOpts
22827
23084
  );
22828
23085
  }
22829
23086
  } else {
@@ -22837,7 +23094,8 @@ function calculateSchedule(parsed) {
22837
23094
  taskMap,
22838
23095
  depOffsetMap,
22839
23096
  parsed.holidays,
22840
- holidaySet
23097
+ holidaySet,
23098
+ sprintOpts
22841
23099
  ) : /* @__PURE__ */ new Set();
22842
23100
  const uncertainSet = /* @__PURE__ */ new Set();
22843
23101
  for (const taskId of sortedIds) {
@@ -22877,6 +23135,20 @@ function calculateSchedule(parsed) {
22877
23135
  result.startDate = minDate;
22878
23136
  result.endDate = maxDate;
22879
23137
  }
23138
+ if (parsed.options.sprintMode && parsed.options.sprintLength && result.tasks.length > 0) {
23139
+ result.sprints = generateSprintBands(
23140
+ parsed.options,
23141
+ result.startDate,
23142
+ result.endDate,
23143
+ projectStart
23144
+ );
23145
+ if (result.sprints.length > 0) {
23146
+ const lastSprintEnd = result.sprints[result.sprints.length - 1].endDate;
23147
+ if (lastSprintEnd.getTime() > result.endDate.getTime()) {
23148
+ result.endDate = lastSprintEnd;
23149
+ }
23150
+ }
23151
+ }
22880
23152
  const topLevelGroups = parsed.nodes.filter((n) => n.kind === "group");
22881
23153
  if (topLevelGroups.length >= 2) {
22882
23154
  const names = topLevelGroups.map(
@@ -23077,7 +23349,7 @@ function breakCycle(cycle, taskMap, depOffsetMap) {
23077
23349
  }
23078
23350
  }
23079
23351
  }
23080
- function computeCriticalPath(sortedIds, taskMap, depOffsetMap, holidays, holidaySet) {
23352
+ function computeCriticalPath(sortedIds, taskMap, depOffsetMap, holidays, holidaySet, sprintOpts) {
23081
23353
  if (sortedIds.length === 0) return /* @__PURE__ */ new Set();
23082
23354
  const latestEnd = /* @__PURE__ */ new Map();
23083
23355
  const latestStart = /* @__PURE__ */ new Map();
@@ -23115,7 +23387,8 @@ function computeCriticalPath(sortedIds, taskMap, depOffsetMap, holidays, holiday
23115
23387
  succTask.offset.duration,
23116
23388
  holidays,
23117
23389
  holidaySet,
23118
- reverseDir
23390
+ reverseDir,
23391
+ sprintOpts
23119
23392
  );
23120
23393
  succLS = adjusted.getTime();
23121
23394
  }
@@ -23127,7 +23400,8 @@ function computeCriticalPath(sortedIds, taskMap, depOffsetMap, holidays, holiday
23127
23400
  depOffset.duration,
23128
23401
  holidays,
23129
23402
  holidaySet,
23130
- reverseDir
23403
+ reverseDir,
23404
+ sprintOpts
23131
23405
  );
23132
23406
  succLS = adjusted.getTime();
23133
23407
  }
@@ -23202,6 +23476,51 @@ function buildResolvedGroups(nodes, taskMap, groups, depth) {
23202
23476
  }
23203
23477
  }
23204
23478
  }
23479
+ function generateSprintBands(options, chartStart, chartEnd, projectStart) {
23480
+ const sprintLength = options.sprintLength;
23481
+ const sprintNumber = options.sprintNumber ?? 1;
23482
+ let anchorDate;
23483
+ if (options.sprintStart) {
23484
+ anchorDate = /* @__PURE__ */ new Date(options.sprintStart + "T00:00:00");
23485
+ } else if (options.start) {
23486
+ anchorDate = new Date(projectStart);
23487
+ } else {
23488
+ const now = /* @__PURE__ */ new Date();
23489
+ anchorDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
23490
+ }
23491
+ anchorDate.setHours(0, 0, 0, 0);
23492
+ const sprintDays = Math.round(
23493
+ sprintLength.amount * (sprintLength.unit === "w" ? 7 : 1)
23494
+ );
23495
+ if (sprintDays <= 0) return [];
23496
+ if (Number.isNaN(anchorDate.getTime())) return [];
23497
+ const chartStartTime = new Date(chartStart);
23498
+ chartStartTime.setHours(0, 0, 0, 0);
23499
+ const chartEndTime = new Date(chartEnd);
23500
+ chartEndTime.setHours(0, 0, 0, 0);
23501
+ const msPerDay = 864e5;
23502
+ const dayDiff = Math.floor(
23503
+ (chartStartTime.getTime() - anchorDate.getTime()) / msPerDay
23504
+ );
23505
+ const startSprintIndex = Math.floor(dayDiff / sprintDays);
23506
+ const sprints = [];
23507
+ const maxSprints = 1e3;
23508
+ for (let i = startSprintIndex; sprints.length < maxSprints; i++) {
23509
+ const sprintStartDate = new Date(anchorDate);
23510
+ sprintStartDate.setDate(sprintStartDate.getDate() + i * sprintDays);
23511
+ sprintStartDate.setHours(0, 0, 0, 0);
23512
+ const sprintEndDate = new Date(sprintStartDate);
23513
+ sprintEndDate.setDate(sprintEndDate.getDate() + sprintDays);
23514
+ sprintEndDate.setHours(0, 0, 0, 0);
23515
+ if (sprintStartDate.getTime() >= chartEndTime.getTime() + msPerDay) break;
23516
+ sprints.push({
23517
+ number: sprintNumber + i,
23518
+ startDate: sprintStartDate,
23519
+ endDate: sprintEndDate
23520
+ });
23521
+ }
23522
+ return sprints;
23523
+ }
23205
23524
  function formatDate(d) {
23206
23525
  const y = d.getFullYear();
23207
23526
  const m = String(d.getMonth() + 1).padStart(2, "0");
@@ -23285,6 +23604,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23285
23604
  options?.currentActiveGroup
23286
23605
  );
23287
23606
  let criticalPathActive = false;
23607
+ let dependenciesActive = !!resolved.options.dependencies;
23608
+ let controlsExpanded = false;
23288
23609
  const tagRows = currentSwimlaneGroup ? buildTagLaneRowList(resolved, currentSwimlaneGroup, collapsedLanes) : null;
23289
23610
  const rows = tagRows ?? buildRowList(resolved, collapsedGroups);
23290
23611
  const isTagMode = tagRows !== null;
@@ -23301,28 +23622,31 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23301
23622
  const maxLabelLen = Math.max(...allLabels.map((l) => l.length), 10);
23302
23623
  const leftMargin = Math.max(MIN_LEFT_MARGIN, maxLabelLen * 7 + 30);
23303
23624
  const totalRows = rows.length;
23625
+ const hasCriticalPath = resolved.options.criticalPath && resolved.tasks.some((t) => t.isCriticalPath);
23626
+ const hasDependencies = resolved.options.dependencies && resolved.tasks.some((t) => t.task.dependencies.length > 0);
23304
23627
  const title = resolved.options.title;
23305
23628
  const titleHeight = title ? 50 : 20;
23306
- const tagLegendReserve = resolved.tagGroups.length > 0 ? LEGEND_HEIGHT + 8 : 0;
23629
+ const tagLegendReserve = resolved.tagGroups.length > 0 || hasCriticalPath || hasDependencies ? LEGEND_HEIGHT + 8 : 0;
23307
23630
  const topDateLabelReserve = 22;
23308
23631
  const hasOverheadLabels = resolved.markers.length > 0 || resolved.eras.length > 0;
23309
- const markerLabelReserve = hasOverheadLabels ? 18 : 0;
23632
+ const markerLabelReserve = hasOverheadLabels ? 28 : 0;
23633
+ const sprintLabelReserve = resolved.sprints.length > 0 ? 16 : 0;
23310
23634
  const CONTENT_TOP_PAD = 16;
23311
- const marginTop = titleHeight + tagLegendReserve + topDateLabelReserve + markerLabelReserve;
23635
+ const marginTop = titleHeight + tagLegendReserve + topDateLabelReserve + markerLabelReserve + sprintLabelReserve;
23312
23636
  const contentH = isTagMode ? totalRows * (BAR_H + ROW_GAP) : totalRows * (BAR_H + ROW_GAP) + GROUP_GAP2 * resolved.groups.length;
23313
23637
  const innerHeight = CONTENT_TOP_PAD + contentH;
23314
23638
  const outerHeight = marginTop + innerHeight + BOTTOM_MARGIN;
23315
23639
  const containerWidth = exportDims?.width ?? (container.clientWidth || 800);
23316
- const innerWidth = containerWidth - leftMargin - RIGHT_MARGIN;
23640
+ const sprintRightPad = resolved.sprints.length > 0 ? 50 : 0;
23641
+ const innerWidth = containerWidth - leftMargin - RIGHT_MARGIN - sprintRightPad;
23317
23642
  const svg = d3Selection10.select(container).append("svg").attr("viewBox", `0 0 ${containerWidth} ${outerHeight}`).attr("width", exportDims ? containerWidth : "100%").attr("preserveAspectRatio", "xMidYMin meet").attr("font-family", FONT_FAMILY).style("overflow", "visible");
23318
23643
  const g = svg.append("g").attr("transform", `translate(${leftMargin}, ${marginTop})`);
23319
23644
  if (title) {
23320
23645
  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);
23321
23646
  }
23322
- const hasCriticalPath = resolved.options.criticalPath && resolved.tasks.some((t) => t.isCriticalPath);
23323
23647
  function drawLegend() {
23324
23648
  svg.selectAll(".gantt-tag-legend-container").remove();
23325
- if (resolved.tagGroups.length > 0 || hasCriticalPath) {
23649
+ if (resolved.tagGroups.length > 0 || hasCriticalPath || hasDependencies) {
23326
23650
  const legendY = titleHeight;
23327
23651
  renderTagLegend(
23328
23652
  svg,
@@ -23344,16 +23668,38 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23344
23668
  recolorBars();
23345
23669
  },
23346
23670
  () => {
23347
- criticalPathActive = !criticalPathActive;
23671
+ controlsExpanded = !controlsExpanded;
23348
23672
  drawLegend();
23349
23673
  },
23350
23674
  currentSwimlaneGroup,
23351
23675
  onSwimlaneChange,
23352
23676
  viewMode,
23353
- resolved.tasks
23677
+ resolved.tasks,
23678
+ controlsExpanded,
23679
+ hasDependencies,
23680
+ dependenciesActive,
23681
+ (toggleId, active) => {
23682
+ if (toggleId === "critical-path") {
23683
+ criticalPathActive = active;
23684
+ } else if (toggleId === "dependencies") {
23685
+ dependenciesActive = active;
23686
+ g.selectAll(
23687
+ ".gantt-dep-arrow, .gantt-dep-arrowhead, .gantt-dep-label"
23688
+ ).attr("display", active ? null : "none");
23689
+ }
23690
+ drawLegend();
23691
+ }
23354
23692
  );
23355
23693
  }
23356
23694
  }
23695
+ function restoreHighlight() {
23696
+ if (criticalPathActive) {
23697
+ applyCriticalPathHighlight(svg, g);
23698
+ } else {
23699
+ svg.attr("data-critical-path-active", null);
23700
+ resetHighlight(g, svg);
23701
+ }
23702
+ }
23357
23703
  function recolorBars() {
23358
23704
  g.selectAll(".gantt-task").each(function() {
23359
23705
  const el = d3Selection10.select(this);
@@ -23393,6 +23739,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23393
23739
  onClickItem
23394
23740
  );
23395
23741
  renderErasAndMarkers(g, svg, resolved, xScale, innerHeight, palette);
23742
+ renderSprintBands(g, svg, resolved, xScale, innerHeight, palette);
23396
23743
  let todayDate = null;
23397
23744
  let todayX = -1;
23398
23745
  const todayColor = palette.accent || "#e74c3c";
@@ -23478,7 +23825,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23478
23825
  );
23479
23826
  }
23480
23827
  }).on("mouseleave", () => {
23481
- resetHighlight(g, svg);
23828
+ restoreHighlight();
23482
23829
  hideGanttDateIndicators(g);
23483
23830
  });
23484
23831
  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(
@@ -23683,7 +24030,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23683
24030
  );
23684
24031
  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);
23685
24032
  }).on("mouseleave", () => {
23686
- resetHighlight(g, svg);
24033
+ restoreHighlight();
23687
24034
  hideGanttDateIndicators(g);
23688
24035
  g.selectAll(".gantt-milestone-hover-label").remove();
23689
24036
  });
@@ -23712,11 +24059,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23712
24059
  );
23713
24060
  }).on("mouseleave", () => {
23714
24061
  if (resolved.options.dependencies) {
23715
- if (criticalPathActive) {
23716
- applyCriticalPathHighlight(svg, g);
23717
- } else {
23718
- resetHighlight(g, svg);
23719
- }
24062
+ restoreHighlight();
23720
24063
  }
23721
24064
  resetTaskLabels(svg);
23722
24065
  hideGanttDateIndicators(g);
@@ -24031,6 +24374,7 @@ function arrowheadPoints(x, y, size, angle) {
24031
24374
  return `${x},${y} ${x + size * Math.cos(a1)},${y + size * Math.sin(a1)} ${x + size * Math.cos(a2)},${y + size * Math.sin(a2)}`;
24032
24375
  }
24033
24376
  function applyCriticalPathHighlight(svg, chartG) {
24377
+ svg.attr("data-critical-path-active", "true");
24034
24378
  chartG.selectAll(".gantt-task").each(function() {
24035
24379
  const el = d3Selection10.select(this);
24036
24380
  el.attr(
@@ -24083,8 +24427,31 @@ function drawSwimlaneIcon2(parent, x, y, isActive, palette) {
24083
24427
  }
24084
24428
  return iconG;
24085
24429
  }
24086
- function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargin, chartInnerWidth, legendY, palette, isDark, hasCriticalPath, criticalPathActive, optionLineNumbers, onToggle, onToggleCriticalPath, currentSwimlaneGroup, onSwimlaneChange, legendViewMode, resolvedTasks) {
24087
- const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
24430
+ function buildControlsToggles(hasCriticalPath, criticalPathActive, hasDependencies, dependenciesActive) {
24431
+ const toggles = [];
24432
+ if (hasCriticalPath) {
24433
+ toggles.push({
24434
+ id: "critical-path",
24435
+ type: "toggle",
24436
+ label: "Critical Path",
24437
+ active: criticalPathActive,
24438
+ onToggle: () => {
24439
+ }
24440
+ });
24441
+ }
24442
+ if (hasDependencies) {
24443
+ toggles.push({
24444
+ id: "dependencies",
24445
+ type: "toggle",
24446
+ label: "Dependencies",
24447
+ active: dependenciesActive,
24448
+ onToggle: () => {
24449
+ }
24450
+ });
24451
+ }
24452
+ return toggles;
24453
+ }
24454
+ 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) {
24088
24455
  let visibleGroups;
24089
24456
  if (activeGroupName) {
24090
24457
  const activeGroup = tagGroups.filter(
@@ -24145,16 +24512,17 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
24145
24512
  totalW += groupW;
24146
24513
  }
24147
24514
  totalW += Math.max(0, (visibleGroups.length - 1) * LEGEND_GROUP_GAP);
24148
- const cpLabel = "Critical Path";
24149
- const cpPillW = measureLegendText(cpLabel, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
24150
- if (hasCriticalPath) {
24515
+ const hasControls = hasCriticalPath || hasDependencies;
24516
+ const controlsToggleLabels = [];
24517
+ if (hasCriticalPath) controlsToggleLabels.push({ label: "Critical Path" });
24518
+ if (hasDependencies) controlsToggleLabels.push({ label: "Dependencies" });
24519
+ if (hasControls) {
24151
24520
  if (visibleGroups.length > 0) totalW += LEGEND_GROUP_GAP;
24152
- totalW += cpPillW;
24521
+ totalW += controlsExpanded ? controlsGroupCapsuleWidth(controlsToggleLabels) : LEGEND_GEAR_PILL_W;
24153
24522
  }
24154
24523
  const containerWidth = chartLeftMargin + chartInnerWidth + RIGHT_MARGIN;
24155
24524
  const legendX = (containerWidth - totalW) / 2;
24156
24525
  const legendRow = svg.append("g").attr("class", "gantt-tag-legend-container").attr("transform", `translate(${legendX}, ${legendY})`);
24157
- let cursorX = 0;
24158
24526
  if (visibleGroups.length > 0) {
24159
24527
  const showIcon = !legendViewMode && tagGroups.length > 0;
24160
24528
  const iconReserve = showIcon ? LEGEND_ICON_W : 0;
@@ -24166,6 +24534,12 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
24166
24534
  entries: entries.map((e) => ({ value: e.value, color: e.color }))
24167
24535
  };
24168
24536
  });
24537
+ const controlsToggles = buildControlsToggles(
24538
+ hasCriticalPath,
24539
+ criticalPathActive,
24540
+ hasDependencies,
24541
+ dependenciesActive
24542
+ );
24169
24543
  const legendConfig = {
24170
24544
  groups: legendGroups,
24171
24545
  position: {
@@ -24173,13 +24547,23 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
24173
24547
  titleRelation: "below-title"
24174
24548
  },
24175
24549
  mode: "fixed",
24176
- capsulePillAddonWidth: iconReserve
24550
+ capsulePillAddonWidth: iconReserve,
24551
+ controlsGroup: controlsToggles.length > 0 ? { toggles: controlsToggles } : void 0
24177
24552
  };
24178
- const legendState = { activeGroup: activeGroupName };
24179
- const tagGroupsW = visibleGroups.reduce((s, _, i) => s + groupWidths[i], 0) + Math.max(0, (visibleGroups.length - 1) * LEGEND_GROUP_GAP);
24553
+ const legendState = {
24554
+ activeGroup: activeGroupName,
24555
+ controlsExpanded
24556
+ };
24557
+ let tagGroupsW = visibleGroups.reduce((s, _, i) => s + groupWidths[i], 0) + Math.max(0, (visibleGroups.length - 1) * LEGEND_GROUP_GAP);
24558
+ if (hasControls) {
24559
+ if (visibleGroups.length > 0) tagGroupsW += LEGEND_GROUP_GAP;
24560
+ tagGroupsW += controlsExpanded ? controlsGroupCapsuleWidth(controlsToggleLabels) : LEGEND_GEAR_PILL_W;
24561
+ }
24180
24562
  const tagGroupG = legendRow.append("g");
24181
24563
  const legendCallbacks = {
24182
24564
  onGroupToggle: onToggle,
24565
+ onControlsExpand: onToggleControlsExpand,
24566
+ onControlsToggle,
24183
24567
  onEntryHover: (groupName, entryValue) => {
24184
24568
  const tagKey = groupName.toLowerCase();
24185
24569
  if (entryValue) {
@@ -24256,31 +24640,38 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
24256
24640
  legendCallbacks,
24257
24641
  tagGroupsW
24258
24642
  );
24259
- for (let i = 0; i < visibleGroups.length; i++) {
24260
- cursorX += groupWidths[i] + LEGEND_GROUP_GAP;
24261
- }
24643
+ } else if (hasControls) {
24644
+ const controlsToggles = buildControlsToggles(
24645
+ hasCriticalPath,
24646
+ criticalPathActive,
24647
+ hasDependencies,
24648
+ dependenciesActive
24649
+ );
24650
+ const legendConfig = {
24651
+ groups: [],
24652
+ position: {
24653
+ placement: "top-center",
24654
+ titleRelation: "below-title"
24655
+ },
24656
+ mode: "fixed",
24657
+ controlsGroup: { toggles: controlsToggles }
24658
+ };
24659
+ const tagGroupG = legendRow.append("g");
24660
+ renderLegendD3(
24661
+ tagGroupG,
24662
+ legendConfig,
24663
+ { activeGroup: null, controlsExpanded },
24664
+ palette,
24665
+ isDark,
24666
+ {
24667
+ onControlsExpand: onToggleControlsExpand,
24668
+ onControlsToggle
24669
+ },
24670
+ totalW
24671
+ );
24262
24672
  }
24263
- if (hasCriticalPath) {
24264
- const cpLineNum = optionLineNumbers["critical-path"];
24265
- const cpG = legendRow.append("g").attr("transform", `translate(${cursorX}, 0)`).attr("class", "gantt-legend-critical-path").style("cursor", "pointer").on("click", () => {
24266
- if (onToggleCriticalPath) onToggleCriticalPath();
24267
- });
24268
- if (cpLineNum) cpG.attr("data-line-number", String(cpLineNum));
24269
- cpG.append("rect").attr("width", cpPillW).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", criticalPathActive ? palette.bg : groupBg);
24270
- if (criticalPathActive) {
24271
- 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);
24272
- }
24273
- 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);
24274
- if (criticalPathActive) {
24275
- applyCriticalPathHighlight(svg, chartG);
24276
- }
24277
- cpG.on("mouseenter", () => {
24278
- applyCriticalPathHighlight(svg, chartG);
24279
- }).on("mouseleave", () => {
24280
- if (!criticalPathActive) {
24281
- resetHighlightAll(svg, chartG);
24282
- }
24283
- });
24673
+ if (criticalPathActive) {
24674
+ applyCriticalPathHighlight(svg, chartG);
24284
24675
  }
24285
24676
  }
24286
24677
  function renderErasAndMarkers(g, svg, resolved, xScale, innerHeight, palette) {
@@ -24296,7 +24687,7 @@ function renderErasAndMarkers(g, svg, resolved, xScale, innerHeight, palette) {
24296
24687
  const eraEndDate = parseDateStringToDate(era.endDate);
24297
24688
  const eraG = g.append("g").attr("class", "gantt-era-group").attr("data-line-number", String(era.lineNumber));
24298
24689
  const eraRect = eraG.append("rect").attr("class", "gantt-era").attr("x", sx).attr("y", 0).attr("width", ex - sx).attr("height", innerHeight).attr("fill", color).attr("opacity", baseEraOpacity);
24299
- eraG.append("text").attr("class", "gantt-era-label").attr("x", (sx + ex) / 2).attr("y", -24).attr("text-anchor", "middle").attr("font-size", "10px").attr("fill", color).attr("opacity", 0.7).style("cursor", "pointer").text(era.label);
24690
+ eraG.append("text").attr("class", "gantt-era-label").attr("x", (sx + ex) / 2).attr("y", -34).attr("text-anchor", "middle").attr("font-size", "10px").attr("fill", color).attr("opacity", 0.7).style("cursor", "pointer").text(era.label);
24300
24691
  eraG.on("mouseenter", () => {
24301
24692
  g.selectAll(".gantt-task").attr(
24302
24693
  "opacity",
@@ -24342,8 +24733,8 @@ function renderErasAndMarkers(g, svg, resolved, xScale, innerHeight, palette) {
24342
24733
  const mx = xScale(parseDateToFractionalYear(marker.date));
24343
24734
  const markerDate = parseDateStringToDate(marker.date);
24344
24735
  const diamondSize = 5;
24345
- const labelY = -24;
24346
- const diamondY = labelY + 14;
24736
+ const labelY = -34;
24737
+ const diamondY = -2;
24347
24738
  const markerG = g.append("g").attr("class", "gantt-marker-group").attr("data-line-number", String(marker.lineNumber)).style("cursor", "pointer");
24348
24739
  markerG.append("rect").attr("x", mx - 40).attr("y", labelY - 12).attr("width", 80).attr("height", innerHeight - labelY + 12).attr("fill", "transparent").attr("pointer-events", "all");
24349
24740
  markerG.append("text").attr("class", "gantt-marker-label").attr("x", mx).attr("y", labelY).attr("text-anchor", "middle").attr("font-size", "11px").attr("font-weight", "600").attr("fill", color).text(marker.label);
@@ -24403,6 +24794,139 @@ function renderErasAndMarkers(g, svg, resolved, xScale, innerHeight, palette) {
24403
24794
  });
24404
24795
  }
24405
24796
  }
24797
+ function renderSprintBands(g, svg, resolved, xScale, innerHeight, palette) {
24798
+ if (resolved.sprints.length === 0) return;
24799
+ if (resolved.eras.length > 0) return;
24800
+ const bandColor = palette.textMuted || palette.text || "#888";
24801
+ const chartMinX = 0;
24802
+ for (let i = 0; i < resolved.sprints.length; i++) {
24803
+ const sprint = resolved.sprints[i];
24804
+ const rawSx = xScale(dateToFractionalYear(sprint.startDate));
24805
+ const rawEx = xScale(dateToFractionalYear(sprint.endDate));
24806
+ if (rawEx <= rawSx) continue;
24807
+ const sx = Math.max(rawSx, chartMinX);
24808
+ const ex = rawEx;
24809
+ const bandWidth = ex - sx;
24810
+ if (bandWidth <= 0) continue;
24811
+ const sprintG = g.append("g").attr("class", "gantt-sprint-group").style("cursor", "pointer");
24812
+ const sprintRect = sprintG.append("rect").attr("class", "gantt-sprint-band").attr("x", sx).attr("y", 0).attr("width", bandWidth).attr("height", innerHeight).attr("fill", bandColor).attr("opacity", sprint.number % 2 === 0 ? SPRINT_BAND_OPACITY : 0);
24813
+ if (sprint.number % 2 !== 0) {
24814
+ sprintG.append("rect").attr("x", sx).attr("y", 0).attr("width", bandWidth).attr("height", innerHeight).attr("fill", "transparent");
24815
+ }
24816
+ const sprintLabel = sprintG.append("text").attr("class", "gantt-sprint-label").attr("x", (sx + ex) / 2).attr("y", -22).attr("text-anchor", "middle").attr("font-size", "10px").attr("font-weight", "600").attr("fill", bandColor).attr("opacity", 0.4).text(String(sprint.number));
24817
+ if (i > 0 && rawSx >= chartMinX) {
24818
+ sprintG.append("line").attr("class", "gantt-sprint-boundary").attr("x1", sx).attr("y1", -6).attr("x2", sx).attr("y2", innerHeight).attr("stroke", bandColor).attr("stroke-width", 1).attr("stroke-dasharray", "3 3").attr("opacity", SPRINT_BOUNDARY_OPACITY);
24819
+ }
24820
+ const sprintStartMs = sprint.startDate.getTime();
24821
+ const sprintEndMs = sprint.endDate.getTime();
24822
+ const overlappingTaskIds = /* @__PURE__ */ new Set();
24823
+ for (const rt of resolved.tasks) {
24824
+ const taskStart = rt.startDate.getTime();
24825
+ const taskEnd = rt.endDate.getTime();
24826
+ if (taskStart < sprintEndMs && taskEnd > sprintStartMs) {
24827
+ overlappingTaskIds.add(rt.task.id);
24828
+ }
24829
+ if (taskStart === taskEnd && taskStart >= sprintStartMs && taskStart < sprintEndMs) {
24830
+ overlappingTaskIds.add(rt.task.id);
24831
+ }
24832
+ }
24833
+ const overlappingGroupNames = /* @__PURE__ */ new Set();
24834
+ for (const rg of resolved.groups) {
24835
+ const gStart = rg.startDate.getTime();
24836
+ const gEnd = rg.endDate.getTime();
24837
+ if (gStart < sprintEndMs && gEnd > sprintStartMs) {
24838
+ overlappingGroupNames.add(rg.name);
24839
+ }
24840
+ }
24841
+ const baseOpacity = sprint.number % 2 === 0 ? SPRINT_BAND_OPACITY : 0;
24842
+ sprintG.on("mouseenter", () => {
24843
+ g.selectAll(".gantt-task").each(function() {
24844
+ const el = d3Selection10.select(this);
24845
+ const id = el.attr("data-task-id");
24846
+ el.attr(
24847
+ "opacity",
24848
+ id && overlappingTaskIds.has(id) ? 1 : FADE_OPACITY
24849
+ );
24850
+ });
24851
+ g.selectAll(".gantt-milestone").each(function() {
24852
+ const el = d3Selection10.select(this);
24853
+ const id = el.attr("data-task-id");
24854
+ el.attr(
24855
+ "opacity",
24856
+ id && overlappingTaskIds.has(id) ? 1 : FADE_OPACITY
24857
+ );
24858
+ });
24859
+ svg.selectAll(".gantt-task-label").each(function() {
24860
+ const el = d3Selection10.select(this);
24861
+ const id = el.attr("data-task-id");
24862
+ el.attr(
24863
+ "opacity",
24864
+ id && overlappingTaskIds.has(id) ? 1 : FADE_OPACITY
24865
+ );
24866
+ });
24867
+ g.selectAll(
24868
+ ".gantt-group-bar, .gantt-group-summary"
24869
+ ).each(function() {
24870
+ const el = d3Selection10.select(this);
24871
+ const name = el.attr("data-group");
24872
+ el.attr(
24873
+ "opacity",
24874
+ name && overlappingGroupNames.has(name) ? 1 : FADE_OPACITY
24875
+ );
24876
+ });
24877
+ svg.selectAll(".gantt-group-label").each(function() {
24878
+ const el = d3Selection10.select(this);
24879
+ const name = el.attr("data-group");
24880
+ el.attr(
24881
+ "opacity",
24882
+ name && overlappingGroupNames.has(name) ? 1 : FADE_OPACITY
24883
+ );
24884
+ });
24885
+ svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
24886
+ g.selectAll(
24887
+ ".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group"
24888
+ ).attr("opacity", FADE_OPACITY);
24889
+ g.selectAll(
24890
+ ".gantt-dep-arrow, .gantt-dep-arrowhead"
24891
+ ).attr("opacity", FADE_OPACITY);
24892
+ g.selectAll(".gantt-marker-group").attr(
24893
+ "opacity",
24894
+ FADE_OPACITY
24895
+ );
24896
+ sprintRect.attr("opacity", SPRINT_HOVER_OPACITY);
24897
+ const startVisible = rawSx >= chartMinX;
24898
+ if (startVisible) {
24899
+ showGanttDateIndicators(
24900
+ g,
24901
+ xScale,
24902
+ sprint.startDate,
24903
+ sprint.endDate,
24904
+ innerHeight,
24905
+ bandColor
24906
+ );
24907
+ } else {
24908
+ showGanttDateIndicators(
24909
+ g,
24910
+ xScale,
24911
+ sprint.endDate,
24912
+ null,
24913
+ innerHeight,
24914
+ bandColor
24915
+ );
24916
+ }
24917
+ const accentColor = palette.accent || palette.text || bandColor;
24918
+ sprintLabel.text(`Sprint ${sprint.number}`).attr("font-size", "13px").attr("font-weight", "700").attr("fill", accentColor).attr("opacity", 1);
24919
+ }).on("mouseleave", () => {
24920
+ resetHighlight(g, svg);
24921
+ sprintRect.attr("opacity", baseOpacity);
24922
+ sprintLabel.text(String(sprint.number)).attr("font-size", "10px").attr("font-weight", "600").attr("fill", bandColor).attr("opacity", 0.4);
24923
+ hideGanttDateIndicators(g);
24924
+ });
24925
+ }
24926
+ const lastSprint = resolved.sprints[resolved.sprints.length - 1];
24927
+ const lastEx = xScale(dateToFractionalYear(lastSprint.endDate));
24928
+ 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);
24929
+ }
24406
24930
  function parseDateStringToDate(s) {
24407
24931
  const parts = s.split("-").map((p) => parseInt(p, 10));
24408
24932
  const year = parts[0];
@@ -24615,6 +25139,10 @@ function resetTaskLabels(svg) {
24615
25139
  svg.selectAll(".gantt-task-label").attr("opacity", 1);
24616
25140
  }
24617
25141
  function resetHighlight(g, svg) {
25142
+ if (svg.attr("data-critical-path-active") === "true") {
25143
+ applyCriticalPathHighlight(svg, g);
25144
+ return;
25145
+ }
24618
25146
  g.selectAll(".gantt-task, .gantt-milestone").attr(
24619
25147
  "opacity",
24620
25148
  1
@@ -24875,7 +25403,7 @@ function renderTimeScaleHorizontal(g, scale, innerWidth, innerHeight, textColor)
24875
25403
  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);
24876
25404
  }
24877
25405
  }
24878
- 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, FADE_OPACITY, MONTH_ABBR;
25406
+ 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;
24879
25407
  var init_renderer9 = __esm({
24880
25408
  "src/gantt/renderer.ts"() {
24881
25409
  "use strict";
@@ -24888,6 +25416,7 @@ var init_renderer9 = __esm({
24888
25416
  init_d3();
24889
25417
  init_legend_constants();
24890
25418
  init_legend_d3();
25419
+ init_legend_layout();
24891
25420
  init_title_constants();
24892
25421
  BAR_H = 22;
24893
25422
  ROW_GAP = 6;
@@ -24912,6 +25441,9 @@ var init_renderer9 = __esm({
24912
25441
  "sat"
24913
25442
  ];
24914
25443
  ERA_COLORS = ["#5e81ac", "#a3be8c", "#ebcb8b", "#d08770", "#b48ead"];
25444
+ SPRINT_BAND_OPACITY = 0.05;
25445
+ SPRINT_HOVER_OPACITY = 0.12;
25446
+ SPRINT_BOUNDARY_OPACITY = 0.3;
24915
25447
  FADE_OPACITY = 0.1;
24916
25448
  MONTH_ABBR = [
24917
25449
  "Jan",
@@ -25178,6 +25710,109 @@ var init_state_renderer = __esm({
25178
25710
  }
25179
25711
  });
25180
25712
 
25713
+ // src/sequence/collapse.ts
25714
+ function applyCollapseProjection(parsed, collapsedGroups) {
25715
+ if (collapsedGroups.size === 0) {
25716
+ return {
25717
+ participants: parsed.participants,
25718
+ messages: parsed.messages,
25719
+ elements: parsed.elements,
25720
+ groups: parsed.groups,
25721
+ collapsedGroupIds: /* @__PURE__ */ new Map()
25722
+ };
25723
+ }
25724
+ const memberToGroup = /* @__PURE__ */ new Map();
25725
+ const collapsedGroupNames = /* @__PURE__ */ new Set();
25726
+ for (const group of parsed.groups) {
25727
+ if (collapsedGroups.has(group.lineNumber)) {
25728
+ collapsedGroupNames.add(group.name);
25729
+ for (const memberId of group.participantIds) {
25730
+ memberToGroup.set(memberId, group.name);
25731
+ }
25732
+ }
25733
+ }
25734
+ const participants = [];
25735
+ const insertedGroups = /* @__PURE__ */ new Set();
25736
+ for (const p of parsed.participants) {
25737
+ const groupName = memberToGroup.get(p.id);
25738
+ if (groupName) {
25739
+ if (!insertedGroups.has(groupName)) {
25740
+ insertedGroups.add(groupName);
25741
+ const group = parsed.groups.find(
25742
+ (g) => g.name === groupName && collapsedGroups.has(g.lineNumber)
25743
+ );
25744
+ participants.push({
25745
+ id: groupName,
25746
+ label: groupName,
25747
+ type: "default",
25748
+ lineNumber: group.lineNumber
25749
+ });
25750
+ }
25751
+ } else if (collapsedGroupNames.has(p.id)) {
25752
+ } else {
25753
+ participants.push(p);
25754
+ }
25755
+ }
25756
+ const remap = (id) => memberToGroup.get(id) ?? id;
25757
+ const messages = parsed.messages.map((msg) => ({
25758
+ ...msg,
25759
+ from: remap(msg.from),
25760
+ to: remap(msg.to)
25761
+ }));
25762
+ const elements = remapElements(parsed.elements, memberToGroup);
25763
+ const groups = parsed.groups.filter(
25764
+ (g) => !collapsedGroups.has(g.lineNumber)
25765
+ );
25766
+ return {
25767
+ participants,
25768
+ messages,
25769
+ elements,
25770
+ groups,
25771
+ collapsedGroupIds: memberToGroup
25772
+ };
25773
+ }
25774
+ function remapElements(elements, memberToGroup) {
25775
+ const remap = (id) => memberToGroup.get(id) ?? id;
25776
+ const result = [];
25777
+ for (const el of elements) {
25778
+ if (isSequenceSection(el)) {
25779
+ result.push(el);
25780
+ } else if (isSequenceNote(el)) {
25781
+ result.push({
25782
+ ...el,
25783
+ participantId: remap(el.participantId)
25784
+ });
25785
+ } else if (isSequenceBlock(el)) {
25786
+ result.push({
25787
+ ...el,
25788
+ children: remapElements(el.children, memberToGroup),
25789
+ elseChildren: remapElements(el.elseChildren, memberToGroup),
25790
+ ...el.elseIfBranches ? {
25791
+ elseIfBranches: el.elseIfBranches.map((branch) => ({
25792
+ ...branch,
25793
+ children: remapElements(branch.children, memberToGroup)
25794
+ }))
25795
+ } : {}
25796
+ });
25797
+ } else {
25798
+ const msg = el;
25799
+ const from = remap(msg.from);
25800
+ const to = remap(msg.to);
25801
+ if (from === to && from !== msg.from && !msg.label) {
25802
+ continue;
25803
+ }
25804
+ result.push({ ...msg, from, to });
25805
+ }
25806
+ }
25807
+ return result;
25808
+ }
25809
+ var init_collapse3 = __esm({
25810
+ "src/sequence/collapse.ts"() {
25811
+ "use strict";
25812
+ init_parser();
25813
+ }
25814
+ });
25815
+
25181
25816
  // src/sequence/tag-resolution.ts
25182
25817
  function propagateGroupTags(participantMeta, groups) {
25183
25818
  for (const group of groups) {
@@ -25632,13 +26267,32 @@ function applyGroupOrdering(participants, groups, messages = []) {
25632
26267
  }
25633
26268
  function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateToLine, options) {
25634
26269
  d3Selection12.select(container).selectAll("*").remove();
25635
- const { title, messages, elements, groups, options: parsedOptions } = parsed;
26270
+ const { title, options: parsedOptions } = parsed;
26271
+ const effectiveCollapsedGroups = /* @__PURE__ */ new Set();
26272
+ for (const group of parsed.groups) {
26273
+ if (group.collapsed) effectiveCollapsedGroups.add(group.lineNumber);
26274
+ }
26275
+ if (options?.collapsedGroups) {
26276
+ for (const ln of options.collapsedGroups) {
26277
+ if (effectiveCollapsedGroups.has(ln)) {
26278
+ effectiveCollapsedGroups.delete(ln);
26279
+ } else {
26280
+ effectiveCollapsedGroups.add(ln);
26281
+ }
26282
+ }
26283
+ }
26284
+ const collapsed = effectiveCollapsedGroups.size > 0 ? applyCollapseProjection(parsed, effectiveCollapsedGroups) : null;
26285
+ const messages = collapsed ? collapsed.messages : parsed.messages;
26286
+ const elements = collapsed ? collapsed.elements : parsed.elements;
26287
+ const groups = collapsed ? collapsed.groups : parsed.groups;
26288
+ const collapsedGroupIds = collapsed?.collapsedGroupIds ?? /* @__PURE__ */ new Map();
25636
26289
  const collapsedSections = options?.collapsedSections;
25637
26290
  const expandedNoteLines = options?.expandedNoteLines;
25638
26291
  const collapseNotesDisabled = parsedOptions["collapse-notes"]?.toLowerCase() === "no";
25639
26292
  const isNoteExpanded = (note) => expandedNoteLines === void 0 || collapseNotesDisabled || expandedNoteLines.has(note.lineNumber);
26293
+ const sourceParticipants = collapsed ? collapsed.participants : parsed.participants;
25640
26294
  const participants = applyPositionOverrides(
25641
- applyGroupOrdering(parsed.participants, groups, messages)
26295
+ applyGroupOrdering(sourceParticipants, groups, messages)
25642
26296
  );
25643
26297
  if (participants.length === 0) return;
25644
26298
  const activationsOff = parsedOptions.activations?.toLowerCase() === "off";
@@ -25809,13 +26463,16 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
25809
26463
  const preSectionMsgIndices = [];
25810
26464
  const sectionRegions = [];
25811
26465
  {
26466
+ const msgLineToIndex = /* @__PURE__ */ new Map();
26467
+ messages.forEach((m, i) => msgLineToIndex.set(m.lineNumber, i));
26468
+ const findMsgIndex = (child) => msgLineToIndex.get(child.lineNumber) ?? -1;
25812
26469
  const collectMsgIndicesFromBlock = (block) => {
25813
26470
  const indices = [];
25814
26471
  for (const child of block.children) {
25815
26472
  if (isSequenceBlock(child)) {
25816
26473
  indices.push(...collectMsgIndicesFromBlock(child));
25817
26474
  } else if (!isSequenceSection(child) && !isSequenceNote(child)) {
25818
- const idx = messages.indexOf(child);
26475
+ const idx = findMsgIndex(child);
25819
26476
  if (idx >= 0) indices.push(idx);
25820
26477
  }
25821
26478
  }
@@ -25825,7 +26482,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
25825
26482
  if (isSequenceBlock(child)) {
25826
26483
  indices.push(...collectMsgIndicesFromBlock(child));
25827
26484
  } else if (!isSequenceSection(child) && !isSequenceNote(child)) {
25828
- const idx = messages.indexOf(child);
26485
+ const idx = findMsgIndex(child);
25829
26486
  if (idx >= 0) indices.push(idx);
25830
26487
  }
25831
26488
  }
@@ -25835,7 +26492,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
25835
26492
  if (isSequenceBlock(child)) {
25836
26493
  indices.push(...collectMsgIndicesFromBlock(child));
25837
26494
  } else if (!isSequenceSection(child) && !isSequenceNote(child)) {
25838
- const idx = messages.indexOf(child);
26495
+ const idx = findMsgIndex(child);
25839
26496
  if (idx >= 0) indices.push(idx);
25840
26497
  }
25841
26498
  }
@@ -25850,7 +26507,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
25850
26507
  } else if (isSequenceBlock(el)) {
25851
26508
  currentTarget.push(...collectMsgIndicesFromBlock(el));
25852
26509
  } else {
25853
- const idx = messages.indexOf(el);
26510
+ const idx = findMsgIndex(el);
25854
26511
  if (idx >= 0) currentTarget.push(idx);
25855
26512
  }
25856
26513
  }
@@ -25912,7 +26569,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
25912
26569
  const titleOffset = title ? TITLE_HEIGHT5 : 0;
25913
26570
  const LEGEND_FIXED_GAP4 = 8;
25914
26571
  const legendTopSpace = parsed.tagGroups.length > 0 ? LEGEND_HEIGHT + LEGEND_FIXED_GAP4 : 0;
25915
- const groupOffset = groups.length > 0 ? GROUP_PADDING_TOP + GROUP_LABEL_SIZE : 0;
26572
+ const groupOffset = parsed.groups.length > 0 ? GROUP_PADDING_TOP + GROUP_LABEL_SIZE : 0;
25916
26573
  const participantStartY = TOP_MARGIN + titleOffset + legendTopSpace + PARTICIPANT_Y_OFFSET + groupOffset;
25917
26574
  const lifelineStartY0 = participantStartY + PARTICIPANT_BOX_HEIGHT;
25918
26575
  const hasActors = participants.some((p) => p.type === "actor");
@@ -26082,6 +26739,17 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26082
26739
  svgWidth
26083
26740
  );
26084
26741
  }
26742
+ const collapsedGroupNames = /* @__PURE__ */ new Set();
26743
+ const collapsedGroupMeta = /* @__PURE__ */ new Map();
26744
+ for (const group of parsed.groups) {
26745
+ if (effectiveCollapsedGroups.has(group.lineNumber)) {
26746
+ collapsedGroupNames.add(group.name);
26747
+ collapsedGroupMeta.set(group.name, {
26748
+ lineNumber: group.lineNumber,
26749
+ metadata: group.metadata
26750
+ });
26751
+ }
26752
+ }
26085
26753
  for (const group of groups) {
26086
26754
  if (group.participantIds.length === 0) continue;
26087
26755
  const memberXs = group.participantIds.map((id) => participantX.get(id)).filter((x) => x !== void 0);
@@ -26098,8 +26766,10 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26098
26766
  isDark ? 15 : 20
26099
26767
  ) : isDark ? palette.surface : palette.bg;
26100
26768
  const strokeColor = groupTagColor || palette.textMuted;
26101
- 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));
26102
- 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);
26769
+ const groupG = svg.append("g").attr("class", "group-box-wrapper").attr("data-group-toggle", "").attr("data-group-line", String(group.lineNumber)).attr("cursor", "pointer");
26770
+ groupG.append("title").text("Click to collapse");
26771
+ 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");
26772
+ 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);
26103
26773
  }
26104
26774
  const lifelineStartY = lifelineStartY0;
26105
26775
  participants.forEach((participant, index) => {
@@ -26108,6 +26778,14 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26108
26778
  const pTagValue = tagMap?.participants.get(participant.id);
26109
26779
  const pTagColor = getTagColor(pTagValue);
26110
26780
  const pTagAttr = tagKey && pTagValue ? { key: tagKey, value: pTagValue.toLowerCase() } : void 0;
26781
+ const isCollapsedGroup = collapsedGroupNames.has(participant.id);
26782
+ let effectiveTagColor = pTagColor;
26783
+ if (isCollapsedGroup && !effectiveTagColor) {
26784
+ const meta = collapsedGroupMeta.get(participant.id);
26785
+ if (meta?.metadata && tagKey) {
26786
+ effectiveTagColor = getTagColor(meta.metadata[tagKey]);
26787
+ }
26788
+ }
26111
26789
  renderParticipant(
26112
26790
  svg,
26113
26791
  participant,
@@ -26115,10 +26793,35 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26115
26793
  cy,
26116
26794
  palette,
26117
26795
  isDark,
26118
- pTagColor,
26796
+ effectiveTagColor,
26119
26797
  pTagAttr
26120
26798
  );
26121
- 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);
26799
+ if (isCollapsedGroup) {
26800
+ const meta = collapsedGroupMeta.get(participant.id);
26801
+ const drillColor = effectiveTagColor || palette.textMuted;
26802
+ const drillBarH = 6;
26803
+ const boxW = PARTICIPANT_BOX_WIDTH;
26804
+ const fullH = PARTICIPANT_BOX_HEIGHT + GROUP_PADDING_TOP + GROUP_PADDING_BOTTOM;
26805
+ const clipId = `clip-drill-group-${participant.id.replace(/[^a-zA-Z0-9-]/g, "-")}`;
26806
+ const participantG = svg.select(
26807
+ `.participant[data-participant-id="${participant.id}"]`
26808
+ );
26809
+ participantG.attr("data-group-toggle", "").attr("data-group-line", String(meta.lineNumber)).attr("cursor", "pointer");
26810
+ participantG.append("title").text("Click to expand");
26811
+ const pFill = effectiveTagColor ? mix(
26812
+ effectiveTagColor,
26813
+ isDark ? palette.surface : palette.bg,
26814
+ isDark ? 30 : 40
26815
+ ) : isDark ? mix(palette.overlay, palette.surface, 50) : mix(palette.bg, palette.surface, 50);
26816
+ const pStroke = effectiveTagColor || palette.border;
26817
+ 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);
26818
+ 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);
26819
+ 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);
26820
+ 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})`);
26821
+ }
26822
+ const llY = isCollapsedGroup ? lifelineStartY + GROUP_PADDING_BOTTOM : lifelineStartY;
26823
+ const llColor = isCollapsedGroup ? effectiveTagColor || palette.textMuted : pTagColor || palette.textMuted;
26824
+ 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);
26122
26825
  if (tagKey && pTagValue) {
26123
26826
  lifelineEl.attr(`data-tag-${tagKey}`, pTagValue.toLowerCase());
26124
26827
  }
@@ -26346,23 +27049,8 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26346
27049
  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");
26347
27050
  const msgCount = sectionMsgCounts.get(sec.lineNumber) ?? 0;
26348
27051
  const labelText = isCollapsed ? `${sec.label} (${msgCount} ${msgCount === 1 ? "message" : "messages"})` : sec.label;
26349
- const labelColor = isCollapsed ? "#ffffff" : lineColor;
26350
- const chevronSpace = 14;
26351
27052
  const labelX = (sectionLineX1 + sectionLineX2) / 2;
26352
- const chevronX = labelX - (labelText.length * 3.5 + 8 + chevronSpace / 2);
26353
- const chevronY = secY;
26354
- if (isCollapsed) {
26355
- sectionG.append("path").attr(
26356
- "d",
26357
- `M ${chevronX} ${chevronY - 4} L ${chevronX + 6} ${chevronY} L ${chevronX} ${chevronY + 4} Z`
26358
- ).attr("fill", labelColor).attr("class", "section-chevron");
26359
- } else {
26360
- sectionG.append("path").attr(
26361
- "d",
26362
- `M ${chevronX - 1} ${chevronY - 3} L ${chevronX + 7} ${chevronY - 3} L ${chevronX + 3} ${chevronY + 3} Z`
26363
- ).attr("fill", labelColor).attr("class", "section-chevron");
26364
- }
26365
- 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);
27053
+ 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);
26366
27054
  }
26367
27055
  const SELF_CALL_WIDTH = 30;
26368
27056
  const SELF_CALL_HEIGHT = 25;
@@ -26644,6 +27332,7 @@ var init_renderer10 = __esm({
26644
27332
  init_inline_markdown();
26645
27333
  init_fonts();
26646
27334
  init_parser();
27335
+ init_collapse3();
26647
27336
  init_tag_resolution();
26648
27337
  init_tag_groups();
26649
27338
  init_legend_constants();
@@ -30967,6 +31656,463 @@ var init_d3 = __esm({
30967
31656
  }
30968
31657
  });
30969
31658
 
31659
+ // node_modules/.pnpm/lz-string@1.5.0/node_modules/lz-string/libs/lz-string.js
31660
+ var require_lz_string = __commonJS({
31661
+ "node_modules/.pnpm/lz-string@1.5.0/node_modules/lz-string/libs/lz-string.js"(exports2, module2) {
31662
+ "use strict";
31663
+ var LZString = (function() {
31664
+ var f = String.fromCharCode;
31665
+ var keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
31666
+ var keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$";
31667
+ var baseReverseDic = {};
31668
+ function getBaseValue(alphabet, character) {
31669
+ if (!baseReverseDic[alphabet]) {
31670
+ baseReverseDic[alphabet] = {};
31671
+ for (var i = 0; i < alphabet.length; i++) {
31672
+ baseReverseDic[alphabet][alphabet.charAt(i)] = i;
31673
+ }
31674
+ }
31675
+ return baseReverseDic[alphabet][character];
31676
+ }
31677
+ var LZString2 = {
31678
+ compressToBase64: function(input) {
31679
+ if (input == null) return "";
31680
+ var res = LZString2._compress(input, 6, function(a) {
31681
+ return keyStrBase64.charAt(a);
31682
+ });
31683
+ switch (res.length % 4) {
31684
+ // To produce valid Base64
31685
+ default:
31686
+ // When could this happen ?
31687
+ case 0:
31688
+ return res;
31689
+ case 1:
31690
+ return res + "===";
31691
+ case 2:
31692
+ return res + "==";
31693
+ case 3:
31694
+ return res + "=";
31695
+ }
31696
+ },
31697
+ decompressFromBase64: function(input) {
31698
+ if (input == null) return "";
31699
+ if (input == "") return null;
31700
+ return LZString2._decompress(input.length, 32, function(index) {
31701
+ return getBaseValue(keyStrBase64, input.charAt(index));
31702
+ });
31703
+ },
31704
+ compressToUTF16: function(input) {
31705
+ if (input == null) return "";
31706
+ return LZString2._compress(input, 15, function(a) {
31707
+ return f(a + 32);
31708
+ }) + " ";
31709
+ },
31710
+ decompressFromUTF16: function(compressed) {
31711
+ if (compressed == null) return "";
31712
+ if (compressed == "") return null;
31713
+ return LZString2._decompress(compressed.length, 16384, function(index) {
31714
+ return compressed.charCodeAt(index) - 32;
31715
+ });
31716
+ },
31717
+ //compress into uint8array (UCS-2 big endian format)
31718
+ compressToUint8Array: function(uncompressed) {
31719
+ var compressed = LZString2.compress(uncompressed);
31720
+ var buf = new Uint8Array(compressed.length * 2);
31721
+ for (var i = 0, TotalLen = compressed.length; i < TotalLen; i++) {
31722
+ var current_value = compressed.charCodeAt(i);
31723
+ buf[i * 2] = current_value >>> 8;
31724
+ buf[i * 2 + 1] = current_value % 256;
31725
+ }
31726
+ return buf;
31727
+ },
31728
+ //decompress from uint8array (UCS-2 big endian format)
31729
+ decompressFromUint8Array: function(compressed) {
31730
+ if (compressed === null || compressed === void 0) {
31731
+ return LZString2.decompress(compressed);
31732
+ } else {
31733
+ var buf = new Array(compressed.length / 2);
31734
+ for (var i = 0, TotalLen = buf.length; i < TotalLen; i++) {
31735
+ buf[i] = compressed[i * 2] * 256 + compressed[i * 2 + 1];
31736
+ }
31737
+ var result = [];
31738
+ buf.forEach(function(c) {
31739
+ result.push(f(c));
31740
+ });
31741
+ return LZString2.decompress(result.join(""));
31742
+ }
31743
+ },
31744
+ //compress into a string that is already URI encoded
31745
+ compressToEncodedURIComponent: function(input) {
31746
+ if (input == null) return "";
31747
+ return LZString2._compress(input, 6, function(a) {
31748
+ return keyStrUriSafe.charAt(a);
31749
+ });
31750
+ },
31751
+ //decompress from an output of compressToEncodedURIComponent
31752
+ decompressFromEncodedURIComponent: function(input) {
31753
+ if (input == null) return "";
31754
+ if (input == "") return null;
31755
+ input = input.replace(/ /g, "+");
31756
+ return LZString2._decompress(input.length, 32, function(index) {
31757
+ return getBaseValue(keyStrUriSafe, input.charAt(index));
31758
+ });
31759
+ },
31760
+ compress: function(uncompressed) {
31761
+ return LZString2._compress(uncompressed, 16, function(a) {
31762
+ return f(a);
31763
+ });
31764
+ },
31765
+ _compress: function(uncompressed, bitsPerChar, getCharFromInt) {
31766
+ if (uncompressed == null) return "";
31767
+ var i, value, context_dictionary = {}, context_dictionaryToCreate = {}, context_c = "", context_wc = "", context_w = "", context_enlargeIn = 2, context_dictSize = 3, context_numBits = 2, context_data = [], context_data_val = 0, context_data_position = 0, ii;
31768
+ for (ii = 0; ii < uncompressed.length; ii += 1) {
31769
+ context_c = uncompressed.charAt(ii);
31770
+ if (!Object.prototype.hasOwnProperty.call(context_dictionary, context_c)) {
31771
+ context_dictionary[context_c] = context_dictSize++;
31772
+ context_dictionaryToCreate[context_c] = true;
31773
+ }
31774
+ context_wc = context_w + context_c;
31775
+ if (Object.prototype.hasOwnProperty.call(context_dictionary, context_wc)) {
31776
+ context_w = context_wc;
31777
+ } else {
31778
+ if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
31779
+ if (context_w.charCodeAt(0) < 256) {
31780
+ for (i = 0; i < context_numBits; i++) {
31781
+ context_data_val = context_data_val << 1;
31782
+ if (context_data_position == bitsPerChar - 1) {
31783
+ context_data_position = 0;
31784
+ context_data.push(getCharFromInt(context_data_val));
31785
+ context_data_val = 0;
31786
+ } else {
31787
+ context_data_position++;
31788
+ }
31789
+ }
31790
+ value = context_w.charCodeAt(0);
31791
+ for (i = 0; i < 8; i++) {
31792
+ context_data_val = context_data_val << 1 | value & 1;
31793
+ if (context_data_position == bitsPerChar - 1) {
31794
+ context_data_position = 0;
31795
+ context_data.push(getCharFromInt(context_data_val));
31796
+ context_data_val = 0;
31797
+ } else {
31798
+ context_data_position++;
31799
+ }
31800
+ value = value >> 1;
31801
+ }
31802
+ } else {
31803
+ value = 1;
31804
+ for (i = 0; i < context_numBits; i++) {
31805
+ context_data_val = context_data_val << 1 | value;
31806
+ if (context_data_position == bitsPerChar - 1) {
31807
+ context_data_position = 0;
31808
+ context_data.push(getCharFromInt(context_data_val));
31809
+ context_data_val = 0;
31810
+ } else {
31811
+ context_data_position++;
31812
+ }
31813
+ value = 0;
31814
+ }
31815
+ value = context_w.charCodeAt(0);
31816
+ for (i = 0; i < 16; i++) {
31817
+ context_data_val = context_data_val << 1 | value & 1;
31818
+ if (context_data_position == bitsPerChar - 1) {
31819
+ context_data_position = 0;
31820
+ context_data.push(getCharFromInt(context_data_val));
31821
+ context_data_val = 0;
31822
+ } else {
31823
+ context_data_position++;
31824
+ }
31825
+ value = value >> 1;
31826
+ }
31827
+ }
31828
+ context_enlargeIn--;
31829
+ if (context_enlargeIn == 0) {
31830
+ context_enlargeIn = Math.pow(2, context_numBits);
31831
+ context_numBits++;
31832
+ }
31833
+ delete context_dictionaryToCreate[context_w];
31834
+ } else {
31835
+ value = context_dictionary[context_w];
31836
+ for (i = 0; i < context_numBits; i++) {
31837
+ context_data_val = context_data_val << 1 | value & 1;
31838
+ if (context_data_position == bitsPerChar - 1) {
31839
+ context_data_position = 0;
31840
+ context_data.push(getCharFromInt(context_data_val));
31841
+ context_data_val = 0;
31842
+ } else {
31843
+ context_data_position++;
31844
+ }
31845
+ value = value >> 1;
31846
+ }
31847
+ }
31848
+ context_enlargeIn--;
31849
+ if (context_enlargeIn == 0) {
31850
+ context_enlargeIn = Math.pow(2, context_numBits);
31851
+ context_numBits++;
31852
+ }
31853
+ context_dictionary[context_wc] = context_dictSize++;
31854
+ context_w = String(context_c);
31855
+ }
31856
+ }
31857
+ if (context_w !== "") {
31858
+ if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
31859
+ if (context_w.charCodeAt(0) < 256) {
31860
+ for (i = 0; i < context_numBits; i++) {
31861
+ context_data_val = context_data_val << 1;
31862
+ if (context_data_position == bitsPerChar - 1) {
31863
+ context_data_position = 0;
31864
+ context_data.push(getCharFromInt(context_data_val));
31865
+ context_data_val = 0;
31866
+ } else {
31867
+ context_data_position++;
31868
+ }
31869
+ }
31870
+ value = context_w.charCodeAt(0);
31871
+ for (i = 0; i < 8; i++) {
31872
+ context_data_val = context_data_val << 1 | value & 1;
31873
+ if (context_data_position == bitsPerChar - 1) {
31874
+ context_data_position = 0;
31875
+ context_data.push(getCharFromInt(context_data_val));
31876
+ context_data_val = 0;
31877
+ } else {
31878
+ context_data_position++;
31879
+ }
31880
+ value = value >> 1;
31881
+ }
31882
+ } else {
31883
+ value = 1;
31884
+ for (i = 0; i < context_numBits; i++) {
31885
+ context_data_val = context_data_val << 1 | value;
31886
+ if (context_data_position == bitsPerChar - 1) {
31887
+ context_data_position = 0;
31888
+ context_data.push(getCharFromInt(context_data_val));
31889
+ context_data_val = 0;
31890
+ } else {
31891
+ context_data_position++;
31892
+ }
31893
+ value = 0;
31894
+ }
31895
+ value = context_w.charCodeAt(0);
31896
+ for (i = 0; i < 16; i++) {
31897
+ context_data_val = context_data_val << 1 | value & 1;
31898
+ if (context_data_position == bitsPerChar - 1) {
31899
+ context_data_position = 0;
31900
+ context_data.push(getCharFromInt(context_data_val));
31901
+ context_data_val = 0;
31902
+ } else {
31903
+ context_data_position++;
31904
+ }
31905
+ value = value >> 1;
31906
+ }
31907
+ }
31908
+ context_enlargeIn--;
31909
+ if (context_enlargeIn == 0) {
31910
+ context_enlargeIn = Math.pow(2, context_numBits);
31911
+ context_numBits++;
31912
+ }
31913
+ delete context_dictionaryToCreate[context_w];
31914
+ } else {
31915
+ value = context_dictionary[context_w];
31916
+ for (i = 0; i < context_numBits; i++) {
31917
+ context_data_val = context_data_val << 1 | value & 1;
31918
+ if (context_data_position == bitsPerChar - 1) {
31919
+ context_data_position = 0;
31920
+ context_data.push(getCharFromInt(context_data_val));
31921
+ context_data_val = 0;
31922
+ } else {
31923
+ context_data_position++;
31924
+ }
31925
+ value = value >> 1;
31926
+ }
31927
+ }
31928
+ context_enlargeIn--;
31929
+ if (context_enlargeIn == 0) {
31930
+ context_enlargeIn = Math.pow(2, context_numBits);
31931
+ context_numBits++;
31932
+ }
31933
+ }
31934
+ value = 2;
31935
+ for (i = 0; i < context_numBits; i++) {
31936
+ context_data_val = context_data_val << 1 | value & 1;
31937
+ if (context_data_position == bitsPerChar - 1) {
31938
+ context_data_position = 0;
31939
+ context_data.push(getCharFromInt(context_data_val));
31940
+ context_data_val = 0;
31941
+ } else {
31942
+ context_data_position++;
31943
+ }
31944
+ value = value >> 1;
31945
+ }
31946
+ while (true) {
31947
+ context_data_val = context_data_val << 1;
31948
+ if (context_data_position == bitsPerChar - 1) {
31949
+ context_data.push(getCharFromInt(context_data_val));
31950
+ break;
31951
+ } else context_data_position++;
31952
+ }
31953
+ return context_data.join("");
31954
+ },
31955
+ decompress: function(compressed) {
31956
+ if (compressed == null) return "";
31957
+ if (compressed == "") return null;
31958
+ return LZString2._decompress(compressed.length, 32768, function(index) {
31959
+ return compressed.charCodeAt(index);
31960
+ });
31961
+ },
31962
+ _decompress: function(length, resetValue, getNextValue) {
31963
+ var dictionary = [], next, enlargeIn = 4, dictSize = 4, numBits = 3, entry = "", result = [], i, w, bits, resb, maxpower, power, c, data = { val: getNextValue(0), position: resetValue, index: 1 };
31964
+ for (i = 0; i < 3; i += 1) {
31965
+ dictionary[i] = i;
31966
+ }
31967
+ bits = 0;
31968
+ maxpower = Math.pow(2, 2);
31969
+ power = 1;
31970
+ while (power != maxpower) {
31971
+ resb = data.val & data.position;
31972
+ data.position >>= 1;
31973
+ if (data.position == 0) {
31974
+ data.position = resetValue;
31975
+ data.val = getNextValue(data.index++);
31976
+ }
31977
+ bits |= (resb > 0 ? 1 : 0) * power;
31978
+ power <<= 1;
31979
+ }
31980
+ switch (next = bits) {
31981
+ case 0:
31982
+ bits = 0;
31983
+ maxpower = Math.pow(2, 8);
31984
+ power = 1;
31985
+ while (power != maxpower) {
31986
+ resb = data.val & data.position;
31987
+ data.position >>= 1;
31988
+ if (data.position == 0) {
31989
+ data.position = resetValue;
31990
+ data.val = getNextValue(data.index++);
31991
+ }
31992
+ bits |= (resb > 0 ? 1 : 0) * power;
31993
+ power <<= 1;
31994
+ }
31995
+ c = f(bits);
31996
+ break;
31997
+ case 1:
31998
+ bits = 0;
31999
+ maxpower = Math.pow(2, 16);
32000
+ power = 1;
32001
+ while (power != maxpower) {
32002
+ resb = data.val & data.position;
32003
+ data.position >>= 1;
32004
+ if (data.position == 0) {
32005
+ data.position = resetValue;
32006
+ data.val = getNextValue(data.index++);
32007
+ }
32008
+ bits |= (resb > 0 ? 1 : 0) * power;
32009
+ power <<= 1;
32010
+ }
32011
+ c = f(bits);
32012
+ break;
32013
+ case 2:
32014
+ return "";
32015
+ }
32016
+ dictionary[3] = c;
32017
+ w = c;
32018
+ result.push(c);
32019
+ while (true) {
32020
+ if (data.index > length) {
32021
+ return "";
32022
+ }
32023
+ bits = 0;
32024
+ maxpower = Math.pow(2, numBits);
32025
+ power = 1;
32026
+ while (power != maxpower) {
32027
+ resb = data.val & data.position;
32028
+ data.position >>= 1;
32029
+ if (data.position == 0) {
32030
+ data.position = resetValue;
32031
+ data.val = getNextValue(data.index++);
32032
+ }
32033
+ bits |= (resb > 0 ? 1 : 0) * power;
32034
+ power <<= 1;
32035
+ }
32036
+ switch (c = bits) {
32037
+ case 0:
32038
+ bits = 0;
32039
+ maxpower = Math.pow(2, 8);
32040
+ power = 1;
32041
+ while (power != maxpower) {
32042
+ resb = data.val & data.position;
32043
+ data.position >>= 1;
32044
+ if (data.position == 0) {
32045
+ data.position = resetValue;
32046
+ data.val = getNextValue(data.index++);
32047
+ }
32048
+ bits |= (resb > 0 ? 1 : 0) * power;
32049
+ power <<= 1;
32050
+ }
32051
+ dictionary[dictSize++] = f(bits);
32052
+ c = dictSize - 1;
32053
+ enlargeIn--;
32054
+ break;
32055
+ case 1:
32056
+ bits = 0;
32057
+ maxpower = Math.pow(2, 16);
32058
+ power = 1;
32059
+ while (power != maxpower) {
32060
+ resb = data.val & data.position;
32061
+ data.position >>= 1;
32062
+ if (data.position == 0) {
32063
+ data.position = resetValue;
32064
+ data.val = getNextValue(data.index++);
32065
+ }
32066
+ bits |= (resb > 0 ? 1 : 0) * power;
32067
+ power <<= 1;
32068
+ }
32069
+ dictionary[dictSize++] = f(bits);
32070
+ c = dictSize - 1;
32071
+ enlargeIn--;
32072
+ break;
32073
+ case 2:
32074
+ return result.join("");
32075
+ }
32076
+ if (enlargeIn == 0) {
32077
+ enlargeIn = Math.pow(2, numBits);
32078
+ numBits++;
32079
+ }
32080
+ if (dictionary[c]) {
32081
+ entry = dictionary[c];
32082
+ } else {
32083
+ if (c === dictSize) {
32084
+ entry = w + w.charAt(0);
32085
+ } else {
32086
+ return null;
32087
+ }
32088
+ }
32089
+ result.push(entry);
32090
+ dictionary[dictSize++] = w + entry.charAt(0);
32091
+ enlargeIn--;
32092
+ w = entry;
32093
+ if (enlargeIn == 0) {
32094
+ enlargeIn = Math.pow(2, numBits);
32095
+ numBits++;
32096
+ }
32097
+ }
32098
+ }
32099
+ };
32100
+ return LZString2;
32101
+ })();
32102
+ if (typeof define === "function" && define.amd) {
32103
+ define(function() {
32104
+ return LZString;
32105
+ });
32106
+ } else if (typeof module2 !== "undefined" && module2 != null) {
32107
+ module2.exports = LZString;
32108
+ } else if (typeof angular !== "undefined" && angular != null) {
32109
+ angular.module("LZString", []).factory("LZString", function() {
32110
+ return LZString;
32111
+ });
32112
+ }
32113
+ }
32114
+ });
32115
+
30970
32116
  // src/index.ts
30971
32117
  var index_exports = {};
30972
32118
  __export(index_exports, {
@@ -30981,6 +32127,7 @@ __export(index_exports, {
30981
32127
  RECOGNIZED_COLOR_NAMES: () => RECOGNIZED_COLOR_NAMES,
30982
32128
  RULE_COUNT: () => RULE_COUNT,
30983
32129
  addDurationToDate: () => addDurationToDate,
32130
+ applyCollapseProjection: () => applyCollapseProjection,
30984
32131
  applyGroupOrdering: () => applyGroupOrdering,
30985
32132
  applyPositionOverrides: () => applyPositionOverrides,
30986
32133
  boldPalette: () => boldPalette,
@@ -31010,8 +32157,10 @@ __export(index_exports, {
31010
32157
  computeTimeTicks: () => computeTimeTicks,
31011
32158
  contrastText: () => contrastText,
31012
32159
  decodeDiagramUrl: () => decodeDiagramUrl,
32160
+ decodeViewState: () => decodeViewState,
31013
32161
  draculaPalette: () => draculaPalette,
31014
32162
  encodeDiagramUrl: () => encodeDiagramUrl,
32163
+ encodeViewState: () => encodeViewState,
31015
32164
  extractDiagramSymbols: () => extractDiagramSymbols,
31016
32165
  extractTagDeclarations: () => extractTagDeclarations,
31017
32166
  formatDateLabel: () => formatDateLabel,
@@ -31921,13 +33070,32 @@ init_legend_d3();
31921
33070
  init_legend_layout();
31922
33071
  init_d3();
31923
33072
  init_renderer10();
33073
+ init_collapse3();
31924
33074
  init_colors();
31925
33075
  init_palettes();
31926
33076
 
31927
33077
  // src/sharing.ts
31928
- var import_lz_string = require("lz-string");
33078
+ var import_lz_string = __toESM(require_lz_string(), 1);
31929
33079
  var DEFAULT_BASE_URL = "https://online.diagrammo.app";
31930
33080
  var COMPRESSED_SIZE_LIMIT = 8192;
33081
+ function encodeViewState(state) {
33082
+ const keys = Object.keys(state);
33083
+ if (keys.length === 0) return "";
33084
+ return (0, import_lz_string.compressToEncodedURIComponent)(JSON.stringify(state));
33085
+ }
33086
+ function decodeViewState(encoded) {
33087
+ if (!encoded) return {};
33088
+ try {
33089
+ const json = (0, import_lz_string.decompressFromEncodedURIComponent)(encoded);
33090
+ if (!json) return {};
33091
+ const parsed = JSON.parse(json);
33092
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
33093
+ return {};
33094
+ return parsed;
33095
+ } catch {
33096
+ return {};
33097
+ }
33098
+ }
31931
33099
  function encodeDiagramUrl(dsl, options) {
31932
33100
  const baseUrl = options?.baseUrl ?? DEFAULT_BASE_URL;
31933
33101
  const compressed = (0, import_lz_string.compressToEncodedURIComponent)(dsl);
@@ -31940,23 +33108,17 @@ function encodeDiagramUrl(dsl, options) {
31940
33108
  };
31941
33109
  }
31942
33110
  let hash = `dgmo=${compressed}`;
31943
- if (options?.viewState?.activeTagGroup) {
31944
- hash += `&tag=${encodeURIComponent(options.viewState.activeTagGroup)}`;
31945
- }
31946
- if (options?.viewState?.collapsedGroups?.length) {
31947
- hash += `&cg=${encodeURIComponent(options.viewState.collapsedGroups.join(","))}`;
31948
- }
31949
- if (options?.viewState?.swimlaneTagGroup) {
31950
- hash += `&swim=${encodeURIComponent(options.viewState.swimlaneTagGroup)}`;
31951
- }
31952
- if (options?.viewState?.collapsedLanes?.length) {
31953
- hash += `&cl=${encodeURIComponent(options.viewState.collapsedLanes.join(","))}`;
33111
+ if (options?.viewState) {
33112
+ const vsEncoded = encodeViewState(options.viewState);
33113
+ if (vsEncoded) {
33114
+ hash += `&vs=${vsEncoded}`;
33115
+ }
31954
33116
  }
31955
- if (options?.viewState?.palette && options.viewState.palette !== "nord") {
31956
- hash += `&pal=${encodeURIComponent(options.viewState.palette)}`;
33117
+ if (options?.palette && options.palette !== "nord") {
33118
+ hash += `&pal=${encodeURIComponent(options.palette)}`;
31957
33119
  }
31958
- if (options?.viewState?.theme && options.viewState.theme !== "dark") {
31959
- hash += `&th=${encodeURIComponent(options.viewState.theme)}`;
33120
+ if (options?.theme && options.theme !== "dark") {
33121
+ hash += `&th=${encodeURIComponent(options.theme)}`;
31960
33122
  }
31961
33123
  if (options?.filename) {
31962
33124
  hash += `&fn=${encodeURIComponent(options.filename)}`;
@@ -31966,6 +33128,8 @@ function encodeDiagramUrl(dsl, options) {
31966
33128
  function decodeDiagramUrl(hash) {
31967
33129
  const empty = { dsl: "", viewState: {} };
31968
33130
  let filename;
33131
+ let palette;
33132
+ let theme;
31969
33133
  if (!hash) return empty;
31970
33134
  let raw = hash;
31971
33135
  if (raw.startsWith("#") || raw.startsWith("?")) {
@@ -31973,38 +33137,31 @@ function decodeDiagramUrl(hash) {
31973
33137
  }
31974
33138
  const parts = raw.split("&");
31975
33139
  let payload = parts[0];
31976
- const viewState = {};
33140
+ let viewState = {};
31977
33141
  for (let i = 1; i < parts.length; i++) {
31978
33142
  const eq = parts[i].indexOf("=");
31979
33143
  if (eq === -1) continue;
31980
33144
  const key = parts[i].slice(0, eq);
31981
- const val = decodeURIComponent(parts[i].slice(eq + 1));
31982
- if (key === "tag" && val) {
31983
- viewState.activeTagGroup = val;
31984
- }
31985
- if (key === "cg" && val) {
31986
- viewState.collapsedGroups = val.split(",").filter(Boolean);
31987
- }
31988
- if (key === "swim" && val) {
31989
- viewState.swimlaneTagGroup = val;
33145
+ const val = parts[i].slice(eq + 1);
33146
+ if (key === "vs" && val) {
33147
+ viewState = decodeViewState(val);
31990
33148
  }
31991
- if (key === "cl" && val) {
31992
- viewState.collapsedLanes = val.split(",").filter(Boolean);
33149
+ if (key === "pal" && val) palette = decodeURIComponent(val);
33150
+ if (key === "th") {
33151
+ const decoded = decodeURIComponent(val);
33152
+ if (decoded === "light" || decoded === "dark") theme = decoded;
31993
33153
  }
31994
- if (key === "pal" && val) viewState.palette = val;
31995
- if (key === "th" && (val === "light" || val === "dark"))
31996
- viewState.theme = val;
31997
- if (key === "fn" && val) filename = val;
33154
+ if (key === "fn" && val) filename = decodeURIComponent(val);
31998
33155
  }
31999
33156
  if (payload.startsWith("dgmo=")) {
32000
33157
  payload = payload.slice(5);
32001
33158
  }
32002
- if (!payload) return { dsl: "", viewState, filename };
33159
+ if (!payload) return { dsl: "", viewState, palette, theme, filename };
32003
33160
  try {
32004
33161
  const result = (0, import_lz_string.decompressFromEncodedURIComponent)(payload);
32005
- return { dsl: result ?? "", viewState, filename };
33162
+ return { dsl: result ?? "", viewState, palette, theme, filename };
32006
33163
  } catch {
32007
- return { dsl: "", viewState, filename };
33164
+ return { dsl: "", viewState, palette, theme, filename };
32008
33165
  }
32009
33166
  }
32010
33167
 
@@ -32765,6 +33922,7 @@ init_branding();
32765
33922
  RECOGNIZED_COLOR_NAMES,
32766
33923
  RULE_COUNT,
32767
33924
  addDurationToDate,
33925
+ applyCollapseProjection,
32768
33926
  applyGroupOrdering,
32769
33927
  applyPositionOverrides,
32770
33928
  boldPalette,
@@ -32794,8 +33952,10 @@ init_branding();
32794
33952
  computeTimeTicks,
32795
33953
  contrastText,
32796
33954
  decodeDiagramUrl,
33955
+ decodeViewState,
32797
33956
  draculaPalette,
32798
33957
  encodeDiagramUrl,
33958
+ encodeViewState,
32799
33959
  extractDiagramSymbols,
32800
33960
  extractTagDeclarations,
32801
33961
  formatDateLabel,