@diagrammo/dgmo 0.86.0 → 0.88.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/advanced.d.ts +6 -18
  2. package/dist/advanced.js +40 -37
  3. package/dist/block.js +6 -17
  4. package/dist/{chunk-FAV4RDVT.js → chunk-2A4DQAXG.js} +1 -1
  5. package/dist/{chunk-JIPUBFAM.js → chunk-5KGM44TG.js} +2 -2
  6. package/dist/{chunk-VEE777MD.js → chunk-5LD7SHXO.js} +3 -3
  7. package/dist/{chunk-QJ2JOLFN.js → chunk-6I5UN4FD.js} +2 -2
  8. package/dist/{chunk-ETBXIKK6.js → chunk-ABP5BUD6.js} +9 -430
  9. package/dist/{chunk-PKXKWHDW.js → chunk-AUS7V54P.js} +1 -1
  10. package/dist/{chunk-BF7JAFJI.js → chunk-B7CU2HW5.js} +2 -2
  11. package/dist/{chunk-X2VEJ5NT.js → chunk-BMUWF77V.js} +2 -2
  12. package/dist/{chunk-BP4BT5TH.js → chunk-CLQIXD2C.js} +1 -1
  13. package/dist/{chunk-QJFT5MAH.js → chunk-DSG6GBUQ.js} +2 -2
  14. package/dist/{chunk-EWSESGC2.js → chunk-EJS3K44P.js} +117 -78
  15. package/dist/{chunk-5JO443MB.js → chunk-HAH5X345.js} +1 -1
  16. package/dist/{chunk-HBCAY3KR.js → chunk-HTNLLT34.js} +1 -1
  17. package/dist/{chunk-HUMQIRJK.js → chunk-HTPZDZKQ.js} +2 -2
  18. package/dist/{chunk-VPVWG2GH.js → chunk-J7Q4VPBJ.js} +1 -1
  19. package/dist/{chunk-QPE646M2.js → chunk-JLETTWUJ.js} +30 -10
  20. package/dist/{chunk-3CQS3CIO.js → chunk-LL6D3MES.js} +1 -1
  21. package/dist/{chunk-ERYHC4KJ.js → chunk-LSQGJN5H.js} +1 -1
  22. package/dist/{chunk-AGO3PFGS.js → chunk-MVCPO6LI.js} +15 -10
  23. package/dist/{chunk-E6S2K6O2.js → chunk-N7XRBWXA.js} +2 -2
  24. package/dist/{chunk-WMIKC5XO.js → chunk-NPWVOFB3.js} +1 -1
  25. package/dist/{chunk-MYHPRQHR.js → chunk-O6MMQQLF.js} +3 -3
  26. package/dist/{chunk-GYORDNOM.js → chunk-P5V63L35.js} +1 -1
  27. package/dist/{chunk-BHZE36F7.js → chunk-PUVTFUNH.js} +1 -1
  28. package/dist/{chunk-TW6WMZFW.js → chunk-QT3KDDXK.js} +156 -7
  29. package/dist/{chunk-ZGHV4QLK.js → chunk-RJNZHFNG.js} +1 -1
  30. package/dist/{chunk-FWNJDD4U.js → chunk-S7W75MIM.js} +3 -20
  31. package/dist/{chunk-4YA5XW43.js → chunk-VW52W5F6.js} +981 -107
  32. package/dist/chunk-WXPBP4IT.js +438 -0
  33. package/dist/{chunk-C6FZZFYD.js → chunk-WZ23JJ7U.js} +1 -1
  34. package/dist/{chunk-LIYWJ6DV.js → chunk-XIFK423U.js} +108 -13
  35. package/dist/{chunk-4ELXLATB.js → chunk-XUDGZJTJ.js} +2 -2
  36. package/dist/{chunk-H6AFNCN3.js → chunk-XVSJYAK7.js} +1 -1
  37. package/dist/{chunk-LHBMQJC3.js → chunk-XWYR5T52.js} +2 -2
  38. package/dist/{chunk-QNKHJ5CK.js → chunk-YK4N6TBN.js} +4 -2
  39. package/dist/{chunk-XFMQSFS4.js → chunk-YMG7JDEY.js} +16 -6
  40. package/dist/chunk-Z6DXWULM.js +23 -0
  41. package/dist/{chunk-PBEH52A2.js → chunk-ZNNVI5SR.js} +1 -1
  42. package/dist/{flowchart-renderer-H72L7BEC.js → flowchart-renderer-THC6Y7EH.js} +7 -6
  43. package/dist/index.d.ts +68 -1
  44. package/dist/index.js +76 -7
  45. package/dist/{layout-QIPCNUSB.js → layout-BOVTLAOT.js} +7 -6
  46. package/dist/{layout-6JARW64V.js → layout-EHTYSMV4.js} +6 -5
  47. package/dist/{layout-YELNBVPC.js → layout-P6DTD4RT.js} +6 -5
  48. package/dist/{layout-T6TAB5OI.js → layout-RANVNAKI.js} +3 -3
  49. package/dist/{layout-FMKSENGL.js → layout-XUW444O5.js} +5 -4
  50. package/dist/{layout-search-AW5VBDCV.js → layout-search-VKSBUCDM.js} +57 -28
  51. package/dist/{renderer-AI2MD52D.js → renderer-222OTVQV.js} +3 -2
  52. package/dist/{renderer-UUWR7NGF.js → renderer-2KVQGGTT.js} +4 -3
  53. package/dist/{renderer-E5EKGIX2.js → renderer-2OOPQ4CH.js} +4 -3
  54. package/dist/{renderer-CZJ4AMYV.js → renderer-3FF4QVGW.js} +3 -2
  55. package/dist/{renderer-R6VJDZFO.js → renderer-4BHOELCE.js} +4 -3
  56. package/dist/{renderer-425QUIYO.js → renderer-4BIMRO4X.js} +4 -3
  57. package/dist/{renderer-5JVVJBAS.js → renderer-A4LMOBZL.js} +8 -7
  58. package/dist/{renderer-3AOOLHJU.js → renderer-A6ACVK4X.js} +3 -2
  59. package/dist/{renderer-ML2CLU25.js → renderer-DCFQDBGJ.js} +4 -3
  60. package/dist/{renderer-RH4U3UVM.js → renderer-LODEVN3V.js} +3 -2
  61. package/dist/{renderer-E7WF75GY.js → renderer-NHPKNNBD.js} +5 -4
  62. package/dist/{renderer-JEEGPLMM.js → renderer-R34GJL7U.js} +8 -7
  63. package/dist/{renderer-2BWRW23Z.js → renderer-RENDISO5.js} +1 -1
  64. package/dist/{renderer-NX4YKWCJ.js → renderer-RNCYODNF.js} +1 -1
  65. package/dist/{renderer-VKAJ52UO.js → renderer-SDUSBCJI.js} +3 -2
  66. package/dist/{renderer-RLSR7N6I.js → renderer-TPOZOAXR.js} +5 -4
  67. package/dist/{renderer-RUGMKHX5.js → renderer-WH2HWE5Z.js} +3 -2
  68. package/dist/{renderer-V6LLASGN.js → renderer-X5A5VIRD.js} +5 -4
  69. package/dist/{renderer-ADX4DWRJ.js → renderer-XUPLAO4B.js} +4 -2
  70. package/dist/{renderer-EWJS2RAZ.js → renderer-YDAVCRTP.js} +3 -2
  71. package/dist/{state-renderer-DDVJ43HR.js → state-renderer-5IQ4OJUI.js} +7 -6
  72. package/gallery/fixtures/journey-map.dgmo +28 -0
  73. package/gallery/fixtures/mindmap.dgmo +31 -0
  74. package/gallery/fixtures/pert.dgmo +27 -0
  75. package/gallery/fixtures/wireframe.dgmo +30 -0
  76. package/package.json +2 -2
