@diagrammo/dgmo 0.4.4 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1379,6 +1379,94 @@ var init_parsing = __esm({
1379
1379
  }
1380
1380
  });
1381
1381
 
1382
+ // src/utils/tag-groups.ts
1383
+ function isTagBlockHeading(trimmed) {
1384
+ return TAG_BLOCK_RE.test(trimmed) || GROUP_HEADING_RE.test(trimmed);
1385
+ }
1386
+ function resolveTagColor(metadata, tagGroups, activeGroupName, isContainer) {
1387
+ if (!activeGroupName) return void 0;
1388
+ const group = tagGroups.find(
1389
+ (g) => g.name.toLowerCase() === activeGroupName.toLowerCase()
1390
+ );
1391
+ if (!group) return void 0;
1392
+ const metaValue = metadata[group.name.toLowerCase()] ?? (isContainer ? void 0 : group.defaultValue);
1393
+ if (!metaValue) return "#999999";
1394
+ return group.entries.find(
1395
+ (e) => e.value.toLowerCase() === metaValue.toLowerCase()
1396
+ )?.color ?? "#999999";
1397
+ }
1398
+ function validateTagValues(entities, tagGroups, pushWarning, suggestFn) {
1399
+ if (tagGroups.length === 0) return;
1400
+ const groupMap = /* @__PURE__ */ new Map();
1401
+ for (const g of tagGroups) groupMap.set(g.name.toLowerCase(), g);
1402
+ for (const entity of entities) {
1403
+ for (const [key, value] of Object.entries(entity.metadata)) {
1404
+ const group = groupMap.get(key);
1405
+ if (!group) continue;
1406
+ const match = group.entries.some(
1407
+ (e) => e.value.toLowerCase() === value.toLowerCase()
1408
+ );
1409
+ if (!match) {
1410
+ const defined = group.entries.map((e) => e.value);
1411
+ let msg = `Unknown value '${value}' for tag group '${group.name}'`;
1412
+ const hint = suggestFn?.(value, defined);
1413
+ if (hint) {
1414
+ msg += `. ${hint}`;
1415
+ } else {
1416
+ msg += ` \u2014 defined values: ${defined.join(", ")}`;
1417
+ }
1418
+ pushWarning(entity.lineNumber, msg);
1419
+ }
1420
+ }
1421
+ }
1422
+ }
1423
+ function injectDefaultTagMetadata(entities, tagGroups, skip) {
1424
+ const defaults = [];
1425
+ for (const group of tagGroups) {
1426
+ if (group.defaultValue) {
1427
+ defaults.push({ key: group.name.toLowerCase(), value: group.defaultValue });
1428
+ }
1429
+ }
1430
+ if (defaults.length === 0) return;
1431
+ for (const entity of entities) {
1432
+ if (skip?.(entity)) continue;
1433
+ for (const { key, value } of defaults) {
1434
+ if (!(key in entity.metadata)) {
1435
+ entity.metadata[key] = value;
1436
+ }
1437
+ }
1438
+ }
1439
+ }
1440
+ function matchTagBlockHeading(trimmed) {
1441
+ const tagMatch = trimmed.match(TAG_BLOCK_RE);
1442
+ if (tagMatch) {
1443
+ return {
1444
+ name: tagMatch[1].trim(),
1445
+ alias: tagMatch[2] || void 0,
1446
+ colorHint: tagMatch[3] || void 0,
1447
+ deprecated: false
1448
+ };
1449
+ }
1450
+ const groupMatch = trimmed.match(GROUP_HEADING_RE);
1451
+ if (groupMatch) {
1452
+ return {
1453
+ name: groupMatch[1].trim(),
1454
+ alias: groupMatch[2] || void 0,
1455
+ colorHint: groupMatch[3] || void 0,
1456
+ deprecated: true
1457
+ };
1458
+ }
1459
+ return null;
1460
+ }
1461
+ var TAG_BLOCK_RE, GROUP_HEADING_RE;
1462
+ var init_tag_groups = __esm({
1463
+ "src/utils/tag-groups.ts"() {
1464
+ "use strict";
1465
+ TAG_BLOCK_RE = /^tag:\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/i;
1466
+ GROUP_HEADING_RE = /^##\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/;
1467
+ }
1468
+ });
1469
+
1382
1470
  // src/sequence/participant-inference.ts
