@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.js CHANGED
@@ -1357,6 +1357,94 @@ var init_parsing = __esm({
1357
1357
  }
1358
1358
  });
1359
1359
 
1360
+ // src/utils/tag-groups.ts
1361
+ function isTagBlockHeading(trimmed) {
1362
+ return TAG_BLOCK_RE.test(trimmed) || GROUP_HEADING_RE.test(trimmed);
1363
+ }
1364
+ function resolveTagColor(metadata, tagGroups, activeGroupName, isContainer) {
1365
+ if (!activeGroupName) return void 0;
1366
+ const group = tagGroups.find(
1367
+ (g) => g.name.toLowerCase() === activeGroupName.toLowerCase()
1368
+ );
1369
+ if (!group) return void 0;
1370
+ const metaValue = metadata[group.name.toLowerCase()] ?? (isContainer ? void 0 : group.defaultValue);
1371
+ if (!metaValue) return "#999999";
1372
+ return group.entries.find(
1373
+ (e) => e.value.toLowerCase() === metaValue.toLowerCase()
1374
+ )?.color ?? "#999999";
1375
+ }
1376
+ function validateTagValues(entities, tagGroups, pushWarning, suggestFn) {
1377
+ if (tagGroups.length === 0) return;
1378
+ const groupMap = /* @__PURE__ */ new Map();
1379
+ for (const g of tagGroups) groupMap.set(g.name.toLowerCase(), g);
1380
+ for (const entity of entities) {
1381
+ for (const [key, value] of Object.entries(entity.metadata)) {
1382
+ const group = groupMap.get(key);
1383
+ if (!group) continue;
1384
+ const match = group.entries.some(
1385
+ (e) => e.value.toLowerCase() === value.toLowerCase()
1386
+ );
1387
+ if (!match) {
1388
+ const defined = group.entries.map((e) => e.value);
1389
+ let msg = `Unknown value '${value}' for tag group '${group.name}'`;
1390
+ const hint = suggestFn?.(value, defined);
1391
+ if (hint) {
1392
+ msg += `. ${hint}`;
1393
+ } else {
1394
+ msg += ` \u2014 defined values: ${defined.join(", ")}`;
1395
+ }
1396
+ pushWarning(entity.lineNumber, msg);
1397
+ }
1398
+ }
1399
+ }
1400
+ }
1401
+ function injectDefaultTagMetadata(entities, tagGroups, skip) {
1402
+ const defaults = [];
1403
+ for (const group of tagGroups) {
1404
+ if (group.defaultValue) {
1405
+ defaults.push({ key: group.name.toLowerCase(), value: group.defaultValue });
1406
+ }
1407
+ }
1408
+ if (defaults.length === 0) return;
1409
+ for (const entity of entities) {
1410
+ if (skip?.(entity)) continue;
1411
+ for (const { key, value } of defaults) {
1412
+ if (!(key in entity.metadata)) {
1413
+ entity.metadata[key] = value;
1414
+ }
1415
+ }
1416
+ }
1417
+ }
1418
+ function matchTagBlockHeading(trimmed) {
1419
+ const tagMatch = trimmed.match(TAG_BLOCK_RE);
1420
+ if (tagMatch) {
1421
+ return {
1422
+ name: tagMatch[1].trim(),
1423
+ alias: tagMatch[2] || void 0,
1424
+ colorHint: tagMatch[3] || void 0,
1425
+ deprecated: false
1426
+ };
1427
+ }
1428
+ const groupMatch = trimmed.match(GROUP_HEADING_RE);
1429
+ if (groupMatch) {
1430
+ return {
1431
+ name: groupMatch[1].trim(),
1432
+ alias: groupMatch[2] || void 0,
1433
+ colorHint: groupMatch[3] || void 0,
1434
+ deprecated: true
1435
+ };
1436
+ }
1437
+ return null;
1438
+ }
1439
+ var TAG_BLOCK_RE, GROUP_HEADING_RE;
1440
+ var init_tag_groups = __esm({
1441
+ "src/utils/tag-groups.ts"() {
1442
+ "use strict";
1443
+ TAG_BLOCK_RE = /^tag:\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/i;
1444
+ GROUP_HEADING_RE = /^##\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/;
1445
+ }
1446
+ });
1447
+
1360
1448
  // src/sequence/participant-inference.ts