@@ -4,7 +4,10 @@ import {
4
4
  NODE_HEIGHT,
5
5
  NODE_WIDTH,
6
6
  measureEdgeLabel
7
- } from "./chunk-TW6WMZFW.js";
7
+ } from "./chunk-QT3KDDXK.js";
8
+ import {
9
+ measureText
10
+ } from "./chunk-KAO7JL4I.js";
8
11
 
9
12
  // src/boxes-and-lines/edge-clip.ts
10
13
  function rectBorderPoint(rect, toward) {
@@ -48,6 +51,8 @@ var DUMMY_THICK = 10;
48
51
  var MARGIN = 40;
49
52
  var GROUP_PAD = 16;
50
53
  var GROUP_LABEL_ZONE = 32;
54
+ var GROUP_TITLE_FONT_SIZE = 14;
55
+ var TITLE_CLEAR = 6;
51
56
  var GROUP_GAP = 24;
52
57
  var TIER_GAP = GROUP_LABEL_ZONE + 2 * GROUP_PAD;
53
58
  var W_REAL_REAL = 1;
@@ -556,6 +561,70 @@ function groupedTierCandidates(parsed, sizes, opts) {
556
561
  });
557
562
  const chainById = /* @__PURE__ */ new Map();
558
563
  for (const ch of chains) chainById.set(ch.edgeIdx, ch.chain);
