@cardenelabs/cdl 0.124.0 → 0.125.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.
package/dist/index.js CHANGED
@@ -12330,6 +12330,8 @@ function diagram(id, options) {
12330
12330
  ...options.textMetrics !== void 0 ? { textMetrics: options.textMetrics } : {},
12331
12331
  // 種類の名前も同じ経路で渡し、空文字の扱いを `layout` の 1 箇所に揃える
12332
12332
  ...options.type !== void 0 ? { type: options.type } : {},
12333
+ // 空の並びは書かなかった図と同じ形にする。 凡例なしの JSON と配置を変えない
12334
+ ...options.legend && options.legend.length > 0 ? { legend: options.legend.map((item) => ({ ...item })) } : {},
12333
12335
  // inputs / formulas / scrollTriggers / eventBindings は空なら省略 (static diagram の JSON 形状維持)
12334
12336
  ...inputs.length > 0 ? { inputs: [...inputs] } : {},
12335
12337
  ...formulas.length > 0 ? { formulas: [...formulas] } : {},
@@ -12442,7 +12444,7 @@ function drawsTitleOutsideBox(kind) {
12442
12444
  }
12443
12445
  var TITLE_FONT_SIZE_DEFAULT = 18;
12444
12446
  var TITLE_FONT_SIZE = new Map([
12445
- ...["dyn-rect", "dyn-circle", "dyn-arc", "dyn-wave", "dyn-polygon", "station"].map(
12447
+ ...["dyn-rect", "dyn-circle", "dyn-arc", "dyn-wave", "dyn-polygon"].map(
12446
12448
  (k) => [k, 13]
12447
12449
  ),
12448
12450
  ...[
@@ -12487,8 +12489,10 @@ var TITLE_FONT_SIZE = new Map([
12487
12489
  "webhook"
12488
12490
  ].map((k) => [k, 20]),
12489
12491
  ...["card", "event", "function"].map((k) => [k, 24]),
12492
+ ["station", 25],
12490
12493
  ["actor", 32],
12491
- ["storage", 34]
12494
+ ["storage", 34],
12495
+ ["timeline-number", 18]
12492
12496
  ]);
12493
12497
  function titleFontSize(kind) {
12494
12498
  return TITLE_FONT_SIZE.get(kind ?? "") ?? TITLE_FONT_SIZE_DEFAULT;
@@ -12498,9 +12502,17 @@ function skipsNodeTitleWidthCheck(kind) {
12498
12502
  return KINDS_WITH_TITLE_WIDTH_OUTSIDE_NODE.has(kind ?? "");
12499
12503
  }
12500
12504
  var TITLE_BOX_PADDING = 52;
12505
+ var TIMELINE_NUMBER_STROKE = 4;
12506
+ function titleUsableWidth(kind, w, h) {
12507
+ if (kind === "timeline-number") {
12508
+ return Math.min(w, h) - 2 * TIMELINE_NUMBER_STROKE;
12509
+ }
12510
+ return w - (drawsTitleOutsideBox(kind) ? 0 : TITLE_BOX_PADDING);
12511
+ }
12501
12512
  var KINDS_SCALING_TITLE_WHEN_LOW = /* @__PURE__ */ new Set(["actor", "event", "function", "storage"]);
12502
12513
  var KINDS_SCALING_TITLE_ALWAYS = /* @__PURE__ */ new Set([
12503
12514
  "card",
12515
+ "timeline-number",
12504
12516
  "shape-file",
12505
12517
  "shape-folder",
12506
12518
  "shape-cloud",
@@ -12584,14 +12596,16 @@ var KINDS_DRAWING_ONLY_SHAPE = [
12584
12596
  ];
12585
12597
  var KINDS_WITHOUT_EYEBROW_TEXT = /* @__PURE__ */ new Set([
12586
12598
  ...KINDS_DRAWING_ONLY_SHAPE,
12587
- ...KINDS_WITHOUT_TITLE_TEXT
12599
+ ...KINDS_WITHOUT_TITLE_TEXT,
12600
+ "timeline-number"
12588
12601
  ]);
12589
12602
  function rendersEyebrow(kind) {
12590
12603
  return !KINDS_WITHOUT_EYEBROW_TEXT.has(kind ?? "");
12591
12604
  }
12592
12605
  var KINDS_WITHOUT_SUBTITLE_TEXT = /* @__PURE__ */ new Set([
12593
12606
  ...KINDS_DRAWING_OWN_PAYLOAD,
12594
- ...KINDS_WITHOUT_TITLE_TEXT
12607
+ ...KINDS_WITHOUT_TITLE_TEXT,
12608
+ "timeline-number"
12595
12609
  ]);
12596
12610
  function rendersSubtitle(kind) {
12597
12611
  return !KINDS_WITHOUT_SUBTITLE_TEXT.has(kind ?? "");
@@ -14698,6 +14712,14 @@ function curvePath(from, to, edge, labelMarginExtra = 0, textMetrics = "proporti
14698
14712
 
14699
14713
  // src/layout/metro-path.ts
14700
14714
  var METRO_CORNER_RADIUS = 24;
14715
+ var METRO_BRANCH_CORNER_RADIUS = 22;
14716
+ var METRO_BRANCH_STUB = 30;
14717
+ var METRO_DECISION_HEIGHT = 88;
14718
+ var METRO_DECISION_WIDTH = 116;
14719
+ var METRO_RETURN_END_OFFSET = 43;
14720
+ var METRO_RETURN_START_OFFSET = 24;
14721
+ var METRO_LINE_WIDTH = 10;
14722
+ var METRO_LINE_WIDTH_CSS = `var(--cdl-metro-line-width, ${METRO_LINE_WIDTH}px)`;
14701
14723
  var T = Math.SQRT2 - 1;
14702
14724
  function round2(v) {
14703
14725
  return Math.round(v * 100) / 100;
@@ -14715,7 +14737,63 @@ function polyline(points) {
14715
14737
  });
14716
14738
  return distinct.map((p, i) => `${i === 0 ? "M" : "L"} ${point(p)}`).join(" ");
14717
14739
  }
14718
- function metroPath(from, to, edge, labelMarginExtra = 0, textMetrics = "proportional") {
14740
+ function quadraticCornerPath(start, corner, end, radius) {
14741
+ const horizontalDir = Math.sign(corner.x - start.x);
14742
+ const verticalDir = Math.sign(end.y - corner.y);
14743
+ const r = Math.min(
14744
+ radius,
14745
+ Math.abs(corner.x - start.x),
14746
+ Math.abs(end.y - corner.y)
14747
+ );
14748
+ const before = { x: corner.x - horizontalDir * r, y: corner.y };
14749
+ const after = { x: corner.x, y: corner.y + verticalDir * r };
14750
+ return [
14751
+ `M ${point(start)}`,
14752
+ `L ${point(before)}`,
14753
+ `Q ${point(corner)} ${point(after)}`,
14754
+ `L ${point(end)}`
14755
+ ].join(" ");
14756
+ }
14757
+ function decisionBranchPath(start, end) {
14758
+ const horizontalDir = Math.sign(end.x - start.x);
14759
+ const verticalDir = Math.sign(end.y - start.y);
14760
+ const turnX = start.x + horizontalDir * (METRO_BRANCH_STUB + METRO_BRANCH_CORNER_RADIUS);
14761
+ const radius = Math.min(
14762
+ METRO_BRANCH_CORNER_RADIUS,
14763
+ Math.abs(end.y - start.y) / 2,
14764
+ Math.abs(end.x - turnX)
14765
+ );
14766
+ const firstCorner = { x: turnX, y: start.y };
14767
+ const secondCorner = { x: turnX, y: end.y };
14768
+ const beforeFirst = {
14769
+ x: turnX - horizontalDir * radius,
14770
+ y: start.y
14771
+ };
14772
+ const afterFirst = {
14773
+ x: turnX,
14774
+ y: start.y + verticalDir * radius
14775
+ };
14776
+ const beforeSecond = {
14777
+ x: turnX,
14778
+ y: end.y - verticalDir * radius
14779
+ };
14780
+ const afterSecond = {
14781
+ x: turnX + horizontalDir * radius,
14782
+ y: end.y
14783
+ };
14784
+ return {
14785
+ d: [
14786
+ `M ${point(start)}`,
14787
+ `L ${point(beforeFirst)}`,
14788
+ `Q ${point(firstCorner)} ${point(afterFirst)}`,
14789
+ `L ${point(beforeSecond)}`,
14790
+ `Q ${point(secondCorner)} ${point(afterSecond)}`,
14791
+ `L ${point(end)}`
14792
+ ].join(" "),
14793
+ turnX
14794
+ };
14795
+ }
14796
+ function metroPath(from, to, edge, _labelMarginExtra = 0, _textMetrics = "proportional") {
14719
14797
  const fromLeft = from.cx - from.w / 2;
14720
14798
  const fromRight = from.cx + from.w / 2;
14721
14799
  const toLeft = to.cx - to.w / 2;
@@ -14750,11 +14828,45 @@ function metroPath(from, to, edge, labelMarginExtra = 0, textMetrics = "proporti
14750
14828
  const sgn = Math.sign(dy);
14751
14829
  let d;
14752
14830
  let mid;
14753
- let tangent;
14754
- if (Math.abs(dy) < SAME_HEIGHT_THRESHOLD) {
14831
+ let metroReturn = false;
14832
+ if (from.kind === "decision" && Math.abs(dy) >= SAME_HEIGHT_THRESHOLD) {
14833
+ const verticalDir = Math.sign(dy);
14834
+ if (Math.abs(to.cx - from.cx) < SAME_HEIGHT_THRESHOLD) {
14835
+ const start = { x: from.cx, y: from.cy + verticalDir * from.h / 2 };
14836
+ const end = { x: to.cx, y: to.cy };
14837
+ d = polyline([start, end]);
14838
+ fromSide = verticalDir < 0 ? "top" : "bottom";
14839
+ toSide = verticalDir < 0 ? "bottom" : "top";
14840
+ mid = { x: start.x, y: (start.y + end.y) / 2 };
14841
+ } else {
14842
+ const horizontalDir = Math.sign(to.cx - from.cx);
14843
+ const start = {
14844
+ x: from.cx + horizontalDir * from.w / 2,
14845
+ y: from.cy
14846
+ };
14847
+ const end = {
14848
+ x: to.cx - horizontalDir * to.w / 2,
14849
+ y: to.cy
14850
+ };
14851
+ const branch = decisionBranchPath(start, end);
14852
+ d = branch.d;
14853
+ fromSide = horizontalDir > 0 ? "right" : "left";
14854
+ toSide = horizontalDir > 0 ? "left" : "right";
14855
+ mid = { x: branch.turnX, y: (start.y + end.y) / 2 };
14856
+ }
14857
+ } else if (edge?.style === "dashed" && from.kind === "station" && to.kind === "station" && to.cx < from.cx && Math.abs(dy) >= SAME_HEIGHT_THRESHOLD) {
14858
+ const verticalDir = Math.sign(to.cy - from.cy);
14859
+ const start = { x: from.cx - METRO_RETURN_START_OFFSET, y: from.cy };
14860
+ const end = { x: to.cx, y: to.cy - verticalDir * METRO_RETURN_END_OFFSET };
14861
+ const corner = { x: end.x, y: start.y };
14862
+ fromSide = "left";
14863
+ toSide = verticalDir < 0 ? "bottom" : "top";
14864
+ d = quadraticCornerPath(start, corner, end, METRO_BRANCH_CORNER_RADIUS);
14865
+ mid = { x: (start.x + corner.x) / 2, y: start.y };
14866
+ metroReturn = true;
14867
+ } else if (Math.abs(dy) < SAME_HEIGHT_THRESHOLD) {
14755
14868
  d = `M ${point({ x: x1, y: y1 })} L ${point({ x: x2, y: y2 })}`;
14756
14869
  mid = { x: (x1 + x2) / 2, y: (y1 + y2) / 2 };
14757
- tangent = { x: x2 - x1, y: y2 - y1 };
14758
14870
  } else if (fromSide !== toSide && Math.abs(x2 - x1) >= Math.abs(dy)) {
14759
14871
  const horizontal = Math.abs(x2 - x1);
14760
14872
  const s = (horizontal - Math.abs(dy)) / 2;
@@ -14784,7 +14896,6 @@ function metroPath(from, to, edge, labelMarginExtra = 0, textMetrics = "proporti
14784
14896
  d = polyline([p0, p1, p2, p3]);
14785
14897
  }
14786
14898
  mid = { x: (x1 + x2) / 2, y: (y1 + y2) / 2 };
14787
- tangent = w;
14788
14899
  } else {
14789
14900
  fromSide = "right";
14790
14901
  toSide = "right";
@@ -14814,33 +14925,32 @@ function metroPath(from, to, edge, labelMarginExtra = 0, textMetrics = "proporti
14814
14925
  `L ${point(p4)}`
14815
14926
  ].join(" ");
14816
14927
  mid = { x: p2.x - T * r, y: p2.y };
14817
- tangent = { x: 0, y: sgn };
14818
- }
14819
- if (edge?.overlay) {
14820
- return { d, fromSide, toSide, labelX: mid.x, labelY: mid.y, labelAnchor: "middle" };
14821
- }
14822
- const tangentLength = Math.hypot(tangent.x, tangent.y);
14823
- let normalX = tangent.y / tangentLength;
14824
- let normalY = -tangent.x / tangentLength;
14825
- if (normalY > Number.EPSILON || Math.abs(normalY) <= Number.EPSILON && normalX < 0) {
14826
- normalX *= -1;
14827
- normalY *= -1;
14828
14928
  }
14829
- const labelW = computeLabelBoxW(edge?.label, edge?.sub, textMetrics);
14830
- const labelH = labelPillH(edge?.sub);
14831
- const distance = labelPathClearanceHorizontal(edge) + (Math.abs(normalX) * labelW + Math.abs(normalY) * labelH) / 2 + labelMarginExtra;
14832
14929
  return {
14833
14930
  d,
14834
14931
  fromSide,
14835
14932
  toSide,
14836
- labelX: mid.x + normalX * distance,
14837
- labelY: mid.y + normalY * distance,
14838
- labelAnchor: "middle"
14933
+ labelX: mid.x,
14934
+ labelY: mid.y,
14935
+ labelAnchor: "middle",
14936
+ ...metroReturn ? { metroReturn: true } : {}
14839
14937
  };
14840
14938
  }
14841
14939
 
14842
14940
  // src/types.ts
14843
14941
  var TONES = ["accent", "teal", "success", "error", "warning", "info"];
14942
+ var LEGEND_MARKS = [
14943
+ "diamond",
14944
+ "filled-circle",
14945
+ "double-circle",
14946
+ "dotted-line",
14947
+ "solid-line",
14948
+ "curved-line",
14949
+ "rounded-label",
14950
+ "numbered-circle",
14951
+ "station",
14952
+ "arrow"
14953
+ ];
14844
14954
  var NODE_KINDS = [
14845
14955
  // dynamic shape (CAR: shape primitive) ... signal 値で内部 fill / radius / angle 等が
14846
14956
  // リアルタイム変化する汎用 container、 shape spec は node.shape に格納
@@ -14962,12 +15072,14 @@ var NODE_KINDS = [
14962
15072
  "shape-lawyer",
14963
15073
  "shape-trader",
14964
15074
  "shape-customer-service",
14965
- // 流れの始まりと終わりの印 2 種 (#560) ... 塗った丸と輪で囲んだ丸。
14966
- // 状態遷移図が使う。 印そのものが意味を持つので文字は描かない
15075
+ // 流れの始まりと終わりの印 2 種 (#560) ... 始点と終点は線の端から推測せず、著者が
15076
+ // `kind: "mark-start"` / `kind: "mark-end"` の node を明示する。印は文字を描かない
14967
15077
  "mark-start",
14968
15078
  "mark-end",
14969
15079
  // 路線図の駅 (#996) ... 線路上の点を小さな丸で示し、名前と説明は丸の外に置く
14970
15080
  "station",
15081
+ // 時間軸の番号 (#1017) ... 実線の軸上に番号を入れた丸を置く。路線図の駅とは別の意味を持つ。
15082
+ "timeline-number",
14971
15083
  // 時系列のやり取り (#578) ... 1 つの箱が図を丸ごと描く。 縦が時間で、参加者は箱を持たない
14972
15084
  "sequence-board"
14973
15085
  ];
@@ -17701,7 +17813,7 @@ function runAxes(laid, diag, violations, counts, push, profile, skippedAxes, \u5
17701
17813
  const LIFELINE_W_THRESHOLD = 8;
17702
17814
  for (const n of laid.nodes) {
17703
17815
  if (n.w < LIFELINE_W_THRESHOLD) continue;
17704
- if (n.kind === "station") continue;
17816
+ if (n.kind === "station" || n.kind === "timeline-number") continue;
17705
17817
  if (n.w < MIN_NODE_W || n.h < MIN_NODE_H) {
17706
17818
  push(
17707
17819
  "node-visibility",
@@ -17727,8 +17839,7 @@ function runAxes(laid, diag, violations, counts, push, profile, skippedAxes, \u5
17727
17839
  if (!isRenderedNode(n)) continue;
17728
17840
  const titleTrimmed = n.title?.trim() ?? "";
17729
17841
  if (!titleTrimmed) continue;
17730
- const \u4F59\u767D = drawsTitleOutsideBox(n.kind) ? 0 : TITLE_BOX_PADDING;
17731
- const \u4F7F\u3048\u308B\u5E452 = n.w - \u4F59\u767D;
17842
+ const \u4F7F\u3048\u308B\u5E452 = titleUsableWidth(n.kind, n.w, n.h);
17732
17843
  const \u5B9F\u5E45 = measureTextWidth(titleTrimmed, {
17733
17844
  fontSize: titleFontSize(n.kind),
17734
17845
  bound: "drawn",
@@ -17747,7 +17858,7 @@ function runAxes(laid, diag, violations, counts, push, profile, skippedAxes, \u5
17747
17858
  push(
17748
17859
  "text-readability",
17749
17860
  // 字ごとの実幅は小数になるので、読む人に出す時だけ丸める (判定は丸めない)
17750
- `node "${n.id}" title "${n.title}" \u304C node \u5E45 ${n.w}px \u3092\u8D85\u904E\u3059\u308B\u53EF\u80FD\u6027 (\u671F\u5F85 ${Math.round(\u5B9F\u5E45 + \u4F59\u767D)}px)`,
17861
+ `node "${n.id}" title "${n.title}" \u304C node \u5E45 ${n.w}px \u3092\u8D85\u904E\u3059\u308B\u53EF\u80FD\u6027 (\u671F\u5F85 ${Math.round(\u5B9F\u5E45 + (n.w - \u4F7F\u3048\u308B\u5E452))}px)`,
17751
17862
  "warn"
17752
17863
  );
17753
17864
  }
@@ -19561,6 +19672,8 @@ var TOKENS = {
19561
19672
  lane_label_h: 40,
19562
19673
  lane_top_pad: 100,
19563
19674
  contain_pad: 28,
19675
+ stage_column_gap: 48,
19676
+ stage_card_text_inset: 18,
19564
19677
  // node sizes (kind ごと、 unified row h は engine が max() で算出)
19565
19678
  node: {
19566
19679
  actor: { w: 340, h: 165 },
@@ -19687,7 +19800,10 @@ var TOKENS = {
19687
19800
  "mark-start": { w: 96, h: 96 },
19688
19801
  "mark-end": { w: 96, h: 96 },
19689
19802
  // 路線図の駅 (#996) ... 正方形にして小さな丸が縦横どちらにも歪まないようにする。
19690
- station: { w: 24, h: 24 },
19803
+ // 半径 16 と縁 6 の外径を、線路との接点を含めて収める。
19804
+ station: { w: 38, h: 38 },
19805
+ // 時間軸の番号 (#1017) ... 半径 24 の丸と太さ 4 の縁を収める。
19806
+ "timeline-number": { w: 52, h: 52 },
19691
19807
  // 時系列のやり取り (#578)。 実寸は中身から決まる (`sequenceBoardMetrics`) ので、
19692
19808
  // ここは書かなかった時の受け皿
19693
19809
  "sequence-board": { w: 960, h: 640 }
@@ -19702,6 +19818,8 @@ var LANE_TOP_PAD = TOKENS.lane_top_pad;
19702
19818
  var STACK_GAP = TOKENS.row_gap;
19703
19819
  var NODE_SIZE = TOKENS.node;
19704
19820
  var CONTAIN_PAD = TOKENS.contain_pad;
19821
+ var STAGE_COLUMN_GAP = TOKENS.stage_column_gap;
19822
+ var STAGE_CARD_TEXT_INSET = TOKENS.stage_card_text_inset;
19705
19823
  var VIEW_PAD = TOKENS.view_pad;
19706
19824
  var HEADER_RESERVE = TOKENS.header_reserve;
19707
19825
  var DEFAULT_LANE_GAP = 80;
@@ -19802,12 +19920,24 @@ function layoutEdges(diag, nodes, lanes = [], textMetrics = "proportional") {
19802
19920
  if (!from) throw new Error(`cdl layout: edge "${e.id}" \u306E from "${e.from}" \u304C node \u306B\u7121\u3044`);
19803
19921
  if (!to) throw new Error(`cdl layout: edge "${e.id}" \u306E to "${e.to}" \u304C node \u306B\u7121\u3044`);
19804
19922
  const c = curvePath(from, to, e, labelMarginExtra, textMetrics);
19923
+ const fromLane = lmap.get(from.lane);
19924
+ const toLane = lmap.get(to.lane);
19925
+ let labelX = c.labelX;
19926
+ if (fromLane?.stage !== void 0 && toLane?.stage !== void 0 && fromLane.id !== toLane.id && (e.label || e.sub)) {
19927
+ const [leftLane, rightLane] = fromLane.x <= toLane.x ? [fromLane, toLane] : [toLane, fromLane];
19928
+ const gapLeft = leftLane.x + leftLane.width - STAGE_COLUMN_GAP / 2;
19929
+ const gapRight = rightLane.x + STAGE_COLUMN_GAP / 2;
19930
+ const labelHalfWidth = computeLabelBoxW(e.label, e.sub, textMetrics) / 2;
19931
+ const minX = gapLeft + labelHalfWidth;
19932
+ const maxX = gapRight - labelHalfWidth;
19933
+ if (minX <= maxX) labelX = Math.min(maxX, Math.max(minX, labelX));
19934
+ }
19805
19935
  positionedEdges.push({
19806
19936
  ...e,
19807
19937
  d: c.d,
19808
19938
  fromSide: c.fromSide,
19809
19939
  toSide: c.toSide,
19810
- labelX: c.labelX + (e.labelOffsetX ?? 0),
19940
+ labelX: labelX + (e.labelOffsetX ?? 0),
19811
19941
  labelY: c.labelY + (e.labelOffsetY ?? 0),
19812
19942
  labelAnchor: c.labelAnchor
19813
19943
  });
@@ -19824,7 +19954,8 @@ function layoutEdges(diag, nodes, lanes = [], textMetrics = "proportional") {
19824
19954
  toSide: m.toSide,
19825
19955
  labelX: m.labelX + (e.labelOffsetX ?? 0),
19826
19956
  labelY: m.labelY + (e.labelOffsetY ?? 0),
19827
- labelAnchor: m.labelAnchor
19957
+ labelAnchor: m.labelAnchor,
19958
+ ...m.metroReturn ? { metroReturn: true } : {}
19828
19959
  });
19829
19960
  } else {
19830
19961
  autoEdges.push(e);
@@ -20516,20 +20647,22 @@ function countPathObstacleCrossings(d, obstacles) {
20516
20647
  const segments = flattenPathSegments(extractPathSegments(d));
20517
20648
  return obstacles.filter((obstacle) => segmentsCrossRect(segments, nodeRect(obstacle))).length;
20518
20649
  }
20519
- function makeBackDetourCandidate(A, B, from, to, obstacles, detourY, side) {
20650
+ function makeBackDetourCandidate(A, B, from, to, obstacles, detourY, side, columnXOverride) {
20520
20651
  const lowIsA = A.y >= B.y;
20521
20652
  const low = lowIsA ? A : B;
20522
20653
  const lowBendY = low.y - VERTICAL_DETOUR_EXIT;
20523
20654
  const bandTop = Math.min(detourY, lowBendY);
20524
20655
  const bandBottom = Math.max(detourY, lowBendY);
20525
20656
  const bandObstacles = obstacles.map(nodeRect).filter((rect) => rect.y + rect.h >= bandTop && rect.y <= bandBottom);
20526
- let columnX = detourColumnFor([from, to], side);
20527
- while (true) {
20528
- const blockers = bandObstacles.filter(
20529
- (rect) => rect.x - VERTICAL_DETOUR_GAP < columnX && columnX < rect.x + rect.w + VERTICAL_DETOUR_GAP
20530
- );
20531
- if (blockers.length === 0) break;
20532
- columnX = side === "left" ? Math.min(...blockers.map((rect) => rect.x - VERTICAL_DETOUR_GAP)) : Math.max(...blockers.map((rect) => rect.x + rect.w + VERTICAL_DETOUR_GAP));
20657
+ let columnX = columnXOverride ?? detourColumnFor([from, to], side);
20658
+ if (columnXOverride === void 0) {
20659
+ while (true) {
20660
+ const blockers = bandObstacles.filter(
20661
+ (rect) => rect.x - VERTICAL_DETOUR_GAP < columnX && columnX < rect.x + rect.w + VERTICAL_DETOUR_GAP
20662
+ );
20663
+ if (blockers.length === 0) break;
20664
+ columnX = side === "left" ? Math.min(...blockers.map((rect) => rect.x - VERTICAL_DETOUR_GAP)) : Math.max(...blockers.map((rect) => rect.x + rect.w + VERTICAL_DETOUR_GAP));
20665
+ }
20533
20666
  }
20534
20667
  const bendAY = lowIsA ? lowBendY : detourY;
20535
20668
  const bendBY = lowIsA ? detourY : lowBendY;
@@ -20638,7 +20771,7 @@ function sidePoint(n, side) {
20638
20771
  return { x: n.cx + n.w / 2, y: n.cy };
20639
20772
  }
20640
20773
  }
20641
- function routePath(from, to, fromSide, toSide, obstacles = [], detourSide, _fromLane, _toLane, edge, labelObstacles = obstacles, fromOffset = { x: 0, y: 0 }, toOffset = { x: 0, y: 0 }, avoidRects = [], textMetrics = "proportional") {
20774
+ function routePath(from, to, fromSide, toSide, obstacles = [], detourSide, fromLane, toLane, edge, labelObstacles = obstacles, fromOffset = { x: 0, y: 0 }, toOffset = { x: 0, y: 0 }, avoidRects = [], textMetrics = "proportional") {
20642
20775
  const labelBoxW = edge ? computeLabelBoxW(edge.label, edge.sub, textMetrics) : 0;
20643
20776
  const labelHalfW = labelBoxW / 2;
20644
20777
  const labelInit = labelInitClearance(edge);
@@ -21008,17 +21141,27 @@ function routePath(from, to, fromSide, toSide, obstacles = [], detourSide, _from
21008
21141
  };
21009
21142
  }
21010
21143
  if (topTopPair) {
21011
- const detourY = Math.min(A.y, B.y) - 100;
21144
+ const stagePoint = (point2, node, lane) => lane?.stage === void 0 ? point2 : {
21145
+ x: Math.min(node.cx + node.w / 2 - 24, lane.x + lane.width - 24),
21146
+ y: point2.y
21147
+ };
21148
+ const backA = stagePoint(A, from, fromLane);
21149
+ const backB = stagePoint(B, to, toLane);
21150
+ const stageTops = [fromLane, toLane].filter((lane) => lane?.stage !== void 0).map((lane) => lane.y);
21151
+ const detourY = Math.min(
21152
+ Math.min(backA.y, backB.y) - 100,
21153
+ ...stageTops.map((top) => top - VERTICAL_DETOUR_GAP)
21154
+ );
21012
21155
  const corner = 14;
21013
- const xDir = B.x > A.x ? 1 : -1;
21156
+ const xDir = backB.x > backA.x ? 1 : -1;
21014
21157
  const existing = {
21015
- d: `M ${A.x} ${A.y}
21016
- L ${A.x} ${detourY + corner}
21017
- Q ${A.x} ${detourY}, ${A.x + xDir * corner} ${detourY}
21018
- L ${B.x - xDir * corner} ${detourY}
21019
- Q ${B.x} ${detourY}, ${B.x} ${detourY + corner}
21020
- L ${B.x} ${B.y}`,
21021
- labelX: (A.x + B.x) / 2,
21158
+ d: `M ${backA.x} ${backA.y}
21159
+ L ${backA.x} ${detourY + corner}
21160
+ Q ${backA.x} ${detourY}, ${backA.x + xDir * corner} ${detourY}
21161
+ L ${backB.x - xDir * corner} ${detourY}
21162
+ Q ${backB.x} ${detourY}, ${backB.x} ${detourY + corner}
21163
+ L ${backB.x} ${backB.y}`,
21164
+ labelX: (backA.x + backB.x) / 2,
21022
21165
  // CAR-468 SSOT ... 旧 detourY - 30 magic number を LABEL_INIT_CLEARANCE (26、 CAR-570 で 34 → 26 縮小) に置換。
21023
21166
  // CAR-518 SSOT ... dotted-flow は 62 world (marker glow radius 分拡張) を labelInit で参照。
21024
21167
  // top-top detour の水平 segment 上方 labelInit world = spec 準拠距離。
@@ -21027,7 +21170,16 @@ function routePath(from, to, fromSide, toSide, obstacles = [], detourSide, _from
21027
21170
  };
21028
21171
  const existingCrossings = countPathObstacleCrossings(existing.d, obstacles);
21029
21172
  if (existingCrossings === 0) return existing;
21030
- const candidate = chooseBackDetourCandidate(A, B, from, to, obstacles, detourY);
21173
+ const candidate = from.lane === to.lane && fromLane?.stage !== void 0 ? makeBackDetourCandidate(
21174
+ backA,
21175
+ backB,
21176
+ from,
21177
+ to,
21178
+ obstacles,
21179
+ detourY,
21180
+ "right",
21181
+ fromLane.x + fromLane.width
21182
+ ) : chooseBackDetourCandidate(backA, backB, from, to, obstacles, detourY);
21031
21183
  if (candidate.crossings >= existingCrossings) return existing;
21032
21184
  return {
21033
21185
  d: candidate.d,
@@ -21961,12 +22113,56 @@ function \u76F4\u524D\u306E\u6298\u308C\u3054\u3068\u305A\u3089\u3059(segs, \u6A
21961
22113
  return parts.join(" ");
21962
22114
  }
21963
22115
 
22116
+ // src/layout/legend.ts
22117
+ var LEGEND_FONT_SIZE = 17;
22118
+ var LEGEND_MARK_SCALE = LEGEND_FONT_SIZE / 21;
22119
+ var LEGEND_MARK_REFERENCE_WIDTH = 46;
22120
+ var LEGEND_MARK_WIDTH = LEGEND_MARK_REFERENCE_WIDTH * LEGEND_MARK_SCALE;
22121
+ var LEGEND_MARK_TEXT_GAP = 12 * LEGEND_MARK_SCALE;
22122
+ var LEGEND_ITEM_GAP_RATIO = 0.07;
22123
+ var LEGEND_BASELINE_GAP = 32;
22124
+ var LEGEND_BOTTOM_EXTENT = 10;
22125
+ function layoutLegend(items, viewBox, textMetrics, nodes) {
22126
+ const renderedNodes = nodes.filter(isRenderedNode);
22127
+ const startX = renderedNodes.length > 0 ? Math.min(...renderedNodes.map((node) => node.cx - node.w / 2)) : viewBox.x + VIEW_PAD;
22128
+ const diagramRight = renderedNodes.length > 0 ? Math.max(...renderedNodes.map((node) => node.cx + node.w / 2)) : viewBox.x + viewBox.w - VIEW_PAD;
22129
+ const itemGap = (diagramRight - startX) * LEGEND_ITEM_GAP_RATIO;
22130
+ const y = viewBox.y + viewBox.h - VIEW_PAD + LEGEND_BASELINE_GAP;
22131
+ let x = startX;
22132
+ const legend = items.map((item) => {
22133
+ const laid = { ...item, x, y };
22134
+ x += LEGEND_MARK_WIDTH + LEGEND_MARK_TEXT_GAP + textWidth(item.text, LEGEND_FONT_SIZE, 0, textMetrics) + itemGap;
22135
+ return laid;
22136
+ });
22137
+ const right = x - itemGap;
22138
+ const bottom = y + LEGEND_BOTTOM_EXTENT + VIEW_PAD;
22139
+ return {
22140
+ legend,
22141
+ viewBox: {
22142
+ ...viewBox,
22143
+ w: Math.max(viewBox.w, right + VIEW_PAD - viewBox.x),
22144
+ h: Math.max(viewBox.h, bottom - viewBox.y)
22145
+ }
22146
+ };
22147
+ }
22148
+
21964
22149
  // src/layout/lanes.ts
21965
22150
  var LANE_POSITION_EPSILON = 1e-9;
22151
+ function equalizeStageColumns(lanes, nodes = []) {
22152
+ const stages = lanes.filter((lane) => lane.stage !== void 0);
22153
+ if (stages.length === 0) return lanes;
22154
+ const top = Math.min(...stages.map((lane) => lane.y));
22155
+ const stageIds = new Set(stages.map((lane) => lane.id));
22156
+ const nodeBottoms = nodes.filter((node) => stageIds.has(node.lane)).map((node) => node.cy + node.h / 2 + CONTAIN_PAD);
22157
+ const bottom = Math.max(...stages.map((lane) => lane.y + lane.height), ...nodeBottoms);
22158
+ return lanes.map(
22159
+ (lane) => lane.stage === void 0 ? lane : { ...lane, y: top, height: bottom - top }
22160
+ );
22161
+ }
21966
22162
  function layoutLanes(diag) {
21967
22163
  const laneGap = diag.viewport?.laneGap ?? diag.viewport?.gap ?? DEFAULT_LANE_GAP;
21968
22164
  let cursorX = 0;
21969
- return diag.lanes.map((lane) => {
22165
+ const lanes = diag.lanes.map((lane, index) => {
21970
22166
  if (lane.posX !== void 0 && lane.posY !== void 0) {
21971
22167
  return {
21972
22168
  ...lane,
@@ -21977,7 +22173,9 @@ function layoutLanes(diag) {
21977
22173
  };
21978
22174
  }
21979
22175
  const resolvedX = lane.x ?? cursorX;
21980
- cursorX = resolvedX + lane.width + laneGap;
22176
+ const nextLane = diag.lanes[index + 1];
22177
+ const gapAfter = lane.stage !== void 0 && nextLane?.stage !== void 0 ? 0 : laneGap;
22178
+ cursorX = resolvedX + lane.width + gapAfter;
21981
22179
  return {
21982
22180
  ...lane,
21983
22181
  x: resolvedX,
@@ -21985,6 +22183,7 @@ function layoutLanes(diag) {
21985
22183
  height: LANE_LABEL_H
21986
22184
  };
21987
22185
  });
22186
+ return equalizeStageColumns(lanes);
21988
22187
  }
21989
22188
  function expandLanesForNodes(lanes, nodes) {
21990
22189
  const targetNodeGap = targetNodeNodeClearance();
@@ -22013,7 +22212,8 @@ function expandLanesForNodes(lanes, nodes) {
22013
22212
  if (lane.posX !== void 0 && lane.posY !== void 0) continue;
22014
22213
  const inside = nodes.filter((n) => n.lane === lane.id);
22015
22214
  const maxNodeW = inside.length > 0 ? Math.max(...inside.map((n) => n.w)) : 0;
22016
- const newWidth = Math.max(lane.width, maxNodeW + padFor(lane) * 2);
22215
+ const stageMinWidth = lane.stage === void 0 ? 0 : maxNodeW + CONTAIN_PAD + STAGE_COLUMN_GAP;
22216
+ const newWidth = Math.max(lane.width, maxNodeW + padFor(lane) * 2, stageMinWidth);
22017
22217
  widthOf.set(lane.id, newWidth);
22018
22218
  deltaOf.set(lane.id, newWidth - lane.width);
22019
22219
  }
@@ -22038,6 +22238,34 @@ function expandLaneGapsForEdgeLabels(lanes, nodes, edges, textMetrics = "proport
22038
22238
  if (lanes.length < 2) return lanes;
22039
22239
  const layoutedLanes = lanes.filter((l) => !(l.posX !== void 0 && l.posY !== void 0));
22040
22240
  if (layoutedLanes.length < 2) return lanes;
22241
+ if (layoutedLanes.length === lanes.length && layoutedLanes.every((lane) => lane.stage !== void 0)) {
22242
+ const sortedStages = [...layoutedLanes].sort((a, b) => a.x - b.x);
22243
+ const stageIndexById = new Map(sortedStages.map((lane, index) => [lane.id, index]));
22244
+ const nodeLaneById = new Map(nodes.map((node) => [node.id, node.lane]));
22245
+ const requiredSurfaceGaps = new Array(sortedStages.length - 1).fill(STAGE_COLUMN_GAP);
22246
+ for (const edge of edges) {
22247
+ if (edge.routing !== "curve" || !edge.label && !edge.sub) continue;
22248
+ const fromIndex = stageIndexById.get(nodeLaneById.get(edge.from) ?? "");
22249
+ const toIndex = stageIndexById.get(nodeLaneById.get(edge.to) ?? "");
22250
+ if (fromIndex === void 0 || toIndex === void 0 || fromIndex === toIndex) continue;
22251
+ const lo = Math.min(fromIndex, toIndex);
22252
+ const hi = Math.max(fromIndex, toIndex);
22253
+ const labelWidth = computeLabelBoxW(edge.label, edge.sub, textMetrics);
22254
+ for (let pair = lo; pair < hi; pair++) {
22255
+ requiredSurfaceGaps[pair] = Math.max(requiredSurfaceGaps[pair], labelWidth);
22256
+ }
22257
+ }
22258
+ const shiftedXById2 = /* @__PURE__ */ new Map([[sortedStages[0].id, sortedStages[0].x]]);
22259
+ for (let index = 1; index < sortedStages.length; index++) {
22260
+ const previous = sortedStages[index - 1];
22261
+ const lane = sortedStages[index];
22262
+ const previousX = shiftedXById2.get(previous.id);
22263
+ const authoredGap = Math.max(0, lane.x - (previous.x + previous.width));
22264
+ const laneGap = Math.max(authoredGap, requiredSurfaceGaps[index - 1] - STAGE_COLUMN_GAP);
22265
+ shiftedXById2.set(lane.id, previousX + previous.width + laneGap);
22266
+ }
22267
+ return lanes.map((lane) => ({ ...lane, x: shiftedXById2.get(lane.id) ?? lane.x }));
22268
+ }
22041
22269
  const nodeById = new Map(nodes.map((n) => [n.id, n]));
22042
22270
  const sortedByX = [...layoutedLanes].sort((a, b) => a.x - b.x);
22043
22271
  const laneIndexById = new Map(sortedByX.map((lane, i) => [lane.id, i]));
@@ -22109,7 +22337,7 @@ function recomputeContainedLanes(lanes, nodes) {
22109
22337
  lifelineBottom = Math.max(lifelineBottom, bottom);
22110
22338
  }
22111
22339
  }
22112
- return lanes.map((lane) => {
22340
+ const contained = lanes.map((lane) => {
22113
22341
  if (lane.lifeline) {
22114
22342
  return {
22115
22343
  ...lane,
@@ -22127,6 +22355,7 @@ function recomputeContainedLanes(lanes, nodes) {
22127
22355
  height: bottom - top + CONTAIN_PAD * 2 + LANE_LABEL_H
22128
22356
  };
22129
22357
  });
22358
+ return equalizeStageColumns(contained, nodes);
22130
22359
  }
22131
22360
 
22132
22361
  // src/layout/head-label.ts
@@ -22276,11 +22505,15 @@ function requiredStackGapForEdgeLabels(diag, allStacks) {
22276
22505
  }
22277
22506
  function layoutNodes(diag, lanes, textMetrics) {
22278
22507
  const laneMap = new Map(lanes.map((l) => [l.id, l]));
22508
+ const metroDecisionIds = new Set(
22509
+ diag.edges.filter((edge) => edge.routing === "metro").flatMap((edge) => [edge.from, edge.to]).filter((id) => diag.nodes.some((node) => node.id === id && node.kind === "decision"))
22510
+ );
22511
+ const nodeSize = (node) => metroDecisionIds.has(node.id) ? { w: METRO_DECISION_WIDTH, h: METRO_DECISION_HEIGHT } : NODE_SIZE[node.kind];
22279
22512
  const allStacks = Array.from(new Set(diag.nodes.map((n) => n.stack))).sort((a, b) => a - b);
22280
22513
  const rowH = /* @__PURE__ */ new Map();
22281
22514
  for (const stack of allStacks) {
22282
22515
  const nodesAtStack = diag.nodes.filter((n) => n.stack === stack);
22283
- const h = Math.max(...nodesAtStack.map((n) => \u7BB1\u306E\u9AD8\u30552(n, NODE_SIZE[n.kind].h)));
22516
+ const h = Math.max(...nodesAtStack.map((n) => \u7BB1\u306E\u9AD8\u30552(n, nodeSize(n).h)));
22284
22517
  rowH.set(stack, h);
22285
22518
  }
22286
22519
  const rowCy = /* @__PURE__ */ new Map();
@@ -22316,7 +22549,7 @@ function layoutNodes(diag, lanes, textMetrics) {
22316
22549
  if (!lane) {
22317
22550
  throw new Error(`cdl layout: node "${n.id}" \u306E lane "${n.lane}" \u304C\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u306A\u3044`);
22318
22551
  }
22319
- const size = NODE_SIZE[n.kind];
22552
+ const size = nodeSize(n);
22320
22553
  if (n.posX !== void 0 && n.posY !== void 0) {
22321
22554
  const w2 = n.posW ?? n.w ?? autoRowsWidth(n, size.w, textMetrics);
22322
22555
  const h2 = n.posH ?? n.h ?? rowH.get(n.stack) ?? size.h;
@@ -22439,6 +22672,9 @@ function dropUnsafeLaneDx(dxByLane, nodes) {
22439
22672
  function applyRowLevelNodeShifts(nodes, lanes, nodeShifts) {
22440
22673
  const dyByStack = aggregateRowDy(nodes, nodeShifts);
22441
22674
  const dxByLane = dropUnsafeLaneDx(aggregateLaneDx(nodes, nodeShifts), nodes);
22675
+ for (const lane of lanes) {
22676
+ if (lane.stage !== void 0) dxByLane.delete(lane.id);
22677
+ }
22442
22678
  let moved = false;
22443
22679
  const outNodes = nodes.map((n) => {
22444
22680
  const dx = dxByLane.get(n.lane) ?? 0;
@@ -22558,7 +22794,15 @@ function layout(diag) {
22558
22794
  );
22559
22795
  }
22560
22796
  }
22561
- const viewBox = computeViewBox(containedLanesFinal, nodesFinal, edges, bboxes, diag.viewport);
22797
+ const contentViewBox = computeViewBox(
22798
+ containedLanesFinal,
22799
+ nodesFinal,
22800
+ edges,
22801
+ bboxes,
22802
+ diag.viewport
22803
+ );
22804
+ const legendLayout = diag.legend?.length ? layoutLegend(diag.legend, contentViewBox, textMetrics, nodesFinal) : void 0;
22805
+ const viewBox = legendLayout?.viewBox ?? contentViewBox;
22562
22806
  return {
22563
22807
  id: diag.id,
22564
22808
  topic: diag.topic,
@@ -22585,6 +22829,7 @@ function layout(diag) {
22585
22829
  ...textMetrics === "monospace" ? { textMetrics: "monospace" } : {},
22586
22830
  // 種類も配色と同じく空の目印を出さない。 遮断をここに揃えると手書きの図にも効く
22587
22831
  ...diag.type ? { type: diag.type } : {},
22832
+ ...legendLayout ? { legend: legendLayout.legend } : {},
22588
22833
  bboxes,
22589
22834
  collisions,
22590
22835
  nearCollisions,
@@ -22773,6 +23018,11 @@ function validate(diag) {
22773
23018
  \u2192 \u4FEE\u6B63\u4F8B: .lane("${n.lane}", { x: 0, width: 400 }) \u3092 node \u5BA3\u8A00\u524D\u306B\u8FFD\u52A0`
22774
23019
  );
22775
23020
  }
23021
+ if (n.leaderTo !== void 0 && !nodeIds.has(n.leaderTo)) {
23022
+ errors.push(
23023
+ `[cdl] unknown-ref: node "${n.id}" \u306E leaderTo "${n.leaderTo}" \u304C node \u306B\u5B58\u5728\u3057\u307E\u305B\u3093${suggest(n.leaderTo, nodeIds, "node")}`
23024
+ );
23025
+ }
22776
23026
  }
22777
23027
  for (const e of diag.edges) {
22778
23028
  if (!nodeIds.has(e.from)) {
@@ -24608,6 +24858,10 @@ function uniformLifelineEndY(lanes, nodes, starts = lifelineStartYs(nodes)) {
24608
24858
 
24609
24859
  // src/render/edge-head.ts
24610
24860
  var EDGE_HEAD_MUTED_MARKER_ID = "cdl-arrow-muted";
24861
+ var METRO_RETURN_ARROW_D = "M 21 0 L 4 9.5 L 4 -9.5 Z";
24862
+ function metroReturnArrowMarkerId(tone) {
24863
+ return `cdl-metro-return-arrow-${tone}`;
24864
+ }
24611
24865
  var EDGE_HEAD_SHAPE = {
24612
24866
  // 何も描かない端。 marker 自体を出さないので `d` は読まれない
24613
24867
  none: { d: "", w: 10, h: 10, refX: 0, filled: false },
@@ -24662,6 +24916,7 @@ function \u7B49\u5E45\u306E\u5B57(text, fontSize) {
24662
24916
  });
24663
24917
  return out;
24664
24918
  }
24919
+ var METRO_PENDING_ATTRIBUTE = "data-cdl-pending";
24665
24920
  function defaultEdgeWidth(active) {
24666
24921
  return active ? 2.5 : 1.25;
24667
24922
  }
@@ -24680,12 +24935,14 @@ function CdlEdgeView({
24680
24935
  drawing,
24681
24936
  progress,
24682
24937
  stateValues,
24683
- flowing = false
24938
+ flowing = false,
24939
+ pending = false
24684
24940
  }) {
24685
24941
  const color = edgeColor(edge);
24686
- const marker = edgeHeadMarkerName(edge.tone, edge.head, !active, edge.headFill);
24687
- const tailMarker = edge.tailHead === void 0 ? void 0 : edgeHeadMarkerName(edge.tone, edge.tailHead, !active, edge.tailHeadFill);
24688
24942
  const style = edge.style ?? "solid";
24943
+ const isMetroReturn = edge.metroReturn === true;
24944
+ const marker = isMetroReturn ? metroReturnArrowMarkerId(edge.tone) : edgeHeadMarkerName(edge.tone, edge.head, !active, edge.headFill);
24945
+ const tailMarker = edge.tailHead === void 0 ? void 0 : edgeHeadMarkerName(edge.tone, edge.tailHead, !active, edge.tailHeadFill);
24689
24946
  const isDotted = style === "dotted-flow";
24690
24947
  const isPassThrough = isDotted && Boolean(edge.dThrough);
24691
24948
  const lineDotsD = isDotted ? shrinkPathEnd(edge.d, 12) : edge.d;
@@ -24695,6 +24952,7 @@ function CdlEdgeView({
24695
24952
  return /* @__PURE__ */ jsxs(
24696
24953
  "g",
24697
24954
  {
24955
+ ...pending ? { [METRO_PENDING_ATTRIBUTE]: "true" } : {},
24698
24956
  "data-cdl-edge": edge.id,
24699
24957
  "data-cdl-routing": edge.routing === "metro" ? "metro" : void 0,
24700
24958
  "data-cdl-from": edge.from,
@@ -24720,11 +24978,12 @@ function CdlEdgeView({
24720
24978
  fill: "none",
24721
24979
  stroke: color,
24722
24980
  strokeOpacity: active ? 0.95 : 0.9,
24723
- strokeWidth: active ? 5 : 4,
24981
+ strokeWidth: edge.routing === "metro" ? METRO_LINE_WIDTH : active ? 5 : 4,
24724
24982
  strokeDasharray: "0 18",
24725
24983
  strokeLinecap: "round",
24726
24984
  style: {
24727
- transition: "stroke 280ms ease, stroke-opacity 280ms ease, stroke-width 280ms ease"
24985
+ transition: "stroke 280ms ease, stroke-opacity 280ms ease, stroke-width 280ms ease",
24986
+ strokeWidth: edge.routing === "metro" ? METRO_LINE_WIDTH_CSS : void 0
24728
24987
  }
24729
24988
  }
24730
24989
  ),
@@ -24779,7 +25038,9 @@ function SolidEdgePath({
24779
25038
  const boundWidth = edge.widthBind && stateValues ? resolveNumericAttr(edge.widthBind, stateValues, defaultWidth) : defaultWidth;
24780
25039
  const boundStroke = edge.strokeBind && stateValues ? interpolate(edge.strokeBind, stateValues) : color;
24781
25040
  const boundDashOffset = edge.dashOffsetBind && stateValues ? resolveNumericAttr(edge.dashOffsetBind, stateValues, 0) : void 0;
24782
- const dashArray = dashed ? "10 8" : active ? boundDashOffset !== void 0 ? "8 6" : void 0 : "4 5";
25041
+ const strokeWidth = edge.routing === "metro" && edge.widthBind === void 0 ? edge.metroReturn ? 6 : METRO_LINE_WIDTH : boundWidth;
25042
+ const strokeWidthCss = edge.routing === "metro" && !edge.metroReturn ? edge.widthBind === void 0 ? METRO_LINE_WIDTH_CSS : `var(--cdl-metro-line-width, ${strokeWidth}px)` : void 0;
25043
+ const dashArray = dashed ? edge.metroReturn ? "0 9.6" : "10 8" : active ? boundDashOffset !== void 0 ? "8 6" : void 0 : "4 5";
24783
25044
  return /* @__PURE__ */ jsx(
24784
25045
  "path",
24785
25046
  {
@@ -24788,14 +25049,15 @@ function SolidEdgePath({
24788
25049
  fill: "none",
24789
25050
  stroke: boundStroke,
24790
25051
  strokeOpacity: active ? 0.95 : 0.9,
24791
- strokeWidth: boundWidth,
25052
+ strokeWidth,
24792
25053
  strokeDasharray: dashArray,
24793
25054
  strokeDashoffset: boundDashOffset,
24794
25055
  strokeLinecap: "round",
24795
25056
  markerEnd: marker === void 0 ? void 0 : `url(#${marker})`,
24796
25057
  markerStart: tailMarker === void 0 ? void 0 : `url(#${tailMarker})`,
24797
25058
  style: {
24798
- transition: "stroke 280ms ease, stroke-opacity 280ms ease, stroke-width 280ms ease"
25059
+ transition: "stroke 280ms ease, stroke-opacity 280ms ease, stroke-width 280ms ease",
25060
+ strokeWidth: strokeWidthCss
24799
25061
  }
24800
25062
  }
24801
25063
  );
@@ -25092,7 +25354,11 @@ function \u5272\u308B\u5019\u88DC(text) {
25092
25354
  function \u5165\u3063\u3066\u3044\u308B([\u5DE6, \u53F3]) {
25093
25355
  return \u5DE6 !== "" && \u53F3 !== "";
25094
25356
  }
25095
- function CardNode({ node, active }) {
25357
+ function CardNode({
25358
+ node,
25359
+ active,
25360
+ compactTitleInset
25361
+ }) {
25096
25362
  const accent = nodeAccent(node, "var(--cdl-tone-accent, #2d6a8f)");
25097
25363
  const x = node.cx - node.w / 2;
25098
25364
  const y = node.cy - node.h / 2;
@@ -25110,12 +25376,12 @@ function CardNode({ node, active }) {
25110
25376
  });
25111
25377
  const \u305A\u3089\u3057 = \u7E70\u308A\u4E0A\u3052 - \u4F59\u308A;
25112
25378
  const titleY = compact ? \u5C0F\u578B\u306B\u547C\u3073\u540D ? node.h / 2 - 2 : node.h / 2 + 8 : 86 - \u305A\u3089\u3057;
25113
- const titleX = compact ? node.w / 2 : 26;
25114
- const paddingX = compact ? 16 : 52;
25379
+ const titleX = compact ? compactTitleInset ?? node.w / 2 : 26;
25380
+ const paddingX = compact ? (compactTitleInset ?? 8) * 2 : 52;
25115
25381
  const availableW = node.w - paddingX;
25116
25382
  const initialTitleSize = compact ? 20 : 24;
25117
25383
  const titleSize = fitTextSize(node.title, availableW, initialTitleSize, 11);
25118
- const textAnchor = compact ? "middle" : "start";
25384
+ const textAnchor = compact && compactTitleInset === void 0 ? "middle" : "start";
25119
25385
  const subtitleY = 124 - \u305A\u3089\u3057;
25120
25386
  const \u6700\u5927\u306E\u884C\u9001\u308A = SUBTITLE_SIZE * \u884C\u9001\u308A\u306E\u500D\u7387;
25121
25387
  const \u4E8C\u884C\u304C\u5165\u308B = !compact && subtitleY - \u6700\u5927\u306E\u884C\u9001\u308A / 2 - SUBTITLE_SIZE * ASCENT > titleY + titleSize * DESCENT && subtitleY + \u6700\u5927\u306E\u884C\u9001\u308A / 2 + SUBTITLE_SIZE * DESCENT <= node.h - 4;
@@ -25878,7 +26144,9 @@ function GenericNode({
25878
26144
  value,
25879
26145
  stateValues,
25880
26146
  \u5024\u306E\u540D\u524D,
25881
- textMetrics = "proportional"
26147
+ textMetrics = "proportional",
26148
+ showKindIcon = true,
26149
+ metroDecision = false
25882
26150
  }) {
25883
26151
  const base = STYLES[node.kind] ?? FALLBACK;
25884
26152
  const toned = toneColor3(node.tone);
@@ -25909,14 +26177,14 @@ function GenericNode({
25909
26177
  if (shape === "diamond") {
25910
26178
  const cx = w / 2;
25911
26179
  const cy = h / 2;
25912
- const pad = 8;
26180
+ const pad = metroDecision ? 0 : 8;
25913
26181
  return /* @__PURE__ */ jsx(
25914
26182
  "path",
25915
26183
  {
25916
26184
  d: `M ${cx} ${pad} L ${w - pad} ${cy} L ${cx} ${h - pad} L ${pad} ${cy} Z`,
25917
26185
  fill: "var(--cdl-node-fill, #ffffff)",
25918
26186
  stroke: active ? style.accent : "var(--cdl-divider, #d4d4d8)",
25919
- strokeWidth: active ? 3 : 1.25
26187
+ strokeWidth: metroDecision ? 1.5 : active ? 3 : 1.25
25920
26188
  }
25921
26189
  );
25922
26190
  }
@@ -25980,11 +26248,11 @@ function GenericNode({
25980
26248
  const \u305A\u3089\u3057 = \u7E70\u308A\u4E0A\u3052 - \u4F59\u308A;
25981
26249
  const \u7D75\u306E\u82AF = centered && STYLES[node.kind]?.shape === "cloud" ? h * CLOUD_ART_CENTER_RISE : 0;
25982
26250
  const eyebrowY = centered ? h / 2 - 26 - \u7D75\u306E\u82AF : 32 + cylinderShift + \u4F59\u308A;
25983
- const titleY = genericTitleBaselineY(node.kind, h, hasRows) - \u305A\u3089\u3057 - \u7D75\u306E\u82AF;
26251
+ const titleY = metroDecision ? h / 2 + 9 : genericTitleBaselineY(node.kind, h, hasRows) - \u305A\u3089\u3057 - \u7D75\u306E\u82AF;
25984
26252
  const subtitleY = centered ? h / 2 + 28 - \u7D75\u306E\u82AF : 96 + cylinderShift - \u305A\u3089\u3057;
25985
26253
  const iconX = centered ? w / 2 + 40 : w - 48;
25986
26254
  const iconY = centered ? h / 2 - 44 - \u7D75\u306E\u82AF : 18 + cylinderShift;
25987
- const dominantBaseline = centered ? "middle" : void 0;
26255
+ const dominantBaseline = centered && !metroDecision ? "middle" : void 0;
25988
26256
  return /* @__PURE__ */ jsxs(
25989
26257
  "g",
25990
26258
  {
@@ -25993,12 +26261,12 @@ function GenericNode({
25993
26261
  "data-cdl-look": style.look,
25994
26262
  children: [
25995
26263
  shapeEl,
25996
- /* @__PURE__ */ jsxs("g", { transform: `translate(${iconX} ${iconY}) scale(1)`, "data-cdl-role": "node-kind-icon", children: [
26264
+ showKindIcon && /* @__PURE__ */ jsxs("g", { transform: `translate(${iconX} ${iconY}) scale(1)`, "data-cdl-role": "node-kind-icon", children: [
25997
26265
  /* @__PURE__ */ jsx("rect", { width: 24, height: 24, fill: "transparent" }),
25998
26266
  /* @__PURE__ */ jsx("path", { d: style.icon, fill: style.eyebrowColor, opacity: 0.7, transform: "scale(1)" })
25999
26267
  ] }),
26000
26268
  node.eyebrow && /* @__PURE__ */ jsx("text", { x: textX, y: eyebrowY, fontSize: 17, fontWeight: 700, letterSpacing: 1.5, fill: paint.onSolid ? "var(--cdl-node-fill, #ffffff)" : style.eyebrowColor, textAnchor, dominantBaseline, children: node.eyebrow }),
26001
- /* @__PURE__ */ jsx("text", { "data-cdl-role": "node-label", x: textX, y: titleY, fontSize: 20, fontWeight: 700, fill: paint.onSolid ? "var(--cdl-node-fill, #ffffff)" : "var(--cdl-text, #1a1f2a)", textAnchor, dominantBaseline, style: node.kind === "function" || node.kind === "api" ? { fontFamily: "'JetBrains Mono', monospace" } : void 0, children: node.kind === "function" || node.kind === "api" ? \u7B49\u5E45\u306E\u5B57(node.title, 20) : node.title }),
26269
+ /* @__PURE__ */ jsx("text", { "data-cdl-role": "node-label", x: textX, y: titleY, fontSize: metroDecision ? 26 : 20, fontWeight: 700, fill: paint.onSolid ? "var(--cdl-node-fill, #ffffff)" : "var(--cdl-text, #1a1f2a)", textAnchor, dominantBaseline, style: node.kind === "function" || node.kind === "api" ? { fontFamily: "'JetBrains Mono', monospace" } : void 0, children: node.kind === "function" || node.kind === "api" ? \u7B49\u5E45\u306E\u5B57(node.title, 20) : node.title }),
26002
26270
  node.subtitle && /* @__PURE__ */ jsx("text", { x: textX, y: subtitleY, fontSize: 17, fill: paint.onSolid ? "var(--cdl-node-fill, #ffffff)" : "var(--cdl-text-dim, #5a6270)", textAnchor, dominantBaseline, style: { fontFamily: "'JetBrains Mono', monospace" }, children: \u7B49\u5E45\u306E\u5B57(node.subtitle, 17) }),
26003
26271
  resolvedValue && /* @__PURE__ */ jsx(
26004
26272
  "text",
@@ -26047,50 +26315,176 @@ function GenericNode({
26047
26315
  var MARK_INNER_RATIO = 0.56;
26048
26316
  var RING_WIDTH_ACTIVE = 6;
26049
26317
  var RING_WIDTH_IDLE = 4;
26318
+ var METRO_START_RADIUS = 17;
26319
+ var METRO_END_RADIUS = 19;
26320
+ var METRO_END_RING_WIDTH = 3.5;
26321
+ var METRO_END_INNER_RADIUS = 10;
26050
26322
  var MARK_DEFAULT = "var(--cdl-tone-accent, #2d6a8f)";
26051
26323
  function markRadius(node) {
26052
26324
  return Math.min(node.w, node.h) / 2;
26053
26325
  }
26054
- function MarkStartNode({ node }) {
26326
+ function MarkStartNode({
26327
+ node,
26328
+ metro = false
26329
+ }) {
26055
26330
  const \u8272 = nodeAccent(node, MARK_DEFAULT);
26056
- return /* @__PURE__ */ jsx("g", { transform: `translate(${node.cx - node.w / 2} ${node.cy - node.h / 2})`, children: /* @__PURE__ */ jsx(
26057
- "circle",
26331
+ return /* @__PURE__ */ jsx(
26332
+ "g",
26058
26333
  {
26059
- "data-cdl-role": "node-body",
26060
- "data-cdl-mark": "start",
26061
- cx: node.w / 2,
26062
- cy: node.h / 2,
26063
- r: markRadius(node),
26064
- fill: \u8272
26334
+ "data-cdl-role": metro ? "mark-start" : void 0,
26335
+ transform: `translate(${node.cx - node.w / 2} ${node.cy - node.h / 2})`,
26336
+ children: /* @__PURE__ */ jsx(
26337
+ "circle",
26338
+ {
26339
+ "data-cdl-role": "node-body",
26340
+ "data-cdl-mark": "start",
26341
+ cx: node.w / 2,
26342
+ cy: node.h / 2,
26343
+ r: metro ? METRO_START_RADIUS : markRadius(node),
26344
+ fill: \u8272
26345
+ }
26346
+ )
26065
26347
  }
26066
- ) });
26348
+ );
26067
26349
  }
26068
- function MarkEndNode({ node, active }) {
26350
+ function MarkEndNode({
26351
+ node,
26352
+ active,
26353
+ metro = false
26354
+ }) {
26069
26355
  const \u8272 = nodeAccent(node, MARK_DEFAULT);
26070
- const r = markRadius(node);
26071
- const \u8F2A\u306E\u592A\u3055 = active ? RING_WIDTH_ACTIVE : RING_WIDTH_IDLE;
26072
- return /* @__PURE__ */ jsxs("g", { transform: `translate(${node.cx - node.w / 2} ${node.cy - node.h / 2})`, children: [
26356
+ const r = metro ? METRO_END_RADIUS : markRadius(node);
26357
+ const \u8F2A\u306E\u592A\u3055 = metro ? METRO_END_RING_WIDTH : active ? RING_WIDTH_ACTIVE : RING_WIDTH_IDLE;
26358
+ return /* @__PURE__ */ jsxs(
26359
+ "g",
26360
+ {
26361
+ "data-cdl-role": metro ? "mark-end" : void 0,
26362
+ transform: `translate(${node.cx - node.w / 2} ${node.cy - node.h / 2})`,
26363
+ children: [
26364
+ /* @__PURE__ */ jsx(
26365
+ "circle",
26366
+ {
26367
+ "data-cdl-role": "node-body",
26368
+ "data-cdl-mark": "end",
26369
+ cx: node.w / 2,
26370
+ cy: node.h / 2,
26371
+ r: metro ? r : Math.max(0, r - \u8F2A\u306E\u592A\u3055 / 2),
26372
+ fill: "var(--cdl-node-fill, #ffffff)",
26373
+ stroke: \u8272,
26374
+ strokeWidth: \u8F2A\u306E\u592A\u3055
26375
+ }
26376
+ ),
26377
+ /* @__PURE__ */ jsx(
26378
+ "circle",
26379
+ {
26380
+ "data-cdl-role": "node-inner",
26381
+ cx: node.w / 2,
26382
+ cy: node.h / 2,
26383
+ r: metro ? METRO_END_INNER_RADIUS : Math.max(0, r * MARK_INNER_RATIO),
26384
+ fill: \u8272
26385
+ }
26386
+ )
26387
+ ]
26388
+ }
26389
+ );
26390
+ }
26391
+ var METRO_STATION_RADIUS = 16;
26392
+ var METRO_STATION_STROKE_WIDTH = 6;
26393
+ var METRO_STATION_RADIUS_CSS = `var(--cdl-metro-station-radius, ${METRO_STATION_RADIUS}px)`;
26394
+ var METRO_STATION_STROKE_WIDTH_CSS = `var(--cdl-metro-station-stroke-width, ${METRO_STATION_STROKE_WIDTH}px)`;
26395
+ var STATION_DEFAULT = "var(--cdl-tone-accent, #2d6a8f)";
26396
+ var STATION_NAME_FONT_SIZE = 25;
26397
+ var STATION_NAME_GAP = 18;
26398
+ var STATION_NAME_RIGHT_GAP = 32;
26399
+ var STATION_NAME_RIGHT_BASELINE = 9;
26400
+ var STATION_SUBTITLE_FONT_SIZE = 18;
26401
+ var STATION_SUBTITLE_GAP = 22;
26402
+ function StationNode({
26403
+ node,
26404
+ nameRight = false
26405
+ }) {
26406
+ const name = node.title.trim();
26407
+ const subtitle = node.subtitle?.trim() ?? "";
26408
+ return /* @__PURE__ */ jsxs("g", { children: [
26073
26409
  /* @__PURE__ */ jsx(
26074
26410
  "circle",
26075
26411
  {
26076
26412
  "data-cdl-role": "node-body",
26077
- "data-cdl-mark": "end",
26078
- cx: node.w / 2,
26079
- cy: node.h / 2,
26080
- r: Math.max(0, r - \u8F2A\u306E\u592A\u3055 / 2),
26413
+ "data-cdl-mark": "station",
26414
+ cx: node.cx,
26415
+ cy: node.cy,
26416
+ r: METRO_STATION_RADIUS,
26081
26417
  fill: "var(--cdl-node-fill, #ffffff)",
26082
- stroke: \u8272,
26083
- strokeWidth: \u8F2A\u306E\u592A\u3055
26418
+ stroke: nodeAccent(node, STATION_DEFAULT),
26419
+ strokeWidth: METRO_STATION_STROKE_WIDTH,
26420
+ style: { r: METRO_STATION_RADIUS_CSS, strokeWidth: METRO_STATION_STROKE_WIDTH_CSS }
26084
26421
  }
26085
26422
  ),
26423
+ name !== "" && /* @__PURE__ */ jsx(
26424
+ "text",
26425
+ {
26426
+ "data-cdl-role": "shape-name",
26427
+ x: nameRight ? node.cx + STATION_NAME_RIGHT_GAP : node.cx,
26428
+ y: nameRight ? node.cy + STATION_NAME_RIGHT_BASELINE : node.cy - METRO_STATION_RADIUS - STATION_NAME_GAP,
26429
+ textAnchor: nameRight ? "start" : "middle",
26430
+ fontSize: STATION_NAME_FONT_SIZE,
26431
+ fontWeight: 700,
26432
+ fill: "var(--cdl-text, #23201b)",
26433
+ stroke: "var(--cdl-node-fill, #ffffff)",
26434
+ strokeWidth: 8,
26435
+ strokeLinejoin: "round",
26436
+ paintOrder: "stroke",
26437
+ children: name
26438
+ }
26439
+ ),
26440
+ subtitle !== "" && /* @__PURE__ */ jsx(
26441
+ "text",
26442
+ {
26443
+ "data-cdl-role": "shape-value",
26444
+ x: node.cx,
26445
+ y: node.cy + METRO_STATION_RADIUS + STATION_SUBTITLE_GAP,
26446
+ textAnchor: "middle",
26447
+ fontSize: STATION_SUBTITLE_FONT_SIZE,
26448
+ fill: "var(--cdl-text-dim, #5a6270)",
26449
+ children: subtitle
26450
+ }
26451
+ )
26452
+ ] });
26453
+ }
26454
+ function TimelineNumberNode({ node }) {
26455
+ const radius = Math.max(0, Math.min(node.w, node.h) / 2 - TIMELINE_NUMBER_STROKE / 2);
26456
+ const fontSize = fitTextSize(
26457
+ node.title,
26458
+ titleUsableWidth(node.kind, node.w, node.h),
26459
+ 18,
26460
+ MIN_TEXT_SIZE
26461
+ );
26462
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
26086
26463
  /* @__PURE__ */ jsx(
26087
26464
  "circle",
26088
26465
  {
26089
- "data-cdl-role": "node-inner",
26090
- cx: node.w / 2,
26091
- cy: node.h / 2,
26092
- r: Math.max(0, r * MARK_INNER_RATIO),
26093
- fill: \u8272
26466
+ "data-cdl-role": "node-body",
26467
+ "data-cdl-mark": "timeline-number",
26468
+ cx: node.cx,
26469
+ cy: node.cy,
26470
+ r: radius,
26471
+ fill: "var(--cdl-node-fill, #ffffff)",
26472
+ stroke: nodeAccent(node, STATION_DEFAULT),
26473
+ strokeWidth: TIMELINE_NUMBER_STROKE
26474
+ }
26475
+ ),
26476
+ /* @__PURE__ */ jsx(
26477
+ "text",
26478
+ {
26479
+ "data-cdl-role": "node-label",
26480
+ x: node.cx,
26481
+ y: node.cy,
26482
+ textAnchor: "middle",
26483
+ dominantBaseline: "central",
26484
+ fontSize,
26485
+ fontWeight: 700,
26486
+ fill: "var(--cdl-text, #1a1f2a)",
26487
+ children: node.title
26094
26488
  }
26095
26489
  )
26096
26490
  ] });
@@ -26422,27 +26816,6 @@ function interpolateColor(template, stateValues, fallback) {
26422
26816
  if (!template) return fallback;
26423
26817
  return interpolate(template, stateValues);
26424
26818
  }
26425
- var STATION_STROKE_ACTIVE = 4;
26426
- var STATION_STROKE_IDLE = 3;
26427
- var STATION_DEFAULT = "var(--cdl-tone-accent, #2d6a8f)";
26428
- function StationNode({ node, active }) {
26429
- const strokeWidth = active ? STATION_STROKE_ACTIVE : STATION_STROKE_IDLE;
26430
- const radius = Math.max(0, Math.min(node.w, node.h) / 2 - strokeWidth / 2);
26431
- const \u5F62 = /* @__PURE__ */ jsx(
26432
- "circle",
26433
- {
26434
- "data-cdl-role": "node-body",
26435
- "data-cdl-mark": "station",
26436
- cx: node.cx,
26437
- cy: node.cy,
26438
- r: radius,
26439
- fill: "var(--cdl-node-fill, #ffffff)",
26440
- stroke: nodeAccent(node, STATION_DEFAULT),
26441
- strokeWidth
26442
- }
26443
- );
26444
- return renderShapeWithOutsideLabels(\u5F62, node, "middle");
26445
- }
26446
26819
 
26447
26820
  // src/kinds/label-stack.ts
26448
26821
  function \u540D\u672D\u306E\u6BB5\u3092\u307B\u3069\u304F(\u771F\u306Ey, \u9593\u9694, \u4E0A\u9650, \u4E0B\u9650) {
@@ -33604,10 +33977,14 @@ function CdlNodeView({
33604
33977
  stateValues,
33605
33978
  currentPhaseId,
33606
33979
  chartLineScale,
33980
+ compactTitleInset,
33607
33981
  \u95A2\u4FC2\u306E\u53D7\u3051\u53E3,
33608
33982
  lang,
33609
33983
  textMetrics,
33610
- \u5024\u306E\u540D\u524D
33984
+ \u5024\u306E\u540D\u524D,
33985
+ metroDiagram = false,
33986
+ metroDecision = false,
33987
+ stationNameRight = false
33611
33988
  }) {
33612
33989
  const value = node.value ? interpolate(node.value, stateValues, \u5024\u306E\u540D\u524D) : void 0;
33613
33990
  const rows = (node.rows ?? []).map((r) => interpolate(r, stateValues, \u5024\u306E\u540D\u524D));
@@ -33648,7 +34025,7 @@ function CdlNodeView({
33648
34025
  case "event":
33649
34026
  return /* @__PURE__ */ jsx(EventNode, { node: resolvedNode, active, progress });
33650
34027
  case "card":
33651
- return /* @__PURE__ */ jsx(CardNode, { node: resolvedNode, active });
34028
+ return /* @__PURE__ */ jsx(CardNode, { node: resolvedNode, active, compactTitleInset });
33652
34029
  // 時系列のやり取り (#578)。 1 つの箱が図を丸ごと描く = 縦が時間で、参加者は箱を持たない
33653
34030
  case "sequence-board":
33654
34031
  return /* @__PURE__ */ jsx(
@@ -33935,11 +34312,13 @@ function CdlNodeView({
33935
34312
  case "shape-customer-service":
33936
34313
  return /* @__PURE__ */ jsx(ShapeCustomerServiceNode, { node: resolvedNode, active });
33937
34314
  case "mark-start":
33938
- return /* @__PURE__ */ jsx(MarkStartNode, { node: resolvedNode, active });
34315
+ return /* @__PURE__ */ jsx(MarkStartNode, { node: resolvedNode, active, metro: metroDiagram });
33939
34316
  case "mark-end":
33940
- return /* @__PURE__ */ jsx(MarkEndNode, { node: resolvedNode, active });
34317
+ return /* @__PURE__ */ jsx(MarkEndNode, { node: resolvedNode, active, metro: metroDiagram });
33941
34318
  case "station":
33942
- return /* @__PURE__ */ jsx(StationNode, { node: resolvedNode, active });
34319
+ return /* @__PURE__ */ jsx(StationNode, { node: resolvedNode, active, nameRight: stationNameRight });
34320
+ case "timeline-number":
34321
+ return /* @__PURE__ */ jsx(TimelineNumberNode, { node: resolvedNode });
33943
34322
  default:
33944
34323
  return /* @__PURE__ */ jsx(
33945
34324
  GenericNode,
@@ -33949,7 +34328,9 @@ function CdlNodeView({
33949
34328
  value: node.value,
33950
34329
  stateValues,
33951
34330
  \u5024\u306E\u540D\u524D,
33952
- textMetrics
34331
+ textMetrics,
34332
+ showKindIcon: !(metroDiagram && node.kind === "decision"),
34333
+ metroDecision
33953
34334
  }
33954
34335
  );
33955
34336
  }
@@ -34063,6 +34444,112 @@ function \u7BB1\u306E\u4E0A\u306E\u8A9E(node, textMetrics = "proportional") {
34063
34444
  )
34064
34445
  ] });
34065
34446
  }
34447
+ var ink = "var(--cdl-text-accent, #143a52)";
34448
+ var paper = "var(--cdl-stage-bg, #f8fafc)";
34449
+ var LEGEND_MARK_RENDERERS = {
34450
+ diamond: () => /* @__PURE__ */ jsx("path", { d: "M 23 -26 L 46 -8 L 23 10 L 0 -8 Z", fill: "none", stroke: ink, strokeWidth: 2 }),
34451
+ "filled-circle": () => /* @__PURE__ */ jsx("circle", { cx: 23, cy: -8, r: 11, fill: ink }),
34452
+ "double-circle": () => /* @__PURE__ */ jsxs(Fragment, { children: [
34453
+ /* @__PURE__ */ jsx("circle", { cx: 23, cy: -8, r: 12, fill: "none", stroke: ink, strokeWidth: 2.5 }),
34454
+ /* @__PURE__ */ jsx("circle", { cx: 23, cy: -8, r: 6, fill: ink })
34455
+ ] }),
34456
+ "dotted-line": () => /* @__PURE__ */ jsx(
34457
+ "path",
34458
+ {
34459
+ d: `M 0 -8 H ${LEGEND_MARK_REFERENCE_WIDTH}`,
34460
+ fill: "none",
34461
+ stroke: ink,
34462
+ strokeWidth: 6,
34463
+ strokeLinecap: "round",
34464
+ strokeDasharray: "0 9.6"
34465
+ }
34466
+ ),
34467
+ "solid-line": () => /* @__PURE__ */ jsx(
34468
+ "path",
34469
+ {
34470
+ d: `M 0 -8 H ${LEGEND_MARK_REFERENCE_WIDTH}`,
34471
+ fill: "none",
34472
+ stroke: ink,
34473
+ strokeWidth: 4,
34474
+ strokeLinecap: "round"
34475
+ }
34476
+ ),
34477
+ "curved-line": () => /* @__PURE__ */ jsx(
34478
+ "path",
34479
+ {
34480
+ d: `M 0 6 C 12 -22, 34 -22, ${LEGEND_MARK_REFERENCE_WIDTH} 6`,
34481
+ fill: "none",
34482
+ stroke: ink,
34483
+ strokeWidth: 4,
34484
+ strokeLinecap: "round"
34485
+ }
34486
+ ),
34487
+ "rounded-label": () => /* @__PURE__ */ jsx(
34488
+ "rect",
34489
+ {
34490
+ x: 0,
34491
+ y: -23,
34492
+ width: LEGEND_MARK_REFERENCE_WIDTH,
34493
+ height: 30,
34494
+ rx: 9,
34495
+ fill: paper,
34496
+ stroke: ink,
34497
+ strokeWidth: 2
34498
+ }
34499
+ ),
34500
+ "numbered-circle": () => /* @__PURE__ */ jsxs(Fragment, { children: [
34501
+ /* @__PURE__ */ jsx("circle", { cx: 23, cy: -8, r: 13, fill: paper, stroke: ink, strokeWidth: 2.5 }),
34502
+ /* @__PURE__ */ jsx("text", { x: 23, y: -2, textAnchor: "middle", fontSize: 17, fontWeight: 700, fill: ink, children: "1" })
34503
+ ] }),
34504
+ station: () => /* @__PURE__ */ jsx("circle", { cx: 23, cy: -8, r: 11, fill: paper, stroke: ink, strokeWidth: 4 }),
34505
+ arrow: () => /* @__PURE__ */ jsx(
34506
+ "path",
34507
+ {
34508
+ d: `M 0 -8 H 42 M 32 -18 L ${LEGEND_MARK_REFERENCE_WIDTH} -8 L 32 2`,
34509
+ fill: "none",
34510
+ stroke: ink,
34511
+ strokeWidth: 3,
34512
+ strokeLinecap: "round",
34513
+ strokeLinejoin: "round"
34514
+ }
34515
+ )
34516
+ };
34517
+ function CdlLegend({ items }) {
34518
+ return /* @__PURE__ */ jsx("g", { "data-cdl-role": "legend", children: items.map((item, index) => {
34519
+ const Mark = LEGEND_MARK_RENDERERS[item.mark];
34520
+ return /* @__PURE__ */ jsxs(
34521
+ "g",
34522
+ {
34523
+ "data-cdl-role": "legend-item",
34524
+ "data-cdl-y": item.y,
34525
+ transform: `translate(${item.x} ${item.y})`,
34526
+ children: [
34527
+ /* @__PURE__ */ jsx(
34528
+ "g",
34529
+ {
34530
+ "data-cdl-role": "legend-mark",
34531
+ "data-cdl-legend-mark": item.mark,
34532
+ transform: `scale(${LEGEND_MARK_SCALE})`,
34533
+ children: /* @__PURE__ */ jsx(Mark, {})
34534
+ }
34535
+ ),
34536
+ /* @__PURE__ */ jsx(
34537
+ "text",
34538
+ {
34539
+ "data-cdl-role": "legend-text",
34540
+ x: LEGEND_MARK_WIDTH + LEGEND_MARK_TEXT_GAP,
34541
+ y: 0,
34542
+ fontSize: LEGEND_FONT_SIZE,
34543
+ fill: "var(--cdl-text-mute, #4d7187)",
34544
+ children: item.text
34545
+ }
34546
+ )
34547
+ ]
34548
+ },
34549
+ `${item.mark}-${index}`
34550
+ );
34551
+ }) });
34552
+ }
34066
34553
 
34067
34554
  // src/render/relation-focus.ts
34068
34555
  function relatedFocusSet(edges, nodeId) {
@@ -34075,10 +34562,17 @@ function relatedFocusSet(edges, nodeId) {
34075
34562
  }
34076
34563
  return \u96C6\u5408;
34077
34564
  }
34565
+ var METRO_LANE_BADGE_WIDTH = 200;
34566
+ var METRO_LANE_BADGE_HEIGHT = 88;
34567
+ var METRO_LANE_BADGE_INSET = 24;
34568
+ var METRO_LANE_GUIDE_GAP = 24;
34078
34569
  function \u63CF\u304F\u3082\u306E\u304C\u3042\u308B(laid) {
34570
+ if (laid.legend && laid.legend.length > 0) return true;
34079
34571
  if (laid.edges.length > 0) return true;
34080
34572
  if (laid.nodes.some((n) => isRenderedNode(n))) return true;
34081
- return laid.lanes.some((l) => l.contain === true || l.lifeline === true || Boolean(l.label));
34573
+ return laid.lanes.some(
34574
+ (l) => l.contain === true || l.lifeline === true || Boolean(l.label) || l.stage !== void 0 || l.metro !== void 0
34575
+ );
34082
34576
  }
34083
34577
  function CdlStage({
34084
34578
  laid,
@@ -34119,6 +34613,7 @@ function CdlStage({
34119
34613
  [\u95A2\u4FC2\u3092\u5149\u3089\u305B\u308B, \u7126\u70B9, laid.edges]
34120
34614
  );
34121
34615
  const activeSet = \u7126\u70B9\u306E\u96C6\u5408 ?? new Set(currentPhase?.activate ?? []);
34616
+ const \u8DEF\u7DDA\u56F3 = laid.edges.some((edge) => edge.routing === "metro") && laid.nodes.some((node) => node.kind === "station");
34122
34617
  const \u7BB1\u306E\u53D7\u3051\u53E3 = \u95A2\u4FC2\u3092\u5149\u3089\u305B\u308B ? { \u89E6\u308C\u305F, \u96E2\u308C\u305F, \u62BC\u3057\u305F, \u7126\u70B9: \u7126\u70B9?.id ?? null } : void 0;
34123
34618
  const \u77E2\u5370\u306E\u51FA\u756A = useMemo(() => {
34124
34619
  const \u521D\u51FA = /* @__PURE__ */ new Map();
@@ -34147,9 +34642,15 @@ function CdlStage({
34147
34642
  const first = \u521D\u51FA.get(edgeId);
34148
34643
  if (first === void 0) return false;
34149
34644
  return \u4ECA === first;
34645
+ },
34646
+ /** 初出の段より前にある区間だけを、通過前として選べるようにする。 */
34647
+ \u307E\u3060\u304B: (edgeId) => {
34648
+ const first = \u521D\u51FA.get(edgeId);
34649
+ return first !== void 0 && \u4ECA >= 0 && \u4ECA < first;
34150
34650
  }
34151
34651
  };
34152
34652
  }, [laid.phases, currentPhase, laid.edgeReveal]);
34653
+ const \u7DDA\u3092\u63CF\u304F = (edge) => edge.routing === "metro" || \u77E2\u5370\u306E\u51FA\u756A.\u51FA\u308B\u304B(edge.id);
34153
34654
  const drawSet = new Set(currentPhase?.draw ?? []);
34154
34655
  const chartLineScales = useMemo(() => {
34155
34656
  const nodes = laid.nodes.filter((node) => node.kind === "chart-line");
@@ -34237,7 +34738,53 @@ function CdlStage({
34237
34738
  )
34238
34739
  );
34239
34740
  }, [laid]);
34741
+ const metroReturnArrowMarkers = useMemo(() => {
34742
+ const tones = new Set(
34743
+ laid.edges.filter((edge) => edge.metroReturn === true).map((edge) => edge.tone)
34744
+ );
34745
+ return [...tones].map((tone) => /* @__PURE__ */ jsx(
34746
+ "marker",
34747
+ {
34748
+ id: metroReturnArrowMarkerId(tone),
34749
+ viewBox: "0 -9.5 21 19",
34750
+ refX: "0",
34751
+ refY: "0",
34752
+ markerWidth: "21",
34753
+ markerHeight: "19",
34754
+ markerUnits: "userSpaceOnUse",
34755
+ orient: "auto",
34756
+ children: /* @__PURE__ */ jsx(
34757
+ "path",
34758
+ {
34759
+ "data-cdl-role": "edge-arrowhead",
34760
+ "data-cdl-mark": "metro-return-arrow",
34761
+ d: METRO_RETURN_ARROW_D,
34762
+ fill: TONE[tone]
34763
+ }
34764
+ )
34765
+ },
34766
+ tone
34767
+ ));
34768
+ }, [laid.edges]);
34240
34769
  const diagramScale = laid.diagramScale ?? 1;
34770
+ const stageLaneIds = new Set(
34771
+ laid.lanes.filter((lane) => lane.stage !== void 0).map((lane) => lane.id)
34772
+ );
34773
+ const stageNoteNodes = laid.nodes.filter(
34774
+ (node) => node.kind === "card" && (stageLaneIds.has(node.lane) || node.leaderTo !== void 0) && isRenderedNode(node) && Boolean(node.subtitle)
34775
+ );
34776
+ const stageNoteNodeIds = new Set(stageNoteNodes.map((node) => node.id));
34777
+ const nodesById = new Map(laid.nodes.map((node) => [node.id, node]));
34778
+ const metroDecisionIds = /* @__PURE__ */ new Set();
34779
+ const stationNamesRight = /* @__PURE__ */ new Set();
34780
+ for (const edge of laid.edges) {
34781
+ if (edge.routing !== "metro") continue;
34782
+ if (nodesById.get(edge.from)?.kind === "decision") metroDecisionIds.add(edge.from);
34783
+ if (nodesById.get(edge.to)?.kind === "decision") metroDecisionIds.add(edge.to);
34784
+ if (edge.toSide === "top" && nodesById.get(edge.to)?.kind === "station") {
34785
+ stationNamesRight.add(edge.to);
34786
+ }
34787
+ }
34241
34788
  if (!\u63CF\u304F\u3082\u306E\u304C\u3042\u308B(laid)) return null;
34242
34789
  return /* @__PURE__ */ jsxs("div", { className: compact ? "px-3 py-3" : "px-6 py-6", style: compact ? void 0 : { minHeight: 460 }, children: [
34243
34790
  /* @__PURE__ */ jsxs(
@@ -34257,6 +34804,7 @@ function CdlStage({
34257
34804
  children: [
34258
34805
  /* @__PURE__ */ jsxs("defs", { children: [
34259
34806
  edgeHeadMarkers,
34807
+ metroReturnArrowMarkers,
34260
34808
  /* @__PURE__ */ jsx(
34261
34809
  "marker",
34262
34810
  {
@@ -34293,7 +34841,61 @@ function CdlStage({
34293
34841
  pointerEvents: "none"
34294
34842
  }
34295
34843
  ),
34296
- lane.contain && /* @__PURE__ */ jsx(
34844
+ lane.stage && /* @__PURE__ */ jsxs(Fragment, { children: [
34845
+ /* @__PURE__ */ jsx(
34846
+ "rect",
34847
+ {
34848
+ "data-cdl-role": "stage-column",
34849
+ x: lane.x + STAGE_COLUMN_GAP / 2,
34850
+ y: lane.y,
34851
+ width: lane.width - STAGE_COLUMN_GAP,
34852
+ height: lane.height,
34853
+ rx: 16,
34854
+ fill: "var(--cdl-stage-bg, #fafafa)",
34855
+ fillOpacity: 0.5,
34856
+ stroke: "var(--cdl-text-mute, #8a8678)",
34857
+ strokeWidth: 1.5
34858
+ }
34859
+ ),
34860
+ /* @__PURE__ */ jsx(
34861
+ "text",
34862
+ {
34863
+ "data-cdl-role": "stage-number",
34864
+ x: lane.x + STAGE_COLUMN_GAP / 2 + 20,
34865
+ y: lane.y + 24,
34866
+ fontSize: 14,
34867
+ fontWeight: 500,
34868
+ letterSpacing: 1.5,
34869
+ fill: "var(--cdl-text-mute, #8a8678)",
34870
+ children: `\u6BB5\u968E ${lane.stage.number}`
34871
+ }
34872
+ ),
34873
+ /* @__PURE__ */ jsx(
34874
+ "text",
34875
+ {
34876
+ "data-cdl-role": "stage-name",
34877
+ x: lane.x + STAGE_COLUMN_GAP / 2 + 20,
34878
+ y: lane.y + 55,
34879
+ fontSize: 22,
34880
+ fontWeight: 700,
34881
+ fill: "var(--cdl-text, #1a1f2a)",
34882
+ children: lane.stage.name
34883
+ }
34884
+ ),
34885
+ /* @__PURE__ */ jsx(
34886
+ "line",
34887
+ {
34888
+ "data-cdl-role": "stage-divider",
34889
+ x1: lane.x + STAGE_COLUMN_GAP / 2,
34890
+ y1: lane.y + 72,
34891
+ x2: lane.x + lane.width - STAGE_COLUMN_GAP / 2,
34892
+ y2: lane.y + 72,
34893
+ stroke: "var(--cdl-divider, #d4d4d8)",
34894
+ strokeWidth: 1.5
34895
+ }
34896
+ )
34897
+ ] }),
34898
+ lane.contain && lane.stage === void 0 && /* @__PURE__ */ jsx(
34297
34899
  "rect",
34298
34900
  {
34299
34901
  "data-cdl-role": "lane-container",
@@ -34329,7 +34931,85 @@ function CdlStage({
34329
34931
  }
34330
34932
  );
34331
34933
  })(),
34332
- lane.label && /* @__PURE__ */ jsx(
34934
+ lane.timelineAxis && (() => {
34935
+ const ys = laid.nodes.filter((node) => node.lane === lane.id && isRenderedNode(node)).map((node) => node.cy);
34936
+ if (ys.length < 2) return null;
34937
+ const y1 = Math.min(...ys);
34938
+ const y2 = Math.max(...ys);
34939
+ if (y2 <= y1) return null;
34940
+ return /* @__PURE__ */ jsx(
34941
+ "line",
34942
+ {
34943
+ "data-cdl-role": "timeline-axis",
34944
+ x1: lane.x + lane.width / 2,
34945
+ y1,
34946
+ x2: lane.x + lane.width / 2,
34947
+ y2,
34948
+ stroke: "var(--cdl-tone-accent, #2d6a8f)",
34949
+ strokeWidth: TIMELINE_NUMBER_STROKE
34950
+ }
34951
+ );
34952
+ })(),
34953
+ lane.metro !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
34954
+ /* @__PURE__ */ jsx(
34955
+ "line",
34956
+ {
34957
+ "data-cdl-role": "metro-lane-guide",
34958
+ x1: lane.x + METRO_LANE_BADGE_INSET + METRO_LANE_BADGE_WIDTH + METRO_LANE_GUIDE_GAP,
34959
+ y1: lane.y + lane.height / 2,
34960
+ x2: lane.x + lane.width - METRO_LANE_BADGE_INSET,
34961
+ y2: lane.y + lane.height / 2,
34962
+ stroke: "var(--cdl-line-mute, #a89d8f)",
34963
+ strokeWidth: 2,
34964
+ strokeDasharray: "22 7 5 7",
34965
+ opacity: 0.45
34966
+ }
34967
+ ),
34968
+ /* @__PURE__ */ jsxs(
34969
+ "g",
34970
+ {
34971
+ "data-cdl-role": "metro-lane-badge",
34972
+ transform: `translate(${lane.x + METRO_LANE_BADGE_INSET} ${lane.y + lane.height / 2 - METRO_LANE_BADGE_HEIGHT / 2})`,
34973
+ children: [
34974
+ /* @__PURE__ */ jsx(
34975
+ "rect",
34976
+ {
34977
+ width: METRO_LANE_BADGE_WIDTH,
34978
+ height: METRO_LANE_BADGE_HEIGHT,
34979
+ rx: 12,
34980
+ fill: "var(--cdl-node-fill, #ffffff)",
34981
+ stroke: "var(--cdl-tone-accent, #2d6a8f)",
34982
+ strokeWidth: 1.25
34983
+ }
34984
+ ),
34985
+ /* @__PURE__ */ jsx(
34986
+ "text",
34987
+ {
34988
+ x: METRO_LANE_BADGE_WIDTH / 2,
34989
+ y: lane.metro.subtitle ? 37 : 53,
34990
+ fontSize: 26,
34991
+ fontWeight: 700,
34992
+ fill: "var(--cdl-text, #1a1f2a)",
34993
+ textAnchor: "middle",
34994
+ children: lane.label
34995
+ }
34996
+ ),
34997
+ lane.metro.subtitle && /* @__PURE__ */ jsx(
34998
+ "text",
34999
+ {
35000
+ x: METRO_LANE_BADGE_WIDTH / 2,
35001
+ y: 67,
35002
+ fontSize: 18,
35003
+ fill: "var(--cdl-text-dim, #5a6270)",
35004
+ textAnchor: "middle",
35005
+ children: lane.metro.subtitle
35006
+ }
35007
+ )
35008
+ ]
35009
+ }
35010
+ )
35011
+ ] }),
35012
+ lane.label && lane.metro === void 0 && /* @__PURE__ */ jsx(
34333
35013
  "text",
34334
35014
  {
34335
35015
  "data-cdl-role": "lane-label",
@@ -34346,7 +35026,34 @@ function CdlStage({
34346
35026
  },
34347
35027
  lane.id
34348
35028
  )),
34349
- laid.edges.filter((edge) => \u77E2\u5370\u306E\u51FA\u756A.\u51FA\u308B\u304B(edge.id)).map((edge) => /* @__PURE__ */ jsx(
35029
+ laid.nodes.map((node) => {
35030
+ if (node.leaderTo === void 0) return null;
35031
+ const target = nodesById.get(node.leaderTo);
35032
+ if (target === void 0) return null;
35033
+ if (!isRenderedNode(node) || !isRenderedNode(target)) return null;
35034
+ const targetIsRight = target.cx >= node.cx;
35035
+ const x1 = node.cx + (targetIsRight ? node.w / 2 : -node.w / 2);
35036
+ const x2 = target.cx + (targetIsRight ? -target.w / 2 : target.w / 2);
35037
+ if (targetIsRight ? x2 <= x1 : x1 <= x2) return null;
35038
+ return /* @__PURE__ */ jsx(
35039
+ "line",
35040
+ {
35041
+ "data-cdl-role": "timeline-leader",
35042
+ "data-cdl-leader-from": node.id,
35043
+ "data-cdl-leader-to": node.leaderTo,
35044
+ x1,
35045
+ y1: node.cy,
35046
+ x2,
35047
+ y2: target.cy,
35048
+ stroke: "var(--cdl-line-mute, #a89d8f)",
35049
+ strokeWidth: 1.5,
35050
+ strokeDasharray: "2 4",
35051
+ strokeLinecap: "round"
35052
+ },
35053
+ `timeline-leader-${node.id}`
35054
+ );
35055
+ }),
35056
+ laid.edges.filter(\u7DDA\u3092\u63CF\u304F).map((edge) => /* @__PURE__ */ jsx(
34350
35057
  CdlEdgeView,
34351
35058
  {
34352
35059
  edge,
@@ -34354,16 +35061,19 @@ function CdlStage({
34354
35061
  flowing: \u7126\u70B9\u306E\u96C6\u5408?.has(edge.id) === true && !\u52D5\u304D\u3092\u63A7\u3048\u308B,
34355
35062
  drawing: \u77E2\u5370\u306E\u51FA\u756A.\u4F38\u3070\u3059\u304B(edge.id),
34356
35063
  progress: \u77E2\u5370\u306E\u51FA\u756A.\u4F38\u3070\u3059\u304B(edge.id) ? \u63CF\u304F\u9032\u307F : progress,
34357
- stateValues
35064
+ stateValues,
35065
+ pending: edge.routing === "metro" && \u77E2\u5370\u306E\u51FA\u756A.\u307E\u3060\u304B(edge.id)
34358
35066
  },
34359
35067
  edge.id
34360
35068
  )),
34361
35069
  laid.nodes.map((node) => {
34362
35070
  const \u63CF\u304F = drawSet.has(node.id);
35071
+ const \u6BB5\u968E\u306E\u88DC\u8DB3 = stageNoteNodeIds.has(node.id);
34363
35072
  const view = /* @__PURE__ */ jsx(
34364
35073
  CdlNodeView,
34365
35074
  {
34366
- node,
35075
+ node: \u6BB5\u968E\u306E\u88DC\u8DB3 ? { ...node, subtitle: void 0 } : node,
35076
+ compactTitleInset: \u6BB5\u968E\u306E\u88DC\u8DB3 ? STAGE_CARD_TEXT_INSET : void 0,
34367
35077
  active: activeSet.has(node.id),
34368
35078
  lang: laid.lang,
34369
35079
  textMetrics: laid.textMetrics,
@@ -34373,13 +35083,32 @@ function CdlStage({
34373
35083
  stateValues,
34374
35084
  \u5024\u306E\u540D\u524D,
34375
35085
  currentPhaseId: currentPhase?.id,
34376
- chartLineScale: chartLineScales.get(node.id)
35086
+ chartLineScale: chartLineScales.get(node.id),
35087
+ metroDiagram: \u8DEF\u7DDA\u56F3,
35088
+ metroDecision: metroDecisionIds.has(node.id),
35089
+ stationNameRight: stationNamesRight.has(node.id)
34377
35090
  }
34378
35091
  );
34379
35092
  return /* @__PURE__ */ jsx("g", { children: view }, node.id);
34380
35093
  }),
34381
- laid.edges.filter((edge) => Boolean(edge.dThrough) && \u77E2\u5370\u306E\u51FA\u756A.\u51FA\u308B\u304B(edge.id)).map((edge) => /* @__PURE__ */ jsx(CdlEdgeGlowView, { edge, active: activeSet.has(edge.id) }, `glow-${edge.id}`)),
34382
- laid.edges.filter((edge) => \u77E2\u5370\u306E\u51FA\u756A.\u51FA\u308B\u304B(edge.id)).map((edge) => /* @__PURE__ */ jsx(
35094
+ stageNoteNodes.map((node) => /* @__PURE__ */ jsx(
35095
+ "text",
35096
+ {
35097
+ "data-cdl-role": "stage-note",
35098
+ "data-cdl-stage-node": node.id,
35099
+ x: node.cx + node.w / 2 - STAGE_CARD_TEXT_INSET,
35100
+ y: node.cy + 1,
35101
+ fontSize: 14,
35102
+ fontWeight: 500,
35103
+ textAnchor: "end",
35104
+ dominantBaseline: "middle",
35105
+ fill: "var(--cdl-text-mute, #8a8678)",
35106
+ children: interpolate(node.subtitle, stateValues, \u5024\u306E\u540D\u524D)
35107
+ },
35108
+ `stage-note-${node.id}`
35109
+ )),
35110
+ laid.edges.filter((edge) => Boolean(edge.dThrough) && \u7DDA\u3092\u63CF\u304F(edge)).map((edge) => /* @__PURE__ */ jsx(CdlEdgeGlowView, { edge, active: activeSet.has(edge.id) }, `glow-${edge.id}`)),
35111
+ laid.edges.filter(\u7DDA\u3092\u63CF\u304F).map((edge) => /* @__PURE__ */ jsx(
34383
35112
  CdlEdgeLabelView,
34384
35113
  {
34385
35114
  edge,
@@ -34388,7 +35117,7 @@ function CdlStage({
34388
35117
  },
34389
35118
  `label-${edge.id}`
34390
35119
  )),
34391
- laid.edges.filter((edge) => (edge.headLabel || edge.tailLabel) && \u77E2\u5370\u306E\u51FA\u756A.\u51FA\u308B\u304B(edge.id)).flatMap(
35120
+ laid.edges.filter((edge) => (edge.headLabel || edge.tailLabel) && \u7DDA\u3092\u63CF\u304F(edge)).flatMap(
34392
35121
  (edge) => ["head", "tail"].map((\u7AEF) => /* @__PURE__ */ jsx(
34393
35122
  CdlEdgeEndLabelView,
34394
35123
  {
@@ -34400,6 +35129,7 @@ function CdlStage({
34400
35129
  `${\u7AEF}-label-${edge.id}`
34401
35130
  ))
34402
35131
  ),
35132
+ laid.legend && /* @__PURE__ */ jsx(CdlLegend, { items: laid.legend }),
34403
35133
  debug && /* @__PURE__ */ jsxs("g", { pointerEvents: "none", children: [
34404
35134
  laid.bboxes.map((bbox, idx) => {
34405
35135
  const color = bbox.kind === "node" ? "#3b82f6" : bbox.kind === "lane-label" ? "#a855f7" : bbox.kind === "edge-label" ? "#10b981" : "#f59e0b";
@@ -36029,6 +36759,6 @@ function slugify(s) {
36029
36759
  return s.toLowerCase().replace(/[^a-z0-9぀-ゟ゠-ヿ一-龯]+/g, "-").replace(/^-|-$/g, "");
36030
36760
  }
36031
36761
 
36032
- export { ARROW_ANGLE_MAX_DEG, ARROW_ENDPOINT_CENTER_TOL, CHAIN_MIN_CLEARANCE, CHAIN_SHIFT_MAX_ACCUMULATED, CHAIN_SHIFT_MAX_ITER, CLASS_RELATION_LOOK, CLEARANCE_LABEL_LABEL, CLEARANCE_LANE_LABEL, CLEARANCE_NODE_LABEL, CLEARANCE_PATH_LABEL, COLUMN_GAP_VARIANCE_TOL, CdlDiagramThumbnail, CdlDiagramView, DETOUR_GAP, DETOUR_SLOT_GAP, DIST_LABEL_PATH_MAX, DIST_LABEL_PATH_MIN, EDGE_HEADS, EDGE_HEAD_DEFAULT, EDGE_HEAD_FILLS, EDGE_HEAD_FILL_DEFAULT, EDGE_LABEL_TEXT, EDGE_REVEALS, EDGE_REVEAL_DEFAULT, EDGE_STUB_OUT, EDGE_STYLES, ER_CARDINALITY_HEAD, FAN_GAP, FONT_RENDER_TOLERANCE_WORLD, FSM_ACTION_MARK, LABEL_INIT_CLEARANCE, LABEL_INIT_CLEARANCE_DOTTED_FLOW, LABEL_SIZE_TOLERANCE_WORLD, LABEL_TO_PATH_CLEARANCE, LANE_BORDER_CLEARANCE_TOL, MARKER_GLOW_RADIUS, MIN_COLUMN_ALIGNMENT_TOLERANCE, MIN_ROW_ALIGNMENT_TOLERANCE, NEAR_COLLISION_DEFAULT_MIN, NEAR_COLLISION_POLICY, NODE_FRAME_SELECTOR, NODE_KINDS, PERF_BUDGET_TOTAL_MS, PERF_SLOWEST_SAMPLE, RELATION_FOCUSES, RELATION_FOCUS_DEFAULT, ROW_GAP_VARIANCE_TOL, SEQUENCE_MESSAGE_LOOK, SPEC_CLEARANCE_POLICY, STAGE_SVG_SELECTOR, TARGET_LABEL_PATH_DIST, TONES, WCAG_AA_LARGE, WCAG_AA_NORMAL, applyDerivedValues, assertNever, attachEventHandlers, batch, bboxClearance, bboxDiffWorld, cdlAdapter, chart, classDiagram, compile, computeEventBindingKey, computeFormulaKey, computeInputSignalKey, computeScrollProgress, computeScrollTriggerKey, computeStateValues, computeViewportScale, computed, countEdgeCrossings, countOrderCrossings, createFormulaComputeds, createInputSignals, createInteractiveSignalsAccessor, createScrollProgressHandle, createScrollProgressSignals, diagram, drawsFigureTitle, effect, er, evaluate as evaluateFormula, extractIdentifiers, flow, flowchart, funnel, gantt, hasInteractivePrimitives, infrastructure, inputDefaultValue, interpolate, isLargeText, labelInitClearance, layout, layoutWithValidation, mindMap, network, orderNodes, parseFormula, pointRectEdgeDistance, pointToSegmentDistance2 as pointToSegmentDistance, predictLabelBBoxWorld, predictNodeBBoxWorld, projectEdgeLabel, projectNode, projectPathSegment, pxToWorld, quadrant, rectRectClearance, rectRectOverlapArea, relatedFocusSet, rendersRows, requiredContrastRatio, requiredNearClearance, requiredRowsHeight, requiredRowsWidth, requiredSpecClearance, resolveEdgeLabelOverlapsWithChain, resolveEdgeLabelOverlapsWithChainAndPropagate, resolveLabelClearanceAfterPathPasses, resolveLang, resolveOverlapsWithChain, resolveTarget, resolveTargetAll, resolveTextMetrics, segmentsIntersect2 as segmentsIntersect, sequence, sequenceBoardMetrics, sequenceStepId, signal, stateMachine, stateMachine2, swimlane, topology, tree, untrack, userJourney, validate, verifyAllDiagramsDom, verifyAuthorIntent, verifyAuthorIntentAll, verifyDiagramDom, visualValidate, visualValidateAll, visualValidateLaid, withDerivedValues, worldToPx, 木の札の幅 };
36762
+ export { ARROW_ANGLE_MAX_DEG, ARROW_ENDPOINT_CENTER_TOL, CHAIN_MIN_CLEARANCE, CHAIN_SHIFT_MAX_ACCUMULATED, CHAIN_SHIFT_MAX_ITER, CLASS_RELATION_LOOK, CLEARANCE_LABEL_LABEL, CLEARANCE_LANE_LABEL, CLEARANCE_NODE_LABEL, CLEARANCE_PATH_LABEL, COLUMN_GAP_VARIANCE_TOL, CdlDiagramThumbnail, CdlDiagramView, DETOUR_GAP, DETOUR_SLOT_GAP, DIST_LABEL_PATH_MAX, DIST_LABEL_PATH_MIN, EDGE_HEADS, EDGE_HEAD_DEFAULT, EDGE_HEAD_FILLS, EDGE_HEAD_FILL_DEFAULT, EDGE_LABEL_TEXT, EDGE_REVEALS, EDGE_REVEAL_DEFAULT, EDGE_STUB_OUT, EDGE_STYLES, ER_CARDINALITY_HEAD, FAN_GAP, FONT_RENDER_TOLERANCE_WORLD, FSM_ACTION_MARK, LABEL_INIT_CLEARANCE, LABEL_INIT_CLEARANCE_DOTTED_FLOW, LABEL_SIZE_TOLERANCE_WORLD, LABEL_TO_PATH_CLEARANCE, LANE_BORDER_CLEARANCE_TOL, LEGEND_MARKS, MARKER_GLOW_RADIUS, MIN_COLUMN_ALIGNMENT_TOLERANCE, MIN_ROW_ALIGNMENT_TOLERANCE, NEAR_COLLISION_DEFAULT_MIN, NEAR_COLLISION_POLICY, NODE_FRAME_SELECTOR, NODE_KINDS, PERF_BUDGET_TOTAL_MS, PERF_SLOWEST_SAMPLE, RELATION_FOCUSES, RELATION_FOCUS_DEFAULT, ROW_GAP_VARIANCE_TOL, SEQUENCE_MESSAGE_LOOK, SPEC_CLEARANCE_POLICY, STAGE_SVG_SELECTOR, TARGET_LABEL_PATH_DIST, TONES, WCAG_AA_LARGE, WCAG_AA_NORMAL, applyDerivedValues, assertNever, attachEventHandlers, batch, bboxClearance, bboxDiffWorld, cdlAdapter, chart, classDiagram, compile, computeEventBindingKey, computeFormulaKey, computeInputSignalKey, computeScrollProgress, computeScrollTriggerKey, computeStateValues, computeViewportScale, computed, countEdgeCrossings, countOrderCrossings, createFormulaComputeds, createInputSignals, createInteractiveSignalsAccessor, createScrollProgressHandle, createScrollProgressSignals, diagram, drawsFigureTitle, effect, er, evaluate as evaluateFormula, extractIdentifiers, flow, flowchart, funnel, gantt, hasInteractivePrimitives, infrastructure, inputDefaultValue, interpolate, isLargeText, labelInitClearance, layout, layoutWithValidation, mindMap, network, orderNodes, parseFormula, pointRectEdgeDistance, pointToSegmentDistance2 as pointToSegmentDistance, predictLabelBBoxWorld, predictNodeBBoxWorld, projectEdgeLabel, projectNode, projectPathSegment, pxToWorld, quadrant, rectRectClearance, rectRectOverlapArea, relatedFocusSet, rendersRows, requiredContrastRatio, requiredNearClearance, requiredRowsHeight, requiredRowsWidth, requiredSpecClearance, resolveEdgeLabelOverlapsWithChain, resolveEdgeLabelOverlapsWithChainAndPropagate, resolveLabelClearanceAfterPathPasses, resolveLang, resolveOverlapsWithChain, resolveTarget, resolveTargetAll, resolveTextMetrics, segmentsIntersect2 as segmentsIntersect, sequence, sequenceBoardMetrics, sequenceStepId, signal, stateMachine, stateMachine2, swimlane, topology, tree, untrack, userJourney, validate, verifyAllDiagramsDom, verifyAuthorIntent, verifyAuthorIntentAll, verifyDiagramDom, visualValidate, visualValidateAll, visualValidateLaid, withDerivedValues, worldToPx, 木の札の幅 };
36033
36763
  //# sourceMappingURL=index.js.map
36034
36764
  //# sourceMappingURL=index.js.map