@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
@@ -0,0 +1,438 @@
1
+ import {
2
+ renderInlineText
3
+ } from "./chunk-S7W75MIM.js";
4
+ import {
5
+ BOX_H,
6
+ BOX_W,
7
+ COMMENT_W,
8
+ PAIR_GAP,
9
+ layoutBracket,
10
+ wrapText
11
+ } from "./chunk-ABP5BUD6.js";
12
+ import {
13
+ layoutInlineHeader
14
+ } from "./chunk-3DOQOXXI.js";
15
+ import {
16
+ TITLE_FONT_SIZE,
17
+ TITLE_FONT_WEIGHT,
18
+ TITLE_Y
19
+ } from "./chunk-VLC6WNRD.js";
20
+ import {
21
+ renderIntegratedLegend
22
+ } from "./chunk-LHL6HDUL.js";
23
+ import {
24
+ getLegendExtent,
25
+ getMaxLegendReservedHeight
26
+ } from "./chunk-FM3WAVB6.js";
27
+ import {
28
+ FONT_FAMILY
29
+ } from "./chunk-ICLB27PZ.js";
30
+ import {
31
+ LEGEND_GROUP_GAP
32
+ } from "./chunk-RH2PROYG.js";
33
+ import {
34
+ measureText
35
+ } from "./chunk-KAO7JL4I.js";
36
+ import {
37
+ resolveTagColor,
38
+ tagAttrKey
39
+ } from "./chunk-L5AFONQU.js";
40
+ import {
41
+ contrastText,
42
+ getSeriesColors,
43
+ mix,
44
+ shapeFill,
45
+ themeBaseBg
46
+ } from "./chunk-7HWY4QAJ.js";
47
+ import {
48
+ resolveColor
49
+ } from "./chunk-2M4IGSCY.js";
50
+
51
+ // src/bracket/renderer.ts
52
+ import * as d3Selection from "d3-selection";
53
+ var PADDING = 16;
54
+ var TITLE_BAND = 40;
55
+ var HALF_SPAN = (BOX_H + PAIR_GAP) / 2;
56
+ var COMMENT_LH = 15;
57
+ var LEGEND_POSITION = {
58
+ placement: "top-center",
59
+ titleRelation: "below-title"
60
+ };
61
+ function clip(s, maxW, fs, bold) {
62
+ if (measureText(s, fs, { bold }) <= maxW) return s;
63
+ let lo = 0;
64
+ let hi = s.length;
65
+ while (lo < hi) {
66
+ const mid = Math.ceil((lo + hi) / 2);
67
+ if (measureText(s.slice(0, mid) + "\u2026", fs, { bold }) <= maxW) lo = mid;
68
+ else hi = mid - 1;
69
+ }
70
+ return lo <= 0 ? "" : s.slice(0, lo) + "\u2026";
71
+ }
72
+ function paintForAccent(palette, isDark, accent, fillMode) {
73
+ const base = themeBaseBg(palette, isDark);
74
+ return {
75
+ accent,
76
+ ink: palette.text,
77
+ muted: mix(palette.text, base, 52),
78
+ // Loser / TBD outlines + connectors are ink-based (not the pale border) so
79
+ // they stay legible on top of a colored, shaded column band.
80
+ hair: mix(palette.text, base, 34),
81
+ wire: mix(palette.text, base, 30),
82
+ track: mix(palette.border, base, 55),
83
+ // §1.9 fill family rides through shapeFill: solid ⇒ raw accent,
84
+ // outline ⇒ theme base bg (accent stays on the stroke), absent ⇒ 25% tint.
85
+ tint: shapeFill(palette, accent, isDark, { mode: fillMode })
86
+ };
87
+ }
88
+ function paintFor(palette, isDark, sideColor, fillMode) {
89
+ const accent = sideColor && resolveColor(sideColor, palette) || sideColor || getSeriesColors(palette)[0];
90
+ return paintForAccent(palette, isDark, accent, fillMode);
91
+ }
92
+ function resolveBracketLegend(parsed, palette) {
93
+ const activeTag = parsed.activeTag;
94
+ const activeGroup = activeTag ? parsed.tagGroups.find((g) => tagAttrKey(g.name) === tagAttrKey(activeTag)) : void 0;
95
+ const legendGroups = activeGroup && !parsed.noLegend ? [
96
+ {
97
+ name: activeGroup.name,
98
+ entries: activeGroup.entries.map((e) => ({
99
+ value: e.value,
100
+ color: resolveColor(e.color, palette) ?? e.color
101
+ }))
102
+ }
103
+ ] : [];
104
+ return { activeGroup, legendGroups };
105
+ }
106
+ function bracketExportDimensions(parsed, palette) {
107
+ const layout = layoutBracket(parsed);
108
+ const width = layout.width + PADDING * 2;
109
+ const titleH = parsed.title ? TITLE_BAND : 0;
110
+ const { legendGroups } = resolveBracketLegend(parsed, palette);
111
+ const legendReserve = legendGroups.length > 0 ? getMaxLegendReservedHeight(
112
+ { groups: legendGroups, position: LEGEND_POSITION, mode: "export" },
113
+ width
114
+ ) + LEGEND_GROUP_GAP : 0;
115
+ const height = titleH + legendReserve + PADDING + layout.height + PADDING;
116
+ return { width, height };
117
+ }
118
+ function renderBracket(container, parsed, palette, isDark, exportDims) {
119
+ d3Selection.select(container).selectAll(":not([data-d3-tooltip])").remove();
120
+ if (parsed.error) return;
121
+ const layout = layoutBracket(parsed);
122
+ const width = exportDims?.width ?? container.clientWidth;
123
+ const height = exportDims?.height ?? container.clientHeight;
124
+ if (width <= 0 || height <= 0) return;
125
+ const showTitle = !!parsed.title;
126
+ const titleH = showTitle ? TITLE_BAND : 0;
127
+ const exportMode = exportDims !== void 0;
128
+ const seedByName = /* @__PURE__ */ new Map();
129
+ for (const s of parsed.seeds)
130
+ if (!seedByName.has(s.name)) seedByName.set(s.name, s.seed);
131
+ const activeTag = parsed.activeTag;
132
+ const activeKey = activeTag ? tagAttrKey(activeTag) : null;
133
+ const tagValueOf = (name) => {
134
+ if (!name || !activeKey) return null;
135
+ const meta = parsed.competitorMeta.get(name);
136
+ return meta?.[activeKey] ?? null;
137
+ };
138
+ const tagColorOf = (name) => {
139
+ if (!name || !activeTag) return null;
140
+ const meta = parsed.competitorMeta.get(name);
141
+ if (!meta || !activeKey || !meta[activeKey]) return null;
142
+ const c = resolveTagColor(meta, [...parsed.tagGroups], activeTag);
143
+ return c ? resolveColor(c, palette) ?? c : null;
144
+ };
145
+ const { activeGroup, legendGroups } = resolveBracketLegend(parsed, palette);
146
+ const legendReserve = legendGroups.length > 0 ? getMaxLegendReservedHeight(
147
+ { groups: legendGroups, position: LEGEND_POSITION, mode: "preview" },
148
+ width
149
+ ) + LEGEND_GROUP_GAP : 0;
150
+ const inlineRequested = parsed.legendInline === true;
151
+ const legendExtent = inlineRequested && legendGroups.length > 0 ? getLegendExtent(
152
+ {
153
+ groups: legendGroups,
154
+ position: {
155
+ placement: "top-center",
156
+ titleRelation: "inline-with-title"
157
+ },
158
+ mode: exportMode ? "export" : "preview"
159
+ },
160
+ { activeGroup: activeGroup?.name ?? null },
161
+ width
162
+ ) : { width: 0, height: 0 };
163
+ const header = layoutInlineHeader({
164
+ requested: inlineRequested,
165
+ title: parsed.title ?? "",
166
+ hasLegend: legendGroups.length > 0,
167
+ legendWidth: legendExtent.width,
168
+ legendHeight: legendExtent.height,
169
+ containerWidth: width,
170
+ titleBandHeight: titleH,
171
+ legendReserve,
172
+ titleBaselineY: TITLE_Y,
173
+ titleFontSize: TITLE_FONT_SIZE
174
+ });
175
+ const svg = d3Selection.select(container).append("svg").attr("width", width).attr("height", height).attr("viewBox", `0 0 ${width} ${height}`).attr("preserveAspectRatio", "xMidYMin meet").attr("xmlns", "http://www.w3.org/2000/svg").attr("class", "dgmo-bracket").attr("role", "img").attr(
176
+ "aria-label",
177
+ `Tournament bracket${parsed.title ? ` \u2014 ${parsed.title}` : ""}${layout.champion ? `, champion ${layout.champion}` : ""}`
178
+ ).style("font-family", FONT_FAMILY);
179
+ if (showTitle) {
180
+ const t = svg.append("text").attr("class", "chart-title").attr("x", header.titleX).attr("y", TITLE_Y).attr("text-anchor", header.titleAnchor).attr("fill", palette.text).attr("font-family", FONT_FAMILY).attr("font-size", TITLE_FONT_SIZE).attr("font-weight", TITLE_FONT_WEIGHT).text(parsed.title);
181
+ if (parsed.titleLineNumber !== null)
182
+ t.attr("data-line-number", parsed.titleLineNumber);
183
+ }
184
+ const areaY = (header.inline ? titleH : titleH + legendReserve) + PADDING;
185
+ const areaX = PADDING;
186
+ const areaW = Math.max(1, width - PADDING * 2);
187
+ const areaH = Math.max(1, height - areaY - PADDING);
188
+ const scale = Math.min(1, areaW / layout.width, areaH / layout.height);
189
+ const offsetX = areaX + (areaW - layout.width * scale) / 2;
190
+ const offsetY = areaY;
191
+ const root = svg.append("g").attr("class", "dgmo-bracket-body").attr("transform", `translate(${offsetX},${offsetY}) scale(${scale})`);
192
+ if (legendGroups.length > 0) {
193
+ const legendG = svg.append("g").attr("class", "dgmo-bracket-legend").attr(
194
+ "transform",
195
+ header.inline ? `translate(${header.legendX}, ${header.legendY})` : `translate(0, ${titleH})`
196
+ );
197
+ renderIntegratedLegend(legendG, {
198
+ groups: legendGroups,
199
+ palette: {
200
+ bg: palette.bg,
201
+ surface: palette.surface,
202
+ text: palette.text,
203
+ textMuted: palette.textMuted,
204
+ primary: palette.primary
205
+ },
206
+ isDark,
207
+ width,
208
+ mode: exportMode ? "export" : "preview",
209
+ position: {
210
+ placement: "top-center",
211
+ titleRelation: header.inline ? "inline-with-title" : "below-title"
212
+ },
213
+ activeGroup: activeGroup?.name ?? null
214
+ });
215
+ }
216
+ const baseAccent = parsed.accentColor ?? resolveColor("blue", palette) ?? getSeriesColors(palette)[0];
217
+ const fillMode = parsed.fillMode;
218
+ const basePaint = paintForAccent(palette, isDark, baseAccent, fillMode);
219
+ const sideColorByLabel = /* @__PURE__ */ new Map();
220
+ for (const s of parsed.sides) sideColorByLabel.set(s.label, s.color);
221
+ const sideBarH = layout.sideLabels.length ? 30 : 0;
222
+ const bandTop = sideBarH + 2;
223
+ const roundY = sideBarH + 24;
224
+ for (const col of layout.columns) {
225
+ if (!col.color) continue;
226
+ const covered = layout.sideLabels.some(
227
+ (s) => col.x >= s.x0 && col.x <= s.x1
228
+ );
229
+ const top = covered ? bandTop : 2;
230
+ const c = resolveColor(col.color, palette) ?? col.color;
231
+ root.append("rect").attr("x", col.x - BOX_W / 2 - 10).attr("y", top).attr("width", BOX_W + 20).attr("height", Math.max(0, layout.height - top)).attr("rx", 10).attr(
232
+ "fill",
233
+ fillMode === "outline" ? themeBaseBg(palette, isDark) : mix(c, palette.bg, isDark ? 12 : 8)
234
+ ).call((sel) => {
235
+ if (fillMode === "outline")
236
+ sel.attr("stroke", mix(c, palette.bg, 35)).attr("stroke-width", 1);
237
+ });
238
+ }
239
+ for (const s of layout.sideLabels) {
240
+ const c = s.color ? resolveColor(s.color, palette) ?? s.color : basePaint.accent;
241
+ root.append("rect").attr("x", s.x0 - 8).attr("y", 2).attr("width", s.x1 - s.x0 + 16).attr("height", 24).attr("rx", 8).attr(
242
+ "fill",
243
+ fillMode === "outline" ? themeBaseBg(palette, isDark) : mix(c, palette.bg, isDark ? 20 : 14)
244
+ ).attr("stroke", fillMode === "outline" ? c : mix(c, palette.bg, 55)).attr("stroke-width", 1);
245
+ root.append("text").attr("x", (s.x0 + s.x1) / 2).attr("y", 18).attr("text-anchor", "middle").attr("font-size", 13).attr("font-weight", "bold").attr("letter-spacing", 0.3).attr("fill", c).text(clip(s.label, s.x1 - s.x0, 13, true));
246
+ }
247
+ for (const col of layout.columns) {
248
+ const c = col.color ? resolveColor(col.color, palette) ?? col.color : basePaint.muted;
249
+ const covered = layout.sideLabels.some(
250
+ (s) => col.x >= s.x0 && col.x <= s.x1
251
+ );
252
+ root.append("text").attr("x", col.x).attr("y", covered ? roundY : 22).attr("text-anchor", "middle").attr("font-size", covered ? 14 : 15).attr("font-weight", "bold").attr("fill", c).attr("letter-spacing", 0.4).text(clip(col.label, BOX_W + 20, covered ? 14 : 15, true));
253
+ }
254
+ for (const m of layout.matches) {
255
+ drawConnector(root, m, m.topFrom, m.y - HALF_SPAN, basePaint);
256
+ drawConnector(root, m, m.botFrom, m.y + HALF_SPAN, basePaint);
257
+ }
258
+ for (const m of layout.matches) {
259
+ const sidePaint = m.side && sideColorByLabel.get(m.side) ? paintFor(palette, isDark, sideColorByLabel.get(m.side), fillMode) : basePaint;
260
+ const topSeed = seedByName.get(m.top ?? "") ?? null;
261
+ const botSeed = seedByName.get(m.bot ?? "") ?? null;
262
+ let homeName = m.home;
263
+ if (!homeName && topSeed !== null && botSeed !== null) {
264
+ homeName = topSeed <= botSeed ? m.top : m.bot;
265
+ }
266
+ const [wSeed, lSeed] = m.winner === "top" ? [topSeed, botSeed] : m.winner === "bot" ? [botSeed, topSeed] : [null, null];
267
+ const isUpset = wSeed !== null && lSeed !== null && wSeed > lSeed && !m.isChampionship;
268
+ const scoreParts = m.score ? m.score.split(/[-–]/).map((p) => p.trim()) : null;
269
+ const winScore = scoreParts?.length === 2 ? scoreParts[0] : "";
270
+ const loseScore = scoreParts?.length === 2 ? scoreParts[1] : "";
271
+ const champWinner = m.isChampionship ? layout.champion : null;
272
+ const mkInfo = (side, name) => {
273
+ const isWinner = m.winner === side;
274
+ const isLoser = m.winner !== null && m.winner !== side;
275
+ return {
276
+ isWinner,
277
+ isLoser,
278
+ pending: m.pending,
279
+ isChampion: champWinner !== null && isWinner,
280
+ score: isWinner ? winScore : isLoser ? loseScore : "",
281
+ seed: seedByName.get(name ?? "") ?? null,
282
+ tagColor: tagColorOf(name),
283
+ tagValue: tagValueOf(name),
284
+ isHome: homeName !== null && homeName === name
285
+ };
286
+ };
287
+ drawBox(
288
+ root,
289
+ m.x,
290
+ m.y - HALF_SPAN,
291
+ m.top,
292
+ mkInfo("top", m.top),
293
+ sidePaint,
294
+ palette,
295
+ isDark,
296
+ m,
297
+ activeKey,
298
+ fillMode
299
+ );
300
+ drawBox(
301
+ root,
302
+ m.x,
303
+ m.y + HALF_SPAN,
304
+ m.bot,
305
+ mkInfo("bot", m.bot),
306
+ sidePaint,
307
+ palette,
308
+ isDark,
309
+ m,
310
+ activeKey,
311
+ fillMode
312
+ );
313
+ if (isUpset) drawUpset(root, m, sidePaint);
314
+ if (m.commentary.length) drawCommentary(root, m, palette);
315
+ }
316
+ }
317
+ function drawConnector(g, m, from, toCy, paint) {
318
+ if (!from) return;
319
+ const parentLeft = m.x - BOX_W / 2;
320
+ const parentRight = m.x + BOX_W / 2;
321
+ const childIsLeft = from.x < m.x;
322
+ const childEdge = childIsLeft ? from.x + BOX_W / 2 : from.x - BOX_W / 2;
323
+ const parentEdge = childIsLeft ? parentLeft : parentRight;
324
+ const midX = (childEdge + parentEdge) / 2;
325
+ const vLo = Math.min(from.yA, from.yB, toCy);
326
+ const vHi = Math.max(from.yA, from.yB, toCy);
327
+ const d = from.yA !== from.yB ? `M ${childEdge} ${from.yA} H ${midX} M ${childEdge} ${from.yB} H ${midX} M ${midX} ${vLo} V ${vHi} M ${midX} ${toCy} H ${parentEdge}` : `M ${childEdge} ${from.y} H ${midX} V ${toCy} H ${parentEdge}`;
328
+ g.append("path").attr("d", d).attr("fill", "none").attr("stroke", paint.wire).attr("stroke-width", 1.5);
329
+ }
330
+ function drawBox(g, cx, cy, name, info, sidePaint, palette, isDark, m, activeKey, fillMode) {
331
+ const x = cx - BOX_W / 2;
332
+ const y = cy - BOX_H / 2;
333
+ const paint = info.tagColor ? paintForAccent(palette, isDark, info.tagColor, fillMode) : sidePaint;
334
+ const cell = g.append("g").attr("class", "dgmo-bracket-box");
335
+ if (m.lineNumber !== null) cell.attr("data-line-number", m.lineNumber);
336
+ if (activeKey && info.tagValue)
337
+ cell.attr(`data-tag-${activeKey}`, info.tagValue.toLowerCase());
338
+ const isTBD = name === null;
339
+ let fill = "none";
340
+ let stroke = paint.hair;
341
+ let sw = 1.25;
342
+ let dash = null;
343
+ let textColor = paint.ink;
344
+ let weight = "bold";
345
+ if (info.isChampion) {
346
+ fill = paint.tint;
347
+ stroke = paint.accent;
348
+ sw = 3;
349
+ weight = "bold";
350
+ } else if (isTBD) {
351
+ fill = paint.track;
352
+ dash = "4 4";
353
+ textColor = paint.muted;
354
+ weight = "normal";
355
+ } else if (info.isWinner) {
356
+ fill = paint.tint;
357
+ stroke = paint.accent;
358
+ sw = 2;
359
+ weight = "bold";
360
+ } else if (info.isLoser) {
361
+ stroke = paint.hair;
362
+ textColor = paint.muted;
363
+ weight = "normal";
364
+ } else if (info.pending) {
365
+ stroke = mix(paint.accent, paint.hair, 50);
366
+ }
367
+ const onSolid = fillMode === "solid" && (info.isChampion || info.isWinner) ? contrastText(
368
+ paint.tint,
369
+ palette.textOnFillLight,
370
+ palette.textOnFillDark
371
+ ) : null;
372
+ if (onSolid) textColor = onSolid;
373
+ const gutterColor = onSolid ?? paint.muted;
374
+ const rect = cell.append("rect").attr("x", x).attr("y", y).attr("width", BOX_W).attr("height", BOX_H).attr("rx", 6).attr("fill", fill).attr("stroke", stroke).attr("stroke-width", sw);
375
+ if (dash) rect.attr("stroke-dasharray", dash);
376
+ let leftPad = 11;
377
+ if (info.seed !== null) {
378
+ cell.append("text").attr("x", x + 9).attr("y", cy + 4).attr("text-anchor", "start").attr("font-size", 10).attr("font-weight", "bold").attr("fill", gutterColor).text(info.seed);
379
+ leftPad = 9 + Math.max(12, measureText(String(info.seed), 10, { bold: true })) + 6;
380
+ }
381
+ let rightPad = 11;
382
+ if (info.score) rightPad += 20;
383
+ if (info.isHome) {
384
+ drawHome(cell, x + BOX_W - rightPad - 6, cy, gutterColor);
385
+ rightPad += 16;
386
+ }
387
+ cell.append("text").attr("x", x + leftPad).attr("y", cy + 4).attr("text-anchor", "start").attr("font-size", 12.5).attr("font-weight", weight).attr("fill", textColor).text(
388
+ clip(
389
+ isTBD ? "TBD" : name,
390
+ BOX_W - leftPad - rightPad,
391
+ 12.5,
392
+ weight === "bold"
393
+ )
394
+ );
395
+ if (info.score) {
396
+ cell.append("text").attr("x", x + BOX_W - 11).attr("y", cy + 4).attr("text-anchor", "end").attr("font-size", 12).attr("font-weight", info.isWinner ? "bold" : "normal").attr("fill", info.isWinner ? textColor : gutterColor).text(info.score);
397
+ }
398
+ }
399
+ function drawHome(cell, hx, cy, color) {
400
+ const s = 5;
401
+ const yTop = cy - s - 1;
402
+ const d = `M ${hx - s} ${cy - 1} L ${hx} ${yTop} L ${hx + s} ${cy - 1} L ${hx + s} ${cy + s} L ${hx - s} ${cy + s} Z`;
403
+ cell.append("path").attr("d", d).attr("fill", "none").attr("stroke", color).attr("stroke-width", 1.2).attr("stroke-linejoin", "round");
404
+ }
405
+ function drawUpset(g, m, paint) {
406
+ g.append("text").attr("x", m.x + BOX_W / 2).attr("y", m.y - HALF_SPAN - BOX_H / 2 - 4).attr("text-anchor", "end").attr("font-size", 9).attr("font-weight", "bold").attr("letter-spacing", 0.6).attr("fill", paint.accent).text("UPSET");
407
+ }
408
+ function drawCommentary(g, m, palette) {
409
+ const x = m.x - BOX_W / 2 + 2;
410
+ let y = m.y + HALF_SPAN + BOX_H / 2 + 13;
411
+ const muted = mix(palette.text, palette.bg, 45);
412
+ const bulletW = measureText("\u2022 ", 11);
413
+ const emit = (line, lx) => {
414
+ const t = g.append("text").attr("x", lx).attr("y", y).attr("text-anchor", "start").attr("font-size", 11).attr("font-style", "italic").attr("fill", muted);
415
+ renderInlineText(t, line, palette, 11);
416
+ y += COMMENT_LH;
417
+ };
418
+ for (const raw of m.commentary) {
419
+ const isBullet = raw.startsWith("\u2022 ");
420
+ if (isBullet) {
421
+ const body = raw.slice(2);
422
+ wrapText(body, COMMENT_W - bulletW, 11).forEach(
423
+ (line, i) => i === 0 ? emit(`\u2022 ${line}`, x) : emit(line, x + bulletW)
424
+ );
425
+ } else {
426
+ for (const line of wrapText(raw, COMMENT_W, 11)) emit(line, x);
427
+ }
428
+ }
429
+ }
430
+ function renderBracketForExport(container, parsed, palette, isDark, exportDims) {
431
+ renderBracket(container, parsed, palette, isDark, exportDims);
432
+ }
433
+
434
+ export {
435
+ bracketExportDimensions,
436
+ renderBracket,
437
+ renderBracketForExport
438
+ };
@@ -11,7 +11,7 @@ import {
11
11
  } from "./chunk-XBJJP2HN.js";