1361
1449
  function inferParticipantType(name) {
1362
1450
  for (const rule of PARTICIPANT_RULES) {
@@ -1686,94 +1774,6 @@ var init_arrows = __esm({
1686
1774
  }
1687
1775
  });
1688
1776
 
1689
- // src/utils/tag-groups.ts
1690
- function isTagBlockHeading(trimmed) {
1691
- return TAG_BLOCK_RE.test(trimmed) || GROUP_HEADING_RE.test(trimmed);
1692
- }
1693
- function resolveTagColor(metadata, tagGroups, activeGroupName, isContainer) {
1694
- if (!activeGroupName) return void 0;
1695
- const group = tagGroups.find(
1696
- (g) => g.name.toLowerCase() === activeGroupName.toLowerCase()
1697
- );
1698
- if (!group) return void 0;
1699
- const metaValue = metadata[group.name.toLowerCase()] ?? (isContainer ? void 0 : group.defaultValue);
1700
- if (!metaValue) return "#999999";
1701
- return group.entries.find(
1702
- (e) => e.value.toLowerCase() === metaValue.toLowerCase()
1703
- )?.color ?? "#999999";
1704
- }
1705
- function validateTagValues(entities, tagGroups, pushWarning, suggestFn) {
1706
- if (tagGroups.length === 0) return;
1707
- const groupMap = /* @__PURE__ */ new Map();
1708
- for (const g of tagGroups) groupMap.set(g.name.toLowerCase(), g);
1709
- for (const entity of entities) {
1710
- for (const [key, value] of Object.entries(entity.metadata)) {
1711
- const group = groupMap.get(key);
1712
- if (!group) continue;
1713
- const match = group.entries.some(
1714
- (e) => e.value.toLowerCase() === value.toLowerCase()
1715
- );
1716
- if (!match) {
1717
- const defined = group.entries.map((e) => e.value);
1718
- let msg = `Unknown value '${value}' for tag group '${group.name}'`;
1719
- const hint = suggestFn?.(value, defined);
1720
- if (hint) {
1721
- msg += `. ${hint}`;
1722
- } else {
1723
- msg += ` \u2014 defined values: ${defined.join(", ")}`;
1724
- }
1725
- pushWarning(entity.lineNumber, msg);
1726
- }
1727
- }
1728
- }
1729
- }
1730
- function injectDefaultTagMetadata(entities, tagGroups, skip) {
1731
- const defaults = [];
1732
- for (const group of tagGroups) {
1733
- if (group.defaultValue) {
1734
- defaults.push({ key: group.name.toLowerCase(), value: group.defaultValue });
1735
- }
1736
- }
1737
- if (defaults.length === 0) return;
1738
- for (const entity of entities) {
1739
- if (skip?.(entity)) continue;
1740
- for (const { key, value } of defaults) {
1741
- if (!(key in entity.metadata)) {
1742
- entity.metadata[key] = value;
1743
- }
1744
- }
1745
- }
1746
- }
1747
- function matchTagBlockHeading(trimmed) {
1748
- const tagMatch = trimmed.match(TAG_BLOCK_RE);
1749
- if (tagMatch) {
1750
- return {
1751
- name: tagMatch[1].trim(),
1752
- alias: tagMatch[2] || void 0,
1753
- colorHint: tagMatch[3] || void 0,
1754
- deprecated: false
1755
- };
1756
- }
1757
- const groupMatch = trimmed.match(GROUP_HEADING_RE);
1758
- if (groupMatch) {
1759
- return {
1760
- name: groupMatch[1].trim(),
1761
- alias: groupMatch[2] || void 0,
1762
- colorHint: groupMatch[3] || void 0,
1763
- deprecated: true
1764
- };
1765
- }
1766
- return null;
1767
- }
1768
- var TAG_BLOCK_RE, GROUP_HEADING_RE;
1769
- var init_tag_groups = __esm({
1770
- "src/utils/tag-groups.ts"() {
1771
- "use strict";
1772
- TAG_BLOCK_RE = /^tag:\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/i;
1773
- GROUP_HEADING_RE = /^##\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/;
1774
- }
1775
- });
1776
-
1777
1777
  // src/sequence/parser.ts
1778
1778
  var parser_exports = {};
1779
1779
  __export(parser_exports, {
@@ -3361,6 +3361,7 @@ function parseERDiagram(content, palette) {
3361
3361
  options: {},
3362
3362
  tables: [],
3363
3363
  relationships: [],
3364
+ tagGroups: [],
3364
3365
  diagnostics: [],
3365
3366
  error: null
3366
3367
  };
@@ -3378,6 +3379,8 @@ function parseERDiagram(content, palette) {
3378
3379
  const tableMap = /* @__PURE__ */ new Map();
3379
3380
  let currentTable = null;
3380
3381
  let contentStarted = false;
3382
+ let currentTagGroup = null;
3383
+ const aliasMap = /* @__PURE__ */ new Map();
3381
3384
  function getOrCreateTable(name, lineNumber) {
3382
3385
  const id = tableId(name);
3383
3386
  const existing = tableMap.get(id);
@@ -3386,6 +3389,7 @@ function parseERDiagram(content, palette) {
3386
3389
  id,
3387
3390
  name,
3388
3391
  columns: [],
3392
+ metadata: {},
3389
3393
  lineNumber
3390
3394
  };
3391
3395
  tableMap.set(id, table);
@@ -3402,6 +3406,50 @@ function parseERDiagram(content, palette) {
3402
3406
  continue;
3403
3407
  }
3404
3408
  if (trimmed.startsWith("//")) continue;
3409
+ if (!contentStarted && indent === 0) {
3410
+ const tagBlockMatch = matchTagBlockHeading(trimmed);
3411
+ if (tagBlockMatch) {
3412
+ if (tagBlockMatch.deprecated) {
3413
+ result.diagnostics.push(makeDgmoError(
3414
+ lineNumber,
3415
+ `'## ${tagBlockMatch.name}' is deprecated for tag groups \u2014 use 'tag: ${tagBlockMatch.name}' instead`,
3416
+ "warning"
3417
+ ));
3418
+ }
3419
+ currentTagGroup = {
3420
+ name: tagBlockMatch.name,
3421
+ alias: tagBlockMatch.alias,
3422
+ entries: [],
3423
+ lineNumber
3424
+ };
3425
+ if (tagBlockMatch.alias) {
3426
+ aliasMap.set(tagBlockMatch.alias.toLowerCase(), tagBlockMatch.name.toLowerCase());
3427
+ }
3428
+ result.tagGroups.push(currentTagGroup);
3429
+ continue;
3430
+ }
3431
+ }
3432
+ if (currentTagGroup && !contentStarted && indent > 0) {
3433
+ const isDefault = /\bdefault\s*$/.test(trimmed);
3434
+ const entryText = isDefault ? trimmed.replace(/\s+default\s*$/, "").trim() : trimmed;
3435
+ const { label, color } = extractColor(entryText, palette);
3436
+ if (!color) {
3437
+ result.diagnostics.push(makeDgmoError(
3438
+ lineNumber,
3439
+ `Expected 'Value(color)' in tag group '${currentTagGroup.name}'`,
3440
+ "warning"
3441
+ ));
3442
+ continue;
3443
+ }
3444
+ if (isDefault) {
3445
+ currentTagGroup.defaultValue = label;
3446
+ }
3447
+ currentTagGroup.entries.push({ value: label, color, lineNumber });
3448
+ continue;
3449
+ }
3450
+ if (currentTagGroup && indent === 0) {
3451
+ currentTagGroup = null;
3452
+ }
3405
3453
  if (!contentStarted && indent === 0 && /^[a-z][a-z0-9-]*\s*:/i.test(trimmed)) {
3406
3454
  const colonIdx = trimmed.indexOf(":");
3407
3455
  const key = trimmed.substring(0, colonIdx).trim().toLowerCase();
@@ -3483,6 +3531,11 @@ function parseERDiagram(content, palette) {
3483
3531
  const table = getOrCreateTable(name, lineNumber);
3484
3532
  if (color) table.color = color;
3485
3533
  table.lineNumber = lineNumber;
3534
+ const pipeStr = tableDecl[3]?.trim();
3535
+ if (pipeStr) {
3536
+ const meta = parsePipeMetadata(["", pipeStr], aliasMap);
3537
+ Object.assign(table.metadata, meta);
3538
+ }
3486
3539
  currentTable = table;
3487
3540
  continue;
3488
3541
  }
@@ -3492,6 +3545,27 @@ function parseERDiagram(content, palette) {
3492
3545
  result.diagnostics.push(diag);
3493
3546
  result.error = formatDgmoError(diag);
3494
3547
  }
3548
+ if (result.tagGroups.length > 0) {
3549
+ const tagEntities = result.tables.map((t) => ({
3550
+ metadata: t.metadata,
3551
+ lineNumber: t.lineNumber
3552
+ }));
3553
+ validateTagValues(
3554
+ tagEntities,
3555
+ result.tagGroups,
3556
+ (line10, msg) => result.diagnostics.push(makeDgmoError(line10, msg, "warning")),
3557
+ suggest
3558
+ );
3559
+ for (const group of result.tagGroups) {
3560
+ if (!group.defaultValue) continue;
3561
+ const key = group.name.toLowerCase();
3562
+ for (const table of result.tables) {
3563
+ if (!table.metadata[key]) {
3564
+ table.metadata[key] = group.defaultValue;
3565
+ }
3566
+ }
3567
+ }
3568
+ }
3495
3569
  if (result.tables.length >= 2 && result.relationships.length >= 1 && !result.error) {
3496
3570
  const connectedIds = /* @__PURE__ */ new Set();
3497
3571
  for (const rel of result.relationships) {
@@ -3543,7 +3617,8 @@ var init_parser3 = __esm({
3543
3617
  init_colors();
3544
3618
  init_diagnostics();
3545
3619
  init_parsing();
3546
- TABLE_DECL_RE = /^([a-zA-Z_]\w*)(?:\s+\(([^)]+)\))?\s*$/;
3620
+ init_tag_groups();
3621
+ TABLE_DECL_RE = /^([a-zA-Z_]\w*)(?:\s*\(([^)]+)\))?(?:\s*\|(.+))?$/;
3547
3622
  COLUMN_RE = /^(\w+)(?:\s*:\s*(\w[\w()]*(?:\s*\[\])?))?(?:\s+\[([^\]]+)\])?\s*$/;
3548
3623
  INDENT_REL_RE = /^([1*?])-(?:(.+)-)?([1*?])\s+([a-zA-Z_]\w*)\s*$/;
3549
3624
  CONSTRAINT_MAP = {
@@ -3581,7 +3656,10 @@ function parseChart(content, palette) {
3581
3656
  const trimmed = lines[i].trim();
3582
3657
  const lineNumber = i + 1;
3583
3658
  if (!trimmed) continue;
3584
- if (/^#{2,}\s+/.test(trimmed)) continue;
3659
+ if (/^#{2,}\s+/.test(trimmed)) {
3660
+ result.diagnostics.push(makeDgmoError(lineNumber, `'${trimmed}' \u2014 ## syntax is no longer supported. Use [Group] containers instead`));
3661
+ continue;
3662
+ }
3585
3663
  if (trimmed.startsWith("//")) continue;
3586
3664
  const colonIndex = trimmed.indexOf(":");
3587
3665
  if (colonIndex === -1) continue;
@@ -3729,9 +3807,16 @@ function parseEChart(content, palette) {
3729
3807
  const trimmed = lines[i].trim();
3730
3808
  const lineNumber = i + 1;
3731
3809
  if (!trimmed) continue;
3732
- const mdCategoryMatch = trimmed.match(/^#{2,}\s+(.+)$/);
3733
- if (mdCategoryMatch) {
3734
- const { label: catName, color: catColor } = extractColor(mdCategoryMatch[1].trim(), palette);
3810
+ if (/^#{2,}\s+/.test(trimmed)) {
3811
+ const name = trimmed.replace(/^#{2,}\s+/, "").replace(/\s*\([^)]*\)\s*$/, "").trim();
3812
+ result.diagnostics.push(makeDgmoError(lineNumber, `'## ${name}' is no longer supported. Use '[${name}]' instead`));
3813
+ continue;
3814
+ }
3815
+ if (trimmed.startsWith("//")) continue;
3816
+ const categoryMatch = trimmed.match(/^\[(.+?)\](?:\s*\(([^)]+)\))?\s*$/);
3817
+ if (categoryMatch) {
3818
+ const catName = categoryMatch[1].trim();
3819
+ const catColor = categoryMatch[2] ? resolveColor(categoryMatch[2].trim(), palette) : null;
3735
3820
  if (catColor) {
3736
3821
  if (!result.categoryColors) result.categoryColors = {};
3737
3822
  result.categoryColors[catName] = catColor;
@@ -3739,12 +3824,6 @@ function parseEChart(content, palette) {
3739
3824
  currentCategory = catName;
3740
3825
  continue;
3741
3826
  }
3742
- if (trimmed.startsWith("//")) continue;
3743
- const categoryMatch = trimmed.match(/^\[(.+)\]$/);
3744
- if (categoryMatch) {
3745
- currentCategory = categoryMatch[1].trim();
3746
- continue;
3747
- }
3748
3827
  const colonIndex = trimmed.indexOf(":");
3749
3828
  if (result.type === "sankey" && colonIndex === -1) {
3750
3829
  const indent = measureIndent(lines[i]);
@@ -5375,6 +5454,7 @@ function parseKanban(content, palette) {
5375
5454
  let currentTagGroup = null;
5376
5455
  let currentColumn = null;
5377
5456
  let currentCard = null;
5457
+ let cardBaseIndent = 0;
5378
5458
  let columnCounter = 0;
5379
5459
  let cardCounter = 0;
5380
5460
  const aliasMap = /* @__PURE__ */ new Map();
@@ -5474,7 +5554,14 @@ function parseKanban(content, palette) {
5474
5554
  }
5475
5555
  currentTagGroup = null;
5476
5556
  }
5477
- const columnMatch = trimmed.match(COLUMN_RE2);
5557
+ const indent = measureIndent(line10);
5558
+ if (LEGACY_COLUMN_RE.test(trimmed)) {
5559
+ const legacyMatch = trimmed.match(LEGACY_COLUMN_RE);
5560
+ const name = legacyMatch[1].replace(/\s*\(.*\)\s*$/, "").trim();
5561
+ warn(lineNumber, `'== ${name} ==' is no longer supported. Use '[${name}]' instead`);
5562
+ continue;
5563
+ }
5564
+ const columnMatch = indent === 0 ? trimmed.match(COLUMN_RE2) : null;
5478
5565
  if (columnMatch) {
5479
5566
  contentStarted = true;
5480
5567
  currentTagGroup = null;
@@ -5486,16 +5573,20 @@ function parseKanban(content, palette) {
5486
5573
  }
5487
5574
  currentCard = null;
5488
5575
  columnCounter++;
5489
- const rawColName = columnMatch[1].trim();
5490
- const wipStr = columnMatch[2];
5491
- const { label: colName, color: colColor } = extractColor(
5492
- rawColName,
5493
- palette
5494
- );
5576
+ const colName = columnMatch[1].trim();
5577
+ const colColor = columnMatch[2] ? resolveColor(columnMatch[2].trim(), palette) : void 0;
5578
+ let wipLimit;
5579
+ const pipeStr = columnMatch[3];
5580
+ if (pipeStr) {
5581
+ const wipMatch = pipeStr.match(/\bwip\s*:\s*(\d+)\b/i);
5582
+ if (wipMatch) {
5583
+ wipLimit = parseInt(wipMatch[1], 10);
5584
+ }
5585
+ }
5495
5586
  currentColumn = {
5496
5587
  id: `col-${columnCounter}`,
5497
5588
  name: colName,
5498
- wipLimit: wipStr ? parseInt(wipStr, 10) : void 0,
5589
+ wipLimit,
5499
5590
  color: colColor,
5500
5591
  cards: [],
5501
5592
  lineNumber
@@ -5510,24 +5601,25 @@ function parseKanban(content, palette) {
5510
5601
  warn(lineNumber, "Card line found before any column");
5511
5602
  continue;
5512
5603
  }
5513
- const indent = measureIndent(line10);
5514
- if (indent > 0 && currentCard) {
5604
+ if (currentCard && indent > cardBaseIndent) {
5515
5605
  currentCard.details.push(trimmed);
5516
5606
  currentCard.endLineNumber = lineNumber;
5517
5607
  continue;
5518
5608
  }
5519
- if (currentCard) {
5609
+ if (indent > 0) {
5610
+ cardCounter++;
5611
+ const card = parseCardLine(
5612
+ trimmed,
5613
+ lineNumber,
5614
+ cardCounter,
5615
+ aliasMap,
5616
+ palette
5617
+ );
5618
+ cardBaseIndent = indent;
5619
+ currentCard = card;
5620
+ currentColumn.cards.push(card);
5621
+ continue;
5520
5622
  }
5521
- cardCounter++;
5522
- const card = parseCardLine(
5523
- trimmed,
5524
- lineNumber,
5525
- cardCounter,
5526
- aliasMap,
5527
- palette
5528
- );
5529
- currentCard = card;
5530
- currentColumn.cards.push(card);
5531
5623
  }
5532
5624
  if (currentCard) {
5533
5625
  }
@@ -5561,7 +5653,7 @@ function parseKanban(content, palette) {
5561
5653
  }
5562
5654
  }
5563
5655
  if (result.columns.length === 0 && !result.error) {
5564
- return fail(1, "No columns found. Use == Column Name == to define columns");
5656
+ return fail(1, "No columns found. Use [Column Name] to define columns");
5565
5657
  }
5566
5658
  return result;
5567
5659
  }
@@ -5598,14 +5690,16 @@ function parseCardLine(trimmed, lineNumber, counter, aliasMap, palette) {
5598
5690
  color
5599
5691
  };
5600
5692
  }
5601
- var COLUMN_RE2;
5693
+ var COLUMN_RE2, LEGACY_COLUMN_RE;
5602
5694
  var init_parser5 = __esm({
5603
5695
  "src/kanban/parser.ts"() {
5604
5696
  "use strict";
5605
5697
  init_diagnostics();
5698
+ init_colors();
5606
5699
  init_tag_groups();
5607
5700
  init_parsing();
5608
- COLUMN_RE2 = /^==\s+(.+?)\s*(?:\[wip:\s*(\d+)\])?\s*==$/;
5701
+ COLUMN_RE2 = /^\[(.+?)\](?:\s*\(([^)]+)\))?\s*(?:\|\s*(.+))?$/;
5702
+ LEGACY_COLUMN_RE = /^==\s+(.+?)\s*(?:\[wip:\s*(\d+)\])?\s*==$/;
5609
5703
  }
5610
5704
  });
5611
5705
 
@@ -9556,7 +9650,7 @@ function computeCardArchive(content, parsed, cardId) {
9556
9650
  const trimmedEnd = withoutCard.length > 0 && withoutCard[withoutCard.length - 1].trim() === "" ? withoutCard : [...withoutCard, ""];
9557
9651
  return [
9558
9652
  ...trimmedEnd,
9559
- "== Archive ==",
9653
+ "[Archive]",
9560
9654
  ...cardLines
9561
9655
  ].join("\n");
9562
9656
  }
@@ -10437,7 +10531,7 @@ function drawCardinality(g, point, prevPoint, cardinality, color, useLabels) {
10437
10531
  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);
10438
10532
  }
10439
10533
  }
10440
- function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem, exportDims) {
10534
+ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem, exportDims, activeTagGroup) {
10441
10535
  d3Selection5.select(container).selectAll(":not([data-d3-tooltip])").remove();
10442
10536
  const width = exportDims?.width ?? container.clientWidth;
10443
10537
  const height = exportDims?.height ?? container.clientHeight;
@@ -10507,8 +10601,16 @@ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem
10507
10601
  }
10508
10602
  for (let ni = 0; ni < layout.nodes.length; ni++) {
10509
10603
  const node = layout.nodes[ni];
10510
- const nodeColor2 = node.color ?? seriesColors2[ni % seriesColors2.length];
10604
+ const tagColor = resolveTagColor(node.metadata, parsed.tagGroups, activeTagGroup ?? null);
10605
+ const nodeColor2 = node.color ?? tagColor ?? seriesColors2[ni % seriesColors2.length];
10511
10606
  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);
10607
+ if (activeTagGroup) {
10608
+ const tagKey = activeTagGroup.toLowerCase();
10609
+ const tagValue = node.metadata[tagKey];
10610
+ if (tagValue) {
10611
+ nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
10612
+ }
10613
+ }
10512
10614
  if (onClickItem) {
10513
10615
  nodeG.style("cursor", "pointer").on("click", () => {
10514
10616
  onClickItem(node.lineNumber);
@@ -10540,6 +10642,35 @@ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem
10540
10642
  }
10541
10643
  }
10542
10644
  }
10645
+ if (parsed.tagGroups.length > 0) {
10646
+ const LEGEND_Y_PAD = 16;
10647
+ const LEGEND_PILL_H = 22;
10648
+ const LEGEND_PILL_RX = 11;
10649
+ const LEGEND_PILL_PAD9 = 10;
10650
+ const LEGEND_GAP2 = 8;
10651
+ const LEGEND_FONT_SIZE2 = 11;
10652
+ const LEGEND_GROUP_GAP7 = 16;
10653
+ const legendG = svg.append("g").attr("class", "er-tag-legend");
10654
+ let legendX = DIAGRAM_PADDING5;
10655
+ let legendY = height - DIAGRAM_PADDING5;
10656
+ for (const group of parsed.tagGroups) {
10657
+ const groupG = legendG.append("g").attr("data-legend-group", group.name.toLowerCase());
10658
+ 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}:`);
10659
+ const labelWidth = (labelText.node()?.getComputedTextLength?.() ?? group.name.length * 7) + 6;
10660
+ legendX += labelWidth;
10661
+ for (const entry of group.entries) {
10662
+ const pillG = groupG.append("g").attr("data-legend-entry", entry.value.toLowerCase()).style("cursor", "pointer");
10663
+ const tmpText = legendG.append("text").attr("font-size", LEGEND_FONT_SIZE2).attr("font-family", FONT_FAMILY).text(entry.value);
10664
+ const textW = tmpText.node()?.getComputedTextLength?.() ?? entry.value.length * 7;
10665
+ tmpText.remove();
10666
+ const pillW = textW + LEGEND_PILL_PAD9 * 2;
10667
+ 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);
10668
+ 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);
10669
+ legendX += pillW + LEGEND_GAP2;
10670
+ }
10671
+ legendX += LEGEND_GROUP_GAP7;
10672
+ }
10673
+ }
10543
10674
  }
10544
10675
  function renderERDiagramForExport(content, theme, palette) {
10545
10676
  const parsed = parseERDiagram(content, palette);
@@ -10583,6 +10714,7 @@ var init_renderer5 = __esm({
10583
10714
  init_fonts();
10584
10715
  init_color_utils();
10585
10716
  init_palettes();
10717
+ init_tag_groups();
10586
10718
  init_parser3();
10587
10719
  init_layout4();
10588
10720
  DIAGRAM_PADDING5 = 20;
@@ -17989,6 +18121,7 @@ function parseD3(content, palette) {
17989
18121
  timelineGroups: [],
17990
18122
  timelineEras: [],
17991
18123
  timelineMarkers: [],
18124
+ timelineTagGroups: [],
17992
18125
  timelineSort: "time",
17993
18126
  timelineScale: true,
17994
18127
  timelineSwimlanes: false,
@@ -18023,25 +18156,75 @@ function parseD3(content, palette) {
18023
18156
  const freeformLines = [];
18024
18157
  let currentArcGroup = null;
18025
18158
  let currentTimelineGroup = null;
18159
+ let currentTimelineTagGroup = null;
18160
+ const timelineAliasMap = /* @__PURE__ */ new Map();
18026
18161
  for (let i = 0; i < lines.length; i++) {
18027
- const line10 = lines[i].trim();
18162
+ const rawLine = lines[i];
18163
+ const line10 = rawLine.trim();
18164
+ const indent = rawLine.length - rawLine.trimStart().length;
18028
18165
  const lineNumber = i + 1;
18029
18166
  if (!line10) continue;
18030
- const sectionMatch = line10.match(/^#{2,}\s+(.+?)(?:\s*\(([^)]+)\))?\s*$/);
18031
- if (sectionMatch) {
18167
+ if (result.type === "timeline" && indent === 0) {
18168
+ const tagBlockMatch = matchTagBlockHeading(line10);
18169
+ if (tagBlockMatch) {
18170
+ if (tagBlockMatch.deprecated) {
18171
+ result.diagnostics.push(makeDgmoError(
18172
+ lineNumber,
18173
+ `'## ${tagBlockMatch.name}' is deprecated for tag groups \u2014 use 'tag: ${tagBlockMatch.name}' instead`,
18174
+ "warning"
18175
+ ));
18176
+ }
18177
+ currentTimelineTagGroup = {
18178
+ name: tagBlockMatch.name,
18179
+ alias: tagBlockMatch.alias,
18180
+ entries: [],
18181
+ lineNumber
18182
+ };
18183
+ if (tagBlockMatch.alias) {
18184
+ timelineAliasMap.set(tagBlockMatch.alias.toLowerCase(), tagBlockMatch.name.toLowerCase());
18185
+ }
18186
+ result.timelineTagGroups.push(currentTimelineTagGroup);
18187
+ continue;
18188
+ }
18189
+ }
18190
+ if (currentTimelineTagGroup && indent > 0) {
18191
+ const trimmedEntry = line10;
18192
+ const isDefault = /\bdefault\s*$/.test(trimmedEntry);
18193
+ const entryText = isDefault ? trimmedEntry.replace(/\s+default\s*$/, "").trim() : trimmedEntry;
18194
+ const { label, color } = extractColor(entryText, palette);
18195
+ if (color) {
18196
+ if (isDefault) currentTimelineTagGroup.defaultValue = label;
18197
+ currentTimelineTagGroup.entries.push({ value: label, color, lineNumber });
18198
+ continue;
18199
+ }
18200
+ }
18201
+ if (currentTimelineTagGroup && indent === 0) {
18202
+ currentTimelineTagGroup = null;
18203
+ }
18204
+ const groupMatch = line10.match(/^\[(.+?)\](?:\s*\(([^)]+)\))?\s*$/);
18205
+ if (groupMatch) {
18032
18206
  if (result.type === "arc") {
18033
- const name = sectionMatch[1].trim();
18034
- const color = sectionMatch[2] ? resolveColor(sectionMatch[2].trim(), palette) : null;
18207
+ const name = groupMatch[1].trim();
18208
+ const color = groupMatch[2] ? resolveColor(groupMatch[2].trim(), palette) : null;
18035
18209
  result.arcNodeGroups.push({ name, nodes: [], color, lineNumber });
18036
18210
  currentArcGroup = name;
18037
18211
  } else if (result.type === "timeline") {
18038
- const name = sectionMatch[1].trim();
18039
- const color = sectionMatch[2] ? resolveColor(sectionMatch[2].trim(), palette) : null;
18212
+ const name = groupMatch[1].trim();
18213
+ const color = groupMatch[2] ? resolveColor(groupMatch[2].trim(), palette) : null;
18040
18214
  result.timelineGroups.push({ name, color, lineNumber });
18041
18215
  currentTimelineGroup = name;
18042
18216
  }
18043
18217
  continue;
18044
18218
  }
18219
+ if (/^#{2,}\s+/.test(line10) && (result.type === "arc" || result.type === "timeline")) {
18220
+ const name = line10.replace(/^#{2,}\s+/, "").replace(/\s*\([^)]*\)\s*$/, "").trim();
18221
+ result.diagnostics.push(makeDgmoError(lineNumber, `'## ${name}' is no longer supported. Use '[${name}]' instead`, "warning"));
18222
+ continue;
18223
+ }
18224
+ if (indent === 0) {
18225
+ currentArcGroup = null;
18226
+ currentTimelineGroup = null;
18227
+ }
18045
18228
  if (line10.startsWith("//")) {
18046
18229
  continue;
18047
18230
  }
@@ -18113,11 +18296,14 @@ function parseD3(content, palette) {
18113
18296
  const amount = parseFloat(durationMatch[2]);
18114
18297
  const unit = durationMatch[3];
18115
18298
  const endDate = addDurationToDate(startDate, amount, unit);
18299
+ const segments = durationMatch[5].split("|");
18300
+ const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
18116
18301
  result.timelineEvents.push({
18117
18302
  date: startDate,
18118
18303
  endDate,
18119
- label: durationMatch[5].trim(),
18304
+ label: segments[0].trim(),
18120
18305
  group: currentTimelineGroup,
18306
+ metadata,
18121
18307
  lineNumber,
18122
18308
  uncertain
18123
18309
  });
@@ -18127,11 +18313,14 @@ function parseD3(content, palette) {
18127
18313
  /^(\d{4}(?:-\d{2})?(?:-\d{2})?)\s*->\s*(\d{4}(?:-\d{2})?(?:-\d{2})?)(\?)?\s*:\s*(.+)$/
18128
18314
  );
18129
18315
  if (rangeMatch) {
18316
+ const segments = rangeMatch[4].split("|");
18317
+ const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
18130
18318
  result.timelineEvents.push({
18131
18319
  date: rangeMatch[1],
18132
18320
  endDate: rangeMatch[2],
18133
- label: rangeMatch[4].trim(),
18321
+ label: segments[0].trim(),
18134
18322
  group: currentTimelineGroup,
18323
+ metadata,
18135
18324
  lineNumber,
18136
18325
  uncertain: rangeMatch[3] === "?"
18137
18326
  });
@@ -18141,11 +18330,14 @@ function parseD3(content, palette) {
18141
18330
  /^(\d{4}(?:-\d{2})?(?:-\d{2})?)\s*:\s*(.+)$/
18142
18331
  );
18143
18332
  if (pointMatch) {
18333
+ const segments = pointMatch[2].split("|");
18334
+ const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
18144
18335
  result.timelineEvents.push({
18145
18336
  date: pointMatch[1],
18146
18337
  endDate: null,
18147
- label: pointMatch[2].trim(),
18338
+ label: segments[0].trim(),
18148
18339
  group: currentTimelineGroup,
18340
+ metadata,
18149
18341
  lineNumber
18150
18342
  });
18151
18343
  continue;
@@ -18286,9 +18478,18 @@ function parseD3(content, palette) {
18286
18478
  continue;
18287
18479
  }
18288
18480
  if (key === "sort") {
18289
- const v = line10.substring(colonIndex + 1).trim().toLowerCase();
18290
- if (v === "time" || v === "group") {
18291
- result.timelineSort = v;
18481
+ const v = line10.substring(colonIndex + 1).trim();
18482
+ const vLower = v.toLowerCase();
18483
+ if (vLower === "time" || vLower === "group") {
18484
+ result.timelineSort = vLower;
18485
+ } else if (vLower === "tag" || vLower.startsWith("tag:")) {
18486
+ result.timelineSort = "tag";
18487
+ if (vLower.startsWith("tag:")) {
18488
+ const groupRef = v.substring(4).trim();
18489
+ if (groupRef) {
18490
+ result.timelineDefaultSwimlaneTG = groupRef;
18491
+ }
18492
+ }
18292
18493
  }
18293
18494
  continue;
18294
18495
  }
@@ -18408,7 +18609,7 @@ function parseD3(content, palette) {
18408
18609
  }
18409
18610
  if (result.arcNodeGroups.length > 0) {
18410
18611
  if (result.arcOrder === "name" || result.arcOrder === "degree") {
18411
- warn(1, `Cannot use "order: ${result.arcOrder}" with ## section headers. Use "order: group" or remove section headers.`);
18612
+ warn(1, `Cannot use "order: ${result.arcOrder}" with [Group] headers. Use "order: group" or remove group headers.`);
18412
18613
  result.arcOrder = "group";
18413
18614
  }
18414
18615
  if (result.arcOrder === "appearance") {
@@ -18421,6 +18622,42 @@ function parseD3(content, palette) {
18421
18622
  if (result.timelineEvents.length === 0) {
18422
18623
  warn(1, 'No events found. Add events as "YYYY: description" or "YYYY->YYYY: description"');
18423
18624
  }
18625
+ if (result.timelineTagGroups.length > 0) {
18626
+ validateTagValues(
18627
+ result.timelineEvents,
18628
+ result.timelineTagGroups,
18629
+ (line10, msg) => result.diagnostics.push(makeDgmoError(line10, msg, "warning")),
18630
+ suggest
18631
+ );
18632
+ for (const group of result.timelineTagGroups) {
18633
+ if (!group.defaultValue) continue;
18634
+ const key = group.name.toLowerCase();
18635
+ for (const event of result.timelineEvents) {
18636
+ if (!event.metadata[key]) {
18637
+ event.metadata[key] = group.defaultValue;
18638
+ }
18639
+ }
18640
+ }
18641
+ }
18642
+ if (result.timelineSort === "tag") {
18643
+ if (result.timelineTagGroups.length === 0) {
18644
+ warn(1, '"sort: tag" requires at least one tag group definition');
18645
+ result.timelineSort = "time";
18646
+ } else if (result.timelineDefaultSwimlaneTG) {
18647
+ const ref = result.timelineDefaultSwimlaneTG.toLowerCase();
18648
+ const match = result.timelineTagGroups.find(
18649
+ (g) => g.name.toLowerCase() === ref || g.alias?.toLowerCase() === ref
18650
+ );
18651
+ if (match) {
18652
+ result.timelineDefaultSwimlaneTG = match.name;
18653
+ } else {
18654
+ warn(1, `"sort: tag:${result.timelineDefaultSwimlaneTG}" \u2014 no tag group matches "${result.timelineDefaultSwimlaneTG}"`);
18655
+ result.timelineDefaultSwimlaneTG = result.timelineTagGroups[0].name;
18656
+ }
18657
+ } else {
18658
+ result.timelineDefaultSwimlaneTG = result.timelineTagGroups[0].name;
18659
+ }
18660
+ }
18424
18661
  return result;
18425
18662
  }
18426
18663
  if (result.type === "venn") {
@@ -19179,7 +19416,7 @@ function buildEventTooltipHtml(ev) {
19179
19416
  function buildEraTooltipHtml(era) {
19180
19417
  return `<strong>${era.label}</strong><br>${formatDateLabel(era.startDate)} \u2192 ${formatDateLabel(era.endDate)}`;
19181
19418
  }
19182
- function renderTimeline(container, parsed, palette, isDark, onClickItem, exportDims) {
19419
+ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportDims, activeTagGroup, swimlaneTagGroup, onTagStateChange) {
19183
19420
  d3Selection12.select(container).selectAll(":not([data-d3-tooltip])").remove();
19184
19421
  const {
19185
19422
  timelineEvents,
@@ -19193,6 +19430,9 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19193
19430
  orientation
19194
19431
  } = parsed;
19195
19432
  if (timelineEvents.length === 0) return;
19433
+ if (swimlaneTagGroup == null && timelineSort === "tag" && parsed.timelineDefaultSwimlaneTG) {
19434
+ swimlaneTagGroup = parsed.timelineDefaultSwimlaneTG;
19435
+ }
19196
19436
  const tooltip = createTooltip(container, palette, isDark);
19197
19437
  const width = exportDims?.width ?? container.clientWidth;
19198
19438
  const height = exportDims?.height ?? container.clientHeight;
@@ -19206,7 +19446,49 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19206
19446
  timelineGroups.forEach((grp, i) => {
19207
19447
  groupColorMap.set(grp.name, grp.color ?? colors[i % colors.length]);
19208
19448
  });
19449
+ let tagLanes = null;
19450
+ if (swimlaneTagGroup) {
19451
+ const tagKey = swimlaneTagGroup.toLowerCase();
19452
+ const tagGroup = parsed.timelineTagGroups.find(
19453
+ (g) => g.name.toLowerCase() === tagKey
19454
+ );
19455
+ if (tagGroup) {
19456
+ const buckets = /* @__PURE__ */ new Map();
19457
+ const otherEvents = [];
19458
+ for (const ev of timelineEvents) {
19459
+ const val = ev.metadata[tagKey];
19460
+ if (val) {
19461
+ const list = buckets.get(val) ?? [];
19462
+ list.push(ev);
19463
+ buckets.set(val, list);
19464
+ } else {
19465
+ otherEvents.push(ev);
19466
+ }
19467
+ }
19468
+ const laneEntries = [...buckets.entries()].sort((a, b) => {
19469
+ const aMin = Math.min(
19470
+ ...a[1].map((e) => parseTimelineDate(e.date))
19471
+ );
19472
+ const bMin = Math.min(
19473
+ ...b[1].map((e) => parseTimelineDate(e.date))
19474
+ );
19475
+ return aMin - bMin;
19476
+ });
19477
+ tagLanes = laneEntries.map(([name, events]) => ({ name, events }));
19478
+ if (otherEvents.length > 0) {
19479
+ tagLanes.push({ name: "(Other)", events: otherEvents });
19480
+ }
19481
+ for (const entry of tagGroup.entries) {
19482
+ groupColorMap.set(entry.value, entry.color);
19483
+ }
19484
+ }
19485
+ }
19486
+ const effectiveColorTG = activeTagGroup ?? swimlaneTagGroup ?? null;
19209
19487
  function eventColor(ev) {
19488
+ if (effectiveColorTG) {
19489
+ const tagColor = resolveTagColor(ev.metadata, parsed.timelineTagGroups, effectiveColorTG);
19490
+ if (tagColor) return tagColor;
19491
+ }
19210
19492
  if (ev.group && groupColorMap.has(ev.group)) {
19211
19493
  return groupColorMap.get(ev.group);
19212
19494
  }
@@ -19277,22 +19559,64 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19277
19559
  }
19278
19560
  function fadeReset(g) {
19279
19561
  g.selectAll(
19280
- ".tl-event, .tl-legend-item, .tl-lane-header, .tl-marker"
19562
+ ".tl-event, .tl-legend-item, .tl-lane-header, .tl-marker, .tl-tag-legend-entry"
19281
19563
  ).attr("opacity", 1);
19282
19564
  g.selectAll(".tl-era").attr("opacity", 1);
19283
19565
  }
19566
+ function fadeToTagValue(g, tagKey, tagValue) {
19567
+ const attrName = `data-tag-${tagKey}`;
19568
+ g.selectAll(".tl-event").each(function() {
19569
+ const el = d3Selection12.select(this);
19570
+ const val = el.attr(attrName);
19571
+ el.attr("opacity", val === tagValue ? 1 : FADE_OPACITY);
19572
+ });
19573
+ g.selectAll(".tl-legend-item, .tl-lane-header").attr(
19574
+ "opacity",
19575
+ FADE_OPACITY
19576
+ );
19577
+ g.selectAll(".tl-marker").attr("opacity", FADE_OPACITY);
19578
+ g.selectAll(".tl-tag-legend-entry").each(function() {
19579
+ const el = d3Selection12.select(this);
19580
+ const entryValue = el.attr("data-legend-entry");
19581
+ if (entryValue === "__group__") return;
19582
+ const entryGroup = el.attr("data-tag-group");
19583
+ el.attr("opacity", entryGroup === tagKey && entryValue === tagValue ? 1 : FADE_OPACITY);
19584
+ });
19585
+ }
19586
+ function setTagAttrs(evG, ev) {
19587
+ for (const [key, value] of Object.entries(ev.metadata)) {
19588
+ evG.attr(`data-tag-${key}`, value.toLowerCase());
19589
+ }
19590
+ }
19591
+ const tagLegendReserve = parsed.timelineTagGroups.length > 0 ? 36 : 0;
19284
19592
  if (isVertical) {
19285
- if (timelineSort === "group" && timelineGroups.length > 0) {
19286
- const groupNames = timelineGroups.map((gr) => gr.name);
19287
- const ungroupedEvents = timelineEvents.filter(
19288
- (ev) => ev.group === null || !groupNames.includes(ev.group)
19289
- );
19290
- const laneNames = ungroupedEvents.length > 0 ? [...groupNames, "(Other)"] : groupNames;
19593
+ const useGroupedVertical = tagLanes != null || timelineSort === "group" && timelineGroups.length > 0;
19594
+ if (useGroupedVertical) {
19595
+ let laneNames;
19596
+ let laneEventsByName;
19597
+ if (tagLanes) {
19598
+ laneNames = tagLanes.map((l) => l.name);
19599
+ laneEventsByName = new Map(tagLanes.map((l) => [l.name, l.events]));
19600
+ } else {
19601
+ const groupNames = timelineGroups.map((gr) => gr.name);
19602
+ const ungroupedEvents = timelineEvents.filter(
19603
+ (ev) => ev.group === null || !groupNames.includes(ev.group)
19604
+ );
19605
+ laneNames = ungroupedEvents.length > 0 ? [...groupNames, "(Other)"] : groupNames;
19606
+ laneEventsByName = new Map(
19607
+ laneNames.map((name) => [
19608
+ name,
19609
+ timelineEvents.filter(
19610
+ (ev) => name === "(Other)" ? ev.group === null || !groupNames.includes(ev.group) : ev.group === name
19611
+ )
19612
+ ])
19613
+ );
19614
+ }
19291
19615
  const laneCount = laneNames.length;
19292
19616
  const scaleMargin = timelineScale ? 40 : 0;
19293
19617
  const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
19294
19618
  const margin = {
19295
- top: 104 + markerMargin,
19619
+ top: 104 + markerMargin + tagLegendReserve,
19296
19620
  right: 40 + scaleMargin,
19297
19621
  bottom: 40,
19298
19622
  left: 60 + scaleMargin
@@ -19342,6 +19666,13 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19342
19666
  formatDateLabel(latestEndDateStr)
19343
19667
  );
19344
19668
  }
19669
+ if (timelineSwimlanes || tagLanes) {
19670
+ laneNames.forEach((laneName, laneIdx) => {
19671
+ const laneX = laneIdx * laneWidth;
19672
+ const fillColor = laneIdx % 2 === 0 ? textColor : "transparent";
19673
+ 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);
19674
+ });
19675
+ }
19345
19676
  laneNames.forEach((laneName, laneIdx) => {
19346
19677
  const laneX = laneIdx * laneWidth;
19347
19678
  const laneColor = groupColorMap.get(laneName) ?? textColor;
@@ -19349,9 +19680,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19349
19680
  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));
19350
19681
  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);
19351
19682
  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");
19352
- const laneEvents = timelineEvents.filter(
19353
- (ev) => laneName === "(Other)" ? ev.group === null || !groupNames.includes(ev.group) : ev.group === laneName
19354
- );
19683
+ const laneEvents = laneEventsByName.get(laneName) ?? [];
19355
19684
  for (const ev of laneEvents) {
19356
19685
  const y = yScale(parseTimelineDate(ev.date));
19357
19686
  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(
@@ -19368,10 +19697,12 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19368
19697
  }).on("click", () => {
19369
19698
  if (onClickItem && ev.lineNumber) onClickItem(ev.lineNumber);
19370
19699
  });
19700
+ setTagAttrs(evG, ev);
19701
+ const evColor = eventColor(ev);
19371
19702
  if (ev.endDate) {
19372
19703
  const y2 = yScale(parseTimelineDate(ev.endDate));
19373
19704
  const rectH = Math.max(y2 - y, 4);
19374
- let fill2 = laneColor;
19705
+ let fill2 = evColor;
19375
19706
  if (ev.uncertain) {
19376
19707
  const gradientId = `uncertain-vg-${ev.lineNumber}`;
19377
19708
  const defs = svg.select("defs").node() || svg.append("defs").node();
@@ -19385,7 +19716,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19385
19716
  evG.append("rect").attr("x", laneCenter - 6).attr("y", y).attr("width", 12).attr("height", rectH).attr("rx", 4).attr("fill", fill2);
19386
19717
  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);
19387
19718
  } else {
19388
- evG.append("circle").attr("cx", laneCenter).attr("cy", y).attr("r", 4).attr("fill", laneColor).attr("stroke", bgColor).attr("stroke-width", 1.5);
19719
+ evG.append("circle").attr("cx", laneCenter).attr("cy", y).attr("r", 4).attr("fill", evColor).attr("stroke", bgColor).attr("stroke-width", 1.5);
19389
19720
  evG.append("text").attr("x", laneCenter + 10).attr("y", y).attr("dy", "0.35em").attr("fill", textColor).attr("font-size", "10px").text(ev.label);
19390
19721
  }
19391
19722
  }
@@ -19394,7 +19725,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19394
19725
  const scaleMargin = timelineScale ? 40 : 0;
19395
19726
  const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
19396
19727
  const margin = {
19397
- top: 104 + markerMargin,
19728
+ top: 104 + markerMargin + tagLegendReserve,
19398
19729
  right: 200,
19399
19730
  bottom: 40,
19400
19731
  left: 60 + scaleMargin
@@ -19474,6 +19805,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19474
19805
  }).on("click", () => {
19475
19806
  if (onClickItem && ev.lineNumber) onClickItem(ev.lineNumber);
19476
19807
  });
19808
+ setTagAttrs(evG, ev);
19477
19809
  if (ev.endDate) {
19478
19810
  const y2 = yScale(parseTimelineDate(ev.endDate));
19479
19811
  const rectH = Math.max(y2 - y, 4);
@@ -19507,18 +19839,24 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19507
19839
  }
19508
19840
  const BAR_H = 22;
19509
19841
  const GROUP_GAP = 12;
19510
- if (timelineSort === "group" && timelineGroups.length > 0) {
19511
- const groupNames = timelineGroups.map((gr) => gr.name);
19512
- const ungroupedEvents = timelineEvents.filter(
19513
- (ev) => ev.group === null || !groupNames.includes(ev.group)
19514
- );
19515
- const laneNames = ungroupedEvents.length > 0 ? [...groupNames, "(Other)"] : groupNames;
19516
- const lanes = laneNames.map((name) => ({
19517
- name,
19518
- events: timelineEvents.filter(
19519
- (ev) => name === "(Other)" ? ev.group === null || !groupNames.includes(ev.group) : ev.group === name
19520
- )
19521
- }));
19842
+ const useGroupedHorizontal = tagLanes != null || timelineSort === "group" && timelineGroups.length > 0;
19843
+ if (useGroupedHorizontal) {
19844
+ let lanes;
19845
+ if (tagLanes) {
19846
+ lanes = tagLanes;
19847
+ } else {
19848
+ const groupNames = timelineGroups.map((gr) => gr.name);
19849
+ const ungroupedEvents = timelineEvents.filter(
19850
+ (ev) => ev.group === null || !groupNames.includes(ev.group)
19851
+ );
19852
+ const laneNames = ungroupedEvents.length > 0 ? [...groupNames, "(Other)"] : groupNames;
19853
+ lanes = laneNames.map((name) => ({
19854
+ name,
19855
+ events: timelineEvents.filter(
19856
+ (ev) => name === "(Other)" ? ev.group === null || !groupNames.includes(ev.group) : ev.group === name
19857
+ )
19858
+ }));
19859
+ }
19522
19860
  const totalEventRows = lanes.reduce((s, l) => s + l.events.length, 0);
19523
19861
  const scaleMargin = timelineScale ? 24 : 0;
19524
19862
  const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
@@ -19526,7 +19864,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19526
19864
  const dynamicLeftMargin = Math.max(120, maxGroupNameLen * 7 + 30);
19527
19865
  const baseTopMargin = title ? 50 : 20;
19528
19866
  const margin = {
19529
- top: baseTopMargin + (timelineScale ? 40 : 0) + markerMargin,
19867
+ top: baseTopMargin + (timelineScale ? 40 : 0) + markerMargin + tagLegendReserve,
19530
19868
  right: 40,
19531
19869
  bottom: 40 + scaleMargin,
19532
19870
  left: dynamicLeftMargin
@@ -19578,7 +19916,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19578
19916
  );
19579
19917
  }
19580
19918
  let curY = markerMargin;
19581
- if (timelineSwimlanes) {
19919
+ if (timelineSwimlanes || tagLanes) {
19582
19920
  let swimY = markerMargin;
19583
19921
  lanes.forEach((lane, idx) => {
19584
19922
  const laneSpan = lane.events.length * rowH;
@@ -19629,12 +19967,14 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19629
19967
  }).on("click", () => {
19630
19968
  if (onClickItem && ev.lineNumber) onClickItem(ev.lineNumber);
19631
19969
  });
19970
+ setTagAttrs(evG, ev);
19971
+ const evColor = eventColor(ev);
19632
19972
  if (ev.endDate) {
19633
19973
  const x2 = xScale(parseTimelineDate(ev.endDate));
19634
19974
  const rectW = Math.max(x2 - x, 4);
19635
19975
  const estLabelWidth = ev.label.length * 7 + 16;
19636
19976
  const labelFitsInside = rectW >= estLabelWidth;
19637
- let fill2 = laneColor;
19977
+ let fill2 = evColor;
19638
19978
  if (ev.uncertain) {
19639
19979
  const gradientId = `uncertain-${ev.lineNumber}`;
19640
19980
  const defs = svg.select("defs").node() || svg.append("defs").node();
@@ -19642,7 +19982,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19642
19982
  { offset: "0%", opacity: 1 },
19643
19983
  { offset: "80%", opacity: 1 },
19644
19984
  { offset: "100%", opacity: 0 }
19645
- ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", laneColor).attr("stop-opacity", (d) => d.opacity);
19985
+ ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", evColor).attr("stop-opacity", (d) => d.opacity);
19646
19986
  fill2 = `url(#${gradientId})`;
19647
19987
  }
19648
19988
  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);
@@ -19659,7 +19999,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19659
19999
  const wouldFlipLeft = x > innerWidth * 0.6;
19660
20000
  const labelFitsLeft = x - 10 - estLabelWidth > 0;
19661
20001
  const flipLeft = wouldFlipLeft && labelFitsLeft;
19662
- evG.append("circle").attr("cx", x).attr("cy", y).attr("r", 5).attr("fill", laneColor).attr("stroke", bgColor).attr("stroke-width", 1.5);
20002
+ evG.append("circle").attr("cx", x).attr("cy", y).attr("r", 5).attr("fill", evColor).attr("stroke", bgColor).attr("stroke-width", 1.5);
19663
20003
  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);
19664
20004
  }
19665
20005
  });
@@ -19670,7 +20010,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19670
20010
  const scaleMargin = timelineScale ? 24 : 0;
19671
20011
  const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
19672
20012
  const margin = {
19673
- top: 104 + (timelineScale ? 40 : 0) + markerMargin,
20013
+ top: 104 + (timelineScale ? 40 : 0) + markerMargin + tagLegendReserve,
19674
20014
  right: 40,
19675
20015
  bottom: 40 + scaleMargin,
19676
20016
  left: 60
@@ -19766,6 +20106,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19766
20106
  }).on("click", () => {
19767
20107
  if (onClickItem && ev.lineNumber) onClickItem(ev.lineNumber);
19768
20108
  });
20109
+ setTagAttrs(evG, ev);
19769
20110
  if (ev.endDate) {
19770
20111
  const x2 = xScale(parseTimelineDate(ev.endDate));
19771
20112
  const rectW = Math.max(x2 - x, 4);
@@ -19801,6 +20142,163 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19801
20142
  }
19802
20143
  });
19803
20144
  }
20145
+ if (parsed.timelineTagGroups.length > 0) {
20146
+ const LG_HEIGHT = 28;
20147
+ const LG_PILL_PAD = 16;
20148
+ const LG_PILL_FONT_SIZE = 11;
20149
+ const LG_PILL_FONT_W = LG_PILL_FONT_SIZE * 0.6;
20150
+ const LG_CAPSULE_PAD = 4;
20151
+ const LG_DOT_R = 4;
20152
+ const LG_ENTRY_FONT_SIZE = 10;
20153
+ const LG_ENTRY_FONT_W = LG_ENTRY_FONT_SIZE * 0.6;
20154
+ const LG_ENTRY_DOT_GAP = 4;
20155
+ const LG_ENTRY_TRAIL = 8;
20156
+ const LG_GROUP_GAP = 12;
20157
+ const LG_ICON_W = 20;
20158
+ const mainSvg = d3Selection12.select(container).select("svg");
20159
+ const mainG = mainSvg.select("g");
20160
+ if (!mainSvg.empty() && !mainG.empty()) {
20161
+ let drawSwimlaneIcon2 = function(parent, x, y, isSwimActive) {
20162
+ const iconG = parent.append("g").attr("class", "tl-swimlane-icon").attr("transform", `translate(${x}, ${y})`).style("cursor", "pointer");
20163
+ const barColor = isSwimActive ? palette.primary : palette.textMuted;
20164
+ const barOpacity = isSwimActive ? 1 : 0.35;
20165
+ const bars = [
20166
+ { y: 0, w: 8 },
20167
+ { y: 4, w: 12 },
20168
+ { y: 8, w: 6 }
20169
+ ];
20170
+ for (const bar of bars) {
20171
+ 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);
20172
+ }
20173
+ return iconG;
20174
+ }, relayout2 = function() {
20175
+ renderTimeline(
20176
+ container,
20177
+ parsed,
20178
+ palette,
20179
+ isDark,
20180
+ onClickItem,
20181
+ exportDims,
20182
+ currentActiveGroup,
20183
+ currentSwimlaneGroup,
20184
+ onTagStateChange
20185
+ );
20186
+ }, drawLegend2 = function() {
20187
+ mainSvg.selectAll(".tl-tag-legend-group").remove();
20188
+ const totalW = legendGroups.reduce((s, lg) => {
20189
+ const isActive = currentActiveGroup != null && lg.group.name.toLowerCase() === currentActiveGroup.toLowerCase();
20190
+ return s + (isActive ? lg.expandedWidth : lg.minifiedWidth);
20191
+ }, 0) + (legendGroups.length - 1) * LG_GROUP_GAP;
20192
+ let cx = (width - totalW) / 2;
20193
+ for (const lg of legendGroups) {
20194
+ const groupKey = lg.group.name.toLowerCase();
20195
+ const isActive = currentActiveGroup != null && currentActiveGroup.toLowerCase() === groupKey;
20196
+ const isSwimActive = currentSwimlaneGroup != null && currentSwimlaneGroup.toLowerCase() === groupKey;
20197
+ const pillLabel = lg.group.name;
20198
+ const pillWidth = pillLabel.length * LG_PILL_FONT_W + LG_PILL_PAD;
20199
+ 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", () => {
20200
+ currentActiveGroup = currentActiveGroup === groupKey ? null : groupKey;
20201
+ drawLegend2();
20202
+ recolorEvents2();
20203
+ onTagStateChange?.(currentActiveGroup, currentSwimlaneGroup);
20204
+ });
20205
+ if (isActive) {
20206
+ gEl.append("rect").attr("width", lg.expandedWidth).attr("height", LG_HEIGHT).attr("rx", LG_HEIGHT / 2).attr("fill", groupBg);
20207
+ }
20208
+ const pillXOff = isActive ? LG_CAPSULE_PAD : 0;
20209
+ const pillYOff = isActive ? LG_CAPSULE_PAD : 0;
20210
+ const pillH = LG_HEIGHT - (isActive ? LG_CAPSULE_PAD * 2 : 0);
20211
+ 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);
20212
+ if (isActive) {
20213
+ 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);
20214
+ }
20215
+ 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);
20216
+ if (isActive) {
20217
+ const iconX = pillXOff + pillWidth + 5;
20218
+ const iconY = (LG_HEIGHT - 10) / 2;
20219
+ const iconEl = drawSwimlaneIcon2(gEl, iconX, iconY, isSwimActive);
20220
+ iconEl.attr("data-swimlane-toggle", groupKey).on("click", (event) => {
20221
+ event.stopPropagation();
20222
+ currentSwimlaneGroup = currentSwimlaneGroup === groupKey ? null : groupKey;
20223
+ onTagStateChange?.(currentActiveGroup, currentSwimlaneGroup);
20224
+ relayout2();
20225
+ });
20226
+ let entryX = pillXOff + pillWidth + LG_ICON_W + 4;
20227
+ for (const entry of lg.group.entries) {
20228
+ const tagKey = lg.group.name.toLowerCase();
20229
+ const tagVal = entry.value.toLowerCase();
20230
+ 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) => {
20231
+ event.stopPropagation();
20232
+ fadeToTagValue(mainG, tagKey, tagVal);
20233
+ mainSvg.selectAll(".tl-tag-legend-entry").each(function() {
20234
+ const el = d3Selection12.select(this);
20235
+ const ev = el.attr("data-legend-entry");
20236
+ if (ev === "__group__") return;
20237
+ const eg = el.attr("data-tag-group");
20238
+ el.attr("opacity", eg === tagKey && ev === tagVal ? 1 : FADE_OPACITY);
20239
+ });
20240
+ }).on("mouseleave", (event) => {
20241
+ event.stopPropagation();
20242
+ fadeReset(mainG);
20243
+ mainSvg.selectAll(".tl-tag-legend-entry").attr("opacity", 1);
20244
+ }).on("click", (event) => {
20245
+ event.stopPropagation();
20246
+ });
20247
+ entryG.append("circle").attr("cx", entryX + LG_DOT_R).attr("cy", LG_HEIGHT / 2).attr("r", LG_DOT_R).attr("fill", entry.color);
20248
+ const textX = entryX + LG_DOT_R * 2 + LG_ENTRY_DOT_GAP;
20249
+ 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);
20250
+ entryX = textX + entry.value.length * LG_ENTRY_FONT_W + LG_ENTRY_TRAIL;
20251
+ }
20252
+ }
20253
+ cx += (isActive ? lg.expandedWidth : lg.minifiedWidth) + LG_GROUP_GAP;
20254
+ }
20255
+ }, recolorEvents2 = function() {
20256
+ const colorTG = currentActiveGroup ?? swimlaneTagGroup ?? null;
20257
+ mainG.selectAll(".tl-event").each(function() {
20258
+ const el = d3Selection12.select(this);
20259
+ const lineNum = el.attr("data-line-number");
20260
+ const ev = lineNum ? eventByLine.get(lineNum) : void 0;
20261
+ if (!ev) return;
20262
+ let color;
20263
+ if (colorTG) {
20264
+ const tagColor = resolveTagColor(
20265
+ ev.metadata,
20266
+ parsed.timelineTagGroups,
20267
+ colorTG
20268
+ );
20269
+ color = tagColor ?? (ev.group && groupColorMap.has(ev.group) ? groupColorMap.get(ev.group) : textColor);
20270
+ } else {
20271
+ color = ev.group && groupColorMap.has(ev.group) ? groupColorMap.get(ev.group) : textColor;
20272
+ }
20273
+ el.selectAll("rect").attr("fill", color);
20274
+ el.selectAll("circle:not(.tl-event-point-outline)").attr("fill", color);
20275
+ });
20276
+ };
20277
+ var drawSwimlaneIcon = drawSwimlaneIcon2, relayout = relayout2, drawLegend = drawLegend2, recolorEvents = recolorEvents2;
20278
+ const legendY = title ? 50 : 10;
20279
+ const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
20280
+ const legendGroups = parsed.timelineTagGroups.map((g) => {
20281
+ const pillW = g.name.length * LG_PILL_FONT_W + LG_PILL_PAD;
20282
+ let entryX = LG_CAPSULE_PAD + pillW + LG_ICON_W + 4;
20283
+ for (const entry of g.entries) {
20284
+ const textX = entryX + LG_DOT_R * 2 + LG_ENTRY_DOT_GAP;
20285
+ entryX = textX + entry.value.length * LG_ENTRY_FONT_W + LG_ENTRY_TRAIL;
20286
+ }
20287
+ return {
20288
+ group: g,
20289
+ minifiedWidth: pillW,
20290
+ expandedWidth: entryX + LG_CAPSULE_PAD
20291
+ };
20292
+ });
20293
+ let currentActiveGroup = activeTagGroup ?? null;
20294
+ let currentSwimlaneGroup = swimlaneTagGroup ?? null;
20295
+ const eventByLine = /* @__PURE__ */ new Map();
20296
+ for (const ev of timelineEvents) {
20297
+ eventByLine.set(String(ev.lineNumber), ev);
20298
+ }
20299
+ drawLegend2();
20300
+ }
20301
+ }
19804
20302
  }
