@cardenelabs/cdl 0.45.0 → 0.47.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,61 @@ CDL (Chainome Diagram Language) の主要変更履歴。
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.47.0] - 2026-09-11
9
+
10
+ ### Added
11
+
12
+ - **箱に触れると関係する線だけが光るようにした** (#806)
13
+
14
+ クラス図と ER 図は **順番を持たない**。 段を進めて線を 1 本ずつ引く見せ方は作り手が決めた
15
+ 読ませ方でしかなく、読み手の問い (この箱はどこと繋がっているか) には答えていなかった。
16
+
17
+ `CdlDiagram.relationFocus` に `"hover"` を書くと、箱に触れた時に その箱 ・ 繋がる線 ・
18
+ 相手の箱だけが光り、他が沈む。 押すと留まり、もう一度押すか `Esc` で外れる
19
+ (線を目で追う間に指が外れると読めないため)。
20
+
21
+ | 書いた値 | 触れた時 |
22
+ |---|---|
23
+ | 書かない / `"off"` | 何もしない (既定) |
24
+ | `"hover"` | 関係する箱と線だけが光り、繋がる線に粒が流れる |
25
+
26
+ 光る集合は「触れた箱」「繋がる線」「線の相手の箱」 の 3 つで、**線の向きは問わない** =
27
+ クラス図の「継ぐ」 は子から親へ、 ER の「持つ」 は親から子へ向くが、読み手にとっては
28
+ どちらも「この箱の関係」 だから。 集合は `activate` と同じ所に入れるので、光る / 沈むの
29
+ 見え方は既にある仕組みをそのまま使う。
30
+
31
+ **触れていない間は段が勝つ** = この指定を足しても、段を持つ図の見え方は変わらない。
32
+ 指定を書かない図では箱に属性も受け口も付かないので、`tab` の順番も DOM も動かない。
33
+
34
+ `prefers-reduced-motion` が立っている時は粒を流さない。 光る側は控えない =
35
+ 動きではなく見分けなので、控える指定とは別の軸。
36
+
37
+ 箱は `role="button"` と `tabIndex` を持ち、`Enter` と `Space` でも留められる。
38
+
39
+ ## [0.46.0] - 2026-09-11
40
+
41
+ ### Fixed
42
+
43
+ - **上向きに曲がる線の角に突起が出るのを直した** (#801)
44
+
45
+ 縦の対の辺 (`fromSide` と `toSide` が上下) は、角を「手前 14 で直線を止め、弧で 14 先へ
46
+ 抜ける」 形で丸める。 その手前と先を上下に固定していたため、**相手が上にある辺**
47
+ (`fromSide === "top"`) では直線が角を 14 通り過ぎ、弧が戻る形になっていた。
48
+ 戻りが画面上の突起として見える。
49
+
50
+ 見本帳 `class-complex-demo` の実物で並べると差が読める。
51
+
52
+ ```
53
+ 正しい (下向き) 壊れていた (上向き)
54
+ L 903 1244 <- 角の 14 手前 L 1613 1244 <- 角を 14 通り過ぎている
55
+ Q 903 1258, 917 1258 Q 1613 1258, 1627 1258
56
+ ```
57
+
58
+ 角の丸めを **進む向き** から決めるようにした。 同じ誤りは `CAR-482` が別の分岐で
59
+ 1 度直しており、本件はその時に見落とされた 2 つ目の分岐。
60
+
61
+ 下向きの辺 (`fromSide === "bottom"`) の `d` は 1 文字も変わらない。
62
+
8
63
  ## [0.45.0] - 2026-09-11
9
64
 
10
65
  ### Added
@@ -2296,7 +2351,9 @@ Initial OSS release.
2296
2351
  使わない = annotated tag を push しても GitHub Release は作られず、 Release を作っていない版で
2297
2352
  行き止まりになる。
2298
2353
 
2299
- [Unreleased]: https://github.com/cardene777/cdl/compare/v0.45.0...HEAD
2354
+ [Unreleased]: https://github.com/cardene777/cdl/compare/v0.47.0...HEAD
2355
+ [0.47.0]: https://github.com/cardene777/cdl/compare/v0.46.0...v0.47.0
2356
+ [0.46.0]: https://github.com/cardene777/cdl/compare/v0.45.0...v0.46.0
2300
2357
  [0.45.0]: https://github.com/cardene777/cdl/compare/v0.44.0...v0.45.0
2301
2358
  [0.44.0]: https://github.com/cardene777/cdl/compare/v0.43.0...v0.44.0
2302
2359
  [0.43.0]: https://github.com/cardene777/cdl/compare/v0.42.0...v0.43.0
package/dist/index.cjs CHANGED
@@ -13751,6 +13751,8 @@ var EDGE_HEADS = [
13751
13751
  var EDGE_HEAD_FILL_DEFAULT = "solid";
13752
13752
  var EDGE_HEAD_FILLS = ["solid", "hollow"];
13753
13753
  var EDGE_REVEALS = ["phase", "all"];
13754
+ var RELATION_FOCUSES = ["off", "hover"];
13755
+ var RELATION_FOCUS_DEFAULT = "off";
13754
13756
  var EDGE_REVEAL_DEFAULT = "phase";
13755
13757
 
13756
13758
  // src/kinds/text-width.ts
@@ -18182,6 +18184,9 @@ var LANE_PAD_MIN = 25;
18182
18184
  // src/layout/edges.ts
18183
18185
  var VERTICAL_DETOUR_GAP = 50;
18184
18186
  var VERTICAL_DETOUR_CORNER = 14;
18187
+ function verticalCornerDir(from, to) {
18188
+ return to < from ? -1 : 1;
18189
+ }
18185
18190
  var VERTICAL_DETOUR_EXIT = 45;
18186
18191
  function nearestBoxDistance(anchorX, dir, verticalTop, verticalBottom, boxes) {
18187
18192
  let nearest = Infinity;
@@ -19363,12 +19368,14 @@ function routePath(from, to, fromSide, toSide, obstacles = [], detourSide, _from
19363
19368
  midY2 = retreatBelowMidBlockers(midY2);
19364
19369
  const corner = 14;
19365
19370
  const labelY = midY2 - labelInit;
19371
+ const \u7E26\u306E\u5411\u304DA = verticalCornerDir(A.y, midY2);
19372
+ const \u7E26\u306E\u5411\u304DB = verticalCornerDir(midY2, B.y);
19366
19373
  return {
19367
19374
  d: `M ${A.x} ${A.y}
19368
- L ${A.x} ${midY2 - corner}
19375
+ L ${A.x} ${midY2 - \u7E26\u306E\u5411\u304DA * corner}
19369
19376
  Q ${A.x} ${midY2}, ${A.x + xDir * corner} ${midY2}
19370
19377
  L ${B.x - xDir * corner} ${midY2}
19371
- Q ${B.x} ${midY2}, ${B.x} ${midY2 + corner}
19378
+ Q ${B.x} ${midY2}, ${B.x} ${midY2 + \u7E26\u306E\u5411\u304DB * corner}
19372
19379
  L ${B.x} ${B.y}`,
19373
19380
  labelX: (A.x + B.x) / 2,
19374
19381
  labelY,
@@ -20717,6 +20724,7 @@ function layout(diag) {
20717
20724
  ...diag.derived ? { derived: diag.derived } : {},
20718
20725
  // 矢印をいつ出すか (#582)。 描き手が読む欄なので、そのまま持ち越す
20719
20726
  ...diag.edgeReveal ? { edgeReveal: diag.edgeReveal } : {},
20727
+ ...diag.relationFocus ? { relationFocus: diag.relationFocus } : {},
20720
20728
  // どの配色で描くか (#1553)。 空文字は書かなかった扱いにする = 空の目印を出すと、
20721
20729
  // 消費側の `[data-cdl-palette]` が配色を書いていない図にも当たる。
20722
20730
  //
@@ -22804,7 +22812,8 @@ function CdlEdgeView({
22804
22812
  active,
22805
22813
  drawing,
22806
22814
  progress,
22807
- stateValues
22815
+ stateValues,
22816
+ flowing = false
22808
22817
  }) {
22809
22818
  const color = edgeColor(edge);
22810
22819
  const marker = edgeHeadMarkerName(edge.tone, edge.head, !active, edge.headFill);
@@ -22822,6 +22831,7 @@ function CdlEdgeView({
22822
22831
  "data-cdl-from": edge.from,
22823
22832
  "data-cdl-to": edge.to,
22824
22833
  "data-cdl-active": active ? "true" : "false",
22834
+ "data-cdl-flowing": flowing ? "true" : "false",
22825
22835
  "data-cdl-drawing": drawing ? "true" : "false",
22826
22836
  "data-cdl-path-d": edge.d,
22827
22837
  "data-cdl-label-x": edge.labelX,
@@ -22874,7 +22884,11 @@ function CdlEdgeView({
22874
22884
  strokeWidth: 14,
22875
22885
  strokeLinecap: "round"
22876
22886
  }
22877
- )
22887
+ ),
22888
+ flowing && !isDotted && /* @__PURE__ */ jsxRuntime.jsxs("g", { "data-cdl-role": "edge-flow", children: [
22889
+ /* @__PURE__ */ jsxRuntime.jsx("path", { id: glowPathId, d: edge.d, fill: "none", stroke: "none" }),
22890
+ /* @__PURE__ */ jsxRuntime.jsx(EdgeGlowCircles, { color, edgeId: edge.id })
22891
+ ] })
22878
22892
  ]
22879
22893
  }
22880
22894
  );
@@ -30650,7 +30664,8 @@ function CdlNodeView({
30650
30664
  progress,
30651
30665
  stateValues,
30652
30666
  currentPhaseId,
30653
- chartLineScale
30667
+ chartLineScale,
30668
+ \u95A2\u4FC2\u306E\u53D7\u3051\u53E3
30654
30669
  }) {
30655
30670
  const value = node.value ? interpolate(node.value, stateValues) : void 0;
30656
30671
  const rows = (node.rows ?? []).map((r) => interpolate(r, stateValues));
@@ -30972,6 +30987,23 @@ function CdlNodeView({
30972
30987
  "data-cdl-eyebrow": resolvedNode.eyebrow ?? "",
30973
30988
  "data-cdl-offset-x": offsetX || void 0,
30974
30989
  "data-cdl-offset-y": offsetY || void 0,
30990
+ ...\u95A2\u4FC2\u306E\u53D7\u3051\u53E3 === void 0 ? {} : {
30991
+ // 読み上げに「押せる」 ことを伝え、 tab でも辿れるようにする。
30992
+ role: "button",
30993
+ tabIndex: 0,
30994
+ "aria-pressed": \u95A2\u4FC2\u306E\u53D7\u3051\u53E3.\u7126\u70B9 === node.id,
30995
+ style: { cursor: "pointer" },
30996
+ onPointerEnter: () => \u95A2\u4FC2\u306E\u53D7\u3051\u53E3.\u89E6\u308C\u305F(node.id),
30997
+ onPointerLeave: () => \u95A2\u4FC2\u306E\u53D7\u3051\u53E3.\u96E2\u308C\u305F(),
30998
+ onFocus: () => \u95A2\u4FC2\u306E\u53D7\u3051\u53E3.\u89E6\u308C\u305F(node.id),
30999
+ onBlur: () => \u95A2\u4FC2\u306E\u53D7\u3051\u53E3.\u96E2\u308C\u305F(),
31000
+ onClick: () => \u95A2\u4FC2\u306E\u53D7\u3051\u53E3.\u62BC\u3057\u305F(node.id),
31001
+ onKeyDown: (e) => {
31002
+ if (e.key !== "Enter" && e.key !== " ") return;
31003
+ e.preventDefault();
31004
+ \u95A2\u4FC2\u306E\u53D7\u3051\u53E3.\u62BC\u3057\u305F(node.id);
31005
+ }
31006
+ },
30975
31007
  transform: hasOffset ? `translate(${offsetX}, ${offsetY})` : void 0,
30976
31008
  opacity: boundOpacity,
30977
31009
  "data-cdl-tone": node.tone,
@@ -30995,6 +31027,18 @@ function CdlNodeView({
30995
31027
  }
30996
31028
  );
30997
31029
  }
31030
+
31031
+ // src/render/relation-focus.ts
31032
+ function relatedFocusSet(edges, nodeId) {
31033
+ const \u96C6\u5408 = /* @__PURE__ */ new Set([nodeId]);
31034
+ for (const e of edges) {
31035
+ if (e.from !== nodeId && e.to !== nodeId) continue;
31036
+ \u96C6\u5408.add(e.id);
31037
+ \u96C6\u5408.add(e.from);
31038
+ \u96C6\u5408.add(e.to);
31039
+ }
31040
+ return \u96C6\u5408;
31041
+ }
30998
31042
  function CdlStage({
30999
31043
  laid,
31000
31044
  currentPhase,
@@ -31005,7 +31049,35 @@ function CdlStage({
31005
31049
  compact = false,
31006
31050
  svgRef
31007
31051
  }) {
31008
- const activeSet = new Set(currentPhase?.activate ?? []);
31052
+ const [\u7126\u70B9, set\u7126\u70B9] = react.useState(null);
31053
+ const \u95A2\u4FC2\u3092\u5149\u3089\u305B\u308B = (laid.relationFocus ?? RELATION_FOCUS_DEFAULT) === "hover";
31054
+ const \u52D5\u304D\u3092\u63A7\u3048\u308B = useReducedMotion();
31055
+ react.useEffect(() => {
31056
+ if (!\u95A2\u4FC2\u3092\u5149\u3089\u305B\u308B) set\u7126\u70B9(null);
31057
+ }, [\u95A2\u4FC2\u3092\u5149\u3089\u305B\u308B]);
31058
+ react.useEffect(() => {
31059
+ if (!\u95A2\u4FC2\u3092\u5149\u3089\u305B\u308B || \u7126\u70B9?.\u7559\u3081\u305F !== true) return;
31060
+ const \u62BC\u3057\u305F2 = (e) => {
31061
+ if (e.key === "Escape") set\u7126\u70B9(null);
31062
+ };
31063
+ window.addEventListener("keydown", \u62BC\u3057\u305F2);
31064
+ return () => window.removeEventListener("keydown", \u62BC\u3057\u305F2);
31065
+ }, [\u95A2\u4FC2\u3092\u5149\u3089\u305B\u308B, \u7126\u70B9?.\u7559\u3081\u305F]);
31066
+ const \u89E6\u308C\u305F = react.useCallback((id) => {
31067
+ set\u7126\u70B9((\u524D) => \u524D?.\u7559\u3081\u305F === true ? \u524D : { id, \u7559\u3081\u305F: false });
31068
+ }, []);
31069
+ const \u96E2\u308C\u305F = react.useCallback(() => {
31070
+ set\u7126\u70B9((\u524D) => \u524D?.\u7559\u3081\u305F === true ? \u524D : null);
31071
+ }, []);
31072
+ const \u62BC\u3057\u305F = react.useCallback((id) => {
31073
+ set\u7126\u70B9((\u524D) => \u524D?.id === id && \u524D.\u7559\u3081\u305F ? null : { id, \u7559\u3081\u305F: true });
31074
+ }, []);
31075
+ const \u7126\u70B9\u306E\u96C6\u5408 = react.useMemo(
31076
+ () => \u95A2\u4FC2\u3092\u5149\u3089\u305B\u308B && \u7126\u70B9 !== null ? relatedFocusSet(laid.edges, \u7126\u70B9.id) : null,
31077
+ [\u95A2\u4FC2\u3092\u5149\u3089\u305B\u308B, \u7126\u70B9, laid.edges]
31078
+ );
31079
+ const activeSet = \u7126\u70B9\u306E\u96C6\u5408 ?? new Set(currentPhase?.activate ?? []);
31080
+ 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;
31009
31081
  const \u77E2\u5370\u306E\u51FA\u756A = react.useMemo(() => {
31010
31082
  const \u521D\u51FA = /* @__PURE__ */ new Map();
31011
31083
  laid.phases.forEach((p, i) => {
@@ -31236,6 +31308,7 @@ function CdlStage({
31236
31308
  {
31237
31309
  edge,
31238
31310
  active: activeSet.has(edge.id),
31311
+ flowing: \u7126\u70B9\u306E\u96C6\u5408?.has(edge.id) === true && !\u52D5\u304D\u3092\u63A7\u3048\u308B,
31239
31312
  drawing: \u77E2\u5370\u306E\u51FA\u756A.\u4F38\u3070\u3059\u304B(edge.id),
31240
31313
  progress: \u77E2\u5370\u306E\u51FA\u756A.\u4F38\u3070\u3059\u304B(edge.id) ? \u63CF\u304F\u9032\u307F : progress,
31241
31314
  stateValues
@@ -31249,6 +31322,7 @@ function CdlStage({
31249
31322
  {
31250
31323
  node,
31251
31324
  active: activeSet.has(node.id),
31325
+ \u95A2\u4FC2\u306E\u53D7\u3051\u53E3: \u7BB1\u306E\u53D7\u3051\u53E3,
31252
31326
  drawing: \u63CF\u304F,
31253
31327
  progress: \u63CF\u304F ? \u63CF\u304F\u9032\u307F : progress,
31254
31328
  stateValues,
@@ -32876,6 +32950,8 @@ exports.NEAR_COLLISION_DEFAULT_MIN = NEAR_COLLISION_DEFAULT_MIN;
32876
32950
  exports.NEAR_COLLISION_POLICY = NEAR_COLLISION_POLICY;
32877
32951
  exports.NODE_FRAME_SELECTOR = NODE_FRAME_SELECTOR;
32878
32952
  exports.NODE_KINDS = NODE_KINDS;
32953
+ exports.RELATION_FOCUSES = RELATION_FOCUSES;
32954
+ exports.RELATION_FOCUS_DEFAULT = RELATION_FOCUS_DEFAULT;
32879
32955
  exports.ROW_GAP_VARIANCE_TOL = ROW_GAP_VARIANCE_TOL;
32880
32956
  exports.SEQUENCE_MESSAGE_LOOK = SEQUENCE_MESSAGE_LOOK;
32881
32957
  exports.SPEC_CLEARANCE_POLICY = SPEC_CLEARANCE_POLICY;
@@ -32941,6 +33017,7 @@ exports.pxToWorld = pxToWorld;
32941
33017
  exports.quadrant = quadrant;
32942
33018
  exports.rectRectClearance = rectRectClearance;
32943
33019
  exports.rectRectOverlapArea = rectRectOverlapArea;
33020
+ exports.relatedFocusSet = relatedFocusSet;
32944
33021
  exports.rendersRows = rendersRows;
32945
33022
  exports.requiredContrastRatio = requiredContrastRatio;
32946
33023
  exports.requiredNearClearance = requiredNearClearance;