564
+ const nodeBox = new Map(
565
+ layoutNodes.map((n) => [
566
+ n.label,
567
+ {
568
+ l: n.x - n.width / 2,
569
+ r: n.x + n.width / 2,
570
+ t: n.y - n.height / 2,
571
+ b: n.y + n.height / 2
572
+ }
573
+ ])
574
+ );
575
+ const depthOf = (label) => {
576
+ let d = 0;
577
+ let cur = label;
578
+ const guard = /* @__PURE__ */ new Set();
579
+ while (cur && !guard.has(cur)) {
580
+ guard.add(cur);
581
+ cur = groupByLabel.get(cur)?.parentGroup;
582
+ if (cur) d++;
583
+ }
584
+ return d;
585
+ };
586
+ const childGroups = /* @__PURE__ */ new Map();
587
+ for (const g of parsed.groups)
588
+ if (g.parentGroup)
589
+ (childGroups.get(g.parentGroup) ?? childGroups.set(g.parentGroup, []).get(g.parentGroup)).push(g.label);
590
+ const rectByGroup = /* @__PURE__ */ new Map();
591
+ const ordered = [...parsed.groups].sort(
592
+ (a, b) => depthOf(b.label) - depthOf(a.label)
593
+ );
594
+ const layoutGroups = [];
595
+ for (const grp of ordered) {
596
+ let l = Infinity, r = -Infinity, t = Infinity, bm = -Infinity;
597
+ const acc2 = (box) => {
598
+ l = Math.min(l, box.l);
599
+ r = Math.max(r, box.r);
600
+ t = Math.min(t, box.t);
601
+ bm = Math.max(bm, box.b);
602
+ };
603
+ for (const c of grp.children) {
604
+ const nb = nodeBox.get(c);
605
+ if (nb) acc2(nb);
606
+ }
607
+ for (const sub of childGroups.get(grp.label) ?? []) {
608
+ const sr = rectByGroup.get(sub);
609
+ if (sr) acc2(sr);
610
+ }
611
+ if (!Number.isFinite(l)) continue;
612
+ const x0 = l - GROUP_PAD;
613
+ const x1 = r + GROUP_PAD;
614
+ const y0 = t - GROUP_LABEL_ZONE;
615
+ const y1 = bm + GROUP_PAD;
616
+ rectByGroup.set(grp.label, { l: x0, r: x1, t: y0, b: y1 });
617
+ layoutGroups.push({
618
+ label: grp.label,
619
+ lineNumber: grp.lineNumber,
620
+ x: (x0 + x1) / 2,
621
+ y: (y0 + y1) / 2,
622
+ width: x1 - x0,
623
+ height: y1 - y0,
624
+ collapsed: false,
625
+ childCount: grp.children.length
626
+ });
627
+ }
559
628
  let minCross = Infinity, maxCross = -Infinity;
560
629
  for (const n of node.values()) {
561
630
  if (n.dummy) continue;
@@ -574,35 +643,69 @@ function groupedTierCandidates(parsed, sizes, opts) {
574
643
  side: sideOf(b),
575
644
  span: Math.abs(node.get(b.src).rank - node.get(b.tgt).rank)
576
645
  })).sort((a, b) => a.side - b.side || a.span - b.span);
577
- const sideCounts = /* @__PURE__ */ new Map();
578
- for (const b of backSorted)
579
- sideCounts.set(b.side, (sideCounts.get(b.side) ?? 0) + 1);
580
646
  const laneCounter = /* @__PURE__ */ new Map();
581
647
  const backPoints = /* @__PURE__ */ new Map();
648
+ const faceKey = (id, side) => `${id}\0${side}`;
649
+ const faceCount = /* @__PURE__ */ new Map();
650
+ for (const b of backSorted)
651
+ for (const id of [b.src, b.tgt])
652
+ faceCount.set(
653
+ faceKey(id, b.side),
654
+ (faceCount.get(faceKey(id, b.side)) ?? 0) + 1
655
+ );
656
+ const faceSeen = /* @__PURE__ */ new Map();
657
+ const portSlot = (id, side) => {
658
+ const key = faceKey(id, side);
659
+ const j = faceSeen.get(key) ?? 0;
660
+ faceSeen.set(key, j + 1);
661
+ return 2 * (j + 1) / (faceCount.get(key) + 1) - 1;
662
+ };
663
+ const titleStrips = layoutGroups.map((g) => {
664
+ const tw = measureText(g.label, GROUP_TITLE_FONT_SIZE, { bold: true });
665
+ const x0 = g.x - tw / 2 - TITLE_CLEAR;
666
+ const x1 = g.x + tw / 2 + TITLE_CLEAR;
667
+ const y0 = g.y - g.height / 2;
668
+ const y1 = y0 + GROUP_LABEL_ZONE;
669
+ return isTB ? { c0: x0, c1: x1, r0: y0, r1: y1 } : { c0: y0, c1: y1, r0: x0, r1: x1 };
670
+ });
671
+ const stubHitsTitle = (r, ca, cb) => titleStrips.some(
672
+ (t) => r > t.r0 && r < t.r1 && Math.min(ca, cb) < t.c1 && Math.max(ca, cb) > t.c0
673
+ );
674
+ const gapMid = (rank, dir) => {
675
+ const nb = rank + dir;
676
+ if (nb < 0 || nb > maxRank) return void 0;
677
+ const a = bandCenter[rank] + dir * bandDepth[rank] / 2;
678
+ const b = bandCenter[nb] - dir * bandDepth[nb] / 2;
679
+ return (a + b) / 2;
680
+ };
582
681
  for (const b of backSorted) {
583
682
  const s = b.side;
584
683
  const k = laneCounter.get(s) ?? 0;
585
684
  laneCounter.set(s, k + 1);
586
- const K = sideCounts.get(s);
587
685
  const src = node.get(b.src);
588
686
  const tgt = node.get(b.tgt);
589
687
  const laneC = s > 0 ? maxCross + GAP + k * LANE : minCross - GAP - k * LANE;
590
688
  const srcCtr = bandCenter[src.rank];
591
689
  const tgtCtr = bandCenter[tgt.rank];
592
- const frac = K > 1 ? (k + 1) / (K + 1) : 0;
593
- const srcR = srcCtr - frac * faceLim(src.depth);
594
- const tgtR = tgtCtr + frac * faceLim(tgt.depth);
690
+ const dir = srcCtr >= tgtCtr ? 1 : -1;
691
+ const srcR = srcCtr + dir * portSlot(b.src, s) * faceLim(src.depth);
692
+ const tgtR = tgtCtr - dir * portSlot(b.tgt, s) * faceLim(tgt.depth);
595
693
  const m = (c, rr) => isTB ? { x: c, y: rr } : { x: rr, y: c };
596
694
  const off = 10;
597
695
  const srcEdgeC = src.cross + s * (src.thick / 2 + off);
598
696
  const tgtEdgeC = tgt.cross + s * (tgt.thick / 2 + off);
697
+ const end = (n, ctr, r, edgeC, toward) => {
698
+ if (stubHitsTitle(r, n.cross, laneC)) {
699
+ const g = gapMid(n.rank, toward);
700
+ if (g !== void 0 && !stubHitsTitle(g, n.cross, laneC))
701
+ return [m(n.cross, ctr), m(n.cross, g), m(laneC, g)];
702
+ }
703
+ return [m(n.cross, ctr), m(edgeC, r), m(laneC, r)];
704
+ };
705
+ const toTgt = tgt.rank >= src.rank ? 1 : -1;
599
706
  backPoints.set(b.edgeIdx, [
600
- m(src.cross, srcCtr),
601
- m(srcEdgeC, srcR),
602
- m(laneC, srcR),
603
- m(laneC, tgtR),
604
- m(tgtEdgeC, tgtR),
605
- m(tgt.cross, tgtCtr)
707
+ ...end(src, srcCtr, srcR, srcEdgeC, toTgt),
708
+ ...end(tgt, tgtCtr, tgtR, tgtEdgeC, -toTgt).reverse()
606
709
  ]);
607
710
  }
