@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 +58 -1
- package/dist/index.cjs +83 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -3
- package/dist/index.d.ts +29 -3
- package/dist/index.js +81 -7
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +79 -6
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +79 -6
- package/dist/react.js.map +1 -1
- package/dist/{render-UGswDfAh.d.cts → render-C53-PeJZ.d.cts} +25 -1
- package/dist/{render-UGswDfAh.d.ts → render-C53-PeJZ.d.ts} +25 -1
- package/package.json +1 -1
- package/src/index.ts +6 -0
- package/src/layout/edges.ts +27 -2
- package/src/layout.ts +1 -0
- package/src/render/edges.tsx +19 -0
- package/src/render/nodes.tsx +40 -1
- package/src/render/relation-focus.ts +40 -0
- package/src/render/stage.tsx +55 -2
- package/src/types.ts +27 -0
|
@@ -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 {
|
|
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 {
|
|
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.
|
|
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/edges.ts
CHANGED
|
@@ -32,6 +32,20 @@ import { DEFAULT_LABEL_MARGIN } from "./tokens";
|
|
|
32
32
|
const VERTICAL_DETOUR_GAP = 50;
|
|
33
33
|
/** 縦 detour の角の丸め半径 (world unit)。 */
|
|
34
34
|
const VERTICAL_DETOUR_CORNER = 14;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 縦に進む区間の**進む向き** (下へ = 1、 上へ = -1)。 角の丸めの位置を決める時に使う。
|
|
38
|
+
*
|
|
39
|
+
* 角は「手前 14 で直線を止め、弧で 14 先へ抜ける」 形で作る。 手前と先がどちら側かは
|
|
40
|
+
* 進む向きで決まるので、下向きに固定すると上向きの辺で直線が角を通り過ぎ、
|
|
41
|
+
* 弧が戻る形になって角の外に突起が出る (`#801`)。
|
|
42
|
+
*
|
|
43
|
+
* `from === to` (縦に動かない) は下向きに倒す。 その時は縦の区間の長さが 0 で、
|
|
44
|
+
* どちらへ倒しても弧は同じ位置に出る。
|
|
45
|
+
*/
|
|
46
|
+
export function verticalCornerDir(from: number, to: number): 1 | -1 {
|
|
47
|
+
return to < from ? -1 : 1;
|
|
48
|
+
}
|
|
35
49
|
/** 縦 detour が source から曲がるまで直進する距離 (world unit)。 EDGE_STUB_OUT を上回る。 */
|
|
36
50
|
const VERTICAL_DETOUR_EXIT = 45;
|
|
37
51
|
|
|
@@ -2197,12 +2211,23 @@ function routePath(
|
|
|
2197
2211
|
// CAR-518 SSOT ... dotted-flow は 62 world (marker glow radius 分拡張) を labelInit で参照。
|
|
2198
2212
|
// fan-in / fan-out の分散は chain 伝搬 shift が担当。
|
|
2199
2213
|
const labelY = midY - labelInit;
|
|
2214
|
+
// **角の丸めは進む向きから決める** (`#801`)。 手前で止める位置と抜けた先の位置は
|
|
2215
|
+
// 「角の手前 14」「角の先 14」 であって、上下のどちらかに固定はできない。
|
|
2216
|
+
//
|
|
2217
|
+
// 固定していた間、`fromSide === "top"` の辺 (相手が上にある = 上向きに出る辺) は
|
|
2218
|
+
// 直線が角を 14 通り過ぎてから弧で戻る形になり、角の外側に 14 の突起が出ていた
|
|
2219
|
+
// (見本帳 `class-complex-demo` の `結ぶ` で実測)。
|
|
2220
|
+
//
|
|
2221
|
+
// 同じ誤りは `CAR-482` が別の分岐 (`cornerDirA` / `cornerDirB`) で 1 度直している。
|
|
2222
|
+
// ここはその時に見落とされた 2 つ目の分岐。
|
|
2223
|
+
const 縦の向きA = verticalCornerDir(A.y, midY);
|
|
2224
|
+
const 縦の向きB = verticalCornerDir(midY, B.y);
|
|
2200
2225
|
return {
|
|
2201
2226
|
d: `M ${A.x} ${A.y}
|
|
2202
|
-
L ${A.x} ${midY - corner}
|
|
2227
|
+
L ${A.x} ${midY - 縦の向きA * corner}
|
|
2203
2228
|
Q ${A.x} ${midY}, ${A.x + xDir * corner} ${midY}
|
|
2204
2229
|
L ${B.x - xDir * corner} ${midY}
|
|
2205
|
-
Q ${B.x} ${midY}, ${B.x} ${midY + corner}
|
|
2230
|
+
Q ${B.x} ${midY}, ${B.x} ${midY + 縦の向きB * corner}
|
|
2206
2231
|
L ${B.x} ${B.y}`,
|
|
2207
2232
|
labelX: (A.x + B.x) / 2,
|
|
2208
2233
|
labelY,
|
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
|
//
|
package/src/render/edges.tsx
CHANGED
|
@@ -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
|
}
|
package/src/render/nodes.tsx
CHANGED
|
@@ -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
|
+
}
|
package/src/render/stage.tsx
CHANGED
|
@@ -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
|
-
|
|
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 で使う。
|