@cardenelabs/cdl 0.46.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.
@@ -168,6 +168,18 @@ declare const EDGE_HEAD_FILLS: readonly EdgeHeadFill[];
168
168
  type EdgeReveal = "phase" | "all";
169
169
  /** 書ける値の一覧 (#582)。 利用側はここから導く = 手で並べると増えた時に取り残される */
170
170
  declare const EDGE_REVEALS: readonly EdgeReveal[];
171
+ /**
172
+ * 触れた箱の関係だけを光らせるか (#806)。
173
+ *
174
+ * - `"off"` ... 何もしない。 **既定**
175
+ * - `"hover"` ... 箱に触れると その箱 ・ 繋がる線 ・ 相手の箱だけが光り、他が沈む。
176
+ * 押すと留まり、もう一度押すか `Esc` で外れる
177
+ */
178
+ type RelationFocus = "off" | "hover";
179
+ /** 書ける値の一覧 (#806)。 利用側はここから導く */
180
+ declare const RELATION_FOCUSES: readonly RelationFocus[];
181
+ /** 書かなかった時の値 (#806)。 既存の図の見え方を変えないため `"off"` */
182
+ declare const RELATION_FOCUS_DEFAULT: RelationFocus;
171
183
  /** 書かなかった時の値 (#582) */
172
184
  declare const EDGE_REVEAL_DEFAULT: EdgeReveal;
173
185
  /**
@@ -2533,6 +2545,16 @@ type CdlDiagram = {
2533
2545
  * `"all"` は「段で光らせはするが、線そのものは最初から見せたい」 図のための逃げ道。
2534
2546
  */
2535
2547
  edgeReveal?: EdgeReveal;
2548
+ /**
2549
+ * 触れた箱の関係だけを光らせるか (既定 = `"off"`、 #806)。
2550
+ *
2551
+ * クラス図と ER 図は順番を持たない = 段を進めて線を 1 本ずつ引く見せ方は、作り手が決めた
2552
+ * 読ませ方でしかなく、読み手の問い (この箱はどこと繋がっているか) に答えない。
2553
+ * `"hover"` を書くと、箱に触れた時だけ その箱 ・ 繋がる線 ・ 相手の箱が光る。
2554
+ *
2555
+ * **触れていない間は段が勝つ** = この指定を足しても、段を持つ図の見え方は変わらない。
2556
+ */
2557
+ relationFocus?: RelationFocus;
2536
2558
  /**
2537
2559
  * interactive input widgets (v0.5+、 CAR #243)。
2538
2560
  * builder chain の `.input.slider() / .number() / .dropdown() / .toggle()` で追加。
@@ -2749,6 +2771,8 @@ type LaidDiagram = {
2749
2771
  derived?: CdlDerivedValue[];
2750
2772
  /** 矢印をいつ出すか (`CdlDiagram.edgeReveal` をそのまま引き継ぐ、 #582) */
2751
2773
  edgeReveal?: EdgeReveal;