608
711
  const flatPoints = /* @__PURE__ */ new Map();
@@ -659,70 +762,6 @@ function groupedTierCandidates(parsed, sizes, opts) {
659
762
  metadata: e.metadata
660
763
  });
661
764
  }
662
- const nodeBox = new Map(
663
- layoutNodes.map((n) => [
664
- n.label,
665
- {
666
- l: n.x - n.width / 2,
667
- r: n.x + n.width / 2,
668
- t: n.y - n.height / 2,
669
- b: n.y + n.height / 2
670
- }
671
- ])
672
- );
673
- const depthOf = (label) => {
674
- let d = 0;
675
- let cur = label;
676
- const guard = /* @__PURE__ */ new Set();
677
- while (cur && !guard.has(cur)) {
678
- guard.add(cur);
679
- cur = groupByLabel.get(cur)?.parentGroup;
680
- if (cur) d++;
681
- }
682
- return d;
683
- };
684
- const childGroups = /* @__PURE__ */ new Map();
685
- for (const g of parsed.groups)
686
- if (g.parentGroup)
687
- (childGroups.get(g.parentGroup) ?? childGroups.set(g.parentGroup, []).get(g.parentGroup)).push(g.label);
688
- const rectByGroup = /* @__PURE__ */ new Map();
689
- const ordered = [...parsed.groups].sort(
690
- (a, b) => depthOf(b.label) - depthOf(a.label)
691
- );
692
- const layoutGroups = [];
693
- for (const grp of ordered) {
694
- let l = Infinity, r = -Infinity, t = Infinity, bm = -Infinity;
695
- const acc2 = (box) => {
696
- l = Math.min(l, box.l);
697
- r = Math.max(r, box.r);
698
- t = Math.min(t, box.t);
699
- bm = Math.max(bm, box.b);
700
- };
701
- for (const c of grp.children) {
702
- const nb = nodeBox.get(c);
703
- if (nb) acc2(nb);
704
- }
705
- for (const sub of childGroups.get(grp.label) ?? []) {
706
- const sr = rectByGroup.get(sub);
707
- if (sr) acc2(sr);
708
- }
709
- if (!Number.isFinite(l)) continue;
710
- const x0 = l - GROUP_PAD;
711
- const x1 = r + GROUP_PAD;
712
- const y0 = t - GROUP_LABEL_ZONE;
713
- const y1 = bm + GROUP_PAD;
714
- rectByGroup.set(grp.label, { l: x0, r: x1, t: y0, b: y1 });
715
- layoutGroups.push({
716
- label: grp.label,
717
- lineNumber: grp.lineNumber,
718
- x: (x0 + x1) / 2,
719
- y: (y0 + y1) / 2,
720
- width: x1 - x0,
721
- height: y1 - y0,
722
- collapsed: false,
723
- childCount: grp.children.length
724
- });
725
- }
726
765
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
727
766
  const ext = (x0, y0, x1, y1) => {
728
767
  minX = Math.min(minX, x0);
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-5DH3KYFH.js";
9
9
  import {
10
10
  renderInlineText
11
- } from "./chunk-FWNJDD4U.js";
11
+ } from "./chunk-S7W75MIM.js";
12
12
  import {
13
13
  mix
14
14
  } from "./chunk-7HWY4QAJ.js";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-6EK55ART.js";
