@cardenelabs/cdl 0.50.0 → 0.51.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/CHANGELOG.md CHANGED
@@ -5,6 +5,23 @@ CDL (Chainome Diagram Language) の主要変更履歴。
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.51.0] - 2026-09-11
9
+
10
+ ### Changed
11
+
12
+ - **クラス図の多重度を、線の中ほどの札から行き先の端へ移した** (#821)
13
+
14
+ クラス図の関係に書いた多重度 (`cardinality: "1..*"`) は、札の語の下の行 (`sub`) に入っていた。
15
+ 語と続けて読まれ (英字の頃は `HAS` と `1..*` が「HAS1」)、 どちらの端の数かも読めなかった。
16
+ UML と同じく、行き先の端の近くへ等幅の字で添える。
17
+
18
+ 置き場所は線の最後の区間から決まる。 端から 22 world 戻った点の、札と反対の側に置く
19
+ (札を持たない線は、横向きなら下、縦向きなら左)。 札と同じ側に置くと、見本帳のクラス図で
20
+ 1 本が札と重なり、もう 1 本が札と 2 world まで寄った。
21
+
22
+ 線に「行き先の端に添える字」 の欄 `headLabel` を足した。 クラス図の組み立て器は多重度を
23
+ `sub` ではなく `headLabel` に渡す。 ER 図の多重度の出し方は変えない (端の印で示す)。
24
+
8
25
  ## [0.50.0] - 2026-09-11
9
26
 
10
27
  ### Fixed
@@ -2418,7 +2435,8 @@ Initial OSS release.
2418
2435
  使わない = annotated tag を push しても GitHub Release は作られず、 Release を作っていない版で
2419
2436
  行き止まりになる。
2420
2437
 
2421
- [Unreleased]: https://github.com/cardene777/cdl/compare/v0.50.0...HEAD
2438
+ [Unreleased]: https://github.com/cardene777/cdl/compare/v0.51.0...HEAD
2439
+ [0.51.0]: https://github.com/cardene777/cdl/compare/v0.50.0...v0.51.0
2422
2440
  [0.50.0]: https://github.com/cardene777/cdl/compare/v0.49.0...v0.50.0
2423
2441
  [0.49.0]: https://github.com/cardene777/cdl/compare/v0.48.0...v0.49.0
2424
2442
  [0.48.0]: https://github.com/cardene777/cdl/compare/v0.47.0...v0.48.0
package/dist/index.cjs CHANGED
@@ -9501,6 +9501,7 @@ function diagram(id, options) {
9501
9501
  tailHeadFill: opts.tailHeadFill,
9502
9502
  guard: opts.guard,
9503
9503
  cardinality: opts.cardinality,
9504
+ headLabel: opts.headLabel,
9504
9505
  labelOffsetX: opts.labelOffsetX,
9505
9506
  labelOffsetY: opts.labelOffsetY,
9506
9507
  routing: opts.routing,
@@ -9627,6 +9628,7 @@ function diagram(id, options) {
9627
9628
  tailHeadFill: d.tailHeadFill,
9628
9629
  guard: d.guard,
9629
9630
  cardinality: d.cardinality,
9631
+ headLabel: d.headLabel,
9630
9632
  labelOffsetX: d.labelOffsetX,
9631
9633
  labelOffsetY: d.labelOffsetY,
9632
9634
  routing: d.routing,
@@ -14105,10 +14107,10 @@ function \u66F8\u8A18\u7D20\u306B\u5206\u3051\u308B(text) {
14105
14107
  if (typeof \u533A\u5207\u308A !== "function") return [...text];
14106
14108
  return [...new \u533A\u5207\u308A(void 0, { granularity: "grapheme" }).segment(text)].map((g) => g.segment);
14107
14109
  }
14108
- function \u7BB1\u306B\u7A4D\u3080(\u884C, \u7BB1\u306E\u5E45, \u7BB1\u306E\u9AD8\u30552) {
14110
+ function \u7BB1\u306B\u7A4D\u3080(\u884C, \u7BB1\u306E\u5E45, \u7BB1\u306E\u9AD8\u30553) {
14109
14111
  const \u5019\u88DC = \u884C.filter((r, i) => i === 0 || r.text.trim() !== "");
14110
14112
  if (\u5019\u88DC.length === 0) return [];
14111
- const \u4F7F\u3048\u308B\u9AD8\u3055 = \u7BB1\u306E\u9AD8\u30552 - \u4E0A\u4E0B\u306E\u4F59\u767D * 2;
14113
+ const \u4F7F\u3048\u308B\u9AD8\u3055 = \u7BB1\u306E\u9AD8\u30553 - \u4E0A\u4E0B\u306E\u4F59\u767D * 2;
14112
14114
  const \u9AD8\u3055 = (rs) => rs.reduce((a, r) => a + r.fontSize * \u884C\u9001\u308A\u306E\u6BD4, 0);
14113
14115
  const \u6B8B\u3059 = [...\u5019\u88DC];
14114
14116
  while (\u6B8B\u3059.length > 1 && \u9AD8\u3055(\u6B8B\u3059) > \u4F7F\u3048\u308B\u9AD8\u3055) \u6B8B\u3059.pop();
@@ -14835,7 +14837,7 @@ function \u30D9\u30B8\u30A8\u306E\u7D2F\u7A4D\u5272\u5408(\u70B9, \u5236\u5FA1\u
14835
14837
  function \u7BB1\u306E\u6587\u5B57({
14836
14838
  \u884C,
14837
14839
  x,
14838
- \u7BB1\u306E\u9AD8\u3055: \u7BB1\u306E\u9AD8\u30552,
14840
+ \u7BB1\u306E\u9AD8\u3055: \u7BB1\u306E\u9AD8\u30553,
14839
14841
  fill,
14840
14842
  \u540D\u524D\u306E\u592A\u3055
14841
14843
  }) {
@@ -14844,7 +14846,7 @@ function \u7BB1\u306E\u6587\u5B57({
14844
14846
  {
14845
14847
  "data-cdl-role": r.\u5F79\u5272 === "subtitle" ? "tree-node-subtitle" : void 0,
14846
14848
  x,
14847
- y: \u7BB1\u306E\u9AD8\u30552 / 2 + r.dy,
14849
+ y: \u7BB1\u306E\u9AD8\u30553 / 2 + r.dy,
14848
14850
  fontSize: r.fontSize,
14849
14851
  fontWeight: r.\u5F79\u5272 === "title" ? \u540D\u524D\u306E\u592A\u3055 : 500,
14850
14852
  textAnchor: "middle",
@@ -22931,6 +22933,73 @@ function \u7B49\u5E45\u306E\u5B57(text, fontSize) {
22931
22933
  });
22932
22934
  return out;
22933
22935
  }
22936
+
22937
+ // src/layout/head-label.ts
22938
+ var \u7AEF\u306E\u5B57\u3092\u623B\u3059\u8DDD\u96E2 = 22;
22939
+ var \u7AEF\u306E\u5B57\u3068\u7DDA\u306E\u9699\u9593 = 8;
22940
+ var \u7AEF\u306E\u5B57\u306E\u5927\u304D\u3055 = EDGE_LABEL_TEXT.sub.fontSize;
22941
+ var \u7BB1\u306E\u9AD8\u30552 = Math.round(\u7AEF\u306E\u5B57\u306E\u5927\u304D\u3055 * 1.2);
22942
+ var BASELINE_RATIO = 0.8;
22943
+ function \u6700\u5F8C\u306E\u5411\u304D(d) {
22944
+ const segs = extractPathSegments(d);
22945
+ const last = segs[segs.length - 1];
22946
+ if (!last) return null;
22947
+ const end = { x: last.x2, y: last.y2 };
22948
+ for (let i = segs.length - 1; i >= 0; i--) {
22949
+ const s = segs[i];
22950
+ const \u5236\u5FA1\u70B9 = s.cmd === "Q" ? s.ctrl?.[0] : s.cmd === "C" ? s.ctrl?.[1] : void 0;
22951
+ for (const p of [\u5236\u5FA1\u70B9, { x: s.x1, y: s.y1 }]) {
22952
+ if (!p) continue;
22953
+ const dx = end.x - p.x;
22954
+ const dy = end.y - p.y;
22955
+ const len = Math.hypot(dx, dy);
22956
+ if (len > 1e-6) return { end, ux: dx / len, uy: dy / len };
22957
+ }
22958
+ }
22959
+ return null;
22960
+ }
22961
+ function \u7AEF\u306E\u5B57\u3092\u7F6E\u304F(edge) {
22962
+ const text = edge.headLabel?.trim();
22963
+ if (!text) return null;
22964
+ const \u5411\u304D = \u6700\u5F8C\u306E\u5411\u304D(edge.d);
22965
+ if (!\u5411\u304D) return null;
22966
+ const { end, ux, uy } = \u5411\u304D;
22967
+ const bx = end.x - ux * \u7AEF\u306E\u5B57\u3092\u623B\u3059\u8DDD\u96E2;
22968
+ const by = end.y - uy * \u7AEF\u306E\u5B57\u3092\u623B\u3059\u8DDD\u96E2;
22969
+ const w = measureTextWidth(text, {
22970
+ fontSize: \u7AEF\u306E\u5B57\u306E\u5927\u304D\u3055,
22971
+ fontWeight: EDGE_LABEL_TEXT.sub.fontWeight,
22972
+ fontFamily: "mono"
22973
+ });
22974
+ const h = \u7BB1\u306E\u9AD8\u30552;
22975
+ const \u672D = hasRenderedLabel(edge) && edge.labelX !== void 0 && edge.labelY !== void 0 ? { x: edge.labelX, y: edge.labelY } : null;
22976
+ let anchor;
22977
+ let box;
22978
+ if (Math.abs(ux) >= Math.abs(uy)) {
22979
+ const \u53F3\u3078\u9032\u3080 = ux > 0;
22980
+ const \u4E0A\u306B\u7F6E\u304F = \u672D !== null && \u672D.y > by;
22981
+ anchor = \u53F3\u3078\u9032\u3080 ? "end" : "start";
22982
+ box = {
22983
+ x: \u53F3\u3078\u9032\u3080 ? bx - w : bx,
22984
+ y: \u4E0A\u306B\u7F6E\u304F ? by - \u7AEF\u306E\u5B57\u3068\u7DDA\u306E\u9699\u9593 - h : by + \u7AEF\u306E\u5B57\u3068\u7DDA\u306E\u9699\u9593,
22985
+ w,
22986
+ h
22987
+ };
22988
+ } else {
22989
+ const \u4E0B\u3078\u9032\u3080 = uy > 0;
22990
+ const \u53F3\u306B\u7F6E\u304F = \u672D !== null && \u672D.x < bx;
22991
+ anchor = \u53F3\u306B\u7F6E\u304F ? "start" : "end";
22992
+ box = {
22993
+ x: \u53F3\u306B\u7F6E\u304F ? bx + \u7AEF\u306E\u5B57\u3068\u7DDA\u306E\u9699\u9593 : bx - \u7AEF\u306E\u5B57\u3068\u7DDA\u306E\u9699\u9593 - w,
22994
+ y: \u4E0B\u3078\u9032\u3080 ? by - h : by,
22995
+ w,
22996
+ h
22997
+ };
22998
+ }
22999
+ const x = anchor === "start" ? box.x : box.x + box.w;
23000
+ const y = box.y + (h - \u7AEF\u306E\u5B57\u306E\u5927\u304D\u3055) / 2 + \u7AEF\u306E\u5B57\u306E\u5927\u304D\u3055 * BASELINE_RATIO;
23001
+ return { x, y, anchor, box };
23002
+ }
22934
23003
  function defaultEdgeWidth(active) {
22935
23004
  return active ? 2.5 : 1.25;
22936
23005
  }
@@ -23193,6 +23262,33 @@ function CdlEdgeLabelView({
23193
23262
  }
23194
23263
  );
23195
23264
  }
23265
+ function CdlEdgeHeadLabelView({
23266
+ edge,
23267
+ active
23268
+ }) {
23269
+ const \u7F6E\u304D\u5834\u6240 = \u7AEF\u306E\u5B57\u3092\u7F6E\u304F(edge);
23270
+ if (!\u7F6E\u304D\u5834\u6240) return null;
23271
+ const color = edgeColor(edge);
23272
+ return /* @__PURE__ */ jsxRuntime.jsx(
23273
+ "text",
23274
+ {
23275
+ "data-cdl-role": "edge-end-label",
23276
+ "data-cdl-edge-end-label-for": edge.id,
23277
+ x: \u7F6E\u304D\u5834\u6240.x,
23278
+ y: \u7F6E\u304D\u5834\u6240.y,
23279
+ fontSize: \u7AEF\u306E\u5B57\u306E\u5927\u304D\u3055,
23280
+ fontWeight: EDGE_LABEL_TEXT.sub.fontWeight,
23281
+ textAnchor: \u7F6E\u304D\u5834\u6240.anchor,
23282
+ fill: active ? color : "var(--cdl-text-dim, #5a6270)",
23283
+ stroke: "var(--cdl-label-bg, #ffffff)",
23284
+ strokeWidth: 4,
23285
+ strokeLinejoin: "round",
23286
+ paintOrder: "stroke",
23287
+ style: { fontFamily: fontFamilyAttr("sub") },
23288
+ children: \u7B49\u5E45\u306E\u5B57(edge.headLabel, \u7AEF\u306E\u5B57\u306E\u5927\u304D\u3055)
23289
+ }
23290
+ );
23291
+ }
23196
23292
 
23197
23293
  // src/kinds/node-tone.ts
23198
23294
  function nodeAccent(node, fallback) {
@@ -31477,6 +31573,14 @@ function CdlStage({
31477
31573
  },
31478
31574
  `label-${edge.id}`
31479
31575
  )),
31576
+ laid.edges.filter((edge) => edge.headLabel && \u77E2\u5370\u306E\u51FA\u756A.\u51FA\u308B\u304B(edge.id)).map((edge) => /* @__PURE__ */ jsxRuntime.jsx(
31577
+ CdlEdgeHeadLabelView,
31578
+ {
31579
+ edge,
31580
+ active: activeSet.has(edge.id)
31581
+ },
31582
+ `head-label-${edge.id}`
31583
+ )),
31480
31584
  debug && /* @__PURE__ */ jsxRuntime.jsxs("g", { pointerEvents: "none", children: [
31481
31585
  laid.bboxes.map((bbox, idx) => {
31482
31586
  const color = bbox.kind === "node" ? "#3b82f6" : bbox.kind === "lane-label" ? "#a855f7" : bbox.kind === "edge-label" ? "#10b981" : "#f59e0b";
@@ -32521,7 +32625,9 @@ function classDiagram(preset) {
32521
32625
  b.edge(r.from, r.to, {
32522
32626
  id: edgeId,
32523
32627
  label: r.label ?? r.type,
32524
- ...r.cardinality ? { sub: r.cardinality } : {},
32628
+ // 多重度は行き先の端に添える (#821)。 札の下の行に入れると語と続けて読まれ
32629
+ // (英字の頃は「HAS 1..*」 が「HAS1」)、 どちらの端の数かも読めない
32630
+ ...r.cardinality ? { headLabel: r.cardinality } : {},
32525
32631
  tone: r.tone ?? \u56F3\u306E\u65E2\u5B9A ?? CLASS_RELATION_TONE[r.type],
32526
32632
  // 関係の種類から線と端の形を決める (cdl#574 / #578)。 書いた値が勝つ
32527
32633
  style: r.style ?? \u898B\u305F\u76EE.style,