1383
1471
  function inferParticipantType(name) {
1384
1472
  for (const rule of PARTICIPANT_RULES) {
@@ -1708,94 +1796,6 @@ var init_arrows = __esm({
1708
1796
  }
1709
1797
  });
1710
1798
 
1711
- // src/utils/tag-groups.ts
1712
- function isTagBlockHeading(trimmed) {
1713
- return TAG_BLOCK_RE.test(trimmed) || GROUP_HEADING_RE.test(trimmed);
1714
- }
1715
- function resolveTagColor(metadata, tagGroups, activeGroupName, isContainer) {
1716
- if (!activeGroupName) return void 0;
1717
- const group = tagGroups.find(
1718
- (g) => g.name.toLowerCase() === activeGroupName.toLowerCase()
1719
- );
1720
- if (!group) return void 0;
1721
- const metaValue = metadata[group.name.toLowerCase()] ?? (isContainer ? void 0 : group.defaultValue);
1722
- if (!metaValue) return "#999999";
1723
- return group.entries.find(
1724
- (e) => e.value.toLowerCase() === metaValue.toLowerCase()
1725
- )?.color ?? "#999999";
1726
- }
1727
- function validateTagValues(entities, tagGroups, pushWarning, suggestFn) {
1728
- if (tagGroups.length === 0) return;
1729
- const groupMap = /* @__PURE__ */ new Map();
1730
- for (const g of tagGroups) groupMap.set(g.name.toLowerCase(), g);
1731
- for (const entity of entities) {
1732
- for (const [key, value] of Object.entries(entity.metadata)) {
1733
- const group = groupMap.get(key);
1734
- if (!group) continue;
1735
- const match = group.entries.some(
1736
- (e) => e.value.toLowerCase() === value.toLowerCase()
1737
- );
1738
- if (!match) {
1739
- const defined = group.entries.map((e) => e.value);
1740
- let msg = `Unknown value '${value}' for tag group '${group.name}'`;
1741
- const hint = suggestFn?.(value, defined);
1742
- if (hint) {
1743
- msg += `. ${hint}`;
1744
- } else {
1745
- msg += ` \u2014 defined values: ${defined.join(", ")}`;
1746
- }
1747
- pushWarning(entity.lineNumber, msg);
1748
- }
1749
- }
1750
- }
1751
- }
1752
- function injectDefaultTagMetadata(entities, tagGroups, skip) {
1753
- const defaults = [];
1754
- for (const group of tagGroups) {
1755
- if (group.defaultValue) {
1756
- defaults.push({ key: group.name.toLowerCase(), value: group.defaultValue });
1757
- }
1758
- }
1759
- if (defaults.length === 0) return;
1760
- for (const entity of entities) {
1761
- if (skip?.(entity)) continue;
1762
- for (const { key, value } of defaults) {
1763
- if (!(key in entity.metadata)) {
1764
- entity.metadata[key] = value;
1765
- }
1766
- }
1767
- }
1768
- }
1769
- function matchTagBlockHeading(trimmed) {
1770
- const tagMatch = trimmed.match(TAG_BLOCK_RE);
1771
- if (tagMatch) {
1772
- return {
1773
- name: tagMatch[1].trim(),
1774
- alias: tagMatch[2] || void 0,
1775
- colorHint: tagMatch[3] || void 0,
1776
- deprecated: false
1777
- };
1778
- }
1779
- const groupMatch = trimmed.match(GROUP_HEADING_RE);
1780
- if (groupMatch) {
1781
- return {
1782
- name: groupMatch[1].trim(),
1783
- alias: groupMatch[2] || void 0,
1784
- colorHint: groupMatch[3] || void 0,
1785
- deprecated: true
1786
- };
1787
- }
1788
- return null;
1789
- }
1790
- var TAG_BLOCK_RE, GROUP_HEADING_RE;
1791
- var init_tag_groups = __esm({
1792
- "src/utils/tag-groups.ts"() {
1793
- "use strict";
1794
- TAG_BLOCK_RE = /^tag:\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/i;
1795
- GROUP_HEADING_RE = /^##\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/;
1796
- }
1797
- });
1798
-
1799
1799
  // src/sequence/parser.ts
1800
1800
  var parser_exports = {};
1801
1801
  __export(parser_exports, {
@@ -3383,6 +3383,7 @@ function parseERDiagram(content, palette) {
3383
3383
  options: {},
3384
3384
  tables: [],
3385
3385
  relationships: [],
3386
+ tagGroups: [],
3386
3387
  diagnostics: [],
3387
3388
  error: null
3388
3389
  };
@@ -3400,6 +3401,8 @@ function parseERDiagram(content, palette) {
3400
3401
  const tableMap = /* @__PURE__ */ new Map();
3401
3402
  let currentTable = null;
3402
3403
  let contentStarted = false;
3404
+ let currentTagGroup = null;
3405
+ const aliasMap = /* @__PURE__ */ new Map();
3403
3406
  function getOrCreateTable(name, lineNumber) {
3404
3407
  const id = tableId(name);
3405
3408
  const existing = tableMap.get(id);
@@ -3408,6 +3411,7 @@ function parseERDiagram(content, palette) {
3408
3411
  id,
3409
3412
  name,
3410
3413
  columns: [],
3414
+ metadata: {},
3411
3415
  lineNumber
3412
3416
  };
3413
3417
  tableMap.set(id, table);
@@ -3424,6 +3428,50 @@ function parseERDiagram(content, palette) {
3424
3428
  continue;
3425
3429
  }
3426
3430
  if (trimmed.startsWith("//")) continue;
3431
+ if (!contentStarted && indent === 0) {
3432
+ const tagBlockMatch = matchTagBlockHeading(trimmed);
3433
+ if (tagBlockMatch) {
3434
+ if (tagBlockMatch.deprecated) {
3435
+ result.diagnostics.push(makeDgmoError(
3436
+ lineNumber,
3437
+ `'## ${tagBlockMatch.name}' is deprecated for tag groups \u2014 use 'tag: ${tagBlockMatch.name}' instead`,
3438
+ "warning"
3439
+ ));
3440
+ }
3441
+ currentTagGroup = {
3442
+ name: tagBlockMatch.name,
3443
+ alias: tagBlockMatch.alias,
3444
+ entries: [],
3445
+ lineNumber
3446
+ };
3447
+ if (tagBlockMatch.alias) {
3448
+ aliasMap.set(tagBlockMatch.alias.toLowerCase(), tagBlockMatch.name.toLowerCase());
3449
+ }
3450
+ result.tagGroups.push(currentTagGroup);
3451
+ continue;
3452
+ }
3453
+ }
3454
+ if (currentTagGroup && !contentStarted && indent > 0) {
3455
+ const isDefault = /\bdefault\s*$/.test(trimmed);
3456
+ const entryText = isDefault ? trimmed.replace(/\s+default\s*$/, "").trim() : trimmed;
3457
+ const { label, color } = extractColor(entryText, palette);
3458
+ if (!color) {
3459
+ result.diagnostics.push(makeDgmoError(
3460
+ lineNumber,
3461
+ `Expected 'Value(color)' in tag group '${currentTagGroup.name}'`,
3462
+ "warning"
3463
+ ));
3464
+ continue;
3465
+ }
3466
+ if (isDefault) {
3467
+ currentTagGroup.defaultValue = label;
3468
+ }
3469
+ currentTagGroup.entries.push({ value: label, color, lineNumber });
3470
+ continue;
3471
+ }
3472
+ if (currentTagGroup && indent === 0) {
3473
+ currentTagGroup = null;
3474
+ }
3427
3475
  if (!contentStarted && indent === 0 && /^[a-z][a-z0-9-]*\s*:/i.test(trimmed)) {
3428
3476
  const colonIdx = trimmed.indexOf(":");
3429
3477
  const key = trimmed.substring(0, colonIdx).trim().toLowerCase();
@@ -3505,6 +3553,11 @@ function parseERDiagram(content, palette) {
3505
3553
  const table = getOrCreateTable(name, lineNumber);
3506
3554
  if (color) table.color = color;
3507
3555
  table.lineNumber = lineNumber;
3556
+ const pipeStr = tableDecl[3]?.trim();
3557
+ if (pipeStr) {
3558
+ const meta = parsePipeMetadata(["", pipeStr], aliasMap);
3559
+ Object.assign(table.metadata, meta);
3560
+ }
3508
3561
  currentTable = table;
3509
3562
  continue;
3510
3563
  }
@@ -3514,6 +3567,27 @@ function parseERDiagram(content, palette) {
3514
3567
  result.diagnostics.push(diag);
3515
3568
  result.error = formatDgmoError(diag);
3516
3569
  }
3570
+ if (result.tagGroups.length > 0) {
3571
+ const tagEntities = result.tables.map((t) => ({
3572
+ metadata: t.metadata,
3573
+ lineNumber: t.lineNumber
3574
+ }));
3575
+ validateTagValues(
3576
+ tagEntities,
3577
+ result.tagGroups,
3578
+ (line10, msg) => result.diagnostics.push(makeDgmoError(line10, msg, "warning")),
3579
+ suggest
3580
+ );
3581
+ for (const group of result.tagGroups) {
3582
+ if (!group.defaultValue) continue;
3583
+ const key = group.name.toLowerCase();
3584
+ for (const table of result.tables) {
3585
+ if (!table.metadata[key]) {
3586
+ table.metadata[key] = group.defaultValue;
3587
+ }
3588
+ }
3589
+ }
3590
+ }
3517
3591
  if (result.tables.length >= 2 && result.relationships.length >= 1 && !result.error) {
3518
3592
  const connectedIds = /* @__PURE__ */ new Set();
3519
3593
  for (const rel of result.relationships) {
@@ -3565,7 +3639,8 @@ var init_parser3 = __esm({
3565
3639
  init_colors();
3566
3640
  init_diagnostics();
3567
3641
  init_parsing();
3568
- TABLE_DECL_RE = /^([a-zA-Z_]\w*)(?:\s+\(([^)]+)\))?\s*$/;
3642
+ init_tag_groups();
3643
+ TABLE_DECL_RE = /^([a-zA-Z_]\w*)(?:\s*\(([^)]+)\))?(?:\s*\|(.+))?$/;
3569
3644
  COLUMN_RE = /^(\w+)(?:\s*:\s*(\w[\w()]*(?:\s*\[\])?))?(?:\s+\[([^\]]+)\])?\s*$/;
3570
3645
  INDENT_REL_RE = /^([1*?])-(?:(.+)-)?([1*?])\s+([a-zA-Z_]\w*)\s*$/;
3571
3646
  CONSTRAINT_MAP = {
@@ -3603,7 +3678,10 @@ function parseChart(content, palette) {
3603
3678
  const trimmed = lines[i].trim();
3604
3679
  const lineNumber = i + 1;
3605
3680
  if (!trimmed) continue;
3606
- if (/^#{2,}\s+/.test(trimmed)) continue;
3681
+ if (/^#{2,}\s+/.test(trimmed)) {
3682
+ result.diagnostics.push(makeDgmoError(lineNumber, `'${trimmed}' \u2014 ## syntax is no longer supported. Use [Group] containers instead`));
3683
+ continue;
3684
+ }
3607
3685
  if (trimmed.startsWith("//")) continue;
3608
3686
  const colonIndex = trimmed.indexOf(":");
3609
3687
  if (colonIndex === -1) continue;
@@ -3750,9 +3828,16 @@ function parseEChart(content, palette) {
3750
3828
  const trimmed = lines[i].trim();
3751
3829
  const lineNumber = i + 1;
3752
3830
  if (!trimmed) continue;
3753
- const mdCategoryMatch = trimmed.match(/^#{2,}\s+(.+)$/);
3754
- if (mdCategoryMatch) {
3755
- const { label: catName, color: catColor } = extractColor(mdCategoryMatch[1].trim(), palette);
3831
+ if (/^#{2,}\s+/.test(trimmed)) {
3832
+ const name = trimmed.replace(/^#{2,}\s+/, "").replace(/\s*\([^)]*\)\s*$/, "").trim();
3833
+ result.diagnostics.push(makeDgmoError(lineNumber, `'## ${name}' is no longer supported. Use '[${name}]' instead`));
3834
+ continue;
3835
+ }
3836
+ if (trimmed.startsWith("//")) continue;
3837
+ const categoryMatch = trimmed.match(/^\[(.+?)\](?:\s*\(([^)]+)\))?\s*$/);
3838
+ if (categoryMatch) {
3839
+ const catName = categoryMatch[1].trim();
3840
+ const catColor = categoryMatch[2] ? resolveColor(categoryMatch[2].trim(), palette) : null;
3756
3841
  if (catColor) {
3757
3842
  if (!result.categoryColors) result.categoryColors = {};
3758
3843
  result.categoryColors[catName] = catColor;
@@ -3760,12 +3845,6 @@ function parseEChart(content, palette) {
3760
3845
  currentCategory = catName;
3761
3846
  continue;
3762
3847
  }
3763
- if (trimmed.startsWith("//")) continue;
3764
- const categoryMatch = trimmed.match(/^\[(.+)\]$/);
3765
- if (categoryMatch) {
3766
- currentCategory = categoryMatch[1].trim();
3767
- continue;
3768
- }
3769
3848
  const colonIndex = trimmed.indexOf(":");
3770
3849
  if (result.type === "sankey" && colonIndex === -1) {
3771
3850
  const indent = measureIndent(lines[i]);
@@ -5397,6 +5476,7 @@ function parseKanban(content, palette) {
5397
5476
  let currentTagGroup = null;
5398
5477
  let currentColumn = null;
5399
5478
  let currentCard = null;
5479
+ let cardBaseIndent = 0;
5400
5480
  let columnCounter = 0;
5401
5481
  let cardCounter = 0;
5402
5482
  const aliasMap = /* @__PURE__ */ new Map();
@@ -5496,7 +5576,14 @@ function parseKanban(content, palette) {
5496
5576
  }
5497
5577
  currentTagGroup = null;
5498
5578
  }
5499
- const columnMatch = trimmed.match(COLUMN_RE2);
5579
+ const indent = measureIndent(line10);
5580
+ if (LEGACY_COLUMN_RE.test(trimmed)) {
5581
+ const legacyMatch = trimmed.match(LEGACY_COLUMN_RE);
5582
+ const name = legacyMatch[1].replace(/\s*\(.*\)\s*$/, "").trim();
5583
+ warn(lineNumber, `'== ${name} ==' is no longer supported. Use '[${name}]' instead`);
5584
+ continue;
5585
+ }
5586
+ const columnMatch = indent === 0 ? trimmed.match(COLUMN_RE2) : null;
5500
5587
  if (columnMatch) {
5501
5588
  contentStarted = true;
5502
5589
  currentTagGroup = null;
@@ -5508,16 +5595,20 @@ function parseKanban(content, palette) {
5508
5595
  }
5509
5596
  currentCard = null;
5510
5597
  columnCounter++;
5511
- const rawColName = columnMatch[1].trim();
5512
- const wipStr = columnMatch[2];
5513
- const { label: colName, color: colColor } = extractColor(
5514
- rawColName,
5515
- palette
5516
- );
5598
+ const colName = columnMatch[1].trim();
5599
+ const colColor = columnMatch[2] ? resolveColor(columnMatch[2].trim(), palette) : void 0;
5600
+ let wipLimit;
5601
+ const pipeStr = columnMatch[3];
5602
+ if (pipeStr) {
5603
+ const wipMatch = pipeStr.match(/\bwip\s*:\s*(\d+)\b/i);
5604
+ if (wipMatch) {
5605
+ wipLimit = parseInt(wipMatch[1], 10);
5606
+ }
5607
+ }
5517
5608
  currentColumn = {
5518
5609
  id: `col-${columnCounter}`,
5519
5610
  name: colName,
5520
- wipLimit: wipStr ? parseInt(wipStr, 10) : void 0,
5611
+ wipLimit,
5521
5612
  color: colColor,
5522
5613
  cards: [],
5523
5614
  lineNumber
@@ -5532,24 +5623,25 @@ function parseKanban(content, palette) {
5532
5623
  warn(lineNumber, "Card line found before any column");
5533
5624
  continue;
5534
5625
  }
5535
- const indent = measureIndent(line10);
5536
- if (indent > 0 && currentCard) {
5626
+ if (currentCard && indent > cardBaseIndent) {
5537
5627
  currentCard.details.push(trimmed);
5538
5628
  currentCard.endLineNumber = lineNumber;
5539
5629
  continue;
5540
5630
  }
5541
- if (currentCard) {
5631
+ if (indent > 0) {
5632
+ cardCounter++;
5633
+ const card = parseCardLine(
5634
+ trimmed,
5635
+ lineNumber,
5636
+ cardCounter,
5637
+ aliasMap,
5638
+ palette
5639
+ );
5640
+ cardBaseIndent = indent;
5641
+ currentCard = card;
5642
+ currentColumn.cards.push(card);
5643
+ continue;
5542
5644
  }
5543
- cardCounter++;
5544
- const card = parseCardLine(
5545
- trimmed,
5546
- lineNumber,
5547
- cardCounter,
5548
- aliasMap,
5549
- palette
5550
- );
5551
- currentCard = card;
5552
- currentColumn.cards.push(card);
5553
5645
  }
5554
5646
  if (currentCard) {
5555
5647
  }
@@ -5583,7 +5675,7 @@ function parseKanban(content, palette) {
5583
5675
  }
5584
5676
  }
5585
5677
  if (result.columns.length === 0 && !result.error) {
5586
- return fail(1, "No columns found. Use == Column Name == to define columns");
5678
+ return fail(1, "No columns found. Use [Column Name] to define columns");
5587
5679
  }
5588
5680
  return result;
5589
5681
  }
@@ -5620,14 +5712,16 @@ function parseCardLine(trimmed, lineNumber, counter, aliasMap, palette) {
5620
5712
  color
5621
5713
  };
5622
5714
  }
5623
- var COLUMN_RE2;
5715
+ var COLUMN_RE2, LEGACY_COLUMN_RE;
5624
5716
  var init_parser5 = __esm({
5625
5717
  "src/kanban/parser.ts"() {
5626
5718
  "use strict";
5627
5719
  init_diagnostics();
5720
+ init_colors();
5628
5721
  init_tag_groups();
5629
5722
  init_parsing();
5630
- COLUMN_RE2 = /^==\s+(.+?)\s*(?:\[wip:\s*(\d+)\])?\s*==$/;
5723
+ COLUMN_RE2 = /^\[(.+?)\](?:\s*\(([^)]+)\))?\s*(?:\|\s*(.+))?$/;
5724
+ LEGACY_COLUMN_RE = /^==\s+(.+?)\s*(?:\[wip:\s*(\d+)\])?\s*==$/;
5631
5725
  }
5632
5726
  });
5633
5727
 
@@ -9578,7 +9672,7 @@ function computeCardArchive(content, parsed, cardId) {
9578
9672
  const trimmedEnd = withoutCard.length > 0 && withoutCard[withoutCard.length - 1].trim() === "" ? withoutCard : [...withoutCard, ""];
9579
9673
  return [
9580
9674
  ...trimmedEnd,
9581
- "== Archive ==",
9675
+ "[Archive]",
9582
9676
  ...cardLines
9583
9677
  ].join("\n");
9584
9678
  }
@@ -10457,7 +10551,7 @@ function drawCardinality(g, point, prevPoint, cardinality, color, useLabels) {
10457
10551
  g.append("line").attr("x1", bx + px * spread).attr("y1", by + py * spread).attr("x2", bx - px * spread).attr("y2", by - py * spread).attr("stroke", color).attr("stroke-width", sw);
10458
10552
  }
10459
10553
  }
10460
- function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem, exportDims) {
10554
+ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem, exportDims, activeTagGroup) {
10461
10555
  d3Selection5.select(container).selectAll(":not([data-d3-tooltip])").remove();
10462
10556
  const width = exportDims?.width ?? container.clientWidth;
10463
10557
  const height = exportDims?.height ?? container.clientHeight;
@@ -10527,8 +10621,16 @@ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem
10527
10621
  }
10528
10622
  for (let ni = 0; ni < layout.nodes.length; ni++) {
10529
10623
  const node = layout.nodes[ni];
10530
- const nodeColor2 = node.color ?? seriesColors2[ni % seriesColors2.length];
10624
+ const tagColor = resolveTagColor(node.metadata, parsed.tagGroups, activeTagGroup ?? null);
10625
+ const nodeColor2 = node.color ?? tagColor ?? seriesColors2[ni % seriesColors2.length];
10531
10626
  const nodeG = contentG.append("g").attr("transform", `translate(${node.x}, ${node.y})`).attr("class", "er-table").attr("data-line-number", String(node.lineNumber)).attr("data-node-id", node.id);
10627
+ if (activeTagGroup) {
10628
+ const tagKey = activeTagGroup.toLowerCase();
10629
+ const tagValue = node.metadata[tagKey];
10630
+ if (tagValue) {
10631
+ nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
10632
+ }
10633
+ }
10532
10634
  if (onClickItem) {
10533
10635
  nodeG.style("cursor", "pointer").on("click", () => {
10534
10636
  onClickItem(node.lineNumber);
@@ -10560,6 +10662,35 @@ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem
10560
10662
  }
10561
10663
  }
10562
10664
  }
10665
+ if (parsed.tagGroups.length > 0) {
10666
+ const LEGEND_Y_PAD = 16;
10667
+ const LEGEND_PILL_H = 22;
10668
+ const LEGEND_PILL_RX = 11;
10669
+ const LEGEND_PILL_PAD9 = 10;
10670
+ const LEGEND_GAP2 = 8;
10671
+ const LEGEND_FONT_SIZE2 = 11;
10672
+ const LEGEND_GROUP_GAP7 = 16;
10673
+ const legendG = svg.append("g").attr("class", "er-tag-legend");
10674
+ let legendX = DIAGRAM_PADDING5;
10675
+ let legendY = height - DIAGRAM_PADDING5;
10676
+ for (const group of parsed.tagGroups) {
10677
+ const groupG = legendG.append("g").attr("data-legend-group", group.name.toLowerCase());
10678
+ const labelText = groupG.append("text").attr("x", legendX).attr("y", legendY + LEGEND_PILL_H / 2).attr("dominant-baseline", "central").attr("fill", palette.textMuted).attr("font-size", LEGEND_FONT_SIZE2).attr("font-family", FONT_FAMILY).text(`${group.name}:`);
10679
+ const labelWidth = (labelText.node()?.getComputedTextLength?.() ?? group.name.length * 7) + 6;
10680
+ legendX += labelWidth;
10681
+ for (const entry of group.entries) {
10682
+ const pillG = groupG.append("g").attr("data-legend-entry", entry.value.toLowerCase()).style("cursor", "pointer");
10683
+ const tmpText = legendG.append("text").attr("font-size", LEGEND_FONT_SIZE2).attr("font-family", FONT_FAMILY).text(entry.value);
10684
+ const textW = tmpText.node()?.getComputedTextLength?.() ?? entry.value.length * 7;
10685
+ tmpText.remove();
10686
+ const pillW = textW + LEGEND_PILL_PAD9 * 2;
10687
+ pillG.append("rect").attr("x", legendX).attr("y", legendY).attr("width", pillW).attr("height", LEGEND_PILL_H).attr("rx", LEGEND_PILL_RX).attr("ry", LEGEND_PILL_RX).attr("fill", mix(entry.color, isDark ? palette.surface : palette.bg, 25)).attr("stroke", entry.color).attr("stroke-width", 1);
10688
+ pillG.append("text").attr("x", legendX + pillW / 2).attr("y", legendY + LEGEND_PILL_H / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("fill", palette.text).attr("font-size", LEGEND_FONT_SIZE2).attr("font-family", FONT_FAMILY).text(entry.value);
10689
+ legendX += pillW + LEGEND_GAP2;
10690
+ }
10691
+ legendX += LEGEND_GROUP_GAP7;
10692
+ }
10693
+ }
10563
10694
  }
10564
10695
  function renderERDiagramForExport(content, theme, palette) {
10565
10696
  const parsed = parseERDiagram(content, palette);
@@ -10605,6 +10736,7 @@ var init_renderer5 = __esm({
10605
10736
  init_fonts();
10606
10737
  init_color_utils();
10607
10738
  init_palettes();
10739
+ init_tag_groups();
10608
10740
  init_parser3();
10609
10741
  init_layout4();
10610
10742
  DIAGRAM_PADDING5 = 20;
@@ -18006,6 +18138,7 @@ function parseD3(content, palette) {
18006
18138
  timelineGroups: [],
18007
18139
  timelineEras: [],
18008
18140
  timelineMarkers: [],
18141
+ timelineTagGroups: [],
18009
18142
  timelineSort: "time",
18010
18143
  timelineScale: true,
18011
18144
  timelineSwimlanes: false,
@@ -18040,25 +18173,75 @@ function parseD3(content, palette) {
18040
18173
  const freeformLines = [];
18041
18174
  let currentArcGroup = null;
18042
18175
  let currentTimelineGroup = null;
18176
+ let currentTimelineTagGroup = null;
18177
+ const timelineAliasMap = /* @__PURE__ */ new Map();
18043
18178
  for (let i = 0; i < lines.length; i++) {
18044
- const line10 = lines[i].trim();
18179
+ const rawLine = lines[i];
18180
+ const line10 = rawLine.trim();
18181
+ const indent = rawLine.length - rawLine.trimStart().length;
18045
18182
  const lineNumber = i + 1;
18046
18183
  if (!line10) continue;
18047
- const sectionMatch = line10.match(/^#{2,}\s+(.+?)(?:\s*\(([^)]+)\))?\s*$/);
18048
- if (sectionMatch) {
18184
+ if (result.type === "timeline" && indent === 0) {
18185
+ const tagBlockMatch = matchTagBlockHeading(line10);
18186
+ if (tagBlockMatch) {
18187
+ if (tagBlockMatch.deprecated) {
18188
+ result.diagnostics.push(makeDgmoError(
18189
+ lineNumber,
18190
+ `'## ${tagBlockMatch.name}' is deprecated for tag groups \u2014 use 'tag: ${tagBlockMatch.name}' instead`,
18191
+ "warning"
18192
+ ));
18193
+ }
18194
+ currentTimelineTagGroup = {
18195
+ name: tagBlockMatch.name,
18196
+ alias: tagBlockMatch.alias,
18197
+ entries: [],
18198
+ lineNumber
18199
+ };
18200
+ if (tagBlockMatch.alias) {
18201
+ timelineAliasMap.set(tagBlockMatch.alias.toLowerCase(), tagBlockMatch.name.toLowerCase());
18202
+ }
18203
+ result.timelineTagGroups.push(currentTimelineTagGroup);
18204
+ continue;
18205
+ }
18206
+ }
18207
+ if (currentTimelineTagGroup && indent > 0) {
18208
+ const trimmedEntry = line10;
18209
+ const isDefault = /\bdefault\s*$/.test(trimmedEntry);
18210
+ const entryText = isDefault ? trimmedEntry.replace(/\s+default\s*$/, "").trim() : trimmedEntry;
18211
+ const { label, color } = extractColor(entryText, palette);
18212
+ if (color) {
18213
+ if (isDefault) currentTimelineTagGroup.defaultValue = label;
18214
+ currentTimelineTagGroup.entries.push({ value: label, color, lineNumber });
18215
+ continue;
18216
+ }
18217
+ }
18218
+ if (currentTimelineTagGroup && indent === 0) {
18219
+ currentTimelineTagGroup = null;
18220
+ }
18221
+ const groupMatch = line10.match(/^\[(.+?)\](?:\s*\(([^)]+)\))?\s*$/);
18222
+ if (groupMatch) {
18049
18223
  if (result.type === "arc") {
18050
- const name = sectionMatch[1].trim();
18051
- const color = sectionMatch[2] ? resolveColor(sectionMatch[2].trim(), palette) : null;
18224
+ const name = groupMatch[1].trim();
18225
+ const color = groupMatch[2] ? resolveColor(groupMatch[2].trim(), palette) : null;
18052
18226
  result.arcNodeGroups.push({ name, nodes: [], color, lineNumber });
18053
18227
  currentArcGroup = name;
18054
18228
  } else if (result.type === "timeline") {
18055
- const name = sectionMatch[1].trim();
18056
- const color = sectionMatch[2] ? resolveColor(sectionMatch[2].trim(), palette) : null;
18229
+ const name = groupMatch[1].trim();
18230
+ const color = groupMatch[2] ? resolveColor(groupMatch[2].trim(), palette) : null;
18057
18231
  result.timelineGroups.push({ name, color, lineNumber });
18058
18232
  currentTimelineGroup = name;
18059
18233
  }
18060
18234
  continue;
18061
18235
  }
18236
+ if (/^#{2,}\s+/.test(line10) && (result.type === "arc" || result.type === "timeline")) {
18237
+ const name = line10.replace(/^#{2,}\s+/, "").replace(/\s*\([^)]*\)\s*$/, "").trim();
18238
+ result.diagnostics.push(makeDgmoError(lineNumber, `'## ${name}' is no longer supported. Use '[${name}]' instead`, "warning"));
18239
+ continue;
18240
+ }
18241
+ if (indent === 0) {
18242
+ currentArcGroup = null;
18243
+ currentTimelineGroup = null;
18244
+ }
18062
18245
  if (line10.startsWith("//")) {
18063
18246
  continue;
18064
18247
  }
@@ -18130,11 +18313,14 @@ function parseD3(content, palette) {
18130
18313
  const amount = parseFloat(durationMatch[2]);
18131
18314
  const unit = durationMatch[3];
18132
18315
  const endDate = addDurationToDate(startDate, amount, unit);
18316
+ const segments = durationMatch[5].split("|");
18317
+ const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
18133
18318
  result.timelineEvents.push({
18134
18319
  date: startDate,
18135
18320
  endDate,
18136
- label: durationMatch[5].trim(),
18321
+ label: segments[0].trim(),
18137
18322
  group: currentTimelineGroup,
18323
+ metadata,
18138
18324
  lineNumber,
18139
18325
  uncertain
18140
18326
  });
@@ -18144,11 +18330,14 @@ function parseD3(content, palette) {
18144
18330
  /^(\d{4}(?:-\d{2})?(?:-\d{2})?)\s*->\s*(\d{4}(?:-\d{2})?(?:-\d{2})?)(\?)?\s*:\s*(.+)$/
18145
18331
  );
18146
18332
  if (rangeMatch) {
18333
+ const segments = rangeMatch[4].split("|");
18334
+ const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
18147
18335
  result.timelineEvents.push({
18148
18336
  date: rangeMatch[1],
18149
18337
  endDate: rangeMatch[2],
18150
- label: rangeMatch[4].trim(),
18338
+ label: segments[0].trim(),
18151
18339
  group: currentTimelineGroup,
18340
+ metadata,
18152
18341
  lineNumber,
18153
18342
  uncertain: rangeMatch[3] === "?"
18154
18343
  });
@@ -18158,11 +18347,14 @@ function parseD3(content, palette) {
18158
18347
  /^(\d{4}(?:-\d{2})?(?:-\d{2})?)\s*:\s*(.+)$/
18159
18348
  );
18160
18349
  if (pointMatch) {
18350
+ const segments = pointMatch[2].split("|");
18351
+ const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
18161
18352
  result.timelineEvents.push({
18162
18353
  date: pointMatch[1],
18163
18354
  endDate: null,
18164
- label: pointMatch[2].trim(),
18355
+ label: segments[0].trim(),
18165
18356
  group: currentTimelineGroup,
18357
+ metadata,
18166
18358
  lineNumber
18167
18359
  });
18168
18360
  continue;
@@ -18303,9 +18495,18 @@ function parseD3(content, palette) {
18303
18495
  continue;
18304
18496
  }
18305
18497
  if (key === "sort") {
18306
- const v = line10.substring(colonIndex + 1).trim().toLowerCase();
18307
- if (v === "time" || v === "group") {
18308
- result.timelineSort = v;
18498
+ const v = line10.substring(colonIndex + 1).trim();
18499
+ const vLower = v.toLowerCase();
18500
+ if (vLower === "time" || vLower === "group") {
18501
+ result.timelineSort = vLower;
18502
+ } else if (vLower === "tag" || vLower.startsWith("tag:")) {
18503
+ result.timelineSort = "tag";
18504
+ if (vLower.startsWith("tag:")) {
18505
+ const groupRef = v.substring(4).trim();
18506
+ if (groupRef) {
18507
+ result.timelineDefaultSwimlaneTG = groupRef;
18508
+ }
18509
+ }
18309
18510
  }
18310
18511
  continue;
18311
18512
  }
@@ -18425,7 +18626,7 @@ function parseD3(content, palette) {
18425
18626
  }
18426
18627
  if (result.arcNodeGroups.length > 0) {
18427
18628
  if (result.arcOrder === "name" || result.arcOrder === "degree") {
18428
- warn(1, `Cannot use "order: ${result.arcOrder}" with ## section headers. Use "order: group" or remove section headers.`);
18629
+ warn(1, `Cannot use "order: ${result.arcOrder}" with [Group] headers. Use "order: group" or remove group headers.`);
18429
18630
  result.arcOrder = "group";
18430
18631
  }
18431
18632
  if (result.arcOrder === "appearance") {
@@ -18438,6 +18639,42 @@ function parseD3(content, palette) {
18438
18639
  if (result.timelineEvents.length === 0) {
18439
18640
  warn(1, 'No events found. Add events as "YYYY: description" or "YYYY->YYYY: description"');
18440
18641
  }
18642
+ if (result.timelineTagGroups.length > 0) {
18643
+ validateTagValues(
18644
+ result.timelineEvents,
18645
+ result.timelineTagGroups,
18646
+ (line10, msg) => result.diagnostics.push(makeDgmoError(line10, msg, "warning")),
18647
+ suggest
18648
+ );
18649
+ for (const group of result.timelineTagGroups) {
18650
+ if (!group.defaultValue) continue;
18651
+ const key = group.name.toLowerCase();
18652
+ for (const event of result.timelineEvents) {
18653
+ if (!event.metadata[key]) {
18654
+ event.metadata[key] = group.defaultValue;
18655
+ }
18656
+ }
18657
+ }
18658
+ }
18659
+ if (result.timelineSort === "tag") {
18660
+ if (result.timelineTagGroups.length === 0) {
18661
+ warn(1, '"sort: tag" requires at least one tag group definition');
18662
+ result.timelineSort = "time";
18663
+ } else if (result.timelineDefaultSwimlaneTG) {
18664
+ const ref = result.timelineDefaultSwimlaneTG.toLowerCase();
18665
+ const match = result.timelineTagGroups.find(
18666
+ (g) => g.name.toLowerCase() === ref || g.alias?.toLowerCase() === ref
18667
+ );
18668
+ if (match) {
18669
+ result.timelineDefaultSwimlaneTG = match.name;
18670
+ } else {
18671
+ warn(1, `"sort: tag:${result.timelineDefaultSwimlaneTG}" \u2014 no tag group matches "${result.timelineDefaultSwimlaneTG}"`);
18672
+ result.timelineDefaultSwimlaneTG = result.timelineTagGroups[0].name;
18673
+ }
18674
+ } else {
18675
+ result.timelineDefaultSwimlaneTG = result.timelineTagGroups[0].name;
18676
+ }
18677
+ }
18441
18678
  return result;
18442
18679
  }
18443
18680
  if (result.type === "venn") {
@@ -19196,7 +19433,7 @@ function buildEventTooltipHtml(ev) {
19196
19433
  function buildEraTooltipHtml(era) {
19197
19434
  return `<strong>${era.label}</strong><br>${formatDateLabel(era.startDate)} \u2192 ${formatDateLabel(era.endDate)}`;
19198
19435
  }
19199
- function renderTimeline(container, parsed, palette, isDark, onClickItem, exportDims) {
19436
+ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportDims, activeTagGroup, swimlaneTagGroup, onTagStateChange) {
19200
19437
  d3Selection12.select(container).selectAll(":not([data-d3-tooltip])").remove();
19201
19438
  const {
19202
19439
  timelineEvents,
@@ -19210,6 +19447,9 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19210
19447
  orientation
19211
19448
  } = parsed;
19212
19449
  if (timelineEvents.length === 0) return;
19450
+ if (swimlaneTagGroup == null && timelineSort === "tag" && parsed.timelineDefaultSwimlaneTG) {
19451
+ swimlaneTagGroup = parsed.timelineDefaultSwimlaneTG;
19452
+ }
19213
19453
  const tooltip = createTooltip(container, palette, isDark);
19214
19454
  const width = exportDims?.width ?? container.clientWidth;
19215
19455
  const height = exportDims?.height ?? container.clientHeight;
@@ -19223,7 +19463,49 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19223
19463
  timelineGroups.forEach((grp, i) => {
19224
19464
  groupColorMap.set(grp.name, grp.color ?? colors[i % colors.length]);
19225
19465
  });
19466
+ let tagLanes = null;
19467
+ if (swimlaneTagGroup) {
19468
+ const tagKey = swimlaneTagGroup.toLowerCase();
19469
+ const tagGroup = parsed.timelineTagGroups.find(
19470
+ (g) => g.name.toLowerCase() === tagKey
19471
+ );
19472
+ if (tagGroup) {
19473
+ const buckets = /* @__PURE__ */ new Map();
19474
+ const otherEvents = [];
19475
+ for (const ev of timelineEvents) {
19476
+ const val = ev.metadata[tagKey];
19477
+ if (val) {
19478
+ const list = buckets.get(val) ?? [];
19479
+ list.push(ev);
19480
+ buckets.set(val, list);
19481
+ } else {
19482
+ otherEvents.push(ev);
19483
+ }
19484
+ }
19485
+ const laneEntries = [...buckets.entries()].sort((a, b) => {
19486
+ const aMin = Math.min(
19487
+ ...a[1].map((e) => parseTimelineDate(e.date))
19488
+ );
19489
+ const bMin = Math.min(
19490
+ ...b[1].map((e) => parseTimelineDate(e.date))
19491
+ );
19492
+ return aMin - bMin;
19493
+ });
19494
+ tagLanes = laneEntries.map(([name, events]) => ({ name, events }));
19495
+ if (otherEvents.length > 0) {
19496
+ tagLanes.push({ name: "(Other)", events: otherEvents });
19497
+ }
19498
+ for (const entry of tagGroup.entries) {
19499
+ groupColorMap.set(entry.value, entry.color);
19500
+ }
19501
+ }
19502
+ }
19503
+ const effectiveColorTG = activeTagGroup ?? swimlaneTagGroup ?? null;
19226
19504
  function eventColor(ev) {
19505
+ if (effectiveColorTG) {
19506
+ const tagColor = resolveTagColor(ev.metadata, parsed.timelineTagGroups, effectiveColorTG);
19507
+ if (tagColor) return tagColor;
19508
+ }
19227
19509
  if (ev.group && groupColorMap.has(ev.group)) {
19228
19510
  return groupColorMap.get(ev.group);
19229
19511
  }
@@ -19294,22 +19576,64 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19294
19576
  }
19295
19577
  function fadeReset(g) {
19296
19578
  g.selectAll(
19297
- ".tl-event, .tl-legend-item, .tl-lane-header, .tl-marker"
19579
+ ".tl-event, .tl-legend-item, .tl-lane-header, .tl-marker, .tl-tag-legend-entry"
19298
19580
  ).attr("opacity", 1);
19299
19581
  g.selectAll(".tl-era").attr("opacity", 1);
19300
19582
  }
19583
+ function fadeToTagValue(g, tagKey, tagValue) {
19584
+ const attrName = `data-tag-${tagKey}`;
19585
+ g.selectAll(".tl-event").each(function() {
19586
+ const el = d3Selection12.select(this);
19587
+ const val = el.attr(attrName);
19588
+ el.attr("opacity", val === tagValue ? 1 : FADE_OPACITY);
19589
+ });
19590
+ g.selectAll(".tl-legend-item, .tl-lane-header").attr(
19591
+ "opacity",
19592
+ FADE_OPACITY
19593
+ );
19594
+ g.selectAll(".tl-marker").attr("opacity", FADE_OPACITY);
19595
+ g.selectAll(".tl-tag-legend-entry").each(function() {
19596
+ const el = d3Selection12.select(this);
19597
+ const entryValue = el.attr("data-legend-entry");
19598
+ if (entryValue === "__group__") return;
19599
+ const entryGroup = el.attr("data-tag-group");
19600
+ el.attr("opacity", entryGroup === tagKey && entryValue === tagValue ? 1 : FADE_OPACITY);
19601
+ });
19602
+ }
19603
+ function setTagAttrs(evG, ev) {
19604
+ for (const [key, value] of Object.entries(ev.metadata)) {
19605
+ evG.attr(`data-tag-${key}`, value.toLowerCase());
19606
+ }
19607
+ }
19608
+ const tagLegendReserve = parsed.timelineTagGroups.length > 0 ? 36 : 0;
19301
19609
  if (isVertical) {
19302
- if (timelineSort === "group" && timelineGroups.length > 0) {
19303
- const groupNames = timelineGroups.map((gr) => gr.name);
19304
- const ungroupedEvents = timelineEvents.filter(
19305
- (ev) => ev.group === null || !groupNames.includes(ev.group)
19306
- );
19307
- const laneNames = ungroupedEvents.length > 0 ? [...groupNames, "(Other)"] : groupNames;
19610
+ const useGroupedVertical = tagLanes != null || timelineSort === "group" && timelineGroups.length > 0;
19611
+ if (useGroupedVertical) {
19612
+ let laneNames;
19613
+ let laneEventsByName;
19614
+ if (tagLanes) {
19615
+ laneNames = tagLanes.map((l) => l.name);
19616
+ laneEventsByName = new Map(tagLanes.map((l) => [l.name, l.events]));
19617
+ } else {
19618
+ const groupNames = timelineGroups.map((gr) => gr.name);
19619
+ const ungroupedEvents = timelineEvents.filter(
19620
+ (ev) => ev.group === null || !groupNames.includes(ev.group)
19621
+ );
19622
+ laneNames = ungroupedEvents.length > 0 ? [...groupNames, "(Other)"] : groupNames;
19623
+ laneEventsByName = new Map(
19624
+ laneNames.map((name) => [
19625
+ name,
19626
+ timelineEvents.filter(
19627
+ (ev) => name === "(Other)" ? ev.group === null || !groupNames.includes(ev.group) : ev.group === name
19628
+ )
19629
+ ])
19630
+ );
19631
+ }
19308
19632
  const laneCount = laneNames.length;
19309
19633
  const scaleMargin = timelineScale ? 40 : 0;
19310
19634
  const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
19311
19635
  const margin = {
19312
- top: 104 + markerMargin,
19636
+ top: 104 + markerMargin + tagLegendReserve,
19313
19637
  right: 40 + scaleMargin,
19314
19638
  bottom: 40,
19315
19639
  left: 60 + scaleMargin
@@ -19359,6 +19683,13 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19359
19683
  formatDateLabel(latestEndDateStr)
19360
19684
  );
19361
19685
  }
19686
+ if (timelineSwimlanes || tagLanes) {
19687
+ laneNames.forEach((laneName, laneIdx) => {
19688
+ const laneX = laneIdx * laneWidth;
19689
+ const fillColor = laneIdx % 2 === 0 ? textColor : "transparent";
19690
+ g.append("rect").attr("class", "tl-swimlane").attr("data-group", laneName).attr("x", laneX).attr("y", 0).attr("width", laneWidth).attr("height", innerHeight).attr("fill", fillColor).attr("opacity", 0.06);
19691
+ });
19692
+ }
19362
19693
  laneNames.forEach((laneName, laneIdx) => {
19363
19694
  const laneX = laneIdx * laneWidth;
19364
19695
  const laneColor = groupColorMap.get(laneName) ?? textColor;
@@ -19366,9 +19697,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19366
19697
  const headerG = g.append("g").attr("class", "tl-lane-header").attr("data-group", laneName).style("cursor", "pointer").on("mouseenter", () => fadeToGroup(g, laneName)).on("mouseleave", () => fadeReset(g));
19367
19698
  headerG.append("text").attr("x", laneCenter).attr("y", -15).attr("text-anchor", "middle").attr("fill", laneColor).attr("font-size", "12px").attr("font-weight", "600").text(laneName);
19368
19699
  g.append("line").attr("x1", laneCenter).attr("y1", 0).attr("x2", laneCenter).attr("y2", innerHeight).attr("stroke", mutedColor).attr("stroke-width", 1).attr("stroke-dasharray", "4,4");
19369
- const laneEvents = timelineEvents.filter(
19370
- (ev) => laneName === "(Other)" ? ev.group === null || !groupNames.includes(ev.group) : ev.group === laneName
19371
- );
19700
+ const laneEvents = laneEventsByName.get(laneName) ?? [];
19372
19701
  for (const ev of laneEvents) {
19373
19702
  const y = yScale(parseTimelineDate(ev.date));
19374
19703
  const evG = g.append("g").attr("class", "tl-event").attr("data-group", laneName).attr("data-line-number", String(ev.lineNumber)).attr("data-date", String(parseTimelineDate(ev.date))).attr(
@@ -19385,10 +19714,12 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19385
19714
  }).on("click", () => {
19386
19715
  if (onClickItem && ev.lineNumber) onClickItem(ev.lineNumber);
19387
19716
  });
19717
+ setTagAttrs(evG, ev);
19718
+ const evColor = eventColor(ev);
19388
19719
  if (ev.endDate) {
19389
19720
  const y2 = yScale(parseTimelineDate(ev.endDate));
19390
19721
  const rectH = Math.max(y2 - y, 4);
19391
- let fill2 = laneColor;
19722
+ let fill2 = evColor;
19392
19723
  if (ev.uncertain) {
19393
19724
  const gradientId = `uncertain-vg-${ev.lineNumber}`;
19394
19725
  const defs = svg.select("defs").node() || svg.append("defs").node();
@@ -19402,7 +19733,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19402
19733
  evG.append("rect").attr("x", laneCenter - 6).attr("y", y).attr("width", 12).attr("height", rectH).attr("rx", 4).attr("fill", fill2);
19403
19734
  evG.append("text").attr("x", laneCenter + 14).attr("y", y + rectH / 2).attr("dy", "0.35em").attr("fill", textColor).attr("font-size", "10px").text(ev.label);
19404
19735
  } else {
19405
- evG.append("circle").attr("cx", laneCenter).attr("cy", y).attr("r", 4).attr("fill", laneColor).attr("stroke", bgColor).attr("stroke-width", 1.5);
19736
+ evG.append("circle").attr("cx", laneCenter).attr("cy", y).attr("r", 4).attr("fill", evColor).attr("stroke", bgColor).attr("stroke-width", 1.5);
19406
19737
  evG.append("text").attr("x", laneCenter + 10).attr("y", y).attr("dy", "0.35em").attr("fill", textColor).attr("font-size", "10px").text(ev.label);
19407
19738
  }
19408
19739
  }
@@ -19411,7 +19742,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19411
19742
  const scaleMargin = timelineScale ? 40 : 0;
19412
19743
  const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
19413
19744
  const margin = {
19414
- top: 104 + markerMargin,
19745
+ top: 104 + markerMargin + tagLegendReserve,
19415
19746
  right: 200,
19416
19747
  bottom: 40,
19417
19748
  left: 60 + scaleMargin
@@ -19491,6 +19822,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19491
19822
  }).on("click", () => {
19492
19823
  if (onClickItem && ev.lineNumber) onClickItem(ev.lineNumber);
19493
19824
  });
19825
+ setTagAttrs(evG, ev);
19494
19826
  if (ev.endDate) {
19495
19827
  const y2 = yScale(parseTimelineDate(ev.endDate));
19496
19828
  const rectH = Math.max(y2 - y, 4);
@@ -19524,18 +19856,24 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19524
19856
  }
19525
19857
  const BAR_H = 22;
19526
19858
  const GROUP_GAP = 12;
19527
- if (timelineSort === "group" && timelineGroups.length > 0) {
19528
- const groupNames = timelineGroups.map((gr) => gr.name);
19529
- const ungroupedEvents = timelineEvents.filter(
19530
- (ev) => ev.group === null || !groupNames.includes(ev.group)
19531
- );
19532
- const laneNames = ungroupedEvents.length > 0 ? [...groupNames, "(Other)"] : groupNames;
19533
- const lanes = laneNames.map((name) => ({
19534
- name,
19535
- events: timelineEvents.filter(
19536
- (ev) => name === "(Other)" ? ev.group === null || !groupNames.includes(ev.group) : ev.group === name
19537
- )
19538
- }));
19859
+ const useGroupedHorizontal = tagLanes != null || timelineSort === "group" && timelineGroups.length > 0;
19860
+ if (useGroupedHorizontal) {
19861
+ let lanes;
19862
+ if (tagLanes) {
19863
+ lanes = tagLanes;
19864
+ } else {
19865
+ const groupNames = timelineGroups.map((gr) => gr.name);
19866
+ const ungroupedEvents = timelineEvents.filter(
19867
+ (ev) => ev.group === null || !groupNames.includes(ev.group)
19868
+ );
19869
+ const laneNames = ungroupedEvents.length > 0 ? [...groupNames, "(Other)"] : groupNames;
19870
+ lanes = laneNames.map((name) => ({
19871
+ name,
19872
+ events: timelineEvents.filter(
19873
+ (ev) => name === "(Other)" ? ev.group === null || !groupNames.includes(ev.group) : ev.group === name
19874
+ )
19875
+ }));
19876
+ }
19539
19877
  const totalEventRows = lanes.reduce((s, l) => s + l.events.length, 0);
19540
19878
  const scaleMargin = timelineScale ? 24 : 0;
19541
19879
  const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
@@ -19543,7 +19881,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19543
19881
  const dynamicLeftMargin = Math.max(120, maxGroupNameLen * 7 + 30);
19544
19882
  const baseTopMargin = title ? 50 : 20;
19545
19883
  const margin = {
19546
- top: baseTopMargin + (timelineScale ? 40 : 0) + markerMargin,
19884
+ top: baseTopMargin + (timelineScale ? 40 : 0) + markerMargin + tagLegendReserve,
19547
19885
  right: 40,
19548
19886
  bottom: 40 + scaleMargin,
19549
19887
  left: dynamicLeftMargin
@@ -19595,7 +19933,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19595
19933
  );
19596
19934
  }
19597
19935
  let curY = markerMargin;
19598
- if (timelineSwimlanes) {
19936
+ if (timelineSwimlanes || tagLanes) {
19599
19937
  let swimY = markerMargin;
19600
19938
  lanes.forEach((lane, idx) => {
19601
19939
  const laneSpan = lane.events.length * rowH;
@@ -19646,12 +19984,14 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19646
19984
  }).on("click", () => {
19647
19985
  if (onClickItem && ev.lineNumber) onClickItem(ev.lineNumber);
19648
19986
  });
19987
+ setTagAttrs(evG, ev);
19988
+ const evColor = eventColor(ev);
19649
19989
  if (ev.endDate) {
19650
19990
  const x2 = xScale(parseTimelineDate(ev.endDate));
19651
19991
  const rectW = Math.max(x2 - x, 4);
19652
19992
  const estLabelWidth = ev.label.length * 7 + 16;
19653
19993
  const labelFitsInside = rectW >= estLabelWidth;
19654
- let fill2 = laneColor;
19994
+ let fill2 = evColor;
19655
19995
  if (ev.uncertain) {
19656
19996
  const gradientId = `uncertain-${ev.lineNumber}`;
19657
19997
  const defs = svg.select("defs").node() || svg.append("defs").node();
@@ -19659,7 +19999,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19659
19999
  { offset: "0%", opacity: 1 },
19660
20000
  { offset: "80%", opacity: 1 },
19661
20001
  { offset: "100%", opacity: 0 }
19662
- ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", laneColor).attr("stop-opacity", (d) => d.opacity);
20002
+ ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", evColor).attr("stop-opacity", (d) => d.opacity);
19663
20003
  fill2 = `url(#${gradientId})`;
19664
20004
  }
19665
20005
  evG.append("rect").attr("x", x).attr("y", y - BAR_H / 2).attr("width", rectW).attr("height", BAR_H).attr("rx", 4).attr("fill", fill2);
@@ -19676,7 +20016,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19676
20016
  const wouldFlipLeft = x > innerWidth * 0.6;
19677
20017
  const labelFitsLeft = x - 10 - estLabelWidth > 0;
19678
20018
  const flipLeft = wouldFlipLeft && labelFitsLeft;
19679
- evG.append("circle").attr("cx", x).attr("cy", y).attr("r", 5).attr("fill", laneColor).attr("stroke", bgColor).attr("stroke-width", 1.5);
20019
+ evG.append("circle").attr("cx", x).attr("cy", y).attr("r", 5).attr("fill", evColor).attr("stroke", bgColor).attr("stroke-width", 1.5);
19680
20020
  evG.append("text").attr("x", flipLeft ? x - 10 : x + 10).attr("y", y).attr("dy", "0.35em").attr("text-anchor", flipLeft ? "end" : "start").attr("fill", textColor).attr("font-size", "12px").text(ev.label);
19681
20021
  }
19682
20022
  });
@@ -19687,7 +20027,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19687
20027
  const scaleMargin = timelineScale ? 24 : 0;
19688
20028
  const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
19689
20029
  const margin = {
19690
- top: 104 + (timelineScale ? 40 : 0) + markerMargin,
20030
+ top: 104 + (timelineScale ? 40 : 0) + markerMargin + tagLegendReserve,
19691
20031
  right: 40,
19692
20032
  bottom: 40 + scaleMargin,
19693
20033
  left: 60
@@ -19783,6 +20123,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19783
20123
  }).on("click", () => {
19784
20124
  if (onClickItem && ev.lineNumber) onClickItem(ev.lineNumber);
19785
20125
  });
20126
+ setTagAttrs(evG, ev);
19786
20127
  if (ev.endDate) {
19787
20128
  const x2 = xScale(parseTimelineDate(ev.endDate));
19788
20129
  const rectW = Math.max(x2 - x, 4);
@@ -19818,6 +20159,163 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19818
20159
  }
19819
20160
  });
19820
20161
  }
20162
+ if (parsed.timelineTagGroups.length > 0) {
20163
+ const LG_HEIGHT = 28;
20164
+ const LG_PILL_PAD = 16;
20165
+ const LG_PILL_FONT_SIZE = 11;
20166
+ const LG_PILL_FONT_W = LG_PILL_FONT_SIZE * 0.6;
20167
+ const LG_CAPSULE_PAD = 4;
20168
+ const LG_DOT_R = 4;
20169
+ const LG_ENTRY_FONT_SIZE = 10;
20170
+ const LG_ENTRY_FONT_W = LG_ENTRY_FONT_SIZE * 0.6;
20171
+ const LG_ENTRY_DOT_GAP = 4;
20172
+ const LG_ENTRY_TRAIL = 8;
20173
+ const LG_GROUP_GAP = 12;
20174
+ const LG_ICON_W = 20;
20175
+ const mainSvg = d3Selection12.select(container).select("svg");
20176
+ const mainG = mainSvg.select("g");
20177
+ if (!mainSvg.empty() && !mainG.empty()) {
20178
+ let drawSwimlaneIcon2 = function(parent, x, y, isSwimActive) {
20179
+ const iconG = parent.append("g").attr("class", "tl-swimlane-icon").attr("transform", `translate(${x}, ${y})`).style("cursor", "pointer");
20180
+ const barColor = isSwimActive ? palette.primary : palette.textMuted;
20181
+ const barOpacity = isSwimActive ? 1 : 0.35;
20182
+ const bars = [
20183
+ { y: 0, w: 8 },
20184
+ { y: 4, w: 12 },
20185
+ { y: 8, w: 6 }
20186
+ ];
20187
+ for (const bar of bars) {
20188
+ iconG.append("rect").attr("x", 0).attr("y", bar.y).attr("width", bar.w).attr("height", 2).attr("rx", 1).attr("fill", barColor).attr("opacity", barOpacity);
20189
+ }
20190
+ return iconG;
20191
+ }, relayout2 = function() {
20192
+ renderTimeline(
20193
+ container,
20194
+ parsed,
20195
+ palette,
20196
+ isDark,
20197
+ onClickItem,
20198
+ exportDims,
20199
+ currentActiveGroup,
20200
+ currentSwimlaneGroup,
20201
+ onTagStateChange
20202
+ );
20203
+ }, drawLegend2 = function() {
20204
+ mainSvg.selectAll(".tl-tag-legend-group").remove();
20205
+ const totalW = legendGroups.reduce((s, lg) => {
20206
+ const isActive = currentActiveGroup != null && lg.group.name.toLowerCase() === currentActiveGroup.toLowerCase();
20207
+ return s + (isActive ? lg.expandedWidth : lg.minifiedWidth);
20208
+ }, 0) + (legendGroups.length - 1) * LG_GROUP_GAP;
20209
+ let cx = (width - totalW) / 2;
20210
+ for (const lg of legendGroups) {
20211
+ const groupKey = lg.group.name.toLowerCase();
20212
+ const isActive = currentActiveGroup != null && currentActiveGroup.toLowerCase() === groupKey;
20213
+ const isSwimActive = currentSwimlaneGroup != null && currentSwimlaneGroup.toLowerCase() === groupKey;
20214
+ const pillLabel = lg.group.name;
20215
+ const pillWidth = pillLabel.length * LG_PILL_FONT_W + LG_PILL_PAD;
20216
+ const gEl = mainSvg.append("g").attr("transform", `translate(${cx}, ${legendY})`).attr("class", "tl-tag-legend-group tl-tag-legend-entry").attr("data-legend-group", groupKey).attr("data-tag-group", groupKey).attr("data-legend-entry", "__group__").style("cursor", "pointer").on("click", () => {
20217
+ currentActiveGroup = currentActiveGroup === groupKey ? null : groupKey;
20218
+ drawLegend2();
20219
+ recolorEvents2();
20220
+ onTagStateChange?.(currentActiveGroup, currentSwimlaneGroup);
20221
+ });
20222
+ if (isActive) {
20223
+ gEl.append("rect").attr("width", lg.expandedWidth).attr("height", LG_HEIGHT).attr("rx", LG_HEIGHT / 2).attr("fill", groupBg);
20224
+ }
20225
+ const pillXOff = isActive ? LG_CAPSULE_PAD : 0;
20226
+ const pillYOff = isActive ? LG_CAPSULE_PAD : 0;
20227
+ const pillH = LG_HEIGHT - (isActive ? LG_CAPSULE_PAD * 2 : 0);
20228
+ gEl.append("rect").attr("x", pillXOff).attr("y", pillYOff).attr("width", pillWidth).attr("height", pillH).attr("rx", pillH / 2).attr("fill", isActive ? palette.bg : groupBg);
20229
+ if (isActive) {
20230
+ gEl.append("rect").attr("x", pillXOff).attr("y", pillYOff).attr("width", pillWidth).attr("height", pillH).attr("rx", pillH / 2).attr("fill", "none").attr("stroke", mix(palette.textMuted, palette.bg, 50)).attr("stroke-width", 0.75);
20231
+ }
20232
+ gEl.append("text").attr("x", pillXOff + pillWidth / 2).attr("y", LG_HEIGHT / 2 + LG_PILL_FONT_SIZE / 2 - 2).attr("font-size", LG_PILL_FONT_SIZE).attr("font-weight", "500").attr("font-family", FONT_FAMILY).attr("fill", isActive ? palette.text : palette.textMuted).attr("text-anchor", "middle").text(pillLabel);
20233
+ if (isActive) {
20234
+ const iconX = pillXOff + pillWidth + 5;
20235
+ const iconY = (LG_HEIGHT - 10) / 2;
20236
+ const iconEl = drawSwimlaneIcon2(gEl, iconX, iconY, isSwimActive);
20237
+ iconEl.attr("data-swimlane-toggle", groupKey).on("click", (event) => {
20238
+ event.stopPropagation();
20239
+ currentSwimlaneGroup = currentSwimlaneGroup === groupKey ? null : groupKey;
20240
+ onTagStateChange?.(currentActiveGroup, currentSwimlaneGroup);
20241
+ relayout2();
20242
+ });
20243
+ let entryX = pillXOff + pillWidth + LG_ICON_W + 4;
20244
+ for (const entry of lg.group.entries) {
20245
+ const tagKey = lg.group.name.toLowerCase();
20246
+ const tagVal = entry.value.toLowerCase();
20247
+ const entryG = gEl.append("g").attr("class", "tl-tag-legend-entry").attr("data-tag-group", tagKey).attr("data-legend-entry", tagVal).style("cursor", "pointer").on("mouseenter", (event) => {
20248
+ event.stopPropagation();
20249
+ fadeToTagValue(mainG, tagKey, tagVal);
20250
+ mainSvg.selectAll(".tl-tag-legend-entry").each(function() {
20251
+ const el = d3Selection12.select(this);
20252
+ const ev = el.attr("data-legend-entry");
20253
+ if (ev === "__group__") return;
20254
+ const eg = el.attr("data-tag-group");
20255
+ el.attr("opacity", eg === tagKey && ev === tagVal ? 1 : FADE_OPACITY);
20256
+ });
20257
+ }).on("mouseleave", (event) => {
20258
+ event.stopPropagation();
20259
+ fadeReset(mainG);
20260
+ mainSvg.selectAll(".tl-tag-legend-entry").attr("opacity", 1);
20261
+ }).on("click", (event) => {
20262
+ event.stopPropagation();
20263
+ });
20264
+ entryG.append("circle").attr("cx", entryX + LG_DOT_R).attr("cy", LG_HEIGHT / 2).attr("r", LG_DOT_R).attr("fill", entry.color);
20265
+ const textX = entryX + LG_DOT_R * 2 + LG_ENTRY_DOT_GAP;
20266
+ entryG.append("text").attr("x", textX).attr("y", LG_HEIGHT / 2 + LG_ENTRY_FONT_SIZE / 2 - 1).attr("font-size", LG_ENTRY_FONT_SIZE).attr("font-family", FONT_FAMILY).attr("fill", palette.textMuted).text(entry.value);
20267
+ entryX = textX + entry.value.length * LG_ENTRY_FONT_W + LG_ENTRY_TRAIL;
20268
+ }
20269
+ }
20270
+ cx += (isActive ? lg.expandedWidth : lg.minifiedWidth) + LG_GROUP_GAP;
20271
+ }
20272
+ }, recolorEvents2 = function() {
20273
+ const colorTG = currentActiveGroup ?? swimlaneTagGroup ?? null;
20274
+ mainG.selectAll(".tl-event").each(function() {
20275
+ const el = d3Selection12.select(this);
20276
+ const lineNum = el.attr("data-line-number");
20277
+ const ev = lineNum ? eventByLine.get(lineNum) : void 0;
20278
+ if (!ev) return;
20279
+ let color;
20280
+ if (colorTG) {
20281
+ const tagColor = resolveTagColor(
20282
+ ev.metadata,
20283
+ parsed.timelineTagGroups,
20284
+ colorTG
20285
+ );
20286
+ color = tagColor ?? (ev.group && groupColorMap.has(ev.group) ? groupColorMap.get(ev.group) : textColor);
20287
+ } else {
20288
+ color = ev.group && groupColorMap.has(ev.group) ? groupColorMap.get(ev.group) : textColor;
20289
+ }
20290
+ el.selectAll("rect").attr("fill", color);
20291
+ el.selectAll("circle:not(.tl-event-point-outline)").attr("fill", color);
20292
+ });
20293
+ };
20294
+ var drawSwimlaneIcon = drawSwimlaneIcon2, relayout = relayout2, drawLegend = drawLegend2, recolorEvents = recolorEvents2;
20295
+ const legendY = title ? 50 : 10;
20296
+ const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
20297
+ const legendGroups = parsed.timelineTagGroups.map((g) => {
20298
+ const pillW = g.name.length * LG_PILL_FONT_W + LG_PILL_PAD;
20299
+ let entryX = LG_CAPSULE_PAD + pillW + LG_ICON_W + 4;
20300
+ for (const entry of g.entries) {
20301
+ const textX = entryX + LG_DOT_R * 2 + LG_ENTRY_DOT_GAP;
20302
+ entryX = textX + entry.value.length * LG_ENTRY_FONT_W + LG_ENTRY_TRAIL;
20303
+ }
20304
+ return {
20305
+ group: g,
20306
+ minifiedWidth: pillW,
20307
+ expandedWidth: entryX + LG_CAPSULE_PAD
20308
+ };
20309
+ });
20310
+ let currentActiveGroup = activeTagGroup ?? null;
20311
+ let currentSwimlaneGroup = swimlaneTagGroup ?? null;
20312
+ const eventByLine = /* @__PURE__ */ new Map();
20313
+ for (const ev of timelineEvents) {
20314
+ eventByLine.set(String(ev.lineNumber), ev);
20315
+ }
20316
+ drawLegend2();
20317
+ }
20318
+ }
19821
20319
  }
19822
20320
  function getRotateFn(mode) {
19823
20321
  if (mode === "mixed") return () => Math.random() > 0.5 ? 0 : 90;
@@ -20785,7 +21283,16 @@ async function renderD3ForExport(content, theme, palette, orgExportState, option
20785
21283
  } else if (parsed.type === "arc") {
20786
21284
  renderArcDiagram(container, parsed, effectivePalette, isDark, void 0, dims);
20787
21285
  } else if (parsed.type === "timeline") {
20788
- renderTimeline(container, parsed, effectivePalette, isDark, void 0, dims);
21286
+ renderTimeline(
21287
+ container,
21288
+ parsed,
21289
+ effectivePalette,
21290
+ isDark,
21291
+ void 0,
21292
+ dims,
21293
+ orgExportState?.activeTagGroup,
21294
+ orgExportState?.swimlaneTagGroup
21295
+ );
20789
21296
  } else if (parsed.type === "venn") {
20790
21297
  renderVenn(container, parsed, effectivePalette, isDark, void 0, dims);
20791
21298
  } else if (parsed.type === "quadrant") {
@@ -20808,8 +21315,10 @@ var init_d3 = __esm({
20808
21315
  init_branding();
20809
21316
  init_colors();
20810
21317
  init_palettes();
21318
+ init_color_utils();
20811
21319
  init_diagnostics();
20812
21320
  init_parsing();
21321
+ init_tag_groups();
20813
21322
  DEFAULT_CLOUD_OPTIONS = {
20814
21323
  rotate: "none",
20815
21324
  max: 0,
@@ -21810,6 +22319,9 @@ function encodeDiagramUrl(dsl, options) {
21810
22319
  if (options?.viewState?.collapsedGroups?.length) {
21811
22320
  hash += `&cg=${encodeURIComponent(options.viewState.collapsedGroups.join(","))}`;
21812
22321
  }
22322
+ if (options?.viewState?.swimlaneTagGroup) {
22323
+ hash += `&swim=${encodeURIComponent(options.viewState.swimlaneTagGroup)}`;
22324
+ }
21813
22325
  return { url: `${baseUrl}?${hash}#${hash}` };
21814
22326
  }
21815
22327
  function decodeDiagramUrl(hash) {
@@ -21833,6 +22345,9 @@ function decodeDiagramUrl(hash) {
21833
22345
  if (key === "cg" && val) {
21834
22346
  viewState.collapsedGroups = val.split(",").filter(Boolean);
21835
22347
  }
22348
+ if (key === "swim" && val) {
22349
+ viewState.swimlaneTagGroup = val;
22350
+ }
21836
22351
  }
21837
22352
  if (payload.startsWith("dgmo=")) {
21838
22353
  payload = payload.slice(5);