@cardenelabs/cdl 0.17.0 → 0.18.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.
@@ -162,6 +162,12 @@ type EdgeHeadFill = "solid" | "hollow";
162
162
  declare const EDGE_HEAD_FILL_DEFAULT: EdgeHeadFill;
163
163
  /** 塗り方の全種。 marker の定義と検査の走査がこの並びを引く */
164
164
  declare const EDGE_HEAD_FILLS: readonly EdgeHeadFill[];
165
+ /** 矢印をいつ出すか (#582)。 `CdlDiagram.edgeReveal` が取る値と 1 対 1 */
166
+ type EdgeReveal = "phase" | "all";
167
+ /** 書ける値の一覧 (#582)。 利用側はここから導く = 手で並べると増えた時に取り残される */
168
+ declare const EDGE_REVEALS: readonly EdgeReveal[];
169
+ /** 書かなかった時の値 (#582) */
170
+ declare const EDGE_REVEAL_DEFAULT: EdgeReveal;
165
171
  /**
166
172
  * 行き先と出どころの **両端に印を置ける** (#578)。
167
173
  *
@@ -2409,6 +2415,22 @@ type CdlDiagram = {
2409
2415
  * 既定は `"extract"` = 付け忘れても検知が緩まない向き。
2410
2416
  */
2411
2417
  structuredData?: "extract" | "exclude";
2418
+ /**
2419
+ * 矢印をいつ出すか (既定 = `"phase"`、 #582)。
2420
+ *
2421
+ * - `"phase"` ... 段のどこかで光る矢印は、その段が来るまで描かない。 1 度出たら出したまま
2422
+ * - `"all"` ... 段に関わらず最初から全部描く
2423
+ *
2424
+ * 既定を `"phase"` にするのは、段を進めるごとに関係が 1 本ずつ増える図で、全ての矢印が
2425
+ * 1 段目から見えていたため (実測 = 段が矢印を引き直しても「増えた」 と読めず、無関係な線が
2426
+ * ちらついたようにしか見えない)。
2427
+ *
2428
+ * **段が矢印を 1 度も名指ししない図は `"phase"` でも隠さない**。 そういう図では矢印が骨格
2429
+ * そのもので、隠す理由が無い = 既定を変えても既存の図は動かない。
2430
+ *
2431
+ * `"all"` は「段で光らせはするが、線そのものは最初から見せたい」 図のための逃げ道。
2432
+ */
2433
+ edgeReveal?: EdgeReveal;
2412
2434
  /**
2413
2435
  * interactive input widgets (v0.5+、 CAR #243)。
2414
2436
  * builder chain の `.input.slider() / .number() / .dropdown() / .toggle()` で追加。
@@ -2619,6 +2641,8 @@ type LaidDiagram = {
2619
2641
  phases: CdlPhase[];
2620
2642
  /** 他の値から自動で決まる値 (`CdlDiagram.derived` をそのまま引き継ぐ) */
2621
2643
  derived?: CdlDerivedValue[];
