zombie-mermaid 2.2.2 → 2.2.4

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 (38) hide show
  1. package/README.md +4 -4
  2. package/dist/ascii.cjs +8 -8
  3. package/dist/ascii.cjs.map +1 -1
  4. package/dist/ascii.js +1137 -1070
  5. package/dist/ascii.js.map +1 -1
  6. package/dist/cli.js +3380 -3295
  7. package/dist/index.cjs +59 -52
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.js +1780 -1712
  10. package/dist/index.js.map +1 -1
  11. package/dist/mcp.cjs +246 -52
  12. package/dist/mcp.cjs.map +1 -1
  13. package/dist/mcp.js +246 -52
  14. package/dist/mcp.js.map +1 -1
  15. package/package.json +25 -21
  16. package/packages/ascii-renderer/src/canvas.ts +7 -4
  17. package/packages/ascii-renderer/src/class-diagram.ts +192 -12
  18. package/packages/ascii-renderer/src/draw-arrows.ts +79 -35
  19. package/packages/ascii-renderer/src/sequence.ts +42 -8
  20. package/packages/ascii-renderer/src/shapes/hexagon.ts +7 -3
  21. package/packages/ascii-renderer/src/shapes/special.ts +12 -4
  22. package/packages/core/src/generated/mono-font-subset.ts +32 -0
  23. package/packages/core/src/text-metrics.ts +22 -0
  24. package/packages/core/src/theme.ts +29 -13
  25. package/packages/mermaid-parser/src/xychart/parser.ts +44 -1
  26. package/src/__tests__/ascii-arrowhead-direction-1083.test.ts +51 -0
  27. package/src/__tests__/ascii-class-box-occupancy.test.ts +82 -43
  28. package/src/__tests__/ascii-class-detour-label-routing-487.test.ts +8 -8
  29. package/src/__tests__/ascii-class-parent-alignment-971.test.ts +139 -0
  30. package/src/__tests__/ascii-class-parent-alignment-972.test.ts +208 -0
  31. package/src/__tests__/ascii-draw-arrows-coverage.test.ts +56 -0
  32. package/src/__tests__/ascii-edge-ending-glyphs.test.ts +3 -3
  33. package/src/__tests__/ascii-sequence-form-invariants.test.ts +51 -0
  34. package/src/__tests__/text-metrics.test.ts +13 -0
  35. package/src/__tests__/theme.test.ts +30 -0
  36. package/src/__tests__/workspace-package-boundaries.test.ts +2 -2
  37. package/src/__tests__/xychart-parser-categories.test.ts +78 -0
  38. package/src/cli/html-viewer.ts +5 -0
package/dist/mcp.cjs CHANGED
@@ -812,12 +812,31 @@ function isGeometricShapesTextDefault(code) {
812
812
  return code >= 9632 && code <= 9727;
813
813
  }
814
814
  /**
815
+ * Arrows-block glyphs that carry Extended_Pictographic (emoji-capable) but
816
+ * no default Emoji_Presentation, and render as a single narrow column in
817
+ * every real terminal — the same misclassification the Geometric Shapes
818
+ * block above hits. Covers:
819
+ *
820
+ * - ↖ U+2196, ↗ U+2197, ↘ U+2198, ↙ U+2199 — drawn by the ASCII renderer
821
+ * itself for diagonal edge-routing arrowheads
822
+ * (packages/ascii-renderer/src/draw-arrows.ts's `drawArrowHead`) in place
823
+ * of the filled triangles (◢◣◤◥) JetBrains Mono NL has no glyph for at
824
+ * all — see issue #1062.
825
+ * - ↔ U+2194, ↕ U+2195, ↩ U+21A9, ↪ U+21AA — not drawn by the renderer
826
+ * itself, but reachable if a diagram author types one into node/edge/note
827
+ * label text, which passes through to the ASCII grid verbatim.
828
+ */
829
+ function isArrowsBlockTextDefault(code) {
830
+ return code >= 8596 && code <= 8601 || code === 8617 || code === 8618;
831
+ }
832
+ /**
815
833
  * Check if a character is an emoji (fullwidth)
816
834
  */
