@cardenelabs/cdl 0.123.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
14928
  }
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
- }
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);
@@ -20512,6 +20643,57 @@ function bowSharedStraightPaths(edges, nodes, skipIds, textMetrics = "proportion
20512
20643
  function nodeRect(n) {
20513
20644
  return { x: n.cx - n.w / 2, y: n.cy - n.h / 2, w: n.w, h: n.h };
20514
20645
  }
20646
+ function countPathObstacleCrossings(d, obstacles) {
20647
+ const segments = flattenPathSegments(extractPathSegments(d));
20648
+ return obstacles.filter((obstacle) => segmentsCrossRect(segments, nodeRect(obstacle))).length;
20649
+ }
20650
+ function makeBackDetourCandidate(A, B, from, to, obstacles, detourY, side, columnXOverride) {
20651
+ const lowIsA = A.y >= B.y;
20652
+ const low = lowIsA ? A : B;
20653
+ const lowBendY = low.y - VERTICAL_DETOUR_EXIT;
20654
+ const bandTop = Math.min(detourY, lowBendY);
20655
+ const bandBottom = Math.max(detourY, lowBendY);
20656
+ const bandObstacles = obstacles.map(nodeRect).filter((rect) => rect.y + rect.h >= bandTop && rect.y <= bandBottom);
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
+ }
20666
+ }
20667
+ const bendAY = lowIsA ? lowBendY : detourY;
20668
+ const bendBY = lowIsA ? detourY : lowBendY;
20669
+ const vertices = [
20670
+ A,
20671
+ { x: A.x, y: bendAY },
20672
+ { x: columnX, y: bendAY },
20673
+ { x: columnX, y: bendBY },
20674
+ { x: B.x, y: bendBY },
20675
+ B
20676
+ ];
20677
+ const d = verticesToPath(vertices);
20678
+ const high = lowIsA ? B : A;
20679
+ return {
20680
+ d,
20681
+ labelX: (high.x + columnX) / 2,
20682
+ crossings: countPathObstacleCrossings(d, obstacles),
20683
+ length: vertices.slice(1).reduce((sum, vertex, index) => {
20684
+ const previous = vertices[index];
20685
+ return sum + Math.hypot(vertex.x - previous.x, vertex.y - previous.y);
20686
+ }, 0)
20687
+ };
20688
+ }
20689
+ function chooseBackDetourCandidate(A, B, from, to, obstacles, detourY) {
20690
+ const left = makeBackDetourCandidate(A, B, from, to, obstacles, detourY, "left");
20691
+ const right = makeBackDetourCandidate(A, B, from, to, obstacles, detourY, "right");
20692
+ if (left.crossings !== right.crossings) return left.crossings < right.crossings ? left : right;
20693
+ if (Math.abs(left.length - right.length) >= 0.5) return left.length < right.length ? left : right;
20694
+ const preferred = Math.abs(A.x - B.x) < 0.5 ? "right" : A.x < B.x ? "left" : "right";
20695
+ return preferred === "left" ? left : right;
20696
+ }
20515
20697
  function rectGap(a, b) {
20516
20698
  const dx = Math.max(0, a.x - (b.x + b.w), b.x - (a.x + a.w));
20517
20699
  const dy = Math.max(0, a.y - (b.y + b.h), b.y - (a.y + a.h));
@@ -20589,7 +20771,7 @@ function sidePoint(n, side) {
20589
20771
  return { x: n.cx + n.w / 2, y: n.cy };
20590
20772
  }
20591
20773
  }
20592
- 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") {
20593
20775
  const labelBoxW = edge ? computeLabelBoxW(edge.label, edge.sub, textMetrics) : 0;
20594
20776
  const labelHalfW = labelBoxW / 2;
20595
20777
  const labelInit = labelInitClearance(edge);
@@ -20959,23 +21141,52 @@ function routePath(from, to, fromSide, toSide, obstacles = [], detourSide, _from
20959
21141
  };
20960
21142
  }
20961
21143
  if (topTopPair) {
20962
- 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
+ );
20963
21155
  const corner = 14;