2644
+ /** 矢印をいつ出すか (`CdlDiagram.edgeReveal` をそのまま引き継ぐ、 #582) */
2645
+ edgeReveal?: EdgeReveal;
2622
2646
  /**
2623
2647
  * キャンバス全要素の bounding box list。
2624
2648
  * engine が「ここに何があるか」 を全部把握、 衝突検証 / debug 表示 / validation で使う。
@@ -2786,4 +2810,4 @@ type CdlDiagramViewProps = {
2786
2810
  };
2787
2811
  declare function CdlDiagramView({ diagram, laid: laidProp, hideHeader, hideMiniPhaseIndicator, focusPhaseId, debug, emitGeometryWarn, headingLevel, interactiveHandlers }: CdlDiagramViewProps): JSX.Element;
2788
2812
 
2789
- export { type LaidLane as A, type BBox as B, type CdlDiagram as C, NODE_KINDS as D, type EdgeStyle as E, type ScrollProgressHandle as F, TONES as G, computeEventBindingKey as H, type InteractiveHandlerMap as I, type JourneyEmotion as J, computeFormulaKey as K, type LaidDiagram as L, computeInputSignalKey as M, type NodeKind as N, computeScrollTriggerKey as O, computed as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, createInteractiveSignalsAccessor as U, createScrollProgressHandle as V, createScrollProgressSignals as W, hasInteractivePrimitives as X, sequenceBoardMetrics as Y, sequenceStepId as Z, signal as _, type CdlInput as a, type CdlLane as b, type CdlNode as c, type Side as d, type EdgeHead as e, type EdgeHeadFill as f, type CdlState as g, type CdlEventTarget as h, type Computed as i, type CdlDerivedValue as j, type CdlScrollTrigger as k, type CdlDiagramViewProps as l, type LaidEdge as m, type LaidNode as n, type CdlEdge as o, CdlDiagramView as p, type CdlEventBinding as q, type CdlEventKind as r, type CdlFormula as s, type CdlPhase as t, EDGE_HEADS as u, EDGE_HEAD_DEFAULT as v, EDGE_HEAD_FILLS as w, EDGE_HEAD_FILL_DEFAULT as x, EDGE_STYLES as y, type InteractiveSignalsAccessor as z };
2813
+ export { sequenceBoardMetrics as $, EDGE_STYLES as A, type BBox as B, type CdlDiagram as C, type EdgeReveal as D, type EdgeStyle as E, type InteractiveSignalsAccessor as F, type LaidLane as G, NODE_KINDS as H, type InteractiveHandlerMap as I, type JourneyEmotion as J, type ScrollProgressHandle as K, type LaidDiagram as L, TONES as M, type NodeKind as N, computeEventBindingKey as O, computeFormulaKey as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, computeInputSignalKey as U, computeScrollTriggerKey as V, computed as W, createInteractiveSignalsAccessor as X, createScrollProgressHandle as Y, createScrollProgressSignals as Z, hasInteractivePrimitives as _, type CdlInput as a, sequenceStepId as a0, signal as a1, type CdlLane as b, type CdlNode as c, type Side as d, type EdgeHead as e, type EdgeHeadFill as f, type CdlState as g, type CdlEventTarget as h, type Computed as i, type CdlDerivedValue as j, type CdlScrollTrigger as k, type CdlDiagramViewProps as l, type LaidEdge as m, type LaidNode as n, type CdlEdge as o, CdlDiagramView as p, type CdlEventBinding as q, type CdlEventKind as r, type CdlFormula as s, type CdlPhase as t, EDGE_HEADS as u, EDGE_HEAD_DEFAULT as v, EDGE_HEAD_FILLS as w, EDGE_HEAD_FILL_DEFAULT as x, EDGE_REVEALS as y, EDGE_REVEAL_DEFAULT as z };
@@ -162,6 +162,12 @@ type EdgeHeadFill = "solid" | "hollow";
162
162
  declare const EDGE_HEAD_FILL_DEFAULT: EdgeHeadFill;
163
163
  /** 塗り方の全種。 marker の定義と検査の走査がこの並びを引く */
164
164
  declare const EDGE_HEAD_FILLS: readonly EdgeHeadFill[];
165
+ /** 矢印をいつ出すか (#582)。 `CdlDiagram.edgeReveal` が取る値と 1 対 1 */
166
+ type EdgeReveal = "phase" | "all";
167
+ /** 書ける値の一覧 (#582)。 利用側はここから導く = 手で並べると増えた時に取り残される */
168
+ declare const EDGE_REVEALS: readonly EdgeReveal[];
169
+ /** 書かなかった時の値 (#582) */
170
+ declare const EDGE_REVEAL_DEFAULT: EdgeReveal;
165
171
  /**
166
172
  * 行き先と出どころの **両端に印を置ける** (#578)。
167
173
  *
@@ -2409,6 +2415,22 @@ type CdlDiagram = {
2409
2415
  * 既定は `"extract"` = 付け忘れても検知が緩まない向き。
2410
2416
  */
2411
2417
  structuredData?: "extract" | "exclude";
2418
+ /**
2419
+ * 矢印をいつ出すか (既定 = `"phase"`、 #582)。
2420
+ *
2421
+ * - `"phase"` ... 段のどこかで光る矢印は、その段が来るまで描かない。 1 度出たら出したまま
2422
+ * - `"all"` ... 段に関わらず最初から全部描く
2423
+ *
2424
+ * 既定を `"phase"` にするのは、段を進めるごとに関係が 1 本ずつ増える図で、全ての矢印が
2425
+ * 1 段目から見えていたため (実測 = 段が矢印を引き直しても「増えた」 と読めず、無関係な線が
2426
+ * ちらついたようにしか見えない)。
2427
+ *
2428
+ * **段が矢印を 1 度も名指ししない図は `"phase"` でも隠さない**。 そういう図では矢印が骨格
2429
+ * そのもので、隠す理由が無い = 既定を変えても既存の図は動かない。
2430
+ *
2431
+ * `"all"` は「段で光らせはするが、線そのものは最初から見せたい」 図のための逃げ道。
2432
+ */
2433
+ edgeReveal?: EdgeReveal;
2412
2434
  /**
2413
2435
  * interactive input widgets (v0.5+、 CAR #243)。
2414
2436
  * builder chain の `.input.slider() / .number() / .dropdown() / .toggle()` で追加。
@@ -2619,6 +2641,8 @@ type LaidDiagram = {
2619
2641
  phases: CdlPhase[];
2620
2642
  /** 他の値から自動で決まる値 (`CdlDiagram.derived` をそのまま引き継ぐ) */