2774
+ /** 触れた箱の関係を光らせるか (`CdlDiagram.relationFocus` をそのまま引き継ぐ、 #806) */
2775
+ relationFocus?: RelationFocus;
2752
2776
  /**
2753
2777
  * キャンバス全要素の bounding box list。
2754
2778
  * engine が「ここに何があるか」 を全部把握、 衝突検証 / debug 表示 / validation で使う。
@@ -2916,4 +2940,4 @@ type CdlDiagramViewProps = {
2916
2940
  };
2917
2941
  declare function CdlDiagramView({ diagram, laid: laidProp, hideHeader, hideMiniPhaseIndicator, focusPhaseId, debug, emitGeometryWarn, headingLevel, interactiveHandlers }: CdlDiagramViewProps): JSX.Element;
2918
2942
 
2919
- export { createScrollProgressHandle as $, EDGE_HEAD_FILL_DEFAULT as A, type BBox as B, type CdlDiagram as C, EDGE_REVEALS as D, type EdgeStyle as E, EDGE_REVEAL_DEFAULT as F, EDGE_STYLES as G, type EdgeReveal as H, type InteractiveHandlerMap as I, type JourneyEmotion as J, type InteractiveSignalsAccessor as K, type LaidDiagram as L, type LaidLane as M, type NodeKind as N, NODE_KINDS as O, type ScrollProgressHandle as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, TONES as U, computeEventBindingKey as V, computeFormulaKey as W, computeInputSignalKey as X, computeScrollTriggerKey as Y, computed as Z, createInteractiveSignalsAccessor as _, type CdlInput as a, createScrollProgressSignals as a0, hasInteractivePrimitives as a1, sequenceBoardMetrics as a2, sequenceStepId as a3, signal as a4, type CdlLane as b, type CdlNode as c, type Side as d, type EdgeRole as e, type EdgeHead as f, type EdgeHeadFill as g, type CdlState as h, type CdlEventTarget as i, type Computed as j, type CdlDerivedValue as k, type CdlScrollTrigger as l, type CdlDiagramViewProps as m, type LaidEdge as n, type LaidNode as o, type CdlEdge as p, CdlDiagramView as q, type CdlEventBinding as r, type CdlEventKind as s, type CdlFormula as t, type CdlPhase as u, type ChartPieForm as v, type ChartSlopeForm as w, EDGE_HEADS as x, EDGE_HEAD_DEFAULT as y, EDGE_HEAD_FILLS as z };
2943
+ export { computeScrollTriggerKey as $, EDGE_HEAD_FILL_DEFAULT as A, type BBox as B, type CdlDiagram as C, EDGE_REVEALS as D, type EdgeStyle as E, EDGE_REVEAL_DEFAULT as F, EDGE_STYLES as G, type EdgeReveal as H, type InteractiveHandlerMap as I, type JourneyEmotion as J, type InteractiveSignalsAccessor as K, type LaidDiagram as L, type LaidLane as M, type NodeKind as N, NODE_KINDS as O, RELATION_FOCUSES as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, RELATION_FOCUS_DEFAULT as U, type RelationFocus as V, type ScrollProgressHandle as W, TONES as X, computeEventBindingKey as Y, computeFormulaKey as Z, computeInputSignalKey as _, type CdlInput as a, computed as a0, createInteractiveSignalsAccessor as a1, createScrollProgressHandle as a2, createScrollProgressSignals as a3, hasInteractivePrimitives as a4, sequenceBoardMetrics as a5, sequenceStepId as a6, signal as a7, type CdlLane as b, type CdlNode as c, type Side as d, type EdgeRole as e, type EdgeHead as f, type EdgeHeadFill as g, type CdlState as h, type CdlEventTarget as i, type Computed as j, type CdlDerivedValue as k, type CdlScrollTrigger as l, type CdlDiagramViewProps as m, type LaidEdge as n, type LaidNode as o, type CdlEdge as p, CdlDiagramView as q, type CdlEventBinding as r, type CdlEventKind as s, type CdlFormula as t, type CdlPhase as u, type ChartPieForm as v, type ChartSlopeForm as w, EDGE_HEADS as x, EDGE_HEAD_DEFAULT as y, EDGE_HEAD_FILLS as z };
@@ -168,6 +168,18 @@ declare const EDGE_HEAD_FILLS: readonly EdgeHeadFill[];
168
168
  type EdgeReveal = "phase" | "all";
169
169
  /** 書ける値の一覧 (#582)。 利用側はここから導く = 手で並べると増えた時に取り残される */
170
170
  declare const EDGE_REVEALS: readonly EdgeReveal[];
171
+ /**
172
+ * 触れた箱の関係だけを光らせるか (#806)。
173
+ *
174
+ * - `"off"` ... 何もしない。 **既定**
175
+ * - `"hover"` ... 箱に触れると その箱 ・ 繋がる線 ・ 相手の箱だけが光り、他が沈む。
176
+ * 押すと留まり、もう一度押すか `Esc` で外れる
177
+ */
178
+ type RelationFocus = "off" | "hover";
179
+ /** 書ける値の一覧 (#806)。 利用側はここから導く */
180
+ declare const RELATION_FOCUSES: readonly RelationFocus[];
181
+ /** 書かなかった時の値 (#806)。 既存の図の見え方を変えないため `"off"` */
182
+ declare const RELATION_FOCUS_DEFAULT: RelationFocus;
171
183
  /** 書かなかった時の値 (#582) */