19805
20303
  function getRotateFn(mode) {
19806
20304
  if (mode === "mixed") return () => Math.random() > 0.5 ? 0 : 90;
@@ -20768,7 +21266,16 @@ async function renderD3ForExport(content, theme, palette, orgExportState, option
20768
21266
  } else if (parsed.type === "arc") {
20769
21267
  renderArcDiagram(container, parsed, effectivePalette, isDark, void 0, dims);
20770
21268
  } else if (parsed.type === "timeline") {
20771
- renderTimeline(container, parsed, effectivePalette, isDark, void 0, dims);
21269
+ renderTimeline(
21270
+ container,
21271
+ parsed,
21272
+ effectivePalette,
21273
+ isDark,
21274
+ void 0,
21275
+ dims,
21276
+ orgExportState?.activeTagGroup,
21277
+ orgExportState?.swimlaneTagGroup
21278
+ );
20772
21279
  } else if (parsed.type === "venn") {
20773
21280
  renderVenn(container, parsed, effectivePalette, isDark, void 0, dims);
20774
21281
  } else if (parsed.type === "quadrant") {
@@ -20786,8 +21293,10 @@ var init_d3 = __esm({
20786
21293
  init_branding();
20787
21294
  init_colors();
20788
21295
  init_palettes();
21296
+ init_color_utils();
20789
21297
  init_diagnostics();
20790
21298
  init_parsing();
21299
+ init_tag_groups();
20791
21300
  DEFAULT_CLOUD_OPTIONS = {
20792
21301
  rotate: "none",
20793
21302
  max: 0,
@@ -21649,6 +22158,9 @@ function encodeDiagramUrl(dsl, options) {
21649
22158
  if (options?.viewState?.collapsedGroups?.length) {
21650
22159
  hash += `&cg=${encodeURIComponent(options.viewState.collapsedGroups.join(","))}`;
21651
22160
  }
22161
+ if (options?.viewState?.swimlaneTagGroup) {
22162
+ hash += `&swim=${encodeURIComponent(options.viewState.swimlaneTagGroup)}`;
22163
+ }
21652
22164
  return { url: `${baseUrl}?${hash}#${hash}` };
21653
22165
  }
21654
22166
  function decodeDiagramUrl(hash) {
@@ -21672,6 +22184,9 @@ function decodeDiagramUrl(hash) {
21672
22184
  if (key === "cg" && val) {
21673
22185
  viewState.collapsedGroups = val.split(",").filter(Boolean);
21674
22186
  }
22187
+ if (key === "swim" && val) {
22188
+ viewState.swimlaneTagGroup = val;
22189
+ }
21675
22190
  }
21676
22191
  if (payload.startsWith("dgmo=")) {
21677
22192
  payload = payload.slice(5);