2621
2643
  derived?: CdlDerivedValue[];
2644
+ /** 矢印をいつ出すか (`CdlDiagram.edgeReveal` をそのまま引き継ぐ、 #582) */
2645
+ edgeReveal?: EdgeReveal;
2622
2646
  /**
2623
2647
  * キャンバス全要素の bounding box list。
2624
2648
  * engine が「ここに何があるか」 を全部把握、 衝突検証 / debug 表示 / validation で使う。
@@ -2786,4 +2810,4 @@ type CdlDiagramViewProps = {
2786
2810
  };
2787
2811
  declare function CdlDiagramView({ diagram, laid: laidProp, hideHeader, hideMiniPhaseIndicator, focusPhaseId, debug, emitGeometryWarn, headingLevel, interactiveHandlers }: CdlDiagramViewProps): JSX.Element;
2788
2812
 
2789
- export { type LaidLane as A, type BBox as B, type CdlDiagram as C, NODE_KINDS as D, type EdgeStyle as E, type ScrollProgressHandle as F, TONES as G, computeEventBindingKey as H, type InteractiveHandlerMap as I, type JourneyEmotion as J, computeFormulaKey as K, type LaidDiagram as L, computeInputSignalKey as M, type NodeKind as N, computeScrollTriggerKey as O, computed as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, createInteractiveSignalsAccessor as U, createScrollProgressHandle as V, createScrollProgressSignals as W, hasInteractivePrimitives as X, sequenceBoardMetrics as Y, sequenceStepId as Z, signal as _, type CdlInput as a, type CdlLane as b, type CdlNode as c, type Side as d, type EdgeHead as e, type EdgeHeadFill as f, type CdlState as g, type CdlEventTarget as h, type Computed as i, type CdlDerivedValue as j, type CdlScrollTrigger as k, type CdlDiagramViewProps as l, type LaidEdge as m, type LaidNode as n, type CdlEdge as o, CdlDiagramView as p, type CdlEventBinding as q, type CdlEventKind as r, type CdlFormula as s, type CdlPhase as t, EDGE_HEADS as u, EDGE_HEAD_DEFAULT as v, EDGE_HEAD_FILLS as w, EDGE_HEAD_FILL_DEFAULT as x, EDGE_STYLES as y, type InteractiveSignalsAccessor as z };
2813
+ export { sequenceBoardMetrics as $, EDGE_STYLES as A, type BBox as B, type CdlDiagram as C, type EdgeReveal as D, type EdgeStyle as E, type InteractiveSignalsAccessor as F, type LaidLane as G, NODE_KINDS as H, type InteractiveHandlerMap as I, type JourneyEmotion as J, type ScrollProgressHandle as K, type LaidDiagram as L, TONES as M, type NodeKind as N, computeEventBindingKey as O, computeFormulaKey as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, computeInputSignalKey as U, computeScrollTriggerKey as V, computed as W, createInteractiveSignalsAccessor as X, createScrollProgressHandle as Y, createScrollProgressSignals as Z, hasInteractivePrimitives as _, type CdlInput as a, sequenceStepId as a0, signal as a1, type CdlLane as b, type CdlNode as c, type Side as d, type EdgeHead as e, type EdgeHeadFill as f, type CdlState as g, type CdlEventTarget as h, type Computed as i, type CdlDerivedValue as j, type CdlScrollTrigger as k, type CdlDiagramViewProps as l, type LaidEdge as m, type LaidNode as n, type CdlEdge as o, CdlDiagramView as p, type CdlEventBinding as q, type CdlEventKind as r, type CdlFormula as s, type CdlPhase as t, EDGE_HEADS as u, EDGE_HEAD_DEFAULT as v, EDGE_HEAD_FILLS as w, EDGE_HEAD_FILL_DEFAULT as x, EDGE_REVEALS as y, EDGE_REVEAL_DEFAULT as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardenelabs/cdl",
3
- "version": "0.17.0",
3
+ "version": "0.18.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
@@ -284,6 +284,7 @@ export type {
284
284
  CdlState,
285
285
  EdgeHead,
286
286
  EdgeHeadFill,
287
+ EdgeReveal,
287
288
  RowMark,
288
289
  EdgeStyle,
289
290
  LaidDiagram,
@@ -305,6 +306,8 @@ export {
305
306
  EDGE_HEAD_DEFAULT,
306
307
  EDGE_HEAD_FILLS,
307
308
  EDGE_HEAD_FILL_DEFAULT,
309
+ EDGE_REVEALS,
310
+ EDGE_REVEAL_DEFAULT,
308
311
  EDGE_STYLES,
309
312
  } from "./types";
310
313
 
@@ -68,7 +68,15 @@ export function SequenceBoardNode({
68
68
  const v = Number.parseInt(interpolate(node.sequenceStep, stateValues ?? {}).trim(), 10);
69
69
  return Number.isFinite(v) ? v : null;
70
70
  })();
71
- const 朱 = "var(--cdl-tone-accent, #2d6a8f)";
71
+ /*
72
+ * いまの言づての色 (#580)。
73
+ *
74
+ * **意味の 6 色とは別に持つ**。 `--cdl-tone-accent` は「呼び」 のような図の中の意味を
75
+ * 描き分ける色で、利用側が系統色から取ることがある (実測 = dragon は青)。 ここが指すのは
76
+ * 意味ではなく「いまどこか」 で、役割は強調した箱の枠と同じ = 利用側の主役色に揃うべき。
77
+ * 書かれていなければ従来どおり意味の色へ落ちる。
78
+ */
79
+ const 朱 = "var(--cdl-now, var(--cdl-tone-accent, #2d6a8f))";
72
80
  /** 済んだ言づての色。 薄めるのではなく墨へ移すので、後からでも読める濃さを持つ */