172
184
  declare const EDGE_REVEAL_DEFAULT: EdgeReveal;
173
185
  /**
@@ -2533,6 +2545,16 @@ type CdlDiagram = {
2533
2545
  * `"all"` は「段で光らせはするが、線そのものは最初から見せたい」 図のための逃げ道。
2534
2546
  */
2535
2547
  edgeReveal?: EdgeReveal;
2548
+ /**
2549
+ * 触れた箱の関係だけを光らせるか (既定 = `"off"`、 #806)。
2550
+ *
2551
+ * クラス図と ER 図は順番を持たない = 段を進めて線を 1 本ずつ引く見せ方は、作り手が決めた
2552
+ * 読ませ方でしかなく、読み手の問い (この箱はどこと繋がっているか) に答えない。
2553
+ * `"hover"` を書くと、箱に触れた時だけ その箱 ・ 繋がる線 ・ 相手の箱が光る。
2554
+ *
2555
+ * **触れていない間は段が勝つ** = この指定を足しても、段を持つ図の見え方は変わらない。
2556
+ */
2557
+ relationFocus?: RelationFocus;
2536
2558
  /**
2537
2559
  * interactive input widgets (v0.5+、 CAR #243)。
2538
2560
  * builder chain の `.input.slider() / .number() / .dropdown() / .toggle()` で追加。
@@ -2749,6 +2771,8 @@ type LaidDiagram = {
2749
2771
  derived?: CdlDerivedValue[];
2750
2772
  /** 矢印をいつ出すか (`CdlDiagram.edgeReveal` をそのまま引き継ぐ、 #582) */
2751
2773
  edgeReveal?: EdgeReveal;
