@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.js CHANGED
@@ -1,12 +1,35 @@
1
+ var __create = Object.create;
1
2
  var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
2
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
3
7
  var __esm = (fn, res) => function __init() {
4
8
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
9
  };
10
+ var __commonJS = (cb, mod) => function __require() {
11
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
+ };
6
13
  var __export = (target, all) => {
7
14
  for (var name in all)
8
15
  __defProp(target, name, { get: all[name], enumerable: true });
9
16
  };
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") {
19
+ for (let key of __getOwnPropNames(from))
20
+ if (!__hasOwnProp.call(to, key) && key !== except)
21
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
22
+ }
23
+ return to;
24
+ };
25
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
26
+ // If the importer is in node compatibility mode or this is not an ESM
27
+ // file that has been converted to a CommonJS file using a Babel-
28
+ // compatible transform (i.e. "__esModule" has not been set), then set
29
+ // "default" to the CommonJS "module.exports" for node compatibility.
30
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
+ mod
32
+ ));
10
33
 
11
34
  // src/diagnostics.ts
12
35
  function makeDgmoError(line10, message, severity = "error") {
@@ -2077,7 +2100,7 @@ function measureLegendText(text, fontSize) {
2077
2100
  }
2078
2101
  return w;
2079
2102
  }
2080
- 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;
2103
+ var LEGEND_HEIGHT, LEGEND_PILL_PAD, LEGEND_PILL_FONT_SIZE, LEGEND_CAPSULE_PAD, LEGEND_DOT_R, LEGEND_ENTRY_FONT_SIZE, LEGEND_ENTRY_DOT_GAP, LEGEND_ENTRY_TRAIL, LEGEND_GROUP_GAP, LEGEND_EYE_SIZE, LEGEND_EYE_GAP, LEGEND_ICON_W, LEGEND_MAX_ENTRY_ROWS, CHAR_W, DEFAULT_W, EYE_OPEN_PATH, EYE_CLOSED_PATH, CONTROLS_ICON_PATH, LEGEND_TOGGLE_DOT_R, LEGEND_TOGGLE_OFF_OPACITY, LEGEND_GEAR_PILL_W;
2081
2104
  var init_legend_constants = __esm({
2082
2105
  "src/utils/legend-constants.ts"() {
2083
2106
  "use strict";
@@ -2184,6 +2207,10 @@ var init_legend_constants = __esm({
2184
2207
  DEFAULT_W = 0.56;
2185
2208
  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";
2186
2209
  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";
2210
+ 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";
2211
+ LEGEND_TOGGLE_DOT_R = LEGEND_DOT_R;
2212
+ LEGEND_TOGGLE_OFF_OPACITY = 0.4;
2213
+ LEGEND_GEAR_PILL_W = 14 + LEGEND_PILL_PAD;
2187
2214
  }
2188
2215
  });
2189
2216
 
@@ -2229,13 +2256,13 @@ function capsuleWidth(name, entries, containerWidth, addonWidth = 0) {
2229
2256
  visibleEntries: entries.length
2230
2257
  };
2231
2258
  }
2232
- const rowWidth = maxCapsuleW - LEGEND_CAPSULE_PAD * 2;
2259
+ const rowWidth = maxCapsuleW - LEGEND_CAPSULE_PAD;
2233
2260
  let row = 1;
2234
- let rowX = pw + 4;
2261
+ let rowX = LEGEND_CAPSULE_PAD + pw + 4 + addonWidth;
2235
2262
  let visible = 0;
2236
2263
  for (let i = 0; i < entries.length; i++) {
2237
2264
  const ew2 = entryWidth(entries[i].value);
2238
- if (rowX + ew2 > rowWidth && rowX > pw + 4) {
2265
+ if (rowX + ew2 > rowWidth && i > 0) {
2239
2266
  row++;
2240
2267
  rowX = 0;
2241
2268
  if (row > LEGEND_MAX_ENTRY_ROWS) {
@@ -2257,6 +2284,63 @@ function capsuleWidth(name, entries, containerWidth, addonWidth = 0) {
2257
2284
  visibleEntries: entries.length
2258
2285
  };
2259
2286
  }
2287
+ function controlsGroupCapsuleWidth(toggles) {
2288
+ let w = LEGEND_CAPSULE_PAD * 2 + LEGEND_GEAR_PILL_W + 4;
2289
+ for (const t of toggles) {
2290
+ w += LEGEND_TOGGLE_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + measureLegendText(t.label, LEGEND_ENTRY_FONT_SIZE) + LEGEND_ENTRY_TRAIL;
2291
+ }
2292
+ return w;
2293
+ }
2294
+ function buildControlsGroupLayout(config, state) {
2295
+ const cg = config.controlsGroup;
2296
+ if (!cg || cg.toggles.length === 0) return void 0;
2297
+ const expanded = !!state.controlsExpanded;
2298
+ const pillH = LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2;
2299
+ if (!expanded) {
2300
+ return {
2301
+ x: 0,
2302
+ y: 0,
2303
+ width: LEGEND_GEAR_PILL_W,
2304
+ height: LEGEND_HEIGHT,
2305
+ expanded: false,
2306
+ pill: { x: 0, y: 0, width: LEGEND_GEAR_PILL_W, height: LEGEND_HEIGHT },
2307
+ toggles: []
2308
+ };
2309
+ }
2310
+ const capsuleW = controlsGroupCapsuleWidth(cg.toggles);
2311
+ const toggleLayouts = [];
2312
+ let tx = LEGEND_CAPSULE_PAD + LEGEND_GEAR_PILL_W + 4;
2313
+ for (const toggle of cg.toggles) {
2314
+ const dotCx = tx + LEGEND_TOGGLE_DOT_R;
2315
+ const dotCy = LEGEND_HEIGHT / 2;
2316
+ const textX = tx + LEGEND_TOGGLE_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP;
2317
+ const textY = LEGEND_HEIGHT / 2;
2318
+ toggleLayouts.push({
2319
+ id: toggle.id,
2320
+ label: toggle.label,
2321
+ active: toggle.active,
2322
+ dotCx,
2323
+ dotCy,
2324
+ textX,
2325
+ textY
2326
+ });
2327
+ tx += LEGEND_TOGGLE_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + measureLegendText(toggle.label, LEGEND_ENTRY_FONT_SIZE) + LEGEND_ENTRY_TRAIL;
2328
+ }
2329
+ return {
2330
+ x: 0,
2331
+ y: 0,
2332
+ width: capsuleW,
2333
+ height: LEGEND_HEIGHT,
2334
+ expanded: true,
2335
+ pill: {
2336
+ x: LEGEND_CAPSULE_PAD,
2337
+ y: LEGEND_CAPSULE_PAD,
2338
+ width: LEGEND_GEAR_PILL_W - LEGEND_CAPSULE_PAD * 2,
2339
+ height: pillH
2340
+ },
2341
+ toggles: toggleLayouts
2342
+ };
2343
+ }
2260
2344
  function computeLegendLayout(config, state, containerWidth) {
2261
2345
  const { groups, controls: configControls, mode } = config;
2262
2346
  const isExport = mode === "inline";
@@ -2271,8 +2355,9 @@ function computeLegendLayout(config, state, containerWidth) {
2271
2355
  activeCapsule: void 0
2272
2356
  };
2273
2357
  }
2358
+ const controlsGroupLayout = isExport ? void 0 : buildControlsGroupLayout(config, state);
2274
2359
  const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0);
2275
- if (visibleGroups.length === 0 && (!configControls || configControls.length === 0)) {
2360
+ if (visibleGroups.length === 0 && (!configControls || configControls.length === 0) && !controlsGroupLayout) {
2276
2361
  return {
2277
2362
  height: 0,
2278
2363
  width: 0,
@@ -2336,7 +2421,8 @@ function computeLegendLayout(config, state, containerWidth) {
2336
2421
  if (totalControlsW > 0) totalControlsW -= CONTROL_GAP;
2337
2422
  }
2338
2423
  const controlsSpace = totalControlsW > 0 ? totalControlsW + LEGEND_GROUP_GAP * 2 : 0;
2339
- const groupAvailW = containerWidth - controlsSpace;
2424
+ const gearSpace = controlsGroupLayout ? controlsGroupLayout.width + LEGEND_GROUP_GAP : 0;
2425
+ const groupAvailW = containerWidth - controlsSpace - gearSpace;
2340
2426
  const pills = [];
2341
2427
  let activeCapsule;
2342
2428
  for (const g of visibleGroups) {
@@ -2345,7 +2431,7 @@ function computeLegendLayout(config, state, containerWidth) {
2345
2431
  if (isActive) {
2346
2432
  activeCapsule = buildCapsuleLayout(
2347
2433
  g,
2348
- containerWidth,
2434
+ groupAvailW,
2349
2435
  config.capsulePillAddonWidth ?? 0
2350
2436
  );
2351
2437
  } else {
@@ -2368,7 +2454,8 @@ function computeLegendLayout(config, state, containerWidth) {
2368
2454
  groupAvailW,
2369
2455
  containerWidth,
2370
2456
  totalControlsW,
2371
- alignLeft
2457
+ alignLeft,
2458
+ controlsGroupLayout
2372
2459
  );
2373
2460
  const height = rows.length * LEGEND_HEIGHT;
2374
2461
  const width = containerWidth;
@@ -2378,7 +2465,8 @@ function computeLegendLayout(config, state, containerWidth) {
2378
2465
  rows,
2379
2466
  activeCapsule,
2380
2467
  controls: controlLayouts,
2381
- pills
2468
+ pills,
2469
+ controlsGroup: controlsGroupLayout
2382
2470
  };
2383
2471
  }
2384
2472
  function buildCapsuleLayout(group, containerWidth, addonWidth = 0) {
@@ -2401,7 +2489,7 @@ function buildCapsuleLayout(group, containerWidth, addonWidth = 0) {
2401
2489
  let ex = LEGEND_CAPSULE_PAD + pw + 4 + addonWidth;
2402
2490
  let ey = 0;
2403
2491
  let rowX = ex;
2404
- const maxRowW = containerWidth - LEGEND_CAPSULE_PAD * 2;
2492
+ const maxRowW = containerWidth - LEGEND_CAPSULE_PAD;
2405
2493
  let currentRow = 0;
2406
2494
  for (let i = 0; i < info.visibleEntries; i++) {
2407
2495
  const entry = group.entries[i];
@@ -2442,19 +2530,27 @@ function buildCapsuleLayout(group, containerWidth, addonWidth = 0) {
2442
2530
  addonX: addonWidth > 0 ? LEGEND_CAPSULE_PAD + pw + 4 : void 0
2443
2531
  };
2444
2532
  }
2445
- function layoutRows(activeCapsule, pills, controls, groupAvailW, containerWidth, totalControlsW, alignLeft = false) {
2533
+ function layoutRows(activeCapsule, pills, controls, groupAvailW, containerWidth, totalControlsW, alignLeft = false, controlsGroup) {
2446
2534
  const rows = [];
2447
2535
  const groupItems = [];
2448
2536
  if (activeCapsule) groupItems.push(activeCapsule);
2449
2537
  groupItems.push(...pills);
2538
+ const gearW = controlsGroup ? controlsGroup.width + LEGEND_GROUP_GAP : 0;
2450
2539
  let currentRowItems = [];
2451
2540
  let currentRowW = 0;
2452
2541
  let rowY = 0;
2453
2542
  for (const item of groupItems) {
2454
2543
  const itemW = item.width + LEGEND_GROUP_GAP;
2455
2544
  if (currentRowW + item.width > groupAvailW && currentRowItems.length > 0) {
2456
- if (!alignLeft)
2457
- centerRowItems(currentRowItems, containerWidth, totalControlsW);
2545
+ if (!alignLeft) {
2546
+ const rowGearW = rows.length === 0 ? gearW : 0;
2547
+ centerRowItems(
2548
+ currentRowItems,
2549
+ containerWidth,
2550
+ totalControlsW,
2551
+ rowGearW
2552
+ );
2553
+ }
2458
2554
  rows.push({ y: rowY, items: currentRowItems });
2459
2555
  rowY += LEGEND_HEIGHT;
2460
2556
  currentRowItems = [];
@@ -2482,19 +2578,32 @@ function layoutRows(activeCapsule, pills, controls, groupAvailW, containerWidth,
2482
2578
  }
2483
2579
  }
2484
2580
  if (currentRowItems.length > 0) {
2485
- centerRowItems(currentRowItems, containerWidth, totalControlsW);
2581
+ centerRowItems(currentRowItems, containerWidth, totalControlsW, gearW);
2486
2582
  rows.push({ y: rowY, items: currentRowItems });
2487
2583
  }
2584
+ if (controlsGroup) {
2585
+ const row0Items = rows[0]?.items ?? [];
2586
+ const groupItemsInRow0 = row0Items.filter(
2587
+ (it) => "groupName" in it
2588
+ );
2589
+ if (groupItemsInRow0.length > 0) {
2590
+ const last = groupItemsInRow0[groupItemsInRow0.length - 1];
2591
+ controlsGroup.x = last.x + last.width + LEGEND_GROUP_GAP;
2592
+ } else {
2593
+ controlsGroup.x = 0;
2594
+ }
2595
+ controlsGroup.y = 0;
2596
+ }
2488
2597
  if (rows.length === 0) {
2489
2598
  rows.push({ y: 0, items: [] });
2490
2599
  }
2491
2600
  return rows;
2492
2601
  }
2493
- function centerRowItems(items, containerWidth, totalControlsW) {
2602
+ function centerRowItems(items, containerWidth, totalControlsW, controlsGroupW = 0) {
2494
2603
  const groupItems = items.filter((it) => "groupName" in it);
2495
2604
  if (groupItems.length === 0) return;
2496
2605
  const totalGroupW = groupItems.reduce((s, it) => s + it.width, 0) + (groupItems.length - 1) * LEGEND_GROUP_GAP;
2497
- const availW = containerWidth - (totalControlsW > 0 ? totalControlsW + LEGEND_GROUP_GAP * 2 : 0);
2606
+ const availW = containerWidth - (totalControlsW > 0 ? totalControlsW + LEGEND_GROUP_GAP * 2 : 0) - controlsGroupW;
2498
2607
  const offset = Math.max(0, (availW - totalGroupW) / 2);
2499
2608
  let x = offset;
2500
2609
  for (const item of groupItems) {
@@ -2552,6 +2661,17 @@ function renderLegendD3(container, config, state, palette, isDark, callbacks, co
2552
2661
  for (const pill of currentLayout.pills) {
2553
2662
  renderPill(legendG, pill, palette, groupBg, callbacks);
2554
2663
  }
2664
+ if (currentLayout.controlsGroup) {
2665
+ renderControlsGroup(
2666
+ legendG,
2667
+ currentLayout.controlsGroup,
2668
+ palette,
2669
+ groupBg,
2670
+ pillBorder,
2671
+ callbacks,
2672
+ config
2673
+ );
2674
+ }
2555
2675
  for (const ctrl of currentLayout.controls) {
2556
2676
  renderControl(
2557
2677
  legendG,
@@ -2666,6 +2786,57 @@ function renderControl(parent, ctrl, palette, _groupBg, pillBorder, _isDark, con
2666
2786
  g.on("click", () => onClick());
2667
2787
  }
2668
2788
  }
2789
+ function renderControlsGroup(parent, layout, palette, groupBg, pillBorder, callbacks, config) {
2790
+ 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");
2791
+ if (!layout.expanded) {
2792
+ g.append("rect").attr("width", layout.width).attr("height", layout.height).attr("rx", layout.height / 2).attr("fill", groupBg);
2793
+ const iconSize = 14;
2794
+ const iconX = (layout.width - iconSize) / 2;
2795
+ const iconY = (layout.height - iconSize) / 2;
2796
+ 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");
2797
+ if (callbacks?.onControlsExpand) {
2798
+ const cb = callbacks.onControlsExpand;
2799
+ g.on("click", () => cb());
2800
+ }
2801
+ } else {
2802
+ const pill = layout.pill;
2803
+ g.append("rect").attr("width", layout.width).attr("height", layout.height).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
2804
+ const pillG = g.append("g").attr("class", "controls-gear-pill").style("cursor", "pointer");
2805
+ 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);
2806
+ 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);
2807
+ const iconSize = 14;
2808
+ const iconX = pill.x + (pill.width - iconSize) / 2;
2809
+ const iconY = pill.y + (pill.height - iconSize) / 2;
2810
+ 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");
2811
+ if (callbacks?.onControlsExpand) {
2812
+ const cb = callbacks.onControlsExpand;
2813
+ pillG.on("click", (event) => {
2814
+ event.stopPropagation();
2815
+ cb();
2816
+ });
2817
+ }
2818
+ const toggles = config?.controlsGroup?.toggles ?? [];
2819
+ for (const tl of layout.toggles) {
2820
+ const toggle = toggles.find((t) => t.id === tl.id);
2821
+ const entryG = g.append("g").attr("data-controls-toggle", tl.id).style("cursor", "pointer");
2822
+ if (tl.active) {
2823
+ entryG.append("circle").attr("cx", tl.dotCx).attr("cy", tl.dotCy).attr("r", LEGEND_TOGGLE_DOT_R).attr("fill", palette.primary ?? palette.text);
2824
+ } else {
2825
+ 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);
2826
+ }
2827
+ 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);
2828
+ if (callbacks?.onControlsToggle && toggle) {
2829
+ const cb = callbacks.onControlsToggle;
2830
+ const id = tl.id;
2831
+ const newActive = !tl.active;
2832
+ entryG.on("click", (event) => {
2833
+ event.stopPropagation();
2834
+ cb(id, newActive);
2835
+ });
2836
+ }
2837
+ }
2838
+ }
2839
+ }
2669
2840
  var init_legend_d3 = __esm({
2670
2841
  "src/utils/legend-d3.ts"() {
2671
2842
  "use strict";
@@ -3219,7 +3390,13 @@ function parseSequenceDgmo(content) {
3219
3390
  const groupName = groupMatch[1].trim();
3220
3391
  const groupColor = groupMatch[2]?.trim();
3221
3392
  let groupMeta;
3222
- const afterBracket = groupMatch[3]?.trim() || "";
3393
+ let afterBracket = groupMatch[3]?.trim() || "";
3394
+ let isCollapsed = false;
3395
+ const collapseMatch = afterBracket.match(/^collapse\b/i);
3396
+ if (collapseMatch) {
3397
+ isCollapsed = true;
3398
+ afterBracket = afterBracket.slice(collapseMatch[0].length).trim();
3399
+ }
3223
3400
  if (afterBracket.startsWith("|")) {
3224
3401
  const segments = afterBracket.split("|");
3225
3402
  const meta = parsePipeMetadata(
@@ -3240,7 +3417,8 @@ function parseSequenceDgmo(content) {
3240
3417
  name: groupName,
3241
3418
  participantIds: [],
3242
3419
  lineNumber,
3243
- ...groupMeta ? { metadata: groupMeta } : {}
3420
+ ...groupMeta ? { metadata: groupMeta } : {},
3421
+ ...isCollapsed ? { collapsed: true } : {}
3244
3422
  };
3245
3423
  result.groups.push(activeGroup);
3246
3424
  continue;
@@ -10337,7 +10515,7 @@ function addBusinessDays(startDate, count, workweek, holidaySet, direction = 1)
10337
10515
  }
10338
10516
  return current;
10339
10517
  }
10340
- function addGanttDuration(startDate, duration, holidays, holidaySet, direction = 1) {
10518
+ function addGanttDuration(startDate, duration, holidays, holidaySet, direction = 1, opts) {
10341
10519
  const { amount, unit } = duration;
10342
10520
  switch (unit) {
10343
10521
  case "bd":
@@ -10399,10 +10577,22 @@ function addGanttDuration(startDate, duration, holidays, holidaySet, direction =
10399
10577
  result.setTime(result.getTime() + amount * 6e4 * direction);
10400
10578
  return result;
10401
10579
  }
10580
+ case "s": {
10581
+ if (!opts?.sprintLength) {
10582
+ throw new Error(
10583
+ 'Sprint duration unit "s" requires sprintLength configuration'
10584
+ );
10585
+ }
10586
+ const sl = opts.sprintLength;
10587
+ const totalDays = amount * sl.amount * (sl.unit === "w" ? 7 : 1);
10588
+ const result = new Date(startDate);
10589
+ result.setDate(result.getDate() + Math.round(totalDays) * direction);
10590
+ return result;
10591
+ }
10402
10592
  }
10403
10593
  }
10404
10594
  function parseDuration(s) {
10405
- const match = s.trim().match(/^(\d+(?:\.\d+)?)(min|bd|d|w|m|q|y|h)$/);
10595
+ const match = s.trim().match(/^(\d+(?:\.\d+)?)(min|bd|d|w|m|q|y|h|s)$/);
10406
10596
  if (!match) return null;
10407
10597
  return { amount: parseFloat(match[1]), unit: match[2] };
10408
10598
  }
@@ -10489,7 +10679,11 @@ function parseGantt(content, palette) {
10489
10679
  defaultSwimlaneGroup: null,
10490
10680
  activeTag: null,
10491
10681
  optionLineNumbers: {},
10492
- holidaysLineNumber: null
10682
+ holidaysLineNumber: null,
10683
+ sprintLength: null,
10684
+ sprintNumber: null,
10685
+ sprintStart: null,
10686
+ sprintMode: null
10493
10687
  },
10494
10688
  diagnostics,
10495
10689
  error: null
@@ -10905,6 +11099,55 @@ function parseGantt(content, palette) {
10905
11099
  case "active-tag":
10906
11100
  result.options.activeTag = value;
10907
11101
  break;
11102
+ case "sprint-length": {
11103
+ const dur = parseDuration(value);
11104
+ if (!dur) {
11105
+ warn(
11106
+ lineNumber,
11107
+ `Invalid sprint-length value: "${value}". Expected a duration like "2w" or "10d".`
11108
+ );
11109
+ } else if (dur.unit !== "d" && dur.unit !== "w") {
11110
+ warn(
11111
+ lineNumber,
11112
+ `sprint-length only accepts "d" or "w" units, got "${dur.unit}".`
11113
+ );
11114
+ } else if (dur.amount <= 0) {
11115
+ warn(lineNumber, `sprint-length must be greater than 0.`);
11116
+ } else if (!Number.isInteger(dur.amount * (dur.unit === "w" ? 7 : 1))) {
11117
+ warn(
11118
+ lineNumber,
11119
+ `sprint-length must resolve to a whole number of days.`
11120
+ );
11121
+ } else {
11122
+ result.options.sprintLength = dur;
11123
+ }
11124
+ break;
11125
+ }
11126
+ case "sprint-number": {
11127
+ const n = Number(value);
11128
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
11129
+ warn(
11130
+ lineNumber,
11131
+ `sprint-number must be a positive integer, got "${value}".`
11132
+ );
11133
+ } else {
11134
+ result.options.sprintNumber = n;
11135
+ }
11136
+ break;
11137
+ }
11138
+ case "sprint-start": {
11139
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
11140
+ warn(
11141
+ lineNumber,
11142
+ `sprint-start requires a full date (YYYY-MM-DD), got "${value}".`
11143
+ );
11144
+ } else if (Number.isNaN((/* @__PURE__ */ new Date(value + "T00:00:00")).getTime())) {
11145
+ warn(lineNumber, `sprint-start is not a valid date: "${value}".`);
11146
+ } else {
11147
+ result.options.sprintStart = value;
11148
+ }
11149
+ break;
11150
+ }
10908
11151
  }
10909
11152
  continue;
10910
11153
  }
@@ -11085,6 +11328,21 @@ function parseGantt(content, palette) {
11085
11328
  result.options.sort = "default";
11086
11329
  }
11087
11330
  validateTagGroupNames(result.tagGroups, warn);
11331
+ const hasSprintOption = result.options.sprintLength !== null || result.options.sprintNumber !== null || result.options.sprintStart !== null;
11332
+ const hasSprintUnit = hasSprintDurationUnit(result.nodes);
11333
+ if (hasSprintOption) {
11334
+ result.options.sprintMode = "explicit";
11335
+ } else if (hasSprintUnit) {
11336
+ result.options.sprintMode = "auto";
11337
+ }
11338
+ if (result.options.sprintMode) {
11339
+ if (!result.options.sprintLength) {
11340
+ result.options.sprintLength = { amount: 2, unit: "w" };
11341
+ }
11342
+ if (result.options.sprintNumber === null) {
11343
+ result.options.sprintNumber = 1;
11344
+ }
11345
+ }
11088
11346
  return result;
11089
11347
  function makeTask(labelRaw, duration, uncertain, ln, explicitStart) {
11090
11348
  const segments = labelRaw.split("|");
@@ -11202,6 +11460,16 @@ function parseWorkweek(s) {
11202
11460
  function isKnownOption(key) {
11203
11461
  return KNOWN_OPTIONS5.has(key);
11204
11462
  }
11463
+ function hasSprintDurationUnit(nodes) {
11464
+ for (const node of nodes) {
11465
+ if (node.kind === "task") {
11466
+ if (node.duration?.unit === "s") return true;
11467
+ } else if (node.kind === "group" || node.kind === "parallel") {
11468
+ if (hasSprintDurationUnit(node.children)) return true;
11469
+ }
11470
+ }
11471
+ return false;
11472
+ }
11205
11473
  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;
11206
11474
  var init_parser9 = __esm({
11207
11475
  "src/gantt/parser.ts"() {
@@ -11211,9 +11479,9 @@ var init_parser9 = __esm({
11211
11479
  init_parsing();
11212
11480
  init_duration();
11213
11481
  init_palettes();
11214
- DURATION_RE = /^(\d+(?:\.\d+)?)(min|bd|d|w|m|q|y|h)(\?)?\s+(.+)$/;
11482
+ DURATION_RE = /^(\d+(?:\.\d+)?)(min|bd|d|w|m|q|y|h|s)(\?)?\s+(.+)$/;
11215
11483
  EXPLICIT_DATE_RE = /^(\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2})?)\s+(.+)$/;
11216
- 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+(.+)$/;
11484
+ 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+(.+)$/;
11217
11485
  GROUP_RE2 = /^\[(.+?)\]\s*(.*)$/;
11218
11486
  DEPENDENCY_RE = /^(?:-(.+?))?->\s*(.+)$/;
11219
11487
  COMMENT_RE = /^\/\//;
@@ -11248,7 +11516,10 @@ var init_parser9 = __esm({
11248
11516
  "dependencies",
11249
11517
  "chart",
11250
11518
  "sort",
11251
- "active-tag"
11519
+ "active-tag",
11520
+ "sprint-length",
11521
+ "sprint-number",
11522
+ "sprint-start"
11252
11523
  ]);
11253
11524
  KNOWN_BOOLEANS4 = /* @__PURE__ */ new Set([
11254
11525
  "critical-path",
@@ -22645,6 +22916,7 @@ function calculateSchedule(parsed) {
22645
22916
  tagGroups: parsed.tagGroups,
22646
22917
  eras: parsed.eras,
22647
22918
  markers: parsed.markers,
22919
+ sprints: [],
22648
22920
  options: parsed.options,
22649
22921
  diagnostics,
22650
22922
  error: parsed.error
@@ -22666,6 +22938,7 @@ function calculateSchedule(parsed) {
22666
22938
  } else {
22667
22939
  projectStart = new Date(2e3, 0, 1);
22668
22940
  }
22941
+ const sprintOpts = parsed.options.sprintLength ? { sprintLength: parsed.options.sprintLength } : void 0;
22669
22942
  const depOffsetMap = /* @__PURE__ */ new Map();
22670
22943
  const allTasks = collectTasks(parsed.nodes);
22671
22944
  if (allTasks.length === 0) {
@@ -22747,7 +23020,8 @@ function calculateSchedule(parsed) {
22747
23020
  depOffset.duration,
22748
23021
  parsed.holidays,
22749
23022
  holidaySet,
22750
- depOffset.direction
23023
+ depOffset.direction,
23024
+ sprintOpts
22751
23025
  );
22752
23026
  }
22753
23027
  if (predEnd.getTime() > start.getTime()) {
@@ -22761,7 +23035,8 @@ function calculateSchedule(parsed) {
22761
23035
  task.offset.duration,
22762
23036
  parsed.holidays,
22763
23037
  holidaySet,
22764
- task.offset.direction
23038
+ task.offset.direction,
23039
+ sprintOpts
22765
23040
  );
22766
23041
  if (start.getTime() < projectStart.getTime()) {
22767
23042
  warn(
@@ -22801,7 +23076,9 @@ function calculateSchedule(parsed) {
22801
23076
  start,
22802
23077
  task.duration,
22803
23078
  parsed.holidays,
22804
- holidaySet
23079
+ holidaySet,
23080
+ 1,
23081
+ sprintOpts
22805
23082
  );
22806
23083
  }
22807
23084
  } else {
@@ -22815,7 +23092,8 @@ function calculateSchedule(parsed) {
22815
23092
  taskMap,
22816
23093
  depOffsetMap,
22817
23094
  parsed.holidays,
22818
- holidaySet
23095
+ holidaySet,
23096
+ sprintOpts
22819
23097
  ) : /* @__PURE__ */ new Set();
22820
23098
  const uncertainSet = /* @__PURE__ */ new Set();
22821
23099
  for (const taskId of sortedIds) {
@@ -22855,6 +23133,20 @@ function calculateSchedule(parsed) {
22855
23133
  result.startDate = minDate;
22856
23134
  result.endDate = maxDate;
22857
23135
  }
23136
+ if (parsed.options.sprintMode && parsed.options.sprintLength && result.tasks.length > 0) {
23137
+ result.sprints = generateSprintBands(
23138
+ parsed.options,
23139
+ result.startDate,
23140
+ result.endDate,
23141
+ projectStart
23142
+ );
23143
+ if (result.sprints.length > 0) {
23144
+ const lastSprintEnd = result.sprints[result.sprints.length - 1].endDate;
23145
+ if (lastSprintEnd.getTime() > result.endDate.getTime()) {
23146
+ result.endDate = lastSprintEnd;
23147
+ }
23148
+ }
23149
+ }
22858
23150
  const topLevelGroups = parsed.nodes.filter((n) => n.kind === "group");
22859
23151
  if (topLevelGroups.length >= 2) {
22860
23152
  const names = topLevelGroups.map(
@@ -23055,7 +23347,7 @@ function breakCycle(cycle, taskMap, depOffsetMap) {
23055
23347
  }
23056
23348
  }
23057
23349
  }
23058
- function computeCriticalPath(sortedIds, taskMap, depOffsetMap, holidays, holidaySet) {
23350
+ function computeCriticalPath(sortedIds, taskMap, depOffsetMap, holidays, holidaySet, sprintOpts) {
23059
23351
  if (sortedIds.length === 0) return /* @__PURE__ */ new Set();
23060
23352
  const latestEnd = /* @__PURE__ */ new Map();
23061
23353
  const latestStart = /* @__PURE__ */ new Map();
@@ -23093,7 +23385,8 @@ function computeCriticalPath(sortedIds, taskMap, depOffsetMap, holidays, holiday
23093
23385
  succTask.offset.duration,
23094
23386
  holidays,
23095
23387
  holidaySet,
23096
- reverseDir
23388
+ reverseDir,
23389
+ sprintOpts
23097
23390
  );
23098
23391
  succLS = adjusted.getTime();
23099
23392
  }
@@ -23105,7 +23398,8 @@ function computeCriticalPath(sortedIds, taskMap, depOffsetMap, holidays, holiday
23105
23398
  depOffset.duration,
23106
23399
  holidays,
23107
23400
  holidaySet,
23108
- reverseDir
23401
+ reverseDir,
23402
+ sprintOpts
23109
23403
  );
23110
23404
  succLS = adjusted.getTime();
23111
23405
  }
@@ -23180,6 +23474,51 @@ function buildResolvedGroups(nodes, taskMap, groups, depth) {
23180
23474
  }
23181
23475
  }
23182
23476
  }
23477
+ function generateSprintBands(options, chartStart, chartEnd, projectStart) {
23478
+ const sprintLength = options.sprintLength;
23479
+ const sprintNumber = options.sprintNumber ?? 1;
23480
+ let anchorDate;
23481
+ if (options.sprintStart) {
23482
+ anchorDate = /* @__PURE__ */ new Date(options.sprintStart + "T00:00:00");
23483
+ } else if (options.start) {
23484
+ anchorDate = new Date(projectStart);
23485
+ } else {
23486
+ const now = /* @__PURE__ */ new Date();
23487
+ anchorDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
23488
+ }
23489
+ anchorDate.setHours(0, 0, 0, 0);
23490
+ const sprintDays = Math.round(
23491
+ sprintLength.amount * (sprintLength.unit === "w" ? 7 : 1)
23492
+ );
23493
+ if (sprintDays <= 0) return [];
23494
+ if (Number.isNaN(anchorDate.getTime())) return [];
23495
+ const chartStartTime = new Date(chartStart);
23496
+ chartStartTime.setHours(0, 0, 0, 0);
23497
+ const chartEndTime = new Date(chartEnd);
23498
+ chartEndTime.setHours(0, 0, 0, 0);
23499
+ const msPerDay = 864e5;
23500
+ const dayDiff = Math.floor(
23501
+ (chartStartTime.getTime() - anchorDate.getTime()) / msPerDay
23502
+ );
23503
+ const startSprintIndex = Math.floor(dayDiff / sprintDays);
23504
+ const sprints = [];
23505
+ const maxSprints = 1e3;
23506
+ for (let i = startSprintIndex; sprints.length < maxSprints; i++) {
23507
+ const sprintStartDate = new Date(anchorDate);
23508
+ sprintStartDate.setDate(sprintStartDate.getDate() + i * sprintDays);
23509
+ sprintStartDate.setHours(0, 0, 0, 0);
23510
+ const sprintEndDate = new Date(sprintStartDate);
23511
+ sprintEndDate.setDate(sprintEndDate.getDate() + sprintDays);
23512
+ sprintEndDate.setHours(0, 0, 0, 0);
23513
+ if (sprintStartDate.getTime() >= chartEndTime.getTime() + msPerDay) break;
23514
+ sprints.push({
23515
+ number: sprintNumber + i,
23516
+ startDate: sprintStartDate,
23517
+ endDate: sprintEndDate
23518
+ });
23519
+ }
23520
+ return sprints;
23521
+ }
23183
23522
  function formatDate(d) {
23184
23523
  const y = d.getFullYear();
23185
23524
  const m = String(d.getMonth() + 1).padStart(2, "0");
@@ -23265,6 +23604,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23265
23604
  options?.currentActiveGroup
23266
23605
  );
23267
23606
  let criticalPathActive = false;
23607
+ let dependenciesActive = !!resolved.options.dependencies;
23608
+ let controlsExpanded = false;
23268
23609
  const tagRows = currentSwimlaneGroup ? buildTagLaneRowList(resolved, currentSwimlaneGroup, collapsedLanes) : null;
23269
23610
  const rows = tagRows ?? buildRowList(resolved, collapsedGroups);
23270
23611
  const isTagMode = tagRows !== null;
@@ -23281,28 +23622,31 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23281
23622
  const maxLabelLen = Math.max(...allLabels.map((l) => l.length), 10);
23282
23623
  const leftMargin = Math.max(MIN_LEFT_MARGIN, maxLabelLen * 7 + 30);
23283
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);
23284
23627
  const title = resolved.options.title;
23285
23628
  const titleHeight = title ? 50 : 20;
23286
- const tagLegendReserve = resolved.tagGroups.length > 0 ? LEGEND_HEIGHT + 8 : 0;
23629
+ const tagLegendReserve = resolved.tagGroups.length > 0 || hasCriticalPath || hasDependencies ? LEGEND_HEIGHT + 8 : 0;
23287
23630
  const topDateLabelReserve = 22;
23288
23631
  const hasOverheadLabels = resolved.markers.length > 0 || resolved.eras.length > 0;
23289
- const markerLabelReserve = hasOverheadLabels ? 18 : 0;
23632
+ const markerLabelReserve = hasOverheadLabels ? 28 : 0;
23633
+ const sprintLabelReserve = resolved.sprints.length > 0 ? 16 : 0;
23290
23634
  const CONTENT_TOP_PAD = 16;
23291
- const marginTop = titleHeight + tagLegendReserve + topDateLabelReserve + markerLabelReserve;
23635
+ const marginTop = titleHeight + tagLegendReserve + topDateLabelReserve + markerLabelReserve + sprintLabelReserve;
23292
23636
  const contentH = isTagMode ? totalRows * (BAR_H + ROW_GAP) : totalRows * (BAR_H + ROW_GAP) + GROUP_GAP2 * resolved.groups.length;
23293
23637
  const innerHeight = CONTENT_TOP_PAD + contentH;
23294
23638
  const outerHeight = marginTop + innerHeight + BOTTOM_MARGIN;
23295
23639
  const containerWidth = exportDims?.width ?? (container.clientWidth || 800);
23296
- const innerWidth = containerWidth - leftMargin - RIGHT_MARGIN;
23640
+ const sprintRightPad = resolved.sprints.length > 0 ? 50 : 0;
23641
+ const innerWidth = containerWidth - leftMargin - RIGHT_MARGIN - sprintRightPad;
23297
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");
23298
23643
  const g = svg.append("g").attr("transform", `translate(${leftMargin}, ${marginTop})`);
23299
23644
  if (title) {
23300
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);
23301
23646
  }
23302
- const hasCriticalPath = resolved.options.criticalPath && resolved.tasks.some((t) => t.isCriticalPath);
23303
23647
  function drawLegend() {
23304
23648
  svg.selectAll(".gantt-tag-legend-container").remove();
23305
- if (resolved.tagGroups.length > 0 || hasCriticalPath) {
23649
+ if (resolved.tagGroups.length > 0 || hasCriticalPath || hasDependencies) {
23306
23650
  const legendY = titleHeight;
23307
23651
  renderTagLegend(
23308
23652
  svg,
@@ -23324,16 +23668,38 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23324
23668
  recolorBars();
23325
23669
  },
23326
23670
  () => {
23327
- criticalPathActive = !criticalPathActive;
23671
+ controlsExpanded = !controlsExpanded;
23328
23672
  drawLegend();
23329
23673
  },
23330
23674
  currentSwimlaneGroup,
23331
23675
  onSwimlaneChange,
23332
23676
  viewMode,
23333
- 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
+ }
23334
23692
  );
23335
23693
  }
23336
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
+ }
23337
23703
  function recolorBars() {
23338
23704
  g.selectAll(".gantt-task").each(function() {
23339
23705
  const el = d3Selection10.select(this);
@@ -23373,6 +23739,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23373
23739
  onClickItem
23374
23740
  );
23375
23741
  renderErasAndMarkers(g, svg, resolved, xScale, innerHeight, palette);
23742
+ renderSprintBands(g, svg, resolved, xScale, innerHeight, palette);
23376
23743
  let todayDate = null;
23377
23744
  let todayX = -1;
23378
23745
  const todayColor = palette.accent || "#e74c3c";
@@ -23458,7 +23825,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23458
23825
  );
23459
23826
  }
23460
23827
  }).on("mouseleave", () => {
23461
- resetHighlight(g, svg);
23828
+ restoreHighlight();
23462
23829
  hideGanttDateIndicators(g);
23463
23830
  });
23464
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(
@@ -23663,7 +24030,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23663
24030
  );
23664
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);
23665
24032
  }).on("mouseleave", () => {
23666
- resetHighlight(g, svg);
24033
+ restoreHighlight();
23667
24034
  hideGanttDateIndicators(g);
23668
24035
  g.selectAll(".gantt-milestone-hover-label").remove();
23669
24036
  });
@@ -23692,11 +24059,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
23692
24059
  );
23693
24060
  }).on("mouseleave", () => {
23694
24061
  if (resolved.options.dependencies) {
23695
- if (criticalPathActive) {
23696
- applyCriticalPathHighlight(svg, g);
23697
- } else {
23698
- resetHighlight(g, svg);
23699
- }
24062
+ restoreHighlight();
23700
24063
  }
23701
24064
  resetTaskLabels(svg);
23702
24065
  hideGanttDateIndicators(g);
@@ -24011,6 +24374,7 @@ function arrowheadPoints(x, y, size, angle) {
24011
24374
  return `${x},${y} ${x + size * Math.cos(a1)},${y + size * Math.sin(a1)} ${x + size * Math.cos(a2)},${y + size * Math.sin(a2)}`;
24012
24375
  }
24013
24376
  function applyCriticalPathHighlight(svg, chartG) {
24377
+ svg.attr("data-critical-path-active", "true");
24014
24378
  chartG.selectAll(".gantt-task").each(function() {
24015
24379
  const el = d3Selection10.select(this);
24016
24380
  el.attr(
@@ -24063,8 +24427,31 @@ function drawSwimlaneIcon2(parent, x, y, isActive, palette) {
24063
24427
  }
24064
24428
  return iconG;
24065
24429
  }
24066
- function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargin, chartInnerWidth, legendY, palette, isDark, hasCriticalPath, criticalPathActive, optionLineNumbers, onToggle, onToggleCriticalPath, currentSwimlaneGroup, onSwimlaneChange, legendViewMode, resolvedTasks) {
24067
- 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) {
24068
24455
  let visibleGroups;
24069
24456
  if (activeGroupName) {
24070
24457
  const activeGroup = tagGroups.filter(
@@ -24125,16 +24512,17 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
24125
24512
  totalW += groupW;
24126
24513
  }
24127
24514
  totalW += Math.max(0, (visibleGroups.length - 1) * LEGEND_GROUP_GAP);
24128
- const cpLabel = "Critical Path";
24129
- const cpPillW = measureLegendText(cpLabel, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
24130
- 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) {
24131
24520
  if (visibleGroups.length > 0) totalW += LEGEND_GROUP_GAP;
24132
- totalW += cpPillW;
24521
+ totalW += controlsExpanded ? controlsGroupCapsuleWidth(controlsToggleLabels) : LEGEND_GEAR_PILL_W;
24133
24522
  }
24134
24523
  const containerWidth = chartLeftMargin + chartInnerWidth + RIGHT_MARGIN;
24135
24524
  const legendX = (containerWidth - totalW) / 2;
24136
24525
  const legendRow = svg.append("g").attr("class", "gantt-tag-legend-container").attr("transform", `translate(${legendX}, ${legendY})`);
24137
- let cursorX = 0;
24138
24526
  if (visibleGroups.length > 0) {
24139
24527
  const showIcon = !legendViewMode && tagGroups.length > 0;
24140
24528
  const iconReserve = showIcon ? LEGEND_ICON_W : 0;
@@ -24146,6 +24534,12 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
24146
24534
  entries: entries.map((e) => ({ value: e.value, color: e.color }))
24147
24535
  };
24148
24536
  });
24537
+ const controlsToggles = buildControlsToggles(
24538
+ hasCriticalPath,
24539
+ criticalPathActive,
24540
+ hasDependencies,
24541
+ dependenciesActive
24542
+ );
24149
24543
  const legendConfig = {
24150
24544
  groups: legendGroups,
24151
24545
  position: {
@@ -24153,13 +24547,23 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
24153
24547
  titleRelation: "below-title"
24154
24548
  },
24155
24549
  mode: "fixed",
24156
- capsulePillAddonWidth: iconReserve
24550
+ capsulePillAddonWidth: iconReserve,
24551
+ controlsGroup: controlsToggles.length > 0 ? { toggles: controlsToggles } : void 0
24552
+ };
24553
+ const legendState = {
24554
+ activeGroup: activeGroupName,
24555
+ controlsExpanded
24157
24556
  };
24158
- const legendState = { activeGroup: activeGroupName };
24159
- const tagGroupsW = visibleGroups.reduce((s, _, i) => s + groupWidths[i], 0) + Math.max(0, (visibleGroups.length - 1) * LEGEND_GROUP_GAP);
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
+ }
24160
24562
  const tagGroupG = legendRow.append("g");
24161
24563
  const legendCallbacks = {
24162
24564
  onGroupToggle: onToggle,
24565
+ onControlsExpand: onToggleControlsExpand,
24566
+ onControlsToggle,
24163
24567
  onEntryHover: (groupName, entryValue) => {
24164
24568
  const tagKey = groupName.toLowerCase();
24165
24569
  if (entryValue) {
@@ -24236,31 +24640,38 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
24236
24640
  legendCallbacks,
24237
24641
  tagGroupsW
24238
24642
  );
24239
- for (let i = 0; i < visibleGroups.length; i++) {
24240
- cursorX += groupWidths[i] + LEGEND_GROUP_GAP;
24241
- }
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
+ );
24242
24672
  }
24243
- if (hasCriticalPath) {
24244
- const cpLineNum = optionLineNumbers["critical-path"];
24245
- const cpG = legendRow.append("g").attr("transform", `translate(${cursorX}, 0)`).attr("class", "gantt-legend-critical-path").style("cursor", "pointer").on("click", () => {
24246
- if (onToggleCriticalPath) onToggleCriticalPath();
24247
- });
24248
- if (cpLineNum) cpG.attr("data-line-number", String(cpLineNum));
24249
- cpG.append("rect").attr("width", cpPillW).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", criticalPathActive ? palette.bg : groupBg);
24250
- if (criticalPathActive) {
24251
- 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);
24252
- }
24253
- 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);
24254
- if (criticalPathActive) {
24255
- applyCriticalPathHighlight(svg, chartG);
24256
- }
24257
- cpG.on("mouseenter", () => {
24258
- applyCriticalPathHighlight(svg, chartG);
24259
- }).on("mouseleave", () => {
24260
- if (!criticalPathActive) {
24261
- resetHighlightAll(svg, chartG);
24262
- }
24263
- });
24673
+ if (criticalPathActive) {
24674
+ applyCriticalPathHighlight(svg, chartG);
24264
24675
  }
24265
24676
  }
24266
24677
  function renderErasAndMarkers(g, svg, resolved, xScale, innerHeight, palette) {
@@ -24276,7 +24687,7 @@ function renderErasAndMarkers(g, svg, resolved, xScale, innerHeight, palette) {
24276
24687
  const eraEndDate = parseDateStringToDate(era.endDate);
24277
24688
  const eraG = g.append("g").attr("class", "gantt-era-group").attr("data-line-number", String(era.lineNumber));
24278
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);
24279
- 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);
24280
24691
  eraG.on("mouseenter", () => {
24281
24692
  g.selectAll(".gantt-task").attr(
24282
24693
  "opacity",
@@ -24322,8 +24733,8 @@ function renderErasAndMarkers(g, svg, resolved, xScale, innerHeight, palette) {
24322
24733
  const mx = xScale(parseDateToFractionalYear(marker.date));
24323
24734
  const markerDate = parseDateStringToDate(marker.date);
24324
24735
  const diamondSize = 5;
24325
- const labelY = -24;
24326
- const diamondY = labelY + 14;
24736
+ const labelY = -34;
24737
+ const diamondY = -2;
24327
24738
  const markerG = g.append("g").attr("class", "gantt-marker-group").attr("data-line-number", String(marker.lineNumber)).style("cursor", "pointer");
24328
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");
24329
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);
@@ -24383,6 +24794,139 @@ function renderErasAndMarkers(g, svg, resolved, xScale, innerHeight, palette) {
24383
24794
  });
24384
24795
  }
24385
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
+ }
24386
24930
  function parseDateStringToDate(s) {
24387
24931
  const parts = s.split("-").map((p) => parseInt(p, 10));
24388
24932
  const year = parts[0];
@@ -24595,6 +25139,10 @@ function resetTaskLabels(svg) {
24595
25139
  svg.selectAll(".gantt-task-label").attr("opacity", 1);
24596
25140
  }
24597
25141
  function resetHighlight(g, svg) {
25142
+ if (svg.attr("data-critical-path-active") === "true") {
25143
+ applyCriticalPathHighlight(svg, g);
25144
+ return;
25145
+ }
24598
25146
  g.selectAll(".gantt-task, .gantt-milestone").attr(
24599
25147
  "opacity",
24600
25148
  1
@@ -24855,7 +25403,7 @@ function renderTimeScaleHorizontal(g, scale, innerWidth, innerHeight, textColor)
24855
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);
24856
25404
  }
24857
25405
  }
24858
- var BAR_H, ROW_GAP, GROUP_GAP2, MILESTONE_SIZE, MIN_LEFT_MARGIN, BOTTOM_MARGIN, RIGHT_MARGIN, CHAR_W2, LABEL_PAD, LABEL_GAP, BAND_ACCENT_W, BAND_RADIUS, bandClipCounter, JS_DAY_TO_WEEKDAY2, ERA_COLORS, FADE_OPACITY, MONTH_ABBR;
25406
+ var BAR_H, ROW_GAP, GROUP_GAP2, MILESTONE_SIZE, MIN_LEFT_MARGIN, BOTTOM_MARGIN, RIGHT_MARGIN, CHAR_W2, LABEL_PAD, LABEL_GAP, BAND_ACCENT_W, BAND_RADIUS, bandClipCounter, JS_DAY_TO_WEEKDAY2, ERA_COLORS, SPRINT_BAND_OPACITY, SPRINT_HOVER_OPACITY, SPRINT_BOUNDARY_OPACITY, FADE_OPACITY, MONTH_ABBR;
24859
25407
  var init_renderer9 = __esm({
24860
25408
  "src/gantt/renderer.ts"() {
24861
25409
  "use strict";
@@ -24866,6 +25414,7 @@ var init_renderer9 = __esm({
24866
25414
  init_d3();
24867
25415
  init_legend_constants();
24868
25416
  init_legend_d3();
25417
+ init_legend_layout();
24869
25418
  init_title_constants();
24870
25419
  BAR_H = 22;
24871
25420
  ROW_GAP = 6;
@@ -24890,6 +25439,9 @@ var init_renderer9 = __esm({
24890
25439
  "sat"
24891
25440
  ];
24892
25441
  ERA_COLORS = ["#5e81ac", "#a3be8c", "#ebcb8b", "#d08770", "#b48ead"];
25442
+ SPRINT_BAND_OPACITY = 0.05;
25443
+ SPRINT_HOVER_OPACITY = 0.12;
25444
+ SPRINT_BOUNDARY_OPACITY = 0.3;
24893
25445
  FADE_OPACITY = 0.1;
24894
25446
  MONTH_ABBR = [
24895
25447
  "Jan",
@@ -25156,6 +25708,109 @@ var init_state_renderer = __esm({
25156
25708
  }
25157
25709
  });
25158
25710
 
25711
+ // src/sequence/collapse.ts
25712
+ function applyCollapseProjection(parsed, collapsedGroups) {
25713
+ if (collapsedGroups.size === 0) {
25714
+ return {
25715
+ participants: parsed.participants,
25716
+ messages: parsed.messages,
25717
+ elements: parsed.elements,
25718
+ groups: parsed.groups,
25719
+ collapsedGroupIds: /* @__PURE__ */ new Map()
25720
+ };
25721
+ }
25722
+ const memberToGroup = /* @__PURE__ */ new Map();
25723
+ const collapsedGroupNames = /* @__PURE__ */ new Set();
25724
+ for (const group of parsed.groups) {
25725
+ if (collapsedGroups.has(group.lineNumber)) {
25726
+ collapsedGroupNames.add(group.name);
25727
+ for (const memberId of group.participantIds) {
25728
+ memberToGroup.set(memberId, group.name);
25729
+ }
25730
+ }
25731
+ }
25732
+ const participants = [];
25733
+ const insertedGroups = /* @__PURE__ */ new Set();
25734
+ for (const p of parsed.participants) {
25735
+ const groupName = memberToGroup.get(p.id);
25736
+ if (groupName) {
25737
+ if (!insertedGroups.has(groupName)) {
25738
+ insertedGroups.add(groupName);
25739
+ const group = parsed.groups.find(
25740
+ (g) => g.name === groupName && collapsedGroups.has(g.lineNumber)
25741
+ );
25742
+ participants.push({
25743
+ id: groupName,
25744
+ label: groupName,
25745
+ type: "default",
25746
+ lineNumber: group.lineNumber
25747
+ });
25748
+ }
25749
+ } else if (collapsedGroupNames.has(p.id)) {
25750
+ } else {
25751
+ participants.push(p);
25752
+ }
25753
+ }
25754
+ const remap = (id) => memberToGroup.get(id) ?? id;
25755
+ const messages = parsed.messages.map((msg) => ({
25756
+ ...msg,
25757
+ from: remap(msg.from),
25758
+ to: remap(msg.to)
25759
+ }));
25760
+ const elements = remapElements(parsed.elements, memberToGroup);
25761
+ const groups = parsed.groups.filter(
25762
+ (g) => !collapsedGroups.has(g.lineNumber)
25763
+ );
25764
+ return {
25765
+ participants,
25766
+ messages,
25767
+ elements,
25768
+ groups,
25769
+ collapsedGroupIds: memberToGroup
25770
+ };
25771
+ }
25772
+ function remapElements(elements, memberToGroup) {
25773
+ const remap = (id) => memberToGroup.get(id) ?? id;
25774
+ const result = [];
25775
+ for (const el of elements) {
25776
+ if (isSequenceSection(el)) {
25777
+ result.push(el);
25778
+ } else if (isSequenceNote(el)) {
25779
+ result.push({
25780
+ ...el,
25781
+ participantId: remap(el.participantId)
25782
+ });
25783
+ } else if (isSequenceBlock(el)) {
25784
+ result.push({
25785
+ ...el,
25786
+ children: remapElements(el.children, memberToGroup),
25787
+ elseChildren: remapElements(el.elseChildren, memberToGroup),
25788
+ ...el.elseIfBranches ? {
25789
+ elseIfBranches: el.elseIfBranches.map((branch) => ({
25790
+ ...branch,
25791
+ children: remapElements(branch.children, memberToGroup)
25792
+ }))
25793
+ } : {}
25794
+ });
25795
+ } else {
25796
+ const msg = el;
25797
+ const from = remap(msg.from);
25798
+ const to = remap(msg.to);
25799
+ if (from === to && from !== msg.from && !msg.label) {
25800
+ continue;
25801
+ }
25802
+ result.push({ ...msg, from, to });
25803
+ }
25804
+ }
25805
+ return result;
25806
+ }
25807
+ var init_collapse3 = __esm({
25808
+ "src/sequence/collapse.ts"() {
25809
+ "use strict";
25810
+ init_parser();
25811
+ }
25812
+ });
25813
+
25159
25814
  // src/sequence/tag-resolution.ts
25160
25815
  function propagateGroupTags(participantMeta, groups) {
25161
25816
  for (const group of groups) {
@@ -25611,13 +26266,32 @@ function applyGroupOrdering(participants, groups, messages = []) {
25611
26266
  }
25612
26267
  function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateToLine, options) {
25613
26268
  d3Selection12.select(container).selectAll("*").remove();
25614
- const { title, messages, elements, groups, options: parsedOptions } = parsed;
26269
+ const { title, options: parsedOptions } = parsed;
26270
+ const effectiveCollapsedGroups = /* @__PURE__ */ new Set();
26271
+ for (const group of parsed.groups) {
26272
+ if (group.collapsed) effectiveCollapsedGroups.add(group.lineNumber);
26273
+ }
26274
+ if (options?.collapsedGroups) {
26275
+ for (const ln of options.collapsedGroups) {
26276
+ if (effectiveCollapsedGroups.has(ln)) {
26277
+ effectiveCollapsedGroups.delete(ln);
26278
+ } else {
26279
+ effectiveCollapsedGroups.add(ln);
26280
+ }
26281
+ }
26282
+ }
26283
+ const collapsed = effectiveCollapsedGroups.size > 0 ? applyCollapseProjection(parsed, effectiveCollapsedGroups) : null;
26284
+ const messages = collapsed ? collapsed.messages : parsed.messages;
26285
+ const elements = collapsed ? collapsed.elements : parsed.elements;
26286
+ const groups = collapsed ? collapsed.groups : parsed.groups;
26287
+ const collapsedGroupIds = collapsed?.collapsedGroupIds ?? /* @__PURE__ */ new Map();
25615
26288
  const collapsedSections = options?.collapsedSections;
25616
26289
  const expandedNoteLines = options?.expandedNoteLines;
25617
26290
  const collapseNotesDisabled = parsedOptions["collapse-notes"]?.toLowerCase() === "no";
25618
26291
  const isNoteExpanded = (note) => expandedNoteLines === void 0 || collapseNotesDisabled || expandedNoteLines.has(note.lineNumber);
26292
+ const sourceParticipants = collapsed ? collapsed.participants : parsed.participants;
25619
26293
  const participants = applyPositionOverrides(
25620
- applyGroupOrdering(parsed.participants, groups, messages)
26294
+ applyGroupOrdering(sourceParticipants, groups, messages)
25621
26295
  );
25622
26296
  if (participants.length === 0) return;
25623
26297
  const activationsOff = parsedOptions.activations?.toLowerCase() === "off";
@@ -25788,13 +26462,16 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
25788
26462
  const preSectionMsgIndices = [];
25789
26463
  const sectionRegions = [];
25790
26464
  {
26465
+ const msgLineToIndex = /* @__PURE__ */ new Map();
26466
+ messages.forEach((m, i) => msgLineToIndex.set(m.lineNumber, i));
26467
+ const findMsgIndex = (child) => msgLineToIndex.get(child.lineNumber) ?? -1;
25791
26468
  const collectMsgIndicesFromBlock = (block) => {
25792
26469
  const indices = [];
25793
26470
  for (const child of block.children) {
25794
26471
  if (isSequenceBlock(child)) {
25795
26472
  indices.push(...collectMsgIndicesFromBlock(child));
25796
26473
  } else if (!isSequenceSection(child) && !isSequenceNote(child)) {
25797
- const idx = messages.indexOf(child);
26474
+ const idx = findMsgIndex(child);
25798
26475
  if (idx >= 0) indices.push(idx);
25799
26476
  }
25800
26477
  }
@@ -25804,7 +26481,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
25804
26481
  if (isSequenceBlock(child)) {
25805
26482
  indices.push(...collectMsgIndicesFromBlock(child));
25806
26483
  } else if (!isSequenceSection(child) && !isSequenceNote(child)) {
25807
- const idx = messages.indexOf(child);
26484
+ const idx = findMsgIndex(child);
25808
26485
  if (idx >= 0) indices.push(idx);
25809
26486
  }
25810
26487
  }
@@ -25814,7 +26491,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
25814
26491
  if (isSequenceBlock(child)) {
25815
26492
  indices.push(...collectMsgIndicesFromBlock(child));
25816
26493
  } else if (!isSequenceSection(child) && !isSequenceNote(child)) {
25817
- const idx = messages.indexOf(child);
26494
+ const idx = findMsgIndex(child);
25818
26495
  if (idx >= 0) indices.push(idx);
25819
26496
  }
25820
26497
  }
@@ -25829,7 +26506,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
25829
26506
  } else if (isSequenceBlock(el)) {
25830
26507
  currentTarget.push(...collectMsgIndicesFromBlock(el));
25831
26508
  } else {
25832
- const idx = messages.indexOf(el);
26509
+ const idx = findMsgIndex(el);
25833
26510
  if (idx >= 0) currentTarget.push(idx);
25834
26511
  }
25835
26512
  }
@@ -25891,7 +26568,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
25891
26568
  const titleOffset = title ? TITLE_HEIGHT5 : 0;
25892
26569
  const LEGEND_FIXED_GAP4 = 8;
25893
26570
  const legendTopSpace = parsed.tagGroups.length > 0 ? LEGEND_HEIGHT + LEGEND_FIXED_GAP4 : 0;
25894
- const groupOffset = groups.length > 0 ? GROUP_PADDING_TOP + GROUP_LABEL_SIZE : 0;
26571
+ const groupOffset = parsed.groups.length > 0 ? GROUP_PADDING_TOP + GROUP_LABEL_SIZE : 0;
25895
26572
  const participantStartY = TOP_MARGIN + titleOffset + legendTopSpace + PARTICIPANT_Y_OFFSET + groupOffset;
25896
26573
  const lifelineStartY0 = participantStartY + PARTICIPANT_BOX_HEIGHT;
25897
26574
  const hasActors = participants.some((p) => p.type === "actor");
@@ -26061,6 +26738,17 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26061
26738
  svgWidth
26062
26739
  );
26063
26740
  }
26741
+ const collapsedGroupNames = /* @__PURE__ */ new Set();
26742
+ const collapsedGroupMeta = /* @__PURE__ */ new Map();
26743
+ for (const group of parsed.groups) {
26744
+ if (effectiveCollapsedGroups.has(group.lineNumber)) {
26745
+ collapsedGroupNames.add(group.name);
26746
+ collapsedGroupMeta.set(group.name, {
26747
+ lineNumber: group.lineNumber,
26748
+ metadata: group.metadata
26749
+ });
26750
+ }
26751
+ }
26064
26752
  for (const group of groups) {
26065
26753
  if (group.participantIds.length === 0) continue;
26066
26754
  const memberXs = group.participantIds.map((id) => participantX.get(id)).filter((x) => x !== void 0);
@@ -26077,8 +26765,10 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26077
26765
  isDark ? 15 : 20
26078
26766
  ) : isDark ? palette.surface : palette.bg;
26079
26767
  const strokeColor = groupTagColor || palette.textMuted;
26080
- 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));
26081
- 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);
26768
+ const groupG = svg.append("g").attr("class", "group-box-wrapper").attr("data-group-toggle", "").attr("data-group-line", String(group.lineNumber)).attr("cursor", "pointer");
26769
+ groupG.append("title").text("Click to collapse");
26770
+ 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");
26771
+ 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);
26082
26772
  }
26083
26773
  const lifelineStartY = lifelineStartY0;
26084
26774
  participants.forEach((participant, index) => {
@@ -26087,6 +26777,14 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26087
26777
  const pTagValue = tagMap?.participants.get(participant.id);
26088
26778
  const pTagColor = getTagColor(pTagValue);
26089
26779
  const pTagAttr = tagKey && pTagValue ? { key: tagKey, value: pTagValue.toLowerCase() } : void 0;
26780
+ const isCollapsedGroup = collapsedGroupNames.has(participant.id);
26781
+ let effectiveTagColor = pTagColor;
26782
+ if (isCollapsedGroup && !effectiveTagColor) {
26783
+ const meta = collapsedGroupMeta.get(participant.id);
26784
+ if (meta?.metadata && tagKey) {
26785
+ effectiveTagColor = getTagColor(meta.metadata[tagKey]);
26786
+ }
26787
+ }
26090
26788
  renderParticipant(
26091
26789
  svg,
26092
26790
  participant,
@@ -26094,10 +26792,35 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26094
26792
  cy,
26095
26793
  palette,
26096
26794
  isDark,
26097
- pTagColor,
26795
+ effectiveTagColor,
26098
26796
  pTagAttr
26099
26797
  );
26100
- 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);
26798
+ if (isCollapsedGroup) {
26799
+ const meta = collapsedGroupMeta.get(participant.id);
26800
+ const drillColor = effectiveTagColor || palette.textMuted;
26801
+ const drillBarH = 6;
26802
+ const boxW = PARTICIPANT_BOX_WIDTH;
26803
+ const fullH = PARTICIPANT_BOX_HEIGHT + GROUP_PADDING_TOP + GROUP_PADDING_BOTTOM;
26804
+ const clipId = `clip-drill-group-${participant.id.replace(/[^a-zA-Z0-9-]/g, "-")}`;
26805
+ const participantG = svg.select(
26806
+ `.participant[data-participant-id="${participant.id}"]`
26807
+ );
26808
+ participantG.attr("data-group-toggle", "").attr("data-group-line", String(meta.lineNumber)).attr("cursor", "pointer");
26809
+ participantG.append("title").text("Click to expand");
26810
+ const pFill = effectiveTagColor ? mix(
26811
+ effectiveTagColor,
26812
+ isDark ? palette.surface : palette.bg,
26813
+ isDark ? 30 : 40
26814
+ ) : isDark ? mix(palette.overlay, palette.surface, 50) : mix(palette.bg, palette.surface, 50);
26815
+ const pStroke = effectiveTagColor || palette.border;
26816
+ 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);
26817
+ 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);
26818
+ 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);
26819
+ 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})`);
26820
+ }
26821
+ const llY = isCollapsedGroup ? lifelineStartY + GROUP_PADDING_BOTTOM : lifelineStartY;
26822
+ const llColor = isCollapsedGroup ? effectiveTagColor || palette.textMuted : pTagColor || palette.textMuted;
26823
+ 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);
26101
26824
  if (tagKey && pTagValue) {
26102
26825
  lifelineEl.attr(`data-tag-${tagKey}`, pTagValue.toLowerCase());
26103
26826
  }
@@ -26325,23 +27048,8 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
26325
27048
  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");
26326
27049
  const msgCount = sectionMsgCounts.get(sec.lineNumber) ?? 0;
26327
27050
  const labelText = isCollapsed ? `${sec.label} (${msgCount} ${msgCount === 1 ? "message" : "messages"})` : sec.label;
26328
- const labelColor = isCollapsed ? "#ffffff" : lineColor;
26329
- const chevronSpace = 14;
26330
27051
  const labelX = (sectionLineX1 + sectionLineX2) / 2;
26331
- const chevronX = labelX - (labelText.length * 3.5 + 8 + chevronSpace / 2);
26332
- const chevronY = secY;
26333
- if (isCollapsed) {
26334
- sectionG.append("path").attr(
26335
- "d",
26336
- `M ${chevronX} ${chevronY - 4} L ${chevronX + 6} ${chevronY} L ${chevronX} ${chevronY + 4} Z`
26337
- ).attr("fill", labelColor).attr("class", "section-chevron");
26338
- } else {
26339
- sectionG.append("path").attr(
26340
- "d",
26341
- `M ${chevronX - 1} ${chevronY - 3} L ${chevronX + 7} ${chevronY - 3} L ${chevronX + 3} ${chevronY + 3} Z`
26342
- ).attr("fill", labelColor).attr("class", "section-chevron");
26343
- }
26344
- 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);
27052
+ 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);
26345
27053
  }
26346
27054
  const SELF_CALL_WIDTH = 30;
26347
27055
  const SELF_CALL_HEIGHT = 25;
@@ -26622,6 +27330,7 @@ var init_renderer10 = __esm({
26622
27330
  init_inline_markdown();
26623
27331
  init_fonts();
26624
27332
  init_parser();
27333
+ init_collapse3();
26625
27334
  init_tag_resolution();
26626
27335
  init_tag_groups();
26627
27336
  init_legend_constants();
@@ -30945,6 +31654,463 @@ var init_d3 = __esm({
30945
31654
  }
30946
31655
  });
30947
31656
 
31657
+ // node_modules/.pnpm/lz-string@1.5.0/node_modules/lz-string/libs/lz-string.js
31658
+ var require_lz_string = __commonJS({
31659
+ "node_modules/.pnpm/lz-string@1.5.0/node_modules/lz-string/libs/lz-string.js"(exports, module) {
31660
+ "use strict";
31661
+ var LZString = (function() {
31662
+ var f = String.fromCharCode;
31663
+ var keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
31664
+ var keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$";
31665
+ var baseReverseDic = {};
31666
+ function getBaseValue(alphabet, character) {
31667
+ if (!baseReverseDic[alphabet]) {
31668
+ baseReverseDic[alphabet] = {};
31669
+ for (var i = 0; i < alphabet.length; i++) {
31670
+ baseReverseDic[alphabet][alphabet.charAt(i)] = i;
31671
+ }
31672
+ }
31673
+ return baseReverseDic[alphabet][character];
31674
+ }
31675
+ var LZString2 = {
31676
+ compressToBase64: function(input) {
31677
+ if (input == null) return "";
31678
+ var res = LZString2._compress(input, 6, function(a) {
31679
+ return keyStrBase64.charAt(a);
31680
+ });
31681
+ switch (res.length % 4) {
31682
+ // To produce valid Base64
31683
+ default:
31684
+ // When could this happen ?
31685
+ case 0:
31686
+ return res;
31687
+ case 1:
31688
+ return res + "===";
31689
+ case 2:
31690
+ return res + "==";
31691
+ case 3:
31692
+ return res + "=";
31693
+ }
31694
+ },
31695
+ decompressFromBase64: function(input) {
31696
+ if (input == null) return "";
31697
+ if (input == "") return null;
31698
+ return LZString2._decompress(input.length, 32, function(index) {
31699
+ return getBaseValue(keyStrBase64, input.charAt(index));
31700
+ });
31701
+ },
31702
+ compressToUTF16: function(input) {
31703
+ if (input == null) return "";
31704
+ return LZString2._compress(input, 15, function(a) {
31705
+ return f(a + 32);
31706
+ }) + " ";
31707
+ },
31708
+ decompressFromUTF16: function(compressed) {
31709
+ if (compressed == null) return "";
31710
+ if (compressed == "") return null;
31711
+ return LZString2._decompress(compressed.length, 16384, function(index) {
31712
+ return compressed.charCodeAt(index) - 32;
31713
+ });
31714
+ },
31715
+ //compress into uint8array (UCS-2 big endian format)
31716
+ compressToUint8Array: function(uncompressed) {
31717
+ var compressed = LZString2.compress(uncompressed);
31718
+ var buf = new Uint8Array(compressed.length * 2);
31719
+ for (var i = 0, TotalLen = compressed.length; i < TotalLen; i++) {
31720
+ var current_value = compressed.charCodeAt(i);
31721
+ buf[i * 2] = current_value >>> 8;
31722
+ buf[i * 2 + 1] = current_value % 256;
31723
+ }
31724
+ return buf;
31725
+ },
31726
+ //decompress from uint8array (UCS-2 big endian format)
31727
+ decompressFromUint8Array: function(compressed) {
31728
+ if (compressed === null || compressed === void 0) {
31729
+ return LZString2.decompress(compressed);
31730
+ } else {
31731
+ var buf = new Array(compressed.length / 2);
31732
+ for (var i = 0, TotalLen = buf.length; i < TotalLen; i++) {
31733
+ buf[i] = compressed[i * 2] * 256 + compressed[i * 2 + 1];
31734
+ }
31735
+ var result = [];
31736
+ buf.forEach(function(c) {
31737
+ result.push(f(c));
31738
+ });
31739
+ return LZString2.decompress(result.join(""));
31740
+ }
31741
+ },
31742
+ //compress into a string that is already URI encoded
31743
+ compressToEncodedURIComponent: function(input) {
31744
+ if (input == null) return "";
31745
+ return LZString2._compress(input, 6, function(a) {
31746
+ return keyStrUriSafe.charAt(a);
31747
+ });
31748
+ },
31749
+ //decompress from an output of compressToEncodedURIComponent
31750
+ decompressFromEncodedURIComponent: function(input) {
31751
+ if (input == null) return "";
31752
+ if (input == "") return null;
31753
+ input = input.replace(/ /g, "+");
31754
+ return LZString2._decompress(input.length, 32, function(index) {
31755
+ return getBaseValue(keyStrUriSafe, input.charAt(index));
31756
+ });
31757
+ },
31758
+ compress: function(uncompressed) {
31759
+ return LZString2._compress(uncompressed, 16, function(a) {
31760
+ return f(a);
31761
+ });
31762
+ },
31763
+ _compress: function(uncompressed, bitsPerChar, getCharFromInt) {
31764
+ if (uncompressed == null) return "";
31765
+ 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;
31766
+ for (ii = 0; ii < uncompressed.length; ii += 1) {
31767
+ context_c = uncompressed.charAt(ii);
31768
+ if (!Object.prototype.hasOwnProperty.call(context_dictionary, context_c)) {
31769
+ context_dictionary[context_c] = context_dictSize++;
31770
+ context_dictionaryToCreate[context_c] = true;
31771
+ }
31772
+ context_wc = context_w + context_c;
31773
+ if (Object.prototype.hasOwnProperty.call(context_dictionary, context_wc)) {
31774
+ context_w = context_wc;
31775
+ } else {
31776
+ if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
31777
+ if (context_w.charCodeAt(0) < 256) {
31778
+ for (i = 0; i < context_numBits; i++) {
31779
+ context_data_val = context_data_val << 1;
31780
+ if (context_data_position == bitsPerChar - 1) {
31781
+ context_data_position = 0;
31782
+ context_data.push(getCharFromInt(context_data_val));
31783
+ context_data_val = 0;
31784
+ } else {
31785
+ context_data_position++;
31786
+ }
31787
+ }
31788
+ value = context_w.charCodeAt(0);
31789
+ for (i = 0; i < 8; i++) {
31790
+ context_data_val = context_data_val << 1 | value & 1;
31791
+ if (context_data_position == bitsPerChar - 1) {
31792
+ context_data_position = 0;
31793
+ context_data.push(getCharFromInt(context_data_val));
31794
+ context_data_val = 0;
31795
+ } else {
31796
+ context_data_position++;
31797
+ }
31798
+ value = value >> 1;
31799
+ }
31800
+ } else {
31801
+ value = 1;
31802
+ for (i = 0; i < context_numBits; i++) {
31803
+ context_data_val = context_data_val << 1 | value;
31804
+ if (context_data_position == bitsPerChar - 1) {
31805
+ context_data_position = 0;
31806
+ context_data.push(getCharFromInt(context_data_val));
31807
+ context_data_val = 0;
31808
+ } else {
31809
+ context_data_position++;
31810
+ }
31811
+ value = 0;
31812
+ }
31813
+ value = context_w.charCodeAt(0);
31814
+ for (i = 0; i < 16; i++) {
31815
+ context_data_val = context_data_val << 1 | value & 1;
31816
+ if (context_data_position == bitsPerChar - 1) {
31817
+ context_data_position = 0;
31818
+ context_data.push(getCharFromInt(context_data_val));
31819
+ context_data_val = 0;
31820
+ } else {
31821
+ context_data_position++;
31822
+ }
31823
+ value = value >> 1;
31824
+ }
31825
+ }
31826
+ context_enlargeIn--;
31827
+ if (context_enlargeIn == 0) {
31828
+ context_enlargeIn = Math.pow(2, context_numBits);
31829
+ context_numBits++;
31830
+ }
31831
+ delete context_dictionaryToCreate[context_w];
31832
+ } else {
31833
+ value = context_dictionary[context_w];
31834
+ for (i = 0; i < context_numBits; i++) {
31835
+ context_data_val = context_data_val << 1 | value & 1;
31836
+ if (context_data_position == bitsPerChar - 1) {
31837
+ context_data_position = 0;
31838
+ context_data.push(getCharFromInt(context_data_val));
31839
+ context_data_val = 0;
31840
+ } else {
31841
+ context_data_position++;
31842
+ }
31843
+ value = value >> 1;
31844
+ }
31845
+ }
31846
+ context_enlargeIn--;
31847
+ if (context_enlargeIn == 0) {
31848
+ context_enlargeIn = Math.pow(2, context_numBits);
31849
+ context_numBits++;
31850
+ }
31851
+ context_dictionary[context_wc] = context_dictSize++;
31852
+ context_w = String(context_c);
31853
+ }
31854
+ }
31855
+ if (context_w !== "") {
31856
+ if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
31857
+ if (context_w.charCodeAt(0) < 256) {
31858
+ for (i = 0; i < context_numBits; i++) {
31859
+ context_data_val = context_data_val << 1;
31860
+ if (context_data_position == bitsPerChar - 1) {
31861
+ context_data_position = 0;
31862
+ context_data.push(getCharFromInt(context_data_val));
31863
+ context_data_val = 0;
31864
+ } else {
31865
+ context_data_position++;
31866
+ }
31867
+ }
31868
+ value = context_w.charCodeAt(0);
31869
+ for (i = 0; i < 8; i++) {
31870
+ context_data_val = context_data_val << 1 | value & 1;
31871
+ if (context_data_position == bitsPerChar - 1) {
31872
+ context_data_position = 0;
31873
+ context_data.push(getCharFromInt(context_data_val));
31874
+ context_data_val = 0;
31875
+ } else {
31876
+ context_data_position++;
31877
+ }
31878
+ value = value >> 1;
31879
+ }
31880
+ } else {
31881
+ value = 1;
31882
+ for (i = 0; i < context_numBits; i++) {
31883
+ context_data_val = context_data_val << 1 | value;
31884
+ if (context_data_position == bitsPerChar - 1) {
31885
+ context_data_position = 0;
31886
+ context_data.push(getCharFromInt(context_data_val));
31887
+ context_data_val = 0;
31888
+ } else {
31889
+ context_data_position++;
31890
+ }
31891
+ value = 0;
31892
+ }
31893
+ value = context_w.charCodeAt(0);
31894
+ for (i = 0; i < 16; i++) {
31895
+ context_data_val = context_data_val << 1 | value & 1;
31896
+ if (context_data_position == bitsPerChar - 1) {
31897
+ context_data_position = 0;
31898
+ context_data.push(getCharFromInt(context_data_val));
31899
+ context_data_val = 0;
31900
+ } else {
31901
+ context_data_position++;
31902
+ }
31903
+ value = value >> 1;
31904
+ }
31905
+ }
31906
+ context_enlargeIn--;
31907
+ if (context_enlargeIn == 0) {
31908
+ context_enlargeIn = Math.pow(2, context_numBits);
31909
+ context_numBits++;
31910
+ }
31911
+ delete context_dictionaryToCreate[context_w];
31912
+ } else {
31913
+ value = context_dictionary[context_w];
31914
+ for (i = 0; i < context_numBits; i++) {
31915
+ context_data_val = context_data_val << 1 | value & 1;
31916
+ if (context_data_position == bitsPerChar - 1) {
31917
+ context_data_position = 0;
31918
+ context_data.push(getCharFromInt(context_data_val));
31919
+ context_data_val = 0;
31920
+ } else {
31921
+ context_data_position++;
31922
+ }
31923
+ value = value >> 1;
31924
+ }
31925
+ }
31926
+ context_enlargeIn--;
31927
+ if (context_enlargeIn == 0) {
31928
+ context_enlargeIn = Math.pow(2, context_numBits);
31929
+ context_numBits++;
31930
+ }
31931
+ }
31932
+ value = 2;
31933
+ for (i = 0; i < context_numBits; i++) {
31934
+ context_data_val = context_data_val << 1 | value & 1;
31935
+ if (context_data_position == bitsPerChar - 1) {
31936
+ context_data_position = 0;
31937
+ context_data.push(getCharFromInt(context_data_val));
31938
+ context_data_val = 0;
31939
+ } else {
31940
+ context_data_position++;
31941
+ }
31942
+ value = value >> 1;
31943
+ }
31944
+ while (true) {
31945
+ context_data_val = context_data_val << 1;
31946
+ if (context_data_position == bitsPerChar - 1) {
31947
+ context_data.push(getCharFromInt(context_data_val));
31948
+ break;
31949
+ } else context_data_position++;
31950
+ }
31951
+ return context_data.join("");
31952
+ },
31953
+ decompress: function(compressed) {
31954
+ if (compressed == null) return "";
31955
+ if (compressed == "") return null;
31956
+ return LZString2._decompress(compressed.length, 32768, function(index) {
31957
+ return compressed.charCodeAt(index);
31958
+ });
31959
+ },
31960
+ _decompress: function(length, resetValue, getNextValue) {
31961
+ 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 };
31962
+ for (i = 0; i < 3; i += 1) {
31963
+ dictionary[i] = i;
31964
+ }
31965
+ bits = 0;
31966
+ maxpower = Math.pow(2, 2);
31967
+ power = 1;
31968
+ while (power != maxpower) {
31969
+ resb = data.val & data.position;
31970
+ data.position >>= 1;
31971
+ if (data.position == 0) {
31972
+ data.position = resetValue;
31973
+ data.val = getNextValue(data.index++);
31974
+ }
31975
+ bits |= (resb > 0 ? 1 : 0) * power;
31976
+ power <<= 1;
31977
+ }
31978
+ switch (next = bits) {
31979
+ case 0:
31980
+ bits = 0;
31981
+ maxpower = Math.pow(2, 8);
31982
+ power = 1;
31983
+ while (power != maxpower) {
31984
+ resb = data.val & data.position;
31985
+ data.position >>= 1;
31986
+ if (data.position == 0) {
31987
+ data.position = resetValue;
31988
+ data.val = getNextValue(data.index++);
31989
+ }
31990
+ bits |= (resb > 0 ? 1 : 0) * power;
31991
+ power <<= 1;
31992
+ }
31993
+ c = f(bits);
31994
+ break;
31995
+ case 1:
31996
+ bits = 0;
31997
+ maxpower = Math.pow(2, 16);
31998
+ power = 1;
31999
+ while (power != maxpower) {
32000
+ resb = data.val & data.position;
32001
+ data.position >>= 1;
32002
+ if (data.position == 0) {
32003
+ data.position = resetValue;
32004
+ data.val = getNextValue(data.index++);
32005
+ }
32006
+ bits |= (resb > 0 ? 1 : 0) * power;
32007
+ power <<= 1;
32008
+ }
32009
+ c = f(bits);
32010
+ break;
32011
+ case 2:
32012
+ return "";
32013
+ }
32014
+ dictionary[3] = c;
32015
+ w = c;
32016
+ result.push(c);
32017
+ while (true) {
32018
+ if (data.index > length) {
32019
+ return "";
32020
+ }
32021
+ bits = 0;
32022
+ maxpower = Math.pow(2, numBits);
32023
+ power = 1;
32024
+ while (power != maxpower) {
32025
+ resb = data.val & data.position;
32026
+ data.position >>= 1;
32027
+ if (data.position == 0) {
32028
+ data.position = resetValue;
32029
+ data.val = getNextValue(data.index++);
32030
+ }
32031
+ bits |= (resb > 0 ? 1 : 0) * power;
32032
+ power <<= 1;
32033
+ }
32034
+ switch (c = bits) {
32035
+ case 0:
32036
+ bits = 0;
32037
+ maxpower = Math.pow(2, 8);
32038
+ power = 1;
32039
+ while (power != maxpower) {
32040
+ resb = data.val & data.position;
32041
+ data.position >>= 1;
32042
+ if (data.position == 0) {
32043
+ data.position = resetValue;
32044
+ data.val = getNextValue(data.index++);
32045
+ }
32046
+ bits |= (resb > 0 ? 1 : 0) * power;
32047
+ power <<= 1;
32048
+ }
32049
+ dictionary[dictSize++] = f(bits);
32050
+ c = dictSize - 1;
32051
+ enlargeIn--;
32052
+ break;
32053
+ case 1:
32054
+ bits = 0;
32055
+ maxpower = Math.pow(2, 16);
32056
+ power = 1;
32057
+ while (power != maxpower) {
32058
+ resb = data.val & data.position;
32059
+ data.position >>= 1;
32060
+ if (data.position == 0) {
32061
+ data.position = resetValue;
32062
+ data.val = getNextValue(data.index++);
32063
+ }
32064
+ bits |= (resb > 0 ? 1 : 0) * power;
32065
+ power <<= 1;
32066
+ }
32067
+ dictionary[dictSize++] = f(bits);
32068
+ c = dictSize - 1;
32069
+ enlargeIn--;
32070
+ break;
32071
+ case 2:
32072
+ return result.join("");
32073
+ }
32074
+ if (enlargeIn == 0) {
32075
+ enlargeIn = Math.pow(2, numBits);
32076
+ numBits++;
32077
+ }
32078
+ if (dictionary[c]) {
32079
+ entry = dictionary[c];
32080
+ } else {
32081
+ if (c === dictSize) {
32082
+ entry = w + w.charAt(0);
32083
+ } else {
32084
+ return null;
32085
+ }
32086
+ }
32087
+ result.push(entry);
32088
+ dictionary[dictSize++] = w + entry.charAt(0);
32089
+ enlargeIn--;
32090
+ w = entry;
32091
+ if (enlargeIn == 0) {
32092
+ enlargeIn = Math.pow(2, numBits);
32093
+ numBits++;
32094
+ }
32095
+ }
32096
+ }
32097
+ };
32098
+ return LZString2;
32099
+ })();
32100
+ if (typeof define === "function" && define.amd) {
32101
+ define(function() {
32102
+ return LZString;
32103
+ });
32104
+ } else if (typeof module !== "undefined" && module != null) {
32105
+ module.exports = LZString;
32106
+ } else if (typeof angular !== "undefined" && angular != null) {
32107
+ angular.module("LZString", []).factory("LZString", function() {
32108
+ return LZString;
32109
+ });
32110
+ }
32111
+ }
32112
+ });
32113
+
30948
32114
  // src/index.ts
30949
32115
  init_diagnostics();
30950
32116
 
@@ -31727,19 +32893,35 @@ init_legend_d3();
31727
32893
  init_legend_layout();
31728
32894
  init_d3();
31729
32895
  init_renderer10();
32896
+ init_collapse3();
31730
32897
  init_colors();
31731
32898
  init_palettes();
31732
32899
 
31733
32900
  // src/sharing.ts
31734
- import {
31735
- compressToEncodedURIComponent,
31736
- decompressFromEncodedURIComponent
31737
- } from "lz-string";
32901
+ var import_lz_string = __toESM(require_lz_string(), 1);
31738
32902
  var DEFAULT_BASE_URL = "https://online.diagrammo.app";
31739
32903
  var COMPRESSED_SIZE_LIMIT = 8192;
32904
+ function encodeViewState(state) {
32905
+ const keys = Object.keys(state);
32906
+ if (keys.length === 0) return "";
32907
+ return (0, import_lz_string.compressToEncodedURIComponent)(JSON.stringify(state));
32908
+ }
32909
+ function decodeViewState(encoded) {
32910
+ if (!encoded) return {};
32911
+ try {
32912
+ const json = (0, import_lz_string.decompressFromEncodedURIComponent)(encoded);
32913
+ if (!json) return {};
32914
+ const parsed = JSON.parse(json);
32915
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
32916
+ return {};
32917
+ return parsed;
32918
+ } catch {
32919
+ return {};
32920
+ }
32921
+ }
31740
32922
  function encodeDiagramUrl(dsl, options) {
31741
32923
  const baseUrl = options?.baseUrl ?? DEFAULT_BASE_URL;
31742
- const compressed = compressToEncodedURIComponent(dsl);
32924
+ const compressed = (0, import_lz_string.compressToEncodedURIComponent)(dsl);
31743
32925
  const byteSize = new TextEncoder().encode(compressed).byteLength;
31744
32926
  if (byteSize > COMPRESSED_SIZE_LIMIT) {
31745
32927
  return {
@@ -31749,23 +32931,17 @@ function encodeDiagramUrl(dsl, options) {
31749
32931
  };
31750
32932
  }
31751
32933
  let hash = `dgmo=${compressed}`;
31752
- if (options?.viewState?.activeTagGroup) {
31753
- hash += `&tag=${encodeURIComponent(options.viewState.activeTagGroup)}`;
31754
- }
31755
- if (options?.viewState?.collapsedGroups?.length) {
31756
- hash += `&cg=${encodeURIComponent(options.viewState.collapsedGroups.join(","))}`;
31757
- }
31758
- if (options?.viewState?.swimlaneTagGroup) {
31759
- hash += `&swim=${encodeURIComponent(options.viewState.swimlaneTagGroup)}`;
31760
- }
31761
- if (options?.viewState?.collapsedLanes?.length) {
31762
- hash += `&cl=${encodeURIComponent(options.viewState.collapsedLanes.join(","))}`;
32934
+ if (options?.viewState) {
32935
+ const vsEncoded = encodeViewState(options.viewState);
32936
+ if (vsEncoded) {
32937
+ hash += `&vs=${vsEncoded}`;
32938
+ }
31763
32939
  }
31764
- if (options?.viewState?.palette && options.viewState.palette !== "nord") {
31765
- hash += `&pal=${encodeURIComponent(options.viewState.palette)}`;
32940
+ if (options?.palette && options.palette !== "nord") {
32941
+ hash += `&pal=${encodeURIComponent(options.palette)}`;
31766
32942
  }
31767
- if (options?.viewState?.theme && options.viewState.theme !== "dark") {
31768
- hash += `&th=${encodeURIComponent(options.viewState.theme)}`;
32943
+ if (options?.theme && options.theme !== "dark") {
32944
+ hash += `&th=${encodeURIComponent(options.theme)}`;
31769
32945
  }
31770
32946
  if (options?.filename) {
31771
32947
  hash += `&fn=${encodeURIComponent(options.filename)}`;
@@ -31775,6 +32951,8 @@ function encodeDiagramUrl(dsl, options) {
31775
32951
  function decodeDiagramUrl(hash) {
31776
32952
  const empty = { dsl: "", viewState: {} };
31777
32953
  let filename;
32954
+ let palette;
32955
+ let theme;
31778
32956
  if (!hash) return empty;
31779
32957
  let raw = hash;
31780
32958
  if (raw.startsWith("#") || raw.startsWith("?")) {
@@ -31782,38 +32960,31 @@ function decodeDiagramUrl(hash) {
31782
32960
  }
31783
32961
  const parts = raw.split("&");
31784
32962
  let payload = parts[0];
31785
- const viewState = {};
32963
+ let viewState = {};
31786
32964
  for (let i = 1; i < parts.length; i++) {
31787
32965
  const eq = parts[i].indexOf("=");
31788
32966
  if (eq === -1) continue;
31789
32967
  const key = parts[i].slice(0, eq);
31790
- const val = decodeURIComponent(parts[i].slice(eq + 1));
31791
- if (key === "tag" && val) {
31792
- viewState.activeTagGroup = val;
31793
- }
31794
- if (key === "cg" && val) {
31795
- viewState.collapsedGroups = val.split(",").filter(Boolean);
31796
- }
31797
- if (key === "swim" && val) {
31798
- viewState.swimlaneTagGroup = val;
32968
+ const val = parts[i].slice(eq + 1);
32969
+ if (key === "vs" && val) {
32970
+ viewState = decodeViewState(val);
31799
32971
  }
31800
- if (key === "cl" && val) {
31801
- viewState.collapsedLanes = val.split(",").filter(Boolean);
32972
+ if (key === "pal" && val) palette = decodeURIComponent(val);
32973
+ if (key === "th") {
32974
+ const decoded = decodeURIComponent(val);
32975
+ if (decoded === "light" || decoded === "dark") theme = decoded;
31802
32976
  }
31803
- if (key === "pal" && val) viewState.palette = val;
31804
- if (key === "th" && (val === "light" || val === "dark"))
31805
- viewState.theme = val;
31806
- if (key === "fn" && val) filename = val;
32977
+ if (key === "fn" && val) filename = decodeURIComponent(val);
31807
32978
  }
31808
32979
  if (payload.startsWith("dgmo=")) {
31809
32980
  payload = payload.slice(5);
31810
32981
  }
31811
- if (!payload) return { dsl: "", viewState, filename };
32982
+ if (!payload) return { dsl: "", viewState, palette, theme, filename };
31812
32983
  try {
31813
- const result = decompressFromEncodedURIComponent(payload);
31814
- return { dsl: result ?? "", viewState, filename };
32984
+ const result = (0, import_lz_string.decompressFromEncodedURIComponent)(payload);
32985
+ return { dsl: result ?? "", viewState, palette, theme, filename };
31815
32986
  } catch {
31816
- return { dsl: "", viewState, filename };
32987
+ return { dsl: "", viewState, palette, theme, filename };
31817
32988
  }
31818
32989
  }
31819
32990
 
@@ -32573,6 +33744,7 @@ export {
32573
33744
  RECOGNIZED_COLOR_NAMES,
32574
33745
  RULE_COUNT,
32575
33746
  addDurationToDate,
33747
+ applyCollapseProjection,
32576
33748
  applyGroupOrdering,
32577
33749
  applyPositionOverrides,
32578
33750
  boldPalette,
@@ -32602,8 +33774,10 @@ export {
32602
33774
  computeTimeTicks,
32603
33775
  contrastText,
32604
33776
  decodeDiagramUrl,
33777
+ decodeViewState,
32605
33778
  draculaPalette,
32606
33779
  encodeDiagramUrl,
33780
+ encodeViewState,
32607
33781
  extractDiagramSymbols,
32608
33782
  extractTagDeclarations,
32609
33783
  formatDateLabel,