73
81
  const 墨 = "var(--cdl-text-2, #4a5162)";
74
82
 
@@ -7,6 +7,7 @@ import {
7
7
  ROW_TOP_STORAGE,
8
8
  STORAGE_ROW_DIVIDER_Y,
9
9
  STORAGE_TITLE_BASELINE,
10
+ STORAGE_TITLE_FONT,
10
11
  SUBTITLE_ROW_SHIFT,
11
12
  rowBaselineY,
12
13
  rowGlyphDepth,
@@ -37,7 +38,7 @@ const ROW_GLYPH_CX = ROW_PAD_X_PX + ROW_GLYPH_HALF + 1;
37
38
  const ROW_GLYPH_LEAD = 40;
38
39
 
39
40
  /** 印を持つ行の型の級。 名前 (26) より 1 段落とし、色も薄くして名前を先に読ませる */
40
- const ROW_TYPE_FONT = 24;
41
+ const ROW_TYPE_FONT = 22;
41
42
 
42
43
  /** 印の中心を baseline からどれだけ上へ置くか。 級の 1/3 が字の見た目の中心 */
43
44
  const ROW_GLYPH_RISE = 8;
@@ -48,10 +49,10 @@ const ROW_GLYPH_RISE = 8;
48
49
  * 題 (78) と 1 行目 (130) の間。 印で分ける箱では区切り線を引かないので、その分の余白が空く。
49
50
  * ER の表名や状態の名前は識別子なので、その下に日本語の呼び名を薄く添える。
50
51
  */
51
- const SUBTITLE_BASELINE = 108;
52
+ const SUBTITLE_BASELINE = 118;
52
53
 
53
54
  /** 呼び名の級。 行の名前 (26) より小さく、型 (24) と同じ */
54
- const SUBTITLE_FONT = 22;
55
+ const SUBTITLE_FONT = 20;
55
56
 
56
57
  /** 主キーの下線を名前の baseline からどれだけ下に引くか */
57
58
  const UNDERLINE_DROP = 7;
@@ -98,7 +99,7 @@ export function StorageNode({
98
99
  const x = node.cx - node.w / 2;
99
100
  const y = node.cy - node.h / 2;
100
101
  // 小型の箱では名前を中央に置く。 固定の位置のままだと下端をまたぐ (#416)
101
- const 名前 = titlePlacement(node, { x: 26, y: STORAGE_TITLE_BASELINE, size: 22 }, { hasEyebrow: Boolean(node.eyebrow) });
102
+ const 名前 = titlePlacement(node, { x: 26, y: STORAGE_TITLE_BASELINE, size: STORAGE_TITLE_FONT }, { hasEyebrow: Boolean(node.eyebrow) });
102
103
  // 全 row を delimiter (`:` / ` = ` / ` → ` / ` -> `) で left / right に分割し、
103
104
  // left 列 max 文字数を算出 → 右列の固定 x 位置を決める (DB table / UML class box 風)。
104
105
  // 結果として全 row の右列開始 x が揃い、 区切り線がガタつかなくなる。
@@ -189,18 +189,19 @@ export function layoutNodes(diag: CdlDiagram, lanes: LaidLane[]): LaidNode[] {
189
189
  const 段の芯 = rowCy.get(n.stack)!;
190
190
  const 段の高さ = rowH.get(n.stack)!;
191
191
  /*
192
- * 行頭の印を持つ箱は **自分の高さで、段の上端に揃える** (#578)。
192
+ * 行頭の印を持つ箱は **自分の高さで、段の芯に揃える** (#578 → #580)。
193
193
  *
194
- * 既定は段の高さに合わせて伸ばし、芯で揃える = 同じ段の箱の上端も下端も揃う。 行が
195
- * 1 つしか無い箱が 4 行の箱と同じ高さになるので、下が空いたまま並ぶ。
194
+ * 既定は段の高さに合わせて伸ばす = 行が 1 つしか無い箱が 4 行の箱と同じ高さになり、
195
+ * 下が空いたまま並ぶ。 印を持つ箱は高さを中身から出しているので (`autoStorageHeight`)、
196
+ * 伸ばすとその値が捨てられる。
196
197
  *
197
- * 印を持つ箱では高さを中身から出しているので (`autoStorageHeight`)、伸ばすとその値が
198
- * 捨てられる。 上端で揃えるのは、読み手が箱の題を横に見比べる順に読むため = 下端は
199
- * 中身の量で決まってよい。
198
+ * **揃える先は芯**。 #578 では上端に揃えていたが、高さの違う箱が横に並ぶと
199
+ * 図全体が上へ寄って見えた (user 指摘)。 芯で揃えると上端も下端も箱ごとに違うが、
200
+ * 並びの重心が 1 本に通る。 見た目検査の `row-alignment` もこの前提で書かれている。
200
201
  */
201
202
  const 自前の高さ = n.rowMarks !== undefined;
202
203
  const h = n.h ?? (自前の高さ ? autoStorageHeight(n, size.h) : 段の高さ);
203
- const cy = 自前の高さ ? 段の芯 - 段の高さ / 2 + h / 2 : 段の芯;
204
+ const cy = 段の芯;
204
205
  // n.w 明示時は尊重、 未指定で rows[] を持つ node は内容長に合わせて auto 拡張。
205
206
  const w = autoRowsWidth(n, size.w);
206
207
  out.push({
@@ -242,8 +242,13 @@ export function genericTitleBaselineY(kind: string | undefined, h: number, hasRo
242
242
  : GENERIC_TITLE_TOP + genericTextShift(kind);
243
243
  }
244
244
 
245
- /** `kinds/storage.tsx` の 1 行目 baseline と行送り。 */
246
- export const ROW_TOP_STORAGE = 130;
245
+ /**
246
+ * `kinds/storage.tsx` 1 行目 baseline と行送り。
247
+ *
248
+ * 1 行目は #1468 で 130 から下げた。 題を 22 から 34 に上げたぶん、題の下に空きが要る
249
+ * (実測 = 130 のままだと題の descender と 1 行目の cap が 6 まで詰まる)。
250
+ */
251
+ export const ROW_TOP_STORAGE = 152;
247
252
  export const ROW_PITCH_STORAGE = 56;
248
253
 
249
254
  /** `kinds/generic.tsx` の 1 行目 baseline と行送り。 */
@@ -276,8 +281,21 @@ export const GLYPH_CAP_RATIO = 0.72;
276
281
  export const ROW_FONT_STORAGE = 26;
277
282
  export const ROW_FONT_GENERIC = 21;
278
283
 
279
- /** `kinds/storage.tsx` の名前 (title) の baseline。 */
280
- export const STORAGE_TITLE_BASELINE = 78;
284
+ /**
285
+ * `kinds/storage.tsx` の名前 (title) の baseline。
286
+ *
287
+ * #1468 で 78 から下げた。 題が 34 になり、上端から baseline までに cap 分の余白が要る。
288
+ */
289
+ export const STORAGE_TITLE_BASELINE = 86;
290
+
291
+ /**
292
+ * `kinds/storage.tsx` の名前 (title) の字の大きさ (#1468)。
293
+ *
294
+ * **行 (`ROW_FONT_STORAGE` = 26) より大きい**。 22 だった間は題の方が小さく、箱の中で
295
+ * 何が見出しか読み取れなかった (user 指摘)。 題 34 / 行 26 / 型 22 の 3 段にして、
296
+ * 上から順に読む順序を字の大きさで示す。
297
+ */
298
+ export const STORAGE_TITLE_FONT = 34;
281
299
 
282
300
  /**
283
301
  * 呼び名を出す箱で、行の群をどれだけ下げるか (#578)。
package/src/layout.ts CHANGED
@@ -371,6 +371,8 @@ export function layout(diag: CdlDiagram): LaidDiagram {
371
371
  states: diag.states,
372
372
  phases: diag.phases,
373
373
  ...(diag.derived ? { derived: diag.derived } : {}),
374
+ // 矢印をいつ出すか (#582)。 描き手が読む欄なので、そのまま持ち越す
375
+ ...(diag.edgeReveal ? { edgeReveal: diag.edgeReveal } : {}),
374
376
  bboxes,
375
377
  collisions,
376
378
  nearCollisions,
@@ -1,5 +1,12 @@
1
1
  import { useMemo, type JSX } from "react";
2
- import { EDGE_HEADS, EDGE_HEAD_FILLS, EDGE_HEAD_FILL_DEFAULT, type CdlPhase, type LaidDiagram } from "../types";
2
+ import {
3
+ EDGE_HEADS,
4
+ EDGE_HEAD_FILLS,
5
+ EDGE_HEAD_FILL_DEFAULT,
6
+ EDGE_REVEAL_DEFAULT,
7
+ type CdlPhase,
8
+ type LaidDiagram,
9
+ } from "../types";
3
10
  import { CdlEdgeView, CdlEdgeGlowView, CdlEdgeLabelView } from "./edges";
4
11
  import { CdlNodeView } from "./nodes";
5
12
  import { TONE, TONE_HEX } from "./tone";
@@ -27,6 +34,33 @@ export function CdlStage({
27
34
  svgRef?: React.RefObject<SVGSVGElement | null>;
28
35
  }): JSX.Element {
29
36
  const activeSet = new Set(currentPhase?.activate ?? []);
37
+ /*
38
+ * まだ出番の来ていない矢印は描かない (#582)。
39
+ *
40
+ * 段を進めるごとに関係が 1 本ずつ増える図で、全ての矢印が 1 段目から見えていた。 段が進むと
41
+ * その矢印だけが引き直されるが、既に見えているので「増えた」 ようには読めず、無関係な線が
42
+ * ちらついたようにしか見えない (user 実機確認)。
43
+ *
44
+ * **段のどこかで光る矢印だけを対象にする**。 段が矢印を 1 度も名指ししない図では、矢印は
45
+ * 図の骨格そのものなので隠す理由が無い。 1 度出たら以降は出したままにする = 消える向きの
46
+ * 動きは、読み手が「戻った」 と読む。
47
+ *
48
+ * 著者が `edgeReveal: "all"` と書いた図では隠さない。
49
+ */
50
+ const 出番の来た矢印 = useMemo(() => {
51
+ // 著者が「最初から全部見せる」 と書いた図では隠さない (#582)
52
+ if ((laid.edgeReveal ?? EDGE_REVEAL_DEFAULT) === "all") return () => true;
53
+ const 初出 = new Map<string, number>();
54
+ laid.phases.forEach((p, i) => {
55
+ for (const id of p.activate ?? []) if (!初出.has(id)) 初出.set(id, i);
56
+ });
57
+ const 今 = currentPhase === undefined ? -1 : laid.phases.findIndex((p) => p.id === currentPhase.id);
58
+ return (edgeId: string): boolean => {
59
+ const first = 初出.get(edgeId);
60
+ if (first === undefined) return true;
61
+ return 今 < 0 || 今 >= first;
62
+ };
63
+ }, [laid.phases, currentPhase]);
30
64
  // この phase で左の起点から描き始める node (cdl#512)。 `activate` とは別集合で持つ =
31
65
  // 焦点が当たり続ける図で毎 phase 引き直すことになるため、 光っているかを引き金にしない
32
66
  const drawSet = new Set(currentPhase?.draw ?? []);
@@ -231,7 +265,7 @@ export function CdlStage({
231
265
  ))}
232
266
 
233
267
  {/* edges (1st pass) */}
234
- {laid.edges.map((edge) => (
268
+ {laid.edges.filter((edge) => 出番の来た矢印(edge.id)).map((edge) => (
235
269
  <CdlEdgeView key={edge.id} edge={edge} active={activeSet.has(edge.id)} progress={progress} stateValues={stateValues} />
236
270
  ))}
237
271
 
@@ -256,13 +290,13 @@ export function CdlStage({
256
290
 
257
291
  {/* 経由 node 貫通する dotted-flow edge の進行点 glow (3rd pass) */}
258
292
  {laid.edges
259
- .filter((edge) => Boolean(edge.dThrough))
293
+ .filter((edge) => Boolean(edge.dThrough) && 出番の来た矢印(edge.id))
260
294
  .map((edge) => (
261
295
  <CdlEdgeGlowView key={`glow-${edge.id}`} edge={edge} active={activeSet.has(edge.id)} />
262
296
  ))}
263
297
 
264
298
  {/* edge labels (3rd pass) ... 全 edge の label を最前面に描画 */}
265
- {laid.edges.map((edge) => (
299
+ {laid.edges.filter((edge) => 出番の来た矢印(edge.id)).map((edge) => (
266
300
  <CdlEdgeLabelView
267
301
  key={`label-${edge.id}`}
268
302
  edge={edge}
package/src/types.ts CHANGED
@@ -213,6 +213,15 @@ export const EDGE_HEAD_FILL_DEFAULT: EdgeHeadFill = "solid";
213
213
  /** 塗り方の全種。 marker の定義と検査の走査がこの並びを引く */
214
214
  export const EDGE_HEAD_FILLS: readonly EdgeHeadFill[] = ["solid", "hollow"];
215
215
 
216
+ /** 矢印をいつ出すか (#582)。 `CdlDiagram.edgeReveal` が取る値と 1 対 1 */
217
+ export type EdgeReveal = "phase" | "all";
218
+
219
+ /** 書ける値の一覧 (#582)。 利用側はここから導く = 手で並べると増えた時に取り残される */
220
+ export const EDGE_REVEALS: readonly EdgeReveal[] = ["phase", "all"];
221
+
222
+ /** 書かなかった時の値 (#582) */
223
+ export const EDGE_REVEAL_DEFAULT: EdgeReveal = "phase";
224
+
216
225
  /**
217
226
  * 行き先と出どころの **両端に印を置ける** (#578)。
218
227
  *
@@ -2480,6 +2489,22 @@ export type CdlDiagram = {
2480
2489
  * 既定は `"extract"` = 付け忘れても検知が緩まない向き。
2481
2490
  */
2482
2491
  structuredData?: "extract" | "exclude";
2492
+ /**
2493
+ * 矢印をいつ出すか (既定 = `"phase"`、 #582)。
2494
+ *
2495
+ * - `"phase"` ... 段のどこかで光る矢印は、その段が来るまで描かない。 1 度出たら出したまま
2496
+ * - `"all"` ... 段に関わらず最初から全部描く
2497
+ *
2498
+ * 既定を `"phase"` にするのは、段を進めるごとに関係が 1 本ずつ増える図で、全ての矢印が
2499
+ * 1 段目から見えていたため (実測 = 段が矢印を引き直しても「増えた」 と読めず、無関係な線が
2500
+ * ちらついたようにしか見えない)。
2501
+ *
2502
+ * **段が矢印を 1 度も名指ししない図は `"phase"` でも隠さない**。 そういう図では矢印が骨格
2503
+ * そのもので、隠す理由が無い = 既定を変えても既存の図は動かない。
2504
+ *
2505
+ * `"all"` は「段で光らせはするが、線そのものは最初から見せたい」 図のための逃げ道。
2506
+ */
2507
+ edgeReveal?: EdgeReveal;
2483
2508
  /**
2484
2509
  * interactive input widgets (v0.5+、 CAR #243)。
2485
2510
  * builder chain の `.input.slider() / .number() / .dropdown() / .toggle()` で追加。
@@ -2692,6 +2717,8 @@ export type LaidDiagram = {
2692
2717
  phases: CdlPhase[];
2693
2718
  /** 他の値から自動で決まる値 (`CdlDiagram.derived` をそのまま引き継ぐ) */
2694
2719
  derived?: CdlDerivedValue[];
2720
+ /** 矢印をいつ出すか (`CdlDiagram.edgeReveal` をそのまま引き継ぐ、 #582) */
2721
+ edgeReveal?: EdgeReveal;
2695
2722
  /**
2696
2723
  * キャンバス全要素の bounding box list。
2697
2724
  * engine が「ここに何があるか」 を全部把握、 衝突検証 / debug 表示 / validation で使う。
@@ -125,6 +125,7 @@ export type { EdgeLabelLine, EdgeLabelTextSpec } from "./label-text";
125
125
  import {
126
126
  ARROW_ENDPOINT_CENTER_TOL,
127
127
  COLUMN_GAP_VARIANCE_TOL,
128
+ requiredNearClearance,
128
129
  DETOUR_CREST_OVERLAP_MIN,
129
130
  DETOUR_SLOT_GAP,
130
131
  EDGE_STUB_OUT,
@@ -2493,18 +2494,9 @@ function runAxes(
2493
2494
  for (const [stack, group] of byStack) {
2494
2495
  if (group.length < 2) continue;
2495
2496
  const cy0 = group[0]!.cy;
2496
- /*
2497
- * **上端で揃える形も認める** (#578 の設計)。
2498
- *
2499
- * 高さの違う箱を横に並べる図 (表 / クラス / 状態) では、中心ではなく上端を揃える。
2500
- * 中心だけを見ると、正しく上端で揃っている行が高さの差の半分だけずれて見える
2501
- * (実測 = 行数の違う表で 28 の差)。 どちらかで揃っていれば揃っているとみなす。
2502
- */
2503
- const top0 = cy0 - (group[0]!.h ?? 0) / 2;
2504
2497
  for (const n of group.slice(1)) {
2505
2498
  const diff = Math.abs(n.cy - cy0);
2506
- const topDiff = Math.abs(n.cy - (n.h ?? 0) / 2 - top0);
2507
- if (diff > MIN_ROW_ALIGNMENT_TOLERANCE && topDiff > MIN_ROW_ALIGNMENT_TOLERANCE) {
2499
+ if (diff > MIN_ROW_ALIGNMENT_TOLERANCE) {
2508
2500
  push(
2509
2501
  "row-alignment",
2510
2502
  `stack=${stack} row 内 node "${n.id}" cy=${n.cy.toFixed(1)} が row 基準 cy=${cy0.toFixed(1)} と ${diff.toFixed(1)} world 差 (許容 ${MIN_ROW_ALIGNMENT_TOLERANCE})`,
@@ -4239,24 +4231,37 @@ function runAxes(
4239
4231
  const sorted = [...group].sort((a, b) => a.cy - b.cy);
4240
4232
  const gaps: number[] = [];
4241
4233
  /*
4242
- * **格子に置く形では上端どうしの間隔で測る** (#578 の設計)。
4243
- *
4244
- * 段の位置を著者が決める図 (クラス / 表 / 状態) では、engine が制御しているのは
4245
- * 段の基準線であって端の間隔ではない。 箱の高さが段ごとに違うと端の間隔は必ずばらつく
4246
- * (実測 = クラス図の 2 列目で 132 と 300)。 どちらかが揃っていれば揃っているとみなす。
4234
+ * 端の間隔で測る理由は下の Issue #202 の節を参照。
4247
4235
  */
4248
- const tops: number[] = [];
4249
4236
  for (let i = 1; i < sorted.length; i++) {
4250
4237
  const prev = sorted[i - 1]!;
4251
4238
  const cur = sorted[i]!;
4252
4239
  gaps.push(cur.cy - cur.h / 2 - (prev.cy + prev.h / 2));
4253
- tops.push(cur.cy - cur.h / 2 - (prev.cy - prev.h / 2));
4254
4240
  }
4255
4241
  const maxGap = Math.max(...gaps);
4256
4242
  const minGap = Math.min(...gaps);
4257
4243
  const variance = maxGap - minGap;
4258
- const topVariance = Math.max(...tops) - Math.min(...tops);
4259
- if (variance > COLUMN_GAP_VARIANCE_TOL && topVariance > COLUMN_GAP_VARIANCE_TOL) {
4244
+ /*
4245
+ * **格子に置いた箱では間隔の均一を求めない** (#580)
4246
+ *
4247
+ * この軸は「engine が縦の間隔を制御している」 ことを前提に、そのばらつきを layout の
4248
+ * 破綻として見る。 行頭の印を持つ箱は前提が違う = 段の位置を著者が `col` / `row` で決め、
4249
+ * 高さは中身から出る (`autoStorageHeight`)。 段ごとに背の高さが違えば、端の間隔も芯の
4250
+ * 間隔も必ずばらつく (実測 = クラス図の 2 列目で端 132/216、芯 528/472)。
4251
+ *
4252
+ * **近づき過ぎだけは見る** (#202)。 間隔の均一を外しても、箱どうしが接する形は通さない
4253
+ * (#202 が実測した形 = 芯の間隔が揃い、端の間隔が 100 と 0)。
4254
+ */
4255
+ const 格子に置く = group.every((n) => n.rowMarks !== undefined);
4256
+ const 端が足りている = minGap >= requiredNearClearance("node", "node");
4257
+ if (格子に置く) {
4258
+ if (!端が足りている) {
4259
+ push(
4260
+ "column-gap-uniform",
4261
+ `lane "${laneId}" 内 node 間の端間 gap ${minGap.toFixed(0)} world が下限 ${requiredNearClearance("node", "node")} を割る`,
4262
+ );
4263
+ }
4264
+ } else if (variance > COLUMN_GAP_VARIANCE_TOL) {
4260
4265
  push(
4261
4266
  "column-gap-uniform",
4262
4267
  `lane "${laneId}" 内 node 間の端間 gap variance ${variance.toFixed(1)} world が spec ${COLUMN_GAP_VARIANCE_TOL} 超過 (min=${minGap.toFixed(0)} max=${maxGap.toFixed(0)})`,