20964
- const xDir = B.x > A.x ? 1 : -1;
20965
- return {
20966
- d: `M ${A.x} ${A.y}
20967
- L ${A.x} ${detourY + corner}
20968
- Q ${A.x} ${detourY}, ${A.x + xDir * corner} ${detourY}
20969
- L ${B.x - xDir * corner} ${detourY}
20970
- Q ${B.x} ${detourY}, ${B.x} ${detourY + corner}
20971
- L ${B.x} ${B.y}`,
20972
- labelX: (A.x + B.x) / 2,
21156
+ const xDir = backB.x > backA.x ? 1 : -1;
21157
+ const existing = {
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,
20973
21165
  // CAR-468 SSOT ... 旧 detourY - 30 magic number を LABEL_INIT_CLEARANCE (26、 CAR-570 で 34 → 26 縮小) に置換。
20974
21166
  // CAR-518 SSOT ... dotted-flow は 62 world (marker glow radius 分拡張) を labelInit で参照。
20975
21167
  // top-top detour の水平 segment 上方 labelInit world = spec 準拠距離。
20976
21168
  labelY: detourY - labelInit,
20977
21169
  labelAnchor: "middle"
20978
21170
  };
21171
+ const existingCrossings = countPathObstacleCrossings(existing.d, obstacles);
21172
+ if (existingCrossings === 0) return existing;
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);
21183
+ if (candidate.crossings >= existingCrossings) return existing;
21184
+ return {
21185
+ d: candidate.d,
21186
+ labelX: candidate.labelX,
21187
+ labelY: detourY - labelInit,
21188
+ labelAnchor: "middle"
21189
+ };
20979
21190
  }
20980
21191
  const midX = fromSide === "left" || fromSide === "right" ? B.x : A.x;
20981
21192
  const midY = fromSide === "top" || fromSide === "bottom" ? B.y : A.y;
@@ -21902,12 +22113,56 @@ function \u76F4\u524D\u306E\u6298\u308C\u3054\u3068\u305A\u3089\u3059(segs, \u6A
21902
22113
  return parts.join(" ");
21903
22114
  }
21904
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
+
21905
22149
  // src/layout/lanes.ts
