@cardenelabs/cdl 0.37.0 → 0.38.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardenelabs/cdl",
3
- "version": "0.37.0",
3
+ "version": "0.38.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",
@@ -60,6 +60,8 @@ export class Timeline {
60
60
  private rafHandle: number | null = null;
61
61
  private phaseIndex = 0;
62
62
  private phaseProgress = 0;
63
+ /** 曲線を通す前の進み (cdl#711)。 経過時間そのものの割合で、 描く動きが読む */
64
+ private phaseLinearProgress = 0;
63
65
  private phaseStartedAt = 0;
64
66
  private status: TimelineStatus = "idle";
65
67
  /**
@@ -75,7 +77,7 @@ export class Timeline {
75
77
  */
76
78
  private readonly initialState: {
77
79
  phaseIndex: number;
78
- phaseProgress: number;
80
+ phaseLinearProgress: number;
79
81
  status: TimelineStatus;
80
82
  };
81
83
 
@@ -94,12 +96,12 @@ export class Timeline {
94
96
  const initial = options.initialPhaseIndex;
95
97
  if (initial != null && initial > 0 && initial < phases.length) {
96
98
  this.phaseIndex = initial;
97
- this.phaseProgress = 1;
99
+ this.setProgress(1);
98
100
  this.status = initial === phases.length - 1 ? "settled" : "paused";
99
101
  }
100
102
  this.initialState = {
101
103
  phaseIndex: this.phaseIndex,
102
- phaseProgress: this.phaseProgress,
104
+ phaseLinearProgress: this.phaseLinearProgress,
103
105
  status: this.status,
104
106
  };
105
107
  }
@@ -133,6 +135,7 @@ export class Timeline {
133
135
  cached.status === this.status &&
134
136
  cached.phaseIndex === this.phaseIndex &&
135
137
  cached.phaseProgress === this.phaseProgress &&
138
+ cached.phaseLinearProgress === this.phaseLinearProgress &&
136
139
  cached.totalPhases === this.phases.length
137
140
  ) {
138
141
  return cached;
@@ -141,6 +144,7 @@ export class Timeline {
141
144
  status: this.status,
142
145
  phaseIndex: this.phaseIndex,
143
146
  phaseProgress: this.phaseProgress,
147
+ phaseLinearProgress: this.phaseLinearProgress,
144
148
  totalPhases: this.phases.length,
145
149
  };
146
150
  this.cachedSnapshot = next;
@@ -168,7 +172,7 @@ export class Timeline {
168
172
  const clamped = Math.max(0, Math.min(this.phases.length - 1, index));
169
173
  this.cancelRaf();
170
174
  this.phaseIndex = clamped;
171
- this.phaseProgress = 1;
175
+ this.setProgress(1);
172
176
  this.status = clamped === this.phases.length - 1 ? "settled" : "paused";
173
177
  this.emit();
174
178
  }
@@ -212,7 +216,7 @@ export class Timeline {
212
216
  if (this.phaseIndex <= 0) return;
213
217
  this.cancelRaf();
214
218
  this.phaseIndex -= 1;
215
- this.phaseProgress = 1;
219
+ this.setProgress(1);
216
220
  this.status = "paused";
217
221
  this.emit();
218
222
  }
@@ -220,7 +224,7 @@ export class Timeline {
220
224
  reset(): void {
221
225
  this.cancelRaf();
222
226
  this.phaseIndex = 0;
223
- this.phaseProgress = 0;
227
+ this.setProgress(0);
224
228
  this.status = "idle";
225
229
  this.emit();
226
230
  }
@@ -242,7 +246,7 @@ export class Timeline {
242
246
  this.subscribers.clear();
243
247
  this.status = this.initialState.status;
244
248
  this.phaseIndex = this.initialState.phaseIndex;
245
- this.phaseProgress = this.initialState.phaseProgress;
249
+ this.setProgress(this.initialState.phaseLinearProgress);
246
250
  }
247
251
 
248
252
  // ─── internals ───────────────────────────────────────────────
@@ -284,7 +288,7 @@ export class Timeline {
284
288
  }
285
289
 
286
290
  private beginPhaseTransition(): void {
287
- this.phaseProgress = 0;
291
+ this.setProgress(0);
288
292
  this.phaseStartedAt = performance.now();
289
293
  this.cancelRaf();
290
294
  const step = (now: number) => {
@@ -292,13 +296,13 @@ export class Timeline {
292
296
  this.autoAdvanceMsOverride ?? this.getPhase().durationMs ?? 600;
293
297
  const elapsed = now - this.phaseStartedAt;
294
298
  const linearT = clamp01(elapsed / duration);
295
- this.phaseProgress = this.easing(linearT);
299
+ this.setProgress(linearT);
296
300
  this.emit();
297
301
  if (linearT < 1) {
298
302
  this.rafHandle = requestAnimationFrame(step);
299
303
  return;
300
304
  }
301
- this.phaseProgress = 1;
305
+ this.setProgress(1);
302
306
  this.emit();
303
307
  if (this.status === "playing") {
304
308
  // 自動進行中は次 Phase へ
@@ -315,6 +319,20 @@ export class Timeline {
315
319
  this.rafHandle = requestAnimationFrame(step);
316
320
  }
317
321
 
322
+ /**
323
+ * 段の進みを 2 つ同時に置く (cdl#711)。
324
+ *
325
+ * **代入する場所を 1 つに閉じる**。 曲げた進みと曲げる前の進みを別々に書くと、
326
+ * どちらかを更新し忘れた経路が「前の段の値のまま」 で残る。
327
+ *
328
+ * 両端 (0 と 1) は曲線を通さず、 そのまま置く。 曲線は差し替えられる欄なので、
329
+ * `easing(1)` が 1 を返す保証がない。 段の始まりと終わりの値は曲線に委ねない。
330
+ */
331
+ private setProgress(linear: number): void {
332
+ this.phaseLinearProgress = linear;
333
+ this.phaseProgress = linear <= 0 ? 0 : linear >= 1 ? 1 : this.easing(linear);
334
+ }
335
+
318
336
  private cancelRaf(): void {
319
337
  if (this.rafHandle != null) {
320
338
  cancelAnimationFrame(this.rafHandle);
@@ -37,6 +37,18 @@ export type TimelineState = {
37
37
  phaseIndex: number;
38
38
  /** 現在 Phase 内の進行度 (0..1)、 視覚要素の補間に使う */
39
39
  phaseProgress: number;
40
+ /**
41
+ * 曲線を通す前の進行度 (0..1、 cdl#711)。 経過時間そのものの割合。
42
+ *
43
+ * `phaseProgress` は既定で `easeOutQuint` を通るため前半に動きが寄る。
44
+ * **値が落ち着く動きには合うが、 線を引く動きには合わない** =
45
+ * `1.2s` の段で半分に達するのが 158 ms になり、 折れ線の 5 点のうち 4 点が
46
+ * 292 ms の間に出てしまう (実測)。
47
+ *
48
+ * 描く動き (`draw` の node と伸ばす edge) はこちらを読む。
49
+ * 両端 (0 と 1) は 2 つとも同じ値になる。
50
+ */
51
+ phaseLinearProgress: number;
40
52
  /** 全 Phase 数 */
41
53
  totalPhases: number;
42
54
  };
@@ -28,6 +28,7 @@ export function CdlStage({
28
28
  laid,
29
29
  currentPhase,
30
30
  progress,
31
+ drawProgress,
31
32
  stateValues,
32
33
  debug,
33
34
  compact = false,
@@ -36,6 +37,13 @@ export function CdlStage({
36
37
  laid: LaidDiagram;
37
38
  currentPhase: CdlPhase | undefined;
38
39
  progress: number;
40
+ /**
41
+ * 描く動きが読む進み (cdl#711)。 曲線を通す前の値で、 経過時間そのものの割合。
42
+ *
43
+ * 書かない呼出は `progress` と同じ値になる = 曲線を通した進みで描く従来の形。
44
+ * 検査から直接呼ぶ経路が既に多数あり、 そこでは 2 つを分ける意味がない。
45
+ */
46
+ drawProgress?: number;
39
47
  stateValues: Record<string, string>;
40
48
  debug: boolean;
41
49
  compact?: boolean;
@@ -112,9 +120,15 @@ export function CdlStage({
112
120
  }
113
121
  return scales;
114
122
  }, [laid]);
115
- // 描く対象から見た進み (cdl#570)。 `drawRatio` を書かない phase では `progress` と同値で、
123
+ /**
124
+ * 描く動きの素の進み (cdl#711)。 渡されなければ従来どおり曲線を通した進みを使う。
125
+ *
126
+ * **`??` で受ける** = 0 は正しい進み (段の始まり) なので、 偽としては落とせない。
127
+ */
128
+ const 描く素の進み = drawProgress ?? progress;
129
+ // 描く対象から見た進み (cdl#570)。 `drawRatio` を書かない phase では `描く素の進み` と同値で、
116
130
  // 描く速さを phase の長さから切り離すためだけに使う
117
- const 描く進み = 描く進みに直す(progress, currentPhase?.drawRatio);
131
+ const 描く進み = 描く進みに直す(描く素の進み, currentPhase?.drawRatio);
118
132
  // 縦線の終点は全 lane で 1 つに揃える (等長保証)。 値の決め方と、 1 lane の異常が図全体から
119
133
  // 縦線を消していた形の対処は `uniformLifelineEndY` の説明。
120
134
  //
@@ -345,7 +359,7 @@ export function CdlStage({
345
359
  edge={edge}
346
360
  active={activeSet.has(edge.id)}
347
361
  drawing={矢印の出番.伸ばすか(edge.id)}
348
- progress={progress}
362
+ progress={矢印の出番.伸ばすか(edge.id) ? 描く進み : progress}
349
363
  stateValues={stateValues}
350
364
  />
351
365
  ))}
package/src/render.tsx CHANGED
@@ -133,6 +133,13 @@ export function CdlDiagramView({ diagram, laid: laidProp, hideHeader = false, hi
133
133
  : state.phaseIndex;
134
134
  const currentPhase = laid.phases[Math.max(0, phaseIdx)] ?? laid.phases[0];
135
135
  const progress = reduced ? 1 : state.phaseProgress;
136
+ /**
137
+ * 描く動きが読む進み (cdl#711)。 曲線を通さない = 経過時間そのものの割合。
138
+ *
139
+ * 線を引く動きに `easeOutQuint` を当てると前半に寄り、 折れ線の 5 点のうち 4 点が
140
+ * 292 ms の間に出る。 ペンの速さは一定であるほど「1 つずつ現れる」 が読める。
141
+ */
142
+ const drawProgress = reduced ? 1 : state.phaseLinearProgress;
136
143
 
137
144
  // state 値を計算 (現在 phase + これまでの phase の最終 tween 値を集約)
138
145
  const baseStateValues = computeStateValues(laid, phaseIdx, progress);
@@ -184,7 +191,7 @@ export function CdlDiagramView({ diagram, laid: laidProp, hideHeader = false, hi
184
191
  {/* interactive panel = input widget / formula readout / scroll readout (SVG の上に配置)、
185
192
  diagram に interactive primitive がなければ null (SSR / render 影響なし) */}
186
193
  {isInteractive && interactive.panel}
187
- <CdlStage laid={laid} currentPhase={currentPhase} progress={progress} stateValues={stateValues} debug={debug} compact={hideHeader} svgRef={isInteractive ? interactive.svgRef : undefined} />
194
+ <CdlStage laid={laid} currentPhase={currentPhase} progress={progress} drawProgress={drawProgress} stateValues={stateValues} debug={debug} compact={hideHeader} svgRef={isInteractive ? interactive.svgRef : undefined} />
188
195
  {/* hideHeader + multi-phase 時、 phase indicator が消えると動きが読めないため
189
196
  mini indicator (dot 列 + 現在 phase title + badge) を bottom に追加 */}
190
197
  {hideHeader && !hideMiniPhaseIndicator && currentPhase && laid.phases.length > 1 && (