817
835
  function isEmoji(char) {
818
836
  if (EMOJI_PRESENTATION_REGEX.test(char)) return true;
819
837
  const code = char.codePointAt(0);
820
838
  if (code !== void 0 && isGeometricShapesTextDefault(code)) return false;
839
+ if (code !== void 0 && isArrowsBlockTextDefault(code)) return false;
821
840
  return EXTENDED_PICTOGRAPHIC_REGEX.test(char);
822
841
  }
823
842
  /**
@@ -1237,6 +1256,38 @@ function sanitizeClassName(className) {
1237
1256
  return /^-?[a-zA-Z_][a-zA-Z0-9_-]*$/.test(className) ? className : void 0;
1238
1257
  }
1239
1258
  //#endregion
1259
+ //#region packages/core/src/generated/mono-font-subset.ts
1260
+ /**
1261
+ * GENERATED FILE — do not edit by hand.
1262
+ *
1263
+ * Regenerate with `pnpm run build:mono-font`
1264
+ * (scripts/build-mono-font-subset.ts), which subsets
1265
+ * third_party/fonts/jetbrains-mono-nl/JetBrainsMonoNL-Regular.ttf down to
1266
+ * Basic Latin + Latin-1 Supplement — the self-hosted, embeddable
1267
+ * `@font-face` packages/core/src/theme.ts's `buildStyleBlock()` inlines
1268
+ * into an SVG's own `<style>` for its `.mono` rule (class-diagram method
1269
+ * signatures, ER-diagram attribute types), replacing a Google Fonts CDN
1270
+ * `@import` this library's SVG output previously depended on by default
1271
+ * (#1061). See that script's header comment for why this subset is
1272
+ * deliberately narrower than the site's own (demo/components/generated/
1273
+ * mono-font-subset.ts).
1274
+ */
1275
+ /** The self-hosted family name every consumer's font stack should list
1276
+ * first — matches this subset's own `name` table entry. */
1277
+ var MONO_FONT_FAMILY = "JetBrains Mono NL";
1278
+ /** The `@font-face` rule embedding the subset as a base64 `woff2` data
1279
+ * URI — no separate network request, no page-depth-relative path to get
1280
+ * wrong (see the script header comment for why data-URI over a static
1281
+ * asset file). */
1282
+ var MONO_FONT_FACE_CSS = `@font-face {
1283
+ font-family: 'JetBrains Mono NL';
1284
+ font-style: normal;
1285
+ font-weight: 400;
1286
+ font-display: swap;
1287
+ src: url(data:font/woff2;base64,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) format('woff2');
1288
+ unicode-range: U+0020-007E, U+00A0-00FF;
1289
+ }`;
1290
+ //#endregion
1240
1291
  //#region packages/core/src/theme.ts
1241
1292
  /** Default bg/fg when no colors are provided (zinc light) */
