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.js CHANGED
@@ -787,12 +787,31 @@ function isGeometricShapesTextDefault(code) {
787
787
  return code >= 9632 && code <= 9727;
788
788
  }
789
789
  /**
790
+ * Arrows-block glyphs that carry Extended_Pictographic (emoji-capable) but
791
+ * no default Emoji_Presentation, and render as a single narrow column in
792
+ * every real terminal — the same misclassification the Geometric Shapes
793
+ * block above hits. Covers:
794
+ *
795
+ * - ↖ U+2196, ↗ U+2197, ↘ U+2198, ↙ U+2199 — drawn by the ASCII renderer
796
+ * itself for diagonal edge-routing arrowheads
797
+ * (packages/ascii-renderer/src/draw-arrows.ts's `drawArrowHead`) in place
798
+ * of the filled triangles (◢◣◤◥) JetBrains Mono NL has no glyph for at
799
+ * all — see issue #1062.
800
+ * - ↔ U+2194, ↕ U+2195, ↩ U+21A9, ↪ U+21AA — not drawn by the renderer
801
+ * itself, but reachable if a diagram author types one into node/edge/note
802
+ * label text, which passes through to the ASCII grid verbatim.
803
+ */
804
+ function isArrowsBlockTextDefault(code) {
805
+ return code >= 8596 && code <= 8601 || code === 8617 || code === 8618;
806
+ }
807
+ /**
790
808
  * Check if a character is an emoji (fullwidth)
791
809
  */
792
810
  function isEmoji(char) {
793
811
  if (EMOJI_PRESENTATION_REGEX.test(char)) return true;
794
812
  const code = char.codePointAt(0);
795
813
  if (code !== void 0 && isGeometricShapesTextDefault(code)) return false;
814
+ if (code !== void 0 && isArrowsBlockTextDefault(code)) return false;
796
815
  return EXTENDED_PICTOGRAPHIC_REGEX.test(char);
797
816
  }
798
817
  /**
@@ -1212,6 +1231,38 @@ function sanitizeClassName(className) {
1212
1231
  return /^-?[a-zA-Z_][a-zA-Z0-9_-]*$/.test(className) ? className : void 0;
1213
1232
  }
1214
1233
  //#endregion
1234
+ //#region packages/core/src/generated/mono-font-subset.ts
1235
+ /**
1236
+ * GENERATED FILE — do not edit by hand.
1237
+ *
1238
+ * Regenerate with `pnpm run build:mono-font`
1239
+ * (scripts/build-mono-font-subset.ts), which subsets
1240
+ * third_party/fonts/jetbrains-mono-nl/JetBrainsMonoNL-Regular.ttf down to
1241
+ * Basic Latin + Latin-1 Supplement — the self-hosted, embeddable
1242
+ * `@font-face` packages/core/src/theme.ts's `buildStyleBlock()` inlines
1243
+ * into an SVG's own `<style>` for its `.mono` rule (class-diagram method
1244
+ * signatures, ER-diagram attribute types), replacing a Google Fonts CDN
1245
+ * `@import` this library's SVG output previously depended on by default
1246
+ * (#1061). See that script's header comment for why this subset is
1247
+ * deliberately narrower than the site's own (demo/components/generated/
1248
+ * mono-font-subset.ts).
1249
+ */
1250
+ /** The self-hosted family name every consumer's font stack should list
1251
+ * first — matches this subset's own `name` table entry. */
1252
+ var MONO_FONT_FAMILY = "JetBrains Mono NL";
1253
+ /** The `@font-face` rule embedding the subset as a base64 `woff2` data
1254
+ * URI — no separate network request, no page-depth-relative path to get
1255
+ * wrong (see the script header comment for why data-URI over a static
1256
+ * asset file). */
1257
+ var MONO_FONT_FACE_CSS = `@font-face {
1258
+ font-family: 'JetBrains Mono NL';
1259
+ font-style: normal;
1260
+ font-weight: 400;
1261
+ font-display: swap;
1262
+ src: url(data:font/woff2;base64,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) format('woff2');
1263
+ unicode-range: U+0020-007E, U+00A0-00FF;
1264
+ }`;
1265
+ //#endregion
1215
1266
  //#region packages/core/src/theme.ts
1216
1267
  /** Default bg/fg when no colors are provided (zinc light) */