4
4
  import {
5
5
  noteBoxSize
6
- } from "./chunk-ZGHV4QLK.js";
6
+ } from "./chunk-RJNZHFNG.js";
7
7
 
8
8
  // src/utils/notes/build.ts
9
9
  function buildPlacedNotes(anchors, noteByNode, direction, collapsedNotes) {
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  layoutERDiagram
3
- } from "./chunk-FAV4RDVT.js";
3
+ } from "./chunk-2A4DQAXG.js";
4
4
  import {
5
5
  NOTE_BADGE_RADIUS,
6
6
  noteConnectorPoints,
7
7
  renderNoteBadge,
8
8
  renderNoteBox,
9
9
  renderNoteConnector
10
- } from "./chunk-5JO443MB.js";
10
+ } from "./chunk-HAH5X345.js";
11
11
  import {
12
12
  ScaleContext
13
13
  } from "./chunk-24YPR5EH.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  renderInlineText
3
- } from "./chunk-FWNJDD4U.js";
3
+ } from "./chunk-S7W75MIM.js";
4
4
  import {
5
5
  ScaleContext
6
6
  } from "./chunk-24YPR5EH.js";
@@ -54,9 +54,26 @@ import {
54
54
  themeBaseBg
55
55
  } from "./chunk-7HWY4QAJ.js";
56
56
 