12
12
  import {
13
13
  renderInlineText
14
- } from "./chunk-FWNJDD4U.js";
14
+ } from "./chunk-S7W75MIM.js";
15
15
  import {
16
16
  ARROWHEAD_HEIGHT,
17
17
  ARROWHEAD_WIDTH,
@@ -3,10 +3,10 @@ import {
3
3
  NODE_HEIGHT,
4
4
  NODE_WIDTH,
5
5
  placeEdgeLabels
6
- } from "./chunk-TW6WMZFW.js";
6
+ } from "./chunk-QT3KDDXK.js";
7
7
  import {
8
8
  buildPlacedNotes
9
- } from "./chunk-HBCAY3KR.js";
9
+ } from "./chunk-HTNLLT34.js";
10
10
  import {
11
11
  noteCanvasShift
12
12
  } from "./chunk-6EK55ART.js";
@@ -22,6 +22,9 @@ import {
22
22
  var MARGIN = 40;
23
23
  var MAX_PARALLEL_EDGES = 5;
24
24
  var PARALLEL_SPACING = 22;
25
+ var DETOUR_TOLERANCE = 2 * PARALLEL_SPACING;
26
+ var MIN_PORT_GAP = 16;
27
+ var PORT_CORNER_CLEAR = 6;
25
28
  var DESC_NODE_WIDTH = 140;
26
29
  var DESC_FONT_SIZE = 10;
27
30
  var DESC_LINE_HEIGHT = 1.4;
@@ -104,7 +107,7 @@ function computeNodeSize(node, reserveValueRow) {
104
107
  return { width: w, height: Math.max(NODE_HEIGHT, totalHeight) };
105
108
  }
106
109
  async function layoutBoxesAndLines(parsed, collapseInfo, layoutOptions) {
107
- const { layoutBoxesAndLinesSearch } = await import("./layout-search-AW5VBDCV.js");
110
+ const { layoutBoxesAndLinesSearch } = await import("./layout-search-VKSBUCDM.js");
108
111
  const searchOpts = {
109
112
  ...layoutOptions?.hideDescriptions !== void 0 && {
110
113
  hideDescriptions: layoutOptions.hideDescriptions
@@ -117,13 +120,6 @@ async function layoutBoxesAndLines(parsed, collapseInfo, layoutOptions) {
117
120
  },
118
121
  ...layoutOptions?.onProgress !== void 0 && {
119
122
  onProgress: layoutOptions.onProgress
120
- },
121
- // Spread into the label-reserving relayout below too, via `...searchOpts`:
122
- // that second search has its OWN clock, so a caller that disabled the
123
- // deadline must have it disabled for both passes or the fixture that needs
124
- // two passes is exactly the one still racing.
125
- ...layoutOptions?.budgetMs !== void 0 && {
126
- budgetMs: layoutOptions.budgetMs
127
123
  }
128
124
  };
129
125
  let topConfigs;
@@ -134,7 +130,7 @@ async function layoutBoxesAndLines(parsed, collapseInfo, layoutOptions) {
134
130
  }
135
131
  });
136
132
  const labelOpts = { groups: parsed.groups };
137
- let chosen = applyParallelEdgeOffsets(searched);
133
+ let chosen = applyParallelEdgeOffsets(spreadCrowdedPorts(searched));
138
134
  let placed = placeEdgeLabels(chosen, labelOpts);
139
135
  if (placed.unresolved.length > 0) {
140
136
  const relaid = await layoutBoxesAndLinesSearch(parsed, collapseInfo, {
@@ -145,7 +141,7 @@ async function layoutBoxesAndLines(parsed, collapseInfo, layoutOptions) {
145
141
  // first search surfaced no dagre candidates.
146
142
  ...topConfigs !== void 0 && topConfigs.length > 0 && { configs: topConfigs }
147
143
  });
148
- const relaidChosen = applyParallelEdgeOffsets(relaid);
144
+ const relaidChosen = applyParallelEdgeOffsets(spreadCrowdedPorts(relaid));
149
145
  const relaidPlaced = placeEdgeLabels(relaidChosen, labelOpts);
150
146
  if (relaidPlaced.unresolved.length < placed.unresolved.length) {
151
147
  placed = relaidPlaced;
@@ -242,6 +238,102 @@ function attachNotes(layout, parsed, collapsedNotes) {
242
238
  height: bbMaxY + shiftY + MARGIN
243
239
  };
244
240
  }
241
+ function spreadCrowdedPorts(layout) {
242
+ const boxes = /* @__PURE__ */ new Map();
243
+ for (const n of layout.nodes)
244
+ boxes.set(n.label, { x: n.x, y: n.y, w: n.width, h: n.height });
245
+ for (const g of layout.groups)
246
+ if (g.collapsed)
247
+ boxes.set("__group_" + g.label, {
248
+ x: g.x,
249
+ y: g.y,
250
+ w: g.width,
251
+ h: g.height
252
+ });
253
+ const faces = /* @__PURE__ */ new Map();
254
+ const onSide = (box, p) => {
255
+ const E = 1;
256
+ const l = box.x - box.w / 2;
257
+ const r = box.x + box.w / 2;
258
+ const t = box.y - box.h / 2;
259
+ const b = box.y + box.h / 2;
260
+ if (p.x < l - E || p.x > r + E || p.y < t - E || p.y > b + E) return null;
261
+ if (Math.abs(p.y - t) <= E) return "top";
262
+ if (Math.abs(p.y - b) <= E) return "bottom";
263
+ if (Math.abs(p.x - l) <= E) return "left";
264
+ if (Math.abs(p.x - r) <= E) return "right";
265
+ return null;
266
+ };
267
+ layout.edges.forEach((e, i) => {
268
+ if (e.source === e.target || e.points.length < 2) return;
269
+ for (const last of [false, true]) {
270
+ const id = last ? e.target : e.source;
271
+ const box = boxes.get(id);
272
+ const p = last ? e.points[e.points.length - 1] : e.points[0];
273
+ if (!box) continue;
274
+ const side = onSide(box, p);
275
+ if (!side) continue;
276
+ const horiz = side === "top" || side === "bottom";
277
+ const key = `${id}\0${side}`;
278
+ let f = faces.get(key);
279
+ if (!f) faces.set(key, f = { box, horiz, ends: [] });
280
+ f.ends.push({ edge: i, last, along: horiz ? p.x : p.y });
281
+ }
282
+ });
283
+ const moved = /* @__PURE__ */ new Map();
284
+ for (const { box, horiz, ends } of faces.values()) {
285
+ if (ends.length < 2) continue;
286
+ ends.sort((a, b) => a.along - b.along || a.edge - b.edge);
287
+ let crowded = false;
288
+ for (let k = 1; k < ends.length; k++)
289
+ if (ends[k].along - ends[k - 1].along < MIN_PORT_GAP) crowded = true;
290
+ if (!crowded) continue;
291
+ const half = (horiz ? box.w : box.h) / 2 - PORT_CORNER_CLEAR;
292
+ const ctr = horiz ? box.x : box.y;
293
+ if (half <= 0) continue;
294
+ const gap = Math.min(MIN_PORT_GAP, 2 * half / (ends.length - 1));
295
+ const mean = ends.reduce((acc, e) => acc + e.along, 0) / ends.length;
296
+ const span = gap * (ends.length - 1);
297
+ const first = Math.max(
298
+ ctr - half,
299
+ Math.min(ctr + half - span, mean - span / 2)
300
+ );
301
+ ends.forEach((e, k) => {
302
+ moved.set(`${e.edge}:${e.last}`, { along: first + k * gap, horiz });
303
+ });
304
+ }
305
+ if (moved.size === 0) return layout;
306
+ return {
307
+ ...layout,
308
+ edges: layout.edges.map((e, i) => {
309
+ const a = moved.get(`${i}:false`);
310
+ const b = moved.get(`${i}:true`);
311
+ if (a === void 0 && b === void 0) return e;
312
+ const pts = e.points.map((p) => ({ x: p.x, y: p.y }));
313
+ const shift = (idx, to) => {
314
+ if (to.horiz) pts[idx].x = to.along;
315
+ else pts[idx].y = to.along;
316
+ };
317
+ if (a !== void 0) shift(0, a);
318
+ if (b !== void 0) shift(pts.length - 1, b);
319
+ return { ...e, points: pts };
320
+ })
321
+ };
322
+ }
323
+ function routeDetour(points) {
324
+ const s = points[0];
325
+ const t = points[points.length - 1];
326
+ if (!s || !t) return 0;
327
+ const dx = t.x - s.x;
328
+ const dy = t.y - s.y;
329
+ const len = Math.hypot(dx, dy);
330
+ let worst = 0;
331
+ for (const p of points) {
332
+ const d = len < 1e-6 ? Math.hypot(p.x - s.x, p.y - s.y) : Math.abs((p.x - s.x) * dy - (p.y - s.y) * dx) / len;
333
+ if (d > worst) worst = d;
334
+ }
335
+ return worst;
336
+ }
245
337
  function applyParallelEdgeOffsets(layout) {
246
338
  const groups = /* @__PURE__ */ new Map();
247
339
  layout.edges.forEach((e, i) => {
@@ -262,8 +354,11 @@ function applyParallelEdgeOffsets(layout) {
262
354
  layout.edges.length
263
355
  ).fill(void 0);
264
356
  for (const [key, idxs] of groups) {
265
- const capped = idxs.slice(0, MAX_PARALLEL_EDGES);
357
+ const kept = idxs.slice(0, MAX_PARALLEL_EDGES);
266
358
  for (const drop of idxs.slice(MAX_PARALLEL_EDGES)) count[drop] = 0;
359
+ const capped = kept.filter(
360
+ (idx) => routeDetour(layout.edges[idx].points) <= DETOUR_TOLERANCE
361
+ );
267
362
  if (capped.length < 2) continue;
268
363
  const sep = key.indexOf("\0");
269
364
  const ca = centre.get(key.slice(0, sep));
@@ -3,14 +3,14 @@ import {
3
3
  } from "./chunk-26EVL454.js";
4
4
  import {
5
5
  layoutGraph
6
- } from "./chunk-PBEH52A2.js";
6
+ } from "./chunk-ZNNVI5SR.js";
7
7
  import {
8
8
  NOTE_BADGE_RADIUS,
9
9
  noteConnectorPoints,
10
10
  renderNoteBadge,
11
11
  renderNoteBox,
12
12
  renderNoteConnector
13
- } from "./chunk-5JO443MB.js";
13
+ } from "./chunk-HAH5X345.js";
14
14
  import {
15
15
  appendArrowheadMarkers
16
16
  } from "./chunk-XBJJP2HN.js";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-LNO3ASX5.js";
4
4
  import {
5
5
  parseInlineMarkdown
6
- } from "./chunk-FWNJDD4U.js";
6
+ } from "./chunk-S7W75MIM.js";
7
7
  import {
8
8
  TITLE_FONT_WEIGHT
9
9
  } from "./chunk-VLC6WNRD.js";
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  layoutClassDiagram
3
- } from "./chunk-BP4BT5TH.js";
3
+ } from "./chunk-CLQIXD2C.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,7 +1,9 @@
1
1
  import {
2
- parseInlineMarkdown,
2
+ parseInlineMarkdown
3
+ } from "./chunk-S7W75MIM.js";
4
+ import {
3
5
  safeHref
4
- } from "./chunk-FWNJDD4U.js";
6
+ } from "./chunk-Z6DXWULM.js";
5
7
  import {
6
8
  ScaleContext
7
9
  } from "./chunk-24YPR5EH.js";