2774
+ /** 触れた箱の関係を光らせるか (`CdlDiagram.relationFocus` をそのまま引き継ぐ、 #806) */
2775
+ relationFocus?: RelationFocus;
2752
2776
  /**
2753
2777
  * キャンバス全要素の bounding box list。
2754
2778
  * engine が「ここに何があるか」 を全部把握、 衝突検証 / debug 表示 / validation で使う。
@@ -2916,4 +2940,4 @@ type CdlDiagramViewProps = {
2916
2940
  };
2917
2941
  declare function CdlDiagramView({ diagram, laid: laidProp, hideHeader, hideMiniPhaseIndicator, focusPhaseId, debug, emitGeometryWarn, headingLevel, interactiveHandlers }: CdlDiagramViewProps): JSX.Element;
2918
2942
 
2919
- export { createScrollProgressHandle as $, EDGE_HEAD_FILL_DEFAULT as A, type BBox as B, type CdlDiagram as C, EDGE_REVEALS as D, type EdgeStyle as E, EDGE_REVEAL_DEFAULT as F, EDGE_STYLES as G, type EdgeReveal as H, type InteractiveHandlerMap as I, type JourneyEmotion as J, type InteractiveSignalsAccessor as K, type LaidDiagram as L, type LaidLane as M, type NodeKind as N, NODE_KINDS as O, type ScrollProgressHandle as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, TONES as U, computeEventBindingKey as V, computeFormulaKey as W, computeInputSignalKey as X, computeScrollTriggerKey as Y, computed as Z, createInteractiveSignalsAccessor as _, type CdlInput as a, createScrollProgressSignals as a0, hasInteractivePrimitives as a1, sequenceBoardMetrics as a2, sequenceStepId as a3, signal as a4, type CdlLane as b, type CdlNode as c, type Side as d, type EdgeRole as e, type EdgeHead as f, type EdgeHeadFill as g, type CdlState as h, type CdlEventTarget as i, type Computed as j, type CdlDerivedValue as k, type CdlScrollTrigger as l, type CdlDiagramViewProps as m, type LaidEdge as n, type LaidNode as o, type CdlEdge as p, CdlDiagramView as q, type CdlEventBinding as r, type CdlEventKind as s, type CdlFormula as t, type CdlPhase as u, type ChartPieForm as v, type ChartSlopeForm as w, EDGE_HEADS as x, EDGE_HEAD_DEFAULT as y, EDGE_HEAD_FILLS as z };
2943
+ export { computeScrollTriggerKey as $, EDGE_HEAD_FILL_DEFAULT as A, type BBox as B, type CdlDiagram as C, EDGE_REVEALS as D, type EdgeStyle as E, EDGE_REVEAL_DEFAULT as F, EDGE_STYLES as G, type EdgeReveal as H, type InteractiveHandlerMap as I, type JourneyEmotion as J, type InteractiveSignalsAccessor as K, type LaidDiagram as L, type LaidLane as M, type NodeKind as N, NODE_KINDS as O, RELATION_FOCUSES as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, RELATION_FOCUS_DEFAULT as U, type RelationFocus as V, type ScrollProgressHandle as W, TONES as X, computeEventBindingKey as Y, computeFormulaKey as Z, computeInputSignalKey as _, type CdlInput as a, computed as a0, createInteractiveSignalsAccessor as a1, createScrollProgressHandle as a2, createScrollProgressSignals as a3, hasInteractivePrimitives as a4, sequenceBoardMetrics as a5, sequenceStepId as a6, signal as a7, type CdlLane as b, type CdlNode as c, type Side as d, type EdgeRole as e, type EdgeHead as f, type EdgeHeadFill as g, type CdlState as h, type CdlEventTarget as i, type Computed as j, type CdlDerivedValue as k, type CdlScrollTrigger as l, type CdlDiagramViewProps as m, type LaidEdge as n, type LaidNode as o, type CdlEdge as p, CdlDiagramView as q, type CdlEventBinding as r, type CdlEventKind as s, type CdlFormula as t, type CdlPhase as u, type ChartPieForm as v, type ChartSlopeForm as w, EDGE_HEADS as x, EDGE_HEAD_DEFAULT as y, EDGE_HEAD_FILLS as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardenelabs/cdl",
3
- "version": "0.46.0",
3
+ "version": "0.47.0",
4
4
  "description": "CDL (Chainome Diagram Language). Mermaid-like declarative DSL that compiles to animated SVG diagrams. Built for blockchain / Solidity flows, generic enough for sequence / flow / state / ER / topology diagrams.",
5
5
  "license": "MIT",
6
6
  "author": "cardene777",
package/src/index.ts CHANGED
@@ -293,6 +293,7 @@ export type {
293
293
  EdgeHead,
294
294
  EdgeHeadFill,
295
295
  EdgeReveal,
296
+ RelationFocus,
296
297
  RowMark,
297
298
  EdgeStyle,
298
299
  LaidDiagram,
@@ -307,6 +308,9 @@ export type {
307
308
  // 手書きせずに済ませるため公開する。
308
309
  // 値としての tone / kind 一覧。 downstream (dragon の記法一覧 / parser 等) が
309
310
  // 受理できる値の集合を手書きせずに済ませるため公開する。
311
+ export { relatedFocusSet } from "./render/relation-focus";
312
+ export type { 関係を持つ線 } from "./render/relation-focus";
313
+
310
314
  export {
311
315
  TONES,
312
316
  NODE_KINDS,
@@ -317,6 +321,8 @@ export {
317
321
  EDGE_REVEALS,
318
322
  EDGE_REVEAL_DEFAULT,
319
323
  EDGE_STYLES,
324
+ RELATION_FOCUSES,
325
+ RELATION_FOCUS_DEFAULT,
320
326
  } from "./types";
321
327
 
322
328
  /**
package/src/layout.ts CHANGED
@@ -415,6 +415,7 @@ export function layout(diag: CdlDiagram): LaidDiagram {
415
415
  ...(diag.derived ? { derived: diag.derived } : {}),
416
416
  // 矢印をいつ出すか (#582)。 描き手が読む欄なので、そのまま持ち越す
417
417
  ...(diag.edgeReveal ? { edgeReveal: diag.edgeReveal } : {}),
418
+ ...(diag.relationFocus ? { relationFocus: diag.relationFocus } : {}),
418
419
  // どの配色で描くか (#1553)。 空文字は書かなかった扱いにする = 空の目印を出すと、
419
420
  // 消費側の `[data-cdl-palette]` が配色を書いていない図にも当たる。
420
421
  //
@@ -47,6 +47,7 @@ export function CdlEdgeView({
47
47
  drawing,
48
48
  progress,
49
49
  stateValues,
50
+ flowing = false,
50
51
  }: {
51
52
  edge: LaidEdge;
52
53
  active: boolean;
@@ -59,6 +60,13 @@ export function CdlEdgeView({
59
60
  drawing: boolean;
60
61
  progress: number;
61
62
  stateValues?: Record<string, string>;
63
+ /**
64
+ * 焦点が当たって流れているか (#806)。 触れた箱に繋がる線だけが `true` になる。
65
+ *
66
+ * `active` と分ける = 段で光る線は伸びる動きを既に持っており、そこへ粒を足すと
67
+ * 2 つの動きが同時に走って線がどちらに進んでいるか読めなくなる。
68
+ */
69
+ flowing?: boolean;
62
70
  }): JSX.Element {
63
71
  const color = edgeColor(edge);
64
72
  // 光っていない edge も tone の色で描く。 以前は一律の灰色に落としていたため、 何も光っていない
@@ -101,6 +109,7 @@ export function CdlEdgeView({
101
109
  data-cdl-from={edge.from}
102
110
  data-cdl-to={edge.to}
103
111
  data-cdl-active={active ? "true" : "false"}
112
+ data-cdl-flowing={flowing ? "true" : "false"}
104
113
  data-cdl-drawing={drawing ? "true" : "false"}
105
114
  data-cdl-path-d={edge.d}
106
115
  data-cdl-label-x={edge.labelX}
@@ -151,6 +160,16 @@ export function CdlEdgeView({
151
160
  strokeLinecap="round"
152
161
  />
153
162
  )}
163
+ {/*
164
+ 焦点が当たった線を流す (#806)。 点線は元から粒を持つので足さない =
165
+ 2 組の粒が別々の速さで走ると、線が 2 本あるように見える。
166
+ */}
167
+ {flowing && !isDotted && (
168
+ <g data-cdl-role="edge-flow">
169
+ <path id={glowPathId} d={edge.d} fill="none" stroke="none" />
170
+ <EdgeGlowCircles color={color} edgeId={edge.id} />
171
+ </g>
172
+ )}
154
173
  </g>
155
174
  );
156
175
  }
@@ -1,4 +1,4 @@
1
- import type { JSX } from "react";
1
+ import type { JSX, KeyboardEvent as ReactKeyboardEvent } from "react";
2
2
  import { ActorNode } from "../kinds/actor";
3
3
  import { CardNode } from "../kinds/card";
4
4
  import { SequenceBoardNode } from "../kinds/sequence-board";
@@ -89,6 +89,23 @@ import {
89
89
  import type { LaidNode } from "../types";
90
90
  import { hasUnresolvedRef, interpolate, resolveNumericAttr } from "./utils";
91
91
 
92
+ /**
93
+ * 触れた箱の関係を光らせる時に、箱へ付ける受け口 (#806)。
94
+ *
95
+ * 指定を書かない図では `undefined` が渡る = `<g>` に属性も handler も足さないので、
96
+ * DOM も tab の順番も変わらない。
97
+ */
98
+ export interface 関係の受け口 {
99
+ /** 触れた時 */
100
+ readonly 触れた: (id: string) => void;
101
+ /** 離れた時 */
102
+ readonly 離れた: () => void;
103
+ /** 押した時 (留める / 外す) */
104
+ readonly 押した: (id: string) => void;
105
+ /** いま焦点が当たっている箱。 誰も当たっていなければ `null` */
106
+ readonly 焦点: string | null;
107
+ }
108
+
92
109
  export function CdlNodeView({
93
110
  node,
94
111
  active,
@@ -97,6 +114,7 @@ export function CdlNodeView({
97
114
  stateValues,
98
115
  currentPhaseId,
99
116
  chartLineScale,
117
+ 関係の受け口,
100
118
  }: {
101
119
  node: LaidNode;
102
120
  active: boolean;
@@ -109,6 +127,8 @@ export function CdlNodeView({
109
127
  stateValues: Record<string, string>;
110
128
  currentPhaseId: string | undefined;
111
129
  chartLineScale?: ChartLineScale;
130
+ /** 触れた箱の関係を光らせる時の受け口 (#806)。 書かない図では `undefined` */
131
+ 関係の受け口?: 関係の受け口;
112
132
  }): JSX.Element {
113
133
  const value = node.value ? interpolate(node.value, stateValues) : undefined;
114
134
  const rows = (node.rows ?? []).map((r) => interpolate(r, stateValues));
@@ -445,6 +465,25 @@ export function CdlNodeView({
445
465
  data-cdl-eyebrow={resolvedNode.eyebrow ?? ""}
446
466
  data-cdl-offset-x={offsetX || undefined}
447
467
  data-cdl-offset-y={offsetY || undefined}
468
+ {...(関係の受け口 === undefined
469
+ ? {}
470
+ : {
471
+ // 読み上げに「押せる」 ことを伝え、 tab でも辿れるようにする。
472
+ role: "button" as const,
473
+ tabIndex: 0,
474
+ "aria-pressed": 関係の受け口.焦点 === node.id,
475
+ style: { cursor: "pointer" as const },
476
+ onPointerEnter: () => 関係の受け口.触れた(node.id),
477
+ onPointerLeave: () => 関係の受け口.離れた(),
478
+ onFocus: () => 関係の受け口.触れた(node.id),
479
+ onBlur: () => 関係の受け口.離れた(),
480
+ onClick: () => 関係の受け口.押した(node.id),
481
+ onKeyDown: (e: ReactKeyboardEvent<SVGGElement>) => {
482
+ if (e.key !== "Enter" && e.key !== " ") return;
483
+ e.preventDefault();
484
+ 関係の受け口.押した(node.id);
485
+ },
486
+ })}
448
487
  transform={hasOffset ? `translate(${offsetX}, ${offsetY})` : undefined}
449
488
  opacity={boundOpacity}
450
489
  data-cdl-tone={node.tone}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * 触れた箱から「光らせる集合」 を作る (#806)。
3
+ *
4
+ * クラス図と ER 図は順番を持たない図で、段を追う見せ方は読み手の問い
5
+ * (この箱はどこと繋がっているか) に答えない。 触れた箱の関係だけを光らせれば、
6
+ * 問いにその場で答えられる。
7
+ *
8
+ * 集合は 3 つで作る = 触れた箱 / その箱に繋がる線 / 線の相手の箱。
9
+ * **線の向きは問わない** = クラス図の「継ぐ」 は子から親へ、 ER の「持つ」 は親から子へ向くが、
10
+ * 読み手にとってはどちらも「この箱の関係」 で、向きで見え方が変わる理由が無い。
11
+ */
12
+
13
+ /** 集合を作るのに要る線の情報だけを取る */
14
+ export interface 関係を持つ線 {
15
+ readonly id: string;
16
+ readonly from: string;
17
+ readonly to: string;
18
+ }
19
+
20
+ /**
21
+ * 触れた箱に繋がるものを集める。
22
+ *
23
+ * @param edges 図の全ての線
24
+ * @param nodeId 触れた箱の id
25
+ * @returns 光らせる id の集合 (触れた箱を必ず含む)
26
+ */
27
+ export function relatedFocusSet(
28
+ edges: readonly 関係を持つ線[],
29
+ nodeId: string,
30
+ ): Set<string> {
31
+ // 線を 1 本も持たない箱でも自分は光る = 「関係が無い」 ことが読み取れる形にする。
32
+ const 集合 = new Set<string>([nodeId]);
33
+ for (const e of edges) {
34
+ if (e.from !== nodeId && e.to !== nodeId) continue;
35
+ 集合.add(e.id);
36
+ 集合.add(e.from);
37
+ 集合.add(e.to);
38
+ }
39
+ return 集合;
40
+ }
@@ -1,6 +1,7 @@
1
- import { useMemo, type JSX } from "react";
1
+ import { useCallback, useEffect, useMemo, useState, type JSX } from "react";
2
2
  import { 折れ線の目盛, type ChartLineScale } from "../kinds/chart-line";
3
3
  import { 描く進みに直す } from "../kinds/draw-ratio";
4
+ import { useReducedMotion } from "../anim/react/useReducedMotion";
4
5
  import { lifelineStartYs, uniformLifelineEndY } from "../layout/lifeline";
5
6
  import { buildDiagramAltText } from "../layout/spec";
6
7
  import {
@@ -8,6 +9,7 @@ import {
8
9
  EDGE_HEAD_FILLS,
9
10
  EDGE_HEAD_FILL_DEFAULT,
10
11
  EDGE_REVEAL_DEFAULT,
12
+ RELATION_FOCUS_DEFAULT,
11
13
  type CdlPhase,
12
14
  type LaidDiagram,
13
15
  } from "../types";
@@ -21,6 +23,7 @@ import {
21
23
  import { CdlEdgeView, CdlEdgeGlowView, CdlEdgeLabelView } from "./edges";
22
24
  import { CdlNodeView } from "./nodes";
23
25
  import { resolveChartData } from "./payload-binding";
26
+ import { relatedFocusSet } from "./relation-focus";
24
27
  import { TONE, TONE_HEX } from "./tone";
25
28
  import { computeStateValues } from "./utils";
26
29
 
@@ -50,7 +53,55 @@ export function CdlStage({
50
53
  /** interactive integration (CAR #246) 用 SVG ref、 caller が event handler attach に使う */
51
54
  svgRef?: React.RefObject<SVGSVGElement | null>;
52
55
  }): JSX.Element {
53
- const activeSet = new Set(currentPhase?.activate ?? []);
56
+ /**
57
+ * 触れている / 留めている箱 (#806)。
58
+ *
59
+ * `留めた` を別に持つ = 線を目で追う間に指が外れると読めないため、押した箱は離しても
60
+ * 光ったままにする。 もう一度押すか `Esc` で外れる。
61
+ */
62
+ const [焦点, set焦点] = useState<{ id: string; 留めた: boolean } | null>(null);
63
+ const 関係を光らせる = (laid.relationFocus ?? RELATION_FOCUS_DEFAULT) === "hover";
64
+ const 動きを控える = useReducedMotion();
65
+
66
+ /*
67
+ * 指定を外した図では状態を捨てる。 残すと、指定を切り替える呼出側で前の焦点が光り続ける。
68
+ */
69
+ useEffect(() => {
70
+ if (!関係を光らせる) set焦点(null);
71
+ }, [関係を光らせる]);
72
+
73
+ /* 留めている間だけ `Esc` を見る。 常に見ると、同じ画面の別の図の `Esc` まで拾う。 */
74
+ useEffect(() => {
75
+ if (!関係を光らせる || 焦点?.留めた !== true) return;
76
+ const 押した = (e: KeyboardEvent): void => {
77
+ if (e.key === "Escape") set焦点(null);
78
+ };
79
+ window.addEventListener("keydown", 押した);
80
+ return () => window.removeEventListener("keydown", 押した);
81
+ }, [関係を光らせる, 焦点?.留めた]);
82
+
83
+ const 触れた = useCallback((id: string) => {
84
+ set焦点((前) => (前?.留めた === true ? 前 : { id, 留めた: false }));
85
+ }, []);
86
+ const 離れた = useCallback(() => {
87
+ set焦点((前) => (前?.留めた === true ? 前 : null));
88
+ }, []);
89
+ const 押した = useCallback((id: string) => {
90
+ set焦点((前) => (前?.id === id && 前.留めた ? null : { id, 留めた: true }));
91
+ }, []);
92
+
93
+ /**
94
+ * 光らせる集合 (#806)。 触れていない間は `null` で、その時は段の集合が勝つ。
95
+ */
96
+ const 焦点の集合 = useMemo(
97
+ () => (関係を光らせる && 焦点 !== null ? relatedFocusSet(laid.edges, 焦点.id) : null),
98
+ [関係を光らせる, 焦点, laid.edges],
99
+ );
100
+ const activeSet = 焦点の集合 ?? new Set(currentPhase?.activate ?? []);
101
+ /** 箱に付ける受け口。 指定を書かない図では `undefined` = DOM も挙動も変わらない */
102
+ const 箱の受け口 = 関係を光らせる
103
+ ? { 触れた, 離れた, 押した, 焦点: 焦点?.id ?? null }
104
+ : undefined;
54
105
  /*
55
106
  * まだ出番の来ていない矢印は描かない (#582)。
56
107
  *
@@ -358,6 +409,7 @@ export function CdlStage({
358
409
  key={edge.id}
359
410
  edge={edge}
360
411
  active={activeSet.has(edge.id)}
412
+ flowing={焦点の集合?.has(edge.id) === true && !動きを控える}
361
413
  drawing={矢印の出番.伸ばすか(edge.id)}
362
414
  progress={矢印の出番.伸ばすか(edge.id) ? 描く進み : progress}
363
415
  stateValues={stateValues}
@@ -374,6 +426,7 @@ export function CdlStage({
374
426
  <CdlNodeView
375
427
  node={node}
376
428
  active={activeSet.has(node.id)}
429
+ 関係の受け口={箱の受け口}
377
430
  drawing={描く}
378
431
  progress={描く ? 描く進み : progress}
379
432
  stateValues={stateValues}
package/src/types.ts CHANGED
@@ -223,6 +223,21 @@ export type EdgeReveal = "phase" | "all";
223
223
  /** 書ける値の一覧 (#582)。 利用側はここから導く = 手で並べると増えた時に取り残される */
224
224
  export const EDGE_REVEALS: readonly EdgeReveal[] = ["phase", "all"];
225
225
 
226
+ /**
227
+ * 触れた箱の関係だけを光らせるか (#806)。
228
+ *
229
+ * - `"off"` ... 何もしない。 **既定**
230
+ * - `"hover"` ... 箱に触れると その箱 ・ 繋がる線 ・ 相手の箱だけが光り、他が沈む。
231
+ * 押すと留まり、もう一度押すか `Esc` で外れる
232
+ */
233
+ export type RelationFocus = "off" | "hover";
234
+
235
+ /** 書ける値の一覧 (#806)。 利用側はここから導く */
236
+ export const RELATION_FOCUSES: readonly RelationFocus[] = ["off", "hover"];
237
+
238
+ /** 書かなかった時の値 (#806)。 既存の図の見え方を変えないため `"off"` */
239
+ export const RELATION_FOCUS_DEFAULT: RelationFocus = "off";
240
+
226
241
  /** 書かなかった時の値 (#582) */
227
242
  export const EDGE_REVEAL_DEFAULT: EdgeReveal = "phase";
228
243
 
@@ -2611,6 +2626,16 @@ export type CdlDiagram = {
2611
2626
  * `"all"` は「段で光らせはするが、線そのものは最初から見せたい」 図のための逃げ道。
2612
2627
  */
2613
2628
  edgeReveal?: EdgeReveal;
2629
+ /**
2630
+ * 触れた箱の関係だけを光らせるか (既定 = `"off"`、 #806)。
2631
+ *
2632
+ * クラス図と ER 図は順番を持たない = 段を進めて線を 1 本ずつ引く見せ方は、作り手が決めた
2633
+ * 読ませ方でしかなく、読み手の問い (この箱はどこと繋がっているか) に答えない。
2634
+ * `"hover"` を書くと、箱に触れた時だけ その箱 ・ 繋がる線 ・ 相手の箱が光る。
2635
+ *
2636
+ * **触れていない間は段が勝つ** = この指定を足しても、段を持つ図の見え方は変わらない。
2637
+ */
2638
+ relationFocus?: RelationFocus;
2614
2639
  /**
2615
2640
  * interactive input widgets (v0.5+、 CAR #243)。
2616
2641
  * builder chain の `.input.slider() / .number() / .dropdown() / .toggle()` で追加。
@@ -2829,6 +2854,8 @@ export type LaidDiagram = {
2829
2854
  derived?: CdlDerivedValue[];
2830
2855
  /** 矢印をいつ出すか (`CdlDiagram.edgeReveal` をそのまま引き継ぐ、 #582) */
2831
2856
  edgeReveal?: EdgeReveal;
2857
+ /** 触れた箱の関係を光らせるか (`CdlDiagram.relationFocus` をそのまま引き継ぐ、 #806) */
2858
+ relationFocus?: RelationFocus;
2832
2859
  /**
2833
2860
  * キャンバス全要素の bounding box list。
2834
2861
  * engine が「ここに何があるか」 を全部把握、 衝突検証 / debug 表示 / validation で使う。