1242
1293
  var DEFAULTS$1 = {
@@ -1484,11 +1535,26 @@ function styleOpenTag(nonce) {
1484
1535
  *
1485
1536
  * Any other value is treated as a literal font name: sanitized and quoted
1486
1537
  * exactly as before.
1538
+ *
1539
+ * `hasMonoFont`'s `.mono` rule (class-diagram method signatures, ER-diagram
1540
+ * attribute types) never reaches out to Google Fonts at all — it used to
1541
+ * `@import` 'JetBrains Mono' from Google's CDN, a third-party network
1542
+ * dependency a published library's default SVG output had no business
1543
+ * making (#1061, filed as a follow-up from #1059's ASCII-side self-hosting
1544
+ * fix). It now embeds {@link SVG_MONO_FONT_FACE_CSS} — a small, subsetted
1545
+ * (Basic Latin + Latin-1 Supplement), self-hosted `@font-face` as a base64
1546
+ * `woff2` data URI, generated by scripts/build-mono-font-subset.ts into
1547
+ * ./generated/mono-font-subset.ts — directly in the SVG's own `<style>`
1548
+ * block. Unlike `font` above, this isn't caller-configurable: it's the same
1549
+ * embed for every consumer, on by default, with no network request either
1550
+ * way — a strict improvement over the dead-or-alive CDN fetch it replaces,
1551
+ * and the only way a *standalone* SVG (no surrounding host page providing
1552
+ * its own copy of the font) renders `.mono` text correctly out of the box.
1487
1553
  */
1488
1554
  function buildStyleBlock(font, hasMonoFont, nonce) {
1489
1555
  const isVarReference = isSafeCssVarReference(font);
1490
1556
  const safeFont = isVarReference ? font.trim() : font.replace(/[<>{};'"]/g, "");
1491
- const fontImports = [...isVarReference || isFontStackOrGenericFamily(font) ? [] : [`@import url('https://fonts.googleapis.com/css2?family=${encodeURIComponent(safeFont)}:wght@400;500;600;700&amp;display=swap');`], ...hasMonoFont ? [`@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&amp;display=swap');`] : []];
1557
+ const fontImports = isVarReference || isFontStackOrGenericFamily(font) ? [] : [`@import url('https://fonts.googleapis.com/css2?family=${encodeURIComponent(safeFont)}:wght@400;500;600;700&amp;display=swap');`];
1492
1558
  const derivedVars = `
1493
1559
  /* Derived from --bg and --fg (overridable via --line, --accent, etc.) */
1494
1560
  --_text: var(--fg);
@@ -1506,8 +1572,9 @@ function buildStyleBlock(font, hasMonoFont, nonce) {
1506
1572
  return [
1507
1573
  styleOpenTag(nonce),
1508
1574
  ...fontImports.length > 0 ? [` ${fontImports.join("\n ")}`] : [],
1575
+ ...hasMonoFont ? [` ${MONO_FONT_FACE_CSS}`] : [],
1509
1576
  ` text { font-family: ${isVarReference ? safeFont : `'${safeFont}'`}, system-ui, sans-serif; }`,
1510
- ...hasMonoFont ? [` .mono { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', ui-monospace, monospace; }`] : [],
1577
+ ...hasMonoFont ? [` .mono { font-family: '${MONO_FONT_FAMILY}', 'SF Mono', 'Fira Code', ui-monospace, monospace; }`] : [],
1511
1578
  ` svg {${derivedVars}`,
1512
1579
  ` }`,
1513
1580
  "</style>"
@@ -2697,7 +2764,7 @@ function parseXYChart(lines) {
2697
2764
  const xCatMatch = line.match(/^x-axis\s+(?:"([^"]*)"\s*)?\[([^\]]+)\]/);
2698
2765
  if (xCatMatch) {
2699
2766
  if (xCatMatch[1]) xAxis.title = xCatMatch[1];
2700
- xAxis.categories = xCatMatch[2].split(",").map((s) => s.trim());
2767
+ xAxis.categories = splitCategoryList(xCatMatch[2]).map((s) => unquote(s.trim()));
2701
2768
  continue;
2702
2769
  }
2703
2770
  const xRangeMatch = line.match(/^x-axis\s+(?:"([^"]*)"\s+)?(-?\d+(?:\.\d+)?)\s*-->\s*(-?\d+(?:\.\d+)?)/);
@@ -2768,6 +2835,39 @@ function parseXYChart(lines) {
2768
2835
  };
2769
2836
  }
2770
2837
  /**
2838
+ * Split a categorical x-axis bracket's comma-separated contents into raw
2839
+ * item strings, treating a comma *inside* a double-quoted item as literal
2840
+ * text rather than a delimiter. A naive `.split(',')` would break
2841
+ * `["Jan, Feb", "Mar"]` into three pieces instead of two — quoting a value
2842
+ * that itself contains a comma is exactly the case quoting exists for. Each
2843
+ * returned raw item is still trimmed and unquoted by the caller.
2844
+ */
2845
+ function splitCategoryList(str) {
2846
+ const items = [];
2847
+ let current = "";
2848
+ let inQuotes = false;
2849
+ for (const char of str) if (char === "\"") {
2850
+ inQuotes = !inQuotes;
2851
+ current += char;
2852
+ } else if (char === "," && !inQuotes) {
2853
+ items.push(current);
2854
+ current = "";
2855
+ } else current += char;
2856
+ items.push(current);
2857
+ return items;
2858
+ }
2859
+ /**
2860
+ * Strip one matching pair of leading/trailing double quotes from a category
2861
+ * item, if present — mirroring how an axis *title* capture group already
2862
+ * unquotes via its regex. `x-axis [A, B, C]` items are only split and
2863
+ * trimmed, so a quoted item like `"CLI output / logs"` previously kept its
2864
+ * literal quote characters in the rendered label. See issue #1087.
2865
+ */
2866
+ function unquote(value) {
2867
+ if (value.length >= 2 && value.startsWith("\"") && value.endsWith("\"")) return value.slice(1, -1);
2868
+ return value;
2869
+ }
2870
+ /**
2771
2871
  * Parse a `bar`/`line` series' comma-separated bracket contents into
2772
2872
  * numbers, throwing on any element that isn't a valid number instead of
2773
2873
  * silently coercing it to `NaN` (which serializes as `null` and can
@@ -9581,10 +9681,10 @@ function canvasToString$1(canvas, options) {
9581
9681
  var VERTICAL_FLIP_MAP = {
9582
9682
  "▲": "▼",
9583
9683
  "▼": "▲",
9584
- "◤": "◣",
9585
- "◣": "◤",
9586
- "◥": "◢",
9587
- "◢": "◥",
9684
+ "↖": "↙",
9685
+ "↙": "↖",
9686
+ "↗": "↘",
9687
+ "↘": "↗",
9588
9688
  "^": "v",
9589
9689
  v: "^",
9590
9690
  "┌": "└",
@@ -13894,51 +13994,74 @@ function markerArrowChar(useAscii, marker) {
13894
13994
  if (marker === "cross") return useAscii ? "x" : "✕";
13895
13995
  }
13896
13996
  /**
13997
+ * Maps a direction to its Unicode arrowhead glyph, or `undefined` for a
13998
+ * direction with no arrowhead (`Middle`, or any other value this switch
13999
+ * doesn't recognize) — callers decide the ultimate fallback.
14000
+ *
14001
+ * Uses triangular Unicode symbols (▲▼◄►) for the four orthogonal
14002
+ * directions and diagonal arrow symbols (↗↖↘↙) for the four diagonal ones.
14003
+ * The diagonal glyphs are plain Arrows-block characters (U+2196–U+2199),
14004
+ * not filled triangles like the orthogonal set — JetBrains Mono NL (this
14005
+ * site's self-hosted ASCII font, see scripts/build-mono-font-subset.ts)
14006
+ * has no glyph at all for the filled diagonal triangles (◢◣◤◥, U+25E2–
14007
+ * U+25E5) that would otherwise match the orthogonal style; U+2196–U+2199
14008
+ * are real, correctly-directional glyphs the font does have, which beats
14009
+ * falling back to an unpinned system font for just these four characters.
14010
+ * See issue #1062.
14011
+ *
14012
+ * Exported (alongside {@link asciiArrowChar}) so tests can exercise every
14013
+ * direction directly — the ELK/pathfinder layout this module actually
14014
+ * draws from rarely if ever produces some direction combinations (e.g.
14015
+ * `LowerLeft` only arises from determinePath's rare Case-4 diagonal
14016
+ * fallback, and only for a specific relative source/target position no
14017
+ * existing sample or hand-written diagram happens to trigger), which would
14018
+ * otherwise leave this mapping's correctness for those directions unverified.
14019
+ */
14020
+ function unicodeArrowChar(dir) {
14021
+ if (dirEquals(dir, Up)) return "▲";
14022
+ if (dirEquals(dir, Down)) return "▼";
14023
+ if (dirEquals(dir, Left)) return "◄";
14024
+ if (dirEquals(dir, Right)) return "►";
14025
+ if (dirEquals(dir, UpperRight)) return "↗";
14026
+ if (dirEquals(dir, UpperLeft)) return "↖";
14027
+ if (dirEquals(dir, LowerRight)) return "↘";
14028
+ if (dirEquals(dir, LowerLeft)) return "↙";
14029
+ }
14030
+ /** ASCII-mode counterpart of {@link unicodeArrowChar} — orthogonal only, no diagonals. */
14031
+ function asciiArrowChar(dir) {
14032
+ if (dirEquals(dir, Up)) return "^";
14033
+ if (dirEquals(dir, Down)) return "v";
14034
+ if (dirEquals(dir, Left)) return "<";
14035
+ if (dirEquals(dir, Right)) return ">";
14036
+ }
14037
+ /**
13897
14038
  * Draw the arrowhead at the end of an edge path.
13898
- * Uses triangular Unicode symbols (▲▼◄►) or ASCII symbols (^v<>).
13899
14039
  *
13900
- * `marker` overrides the directional triangle with a fixed circle/cross
13901
- * glyph for `--o`/`--x` (flowchart) terminators — those are direction-
14040
+ * `marker` overrides the directional glyph with a fixed circle/cross glyph
14041
+ * for `--o`/`--x` (flowchart) terminators — those are direction-
13902
14042
  * independent, so the direction computed below is only used to place it,
13903
14043
  * never to pick which glyph to draw. See issue #330.
13904
14044
  */
13905
14045
  function drawArrowHead(graph, lastLine, fallbackDir, marker) {
13906
14046
  const canvas = copyCanvas(graph.canvas);
13907
14047
  if (lastLine.length === 0) return canvas;
13908
- const from = lastLine[0];
13909
14048
  const lastPos = lastLine[lastLine.length - 1];
13910
- let dir = determineDirection(from, lastPos);
14049
+ let dir = determineDirection(lastLine.length >= 2 ? lastLine[lastLine.length - 2] : lastLine[0], lastPos);
13911
14050
  if (lastLine.length === 1 || dirEquals(dir, Middle)) dir = fallbackDir;
13912
- let char;
13913
14051
  const markerChar = markerArrowChar(graph.config.useAscii, marker);
14052
+ let char;
13914
14053
  if (markerChar !== void 0) char = markerChar;
13915
14054
  else if (!graph.config.useAscii) {
13916
- if (dirEquals(dir, Up)) char = "▲";
13917
- else if (dirEquals(dir, Down)) char = "▼";
13918
- else if (dirEquals(dir, Left)) char = "◄";
13919
- else if (dirEquals(dir, Right)) char = "►";
13920
- else if (dirEquals(dir, UpperRight)) char = "◥";
13921
- else if (dirEquals(dir, UpperLeft)) char = "◤";
13922
- else if (dirEquals(dir, LowerRight)) char = "◢";
13923
- else if (dirEquals(dir, LowerLeft)) char = "◣";
13924
- else if (dirEquals(fallbackDir, Up)) char = "▲";
13925
- else if (dirEquals(fallbackDir, Down)) char = "▼";
13926
- else if (dirEquals(fallbackDir, Left)) char = "◄";
13927
- else if (dirEquals(fallbackDir, Right)) char = "►";
13928
- else if (dirEquals(fallbackDir, UpperRight)) char = "◥";
13929
- else if (dirEquals(fallbackDir, UpperLeft)) char = "◤";
13930
- else if (dirEquals(fallbackDir, LowerRight)) char = "◢";
13931
- else if (dirEquals(fallbackDir, LowerLeft)) char = "◣";
13932
- else char = "●";
13933
- } else if (dirEquals(dir, Up)) char = "^";
13934
- else if (dirEquals(dir, Down)) char = "v";
13935
- else if (dirEquals(dir, Left)) char = "<";
13936
- else if (dirEquals(dir, Right)) char = ">";
13937
- else if (dirEquals(fallbackDir, Up)) char = "^";
13938
- else if (dirEquals(fallbackDir, Down)) char = "v";
13939
- else if (dirEquals(fallbackDir, Left)) char = "<";
13940
- else if (dirEquals(fallbackDir, Right)) char = ">";
13941
- else char = "*";
14055
+ const resolved = unicodeArrowChar(dir);
14056
+ /* v8 ignore else */
14057
+ if (resolved !== void 0) char = resolved;
14058
+ else char = unicodeArrowChar(fallbackDir) ?? "●";
14059
+ } else {
14060
+ const resolved = asciiArrowChar(dir);
14061
+ /* v8 ignore else */
14062
+ if (resolved !== void 0) char = resolved;
14063
+ else char = asciiArrowChar(fallbackDir) ?? "*";
14064
+ }
13942
14065
  write(canvas, lastPos.x, lastPos.y, char);
13943
14066
  return canvas;
13944
14067
  }
@@ -15581,21 +15704,29 @@ function renderSequenceAscii(text, config, colorMode, theme) {
15581
15704
  }
15582
15705
  const leftNoteWidth = new Array(diagram.actors.length).fill(0);
15583
15706
  const rightNoteWidth = new Array(diagram.actors.length).fill(0);
15584
- for (const note of diagram.notes) {
15585
- if (note.position !== "left" && note.position !== "right") continue;
15586
- const aIdx = actorIdx.get(note.actorIds[0]) ?? 0;
15707
+ const overNoteHalfLeft = new Array(diagram.actors.length).fill(0);
15708
+ const overNoteHalfRight = new Array(diagram.actors.length).fill(0);
15709
+ for (const note of diagram.notes) if (note.position === "left" || note.position === "right") {
15710
+ const aIdx = actorIndexOf(note.actorIds[0]);
15587
15711
  const w = noteBoxWidth(note);
15588
15712
  if (note.position === "left") leftNoteWidth[aIdx] = Math.max(leftNoteWidth[aIdx], w);
15589
15713
  else rightNoteWidth[aIdx] = Math.max(rightNoteWidth[aIdx], w);
15714
+ } else if (note.position === "over" && note.actorIds.length === 1) {
15715
+ const aIdx = actorIndexOf(note.actorIds[0]);
15716
+ const w = noteBoxWidth(note);
15717
+ const halfLeft = Math.floor(w / 2);
15718
+ const halfRight = w - halfLeft;
15719
+ overNoteHalfLeft[aIdx] = Math.max(overNoteHalfLeft[aIdx], halfLeft);
15720
+ overNoteHalfRight[aIdx] = Math.max(overNoteHalfRight[aIdx], halfRight);
15590
15721
  }
15591
15722
  const minLifelineGap = paddingOffset(config.paddingX, 5, 10, 4);
15592
- const llX = [Math.max(halfBox[0] + (boxSpanOf[0] === -1 ? 0 : 2), leftNoteWidth[0] + 1)];
15723
+ const llX = [Math.max(halfBox[0] + (boxSpanOf[0] === -1 ? 0 : 2), leftNoteWidth[0] + 1, overNoteHalfLeft[0])];
15593
15724
  for (let i = 1; i < diagram.actors.length; i++) {
15594
15725
  const prevSpan = boxSpanOf[i - 1];
15595
15726
  const thisSpan = boxSpanOf[i];
15596
15727
  const walls = prevSpan === thisSpan ? 0 : (prevSpan === -1 ? 0 : 1) + (thisSpan === -1 ? 0 : 1);
15597
15728
  const wallExtra = walls === 0 ? 0 : walls * 2 - 1;
15598
- const noteGapNeed = Math.max(rightNoteWidth[i - 1], leftNoteWidth[i]) + 2;
15729
+ const noteGapNeed = Math.max(rightNoteWidth[i - 1], leftNoteWidth[i], overNoteHalfRight[i - 1], overNoteHalfLeft[i]) + 2;
15599
15730
  const gap = Math.max(halfBox[i - 1] + halfBox[i] + 2 + wallExtra, adjMaxWidth[i - 1] + 2, noteGapNeed, minLifelineGap);
15600
15731
  llX[i] = llX[i - 1] + gap;
15601
15732
  }
@@ -16400,27 +16531,90 @@ function renderClassAscii(text, config, colorMode, theme, options = {}) {
16400
16531
  for (let lv = 0; lv <= maxLevel; lv++) {
16401
16532
  const group = levelGroups[lv];
16402
16533
  if (group.length === 0) continue;
16534
+ const blockKeyOf = (id) => {
16535
+ const pset = parents.get(id);
16536
+ return pset && pset.size > 0 ? [...pset].sort().join(",") : `root:${id}`;
16537
+ };
16538
+ const blockMembers = /* @__PURE__ */ new Map();
16539
+ for (const id of group) {
16540
+ const key = blockKeyOf(id);
16541
+ let members = blockMembers.get(key);
16542
+ if (!members) {
16543
+ members = [];
16544
+ blockMembers.set(key, members);
16545
+ }
16546
+ members.push(id);
16547
+ }
16548
+ const baselineSlotLeft = /* @__PURE__ */ new Map();
16549
+ const slotWidthOf = /* @__PURE__ */ new Map();
16550
+ const leftPadOf = /* @__PURE__ */ new Map();
16551
+ {
16552
+ let x = 0;
16553
+ for (const id of group) {
16554
+ const w = classBoxW.get(id);
16555
+ const reach = columnReach.get(id);
16556
+ const leftPad = Math.max(0, reach.left - Math.floor(w / 2));
16557
+ const rightPad = Math.max(0, reach.right - (w - 1 - Math.floor(w / 2)));
16558
+ baselineSlotLeft.set(id, x);
16559
+ slotWidthOf.set(id, leftPad + w + rightPad);
16560
+ leftPadOf.set(id, leftPad);
16561
+ x += leftPad + w + rightPad + hGap;
16562
+ }
16563
+ }
16403
16564
  let currentX = 0;
16404
16565
  let maxH = 0;
16405
- for (const id of group) {
16566
+ /** Places one class's box at slot-left `slotLeft`, advancing `currentX`/`maxH`/`maxSlotEnd`. */
16567
+ const placeAt = (id, slotLeft) => {
16406
16568
  const cls = classById.get(id);
16407
16569
  const w = classBoxW.get(id);
16408
16570
  const h = classBoxH.get(id);
16409
- const reach = columnReach.get(id);
16410
- const leftPad = Math.max(0, reach.left - Math.floor(w / 2));
16411
- const rightPad = Math.max(0, reach.right - (w - 1 - Math.floor(w / 2)));
16571
+ const leftPad = leftPadOf.get(id);
16572
+ const slotWidth = slotWidthOf.get(id);
16412
16573
  placed.set(id, {
16413
16574
  cls,
16414
16575
  sections: classSections.get(id),
16415
- x: currentX + leftPad,
16576
+ x: slotLeft + leftPad,
16416
16577
  y: currentY,
16417
16578
  width: w,
16418
16579
  height: h
16419
16580
  });
16420
- const slotWidth = leftPad + w + rightPad;
16421
- maxSlotEnd = Math.max(maxSlotEnd, currentX + slotWidth);
16422
- currentX += slotWidth + hGap;
16581
+ maxSlotEnd = Math.max(maxSlotEnd, slotLeft + slotWidth);
16582
+ currentX = slotLeft + slotWidth + hGap;
16423
16583
  maxH = Math.max(maxH, h);
16584
+ };
16585
+ const placedThisLevel = /* @__PURE__ */ new Set();
16586
+ for (const id of group) {
16587
+ if (placedThisLevel.has(id)) continue;
16588
+ const key = blockKeyOf(id);
16589
+ const pset = parents.get(id);
16590
+ const qualifying = pset ? [...pset].filter((pid) => level.get(pid) < lv) : [];
16591
+ if (qualifying.length === 0) {
16592
+ placeAt(id, Math.max(baselineSlotLeft.get(id), currentX, 0));
16593
+ placedThisLevel.add(id);
16594
+ continue;
16595
+ }
16596
+ const members = blockMembers.get(key);
16597
+ const parentCenters = qualifying.map((pid) => {
16598
+ const p = placed.get(pid);
16599
+ return p.x + Math.floor(p.width / 2);
16600
+ });
16601
+ const desiredCenter = Math.round(parentCenters.reduce((sum, c) => sum + c, 0) / parentCenters.length);
16602
+ let totalWidth = 0;
16603
+ for (let i = 0; i < members.length; i++) {
16604
+ totalWidth += slotWidthOf.get(members[i]);
16605
+ if (i < members.length - 1) totalWidth += hGap;
16606
+ }
16607
+ const firstLeftPad = leftPadOf.get(members[0]);
16608
+ const lastMember = members[members.length - 1];
16609
+ const lastRightPad = slotWidthOf.get(lastMember) - leftPadOf.get(lastMember) - classBoxW.get(lastMember);
16610
+ const visibleWidth = totalWidth - firstLeftPad - lastRightPad;
16611
+ const blockDesiredLeft = desiredCenter - firstLeftPad - Math.floor(visibleWidth / 2);
16612
+ let memberX = Math.max(blockDesiredLeft, currentX, 0);
16613
+ for (const memberId of members) {
16614
+ placeAt(memberId, memberX);
16615
+ placedThisLevel.add(memberId);
16616
+ memberX = currentX;
16617
+ }
16424
16618
  }
16425
16619
  currentY += maxH + vGap;
16426
16620
  }