57
+ // src/embed/escape.ts
58
+ var HTML_ENTITIES = {
59
+ "&": "&amp;",
60
+ "<": "&lt;",
61
+ ">": "&gt;",
62
+ '"': "&quot;",
63
+ "'": "&#39;"
64
+ };
65
+ function escapeHtml(s) {
66
+ return s.replace(/[&<>"']/g, (ch) => HTML_ENTITIES[ch] ?? ch);
67
+ }
68
+ function escapeAttr(s) {
69
+ return s.replace(/[&<>"']/g, (ch) => HTML_ENTITIES[ch] ?? ch);
70
+ }
71
+
57
72
  // src/timeline/renderer.ts
58
73
  import * as d3Scale from "d3-scale";
59
74
  import * as d3Selection from "d3-selection";
75
+ var MIN_TOP_SCALE_BAND = 26;
76
+ var MIN_BOTTOM_BAND = 30;
60
77
  function renderEras(g, eras, scale, isVertical, innerWidth, innerHeight, onEnter, onLeave, hasScale = false, _tooltip = null, palette, reservedLabelY, fillMode, outlineBg) {
61
78
  const eraColors = getEraColors(
62
79
  palette ?? getPalette(DEFAULT_PALETTE_ID).light
@@ -281,7 +298,7 @@ function hideEventDatesOnScale(g) {
281
298
  }
282
299
  function buildEventTooltipHtml(ev) {
283
300
  const datePart = ev.endDate ? `${formatDateLabel(ev.date)} \u2192 ${formatDateLabel(ev.endDate)}` : formatDateLabel(ev.date);
284
- return `<strong>${ev.label}</strong><br>${datePart}`;
301
+ return `<strong>${escapeHtml(ev.label)}</strong><br>${datePart}`;
285
302
  }
286
303
  function renderTimelineGroupLegend(g, groups, groupColorMap, textColor, palette, isDark, legendY, onHover, onLeave) {
287
304
  const PILL_H = 22;
@@ -784,11 +801,12 @@ function renderTimelineHorizontalTimeSort(container, parsed, palette, isDark, se
784
801
  const MARKER_ROW_H = ctx.structural(22);
785
802
  const eraReserve = timelineEras.length > 0 ? ERA_ROW_H : 0;
786
803
  const markerReserve = timelineMarkers.length > 0 ? MARKER_ROW_H : 0;
787
- const topScaleH = timelineScale ? ctx.structural(40) : 0;
804
+ const topScaleH = timelineScale ? Math.max(ctx.structural(40), MIN_TOP_SCALE_BAND) : 0;
805
+ const hasGroupLegend = timelineGroups.length > 0 && !parsed.noLegend;
788
806
  const margin = {
789
- top: ctx.aesthetic(104) + topScaleH + eraReserve + markerReserve + tagLegendReserve,
807
+ top: 50 + (hasGroupLegend ? 54 : ctx.aesthetic(54)) + topScaleH + eraReserve + markerReserve + tagLegendReserve,
790
808
  right: ctx.aesthetic(40),
791
- bottom: ctx.aesthetic(40) + scaleMargin,
809
+ bottom: Math.max(ctx.aesthetic(40), MIN_BOTTOM_BAND) + scaleMargin,
792
810
  left: ctx.aesthetic(60)
793
811
  };
794
812
  const markerLabelY = markerReserve ? -(topScaleH + MARKER_ROW_H / 2) : 0;
@@ -859,8 +877,8 @@ function renderTimelineHorizontalTimeSort(container, parsed, palette, isDark, se
859
877
  formatBoundaryLabel(latestEndDateStr, earliestStartDateStr)
860
878
  );
861
879
  }
862
- if (timelineGroups.length > 0 && !parsed.noLegend) {
863
- const legendY = timelineScale ? -ctx.aesthetic(75) : -ctx.aesthetic(55);
880
+ if (hasGroupLegend) {
881
+ const legendY = timelineScale ? -(topScaleH + 35) : -55;
864
882
  renderTimelineGroupLegend(
865
883
  g,
866
884
  timelineGroups,
@@ -1018,7 +1036,7 @@ function renderTimelineHorizontalGrouped(container, parsed, palette, isDark, set
1018
1036
  const MARKER_ROW_H = ctx.structural(22);
1019
1037
  const eraReserve = timelineEras.length > 0 ? ERA_ROW_H : 0;
1020
1038
  const markerReserve = timelineMarkers.length > 0 ? MARKER_ROW_H : 0;
1021
- const topScaleH = timelineScale ? ctx.structural(40) : 0;
1039
+ const topScaleH = timelineScale ? Math.max(ctx.structural(40), MIN_TOP_SCALE_BAND) : 0;
1022
1040
  const maxGroupNameLen = Math.max(...lanes.map((l) => l.name.length)) + 2;
1023
1041
  const maxEventLabelLen = Math.max(
1024
1042
  0,
@@ -1029,11 +1047,11 @@ function renderTimelineHorizontalGrouped(container, parsed, palette, isDark, set
1029
1047
  ctx.aesthetic(140),
1030
1048
  maxLeftLabelLen * sCharW + ctx.aesthetic(30)
1031
1049
  );
1032
- const baseTopMargin = title ? ctx.aesthetic(50) : ctx.aesthetic(20);
1050
+ const baseTopMargin = title ? 50 : ctx.aesthetic(20);
1033
1051
  const margin = {
1034
1052
  top: baseTopMargin + topScaleH + eraReserve + markerReserve + tagLegendReserve,
1035
1053
  right: ctx.aesthetic(40),
1036
- bottom: ctx.aesthetic(40) + scaleMargin,
1054
+ bottom: Math.max(ctx.aesthetic(40), MIN_BOTTOM_BAND) + scaleMargin,
1037
1055
  left: dynamicLeftMargin
1038
1056
  };
1039
1057
  const markerLabelY = markerReserve ? -(topScaleH + MARKER_ROW_H / 2) : 0;
@@ -1121,7 +1139,7 @@ function renderTimelineHorizontalGrouped(container, parsed, palette, isDark, set
1121
1139
  g.append("rect").attr("class", "tl-group-header-bg").attr("data-group", lane.name).attr("x", bandX).attr("y", bandY).attr("width", bandW).attr("height", bandH).attr("rx", sBandRx).attr("fill", shapeFill(palette, laneColor, isDark, { mode: fillMode })).style("pointer-events", "none");
1122
1140
  g.append("rect").attr("class", "tl-group-header-accent").attr("data-group", lane.name).attr("x", bandX).attr("y", bandY).attr("width", sBandAccentW).attr("height", bandH).attr("fill", laneColor).attr("clip-path", `url(#${clipId})`).style("pointer-events", "none");
1123
1141
  const headerG = g.append("g").attr("class", "tl-group-header").attr("data-group", lane.name).style("cursor", "pointer").on("mouseenter", () => fadeToGroup(g, lane.name)).on("mouseleave", () => fadeReset(g)).on("click", () => toggleGroup(lane.name));
1124
- headerG.append("text").attr("x", -margin.left + ctx.aesthetic(10)).attr("y", curY + sBarH / 2).attr("dy", "0.35em").attr("text-anchor", "start").attr("fill", textColor).attr("font-size", sLaneHeaderFont).attr("font-weight", "bold").text(`${toggleIcon} ${lane.name}`);
1142
+ headerG.append("text").attr("x", bandX + 5).attr("y", curY + sBarH / 2).attr("dy", "0.35em").attr("text-anchor", "start").attr("fill", textColor).attr("font-size", sLaneHeaderFont).attr("font-weight", "bold").text(`${toggleIcon} ${lane.name}`);
1125
1143
  if (lane.events.length > 0) {
1126
1144
  const evDates = lane.events.map((ev) => parseTimelineDate(ev.date));
1127
1145
  const evEndDates = lane.events.filter((ev) => ev.endDate).map((ev) => parseTimelineDate(ev.endDate));
@@ -1672,6 +1690,8 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
1672
1690
  }
1673
1691
 
1674
1692
  export {
1693
+ escapeHtml,
1694
+ escapeAttr,
1675
1695
  formatDateLabel,
1676
1696
  renderTimeline
1677
1697
  };
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-UIUXIDBX.js";
5
5
  import {
6
6
  renderInlineText
7
- } from "./chunk-FWNJDD4U.js";
7
+ } from "./chunk-S7W75MIM.js";
8
8
  import {
9
9
  ScaleContext
10
10
  } from "./chunk-24YPR5EH.js";
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-XBJJP2HN.js";
10
10
  import {
11
11
  renderInlineText
12
- } from "./chunk-FWNJDD4U.js";
12
+ } from "./chunk-S7W75MIM.js";
13
13
  import {
14
14
  ScaleContext
15
15
  } from "./chunk-24YPR5EH.js";
@@ -72,10 +72,10 @@ import * as d3Scale from "d3-scale";
72
72
  import * as d3Selection from "d3-selection";
73
73
  var BAR_H = 22;
74
74
  var ROW_GAP = 6;
75
- var GROUP_GAP = 14;
76
75
  var MILESTONE_SIZE = 10;
77
76
  var MIN_LEFT_MARGIN = 120;
78
77
  var BOTTOM_MARGIN = 40;
78
+ var BOTTOM_AXIS_LABEL_RESERVE = 30;
79
79
  var RIGHT_MARGIN = 20;
80
80
  var LABEL_PAD = 8;
81
81
  var LABEL_GAP = 5;
@@ -213,9 +213,11 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
213
213
  const ctx = exportDims ? ScaleContext.identity() : ScaleContext.from(containerWidth, idealWidth);
214
214
  const sBarH = ctx.structural(BAR_H);
215
215
  const sRowGap = ctx.structural(ROW_GAP);
216
- const sGroupGap = ctx.structural(GROUP_GAP);
217
216
  const sMilestoneSize = ctx.structural(MILESTONE_SIZE);
218
- const sBottomMargin = ctx.aesthetic(BOTTOM_MARGIN);
217
+ const sBottomMargin = Math.max(
218
+ ctx.aesthetic(BOTTOM_MARGIN),
219
+ BOTTOM_AXIS_LABEL_RESERVE
220
+ );
219
221
  const sRightMargin = ctx.aesthetic(RIGHT_MARGIN);
220
222
  const sLabelPad = ctx.structural(LABEL_PAD);
221
223
  const sLabelGap = ctx.structural(LABEL_GAP);
@@ -225,10 +227,10 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
225
227
  const sTitleY = ctx.aesthetic(TITLE_Y);
226
228
  const sHeaderRowH = ctx.structural(HEADER_ROW_H);
227
229
  const sContentTopPad = ctx.aesthetic(CONTENT_TOP_PAD);
228
- const sTopDateLabelReserve = ctx.structural(topDateLabelReserve);
230
+ const sTopDateLabelReserve = topDateLabelReserve;
229
231
  const sSprintLabelReserve = ctx.structural(sprintLabelReserve);
230
232
  const sTitleHeight = ctx.aesthetic(titleHeight);
231
- const sTagLegendReserve = ctx.structural(tagLegendReserve);
233
+ const sTagLegendReserve = tagLegendReserve;
232
234
  const inlineRequested = resolved.options.legendInline === true && resolved.tagGroups.length > 0;
233
235
  const legendExtent = inlineRequested ? getLegendExtent(
234
236
  {
@@ -267,15 +269,17 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
267
269
  const sTodayLabelY = sTodayReserve ? -(sTopDateLabelReserve + sHeaderRowH / 2) : 0;
268
270
  const sMarkerLabelY = sMarkerReserve ? -(sTopDateLabelReserve + sTodayReserve + sHeaderRowH / 2) : 0;
269
271
  const sEraLabelY = sEraReserve ? -(sTopDateLabelReserve + sTodayReserve + sMarkerReserve + sHeaderRowH / 2) : 0;
270
- const sContentH = isTagMode ? totalRows * (sBarH + sRowGap) : totalRows * (sBarH + sRowGap) + sGroupGap * resolved.groups.length;
272
+ const sContentH = isTagMode ? totalRows * (sBarH + sRowGap) : totalRows * (sBarH + sRowGap);
271
273
  const sInnerHeight = sContentTopPad + sContentH;
272
274
  const sOuterHeight = sMarginTop + sInnerHeight + sBottomMargin;
273
275
  const sprintRightPad = resolved.sprints.length > 0 ? 50 : 0;
274
- const innerWidth = containerWidth - leftMargin - sRightMargin - sprintRightPad;
275
- const svg = d3Selection.select(container).append("svg").attr("viewBox", `0 0 ${containerWidth} ${sOuterHeight}`).attr("width", exportDims ? containerWidth : "100%").attr("preserveAspectRatio", "xMidYMin meet").attr("font-family", FONT_FAMILY).style("overflow", "visible");
276
+ const containerHeight = options?.fitToContainer ? container.clientHeight : 0;
277
+ const layoutWidth = !exportDims && containerHeight > 0 && sOuterHeight > containerHeight ? containerWidth * sOuterHeight / containerHeight : containerWidth;
278
+ const innerWidth = layoutWidth - leftMargin - sRightMargin - sprintRightPad;
279
+ const svg = d3Selection.select(container).append("svg").attr("viewBox", `0 0 ${layoutWidth} ${sOuterHeight}`).attr("width", exportDims ? containerWidth : "100%").attr("preserveAspectRatio", "xMidYMin meet").attr("font-family", FONT_FAMILY).style("overflow", "visible");
276
280
  const g = svg.append("g").attr("transform", `translate(${leftMargin}, ${sMarginTop})`);
277
281
  if (showTitle) {
278
- svg.append("text").attr("class", "chart-title").attr("x", header.titleX).attr("y", sTitleY).attr("text-anchor", header.titleAnchor).attr("font-size", sTitleFontSize).attr("font-weight", TITLE_FONT_WEIGHT).attr("fill", palette.text).text(title);
282
+ svg.append("text").attr("class", "chart-title").attr("x", header.inline ? header.titleX : layoutWidth / 2).attr("y", sTitleY).attr("text-anchor", header.titleAnchor).attr("font-size", sTitleFontSize).attr("font-weight", TITLE_FONT_WEIGHT).attr("fill", palette.text).text(title);
279
283
  }
280
284
  function drawLegend() {
281
285
  svg.selectAll(".gantt-tag-legend-container").remove();
@@ -362,7 +366,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
362
366
  drawLegend();
363
367
  const startTime = dateToFractionalYear(resolved.startDate);
364
368
  const endTime = dateToFractionalYear(resolved.endDate);
365
- const domainPad = Math.max((endTime - startTime) * 0.02, 0.01);
369
+ const span = endTime - startTime;
370
+ const domainPad = span > 0 ? span * 0.02 : 1 / 365;
366
371
  const domainMin = startTime - domainPad;
367
372
  const domainMax = endTime + domainPad;
368
373
  const xScale = d3Scale.scaleLinear().domain([domainMin, domainMax]).range([0, innerWidth]);
@@ -6,13 +6,13 @@ import {
6
6
  } from "./chunk-DOXBS7F5.js";
7
7
  import {
8
8
  wrapNoteBody
9
- } from "./chunk-ZGHV4QLK.js";
9
+ } from "./chunk-RJNZHFNG.js";
10
10
  import {
11
11
  NOTE_BULLET_INDENT
12
12
  } from "./chunk-5DH3KYFH.js";
13
13
  import {
14
14
  renderInlineText
15
- } from "./chunk-FWNJDD4U.js";
15
+ } from "./chunk-S7W75MIM.js";
16
16
  import {
17
17
  ScaleContext
18
18
  } from "./chunk-24YPR5EH.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  stripInlineMarkdown
3
- } from "./chunk-FWNJDD4U.js";
3
+ } from "./chunk-S7W75MIM.js";
4
4
 
5
5
  // src/utils/wrapped-desc.ts
6
6
  var BULLET_PREFIX = "\u2022 ";
@@ -4,18 +4,18 @@ import {
4
4
  renderNoteBadge,
5
5
  renderNoteBox,
6
6
  renderNoteConnector
7
- } from "./chunk-5JO443MB.js";
7
+ } from "./chunk-HAH5X345.js";
8
8
  import {
9
9
  appendArrowheadMarkers,
10
10
  appendReverseArrowheadMarkers
11
11
  } from "./chunk-XBJJP2HN.js";
12
12
  import {
13
13
  wrapDescriptionLines
14
- } from "./chunk-WMIKC5XO.js";
14
+ } from "./chunk-NPWVOFB3.js";
15
15
  import {
16
16
  renderInlineText,
17
17
  stripInlineMarkdown
18
- } from "./chunk-FWNJDD4U.js";
18
+ } from "./chunk-S7W75MIM.js";
19
19
  import {
20
20
  ScaleContext
21
21
  } from "./chunk-24YPR5EH.js";
@@ -1579,7 +1579,7 @@ function renderScurveBlock(svg, data, args) {
1579
1579
  }));
1580
1580
  const curve = d3Shape.line().x((d) => d.x).y((d) => d.y).curve(d3Shape.curveMonotoneX)(points);
1581
1581
  if (curve) {
1582
- const clipId = `pert-scurve-clip-${Math.random().toString(36).slice(2, 10)}`;
1582
+ const clipId = `pert-scurve-clip-${[plotLeft, plotTop, plotW, plotH].map((v) => Math.round(v)).join("-")}`;
1583
1583
  block.append("clipPath").attr("id", clipId).append("rect").attr("x", plotLeft).attr("y", plotTop).attr("width", plotW).attr("height", plotH);
1584
1584
  block.append("path").attr("class", "pert-scurve-line").attr("d", curve).attr("fill", "none").attr("stroke", palette.colors.red).attr("stroke-width", 2).attr("clip-path", `url(#${clipId})`);
1585
1585
  }
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-R2K5L4UA.js";
4
4
  import {
5
5
  renderInlineText
6
- } from "./chunk-FWNJDD4U.js";
6
+ } from "./chunk-S7W75MIM.js";
7
7
  import {
8
8
  ScaleContext
9
9
  } from "./chunk-24YPR5EH.js";