@cardenelabs/cdl 0.16.0 → 0.17.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.cjs CHANGED
@@ -9494,6 +9494,9 @@ function diagram(id, options) {
9494
9494
  side: opts.side,
9495
9495
  style: opts.style,
9496
9496
  head: opts.head,
9497
+ headFill: opts.headFill,
9498
+ tailHead: opts.tailHead,
9499
+ tailHeadFill: opts.tailHeadFill,
9497
9500
  guard: opts.guard,
9498
9501
  cardinality: opts.cardinality,
9499
9502
  labelOffsetX: opts.labelOffsetX,
@@ -9617,6 +9620,9 @@ function diagram(id, options) {
9617
9620
  side: d.side,
9618
9621
  style: d.style,
9619
9622
  head: d.head,
9623
+ headFill: d.headFill,
9624
+ tailHead: d.tailHead,
9625
+ tailHeadFill: d.tailHeadFill,
9620
9626
  guard: d.guard,
9621
9627
  cardinality: d.cardinality,
9622
9628
  labelOffsetX: d.labelOffsetX,
@@ -11718,6 +11724,9 @@ function hasRenderedLabel(e) {
11718
11724
  return Boolean(e.label?.trim() || e.sub?.trim());
11719
11725
  }
11720
11726
  var KINDS_WITHOUT_TITLE_TEXT = /* @__PURE__ */ new Set([
11727
+ // 時系列のやり取り (#578)。 図を丸ごと描く種別で、描くのは面々の名前と言づての語。
11728
+ // 箱の `title` は使わない
11729
+ "sequence-board",
11721
11730
  // 形だけを描く (DynShapeNode)
11722
11731
  "dyn-rect",
11723
11732
  "dyn-circle",
@@ -11806,6 +11815,7 @@ var GLYPH_CAP_RATIO = 0.72;
11806
11815
  var ROW_FONT_STORAGE = 26;
11807
11816
  var ROW_FONT_GENERIC = 21;
11808
11817
  var STORAGE_TITLE_BASELINE = 78;
11818
+ var SUBTITLE_ROW_SHIFT = 46;
11809
11819
  var STORAGE_ROW_DIVIDER_Y = Math.round(
11810
11820
  (STORAGE_TITLE_BASELINE + (ROW_TOP_STORAGE - ROW_FONT_STORAGE * GLYPH_CAP_RATIO)) / 2
11811
11821
  );
@@ -13409,7 +13419,10 @@ var TOKENS = {
13409
13419
  // 印は箱いっぱいに描く (線が箱の縁で止まるため) ので、この値が丸の直径になる。
13410
13420
  // 箱としての最小 (80x40) より大きく、名前を持つ箱 (card 320x150) よりは明らかに小さい
13411
13421
  "mark-start": { w: 96, h: 96 },
13412
- "mark-end": { w: 96, h: 96 }
13422
+ "mark-end": { w: 96, h: 96 },
13423
+ // 時系列のやり取り (#578)。 実寸は中身から決まる (`sequenceBoardMetrics`) ので、
13424
+ // ここは書かなかった時の受け皿
13425
+ "sequence-board": { w: 960, h: 640 }
13413
13426
  },
13414
13427
  // row gap (stack 間の vertical gap、 lane 内 / 跨ぎ共通)
13415
13428
  // edge label が node の上 36 px に配置されるため、 boxH 68 + 36 + 余裕 = 100 必要。
@@ -15072,8 +15085,12 @@ function requiredRowsWidth(rows, kind) {
15072
15085
  function autoStorageHeight(n, defaultH) {
15073
15086
  if (n.kind !== "storage") return n.h ?? defaultH;
15074
15087
  if (n.h != null) return n.h;
15075
- if (!n.rows || n.rows.length === 0) return 150;
15076
- return Math.max(defaultH, requiredRowsHeight("storage", n.rows.length) ?? defaultH);
15088
+ const \u5370\u3042\u308A = n.rowMarks !== void 0;
15089
+ const \u547C\u3073\u540D\u306E\u5206 = \u5370\u3042\u308A && n.subtitle ? SUBTITLE_ROW_SHIFT : 0;
15090
+ if (!n.rows || n.rows.length === 0) return 150 + \u547C\u3073\u540D\u306E\u5206;
15091
+ const \u5FC5\u8981 = requiredRowsHeight("storage", n.rows.length) ?? defaultH;
15092
+ if (\u5370\u3042\u308A) return \u5FC5\u8981 + \u547C\u3073\u540D\u306E\u5206;
15093
+ return Math.max(defaultH, \u5FC5\u8981);
15077
15094
  }
15078
15095
  function autoRowsWidth(n, defaultW) {
15079
15096
  if (n.w != null) return n.w;
@@ -15161,8 +15178,11 @@ function layoutNodes(diag, lanes) {
15161
15178
  continue;
15162
15179
  }
15163
15180
  const cx = lane.x + lane.width / 2;
15164
- const cy = rowCy.get(n.stack);
15165
- const h = n.h ?? rowH.get(n.stack);
15181
+ const \u6BB5\u306E\u82AF = rowCy.get(n.stack);
15182
+ const \u6BB5\u306E\u9AD8\u3055 = rowH.get(n.stack);
15183
+ const \u81EA\u524D\u306E\u9AD8\u3055 = n.rowMarks !== void 0;
15184
+ const h = n.h ?? (\u81EA\u524D\u306E\u9AD8\u3055 ? autoStorageHeight(n, size.h) : \u6BB5\u306E\u9AD8\u3055);
15185
+ const cy = \u81EA\u524D\u306E\u9AD8\u3055 ? \u6BB5\u306E\u82AF - \u6BB5\u306E\u9AD8\u3055 / 2 + h / 2 : \u6BB5\u306E\u82AF;
15166
15186
  const w = autoRowsWidth(n, size.w);
15167
15187
  out.push({
15168
15188
  ...n,
@@ -15491,10 +15511,25 @@ var NODE_KINDS = [
15491
15511
  // 流れの始まりと終わりの印 2 種 (#560) ... 塗った丸と輪で囲んだ丸。
15492
15512
  // 状態遷移図が使う。 印そのものが意味を持つので文字は描かない
15493
15513
  "mark-start",
15494
- "mark-end"
15514
+ "mark-end",
15515
+ // 時系列のやり取り (#578) ... 1 つの箱が図を丸ごと描く。 縦が時間で、参加者は箱を持たない
15516
+ "sequence-board"
15495
15517
  ];
15518
+ var EDGE_STYLES = ["solid", "dotted-flow", "dashed"];
15496
15519
  var EDGE_HEAD_DEFAULT = "triangle";
15497
- var EDGE_HEADS = ["triangle", "diamond", "open", "crow"];
15520
+ var EDGE_HEADS = [
15521
+ "none",
15522
+ "triangle",
15523
+ "diamond",
15524
+ "open",
15525
+ "crow",
15526
+ "one",
15527
+ "zero-one",
15528
+ "many",
15529
+ "zero-many"
15530
+ ];
15531
+ var EDGE_HEAD_FILL_DEFAULT = "solid";
15532
+ var EDGE_HEAD_FILLS = ["solid", "hollow"];
15498
15533
 
15499
15534
  // src/anim/core/easing.ts
15500
15535
  function cubicBezier(x1, y1, x2, y2) {
@@ -15989,7 +16024,7 @@ function pathSubpath(d, progress) {
15989
16024
  }
15990
16025
 
15991
16026
  // src/render/template-fields.ts
15992
- var TEXT_FIELDS = ["title", "eyebrow", "subtitle", "value", "wBind", "hBind", "visibleIf"];
16027
+ var TEXT_FIELDS = ["title", "eyebrow", "subtitle", "value", "wBind", "hBind", "visibleIf", "sequenceStep"];
15993
16028
  var LIST_FIELDS = ["rows"];
15994
16029
  var NUMERIC_FIELDS = ["opacity", "renderOffsetX", "renderOffsetY"];
15995
16030
  var SHAPE_TEMPLATE_FIELDS = ["source", "radius", "fillProgress", "angle", "level", "rotation", "fill"];
@@ -18314,6 +18349,7 @@ function runAxes(laid, diag, violations, counts, push, profile, skippedAxes) {
18314
18349
  if (!isRenderedNode(n)) continue;
18315
18350
  for (const row of n.rows) {
18316
18351
  if (DIVIDER_ONLY.test(row)) continue;
18352
+ if (n.rowMarks !== void 0) continue;
18317
18353
  if (!ROW_FORMAT.test(row)) {
18318
18354
  push("row-format", `node "${n.id}" row "${row}" \u304C "key: value" \u5F62\u5F0F\u3067\u306A\u3044`);
18319
18355
  }
@@ -18645,9 +18681,11 @@ function runAxes(laid, diag, violations, counts, push, profile, skippedAxes) {
18645
18681
  for (const [stack, group] of byStack) {
18646
18682
  if (group.length < 2) continue;
18647
18683
  const cy0 = group[0].cy;
18684
+ const top0 = cy0 - (group[0].h ?? 0) / 2;
18648
18685
  for (const n of group.slice(1)) {
18649
18686
  const diff = Math.abs(n.cy - cy0);
18650
- if (diff > MIN_ROW_ALIGNMENT_TOLERANCE) {
18687
+ const topDiff = Math.abs(n.cy - (n.h ?? 0) / 2 - top0);
18688
+ if (diff > MIN_ROW_ALIGNMENT_TOLERANCE && topDiff > MIN_ROW_ALIGNMENT_TOLERANCE) {
18651
18689
  push(
18652
18690
  "row-alignment",
18653
18691
  `stack=${stack} row \u5185 node "${n.id}" cy=${n.cy.toFixed(1)} \u304C row \u57FA\u6E96 cy=${cy0.toFixed(1)} \u3068 ${diff.toFixed(1)} world \u5DEE (\u8A31\u5BB9 ${MIN_ROW_ALIGNMENT_TOLERANCE})`
@@ -19696,15 +19734,18 @@ function runAxes(laid, diag, violations, counts, push, profile, skippedAxes) {
19696
19734
  if (group.length < 3) continue;
19697
19735
  const sorted = [...group].sort((a, b) => a.cy - b.cy);
19698
19736
  const gaps = [];
19737
+ const tops = [];
19699
19738
  for (let i = 1; i < sorted.length; i++) {
19700
19739
  const prev = sorted[i - 1];
19701
19740
  const cur = sorted[i];
19702
19741
  gaps.push(cur.cy - cur.h / 2 - (prev.cy + prev.h / 2));
19742
+ tops.push(cur.cy - cur.h / 2 - (prev.cy - prev.h / 2));
19703
19743
  }
19704
19744
  const maxGap = Math.max(...gaps);
19705
19745
  const minGap = Math.min(...gaps);
19706
19746
  const variance = maxGap - minGap;
19707
- if (variance > COLUMN_GAP_VARIANCE_TOL) {
19747
+ const topVariance = Math.max(...tops) - Math.min(...tops);
19748
+ if (variance > COLUMN_GAP_VARIANCE_TOL && topVariance > COLUMN_GAP_VARIANCE_TOL) {
19708
19749
  push(
19709
19750
  "column-gap-uniform",
19710
19751
  `lane "${laneId}" \u5185 node \u9593\u306E\u7AEF\u9593 gap variance ${variance.toFixed(1)} world \u304C spec ${COLUMN_GAP_VARIANCE_TOL} \u8D85\u904E (min=${minGap.toFixed(0)} max=${maxGap.toFixed(0)})`
@@ -21178,14 +21219,42 @@ function StateDiffCard({
21178
21219
 
21179
21220
  // src/render/edge-head.ts
21180
21221
  var EDGE_HEAD_SHAPE = {
21181
- triangle: { d: "M 0 0 L 10 5 L 0 10 z", refX: 9, filled: true },
21182
- diamond: { d: "M 0 5 L 5 0 L 10 5 L 5 10 z", refX: 9, filled: true },
21183
- open: { d: "M 1 0 L 9 5 L 1 10", refX: 9, filled: false },
21184
- crow: { d: "M 10 0 L 0 5 M 10 5 L 0 5 M 10 10 L 0 5", refX: 10, filled: false }
21222
+ // 何も描かない端。 marker 自体を出さないので `d` は読まれない
21223
+ none: { d: "", w: 10, h: 10, refX: 0, filled: false },
21224
+ triangle: { d: "M 0 0 L 10 5 L 0 10 z", w: 10, h: 10, refX: 9, filled: true },
21225
+ diamond: { d: "M 0 5 L 5 0 L 10 5 L 5 10 z", w: 10, h: 10, refX: 9, filled: true },
21226
+ open: { d: "M 1 0 L 9 5 L 1 10", w: 10, h: 10, refX: 9, filled: false },
21227
+ crow: { d: "M 10 0 L 0 5 M 10 5 L 0 5 M 10 10 L 0 5", w: 10, h: 10, refX: 10, filled: false },
21228
+ // ER の端。 **箱に近い側 (x が大きい側) が個数、その外側が任意か** (#578)。
21229
+ //
21230
+ // 枠は 24 × 10 で、線の先端に合わせるのは x=24 = 箱の縁。 marker は経路の向きに回るので、
21231
+ // 行き先側でも出どころ側でも「箱に近い側」 が同じ端になる。
21232
+ //
21233
+ // 三又の真ん中の枝は **関係の線そのもの** が兼ねる。 別に引くと同じ所に 2 本重なって太く見える
21234
+ one: { d: "M 16 0 L 16 10 M 4 0 L 4 10", w: 24, h: 10, refX: 24, scale: 1.8, filled: false },
21235
+ "zero-one": { d: "M 16 0 L 16 10 M 7 5 a 3 3 0 1 0 -6 0 a 3 3 0 1 0 6 0", w: 24, h: 10, refX: 24, scale: 1.8, filled: false },
21236
+ many: { d: "M 24 0 L 13 5 L 24 10 M 4 0 L 4 10", w: 24, h: 10, refX: 24, scale: 1.8, filled: false },
21237
+ "zero-many": {
21238
+ d: "M 24 0 L 13 5 L 24 10 M 7 5 a 3 3 0 1 0 -6 0 a 3 3 0 1 0 6 0",
21239
+ w: 24,
21240
+ h: 10,
21241
+ refX: 24,
21242
+ scale: 1.8,
21243
+ filled: false
21244
+ }
21185
21245
  };
21186
- function edgeHeadMarkerId(tone, head, small) {
21246
+ function edgeHeadMarkerId(tone, head, small, fill = EDGE_HEAD_FILL_DEFAULT) {
21187
21247
  const \u5C0F = small ? "-sm" : "";
21188
- return head === EDGE_HEAD_DEFAULT ? `cdl-arrow-${tone}${\u5C0F}` : `cdl-arrow-${tone}-${head}${\u5C0F}`;
21248
+ const \u5857 = fill === EDGE_HEAD_FILL_DEFAULT || !EDGE_HEAD_SHAPE[head].filled ? "" : `-${fill}`;
21249
+ return head === EDGE_HEAD_DEFAULT && \u5857 === "" ? `cdl-arrow-${tone}${\u5C0F}` : `cdl-arrow-${tone}-${head}${\u5857}${\u5C0F}`;
21250
+ }
21251
+ function edgeHeadPaint(head, fill, color) {
21252
+ const \u5F62 = EDGE_HEAD_SHAPE[head];
21253
+ if (!\u5F62.filled) return { fill: "none", stroke: color, strokeWidth: 1.6 };
21254
+ if (fill === "hollow") {
21255
+ return { fill: "var(--cdl-node-fill, #ffffff)", stroke: color, strokeWidth: 1.4 };
21256
+ }
21257
+ return { fill: color, stroke: "none" };
21189
21258
  }
21190
21259
  function opacityAttr(opacity) {
21191
21260
  return opacity < 1 ? opacity : void 0;
@@ -21200,7 +21269,13 @@ function CdlEdgeView({
21200
21269
  stateValues
21201
21270
  }) {
21202
21271
  const color = toneColor(edge.tone);
21203
- const marker = hasTone(edge.tone) ? edgeHeadMarkerId(edge.tone, edge.head ?? EDGE_HEAD_DEFAULT, !active) : "cdl-arrow-muted";
21272
+ const \u7AEF\u306E\u540D\u524D = (h, f) => {
21273
+ const \u5F62 = h ?? EDGE_HEAD_DEFAULT;
21274
+ if (\u5F62 === "none") return void 0;
21275
+ return hasTone(edge.tone) ? edgeHeadMarkerId(edge.tone, \u5F62, !active, f ?? EDGE_HEAD_FILL_DEFAULT) : "cdl-arrow-muted";
21276
+ };
21277
+ const marker = \u7AEF\u306E\u540D\u524D(edge.head, edge.headFill);
21278
+ const tailMarker = edge.tailHead === void 0 ? void 0 : \u7AEF\u306E\u540D\u524D(edge.tailHead, edge.tailHeadFill);
21204
21279
  const style = edge.style ?? "solid";
21205
21280
  const isDotted = style === "dotted-flow";
21206
21281
  const isPassThrough = isDotted && Boolean(edge.dThrough);
@@ -21248,6 +21323,8 @@ function CdlEdgeView({
21248
21323
  progress,
21249
21324
  color,
21250
21325
  marker,
21326
+ tailMarker,
21327
+ dashed: style === "dashed",
21251
21328
  stateValues
21252
21329
  }
21253
21330
  ),
@@ -21273,6 +21350,8 @@ function SolidEdgePath({
21273
21350
  progress,
21274
21351
  color,
21275
21352
  marker,
21353
+ tailMarker,
21354
+ dashed,
21276
21355
  stateValues
21277
21356
  }) {
21278
21357
  const visibleD = active ? pathSubpath(edge.d, progress) : edge.d;
@@ -21280,7 +21359,7 @@ function SolidEdgePath({
21280
21359
  const boundWidth = edge.widthBind && stateValues ? resolveNumericAttr(edge.widthBind, stateValues, defaultWidth) : defaultWidth;
21281
21360
  const boundStroke = edge.strokeBind && stateValues ? interpolate(edge.strokeBind, stateValues) : color;
21282
21361
  const boundDashOffset = edge.dashOffsetBind && stateValues ? resolveNumericAttr(edge.dashOffsetBind, stateValues, 0) : void 0;
21283
- const dashArray = active ? boundDashOffset !== void 0 ? "8 6" : void 0 : "4 5";
21362
+ const dashArray = dashed ? "10 8" : active ? boundDashOffset !== void 0 ? "8 6" : void 0 : "4 5";
21284
21363
  return /* @__PURE__ */ jsxRuntime.jsx(
21285
21364
  "path",
21286
21365
  {
@@ -21293,7 +21372,8 @@ function SolidEdgePath({
21293
21372
  strokeDasharray: dashArray,
21294
21373
  strokeDashoffset: boundDashOffset,
21295
21374
  strokeLinecap: "round",
21296
- markerEnd: `url(#${marker})`,
21375
+ markerEnd: marker === void 0 ? void 0 : `url(#${marker})`,
21376
+ markerStart: tailMarker === void 0 ? void 0 : `url(#${tailMarker})`,
21297
21377
  style: {
21298
21378
  transition: "stroke 280ms ease, stroke-opacity 280ms ease, stroke-width 280ms ease"
21299
21379
  }
@@ -21505,7 +21585,8 @@ function CardNode({ node, active }) {
21505
21585
  return /* @__PURE__ */ jsxRuntime.jsx("g", { transform: `translate(${x} ${y})` });
21506
21586
  }
21507
21587
  const compact = isCompactBox(node.h);
21508
- const titleY = compact ? node.h / 2 + 8 : 86;
21588
+ const \u5C0F\u578B\u306B\u547C\u3073\u540D = compact && Boolean(node.subtitle);
21589
+ const titleY = compact ? \u5C0F\u578B\u306B\u547C\u3073\u540D ? node.h / 2 - 2 : node.h / 2 + 8 : 86;
21509
21590
  const titleX = compact ? node.w / 2 : 26;
21510
21591
  const paddingX = compact ? 16 : 52;
21511
21592
  const availableW = node.w - paddingX;
@@ -21542,9 +21623,262 @@ function CardNode({ node, active }) {
21542
21623
  children: node.title
21543
21624
  }
21544
21625
  ),
21545
- !compact && node.subtitle && /* @__PURE__ */ jsxRuntime.jsx("text", { x: 26, y: 124, fontSize: subtitleSize, fill: "var(--cdl-text-dim, #5a6270)", children: node.subtitle })
21626
+ !compact && node.subtitle && /* @__PURE__ */ jsxRuntime.jsx("text", { x: 26, y: 124, fontSize: subtitleSize, fill: "var(--cdl-text-dim, #5a6270)", children: node.subtitle }),
21627
+ \u5C0F\u578B\u306B\u547C\u3073\u540D && /* @__PURE__ */ jsxRuntime.jsx(
21628
+ "text",
21629
+ {
21630
+ "data-cdl-role": "node-subtitle",
21631
+ x: node.w / 2,
21632
+ y: node.h / 2 + 24,
21633
+ fontSize: Math.min(subtitleSize, 18),
21634
+ textAnchor: "middle",
21635
+ fill: "var(--cdl-text-dim, #5a6270)",
21636
+ children: node.subtitle
21637
+ }
21638
+ )
21639
+ ] });
21640
+ }
21641
+
21642
+ // src/kinds/sequence-board-metrics.ts
21643
+ var SEQ_EDGE = 16;
21644
+ var SEQ_ROW = 64;
21645
+ var SEQ_GUTTER_PAD = 40;
21646
+ var SEQ_GUTTER_GAP = 40;
21647
+ var SEQ_HEAD = 96;
21648
+ var SEQ_ROW_PAD = 56;
21649
+ var SEQ_COL_PAD = 80;
21650
+ var SEQ_BAND_W = 52;
21651
+ var SEQ_BAND_PAD = 24;
21652
+ var SEQ_NAME = 24;
21653
+ var SEQ_SUB = 20;
21654
+ var SEQ_LABEL = 26;
21655
+ var SEQ_DOT = 7;
21656
+ var SEQ_SELF_W = 52;
21657
+ var SEQ_COL_MIN = 200;
21658
+ var g4 = (v) => Math.round(v / 4) * 4;
21659
+ function sequenceStepId() {
21660
+ return "seq_step";
21661
+ }
21662
+ function sequenceBoardMetrics(data) {
21663
+ const \u540D\u5E45 = data.actors.map(
21664
+ (a) => Math.max(textWidth(a.name, SEQ_NAME), a.subtitle ? textWidth(a.subtitle, SEQ_SUB) : 0)
21665
+ );
21666
+ const colW = g4(Math.max(SEQ_COL_MIN, Math.max(0, ...\u540D\u5E45) + SEQ_COL_PAD));
21667
+ const \u8A9E\u5E45 = Math.max(0, ...data.messages.map((m) => textWidth(m.label, SEQ_LABEL)));
21668
+ const gutter = g4(SEQ_EDGE + SEQ_GUTTER_PAD + \u8A9E\u5E45 + SEQ_GUTTER_GAP);
21669
+ const headY = SEQ_EDGE + SEQ_HEAD;
21670
+ const rowY = (i) => headY + SEQ_ROW_PAD + i * SEQ_ROW;
21671
+ const \u6700\u7D42 = data.messages.length > 0 ? rowY(data.messages.length - 1) : headY;
21672
+ const bottom = \u6700\u7D42 + SEQ_ROW_PAD;
21673
+ const cx = (i) => gutter + colW / 2 + i * colW;
21674
+ const w = g4(cx(Math.max(0, data.actors.length - 1)) + colW / 2 + SEQ_EDGE);
21675
+ const h = g4(bottom + SEQ_EDGE);
21676
+ return { w, h, gutter, colW, headY, cx, rowY, bottom };
21677
+ }
21678
+ function SequenceBoardNode({
21679
+ node,
21680
+ stateValues
21681
+ }) {
21682
+ const data = node.sequenceData;
21683
+ if (!data) return /* @__PURE__ */ jsxRuntime.jsx("g", { "data-cdl-role": "sequence-board" });
21684
+ const m = sequenceBoardMetrics(data);
21685
+ const x = node.cx - node.w / 2;
21686
+ const y = node.cy - node.h / 2;
21687
+ const \u73FE\u5728 = (() => {
21688
+ if (!node.sequenceStep) return null;
21689
+ const v = Number.parseInt(interpolate(node.sequenceStep, stateValues ?? {}).trim(), 10);
21690
+ return Number.isFinite(v) ? v : null;
21691
+ })();
21692
+ const \u6731 = "var(--cdl-tone-accent, #2d6a8f)";
21693
+ const \u58A8 = "var(--cdl-text-2, #4a5162)";
21694
+ const \u898B\u305F\u76EE = (i) => {
21695
+ if (\u73FE\u5728 === null) return { opacity: 1, color: \u6731 };
21696
+ if (i > \u73FE\u5728) return { opacity: 0, color: \u6731 };
21697
+ return i === \u73FE\u5728 ? { opacity: 1, color: \u6731 } : { opacity: 1, color: \u58A8 };
21698
+ };
21699
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { "data-cdl-role": "sequence-board", transform: `translate(${x} ${y})`, children: [
21700
+ data.bands.map((b, i) => {
21701
+ if (\u73FE\u5728 !== null && b.from > \u73FE\u5728) return null;
21702
+ const \u4E0B = \u73FE\u5728 === null ? b.to : Math.min(b.to, \u73FE\u5728);
21703
+ return /* @__PURE__ */ jsxRuntime.jsx(
21704
+ "rect",
21705
+ {
21706
+ "data-cdl-role": "sequence-band",
21707
+ x: m.cx(b.actor) - SEQ_BAND_W / 2,
21708
+ y: m.rowY(b.from) - SEQ_BAND_PAD / 2,
21709
+ width: SEQ_BAND_W,
21710
+ height: m.rowY(\u4E0B) - m.rowY(b.from) + SEQ_BAND_PAD,
21711
+ rx: 4,
21712
+ fill: \u6731,
21713
+ fillOpacity: 0.12
21714
+ },
21715
+ `band-${i}`
21716
+ );
21717
+ }),
21718
+ data.actors.map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
21719
+ "line",
21720
+ {
21721
+ "data-cdl-role": "sequence-thread",
21722
+ x1: m.cx(i),
21723
+ y1: m.headY,
21724
+ x2: m.cx(i),
21725
+ y2: m.bottom,
21726
+ stroke: "var(--cdl-divider, #d4d4d8)",
21727
+ strokeWidth: 3
21728
+ },
21729
+ `thread-${i}`
21730
+ )),
21731
+ data.actors.map((a, i) => /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
21732
+ /* @__PURE__ */ jsxRuntime.jsx(
21733
+ "text",
21734
+ {
21735
+ "data-cdl-role": "sequence-actor",
21736
+ x: m.cx(i),
21737
+ y: SEQ_EDGE + 44,
21738
+ fontSize: SEQ_NAME,
21739
+ fontWeight: 700,
21740
+ textAnchor: "middle",
21741
+ fill: "var(--cdl-text, #1a1f2a)",
21742
+ style: { fontFamily: "'JetBrains Mono', monospace" },
21743
+ children: a.name
21744
+ }
21745
+ ),
21746
+ a.subtitle && /* @__PURE__ */ jsxRuntime.jsx(
21747
+ "text",
21748
+ {
21749
+ "data-cdl-role": "sequence-actor-subtitle",
21750
+ x: m.cx(i),
21751
+ y: SEQ_EDGE + 76,
21752
+ fontSize: SEQ_SUB,
21753
+ textAnchor: "middle",
21754
+ fill: "var(--cdl-text-dim, #5a6270)",
21755
+ children: a.subtitle
21756
+ }
21757
+ )
21758
+ ] }, `head-${i}`)),
21759
+ /* @__PURE__ */ jsxRuntime.jsx(
21760
+ "line",
21761
+ {
21762
+ "data-cdl-role": "sequence-rule",
21763
+ x1: SEQ_EDGE,
21764
+ y1: m.headY,
21765
+ x2: m.w - SEQ_EDGE,
21766
+ y2: m.headY,
21767
+ stroke: "var(--cdl-divider, #d4d4d8)",
21768
+ strokeWidth: 2
21769
+ }
21770
+ ),
21771
+ data.messages.map((msg, i) => /* @__PURE__ */ jsxRuntime.jsx(SequenceRow, { msg, yy: m.rowY(i), cx: m.cx, ...\u898B\u305F\u76EE(i) }, `msg-${i}`))
21772
+ ] });
21773
+ }
21774
+ function SequenceRow({
21775
+ msg,
21776
+ yy,
21777
+ cx,
21778
+ opacity,
21779
+ color
21780
+ }) {
21781
+ const \u7834 = msg.kind === "return";
21782
+ const \u5857\u308B = msg.kind === "call";
21783
+ const \u51FA = cx(msg.from);
21784
+ const \u81EA\u5206\u3078 = msg.from === msg.to;
21785
+ const dir = \u81EA\u5206\u3078 ? 1 : Math.sign(cx(msg.to) - \u51FA);
21786
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { "data-cdl-role": "sequence-message", "data-cdl-message-kind": msg.kind, opacity, children: [
21787
+ /* @__PURE__ */ jsxRuntime.jsx(
21788
+ "text",
21789
+ {
21790
+ "data-cdl-role": "sequence-label",
21791
+ x: SEQ_EDGE + SEQ_GUTTER_PAD,
21792
+ y: yy + 8,
21793
+ fontSize: SEQ_LABEL,
21794
+ fontWeight: 500,
21795
+ fill: "var(--cdl-text, #1a1f2a)",
21796
+ children: msg.label
21797
+ }
21798
+ ),
21799
+ \u81EA\u5206\u3078 ? (
21800
+ // 自分宛て。 相手がいないので糸の右へ小さく回して戻る
21801
+ /* @__PURE__ */ jsxRuntime.jsx(
21802
+ "path",
21803
+ {
21804
+ "data-cdl-role": "sequence-line",
21805
+ d: `M ${\u51FA + 10} ${yy - 16} H ${\u51FA + SEQ_SELF_W - 16} Q ${\u51FA + SEQ_SELF_W} ${yy - 16} ${\u51FA + SEQ_SELF_W} ${yy} Q ${\u51FA + SEQ_SELF_W} ${yy + 16} ${\u51FA + SEQ_SELF_W - 16} ${yy + 16} H ${\u51FA + 22}`,
21806
+ fill: "none",
21807
+ stroke: color,
21808
+ strokeWidth: 2.5,
21809
+ strokeLinecap: "round"
21810
+ }
21811
+ )
21812
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
21813
+ "path",
21814
+ {
21815
+ "data-cdl-role": "sequence-line",
21816
+ d: `M ${\u51FA + dir * 12} ${yy} H ${cx(msg.to) - dir * 14}`,
21817
+ fill: "none",
21818
+ stroke: color,
21819
+ strokeWidth: 2.5,
21820
+ strokeLinecap: "round",
21821
+ strokeDasharray: \u7834 ? "10 8" : void 0
21822
+ }
21823
+ ),
21824
+ /* @__PURE__ */ jsxRuntime.jsx(
21825
+ Arrow,
21826
+ {
21827
+ x: \u81EA\u5206\u3078 ? \u51FA + 14 : cx(msg.to),
21828
+ y: \u81EA\u5206\u3078 ? yy + 16 : yy,
21829
+ dir: \u81EA\u5206\u3078 ? -1 : dir,
21830
+ filled: \u5857\u308B,
21831
+ color
21832
+ }
21833
+ ),
21834
+ /* @__PURE__ */ jsxRuntime.jsx(
21835
+ "circle",
21836
+ {
21837
+ "data-cdl-role": "sequence-source",
21838
+ cx: \u51FA,
21839
+ cy: \u81EA\u5206\u3078 ? yy - 16 : yy,
21840
+ r: SEQ_DOT,
21841
+ fill: color
21842
+ }
21843
+ )
21546
21844
  ] });
21547
21845
  }
21846
+ function Arrow({
21847
+ x,
21848
+ y,
21849
+ dir,
21850
+ filled,
21851
+ color
21852
+ }) {
21853
+ const \u4E08 = 18;
21854
+ const \u534A = 8.5;
21855
+ const \u5148 = x;
21856
+ const \u80F4 = x - dir * \u4E08;
21857
+ return filled ? /* @__PURE__ */ jsxRuntime.jsx(
21858
+ "path",
21859
+ {
21860
+ "data-cdl-role": "sequence-head",
21861
+ "data-cdl-head-fill": "solid",
21862
+ d: `M ${\u5148} ${y} L ${\u80F4} ${y - \u534A} L ${\u80F4} ${y + \u534A} Z`,
21863
+ fill: color,
21864
+ stroke: color,
21865
+ strokeWidth: 3,
21866
+ strokeLinejoin: "round"
21867
+ }
21868
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
21869
+ "path",
21870
+ {
21871
+ "data-cdl-role": "sequence-head",
21872
+ "data-cdl-head-fill": "open",
21873
+ d: `M ${\u80F4} ${y - \u534A} L ${\u5148} ${y} L ${\u80F4} ${y + \u534A}`,
21874
+ fill: "none",
21875
+ stroke: color,
21876
+ strokeWidth: 2.5,
21877
+ strokeLinecap: "round",
21878
+ strokeLinejoin: "round"
21879
+ }
21880
+ );
21881
+ }
21548
21882
  function EventNode({
21549
21883
  node,
21550
21884
  active,
@@ -21646,6 +21980,29 @@ function FunctionNode({ node, active }) {
21646
21980
  node.subtitle && /* @__PURE__ */ jsxRuntime.jsx("text", { x: 26, y: 126, fontSize: 20, fill: "var(--cdl-text-dim, #5a6270)", children: node.subtitle })
21647
21981
  ] });
21648
21982
  }
21983
+ var ROW_GLYPH_HALF = 7;
21984
+ var ROW_GLYPH_CX = ROW_PAD_X_PX + ROW_GLYPH_HALF + 1;
21985
+ var ROW_GLYPH_LEAD = 40;
21986
+ var ROW_TYPE_FONT = 24;
21987
+ var SUBTITLE_BASELINE = 108;
21988
+ var SUBTITLE_FONT = 22;
21989
+ var UNDERLINE_DROP = 7;
21990
+ function RowGlyph({ mark, x, y, color }) {
21991
+ const \u534A = ROW_GLYPH_HALF;
21992
+ const d = mark.shape === "square" ? `M ${x - \u534A} ${y - \u534A} H ${x + \u534A} V ${y + \u534A} H ${x - \u534A} Z` : `M ${x - \u534A} ${y - \u534A} L ${x + \u534A} ${y} L ${x - \u534A} ${y + \u534A} Z`;
21993
+ return /* @__PURE__ */ jsxRuntime.jsx(
21994
+ "path",
21995
+ {
21996
+ "data-cdl-role": "node-row-glyph",
21997
+ "data-cdl-row-glyph": `${mark.shape}-${mark.filled ? "filled" : "hollow"}`,
21998
+ d,
21999
+ fill: mark.filled ? color : "var(--cdl-node-fill, #ffffff)",
22000
+ stroke: color,
22001
+ strokeWidth: mark.filled ? 0 : 2.4,
22002
+ strokeLinejoin: "round"
22003
+ }
22004
+ );
22005
+ }
21649
22006
  function StorageNode({
21650
22007
  node,
21651
22008
  active,
@@ -21663,6 +22020,8 @@ function StorageNode({
21663
22020
  const leftEndDrawn = ROW_PAD_X_PX + leftMaxWidth * STORAGE_CHAR_ADVANCE_PX;
21664
22021
  const columnDividerX = (leftEndDrawn + rightColX) / 2;
21665
22022
  const columnDividerFits = columnDividerX <= node.w - ROW_PAD_X_PX;
22023
+ const \u5370\u3067\u5206\u3051\u308B = node.rowMarks !== void 0;
22024
+ const \u884C\u306E\u4E0B\u3052 = \u5370\u3067\u5206\u3051\u308B && node.subtitle ? SUBTITLE_ROW_SHIFT : 0;
21666
22025
  const lastRowBottom = Math.min(
21667
22026
  ROW_TOP_STORAGE + (rows.length - 1) * ROW_PITCH_STORAGE + (rowGlyphDepth("storage") ?? 0),
21668
22027
  node.h - ROW_PAD_X_PX
@@ -21683,6 +22042,19 @@ function StorageNode({
21683
22042
  }
21684
22043
  ),
21685
22044
  node.eyebrow && /* @__PURE__ */ jsxRuntime.jsx("text", { x: 26, y: 38, fontSize: 18, fontWeight: 700, letterSpacing: 2, fill: accent, children: node.eyebrow }),
22045
+ \u5370\u3067\u5206\u3051\u308B && node.subtitle && // 呼び名は **印で分ける箱でだけ** 出す (#578)。 従来の箱は題の下に区切り線が来るので
22046
+ // 置き場が無い
22047
+ /* @__PURE__ */ jsxRuntime.jsx(
22048
+ "text",
22049
+ {
22050
+ "data-cdl-role": "node-subtitle",
22051
+ x: ROW_PAD_X_PX,
22052
+ y: SUBTITLE_BASELINE,
22053
+ fontSize: SUBTITLE_FONT,
22054
+ fill: "var(--cdl-text-dim, #5a6270)",
22055
+ children: node.subtitle
22056
+ }
22057
+ ),
21686
22058
  /* @__PURE__ */ jsxRuntime.jsx(
21687
22059
  "text",
21688
22060
  {
@@ -21697,7 +22069,7 @@ function StorageNode({
21697
22069
  children: node.title
21698
22070
  }
21699
22071
  ),
21700
- rows.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
22072
+ rows.length > 0 && !\u5370\u3067\u5206\u3051\u308B && /* @__PURE__ */ jsxRuntime.jsx(
21701
22073
  "line",
21702
22074
  {
21703
22075
  "data-cdl-role": "node-row-divider",
@@ -21709,7 +22081,7 @@ function StorageNode({
21709
22081
  strokeWidth: 1.25
21710
22082
  }
21711
22083
  ),
21712
- rows.length > 0 && hasRightColumn && columnDividerFits && /* @__PURE__ */ jsxRuntime.jsx(
22084
+ rows.length > 0 && hasRightColumn && columnDividerFits && !\u5370\u3067\u5206\u3051\u308B && /* @__PURE__ */ jsxRuntime.jsx(
21713
22085
  "line",
21714
22086
  {
21715
22087
  "data-cdl-role": "node-column-divider",
@@ -21722,15 +22094,37 @@ function StorageNode({
21722
22094
  }
21723
22095
  ),
21724
22096
  splits.map((s, idx) => {
21725
- return /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: `translate(0 ${rowBaselineY("storage", idx)})`, children: [
22097
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: `translate(0 ${(rowBaselineY("storage", idx) ?? 0) + \u884C\u306E\u4E0B\u3052})`, children: [
22098
+ \u5370\u3067\u5206\u3051\u308B && node.rowMarks?.[idx] != null && /* @__PURE__ */ jsxRuntime.jsx(
22099
+ RowGlyph,
22100
+ {
22101
+ mark: node.rowMarks[idx],
22102
+ x: ROW_GLYPH_CX,
22103
+ y: -8,
22104
+ color: "var(--cdl-text, #1a1f2a)"
22105
+ }
22106
+ ),
22107
+ \u5370\u3067\u5206\u3051\u308B && node.rowMarks?.[idx]?.underline === true && // 主キーの下線 (#578)。 印の 2 軸とは別の段に載るので、外部キーの山形と重ねられる
22108
+ /* @__PURE__ */ jsxRuntime.jsx(
22109
+ "line",
22110
+ {
22111
+ "data-cdl-role": "node-row-underline",
22112
+ x1: ROW_PAD_X_PX + ROW_GLYPH_LEAD,
22113
+ y1: UNDERLINE_DROP,
22114
+ x2: ROW_PAD_X_PX + ROW_GLYPH_LEAD + displayWidth(s.left) * STORAGE_CHAR_ADVANCE_PX,
22115
+ y2: UNDERLINE_DROP,
22116
+ stroke: "var(--cdl-text, #1a1f2a)",
22117
+ strokeWidth: 2
22118
+ }
22119
+ ),
21726
22120
  /* @__PURE__ */ jsxRuntime.jsx(
21727
22121
  "text",
21728
22122
  {
21729
22123
  "data-cdl-role": "node-row",
21730
22124
  "data-cdl-row-index": idx,
21731
- x: ROW_PAD_X_PX,
22125
+ x: \u5370\u3067\u5206\u3051\u308B ? ROW_PAD_X_PX + ROW_GLYPH_LEAD : ROW_PAD_X_PX,
21732
22126
  fontSize: 26,
21733
- fontWeight: 600,
22127
+ fontWeight: \u5370\u3067\u5206\u3051\u308B ? 500 : 600,
21734
22128
  fill: "var(--cdl-text, #1a1f2a)",
21735
22129
  style: { fontFamily: "'JetBrains Mono', monospace" },
21736
22130
  children: s.left
@@ -21741,11 +22135,11 @@ function StorageNode({
21741
22135
  {
21742
22136
  "data-cdl-role": "node-row",
21743
22137
  "data-cdl-row-index": idx,
21744
- x: rightColX,
21745
- fontSize: 26,
21746
- fontWeight: highlightRows ? 700 : 600,
21747
- textAnchor: "start",
21748
- fill: highlightRows ? accent : "var(--cdl-text, #1a1f2a)",
22138
+ x: \u5370\u3067\u5206\u3051\u308B ? node.w - ROW_PAD_X_PX : rightColX,
22139
+ fontSize: \u5370\u3067\u5206\u3051\u308B ? ROW_TYPE_FONT : 26,
22140
+ fontWeight: \u5370\u3067\u5206\u3051\u308B ? 500 : highlightRows ? 700 : 600,
22141
+ textAnchor: \u5370\u3067\u5206\u3051\u308B ? "end" : "start",
22142
+ fill: \u5370\u3067\u5206\u3051\u308B ? "var(--cdl-text-dim, #5a6270)" : highlightRows ? accent : "var(--cdl-text, #1a1f2a)",
21749
22143
  style: { fontFamily: "'JetBrains Mono', monospace" },
21750
22144
  children: s.right
21751
22145
  }
@@ -28101,6 +28495,9 @@ function CdlNodeView({
28101
28495
  return /* @__PURE__ */ jsxRuntime.jsx(EventNode, { node: resolvedNode, active, progress });
28102
28496
  case "card":
28103
28497
  return /* @__PURE__ */ jsxRuntime.jsx(CardNode, { node: resolvedNode, active });
28498
+ // 時系列のやり取り (#578)。 1 つの箱が図を丸ごと描く = 縦が時間で、参加者は箱を持たない
28499
+ case "sequence-board":
28500
+ return /* @__PURE__ */ jsxRuntime.jsx(SequenceBoardNode, { node: resolvedNode, stateValues });
28104
28501
  // 図表 9 種は payload が `{signal}` を取れるので stateValues を渡す
28105
28502
  // (`render/payload-binding.ts` が描画直前に解決する)。 取れる欄は種別で違い、
28106
28503
  // 数と語を取る 7 種に対し、mind-map / tree-hierarchy は **名前だけ** を取る (#467)。
@@ -28428,36 +28825,47 @@ function CdlStage({
28428
28825
  /* @__PURE__ */ jsxRuntime.jsxs("defs", { children: [
28429
28826
  Object.entries(TONE).flatMap(
28430
28827
  ([tone, color]) => [["", 14], ["-sm", 10]].flatMap(
28431
- ([suffix, size]) => EDGE_HEADS.map((head) => {
28432
- const \u5F62 = EDGE_HEAD_SHAPE[head];
28433
- return /* @__PURE__ */ jsxRuntime.jsx(
28434
- "marker",
28435
- {
28436
- id: edgeHeadMarkerId(tone, head, suffix === "-sm"),
28437
- viewBox: "0 0 10 10",
28438
- refX: \u5F62.refX,
28439
- refY: "5",
28440
- markerWidth: size,
28441
- markerHeight: size,
28442
- markerUnits: "userSpaceOnUse",
28443
- orient: "auto-start-reverse",
28444
- children: /* @__PURE__ */ jsxRuntime.jsx(
28445
- "path",
28828
+ ([suffix, size]) => EDGE_HEADS.flatMap(
28829
+ (head) => (
28830
+ // 塗り方は形と別の軸 (#578)。 塗らない形では 2 つが同じ名前に畳まれるので、
28831
+ // `edgeHeadMarkerId` が返す名前で重複を外す
28832
+ EDGE_HEAD_FILLS.map((fill) => {
28833
+ const \u5F62 = EDGE_HEAD_SHAPE[head];
28834
+ const id = edgeHeadMarkerId(tone, head, suffix === "-sm", fill);
28835
+ if (\u5F62.d === "") return null;
28836
+ if (fill !== EDGE_HEAD_FILL_DEFAULT && !\u5F62.filled) return null;
28837
+ const \u5857 = edgeHeadPaint(head, fill, color);
28838
+ return /* @__PURE__ */ jsxRuntime.jsx(
28839
+ "marker",
28446
28840
  {
28447
- "data-cdl-role": "edge-arrowhead",
28448
- "data-cdl-edge-head": head,
28449
- d: \u5F62.d,
28450
- fill: \u5F62.filled ? color : "none",
28451
- stroke: \u5F62.filled ? "none" : color,
28452
- strokeWidth: \u5F62.filled ? void 0 : 1.6,
28453
- strokeLinecap: \u5F62.filled ? void 0 : "round",
28454
- strokeLinejoin: \u5F62.filled ? void 0 : "round"
28455
- }
28456
- )
28457
- },
28458
- `${tone}${suffix}-${head}`
28459
- );
28460
- })
28841
+ id,
28842
+ viewBox: `0 0 ${\u5F62.w} ${\u5F62.h}`,
28843
+ refX: \u5F62.refX,
28844
+ refY: \u5F62.h / 2,
28845
+ markerWidth: size * (\u5F62.scale ?? 1) * \u5F62.w / \u5F62.h,
28846
+ markerHeight: size * (\u5F62.scale ?? 1),
28847
+ markerUnits: "userSpaceOnUse",
28848
+ orient: "auto-start-reverse",
28849
+ children: /* @__PURE__ */ jsxRuntime.jsx(
28850
+ "path",
28851
+ {
28852
+ "data-cdl-role": "edge-arrowhead",
28853
+ "data-cdl-edge-head": head,
28854
+ "data-cdl-edge-head-fill": fill,
28855
+ d: \u5F62.d,
28856
+ fill: \u5857.fill,
28857
+ stroke: \u5857.stroke,
28858
+ strokeWidth: \u5857.strokeWidth,
28859
+ strokeLinecap: \u5857.strokeWidth === void 0 ? void 0 : "round",
28860
+ strokeLinejoin: \u5857.strokeWidth === void 0 ? void 0 : "round"
28861
+ }
28862
+ )
28863
+ },
28864
+ id
28865
+ );
28866
+ })
28867
+ )
28868
+ )
28461
28869
  )
28462
28870
  ),
28463
28871
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -29146,65 +29554,58 @@ function flow(preset) {
29146
29554
  };
29147
29555
  return api;
29148
29556
  }
29557
+ var SEQUENCE_MESSAGE_LOOK = {
29558
+ call: { style: "solid", head: "triangle" },
29559
+ return: { style: "dashed", head: "open" },
29560
+ fire: { style: "solid", head: "open" }
29561
+ };
29149
29562
  function sequence(preset) {
29150
- const laneW = preset.laneWidth ?? 340;
29151
29563
  const tone = preset.defaultTone ?? "accent";
29152
- const style = preset.defaultStyle ?? "solid";
29153
29564
  const b = diagram(preset.id, { topic: preset.topic });
29154
- const actorIds = /* @__PURE__ */ new Map();
29155
- const headerNodeIds = [];
29156
- preset.actors.forEach((label, i) => {
29157
- const id = slugify(label) || `actor-${i}`;
29158
- actorIds.set(label, id);
29159
- actorIds.set(id, id);
29160
- b.lane(id, { width: laneW, label, lifeline: true });
29161
- const actorW = Math.max(140, label.length * 22 + 52);
29162
- const headerId = `${id}-header`;
29163
- b.node(headerId, { lane: id, stack: 0, kind: "card", title: label, w: actorW, h: 72 });
29164
- headerNodeIds.push(headerId);
29165
- const spacerId = `${id}-spacer`;
29166
- b.node(spacerId, { lane: id, stack: 1, kind: "card", title: "", w: 2, h: 40 });
29565
+ const \u9762\u3005 = preset.actors.map(
29566
+ (a) => typeof a === "string" ? { name: a } : { name: a.name, ...a.subtitle ? { subtitle: a.subtitle } : {} }
29567
+ );
29568
+ const \u756A\u53F7 = /* @__PURE__ */ new Map();
29569
+ \u9762\u3005.forEach((a, i) => {
29570
+ \u756A\u53F7.set(a.name, i);
29571
+ \u756A\u53F7.set(slugify(a.name) || `actor-${i}`, i);
29167
29572
  });
29168
- const phaseActivate = [...headerNodeIds];
29169
- let stepIdx = 0;
29573
+ const \u8A00\u3065\u3066 = [];
29574
+ const \u8A9E = /* @__PURE__ */ new Map();
29170
29575
  const api = {
29171
- step(s) {
29172
- const fromLaneId = actorIds.get(s.from) ?? s.from;
29173
- const toLaneId = actorIds.get(s.to) ?? s.to;
29174
- const stack = stepIdx + 2;
29175
- const fromBoxId = `s${stepIdx}-${fromLaneId}`;
29176
- const toBoxId = `s${stepIdx}-${toLaneId}`;
29177
- b.node(fromBoxId, { lane: fromLaneId, stack, kind: "card", title: "", w: 2, h: 2 });
29178
- if (fromLaneId !== toLaneId) {
29179
- b.node(toBoxId, { lane: toLaneId, stack, kind: "card", title: "", w: 2, h: 2 });
29180
- }
29181
- const edgeId = `e${stepIdx}-${fromLaneId}-${toLaneId}`;
29182
- b.edge(fromBoxId, fromLaneId === toLaneId ? fromBoxId : toBoxId, {
29183
- id: edgeId,
29184
- label: s.label,
29185
- ...s.sub ? { sub: s.sub } : {},
29186
- tone: s.tone ?? tone,
29187
- style: s.style ?? style
29188
- });
29189
- phaseActivate.push(fromBoxId);
29190
- if (fromLaneId !== toLaneId) phaseActivate.push(toBoxId);
29191
- phaseActivate.push(edgeId);
29192
- stepIdx += 1;
29576
+ step(s2) {
29577
+ const from = \u756A\u53F7.get(s2.from) ?? 0;
29578
+ const to = \u756A\u53F7.get(s2.to) ?? 0;
29579
+ const kind = s2.kind ?? ((s2.style ?? preset.defaultStyle) === "dotted-flow" ? "return" : "call");
29580
+ \u8A00\u3065\u3066.push({ label: s2.label, from, to, kind });
29581
+ if (s2.sub) \u8A9E.set(\u8A00\u3065\u3066.length - 1, s2.sub);
29193
29582
  return api;
29194
29583
  },
29195
29584
  build() {
29196
- const footerStack = stepIdx + 2;
29197
- preset.actors.forEach((label) => {
29198
- const laneId = actorIds.get(label) ?? slugify(label);
29199
- const footerId = `${laneId}-footer`;
29200
- const actorW = Math.max(140, label.length * 22 + 52);
29201
- b.node(footerId, { lane: laneId, stack: footerStack, kind: "card", title: label, w: actorW, h: 72, role: "lifeline-footer" });
29202
- phaseActivate.push(footerId);
29585
+ const \u5E2F = preset.bands ? preset.bands.map((x) => ({ actor: \u756A\u53F7.get(x.actor) ?? 0, from: x.from, to: x.to })) : \u9762\u3005.map((_, i) => {
29586
+ const \u51FA\u756A = \u8A00\u3065\u3066.map((m2, idx) => m2.from === i || m2.to === i ? idx : -1).filter((idx) => idx >= 0);
29587
+ return \u51FA\u756A.length > 0 ? { actor: i, from: \u51FA\u756A[0], to: \u51FA\u756A[\u51FA\u756A.length - 1] } : null;
29588
+ }).filter((x) => x !== null);
29589
+ const data = { actors: \u9762\u3005, messages: \u8A00\u3065\u3066, bands: \u5E2F };
29590
+ const m = sequenceBoardMetrics(data);
29591
+ const laneId = "seq";
29592
+ b.lane(laneId, { width: m.w + LANE_PAD_MIN * 2 });
29593
+ b.node(preset.id, {
29594
+ lane: laneId,
29595
+ stack: 0,
29596
+ kind: "sequence-board",
29597
+ w: m.w,
29598
+ h: m.h,
29599
+ title: "",
29600
+ sequenceData: data,
29601
+ // 段が進むと濃さが移る。 書かない図では全部を濃く出す
29602
+ sequenceStep: `{${sequenceStepId()}}`,
29603
+ tone
29203
29604
  });
29204
29605
  b.phase(
29205
- "seq",
29606
+ "sequence",
29206
29607
  { duration: 2400, title: preset.topic, body: "sequence \u306E\u5168 message \u3092\u6642\u7CFB\u5217\u5C55\u958B\u3002" },
29207
- (p) => p.activate(...phaseActivate).badge("sequence")
29608
+ (p) => p.activate(preset.id).badge("sequence")
29208
29609
  );
29209
29610
  return b.build();
29210
29611
  }
@@ -29272,6 +29673,14 @@ function topology(preset) {
29272
29673
  };
29273
29674
  return api;
29274
29675
  }
29676
+ var ER_CARDINALITY_HEAD = {
29677
+ "1:1": { tail: "one", head: "one" },
29678
+ "1:N": { tail: "one", head: "many" },
29679
+ "N:1": { tail: "many", head: "one" },
29680
+ "N:M": { tail: "many", head: "many" },
29681
+ "0..1": { tail: "one", head: "zero-one" },
29682
+ "1..*": { tail: "one", head: "many" }
29683
+ };
29275
29684
  function er(preset) {
29276
29685
  const entityW = resolveStorageBoxW(preset.entityWidth, "er", "entityWidth");
29277
29686
  const tone = preset.defaultTone ?? "info";
@@ -29282,7 +29691,27 @@ function er(preset) {
29282
29691
  const api = {
29283
29692
  entity(e) {
29284
29693
  const laneId = `lane-${e.id}`;
29285
- const boxW = fitRowsWidth(entityW, e.rows);
29694
+ const \u9375 = (e.columns ?? []).filter((c) => c.pk === true);
29695
+ const \u5024 = (e.columns ?? []).filter((c) => c.pk !== true);
29696
+ const \u884C = [];
29697
+ const \u5370 = [];
29698
+ const \u8DB3\u3059 = (c) => {
29699
+ \u884C.push(`${c.name}: ${c.type}`);
29700
+ \u5370.push({
29701
+ shape: c.fk === true ? "chevron" : "square",
29702
+ filled: c.optional !== true,
29703
+ ...c.pk === true ? { underline: true } : {}
29704
+ });
29705
+ };
29706
+ \u9375.forEach(\u8DB3\u3059);
29707
+ if (\u9375.length > 0 && \u5024.length > 0) {
29708
+ \u884C.push("");
29709
+ \u5370.push(null);
29710
+ }
29711
+ \u5024.forEach(\u8DB3\u3059);
29712
+ const \u8A2D\u8A08\u306E\u5F62 = \u884C.length > 0;
29713
+ const rows = \u8A2D\u8A08\u306E\u5F62 ? \u884C : e.rows ?? [];
29714
+ const boxW = fitRowsWidth(entityW, rows);
29286
29715
  b.lane(laneId, { width: boxLaneW(boxW) });
29287
29716
  b.node(e.id, {
29288
29717
  lane: laneId,
@@ -29290,8 +29719,12 @@ function er(preset) {
29290
29719
  kind: "storage",
29291
29720
  w: boxW,
29292
29721
  title: e.title,
29293
- eyebrow: "\u30A8\u30F3\u30C6\u30A3\u30C6\u30A3",
29294
- rows: e.rows
29722
+ // 種類の札は設計の形では出さない (#578)。 「エンティティ」 と書いた札は全ての箱に
29723
+ // 同じ字が並ぶだけで、代わりに表の呼び名を題の下へ置く
29724
+ ...\u8A2D\u8A08\u306E\u5F62 ? {} : { eyebrow: "\u30A8\u30F3\u30C6\u30A3\u30C6\u30A3" },
29725
+ ...\u8A2D\u8A08\u306E\u5F62 && e.subtitle ? { subtitle: e.subtitle } : {},
29726
+ rows,
29727
+ ...\u8A2D\u8A08\u306E\u5F62 ? { rowMarks: \u5370 } : {}
29295
29728
  });
29296
29729
  entities.push(e.id);
29297
29730
  phaseActivate.push(e.id);
@@ -29300,14 +29733,19 @@ function er(preset) {
29300
29733
  relation(r) {
29301
29734
  const currentIdx = relIdx++;
29302
29735
  const edgeId = `rel-${currentIdx}-${r.from}-${r.to}`;
29303
- const label = r.label ?? r.cardinality;
29304
- const sub = r.label ? r.cardinality : void 0;
29736
+ const label = r.label ?? r.cardinality ?? "";
29737
+ const \u5370\u304C\u793A\u3059 = r.tailHead !== void 0 || r.head !== void 0;
29738
+ const sub = r.label && r.cardinality && !\u5370\u304C\u793A\u3059 ? r.cardinality : void 0;
29739
+ const \u65E2\u5B9A = r.cardinality ? ER_CARDINALITY_HEAD[r.cardinality] : void 0;
29305
29740
  b.edge(r.from, r.to, {
29306
29741
  id: edgeId,
29307
29742
  label,
29308
29743
  ...sub ? { sub } : {},
29309
29744
  tone: r.tone ?? tone,
29310
- style: "solid"
29745
+ style: r.style ?? "solid",
29746
+ // 端の印は両端に立つ (#578)。 箱に近い側が個数、その外側が任意か
29747
+ ...r.head ?? \u65E2\u5B9A?.head ? { head: r.head ?? \u65E2\u5B9A.head } : {},
29748
+ ...r.tailHead ?? \u65E2\u5B9A?.tail ? { tailHead: r.tailHead ?? \u65E2\u5B9A.tail } : {}
29311
29749
  });
29312
29750
  phaseActivate.push(edgeId);
29313
29751
  return api;
@@ -29323,49 +29761,114 @@ function er(preset) {
29323
29761
  };
29324
29762
  return api;
29325
29763
  }
29764
+ var MARK_BOX = 96;
29765
+ var FSM_ACTION_MARK = {
29766
+ entry: { shape: "chevron", filled: true },
29767
+ exit: { shape: "chevron", filled: false },
29768
+ do: { shape: "square", filled: true },
29769
+ internal: { shape: "square", filled: false }
29770
+ };
29326
29771
  function stateMachine(preset) {
29327
29772
  const stateW = resolveStateW(preset.stateWidth, "stateMachine");
29328
- const laneW = boxLaneW(stateW);
29329
29773
  const tone = preset.defaultTone ?? "accent";
29330
29774
  const b = diagram(preset.id, { topic: preset.topic });
29331
29775
  const phaseActivate = [];
29332
- let transIdx = 0;
29333
29776
  const stateOrder = /* @__PURE__ */ new Map();
29777
+ const \u72B6\u614B = [];
29778
+ const \u7BC0\u70B9 = [];
29779
+ const \u9077\u79FB = [];
29780
+ const \u4E26\u3073 = [];
29334
29781
  const api = {
29335
29782
  state(s) {
29336
- const laneId = `lane-${s.id}`;
29337
- b.lane(laneId, { width: laneW });
29338
- const eyebrow = s.initial ? "\u521D\u671F" : s.final ? "\u6700\u7D42" : "\u72B6\u614B";
29339
- b.node(s.id, {
29340
- lane: laneId,
29341
- stack: 0,
29342
- kind: "card",
29343
- w: stateW,
29344
- title: s.title,
29345
- eyebrow
29346
- });
29783
+ \u4E26\u3073.push({ \u7A2E: "\u72B6\u614B", i: \u72B6\u614B.length });
29784
+ \u72B6\u614B.push(s);
29347
29785
  stateOrder.set(s.id, stateOrder.size);
29348
- phaseActivate.push(s.id);
29786
+ return api;
29787
+ },
29788
+ mark(m) {
29789
+ \u4E26\u3073.push({ \u7A2E: "\u7BC0\u70B9", i: \u7BC0\u70B9.length });
29790
+ \u7BC0\u70B9.push(m);
29791
+ stateOrder.set(m.id, stateOrder.size);
29349
29792
  return api;
29350
29793
  },
29351
29794
  transition(t) {
29352
- const currentIdx = transIdx++;
29353
- const edgeId = `t${currentIdx}-${t.from}-${t.to}`;
29354
- const fromIdx = stateOrder.get(t.from) ?? 0;
29355
- const toIdx = stateOrder.get(t.to) ?? 0;
29356
- const isBack = toIdx < fromIdx;
29357
- b.edge(t.from, t.to, {
29358
- id: edgeId,
29359
- label: t.trigger,
29360
- ...t.guard ? { sub: t.guard } : {},
29361
- tone: t.tone ?? tone,
29362
- style: "solid",
29363
- ...isBack ? { routing: "back-detour" } : {}
29364
- });
29365
- phaseActivate.push(edgeId);
29795
+ \u9077\u79FB.push(t);
29366
29796
  return api;
29367
29797
  },
29368
29798
  build() {
29799
+ const \u8A2D\u8A08\u306E\u5F62 = \u72B6\u614B.some((s) => s.actions !== void 0 || s.subtitle !== void 0) || \u7BC0\u70B9.length > 0;
29800
+ const \u683C\u5B50 = \u72B6\u614B.some((s) => s.col !== void 0) || \u7BC0\u70B9.some((m) => m.col !== void 0);
29801
+ const \u4E2D\u8EAB = \u72B6\u614B.map((s) => {
29802
+ const \u884C = (s.actions ?? []).map((a) => a.note ? `${a.label}: ${a.note}` : a.label);
29803
+ const \u5370 = (s.actions ?? []).map((a) => FSM_ACTION_MARK[a.when]);
29804
+ const laneId = \u683C\u5B50 && s.col !== void 0 ? `col-${s.col}` : `lane-${s.id}`;
29805
+ const boxW = \u8A2D\u8A08\u306E\u5F62 ? fitRowsWidth(stateW, \u884C) : stateW;
29806
+ return { s, \u884C, \u5370, laneId, boxW, stack: \u683C\u5B50 ? s.row ?? 0 : 0 };
29807
+ });
29808
+ const \u7BC0\u70B9\u306E\u4E2D\u8EAB = \u7BC0\u70B9.map((m) => ({
29809
+ m,
29810
+ laneId: \u683C\u5B50 && m.col !== void 0 ? `col-${m.col}` : `lane-${m.id}`,
29811
+ stack: \u683C\u5B50 ? m.row ?? 0 : 0
29812
+ }));
29813
+ const \u5E2F\u5E45 = /* @__PURE__ */ new Map();
29814
+ for (const c of \u4E2D\u8EAB) \u5E2F\u5E45.set(c.laneId, Math.max(\u5E2F\u5E45.get(c.laneId) ?? 0, c.boxW));
29815
+ for (const c of \u7BC0\u70B9\u306E\u4E2D\u8EAB) \u5E2F\u5E45.set(c.laneId, Math.max(\u5E2F\u5E45.get(c.laneId) ?? 0, MARK_BOX));
29816
+ const \u6E08 = /* @__PURE__ */ new Set();
29817
+ const \u5E2F\u3092\u4F5C\u308B = (laneId) => {
29818
+ if (\u6E08.has(laneId)) return;
29819
+ \u6E08.add(laneId);
29820
+ b.lane(laneId, { width: boxLaneW(\u5E2F\u5E45.get(laneId) ?? stateW) });
29821
+ };
29822
+ for (const c of \u7BC0\u70B9\u306E\u4E2D\u8EAB) if (c.stack === 0) \u5E2F\u3092\u4F5C\u308B(c.laneId);
29823
+ for (const c of \u4E2D\u8EAB) \u5E2F\u3092\u4F5C\u308B(c.laneId);
29824
+ for (const c of \u7BC0\u70B9\u306E\u4E2D\u8EAB) \u5E2F\u3092\u4F5C\u308B(c.laneId);
29825
+ for (const o of \u4E26\u3073) {
29826
+ if (o.\u7A2E === "\u7BC0\u70B9") {
29827
+ const c2 = \u7BC0\u70B9\u306E\u4E2D\u8EAB[o.i];
29828
+ b.node(c2.m.id, {
29829
+ lane: c2.laneId,
29830
+ stack: c2.stack,
29831
+ kind: c2.m.kind === "start" ? "mark-start" : "mark-end",
29832
+ w: MARK_BOX,
29833
+ h: MARK_BOX,
29834
+ title: ""
29835
+ });
29836
+ phaseActivate.push(c2.m.id);
29837
+ continue;
29838
+ }
29839
+ const c = \u4E2D\u8EAB[o.i];
29840
+ const eyebrow = \u7BC0\u70B9.length > 0 ? void 0 : c.s.initial ? "\u521D\u671F" : c.s.final ? "\u6700\u7D42" : "\u72B6\u614B";
29841
+ b.node(c.s.id, {
29842
+ lane: c.laneId,
29843
+ stack: c.stack,
29844
+ kind: \u8A2D\u8A08\u306E\u5F62 ? "storage" : "card",
29845
+ w: c.boxW,
29846
+ title: c.s.title,
29847
+ ...eyebrow ? { eyebrow } : {},
29848
+ ...c.s.subtitle ? { subtitle: c.s.subtitle } : {},
29849
+ // 行を持たない状態でも欄を書く (#578)。 書かないとその箱だけ従来の意匠に落ち、
29850
+ // 呼び名が消える
29851
+ ...\u8A2D\u8A08\u306E\u5F62 ? { rows: c.\u884C, rowMarks: c.\u5370 } : {}
29852
+ });
29853
+ phaseActivate.push(c.s.id);
29854
+ }
29855
+ \u9077\u79FB.forEach((t, currentIdx) => {
29856
+ const edgeId = `t${currentIdx}-${t.from}-${t.to}`;
29857
+ const fromIdx = stateOrder.get(t.from) ?? 0;
29858
+ const toIdx = stateOrder.get(t.to) ?? 0;
29859
+ const isBack = toIdx < fromIdx;
29860
+ b.edge(t.from, t.to, {
29861
+ id: edgeId,
29862
+ label: t.trigger,
29863
+ ...t.guard ? { sub: t.guard } : {},
29864
+ tone: t.tone ?? tone,
29865
+ style: "solid",
29866
+ // 遷移の印は 1 種類だけ = 実線に開いた矢 (#578)。 線と印が分かれず、違いは語の中に入る
29867
+ head: t.head ?? "open",
29868
+ ...isBack ? { routing: "back-detour" } : {}
29869
+ });
29870
+ phaseActivate.push(edgeId);
29871
+ });
29369
29872
  b.phase(
29370
29873
  "fsm",
29371
29874
  { duration: 2400, title: preset.topic, body: "FSM \u306E\u5168 state + transition \u3092 visible \u5316\u3002" },
@@ -29429,72 +29932,103 @@ function infrastructure(preset) {
29429
29932
  };
29430
29933
  return api;
29431
29934
  }
29432
- var CLASS_RELATION_HEAD = {
29433
- extends: "triangle",
29434
- implements: "triangle",
29435
- uses: "open",
29436
- aggregates: "diamond",
29437
- composes: "diamond"
29935
+ var CLASS_RELATION_LOOK = {
29936
+ extends: { head: "triangle", fill: "hollow", tailHead: "none", tailFill: "solid", style: "solid" },
29937
+ implements: { head: "triangle", fill: "hollow", tailHead: "none", tailFill: "solid", style: "dashed" },
29938
+ aggregates: { head: "none", fill: "solid", tailHead: "diamond", tailFill: "hollow", style: "solid" },
29939
+ composes: { head: "none", fill: "solid", tailHead: "diamond", tailFill: "solid", style: "solid" },
29940
+ associates: { head: "open", fill: "solid", tailHead: "none", tailFill: "solid", style: "solid" },
29941
+ uses: { head: "open", fill: "solid", tailHead: "none", tailFill: "solid", style: "dashed" }
29438
29942
  };
29943
+ Object.fromEntries(
29944
+ Object.entries(CLASS_RELATION_LOOK).map(([k, v]) => [k, v.head])
29945
+ );
29946
+ function isPublic(row) {
29947
+ return !row.startsWith("-") && !row.startsWith("#");
29948
+ }
29949
+ function stripAccess(row) {
29950
+ const \u672C\u4F53 = isPublic(row) && !row.startsWith("+") ? row : row.slice(1);
29951
+ return \u672C\u4F53.replace("()", "");
29952
+ }
29439
29953
  function classDiagram(preset) {
29440
29954
  const classW = resolveStorageBoxW(preset.classWidth, "classDiagram", "classWidth");
29441
29955
  const tone = preset.defaultTone ?? "info";
29442
29956
  const b = diagram(preset.id, { topic: preset.topic });
29443
29957
  const classes = [];
29444
29958
  const phaseActivate = [];
29445
- let relIdx = 0;
29959
+ const \u7BB1 = [];
29960
+ const \u95A2\u4FC2 = [];
29446
29961
  const api = {
29447
29962
  class(c) {
29448
- const laneId = `lane-${c.id}`;
29449
- const rows = [];
29450
- if (c.attributes && c.attributes.length > 0) {
29451
- rows.push(...c.attributes);
29452
- }
29453
- if (c.methods && c.methods.length > 0) {
29454
- if (rows.length > 0) rows.push("\u2500\u2500\u2500");
29455
- rows.push(...c.methods);
29456
- }
29457
- const boxW = fitRowsWidth(classW, rows);
29458
- b.lane(laneId, { width: boxLaneW(boxW) });
29459
- b.node(c.id, {
29460
- lane: laneId,
29461
- stack: 0,
29462
- kind: "storage",
29463
- w: boxW,
29464
- title: c.title,
29465
- eyebrow: c.stereotype ?? "\u30AF\u30E9\u30B9",
29466
- ...rows.length > 0 ? { rows } : {}
29467
- });
29468
- classes.push(c.id);
29469
- phaseActivate.push(c.id);
29963
+ \u7BB1.push(c);
29470
29964
  return api;
29471
29965
  },
29472
29966
  relation(r) {
29473
- const currentIdx = relIdx++;
29474
- const edgeId = `cr-${currentIdx}-${r.from}-${r.to}`;
29475
- const label = r.label ?? r.type;
29476
- const sub = r.cardinality;
29477
- b.edge(r.from, r.to, {
29478
- id: edgeId,
29479
- label,
29480
- ...sub ? { sub } : {},
29481
- tone: r.tone ?? tone,
29482
- style: "solid",
29483
- // 関係の種類から端の形を決める (cdl#574)。 書いた形が勝つ
29484
- head: r.head ?? CLASS_RELATION_HEAD[r.type]
29485
- // CAR-531 SSOT ... 旧実装は `labelOffsetY: currentIdx % 2 === 0 ? -60 : 60` で
29486
- // 複数 relation label の path 沿い集中を強制分散していた。 CAR-430 で導入した
29487
- // engine 側 fan offset (FAN_STEP 40 world) が同 from / to 群を自動分散する現在、
29488
- // author 指定 ±60 は engine 分散と二重発火して extends label が SSOT
29489
- // LABEL_INIT_CLEARANCE (34、 CAR-630 で 26 → 34 復帰) を大幅に超える 114 world 離れる bug の直接原因になっていた。
29490
- // author 指定 offset を削除、 engine 側の LABEL_INIT_CLEARANCE + fan offset に一任する。
29491
- // 個別 relation で offset を再導入したい author は future extension (UmlRelation に
29492
- // labelOffsetY? を追加) で明示的に指定する経路を残す。
29493
- });
29494
- phaseActivate.push(edgeId);
29967
+ \u95A2\u4FC2.push(r);
29495
29968
  return api;
29496
29969
  },
29497
29970
  build() {
29971
+ const \u4E2D\u8EAB = \u7BB1.map((c) => {
29972
+ const rows = [];
29973
+ const rowMarks = [];
29974
+ for (const a of c.attributes ?? []) {
29975
+ rows.push(stripAccess(a));
29976
+ rowMarks.push({ shape: "square", filled: isPublic(a) });
29977
+ }
29978
+ if ((c.attributes?.length ?? 0) > 0 && (c.methods?.length ?? 0) > 0) {
29979
+ rows.push("");
29980
+ rowMarks.push(null);
29981
+ }
29982
+ for (const m of c.methods ?? []) {
29983
+ rows.push(stripAccess(m));
29984
+ rowMarks.push({ shape: "chevron", filled: isPublic(m) });
29985
+ }
29986
+ const boxW = fitRowsWidth(classW, rows);
29987
+ const laneId = c.col !== void 0 ? `col-${c.col}` : `lane-${c.id}`;
29988
+ return { c, rows, rowMarks, boxW, laneId, stack: c.col !== void 0 ? c.row ?? 0 : 0 };
29989
+ });
29990
+ const \u5E2F\u5E45 = /* @__PURE__ */ new Map();
29991
+ for (const m of \u4E2D\u8EAB) \u5E2F\u5E45.set(m.laneId, Math.max(\u5E2F\u5E45.get(m.laneId) ?? 0, m.boxW));
29992
+ const \u6E08 = /* @__PURE__ */ new Set();
29993
+ for (const m of \u4E2D\u8EAB) {
29994
+ if (\u6E08.has(m.laneId)) continue;
29995
+ \u6E08.add(m.laneId);
29996
+ b.lane(m.laneId, { width: boxLaneW(\u5E2F\u5E45.get(m.laneId) ?? m.boxW) });
29997
+ }
29998
+ for (const m of \u4E2D\u8EAB) {
29999
+ b.node(m.c.id, {
30000
+ lane: m.laneId,
30001
+ stack: m.stack,
30002
+ kind: "storage",
30003
+ w: m.boxW,
30004
+ title: m.c.title,
30005
+ // 種類の札は **書いた時だけ** 出す (#578)。 「クラス」 と書いた札は箱がクラスで
30006
+ // あることしか言わず、図の中の全ての箱に同じ字が並ぶ
30007
+ ...m.c.stereotype ? { eyebrow: m.c.stereotype } : {},
30008
+ ...m.rows.length > 0 ? { rows: m.rows, rowMarks: m.rowMarks } : {}
30009
+ });
30010
+ classes.push(m.c.id);
30011
+ phaseActivate.push(m.c.id);
30012
+ }
30013
+ \u95A2\u4FC2.forEach((r, currentIdx) => {
30014
+ const edgeId = `cr-${currentIdx}-${r.from}-${r.to}`;
30015
+ const \u898B\u305F\u76EE = CLASS_RELATION_LOOK[r.type];
30016
+ b.edge(r.from, r.to, {
30017
+ id: edgeId,
30018
+ label: r.label ?? r.type,
30019
+ ...r.cardinality ? { sub: r.cardinality } : {},
30020
+ tone: r.tone ?? tone,
30021
+ // 関係の種類から線と端の形を決める (cdl#574 / #578)。 書いた値が勝つ
30022
+ style: r.style ?? \u898B\u305F\u76EE.style,
30023
+ head: r.head ?? \u898B\u305F\u76EE.head,
30024
+ headFill: r.headFill ?? \u898B\u305F\u76EE.fill,
30025
+ tailHead: r.tailHead ?? \u898B\u305F\u76EE.tailHead,
30026
+ tailHeadFill: \u898B\u305F\u76EE.tailFill
30027
+ // CAR-531 SSOT ... label の分散は engine 側 (LABEL_INIT_CLEARANCE + fan offset) に
30028
+ // 一任する。 author 指定 ±60 は engine 分散と二重発火して label が大きく離れる
30029
+ });
30030
+ phaseActivate.push(edgeId);
30031
+ });
29498
30032
  b.phase(
29499
30033
  "class",
29500
30034
  { duration: 2400, title: preset.topic, body: "UML class \u5168 class + relation \u3092 visible \u5316\u3002" },
@@ -30009,6 +30543,7 @@ exports.ARROW_ENDPOINT_CENTER_TOL = ARROW_ENDPOINT_CENTER_TOL;
30009
30543
  exports.CHAIN_MIN_CLEARANCE = CHAIN_MIN_CLEARANCE;
30010
30544
  exports.CHAIN_SHIFT_MAX_ACCUMULATED = CHAIN_SHIFT_MAX_ACCUMULATED;
30011
30545
  exports.CHAIN_SHIFT_MAX_ITER = CHAIN_SHIFT_MAX_ITER;
30546
+ exports.CLASS_RELATION_LOOK = CLASS_RELATION_LOOK;
30012
30547
  exports.CLEARANCE_LABEL_LABEL = CLEARANCE_LABEL_LABEL;
30013
30548
  exports.CLEARANCE_LANE_LABEL = CLEARANCE_LANE_LABEL;
30014
30549
  exports.CLEARANCE_NODE_LABEL = CLEARANCE_NODE_LABEL;
@@ -30022,10 +30557,15 @@ exports.DIST_LABEL_PATH_MAX = DIST_LABEL_PATH_MAX;
30022
30557
  exports.DIST_LABEL_PATH_MIN = DIST_LABEL_PATH_MIN;
30023
30558
  exports.EDGE_HEADS = EDGE_HEADS;
30024
30559
  exports.EDGE_HEAD_DEFAULT = EDGE_HEAD_DEFAULT;
30560
+ exports.EDGE_HEAD_FILLS = EDGE_HEAD_FILLS;
30561
+ exports.EDGE_HEAD_FILL_DEFAULT = EDGE_HEAD_FILL_DEFAULT;
30025
30562
  exports.EDGE_LABEL_TEXT = EDGE_LABEL_TEXT;
30026
30563
  exports.EDGE_STUB_OUT = EDGE_STUB_OUT;
30564
+ exports.EDGE_STYLES = EDGE_STYLES;
30565
+ exports.ER_CARDINALITY_HEAD = ER_CARDINALITY_HEAD;
30027
30566
  exports.FAN_GAP = FAN_GAP;
30028
30567
  exports.FONT_RENDER_TOLERANCE_WORLD = FONT_RENDER_TOLERANCE_WORLD;
30568
+ exports.FSM_ACTION_MARK = FSM_ACTION_MARK;
30029
30569
  exports.LABEL_INIT_CLEARANCE = LABEL_INIT_CLEARANCE;
30030
30570
  exports.LABEL_INIT_CLEARANCE_DOTTED_FLOW = LABEL_INIT_CLEARANCE_DOTTED_FLOW;
30031
30571
  exports.LABEL_SIZE_TOLERANCE_WORLD = LABEL_SIZE_TOLERANCE_WORLD;
@@ -30039,6 +30579,7 @@ exports.NEAR_COLLISION_POLICY = NEAR_COLLISION_POLICY;
30039
30579
  exports.NODE_FRAME_SELECTOR = NODE_FRAME_SELECTOR;
30040
30580
  exports.NODE_KINDS = NODE_KINDS;
30041
30581
  exports.ROW_GAP_VARIANCE_TOL = ROW_GAP_VARIANCE_TOL;
30582
+ exports.SEQUENCE_MESSAGE_LOOK = SEQUENCE_MESSAGE_LOOK;
30042
30583
  exports.SPEC_CLEARANCE_POLICY = SPEC_CLEARANCE_POLICY;
30043
30584
  exports.STAGE_SVG_SELECTOR = STAGE_SVG_SELECTOR;
30044
30585
  exports.TARGET_LABEL_PATH_DIST = TARGET_LABEL_PATH_DIST;
@@ -30113,6 +30654,8 @@ exports.resolveTarget = resolveTarget;
30113
30654
  exports.resolveTargetAll = resolveTargetAll;
30114
30655
  exports.segmentsIntersect = segmentsIntersect2;
30115
30656
  exports.sequence = sequence;
30657
+ exports.sequenceBoardMetrics = sequenceBoardMetrics;
30658
+ exports.sequenceStepId = sequenceStepId;
30116
30659
  exports.signal = signal;
30117
30660
  exports.stateMachine = stateMachine;
30118
30661
  exports.stateMachine2 = stateMachine2;