1217
1268
  var DEFAULTS$1 = {
@@ -1459,11 +1510,26 @@ function styleOpenTag(nonce) {
1459
1510
  *
1460
1511
  * Any other value is treated as a literal font name: sanitized and quoted
1461
1512
  * exactly as before.
1513
+ *
1514
+ * `hasMonoFont`'s `.mono` rule (class-diagram method signatures, ER-diagram
1515
+ * attribute types) never reaches out to Google Fonts at all — it used to
1516
+ * `@import` 'JetBrains Mono' from Google's CDN, a third-party network
1517
+ * dependency a published library's default SVG output had no business
1518
+ * making (#1061, filed as a follow-up from #1059's ASCII-side self-hosting
1519
+ * fix). It now embeds {@link SVG_MONO_FONT_FACE_CSS} — a small, subsetted
1520
+ * (Basic Latin + Latin-1 Supplement), self-hosted `@font-face` as a base64
1521
+ * `woff2` data URI, generated by scripts/build-mono-font-subset.ts into
1522
+ * ./generated/mono-font-subset.ts — directly in the SVG's own `<style>`
1523
+ * block. Unlike `font` above, this isn't caller-configurable: it's the same
1524
+ * embed for every consumer, on by default, with no network request either
1525
+ * way — a strict improvement over the dead-or-alive CDN fetch it replaces,
1526
+ * and the only way a *standalone* SVG (no surrounding host page providing
1527
+ * its own copy of the font) renders `.mono` text correctly out of the box.
1462
1528
  */
1463
1529
  function buildStyleBlock(font, hasMonoFont, nonce) {
1464
1530
  const isVarReference = isSafeCssVarReference(font);
1465
1531
  const safeFont = isVarReference ? font.trim() : font.replace(/[<>{};'"]/g, "");
1466
- 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');`] : []];
1532
+ const fontImports = isVarReference || isFontStackOrGenericFamily(font) ? [] : [`@import url('https://fonts.googleapis.com/css2?family=${encodeURIComponent(safeFont)}:wght@400;500;600;700&amp;display=swap');`];
1467
1533
  const derivedVars = `
1468
1534
  /* Derived from --bg and --fg (overridable via --line, --accent, etc.) */
1469
1535
  --_text: var(--fg);
@@ -1481,8 +1547,9 @@ function buildStyleBlock(font, hasMonoFont, nonce) {
1481
1547
  return [
1482
1548
  styleOpenTag(nonce),
1483
1549
  ...fontImports.length > 0 ? [` ${fontImports.join("\n ")}`] : [],
1550
+ ...hasMonoFont ? [` ${MONO_FONT_FACE_CSS}`] : [],
1484
1551
  ` text { font-family: ${isVarReference ? safeFont : `'${safeFont}'`}, system-ui, sans-serif; }`,
1485
- ...hasMonoFont ? [` .mono { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', ui-monospace, monospace; }`] : [],
1552
+ ...hasMonoFont ? [` .mono { font-family: '${MONO_FONT_FAMILY}', 'SF Mono', 'Fira Code', ui-monospace, monospace; }`] : [],
1486
1553
  ` svg {${derivedVars}`,
1487
1554
  ` }`,
1488
1555
  "</style>"
@@ -2672,7 +2739,7 @@ function parseXYChart(lines) {
2672
2739
  const xCatMatch = line.match(/^x-axis\s+(?:"([^"]*)"\s*)?\[([^\]]+)\]/);
2673
2740
  if (xCatMatch) {
2674
2741
  if (xCatMatch[1]) xAxis.title = xCatMatch[1];
2675
- xAxis.categories = xCatMatch[2].split(",").map((s) => s.trim());
2742
+ xAxis.categories = splitCategoryList(xCatMatch[2]).map((s) => unquote(s.trim()));
2676
2743
  continue;
2677
2744
  }
2678
2745
  const xRangeMatch = line.match(/^x-axis\s+(?:"([^"]*)"\s+)?(-?\d+(?:\.\d+)?)\s*-->\s*(-?\d+(?:\.\d+)?)/);
@@ -2743,6 +2810,39 @@ function parseXYChart(lines) {
2743
2810
  };
2744
2811
  }
2745
2812
  /**
2813
+ * Split a categorical x-axis bracket's comma-separated contents into raw
2814
+ * item strings, treating a comma *inside* a double-quoted item as literal
2815
+ * text rather than a delimiter. A naive `.split(',')` would break
2816
+ * `["Jan, Feb", "Mar"]` into three pieces instead of two — quoting a value
2817
+ * that itself contains a comma is exactly the case quoting exists for. Each
2818
+ * returned raw item is still trimmed and unquoted by the caller.
2819
+ */
2820
+ function splitCategoryList(str) {
2821
+ const items = [];
2822
+ let current = "";
2823
+ let inQuotes = false;
2824
+ for (const char of str) if (char === "\"") {
2825
+ inQuotes = !inQuotes;
2826
+ current += char;
2827
+ } else if (char === "," && !inQuotes) {
2828
+ items.push(current);
2829
+ current = "";
2830
+ } else current += char;
2831
+ items.push(current);
2832
+ return items;
2833
+ }
2834
+ /**
2835
+ * Strip one matching pair of leading/trailing double quotes from a category
2836
+ * item, if present — mirroring how an axis *title* capture group already
2837
+ * unquotes via its regex. `x-axis [A, B, C]` items are only split and
2838
+ * trimmed, so a quoted item like `"CLI output / logs"` previously kept its
2839
+ * literal quote characters in the rendered label. See issue #1087.
2840
+ */
2841
+ function unquote(value) {
2842
+ if (value.length >= 2 && value.startsWith("\"") && value.endsWith("\"")) return value.slice(1, -1);
2843
+ return value;
2844
+ }
2845
+ /**
2746
2846
  * Parse a `bar`/`line` series' comma-separated bracket contents into
2747
2847
  * numbers, throwing on any element that isn't a valid number instead of
2748
2848
  * silently coercing it to `NaN` (which serializes as `null` and can
@@ -9556,10 +9656,10 @@ function canvasToString$1(canvas, options) {
9556
9656
  var VERTICAL_FLIP_MAP = {
9557
9657
  "▲": "▼",
9558
9658
  "▼": "▲",
9559
- "◤": "◣",
9560
- "◣": "◤",
9561
- "◥": "◢",
9562
- "◢": "◥",
9659
+ "↖": "↙",
9660
+ "↙": "↖",
9661
+ "↗": "↘",
9662
+ "↘": "↗",
9563
9663
  "^": "v",
9564
9664
  v: "^",
9565
9665
  "┌": "└",
@@ -13869,51 +13969,74 @@ function markerArrowChar(useAscii, marker) {
13869
13969
  if (marker === "cross") return useAscii ? "x" : "✕";
13870
13970
  }
13871
13971
  /**
13972
+ * Maps a direction to its Unicode arrowhead glyph, or `undefined` for a
13973
+ * direction with no arrowhead (`Middle`, or any other value this switch
13974
+ * doesn't recognize) — callers decide the ultimate fallback.
13975
+ *
13976
+ * Uses triangular Unicode symbols (▲▼◄►) for the four orthogonal
13977
+ * directions and diagonal arrow symbols (↗↖↘↙) for the four diagonal ones.
13978
+ * The diagonal glyphs are plain Arrows-block characters (U+2196–U+2199),
13979
+ * not filled triangles like the orthogonal set — JetBrains Mono NL (this
13980
+ * site's self-hosted ASCII font, see scripts/build-mono-font-subset.ts)
13981
+ * has no glyph at all for the filled diagonal triangles (◢◣◤◥, U+25E2–
13982
+ * U+25E5) that would otherwise match the orthogonal style; U+2196–U+2199
13983
+ * are real, correctly-directional glyphs the font does have, which beats
13984
+ * falling back to an unpinned system font for just these four characters.
13985
+ * See issue #1062.
13986
+ *
13987
+ * Exported (alongside {@link asciiArrowChar}) so tests can exercise every
13988
+ * direction directly — the ELK/pathfinder layout this module actually
13989
+ * draws from rarely if ever produces some direction combinations (e.g.
13990
+ * `LowerLeft` only arises from determinePath's rare Case-4 diagonal
13991
+ * fallback, and only for a specific relative source/target position no
13992
+ * existing sample or hand-written diagram happens to trigger), which would
13993
+ * otherwise leave this mapping's correctness for those directions unverified.
13994
+ */
13995
+ function unicodeArrowChar(dir) {
13996
+ if (dirEquals(dir, Up)) return "▲";
13997
+ if (dirEquals(dir, Down)) return "▼";
13998
+ if (dirEquals(dir, Left)) return "◄";
13999
+ if (dirEquals(dir, Right)) return "►";
14000
+ if (dirEquals(dir, UpperRight)) return "↗";
14001
+ if (dirEquals(dir, UpperLeft)) return "↖";
14002
+ if (dirEquals(dir, LowerRight)) return "↘";
14003
+ if (dirEquals(dir, LowerLeft)) return "↙";
14004
+ }
14005
+ /** ASCII-mode counterpart of {@link unicodeArrowChar} — orthogonal only, no diagonals. */
14006
+ function asciiArrowChar(dir) {
14007
+ if (dirEquals(dir, Up)) return "^";
14008
+ if (dirEquals(dir, Down)) return "v";
14009
+ if (dirEquals(dir, Left)) return "<";
14010
+ if (dirEquals(dir, Right)) return ">";
14011
+ }
14012
+ /**
13872
14013
  * Draw the arrowhead at the end of an edge path.
13873
- * Uses triangular Unicode symbols (▲▼◄►) or ASCII symbols (^v<>).
13874
14014
  *
13875
- * `marker` overrides the directional triangle with a fixed circle/cross
13876
- * glyph for `--o`/`--x` (flowchart) terminators — those are direction-
14015
+ * `marker` overrides the directional glyph with a fixed circle/cross glyph
14016
+ * for `--o`/`--x` (flowchart) terminators — those are direction-
13877
14017
  * independent, so the direction computed below is only used to place it,
13878
14018
  * never to pick which glyph to draw. See issue #330.
13879
14019
  */
13880
14020
  function drawArrowHead(graph, lastLine, fallbackDir, marker) {
13881
14021
  const canvas = copyCanvas(graph.canvas);
13882
14022
  if (lastLine.length === 0) return canvas;
13883
- const from = lastLine[0];
13884
14023
  const lastPos = lastLine[lastLine.length - 1];
13885
- let dir = determineDirection(from, lastPos);
14024
+ let dir = determineDirection(lastLine.length >= 2 ? lastLine[lastLine.length - 2] : lastLine[0], lastPos);
13886
14025
  if (lastLine.length === 1 || dirEquals(dir, Middle)) dir = fallbackDir;
13887
- let char;
13888
14026
  const markerChar = markerArrowChar(graph.config.useAscii, marker);
14027
+ let char;
13889
14028
  if (markerChar !== void 0) char = markerChar;
13890
14029
  else if (!graph.config.useAscii) {
13891
- if (dirEquals(dir, Up)) char = "▲";
13892
- else if (dirEquals(dir, Down)) char = "▼";
13893
- else if (dirEquals(dir, Left)) char = "◄";
13894
- else if (dirEquals(dir, Right)) char = "►";
13895
- else if (dirEquals(dir, UpperRight)) char = "◥";
13896
- else if (dirEquals(dir, UpperLeft)) char = "◤";
13897
- else if (dirEquals(dir, LowerRight)) char = "◢";
13898
- else if (dirEquals(dir, LowerLeft)) char = "◣";
13899
- else if (dirEquals(fallbackDir, Up)) char = "▲";
13900
- else if (dirEquals(fallbackDir, Down)) char = "▼";
13901
- else if (dirEquals(fallbackDir, Left)) char = "◄";
13902
- else if (dirEquals(fallbackDir, Right)) char = "►";
13903
- else if (dirEquals(fallbackDir, UpperRight)) char = "◥";
13904
- else if (dirEquals(fallbackDir, UpperLeft)) char = "◤";
13905
- else if (dirEquals(fallbackDir, LowerRight)) char = "◢";
13906
- else if (dirEquals(fallbackDir, LowerLeft)) char = "◣";
13907
- else char = "●";
13908
- } else if (dirEquals(dir, Up)) char = "^";
13909
- else if (dirEquals(dir, Down)) char = "v";
13910
- else if (dirEquals(dir, Left)) char = "<";
13911
- else if (dirEquals(dir, Right)) char = ">";
13912
- else if (dirEquals(fallbackDir, Up)) char = "^";
13913
- else if (dirEquals(fallbackDir, Down)) char = "v";
13914
- else if (dirEquals(fallbackDir, Left)) char = "<";
13915
- else if (dirEquals(fallbackDir, Right)) char = ">";
13916
- else char = "*";
14030
+ const resolved = unicodeArrowChar(dir);
14031
+ /* v8 ignore else */
14032
+ if (resolved !== void 0) char = resolved;
14033
+ else char = unicodeArrowChar(fallbackDir) ?? "●";
14034
+ } else {
14035
+ const resolved = asciiArrowChar(dir);
14036
+ /* v8 ignore else */
14037
+ if (resolved !== void 0) char = resolved;
14038
+ else char = asciiArrowChar(fallbackDir) ?? "*";
14039
+ }
13917
14040
  write(canvas, lastPos.x, lastPos.y, char);
13918
14041
  return canvas;
13919
14042
  }
@@ -15556,21 +15679,29 @@ function renderSequenceAscii(text, config, colorMode, theme) {
15556
15679
  }
15557
15680
  const leftNoteWidth = new Array(diagram.actors.length).fill(0);
15558
15681
  const rightNoteWidth = new Array(diagram.actors.length).fill(0);
15559
- for (const note of diagram.notes) {
15560
- if (note.position !== "left" && note.position !== "right") continue;
15561
- const aIdx = actorIdx.get(note.actorIds[0]) ?? 0;
15682
+ const overNoteHalfLeft = new Array(diagram.actors.length).fill(0);
15683
+ const overNoteHalfRight = new Array(diagram.actors.length).fill(0);
15684
+ for (const note of diagram.notes) if (note.position === "left" || note.position === "right") {
15685
+ const aIdx = actorIndexOf(note.actorIds[0]);
15562
15686
  const w = noteBoxWidth(note);
15563
15687
  if (note.position === "left") leftNoteWidth[aIdx] = Math.max(leftNoteWidth[aIdx], w);
15564
15688
  else rightNoteWidth[aIdx] = Math.max(rightNoteWidth[aIdx], w);
15689
+ } else if (note.position === "over" && note.actorIds.length === 1) {
15690
+ const aIdx = actorIndexOf(note.actorIds[0]);
15691
+ const w = noteBoxWidth(note);
15692
+ const halfLeft = Math.floor(w / 2);
15693
+ const halfRight = w - halfLeft;
15694
+ overNoteHalfLeft[aIdx] = Math.max(overNoteHalfLeft[aIdx], halfLeft);
15695
+ overNoteHalfRight[aIdx] = Math.max(overNoteHalfRight[aIdx], halfRight);
15565
15696
  }
15566
15697
  const minLifelineGap = paddingOffset(config.paddingX, 5, 10, 4);
15567
- const llX = [Math.max(halfBox[0] + (boxSpanOf[0] === -1 ? 0 : 2), leftNoteWidth[0] + 1)];
15698
+ const llX = [Math.max(halfBox[0] + (boxSpanOf[0] === -1 ? 0 : 2), leftNoteWidth[0] + 1, overNoteHalfLeft[0])];
15568
15699
  for (let i = 1; i < diagram.actors.length; i++) {
15569
15700
  const prevSpan = boxSpanOf[i - 1];
15570
15701
  const thisSpan = boxSpanOf[i];
15571
15702
  const walls = prevSpan === thisSpan ? 0 : (prevSpan === -1 ? 0 : 1) + (thisSpan === -1 ? 0 : 1);
15572
15703
  const wallExtra = walls === 0 ? 0 : walls * 2 - 1;
15573
- const noteGapNeed = Math.max(rightNoteWidth[i - 1], leftNoteWidth[i]) + 2;
15704
+ const noteGapNeed = Math.max(rightNoteWidth[i - 1], leftNoteWidth[i], overNoteHalfRight[i - 1], overNoteHalfLeft[i]) + 2;
15574
15705
  const gap = Math.max(halfBox[i - 1] + halfBox[i] + 2 + wallExtra, adjMaxWidth[i - 1] + 2, noteGapNeed, minLifelineGap);
15575
15706
  llX[i] = llX[i - 1] + gap;
15576
15707
  }
@@ -16375,27 +16506,90 @@ function renderClassAscii(text, config, colorMode, theme, options = {}) {
16375
16506
  for (let lv = 0; lv <= maxLevel; lv++) {
16376
16507
  const group = levelGroups[lv];
16377
16508
  if (group.length === 0) continue;
16509
+ const blockKeyOf = (id) => {
16510
+ const pset = parents.get(id);
16511
+ return pset && pset.size > 0 ? [...pset].sort().join(",") : `root:${id}`;
16512
+ };
16513
+ const blockMembers = /* @__PURE__ */ new Map();
16514
+ for (const id of group) {
16515
+ const key = blockKeyOf(id);
16516
+ let members = blockMembers.get(key);
16517
+ if (!members) {
16518
+ members = [];
16519
+ blockMembers.set(key, members);
16520
+ }
16521
+ members.push(id);
16522
+ }
16523
+ const baselineSlotLeft = /* @__PURE__ */ new Map();
16524
+ const slotWidthOf = /* @__PURE__ */ new Map();
16525
+ const leftPadOf = /* @__PURE__ */ new Map();
16526
+ {
16527
+ let x = 0;
16528
+ for (const id of group) {
16529
+ const w = classBoxW.get(id);
16530
+ const reach = columnReach.get(id);
16531
+ const leftPad = Math.max(0, reach.left - Math.floor(w / 2));
16532
+ const rightPad = Math.max(0, reach.right - (w - 1 - Math.floor(w / 2)));
16533
+ baselineSlotLeft.set(id, x);
16534
+ slotWidthOf.set(id, leftPad + w + rightPad);
16535
+ leftPadOf.set(id, leftPad);
16536
+ x += leftPad + w + rightPad + hGap;
16537
+ }
16538
+ }
16378
16539
  let currentX = 0;
16379
16540
  let maxH = 0;
16380
- for (const id of group) {
16541
+ /** Places one class's box at slot-left `slotLeft`, advancing `currentX`/`maxH`/`maxSlotEnd`. */
16542
+ const placeAt = (id, slotLeft) => {
16381
16543
  const cls = classById.get(id);
16382
16544
  const w = classBoxW.get(id);
16383
16545
  const h = classBoxH.get(id);
16384
- const reach = columnReach.get(id);
16385
- const leftPad = Math.max(0, reach.left - Math.floor(w / 2));
16386
- const rightPad = Math.max(0, reach.right - (w - 1 - Math.floor(w / 2)));
16546
+ const leftPad = leftPadOf.get(id);
16547
+ const slotWidth = slotWidthOf.get(id);
16387
16548
  placed.set(id, {
16388
16549
  cls,
16389
16550
  sections: classSections.get(id),
16390
- x: currentX + leftPad,
16551
+ x: slotLeft + leftPad,
16391
16552
  y: currentY,
16392
16553
  width: w,
16393
16554
  height: h
16394
16555
  });
16395
- const slotWidth = leftPad + w + rightPad;
16396
- maxSlotEnd = Math.max(maxSlotEnd, currentX + slotWidth);
16397
- currentX += slotWidth + hGap;
16556
+ maxSlotEnd = Math.max(maxSlotEnd, slotLeft + slotWidth);
16557
+ currentX = slotLeft + slotWidth + hGap;
16398
16558
  maxH = Math.max(maxH, h);
16559
+ };
16560
+ const placedThisLevel = /* @__PURE__ */ new Set();
16561
+ for (const id of group) {
16562
+ if (placedThisLevel.has(id)) continue;
16563
+ const key = blockKeyOf(id);
16564
+ const pset = parents.get(id);
16565
+ const qualifying = pset ? [...pset].filter((pid) => level.get(pid) < lv) : [];
16566
+ if (qualifying.length === 0) {
16567
+ placeAt(id, Math.max(baselineSlotLeft.get(id), currentX, 0));
16568
+ placedThisLevel.add(id);
16569
+ continue;
16570
+ }
16571
+ const members = blockMembers.get(key);
16572
+ const parentCenters = qualifying.map((pid) => {
16573
+ const p = placed.get(pid);
16574
+ return p.x + Math.floor(p.width / 2);
16575
+ });
16576
+ const desiredCenter = Math.round(parentCenters.reduce((sum, c) => sum + c, 0) / parentCenters.length);
16577
+ let totalWidth = 0;
16578
+ for (let i = 0; i < members.length; i++) {
16579
+ totalWidth += slotWidthOf.get(members[i]);
16580
+ if (i < members.length - 1) totalWidth += hGap;
16581
+ }
16582
+ const firstLeftPad = leftPadOf.get(members[0]);
16583
+ const lastMember = members[members.length - 1];
16584
+ const lastRightPad = slotWidthOf.get(lastMember) - leftPadOf.get(lastMember) - classBoxW.get(lastMember);
16585
+ const visibleWidth = totalWidth - firstLeftPad - lastRightPad;
16586
+ const blockDesiredLeft = desiredCenter - firstLeftPad - Math.floor(visibleWidth / 2);
16587
+ let memberX = Math.max(blockDesiredLeft, currentX, 0);
16588
+ for (const memberId of members) {
16589
+ placeAt(memberId, memberX);
16590
+ placedThisLevel.add(memberId);
16591
+ memberX = currentX;
16592
+ }
16399
16593
  }
16400
16594
  currentY += maxH + vGap;
16401
16595
  }