21906
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
+ }
21907
22162
  function layoutLanes(diag) {
21908
22163
  const laneGap = diag.viewport?.laneGap ?? diag.viewport?.gap ?? DEFAULT_LANE_GAP;
21909
22164
  let cursorX = 0;
21910
- return diag.lanes.map((lane) => {
22165
+ const lanes = diag.lanes.map((lane, index) => {
21911
22166
  if (lane.posX !== void 0 && lane.posY !== void 0) {
21912
22167
  return {
21913
22168
  ...lane,
@@ -21918,7 +22173,9 @@ function layoutLanes(diag) {
21918
22173
  };
21919
22174
  }
21920
22175
  const resolvedX = lane.x ?? cursorX;
21921
- 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;
21922
22179
  return {
21923
22180
  ...lane,
21924
22181
  x: resolvedX,
@@ -21926,6 +22183,7 @@ function layoutLanes(diag) {
21926
22183
  height: LANE_LABEL_H
21927
22184
  };
21928
22185
  });
22186
+ return equalizeStageColumns(lanes);
21929
22187
  }
21930
22188
  function expandLanesForNodes(lanes, nodes) {
21931
22189
  const targetNodeGap = targetNodeNodeClearance();
@@ -21954,7 +22212,8 @@ function expandLanesForNodes(lanes, nodes) {
21954
22212
  if (lane.posX !== void 0 && lane.posY !== void 0) continue;
21955
22213
  const inside = nodes.filter((n) => n.lane === lane.id);
21956
22214
  const maxNodeW = inside.length > 0 ? Math.max(...inside.map((n) => n.w)) : 0;
21957
- 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);
21958
22217
  widthOf.set(lane.id, newWidth);
21959
22218
  deltaOf.set(lane.id, newWidth - lane.width);
21960
22219
  }
@@ -21979,6 +22238,34 @@ function expandLaneGapsForEdgeLabels(lanes, nodes, edges, textMetrics = "proport
21979
22238
  if (lanes.length < 2) return lanes;
21980
22239
  const layoutedLanes = lanes.filter((l) => !(l.posX !== void 0 && l.posY !== void 0));
21981
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
+ }
21982
22269
  const nodeById = new Map(nodes.map((n) => [n.id, n]));
21983
22270
  const sortedByX = [...layoutedLanes].sort((a, b) => a.x - b.x);
21984
22271
  const laneIndexById = new Map(sortedByX.map((lane, i) => [lane.id, i]));
@@ -22050,7 +22337,7 @@ function recomputeContainedLanes(lanes, nodes) {
22050
22337
  lifelineBottom = Math.max(lifelineBottom, bottom);
22051
22338
  }
22052
22339
  }
22053
- return lanes.map((lane) => {
22340
+ const contained = lanes.map((lane) => {
22054
22341
  if (lane.lifeline) {
22055
22342
  return {
22056
22343
  ...lane,
@@ -22068,6 +22355,7 @@ function recomputeContainedLanes(lanes, nodes) {
22068
22355
  height: bottom - top + CONTAIN_PAD * 2 + LANE_LABEL_H
22069
22356
  };
22070
22357
  });
22358
+ return equalizeStageColumns(contained, nodes);
22071
22359
  }
22072
22360
 
22073
22361
  // src/layout/head-label.ts
@@ -22217,11 +22505,15 @@ function requiredStackGapForEdgeLabels(diag, allStacks) {
22217
22505
  }
22218
22506
  function layoutNodes(diag, lanes, textMetrics) {
22219
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];
22220
22512
  const allStacks = Array.from(new Set(diag.nodes.map((n) => n.stack))).sort((a, b) => a - b);
22221
22513
  const rowH = /* @__PURE__ */ new Map();
22222
22514
  for (const stack of allStacks) {
22223
22515
  const nodesAtStack = diag.nodes.filter((n) => n.stack === stack);
22224
- 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)));
22225
22517
  rowH.set(stack, h);
22226
22518
  }
22227
22519
  const rowCy = /* @__PURE__ */ new Map();
@@ -22257,7 +22549,7 @@ function layoutNodes(diag, lanes, textMetrics) {
22257
22549
  if (!lane) {
22258
22550
  throw new Error(`cdl layout: node "${n.id}" \u306E lane "${n.lane}" \u304C\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u306A\u3044`);
22259
22551
  }
22260
- const size = NODE_SIZE[n.kind];
22552
+ const size = nodeSize(n);
22261
22553
  if (n.posX !== void 0 && n.posY !== void 0) {
22262
22554
  const w2 = n.posW ?? n.w ?? autoRowsWidth(n, size.w, textMetrics);
22263
22555
  const h2 = n.posH ?? n.h ?? rowH.get(n.stack) ?? size.h;
@@ -22380,6 +22672,9 @@ function dropUnsafeLaneDx(dxByLane, nodes) {
22380
22672
  function applyRowLevelNodeShifts(nodes, lanes, nodeShifts) {
22381
22673
  const dyByStack = aggregateRowDy(nodes, nodeShifts);
22382
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
+ }
22383
22678
  let moved = false;
22384
22679
  const outNodes = nodes.map((n) => {
22385
22680
  const dx = dxByLane.get(n.lane) ?? 0;
@@ -22499,7 +22794,15 @@ function layout(diag) {
22499
22794
  );
22500
22795
  }
22501
22796
  }
22502
- 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;
22503
22806
  return {
22504
22807
  id: diag.id,
22505
22808
  topic: diag.topic,
@@ -22526,6 +22829,7 @@ function layout(diag) {
22526
22829
  ...textMetrics === "monospace" ? { textMetrics: "monospace" } : {},
22527
22830
  // 種類も配色と同じく空の目印を出さない。 遮断をここに揃えると手書きの図にも効く
22528
22831
  ...diag.type ? { type: diag.type } : {},
22832
+ ...legendLayout ? { legend: legendLayout.legend } : {},
22529
22833
  bboxes,
22530
22834
  collisions,
22531
22835
  nearCollisions,
@@ -22714,6 +23018,11 @@ function validate(diag) {
22714
23018
  \u2192 \u4FEE\u6B63\u4F8B: .lane("${n.lane}", { x: 0, width: 400 }) \u3092 node \u5BA3\u8A00\u524D\u306B\u8FFD\u52A0`
22715
23019
  );
22716
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
+ }
22717
23026
  }
22718
23027
  for (const e of diag.edges) {
22719
23028
  if (!nodeIds.has(e.from)) {
@@ -24549,6 +24858,10 @@ function uniformLifelineEndY(lanes, nodes, starts = lifelineStartYs(nodes)) {
24549
24858
 
24550
24859
  // src/render/edge-head.ts
24551
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
+ }
24552
24865
  var EDGE_HEAD_SHAPE = {
24553
24866
  // 何も描かない端。 marker 自体を出さないので `d` は読まれない
24554
24867
  none: { d: "", w: 10, h: 10, refX: 0, filled: false },
@@ -24603,6 +24916,7 @@ function \u7B49\u5E45\u306E\u5B57(text, fontSize) {
24603
24916
  });
24604
24917
  return out;
24605
24918
  }
24919
+ var METRO_PENDING_ATTRIBUTE = "data-cdl-pending";
24606
24920
  function defaultEdgeWidth(active) {
24607
24921
  return active ? 2.5 : 1.25;
24608
24922
  }
@@ -24621,12 +24935,14 @@ function CdlEdgeView({
24621
24935
  drawing,
24622
24936
  progress,
24623
24937
  stateValues,
24624
- flowing = false
24938
+ flowing = false,
24939
+ pending = false
24625
24940
  }) {
24626
24941
  const color = edgeColor(edge);
24627
- const marker = edgeHeadMarkerName(edge.tone, edge.head, !active, edge.headFill);
24628
- const tailMarker = edge.tailHead === void 0 ? void 0 : edgeHeadMarkerName(edge.tone, edge.tailHead, !active, edge.tailHeadFill);
24629
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);
24630
24946
  const isDotted = style === "dotted-flow";
24631
24947
  const isPassThrough = isDotted && Boolean(edge.dThrough);
24632
24948
  const lineDotsD = isDotted ? shrinkPathEnd(edge.d, 12) : edge.d;
@@ -24636,6 +24952,7 @@ function CdlEdgeView({
24636
24952
  return /* @__PURE__ */ jsxs(
24637
24953
  "g",
24638
24954
  {
24955
+ ...pending ? { [METRO_PENDING_ATTRIBUTE]: "true" } : {},
24639
24956
  "data-cdl-edge": edge.id,
24640
24957
  "data-cdl-routing": edge.routing === "metro" ? "metro" : void 0,
24641
24958
  "data-cdl-from": edge.from,
@@ -24661,11 +24978,12 @@ function CdlEdgeView({
24661
24978
  fill: "none",
24662
24979
  stroke: color,
24663
24980
  strokeOpacity: active ? 0.95 : 0.9,
24664
- strokeWidth: active ? 5 : 4,
24981
+ strokeWidth: edge.routing === "metro" ? METRO_LINE_WIDTH : active ? 5 : 4,
24665
24982
  strokeDasharray: "0 18",
24666
24983
  strokeLinecap: "round",
24667
24984
  style: {
24668
- 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
24669
24987
  }
24670
24988
  }
24671
24989
  ),
@@ -24720,7 +25038,9 @@ function SolidEdgePath({
24720
25038
  const boundWidth = edge.widthBind && stateValues ? resolveNumericAttr(edge.widthBind, stateValues, defaultWidth) : defaultWidth;
24721
25039
  const boundStroke = edge.strokeBind && stateValues ? interpolate(edge.strokeBind, stateValues) : color;
24722
25040
  const boundDashOffset = edge.dashOffsetBind && stateValues ? resolveNumericAttr(edge.dashOffsetBind, stateValues, 0) : void 0;
24723
- 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";
24724
25044
  return /* @__PURE__ */ jsx(
24725
25045
  "path",
24726
25046
  {
@@ -24729,14 +25049,15 @@ function SolidEdgePath({
24729
25049
  fill: "none",
24730
25050
  stroke: boundStroke,
24731
25051
  strokeOpacity: active ? 0.95 : 0.9,
24732
- strokeWidth: boundWidth,
25052
+ strokeWidth,
24733
25053
  strokeDasharray: dashArray,
24734
25054
  strokeDashoffset: boundDashOffset,
24735
25055
  strokeLinecap: "round",
24736
25056
  markerEnd: marker === void 0 ? void 0 : `url(#${marker})`,
24737
25057
  markerStart: tailMarker === void 0 ? void 0 : `url(#${tailMarker})`,
24738
25058
  style: {
24739
- 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
24740
25061
  }
24741
25062
  }
24742
25063
  );
@@ -25033,7 +25354,11 @@ function \u5272\u308B\u5019\u88DC(text) {
25033
25354
  function \u5165\u3063\u3066\u3044\u308B([\u5DE6, \u53F3]) {
25034
25355
  return \u5DE6 !== "" && \u53F3 !== "";
25035
25356
  }
25036
- function CardNode({ node, active }) {
25357
+ function CardNode({
25358
+ node,
25359
+ active,
25360
+ compactTitleInset
25361
+ }) {
25037
25362
  const accent = nodeAccent(node, "var(--cdl-tone-accent, #2d6a8f)");
25038
25363
  const x = node.cx - node.w / 2;
25039
25364
  const y = node.cy - node.h / 2;
@@ -25051,12 +25376,12 @@ function CardNode({ node, active }) {
25051
25376
  });
25052
25377
  const \u305A\u3089\u3057 = \u7E70\u308A\u4E0A\u3052 - \u4F59\u308A;
25053
25378
  const titleY = compact ? \u5C0F\u578B\u306B\u547C\u3073\u540D ? node.h / 2 - 2 : node.h / 2 + 8 : 86 - \u305A\u3089\u3057;
25054
- const titleX = compact ? node.w / 2 : 26;
25055
- const paddingX = compact ? 16 : 52;
25379
+ const titleX = compact ? compactTitleInset ?? node.w / 2 : 26;
25380
+ const paddingX = compact ? (compactTitleInset ?? 8) * 2 : 52;
25056
25381
  const availableW = node.w - paddingX;
25057
25382
  const initialTitleSize = compact ? 20 : 24;
25058
25383
  const titleSize = fitTextSize(node.title, availableW, initialTitleSize, 11);
25059
- const textAnchor = compact ? "middle" : "start";
25384
+ const textAnchor = compact && compactTitleInset === void 0 ? "middle" : "start";
25060
25385
  const subtitleY = 124 - \u305A\u3089\u3057;
25061
25386
  const \u6700\u5927\u306E\u884C\u9001\u308A = SUBTITLE_SIZE * \u884C\u9001\u308A\u306E\u500D\u7387;
25062
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;
@@ -25819,7 +26144,9 @@ function GenericNode({
25819
26144
  value,
25820
26145
  stateValues,
25821
26146
  \u5024\u306E\u540D\u524D,
25822
- textMetrics = "proportional"
26147
+ textMetrics = "proportional",
26148
+ showKindIcon = true,
26149
+ metroDecision = false
25823
26150
  }) {
25824
26151
  const base = STYLES[node.kind] ?? FALLBACK;
25825
26152
  const toned = toneColor3(node.tone);
@@ -25850,14 +26177,14 @@ function GenericNode({
25850
26177
  if (shape === "diamond") {
25851
26178
  const cx = w / 2;
25852
26179
  const cy = h / 2;
25853
- const pad = 8;
26180
+ const pad = metroDecision ? 0 : 8;
25854
26181
  return /* @__PURE__ */ jsx(
25855
26182
  "path",
25856
26183
  {
25857
26184
  d: `M ${cx} ${pad} L ${w - pad} ${cy} L ${cx} ${h - pad} L ${pad} ${cy} Z`,
25858
26185
  fill: "var(--cdl-node-fill, #ffffff)",
25859
26186
  stroke: active ? style.accent : "var(--cdl-divider, #d4d4d8)",
25860
- strokeWidth: active ? 3 : 1.25
26187
+ strokeWidth: metroDecision ? 1.5 : active ? 3 : 1.25
25861
26188
  }
25862
26189
  );
25863
26190
  }
@@ -25921,11 +26248,11 @@ function GenericNode({
25921
26248
  const \u305A\u3089\u3057 = \u7E70\u308A\u4E0A\u3052 - \u4F59\u308A;
25922
26249
  const \u7D75\u306E\u82AF = centered && STYLES[node.kind]?.shape === "cloud" ? h * CLOUD_ART_CENTER_RISE : 0;
25923
26250
  const eyebrowY = centered ? h / 2 - 26 - \u7D75\u306E\u82AF : 32 + cylinderShift + \u4F59\u308A;
25924
- 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;
25925
26252
  const subtitleY = centered ? h / 2 + 28 - \u7D75\u306E\u82AF : 96 + cylinderShift - \u305A\u3089\u3057;
25926
26253
  const iconX = centered ? w / 2 + 40 : w - 48;
25927
26254
  const iconY = centered ? h / 2 - 44 - \u7D75\u306E\u82AF : 18 + cylinderShift;
25928
- const dominantBaseline = centered ? "middle" : void 0;
26255
+ const dominantBaseline = centered && !metroDecision ? "middle" : void 0;
25929
26256
  return /* @__PURE__ */ jsxs(
25930
26257
  "g",
25931
26258
  {
@@ -25934,12 +26261,12 @@ function GenericNode({
25934
26261
  "data-cdl-look": style.look,
25935
26262
  children: [
25936
26263
  shapeEl,
25937
- /* @__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: [
25938
26265
  /* @__PURE__ */ jsx("rect", { width: 24, height: 24, fill: "transparent" }),
25939
26266
  /* @__PURE__ */ jsx("path", { d: style.icon, fill: style.eyebrowColor, opacity: 0.7, transform: "scale(1)" })
25940
26267
  ] }),
25941
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 }),
25942
- /* @__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 }),
25943
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) }),
25944
26271
  resolvedValue && /* @__PURE__ */ jsx(
25945
26272
  "text",
@@ -25988,50 +26315,176 @@ function GenericNode({
25988
26315
  var MARK_INNER_RATIO = 0.56;
25989
26316
  var RING_WIDTH_ACTIVE = 6;
25990
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;
25991
26322
  var MARK_DEFAULT = "var(--cdl-tone-accent, #2d6a8f)";
25992
26323
  function markRadius(node) {
25993
26324
  return Math.min(node.w, node.h) / 2;
25994
26325
  }
25995
- function MarkStartNode({ node }) {
26326
+ function MarkStartNode({
26327
+ node,
26328
+ metro = false
26329
+ }) {
25996
26330
  const \u8272 = nodeAccent(node, MARK_DEFAULT);
25997
- return /* @__PURE__ */ jsx("g", { transform: `translate(${node.cx - node.w / 2} ${node.cy - node.h / 2})`, children: /* @__PURE__ */ jsx(
25998
- "circle",
26331
+ return /* @__PURE__ */ jsx(
26332
+ "g",
25999
26333
  {
26000
- "data-cdl-role": "node-body",
26001
- "data-cdl-mark": "start",
26002
- cx: node.w / 2,
26003
- cy: node.h / 2,
26004
- r: markRadius(node),
26005
- 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
+ )
26006
26347
  }
26007
- ) });
26348
+ );
26008
26349
  }
26009
- function MarkEndNode({ node, active }) {
26350
+ function MarkEndNode({
26351
+ node,
26352
+ active,
26353
+ metro = false
26354
+ }) {
26010
26355
  const \u8272 = nodeAccent(node, MARK_DEFAULT);
26011
- const r = markRadius(node);
26012
- const \u8F2A\u306E\u592A\u3055 = active ? RING_WIDTH_ACTIVE : RING_WIDTH_IDLE;
26013
- 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: [
26014
26409
  /* @__PURE__ */ jsx(
26015
26410
  "circle",
26016
26411
  {
26017
26412
  "data-cdl-role": "node-body",
26018
- "data-cdl-mark": "end",
26019
- cx: node.w / 2,
26020
- cy: node.h / 2,
26021
- 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,
26022
26417
  fill: "var(--cdl-node-fill, #ffffff)",
26023
- stroke: \u8272,
26024
- 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 }
26025
26421
  }
26026
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: [
26027
26463
  /* @__PURE__ */ jsx(
26028
26464
  "circle",
26029
26465
  {
26030
- "data-cdl-role": "node-inner",
26031
- cx: node.w / 2,
26032
- cy: node.h / 2,
26033
- r: Math.max(0, r * MARK_INNER_RATIO),
26034
- 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
26035
26488
  }
26036
26489
  )
26037
26490
  ] });
@@ -26363,27 +26816,6 @@ function interpolateColor(template, stateValues, fallback) {
26363
26816
  if (!template) return fallback;
26364
26817
  return interpolate(template, stateValues);
26365
26818
  }
26366
- var STATION_STROKE_ACTIVE = 4;
26367
- var STATION_STROKE_IDLE = 3;
26368
- var STATION_DEFAULT = "var(--cdl-tone-accent, #2d6a8f)";
26369
- function StationNode({ node, active }) {
26370
- const strokeWidth = active ? STATION_STROKE_ACTIVE : STATION_STROKE_IDLE;
26371
- const radius = Math.max(0, Math.min(node.w, node.h) / 2 - strokeWidth / 2);
26372
- const \u5F62 = /* @__PURE__ */ jsx(
26373
- "circle",
26374
- {
26375
- "data-cdl-role": "node-body",
26376
- "data-cdl-mark": "station",
26377
- cx: node.cx,
26378
- cy: node.cy,
26379
- r: radius,
26380
- fill: "var(--cdl-node-fill, #ffffff)",
26381
- stroke: nodeAccent(node, STATION_DEFAULT),
26382
- strokeWidth
26383
- }
26384
- );
26385
- return renderShapeWithOutsideLabels(\u5F62, node, "middle");
26386
- }
26387
26819
 
26388
26820
  // src/kinds/label-stack.ts
26389
26821
  function \u540D\u672D\u306E\u6BB5\u3092\u307B\u3069\u304F(\u771F\u306Ey, \u9593\u9694, \u4E0A\u9650, \u4E0B\u9650) {
@@ -33545,10 +33977,14 @@ function CdlNodeView({
33545
33977
  stateValues,
33546
33978
  currentPhaseId,
33547
33979
  chartLineScale,
33980
+ compactTitleInset,
33548
33981
  \u95A2\u4FC2\u306E\u53D7\u3051\u53E3,
33549
33982
  lang,
33550
33983
  textMetrics,
33551
- \u5024\u306E\u540D\u524D
33984
+ \u5024\u306E\u540D\u524D,
33985
+ metroDiagram = false,
33986
+ metroDecision = false,
33987
+ stationNameRight = false
33552
33988
  }) {
33553
33989
  const value = node.value ? interpolate(node.value, stateValues, \u5024\u306E\u540D\u524D) : void 0;
33554
33990
  const rows = (node.rows ?? []).map((r) => interpolate(r, stateValues, \u5024\u306E\u540D\u524D));
@@ -33589,7 +34025,7 @@ function CdlNodeView({
33589
34025
  case "event":
33590
34026
  return /* @__PURE__ */ jsx(EventNode, { node: resolvedNode, active, progress });
33591
34027
  case "card":
33592
- return /* @__PURE__ */ jsx(CardNode, { node: resolvedNode, active });
34028
+ return /* @__PURE__ */ jsx(CardNode, { node: resolvedNode, active, compactTitleInset });
33593
34029
  // 時系列のやり取り (#578)。 1 つの箱が図を丸ごと描く = 縦が時間で、参加者は箱を持たない
33594
34030
  case "sequence-board":
33595
34031
  return /* @__PURE__ */ jsx(
@@ -33876,11 +34312,13 @@ function CdlNodeView({
33876
34312
  case "shape-customer-service":
33877
34313
  return /* @__PURE__ */ jsx(ShapeCustomerServiceNode, { node: resolvedNode, active });
33878
34314
  case "mark-start":
33879
- return /* @__PURE__ */ jsx(MarkStartNode, { node: resolvedNode, active });
34315
+ return /* @__PURE__ */ jsx(MarkStartNode, { node: resolvedNode, active, metro: metroDiagram });
33880
34316
  case "mark-end":
33881
- return /* @__PURE__ */ jsx(MarkEndNode, { node: resolvedNode, active });
34317
+ return /* @__PURE__ */ jsx(MarkEndNode, { node: resolvedNode, active, metro: metroDiagram });
33882
34318
  case "station":
33883
- 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 });
33884
34322
  default:
33885
34323
  return /* @__PURE__ */ jsx(
33886
34324
  GenericNode,
@@ -33890,7 +34328,9 @@ function CdlNodeView({
33890
34328
  value: node.value,
33891
34329
  stateValues,
33892
34330
  \u5024\u306E\u540D\u524D,
33893
- textMetrics
34331
+ textMetrics,
34332
+ showKindIcon: !(metroDiagram && node.kind === "decision"),
34333
+ metroDecision
33894
34334
  }
33895
34335
  );
33896
34336
  }
@@ -34004,6 +34444,112 @@ function \u7BB1\u306E\u4E0A\u306E\u8A9E(node, textMetrics = "proportional") {
34004
34444
  )
34005
34445
  ] });
34006
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
+ }
34007
34553
 
34008
34554
  // src/render/relation-focus.ts
34009
34555
  function relatedFocusSet(edges, nodeId) {
@@ -34016,10 +34562,17 @@ function relatedFocusSet(edges, nodeId) {
34016
34562
  }
34017
34563
  return \u96C6\u5408;
34018
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;
34019
34569
  function \u63CF\u304F\u3082\u306E\u304C\u3042\u308B(laid) {
34570
+ if (laid.legend && laid.legend.length > 0) return true;
34020
34571
  if (laid.edges.length > 0) return true;
34021
34572
  if (laid.nodes.some((n) => isRenderedNode(n))) return true;
34022
- 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
+ );
34023
34576
  }
34024
34577
  function CdlStage({
34025
34578
  laid,
@@ -34060,6 +34613,7 @@ function CdlStage({
34060
34613
  [\u95A2\u4FC2\u3092\u5149\u3089\u305B\u308B, \u7126\u70B9, laid.edges]
34061
34614
  );
34062
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");
34063
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;
34064
34618
  const \u77E2\u5370\u306E\u51FA\u756A = useMemo(() => {
34065
34619
  const \u521D\u51FA = /* @__PURE__ */ new Map();
@@ -34088,9 +34642,15 @@ function CdlStage({
34088
34642
  const first = \u521D\u51FA.get(edgeId);
34089
34643
  if (first === void 0) return false;
34090
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;
34091
34650
  }
34092
34651
  };
34093
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);
34094
34654
  const drawSet = new Set(currentPhase?.draw ?? []);
34095
34655
  const chartLineScales = useMemo(() => {
34096
34656
  const nodes = laid.nodes.filter((node) => node.kind === "chart-line");
@@ -34178,7 +34738,53 @@ function CdlStage({
34178
34738
  )
34179
34739
  );
34180
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]);
34181
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
+ }
34182
34788
  if (!\u63CF\u304F\u3082\u306E\u304C\u3042\u308B(laid)) return null;
34183
34789
  return /* @__PURE__ */ jsxs("div", { className: compact ? "px-3 py-3" : "px-6 py-6", style: compact ? void 0 : { minHeight: 460 }, children: [
34184
34790
  /* @__PURE__ */ jsxs(
@@ -34198,6 +34804,7 @@ function CdlStage({
34198
34804
  children: [
34199
34805
  /* @__PURE__ */ jsxs("defs", { children: [
34200
34806
  edgeHeadMarkers,
34807
+ metroReturnArrowMarkers,
34201
34808
  /* @__PURE__ */ jsx(
34202
34809
  "marker",
34203
34810
  {
@@ -34234,7 +34841,61 @@ function CdlStage({
34234
34841
  pointerEvents: "none"
34235
34842
  }
34236
34843
  ),
34237
- 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(
34238
34899
  "rect",
34239
34900
  {
34240
34901
  "data-cdl-role": "lane-container",
@@ -34270,7 +34931,85 @@ function CdlStage({
34270
34931
  }
34271
34932
  );
34272
34933
  })(),
34273
- 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(
34274
35013
  "text",
34275
35014
  {
34276
35015
  "data-cdl-role": "lane-label",
@@ -34287,7 +35026,34 @@ function CdlStage({
34287
35026
  },
34288
35027
  lane.id
34289
35028
  )),
34290
- 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(
34291
35057
  CdlEdgeView,
34292
35058
  {
34293
35059
  edge,
@@ -34295,16 +35061,19 @@ function CdlStage({
34295
35061
  flowing: \u7126\u70B9\u306E\u96C6\u5408?.has(edge.id) === true && !\u52D5\u304D\u3092\u63A7\u3048\u308B,
34296
35062
  drawing: \u77E2\u5370\u306E\u51FA\u756A.\u4F38\u3070\u3059\u304B(edge.id),
34297
35063
  progress: \u77E2\u5370\u306E\u51FA\u756A.\u4F38\u3070\u3059\u304B(edge.id) ? \u63CF\u304F\u9032\u307F : progress,
34298
- stateValues
35064
+ stateValues,
35065
+ pending: edge.routing === "metro" && \u77E2\u5370\u306E\u51FA\u756A.\u307E\u3060\u304B(edge.id)
34299
35066
  },
34300
35067
  edge.id
34301
35068
  )),
34302
35069
  laid.nodes.map((node) => {
34303
35070
  const \u63CF\u304F = drawSet.has(node.id);
35071
+ const \u6BB5\u968E\u306E\u88DC\u8DB3 = stageNoteNodeIds.has(node.id);
34304
35072
  const view = /* @__PURE__ */ jsx(
34305
35073
  CdlNodeView,
34306
35074
  {
34307
- 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,
34308
35077
  active: activeSet.has(node.id),
34309
35078
  lang: laid.lang,
34310
35079
  textMetrics: laid.textMetrics,
@@ -34314,13 +35083,32 @@ function CdlStage({
34314
35083
  stateValues,
34315
35084
  \u5024\u306E\u540D\u524D,
34316
35085
  currentPhaseId: currentPhase?.id,
34317
- 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)
34318
35090
  }
34319
35091
  );
34320
35092
  return /* @__PURE__ */ jsx("g", { children: view }, node.id);
34321
35093
  }),
34322
- 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}`)),
34323
- 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(
34324
35112
  CdlEdgeLabelView,
34325
35113
  {
34326
35114
  edge,
@@ -34329,7 +35117,7 @@ function CdlStage({
34329
35117
  },
34330
35118
  `label-${edge.id}`
34331
35119
  )),
34332
- 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(
34333
35121
  (edge) => ["head", "tail"].map((\u7AEF) => /* @__PURE__ */ jsx(
34334
35122
  CdlEdgeEndLabelView,
34335
35123
  {
@@ -34341,6 +35129,7 @@ function CdlStage({
34341
35129
  `${\u7AEF}-label-${edge.id}`
34342
35130
  ))
34343
35131
  ),
35132
+ laid.legend && /* @__PURE__ */ jsx(CdlLegend, { items: laid.legend }),
34344
35133
  debug && /* @__PURE__ */ jsxs("g", { pointerEvents: "none", children: [
34345
35134
  laid.bboxes.map((bbox, idx) => {
34346
35135
  const color = bbox.kind === "node" ? "#3b82f6" : bbox.kind === "lane-label" ? "#a855f7" : bbox.kind === "edge-label" ? "#10b981" : "#f59e0b";
@@ -35970,6 +36759,6 @@ function slugify(s) {
35970
36759
  return s.toLowerCase().replace(/[^a-z0-9぀-ゟ゠-ヿ一-龯]+/g, "-").replace(/^-|-$/g, "");
35971
36760
  }
35972
36761
 
35973
- 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, 木の札の幅 };
35974
36763
  //# sourceMappingURL=index.js.map
35975
36764
  //# sourceMappingURL=index.js.map