@cardenelabs/cdl 0.5.0 → 0.6.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/dist/index.cjs CHANGED
@@ -363,6 +363,15 @@ var ALLOWED_MATH_FNS = /* @__PURE__ */ new Set([
363
363
  "round"
364
364
  ]);
365
365
 
366
+ // src/template-name.ts
367
+ var TEMPLATE_NAME_CHARS = "\\w+";
368
+ var TEMPLATE_REF_SRC = `\\{(${TEMPLATE_NAME_CHARS})(\\.length|\\.sum|\\.max|\\.min|\\.avg|\\[\\d+\\])?\\}`;
369
+ var TEMPLATE_LEFTOVER_SRC = `\\{(${TEMPLATE_NAME_CHARS})([.[][^}]*)?\\}`;
370
+ var NAME_ONLY_RE = new RegExp(`^${TEMPLATE_NAME_CHARS}$`);
371
+ function isValidTemplateName(name) {
372
+ return NAME_ONLY_RE.test(name);
373
+ }
374
+
366
375
  // src/formula/parser.ts
367
376
  function tokenize(src) {
368
377
  const tokens = [];
@@ -403,6 +412,25 @@ function tokenize(src) {
403
412
  tokens.push({ kind: "num", value: num, pos: start });
404
413
  continue;
405
414
  }
415
+ if (c === "{") {
416
+ const start = i;
417
+ const close = src.indexOf("}", i + 1);
418
+ if (close === -1) {
419
+ throw new Error(`unclosed "{" at position ${start} (write a name like {waiting})`);
420
+ }
421
+ const name = src.slice(i + 1, close).trim();
422
+ if (name === "") {
423
+ throw new Error(`empty name "{}" at position ${start}`);
424
+ }
425
+ if (!isValidTemplateName(name)) {
426
+ throw new Error(
427
+ `invalid name "{${name}}" at position ${start} (\u7A7A\u767D / { } / . / [ ] \u306F\u540D\u524D\u306B\u4F7F\u3048\u306A\u3044)`
428
+ );
429
+ }
430
+ tokens.push({ kind: "id", name, pos: start });
431
+ i = close + 1;
432
+ continue;
433
+ }
406
434
  if (c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "_" || c === "$") {
407
435
  const start = i;
408
436
  while (i < src.length) {
@@ -439,6 +467,10 @@ function tokenize(src) {
439
467
  tokens.push({ kind: "math", fn: `Math.${fnName}`, pos: start });
440
468
  continue;
441
469
  }
470
+ if (ALLOWED_MATH_FNS.has(name) && src[i] === "(") {
471
+ tokens.push({ kind: "math", fn: `Math.${name}`, pos: start });
472
+ continue;
473
+ }
442
474
  if (src[i] === ".") {
443
475
  throw new Error(
444
476
  `property access "${name}.${src[i + 1] ?? ""}..." at position ${start} is not allowed (only Math.* namespace is accessible)`
@@ -878,6 +910,142 @@ function createFormulaComputeds(diagram2, signals) {
878
910
  return result;
879
911
  }
880
912
 
913
+ // src/render/derived-values.ts
914
+ var parseCache = /* @__PURE__ */ new WeakMap();
915
+ function contentStamp(derived) {
916
+ return derived.map((d) => `${d.id.length}:${d.id}|${d.expression.length}:${d.expression}`).join("|");
917
+ }
918
+ function applyDerivedValues(base, derived) {
919
+ if (!derived || derived.length === 0) return { values: base, notices: [] };
920
+ const phase = parseAndOrder(derived);
921
+ const values = Object.assign(/* @__PURE__ */ Object.create(null), base);
922
+ const notices = [...phase.notices];
923
+ for (const entry of phase.entries) {
924
+ const out = evaluateOne(entry, values);
925
+ if (out.ok) values[entry.id] = out.value;
926
+ else notices.push({ id: entry.id, kind: out.kind, message: out.message });
927
+ }
928
+ return { values, notices };
929
+ }
930
+ function derivedRefIds(expression) {
931
+ try {
932
+ return [...extractIdentifiers(parseFormula(expression))];
933
+ } catch {
934
+ return [];
935
+ }
936
+ }
937
+ function withDerivedValues(base, derived) {
938
+ return applyDerivedValues(base, derived).values;
939
+ }
940
+ function parseAndOrder(derived) {
941
+ const stamp = contentStamp(derived);
942
+ const cached = parseCache.get(derived);
943
+ if (cached && cached.stamp === stamp) return cached;
944
+ const notices = [];
945
+ const seen = /* @__PURE__ */ new Set();
946
+ const parsed = [];
947
+ for (const d of derived) {
948
+ if (seen.has(d.id)) {
949
+ notices.push({
950
+ id: d.id,
951
+ kind: "duplicate-id",
952
+ message: `"${d.id}" \u304C 2 \u5EA6\u5BA3\u8A00\u3055\u308C\u3066\u3044\u308B\u3002 \u5148\u306B\u66F8\u3044\u305F\u5F0F\u3092\u4F7F\u3046`
953
+ });
954
+ continue;
955
+ }
956
+ seen.add(d.id);
957
+ if (!isValidTemplateName(d.id)) {
958
+ notices.push({
959
+ id: d.id,
960
+ kind: "invalid-id",
961
+ message: `"${d.id}" \u306F\u540D\u524D\u3068\u3057\u3066\u4F7F\u3048\u306A\u3044 (\u82F1\u6570\u5B57\u3068 _ \u306E\u307F)\u3002 \u8AAD\u307F\u624B\u304C\u62FE\u3048\u306A\u3044\u305F\u3081\u6B62\u3081\u305F`
962
+ });
963
+ continue;
964
+ }
965
+ try {
966
+ const ast = parseFormula(d.expression);
967
+ parsed.push({ id: d.id, ast, refs: extractIdentifiers(ast) });
968
+ } catch (e) {
969
+ notices.push({
970
+ id: d.id,
971
+ kind: "parse-error",
972
+ message: `"${d.id}" \u306E\u5F0F\u3092\u8AAD\u3081\u306A\u3044: ${e instanceof Error ? e.message : String(e)}`
973
+ });
974
+ }
975
+ }
976
+ const { order, cyclic } = topoSort(parsed);
977
+ for (const id of cyclic) {
978
+ notices.push({
979
+ id,
980
+ kind: "cycle",
981
+ message: `"${id}" \u306F\u53C2\u7167\u3092\u8FBF\u308B\u3068\u81EA\u5206\u306B\u623B\u308B\u305F\u3081\u6B62\u3081\u305F (\u8F2A\u306B\u5165\u3063\u3066\u3044\u306A\u3044\u5024\u306F\u52D5\u304F)`
982
+ });
983
+ }
984
+ const phase = { entries: order, notices, cyclic, stamp };
985
+ parseCache.set(derived, phase);
986
+ return phase;
987
+ }
988
+ function topoSort(entries) {
989
+ const byId = new Map(entries.map((e) => [e.id, e]));
990
+ const state = /* @__PURE__ */ new Map();
991
+ const order = [];
992
+ const cyclic = /* @__PURE__ */ new Set();
993
+ const path = [];
994
+ const visit = (entry) => {
995
+ const s = state.get(entry.id);
996
+ if (s === "done") return;
997
+ if (s === "visiting") {
998
+ const at = path.indexOf(entry.id);
999
+ for (const id of path.slice(at < 0 ? 0 : at)) cyclic.add(id);
1000
+ return;
1001
+ }
1002
+ state.set(entry.id, "visiting");
1003
+ path.push(entry.id);
1004
+ for (const ref of entry.refs) {
1005
+ if (ref === entry.id) {
1006
+ cyclic.add(entry.id);
1007
+ continue;
1008
+ }
1009
+ const next = byId.get(ref);
1010
+ if (next) visit(next);
1011
+ }
1012
+ path.pop();
1013
+ state.set(entry.id, "done");
1014
+ if (!cyclic.has(entry.id)) order.push(entry);
1015
+ };
1016
+ for (const e of entries) visit(e);
1017
+ return { order, cyclic: [...cyclic] };
1018
+ }
1019
+ var MissingReferenceError = class extends Error {
1020
+ };
1021
+ function evaluateOne(entry, values) {
1022
+ const resolver = (name) => {
1023
+ const raw = Object.hasOwn(values, name) ? values[name] : void 0;
1024
+ if (raw === void 0) throw new MissingReferenceError(`"${name}" \u3068\u3044\u3046\u5024\u304C\u7121\u3044`);
1025
+ const trimmed = raw.trim();
1026
+ const num = Number(trimmed);
1027
+ if (trimmed === "" || !Number.isFinite(num)) {
1028
+ throw new Error(`"${name}" \u306E\u5024 "${raw}" \u306F\u6570\u3068\u3057\u3066\u8AAD\u3081\u306A\u3044`);
1029
+ }
1030
+ return num;
1031
+ };
1032
+ try {
1033
+ const result = evaluate(entry.ast, resolver);
1034
+ if (typeof result === "number" && !Number.isFinite(result)) {
1035
+ return {
1036
+ ok: false,
1037
+ kind: "eval-error",
1038
+ message: `"${entry.id}" \u3092\u6B62\u3081\u305F: \u7D50\u679C\u304C\u6570\u306B\u306A\u3089\u306A\u304B\u3063\u305F`
1039
+ };
1040
+ }
1041
+ return { ok: true, value: typeof result === "boolean" ? result ? "1" : "0" : String(result) };
1042
+ } catch (e) {
1043
+ const detail = e instanceof Error ? e.message : String(e);
1044
+ const kind = e instanceof MissingReferenceError ? "unknown-reference" : "eval-error";
1045
+ return { ok: false, kind, message: `"${entry.id}" \u3092\u6B62\u3081\u305F: ${detail}` };
1046
+ }
1047
+ }
1048
+
881
1049
  // src/scroll-trigger/progress.ts
882
1050
  function computeScrollProgress(elementRect, viewportHeight, spec) {
883
1051
  if (viewportHeight <= 0) {
@@ -11516,7 +11684,6 @@ var KINDS_WITHOUT_TITLE_TEXT = /* @__PURE__ */ new Set([
11516
11684
  "chart-bar",
11517
11685
  "gantt-timeline",
11518
11686
  "mind-map",
11519
- "mind-radial",
11520
11687
  "funnel-stages",
11521
11688
  "quadrant-matrix",
11522
11689
  "tree-hierarchy",
@@ -12916,10 +13083,9 @@ var TOKENS = {
12916
13083
  "chart-line": { w: 640, h: 360 },
12917
13084
  "chart-pie": { w: 640, h: 320 },
12918
13085
  "chart-bar": { w: 640, h: 360 },
12919
- // Timeline / Radial 系 (CAR-994 Phase B)
13086
+ // Timeline / Mind 系 (CAR-994 Phase B)
12920
13087
  "gantt-timeline": { w: 720, h: 360 },
12921
13088
  "mind-map": { w: 720, h: 480 },
12922
- "mind-radial": { w: 640, h: 640 },
12923
13089
  // Analytic 系 (CAR-994 Phase C)
12924
13090
  "funnel-stages": { w: 560, h: 480 },
12925
13091
  "quadrant-matrix": { w: 640, h: 480 },
@@ -14975,6 +15141,7 @@ function layout(diag) {
14975
15141
  edges,
14976
15142
  states: diag.states,
14977
15143
  phases: diag.phases,
15144
+ ...diag.derived ? { derived: diag.derived } : {},
14978
15145
  bboxes,
14979
15146
  collisions,
14980
15147
  nearCollisions,
@@ -15035,10 +15202,9 @@ var NODE_KINDS = [
15035
15202
  "chart-pie",
15036
15203
  "chart-line",
15037
15204
  "chart-bar",
15038
- // Timeline / Radial 系 (3、 CAR-994 Phase B) ... 1 node に payload 配列、 SVG geometry render
15205
+ // Timeline / Mind 系 (2、 CAR-994 Phase B) ... 1 node に payload 配列、 SVG geometry render
15039
15206
  "gantt-timeline",
15040
15207
  "mind-map",
15041
- "mind-radial",
15042
15208
  // Analytic 系 (4、 CAR-994 Phase C) ... 1 node に payload 配列、 SVG geometry render
15043
15209
  "funnel-stages",
15044
15210
  "quadrant-matrix",
@@ -15105,242 +15271,736 @@ var NODE_KINDS = [
15105
15271
  "shape-customer-service"
15106
15272
  ];
15107
15273
 
15108
- // src/validate.ts
15109
- var TONES2 = new Set(TONES);
15110
- var KINDS = new Set(NODE_KINDS);
15111
- var ENG_BANNED = /\b(FUNCTION|STORAGE|EVENT LOG|BALANCES MAPPING|READ|WRITE|EMIT|CALL)\b/;
15112
- function validate(diag) {
15113
- const errors = [];
15114
- const warnings = [];
15115
- if (diag.lanes.length === 0) {
15116
- errors.push(
15117
- `[cdl] empty: lane \u304C 0 \u4EF6\u3067\u3059
15118
- \u2192 \u4FEE\u6B63\u4F8B: diagram("${diag.id}", { topic }).lane("col1", { x: 0, width: 400 }).node(...).build()`
15119
- );
15120
- }
15121
- if (diag.nodes.length === 0) {
15122
- errors.push(
15123
- `[cdl] empty: node \u304C 0 \u4EF6\u3067\u3059
15124
- \u2192 \u4FEE\u6B63\u4F8B: .node("a", { lane: "col1", stack: 0, kind: "actor", title: "Client" })`
15125
- );
15126
- }
15127
- if (diag.phases.length === 0) {
15128
- errors.push(
15129
- `[cdl] empty: phase \u304C 0 \u4EF6\u3067\u3059 (animation \u3092\u99C6\u52D5\u3059\u308B\u305F\u3081\u306E\u6700\u4F4E 1 phase \u304C\u5FC5\u8981)
15130
- \u2192 \u4FEE\u6B63\u4F8B: .phase("p1", { duration: 1800, title: "step 1", body: "..." }, p => p.activate("a"))`
15131
- );
15132
- }
15133
- const checkDup = (kind, items) => {
15134
- const seen = /* @__PURE__ */ new Map();
15135
- items.forEach((item, idx) => {
15136
- const prev = seen.get(item.id);
15137
- if (prev !== void 0) {
15138
- errors.push(`[cdl] duplicate-id: ${kind} "${item.id}" \u304C ${prev} \u4EF6\u76EE + ${idx} \u4EF6\u76EE\u3067\u91CD\u8907`);
15139
- } else {
15140
- seen.set(item.id, idx);
15141
- }
15142
- });
15143
- };
15144
- checkDup("lane", diag.lanes);
15145
- checkDup("node", diag.nodes);
15146
- checkDup("edge", diag.edges);
15147
- checkDup("state", diag.states);
15148
- checkDup("phase", diag.phases);
15149
- for (const e of diag.edges) {
15150
- if (!TONES2.has(e.tone)) {
15151
- errors.push(`[cdl] unknown-tone: edge "${e.id}" tone "${e.tone}" \u306F\u672A\u5BFE\u5FDC (${[...TONES2].join(" / ")} \u306E\u3044\u305A\u308C\u304B)`);
15152
- }
15153
- }
15154
- for (const n of diag.nodes) {
15155
- if (!KINDS.has(n.kind)) {
15156
- errors.push(`[cdl] unknown-kind: node "${n.id}" kind "${n.kind}" \u306F\u672A\u5BFE\u5FDC (${[...KINDS].join(" / ")} \u306E\u3044\u305A\u308C\u304B)`);
15274
+ // src/anim/core/easing.ts
15275
+ function cubicBezier(x1, y1, x2, y2) {
15276
+ const A = (a, b) => 1 - 3 * b + 3 * a;
15277
+ const B = (a, b) => 3 * b - 6 * a;
15278
+ const C = (a) => 3 * a;
15279
+ const sampleCurveX = (t) => ((A(x1, x2) * t + B(x1, x2)) * t + C(x1)) * t;
15280
+ const sampleCurveY = (t) => ((A(y1, y2) * t + B(y1, y2)) * t + C(y1)) * t;
15281
+ const sampleDerivativeX = (t) => 3 * A(x1, x2) * t * t + 2 * B(x1, x2) * t + C(x1);
15282
+ function solveCurveX(x) {
15283
+ if (x <= 0) return 0;
15284
+ if (x >= 1) return 1;
15285
+ let t = x;
15286
+ for (let i = 0; i < 8; i += 1) {
15287
+ const currentX = sampleCurveX(t) - x;
15288
+ if (Math.abs(currentX) < 1e-5) return t;
15289
+ const currentSlope = sampleDerivativeX(t);
15290
+ if (Math.abs(currentSlope) < 1e-5) break;
15291
+ t -= currentX / currentSlope;
15157
15292
  }
15158
- if (n.tone !== void 0 && !TONES2.has(n.tone)) {
15159
- errors.push(`[cdl] unknown-tone: node "${n.id}" tone "${n.tone}" \u306F\u672A\u5BFE\u5FDC (${[...TONES2].join(" / ")} \u306E\u3044\u305A\u308C\u304B)`);
15293
+ let lo = 0;
15294
+ let hi = 1;
15295
+ let guess = x;
15296
+ while (lo < hi) {
15297
+ const currentX = sampleCurveX(guess);
15298
+ if (Math.abs(currentX - x) < 1e-5) return guess;
15299
+ if (x > currentX) lo = guess;
15300
+ else hi = guess;
15301
+ guess = (hi - lo) / 2 + lo;
15160
15302
  }
15303
+ return guess;
15161
15304
  }
15162
- const laneIds = new Set(diag.lanes.map((l) => l.id));
15163
- const nodeIds = new Set(diag.nodes.map((n) => n.id));
15164
- const edgeIds = new Set(diag.edges.map((e) => e.id));
15165
- const stateIds = new Set(diag.states.map((s) => s.id));
15166
- const suggest = (target, candidates) => {
15167
- const list = [...candidates];
15168
- if (list.length === 0) return "";
15169
- let best = "";
15170
- let bestD = Infinity;
15171
- for (const c of list) {
15172
- const d = levenshtein(target, c);
15173
- if (d < bestD) {
15174
- bestD = d;
15175
- best = c;
15176
- }
15177
- }
15178
- return bestD <= 2 ? ` (\u3082\u3057\u304B\u3057\u3066 "${best}"?)` : ` (\u5B9A\u7FA9\u6E08 lane: ${list.slice(0, 5).join(", ")}${list.length > 5 ? "..." : ""})`;
15305
+ return (t) => {
15306
+ if (t <= 0) return 0;
15307
+ if (t >= 1) return 1;
15308
+ return sampleCurveY(solveCurveX(t));
15179
15309
  };
15180
- for (const n of diag.nodes) {
15181
- if (!laneIds.has(n.lane)) {
15182
- errors.push(
15183
- `[cdl] unknown-ref: node "${n.id}" \u306E lane "${n.lane}" \u304C\u672A\u5B9A\u7FA9\u3067\u3059${suggest(n.lane, laneIds)}
15184
- \u2192 \u4FEE\u6B63\u4F8B: .lane("${n.lane}", { x: 0, width: 400 }) \u3092 node \u5BA3\u8A00\u524D\u306B\u8FFD\u52A0`
15185
- );
15186
- }
15187
- }
15188
- for (const e of diag.edges) {
15189
- if (!nodeIds.has(e.from)) {
15190
- errors.push(
15191
- `[cdl] unknown-ref: edge "${e.id}" \u306E from "${e.from}" \u304C node \u306B\u5B58\u5728\u3057\u307E\u305B\u3093${suggest(e.from, nodeIds)}
15192
- \u2192 \u4FEE\u6B63\u4F8B: .node("${e.from}", { lane: "...", stack: 0, kind: "actor", title: "..." }) \u3092 edge \u5BA3\u8A00\u524D\u306B\u8FFD\u52A0`
15193
- );
15310
+ }
15311
+ var easeOutQuint = cubicBezier(0.22, 1, 0.36, 1);
15312
+ function clamp01(value) {
15313
+ if (value < 0) return 0;
15314
+ if (value > 1) return 1;
15315
+ return value;
15316
+ }
15317
+ function lerp(start, end, t) {
15318
+ return start + (end - start) * t;
15319
+ }
15320
+
15321
+ // src/anim/core/timeline.ts
15322
+ var Timeline = class {
15323
+ phases;
15324
+ easing;
15325
+ loop;
15326
+ autoAdvanceMsOverride;
15327
+ phaseHoldMs;
15328
+ loopRestartDelayMs;
15329
+ autoplay;
15330
+ subscribers = /* @__PURE__ */ new Set();
15331
+ holdTimerHandle = null;
15332
+ rafHandle = null;
15333
+ phaseIndex = 0;
15334
+ phaseProgress = 0;
15335
+ phaseStartedAt = 0;
15336
+ status = "idle";
15337
+ /**
15338
+ * snapshot を「state が変わった時のみ」 新 object として返すためのキャッシュ。
15339
+ * useSyncExternalStore は getSnapshot() の戻り値が前回と参照同値の時に
15340
+ * re-render を skip する設計なので、 これがないと毎 frame 別 object で
15341
+ * 無限 render → React #185 になる。
15342
+ */
15343
+ cachedSnapshot = null;
15344
+ /**
15345
+ * constructor 直後の state snapshot。 destroy() で復元して StrictMode 二重 mount 経路の
15346
+ * autostart() が正しく初期状態から再開できるようにする (initialPhaseIndex + autoplay 組合せ保持)。
15347
+ */
15348
+ initialState;
15349
+ constructor(phases, options = {}) {
15350
+ if (phases.length === 0) {
15351
+ throw new Error("Timeline requires at least one phase");
15194
15352
  }
15195
- if (!nodeIds.has(e.to)) {
15196
- errors.push(
15197
- `[cdl] unknown-ref: edge "${e.id}" \u306E to "${e.to}" \u304C node \u306B\u5B58\u5728\u3057\u307E\u305B\u3093${suggest(e.to, nodeIds)}
15198
- \u2192 \u4FEE\u6B63\u4F8B: .node("${e.to}", { lane: "...", stack: 0, kind: "...", title: "..." }) \u3092 edge \u5BA3\u8A00\u524D\u306B\u8FFD\u52A0`
15199
- );
15353
+ this.phases = phases;
15354
+ this.easing = options.easing ?? easeOutQuint;
15355
+ this.loop = options.loop ?? false;
15356
+ this.autoAdvanceMsOverride = options.autoAdvanceMs;
15357
+ this.phaseHoldMs = options.phaseHoldMs ?? 0;
15358
+ this.loopRestartDelayMs = options.loopRestartDelayMs ?? this.phaseHoldMs;
15359
+ this.autoplay = options.autoplay ?? false;
15360
+ const initial = options.initialPhaseIndex;
15361
+ if (initial != null && initial > 0 && initial < phases.length) {
15362
+ this.phaseIndex = initial;
15363
+ this.phaseProgress = 1;
15364
+ this.status = initial === phases.length - 1 ? "settled" : "paused";
15200
15365
  }
15201
- if (e.from === e.to) {
15202
- errors.push(
15203
- `[cdl] self-loop: edge "${e.id}" \u306E from \u3068 to \u304C\u540C\u3058 node "${e.from}" \u3067\u3059 (self-loop \u306F\u73FE\u72B6\u672A\u5BFE\u5FDC)
15204
- \u2192 \u5225 node \u3092\u4F5C\u3063\u3066 2 \u6BB5\u306B\u5206\u3051\u308B\u304B\u3001 \u5F8C\u7D9A PR \u306E self-loop support \u3092\u5F85\u3063\u3066\u304F\u3060\u3055\u3044`
15205
- );
15366
+ this.initialState = {
15367
+ phaseIndex: this.phaseIndex,
15368
+ phaseProgress: this.phaseProgress,
15369
+ status: this.status
15370
+ };
15371
+ }
15372
+ /**
15373
+ * mount 後に呼んで autoplay opt-in が真なら play を発火する。
15374
+ * constructor で呼ぶと SSR 環境で rAF が動いて失敗するので別経路に分離。
15375
+ */
15376
+ autostart() {
15377
+ if (this.autoplay && this.status === "idle") {
15378
+ this.play();
15206
15379
  }
15207
15380
  }
15208
- for (const p of diag.phases) {
15209
- for (const id of p.activate) {
15210
- if (!nodeIds.has(id) && !edgeIds.has(id)) {
15211
- errors.push(`[cdl] unknown-ref: phase "${p.id}" \u306E activate "${id}" \u306F node \u3067\u3082 edge \u3067\u3082\u306A\u3044 (typo \u306E\u53EF\u80FD\u6027)`);
15212
- }
15213
- }
15214
- for (const t of p.tweens) {
15215
- if (!stateIds.has(t.stateId)) {
15216
- errors.push(`[cdl] unknown-ref: phase "${p.id}" tween \u5BFE\u8C61 state "${t.stateId}" \u672A\u5B9A\u7FA9`);
15217
- }
15218
- if (typeof t.from === "number" && typeof t.to === "number" && t.from === t.to) {
15219
- warnings.push(`phase "${p.id}" tween state "${t.stateId}" \u306E from === to (${t.from})\u3001 \u52D5\u304D\u306A\u3057`);
15220
- }
15221
- }
15222
- for (const s of p.sets) {
15223
- if (!stateIds.has(s.stateId)) {
15224
- errors.push(`[cdl] unknown-ref: phase "${p.id}" set \u5BFE\u8C61 state "${s.stateId}" \u672A\u5B9A\u7FA9`);
15225
- }
15226
- }
15227
- if (p.duration <= 0) {
15228
- errors.push(`[cdl] invalid-duration: phase "${p.id}" duration "${p.duration}" \u306F\u6B63\u5024\u5FC5\u9808`);
15381
+ // ─── public API ──────────────────────────────────────────────
15382
+ subscribe(subscriber) {
15383
+ this.subscribers.add(subscriber);
15384
+ return () => {
15385
+ this.subscribers.delete(subscriber);
15386
+ };
15387
+ }
15388
+ snapshot() {
15389
+ const cached = this.cachedSnapshot;
15390
+ if (cached && cached.status === this.status && cached.phaseIndex === this.phaseIndex && cached.phaseProgress === this.phaseProgress && cached.totalPhases === this.phases.length) {
15391
+ return cached;
15229
15392
  }
15393
+ const next = {
15394
+ status: this.status,
15395
+ phaseIndex: this.phaseIndex,
15396
+ phaseProgress: this.phaseProgress,
15397
+ totalPhases: this.phases.length
15398
+ };
15399
+ this.cachedSnapshot = next;
15400
+ return next;
15230
15401
  }
15231
- const refRe = /\{(\w+)\}/g;
15232
- for (const n of diag.nodes) {
15233
- const checks = [];
15234
- if (n.value) checks.push(n.value);
15235
- if (n.rows) checks.push(...n.rows);
15236
- for (const text of checks) {
15237
- let m;
15238
- while (m = refRe.exec(text)) {
15239
- const id = m[1];
15240
- if (id && !stateIds.has(id)) {
15241
- warnings.push(`node "${n.id}" \u304C\u672A\u5B9A\u7FA9 state "${id}" \u3092\u53C2\u7167`);
15242
- }
15243
- }
15402
+ getPhase() {
15403
+ const phase = this.phases[this.phaseIndex];
15404
+ if (!phase) {
15405
+ throw new Error(`Invalid phase index ${this.phaseIndex}`);
15244
15406
  }
15407
+ return phase;
15245
15408
  }
15246
- const allActivated = /* @__PURE__ */ new Set();
15247
- for (const p of diag.phases) {
15248
- for (const id of p.activate) allActivated.add(id);
15409
+ /** Phase ID 指定でジャンプ (見つからない場合は no-op) */
15410
+ gotoId(id) {
15411
+ const index = this.phases.findIndex((phase) => phase.id === id);
15412
+ if (index >= 0) this.goto(index);
15249
15413
  }
15250
- for (const n of diag.nodes) {
15251
- if (!allActivated.has(n.id)) {
15252
- warnings.push(`node "${n.id}" \u304C\u3069\u306E phase \u3067\u3082 activate \u3055\u308C\u3066\u3044\u306A\u3044 (unused\u3001 typo \u306E\u53EF\u80FD\u6027)`);
15253
- }
15414
+ /** Phase index 指定でジャンプ */
15415
+ goto(index) {
15416
+ const clamped = Math.max(0, Math.min(this.phases.length - 1, index));
15417
+ this.cancelRaf();
15418
+ this.phaseIndex = clamped;
15419
+ this.phaseProgress = 1;
15420
+ this.status = clamped === this.phases.length - 1 ? "settled" : "paused";
15421
+ this.emit();
15254
15422
  }
15255
- for (const e of diag.edges) {
15256
- if (!allActivated.has(e.id)) {
15257
- warnings.push(`edge "${e.id}" \u304C\u3069\u306E phase \u3067\u3082 activate \u3055\u308C\u3066\u3044\u306A\u3044 (unused\u3001 typo \u306E\u53EF\u80FD\u6027)`);
15423
+ play() {
15424
+ if (this.status === "playing") return;
15425
+ this.status = "playing";
15426
+ if (this.phaseProgress >= 1 && this.phaseIndex < this.phases.length - 1) {
15427
+ this.advancePhase();
15428
+ } else {
15429
+ this.beginPhaseTransition();
15258
15430
  }
15431
+ this.emit();
15259
15432
  }
15260
- const stateUsed = /* @__PURE__ */ new Set();
15261
- for (const p of diag.phases) {
15262
- for (const t of p.tweens) stateUsed.add(t.stateId);
15263
- for (const s of p.sets) stateUsed.add(s.stateId);
15433
+ pause() {
15434
+ if (this.status !== "playing") return;
15435
+ this.cancelRaf();
15436
+ this.status = "paused";
15437
+ this.emit();
15264
15438
  }
15265
- for (const n of diag.nodes) {
15266
- const texts2 = [n.value, ...n.rows ?? []].filter(Boolean);
15267
- for (const text of texts2) {
15268
- let m;
15269
- const re = /\{(\w+)\}/g;
15270
- while (m = re.exec(text)) {
15271
- if (m[1]) stateUsed.add(m[1]);
15439
+ next() {
15440
+ if (this.phaseIndex >= this.phases.length - 1) {
15441
+ if (this.loop) {
15442
+ this.goto(0);
15443
+ this.play();
15272
15444
  }
15445
+ return;
15273
15446
  }
15274
- }
15275
- for (const s of diag.states) {
15276
- if (!stateUsed.has(s.id)) {
15277
- warnings.push(`state "${s.id}" \u304C\u3069\u3053\u3067\u3082\u53C2\u7167\u3055\u308C\u3066\u3044\u306A\u3044 (unused)`);
15447
+ this.cancelRaf();
15448
+ this.phaseIndex += 1;
15449
+ this.beginPhaseTransition();
15450
+ if (this.status !== "playing") {
15451
+ this.status = "paused";
15278
15452
  }
15453
+ this.emit();
15279
15454
  }
15280
- const laneUsed = /* @__PURE__ */ new Set();
15281
- for (const n of diag.nodes) laneUsed.add(n.lane);
15282
- for (const l of diag.lanes) {
15283
- if (!laneUsed.has(l.id)) {
15284
- warnings.push(`lane "${l.id}" \u306B\u3069\u306E node \u3082\u914D\u7F6E\u3055\u308C\u3066\u3044\u306A\u3044 (unused)`);
15285
- }
15455
+ prev() {
15456
+ if (this.phaseIndex <= 0) return;
15457
+ this.cancelRaf();
15458
+ this.phaseIndex -= 1;
15459
+ this.phaseProgress = 1;
15460
+ this.status = "paused";
15461
+ this.emit();
15286
15462
  }
15287
- const texts = [];
15288
- for (const l of diag.lanes) if (l.label) texts.push({ where: `lane "${l.id}".label`, text: l.label });
15289
- for (const n of diag.nodes) {
15290
- texts.push({ where: `node "${n.id}".title`, text: n.title });
15291
- if (n.eyebrow) texts.push({ where: `node "${n.id}".eyebrow`, text: n.eyebrow });
15292
- if (n.subtitle) texts.push({ where: `node "${n.id}".subtitle`, text: n.subtitle });
15463
+ reset() {
15464
+ this.cancelRaf();
15465
+ this.phaseIndex = 0;
15466
+ this.phaseProgress = 0;
15467
+ this.status = "idle";
15468
+ this.emit();
15293
15469
  }
15294
- for (const e of diag.edges) {
15295
- texts.push({ where: `edge "${e.id}".label`, text: e.label });
15296
- if (e.sub) texts.push({ where: `edge "${e.id}".sub`, text: e.sub });
15470
+ /**
15471
+ * cleanup + reset for-remount。 subscribers.clear() + cancelRaf() active resource
15472
+ * 全解放した後、 (phaseIndex, phaseProgress, status) constructor 直後の initialState に
15473
+ * 復元する。 API 名は "destroy" だが object dead / GC-ready 契約ではなく、 destroy 後も
15474
+ * subscribe / autostart / snapshot / play が正しく動作する reusable reset object として設計。
15475
+ *
15476
+ * 主 caller = React `useEffect` の cleanup で、 StrictMode の dev 二重 mount 経路で
15477
+ * setup 再実行時に autostart() が正しく再発火するよう status を initialState に戻す。
15478
+ *
15479
+ * 詳細 = active edge が pathSubpath(d, 0) = "M x y" のみで空 line 描画される
15480
+ * regression (dragon Issue #381) を防ぐため、 destroy() は「破棄」 でなく「再利用可能な reset」。
15481
+ */
15482
+ destroy() {
15483
+ this.cancelRaf();
15484
+ this.subscribers.clear();
15485
+ this.status = this.initialState.status;
15486
+ this.phaseIndex = this.initialState.phaseIndex;
15487
+ this.phaseProgress = this.initialState.phaseProgress;
15297
15488
  }
15298
- for (const { where, text } of texts) {
15299
- if (ENG_BANNED.test(text)) {
15300
- warnings.push(`${where} \u306B uppercase \u82F1\u5358\u8A9E\u304C\u691C\u51FA: "${text}"`);
15489
+ // ─── internals ───────────────────────────────────────────────
15490
+ advancePhase() {
15491
+ if (this.phaseIndex >= this.phases.length - 1) {
15492
+ if (this.loop) {
15493
+ this.scheduleHold(this.loopRestartDelayMs, () => {
15494
+ this.phaseIndex = 0;
15495
+ this.beginPhaseTransition();
15496
+ });
15497
+ } else {
15498
+ this.status = "settled";
15499
+ this.emit();
15500
+ }
15501
+ return;
15301
15502
  }
15503
+ this.scheduleHold(this.phaseHoldMs, () => {
15504
+ this.phaseIndex += 1;
15505
+ this.beginPhaseTransition();
15506
+ });
15302
15507
  }
15303
- if (warnings.length > 0) {
15304
- for (const w of warnings) console.warn(`[cdl validate] warn: ${w}`);
15508
+ scheduleHold(ms, after) {
15509
+ if (this.holdTimerHandle != null) {
15510
+ clearTimeout(this.holdTimerHandle);
15511
+ this.holdTimerHandle = null;
15512
+ }
15513
+ if (ms <= 0) {
15514
+ after();
15515
+ return;
15516
+ }
15517
+ this.holdTimerHandle = setTimeout(() => {
15518
+ this.holdTimerHandle = null;
15519
+ if (this.status === "playing") after();
15520
+ }, ms);
15305
15521
  }
15306
- if (errors.length > 0) {
15307
- throw new Error(`cdl validate failed:
15308
- - ${errors.join("\n - ")}`);
15522
+ beginPhaseTransition() {
15523
+ this.phaseProgress = 0;
15524
+ this.phaseStartedAt = performance.now();
15525
+ this.cancelRaf();
15526
+ const step = (now) => {
15527
+ const duration = this.autoAdvanceMsOverride ?? this.getPhase().durationMs ?? 600;
15528
+ const elapsed = now - this.phaseStartedAt;
15529
+ const linearT = clamp01(elapsed / duration);
15530
+ this.phaseProgress = this.easing(linearT);
15531
+ this.emit();
15532
+ if (linearT < 1) {
15533
+ this.rafHandle = requestAnimationFrame(step);
15534
+ return;
15535
+ }
15536
+ this.phaseProgress = 1;
15537
+ this.emit();
15538
+ if (this.status === "playing") {
15539
+ if (this.phaseIndex < this.phases.length - 1) {
15540
+ this.advancePhase();
15541
+ } else if (this.loop) {
15542
+ this.advancePhase();
15543
+ } else {
15544
+ this.status = "settled";
15545
+ this.emit();
15546
+ }
15547
+ }
15548
+ };
15549
+ this.rafHandle = requestAnimationFrame(step);
15309
15550
  }
15310
- }
15311
- function levenshtein(a, b) {
15312
- const m = a.length;
15313
- const n = b.length;
15314
- if (m === 0) return n;
15315
- if (n === 0) return m;
15316
- const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
15317
- for (let i = 0; i <= m; i++) dp[i][0] = i;
15318
- for (let j = 0; j <= n; j++) dp[0][j] = j;
15319
- for (let i = 1; i <= m; i++) {
15320
- for (let j = 1; j <= n; j++) {
15321
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
15322
- dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
15551
+ cancelRaf() {
15552
+ if (this.rafHandle != null) {
15553
+ cancelAnimationFrame(this.rafHandle);
15554
+ this.rafHandle = null;
15555
+ }
15556
+ if (this.holdTimerHandle != null) {
15557
+ clearTimeout(this.holdTimerHandle);
15558
+ this.holdTimerHandle = null;
15323
15559
  }
15324
15560
  }
15325
- return dp[m][n];
15326
- }
15561
+ emit() {
15562
+ const snapshot = this.snapshot();
15563
+ for (const subscriber of this.subscribers) {
15564
+ subscriber(snapshot);
15565
+ }
15566
+ }
15567
+ };
15327
15568
 
15328
- // src/compile.ts
15329
- function compile(diag) {
15330
- validate(diag);
15331
- return layout(diag);
15569
+ // src/render/utils.ts
15570
+ var TEMPLATE_REF_RE = new RegExp(TEMPLATE_REF_SRC, "g");
15571
+ var TEMPLATE_LEFTOVER_TEST_RE = new RegExp(TEMPLATE_LEFTOVER_SRC);
15572
+ function hasUnresolvedRef(text) {
15573
+ return TEMPLATE_LEFTOVER_TEST_RE.test(text);
15332
15574
  }
15333
-
15334
- // src/layout/geometry.ts
15335
- function pointRectEdgeDistance(px, py, rect) {
15336
- const dx = Math.max(rect.x - px, 0, px - (rect.x + rect.w));
15337
- const dy = Math.max(rect.y - py, 0, py - (rect.y + rect.h));
15338
- return Math.hypot(dx, dy);
15575
+ function templateRefIds(text) {
15576
+ const re = new RegExp(TEMPLATE_LEFTOVER_SRC, "g");
15577
+ const out = [];
15578
+ let m;
15579
+ while ((m = re.exec(text)) !== null) {
15580
+ if (m[1]) out.push(m[1]);
15581
+ }
15582
+ return out;
15339
15583
  }
15340
- function rectRectOverlapArea(a, b) {
15341
- const dx = Math.min(a.x + a.w, b.x + b.w) - Math.max(a.x, b.x);
15342
- const dy = Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y);
15343
- if (dx <= 0 || dy <= 0) return 0;
15584
+ function computeStateValues(laid, phaseIdx, progress) {
15585
+ const values = {};
15586
+ for (const s of laid.states) values[s.id] = s.initial;
15587
+ for (let i = 0; i < laid.phases.length; i++) {
15588
+ const phase = laid.phases[i];
15589
+ if (!phase) continue;
15590
+ if (i <= phaseIdx) {
15591
+ for (const s of phase.sets) {
15592
+ values[s.stateId] = s.value;
15593
+ }
15594
+ }
15595
+ for (const t of phase.tweens) {
15596
+ if (i < phaseIdx) {
15597
+ values[t.stateId] = t.to;
15598
+ } else if (i === phaseIdx) {
15599
+ values[t.stateId] = Math.round(lerp(t.from, t.to, progress));
15600
+ }
15601
+ }
15602
+ }
15603
+ const out = {};
15604
+ for (const [k, v] of Object.entries(values)) out[k] = String(v);
15605
+ return withDerivedValues(out, laid.derived);
15606
+ }
15607
+ function interpolate(template, values) {
15608
+ return template.replace(TEMPLATE_REF_RE, (_, id, accessor) => {
15609
+ const raw = Object.hasOwn(values, id) ? values[id] : void 0;
15610
+ if (raw === void 0) return `{${id}${accessor ?? ""}}`;
15611
+ if (!accessor) return raw;
15612
+ const arr = parseArraySignal(raw);
15613
+ if (!arr) return `{${id}${accessor}}`;
15614
+ if (accessor === ".length") return String(arr.length);
15615
+ const nums = arr.map((v) => Number.isFinite(Number(v)) ? Number(v) : 0);
15616
+ if (accessor === ".sum") return String(nums.reduce((s, v) => s + v, 0));
15617
+ if (accessor === ".max") return nums.length ? String(nums.reduce((a, b) => a > b ? a : b, nums[0])) : "0";
15618
+ if (accessor === ".min") return nums.length ? String(nums.reduce((a, b) => a < b ? a : b, nums[0])) : "0";
15619
+ if (accessor === ".avg") {
15620
+ if (!nums.length) return "0";
15621
+ const sum = nums.reduce((s, v) => s + v, 0);
15622
+ return String(Math.round(sum / nums.length * 100) / 100);
15623
+ }
15624
+ if (accessor.startsWith("[")) {
15625
+ const idx = Number(accessor.slice(1, -1));
15626
+ if (Number.isInteger(idx) && idx >= 0 && idx < arr.length) return String(arr[idx]);
15627
+ return `{${id}${accessor}}`;
15628
+ }
15629
+ return raw;
15630
+ });
15631
+ }
15632
+ function parseArraySignal(raw) {
15633
+ const trimmed = raw.trim();
15634
+ if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return null;
15635
+ try {
15636
+ const parsed = JSON.parse(trimmed);
15637
+ if (Array.isArray(parsed)) return parsed;
15638
+ } catch {
15639
+ const inner = trimmed.slice(1, -1).trim();
15640
+ if (!inner) return [];
15641
+ if (/^[\s\d.,+\-eE]+$/.test(inner)) return inner.split(",").map((s) => Number(s.trim()));
15642
+ }
15643
+ return null;
15644
+ }
15645
+ function resolveNumericAttr(value, values, fallback) {
15646
+ if (typeof value === "number") return value;
15647
+ if (typeof value !== "string") return fallback;
15648
+ const trimmed = value.trim();
15649
+ if (trimmed === "") return fallback;
15650
+ const resolved = interpolate(trimmed, values);
15651
+ const parsed = Number(resolved);
15652
+ return Number.isFinite(parsed) ? parsed : fallback;
15653
+ }
15654
+ function shrinkPathEnd(d, shrinkPx) {
15655
+ const commandRegex = /([MLCQZmlcqz])([^MLCQZmlcqz]*)/g;
15656
+ const matches = [];
15657
+ let m;
15658
+ while ((m = commandRegex.exec(d)) !== null) {
15659
+ const cmd = m[1];
15660
+ const args = (m[2] ?? "").trim().split(/[\s,]+/).filter(Boolean).map(parseFloat);
15661
+ matches.push({ cmd, args });
15662
+ }
15663
+ if (matches.length < 2) return d;
15664
+ let prevEnd = { x: 0, y: 0 };
15665
+ for (let i = 0; i < matches.length - 1; i++) {
15666
+ const { cmd, args } = matches[i];
15667
+ if (cmd === "M" || cmd === "L") {
15668
+ prevEnd = { x: args[0], y: args[1] };
15669
+ } else if (cmd === "Q") {
15670
+ prevEnd = { x: args[2], y: args[3] };
15671
+ } else if (cmd === "C") {
15672
+ prevEnd = { x: args[4], y: args[5] };
15673
+ }
15674
+ }
15675
+ const last = matches[matches.length - 1];
15676
+ let endX, endY;
15677
+ if (last.cmd === "L" || last.cmd === "M") {
15678
+ endX = last.args[0];
15679
+ endY = last.args[1];
15680
+ } else if (last.cmd === "Q") {
15681
+ endX = last.args[2];
15682
+ endY = last.args[3];
15683
+ } else if (last.cmd === "C") {
15684
+ endX = last.args[4];
15685
+ endY = last.args[5];
15686
+ } else {
15687
+ return d;
15688
+ }
15689
+ const dx = endX - prevEnd.x;
15690
+ const dy = endY - prevEnd.y;
15691
+ const len = Math.hypot(dx, dy);
15692
+ if (len < shrinkPx + 1) return d;
15693
+ const ratio = (len - shrinkPx) / len;
15694
+ const newEndX = prevEnd.x + dx * ratio;
15695
+ const newEndY = prevEnd.y + dy * ratio;
15696
+ const tokens = d.trim().split(/\s+/);
15697
+ if (tokens.length < 2) return d;
15698
+ tokens[tokens.length - 2] = newEndX.toFixed(1);
15699
+ tokens[tokens.length - 1] = newEndY.toFixed(1);
15700
+ return tokens.join(" ");
15701
+ }
15702
+ function pathSubpath(d, progress) {
15703
+ if (progress >= 1) return d;
15704
+ if (progress <= 0) {
15705
+ const first = d.trim().match(/^M\s+([-\d.]+)\s+([-\d.]+)/);
15706
+ if (!first) return d;
15707
+ return `M ${first[1]} ${first[2]}`;
15708
+ }
15709
+ const tokens = d.trim().split(/\s+/);
15710
+ const points = [];
15711
+ let i = 0;
15712
+ while (i < tokens.length) {
15713
+ const t = tokens[i];
15714
+ if (t === "M" || t === "L") {
15715
+ const x = parseFloat(tokens[i + 1]);
15716
+ const y = parseFloat(tokens[i + 2]);
15717
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return d;
15718
+ points.push({ x, y });
15719
+ i += 3;
15720
+ } else {
15721
+ return d;
15722
+ }
15723
+ }
15724
+ if (points.length < 2) return d;
15725
+ const segLens = [];
15726
+ let total = 0;
15727
+ for (let k = 1; k < points.length; k++) {
15728
+ const dx = points[k].x - points[k - 1].x;
15729
+ const dy = points[k].y - points[k - 1].y;
15730
+ const len = Math.hypot(dx, dy);
15731
+ segLens.push(len);
15732
+ total += len;
15733
+ }
15734
+ if (total === 0) return d;
15735
+ const target = total * progress;
15736
+ let acc = 0;
15737
+ for (let k = 0; k < segLens.length; k++) {
15738
+ const segLen = segLens[k];
15739
+ if (acc + segLen >= target) {
15740
+ const remain = target - acc;
15741
+ const ratio = segLen === 0 ? 0 : remain / segLen;
15742
+ const sx = points[k].x;
15743
+ const sy = points[k].y;
15744
+ const ex = points[k + 1].x;
15745
+ const ey = points[k + 1].y;
15746
+ const tx = sx + (ex - sx) * ratio;
15747
+ const ty = sy + (ey - sy) * ratio;
15748
+ const head = points.slice(0, k + 1).map((p, idx) => `${idx === 0 ? "M" : "L"} ${p.x} ${p.y}`).join(" ");
15749
+ return `${head} L ${tx.toFixed(1)} ${ty.toFixed(1)}`;
15750
+ }
15751
+ acc += segLen;
15752
+ }
15753
+ return d;
15754
+ }
15755
+
15756
+ // src/validate.ts
15757
+ var TONES2 = new Set(TONES);
15758
+ var KINDS = new Set(NODE_KINDS);
15759
+ var ENG_BANNED = /\b(FUNCTION|STORAGE|EVENT LOG|BALANCES MAPPING|READ|WRITE|EMIT|CALL)\b/;
15760
+ function validate(diag) {
15761
+ const errors = [];
15762
+ const warnings = [];
15763
+ if (diag.lanes.length === 0) {
15764
+ errors.push(
15765
+ `[cdl] empty: lane \u304C 0 \u4EF6\u3067\u3059
15766
+ \u2192 \u4FEE\u6B63\u4F8B: diagram("${diag.id}", { topic }).lane("col1", { x: 0, width: 400 }).node(...).build()`
15767
+ );
15768
+ }
15769
+ if (diag.nodes.length === 0) {
15770
+ errors.push(
15771
+ `[cdl] empty: node \u304C 0 \u4EF6\u3067\u3059
15772
+ \u2192 \u4FEE\u6B63\u4F8B: .node("a", { lane: "col1", stack: 0, kind: "actor", title: "Client" })`
15773
+ );
15774
+ }
15775
+ if (diag.phases.length === 0) {
15776
+ errors.push(
15777
+ `[cdl] empty: phase \u304C 0 \u4EF6\u3067\u3059 (animation \u3092\u99C6\u52D5\u3059\u308B\u305F\u3081\u306E\u6700\u4F4E 1 phase \u304C\u5FC5\u8981)
15778
+ \u2192 \u4FEE\u6B63\u4F8B: .phase("p1", { duration: 1800, title: "step 1", body: "..." }, p => p.activate("a"))`
15779
+ );
15780
+ }
15781
+ const checkDup = (kind, items) => {
15782
+ const seen = /* @__PURE__ */ new Map();
15783
+ items.forEach((item, idx) => {
15784
+ const prev = seen.get(item.id);
15785
+ if (prev !== void 0) {
15786
+ errors.push(`[cdl] duplicate-id: ${kind} "${item.id}" \u304C ${prev} \u4EF6\u76EE + ${idx} \u4EF6\u76EE\u3067\u91CD\u8907`);
15787
+ } else {
15788
+ seen.set(item.id, idx);
15789
+ }
15790
+ });
15791
+ };
15792
+ checkDup("lane", diag.lanes);
15793
+ checkDup("node", diag.nodes);
15794
+ checkDup("edge", diag.edges);
15795
+ checkDup("state", diag.states);
15796
+ checkDup("phase", diag.phases);
15797
+ for (const e of diag.edges) {
15798
+ if (!TONES2.has(e.tone)) {
15799
+ errors.push(`[cdl] unknown-tone: edge "${e.id}" tone "${e.tone}" \u306F\u672A\u5BFE\u5FDC (${[...TONES2].join(" / ")} \u306E\u3044\u305A\u308C\u304B)`);
15800
+ }
15801
+ }
15802
+ for (const n of diag.nodes) {
15803
+ if (!KINDS.has(n.kind)) {
15804
+ errors.push(`[cdl] unknown-kind: node "${n.id}" kind "${n.kind}" \u306F\u672A\u5BFE\u5FDC (${[...KINDS].join(" / ")} \u306E\u3044\u305A\u308C\u304B)`);
15805
+ }
15806
+ if (n.tone !== void 0 && !TONES2.has(n.tone)) {
15807
+ errors.push(`[cdl] unknown-tone: node "${n.id}" tone "${n.tone}" \u306F\u672A\u5BFE\u5FDC (${[...TONES2].join(" / ")} \u306E\u3044\u305A\u308C\u304B)`);
15808
+ }
15809
+ }
15810
+ const laneIds = new Set(diag.lanes.map((l) => l.id));
15811
+ const nodeIds = new Set(diag.nodes.map((n) => n.id));
15812
+ const edgeIds = new Set(diag.edges.map((e) => e.id));
15813
+ const stateIds = new Set(diag.states.map((s) => s.id));
15814
+ const readableIds = /* @__PURE__ */ new Set([...stateIds, ...(diag.derived ?? []).map((d) => d.id)]);
15815
+ const suggest = (target, candidates) => {
15816
+ const list = [...candidates];
15817
+ if (list.length === 0) return "";
15818
+ let best = "";
15819
+ let bestD = Infinity;
15820
+ for (const c of list) {
15821
+ const d = levenshtein(target, c);
15822
+ if (d < bestD) {
15823
+ bestD = d;
15824
+ best = c;
15825
+ }
15826
+ }
15827
+ return bestD <= 2 ? ` (\u3082\u3057\u304B\u3057\u3066 "${best}"?)` : ` (\u5B9A\u7FA9\u6E08 lane: ${list.slice(0, 5).join(", ")}${list.length > 5 ? "..." : ""})`;
15828
+ };
15829
+ for (const n of diag.nodes) {
15830
+ if (!laneIds.has(n.lane)) {
15831
+ errors.push(
15832
+ `[cdl] unknown-ref: node "${n.id}" \u306E lane "${n.lane}" \u304C\u672A\u5B9A\u7FA9\u3067\u3059${suggest(n.lane, laneIds)}
15833
+ \u2192 \u4FEE\u6B63\u4F8B: .lane("${n.lane}", { x: 0, width: 400 }) \u3092 node \u5BA3\u8A00\u524D\u306B\u8FFD\u52A0`
15834
+ );
15835
+ }
15836
+ }
15837
+ for (const e of diag.edges) {
15838
+ if (!nodeIds.has(e.from)) {
15839
+ errors.push(
15840
+ `[cdl] unknown-ref: edge "${e.id}" \u306E from "${e.from}" \u304C node \u306B\u5B58\u5728\u3057\u307E\u305B\u3093${suggest(e.from, nodeIds)}
15841
+ \u2192 \u4FEE\u6B63\u4F8B: .node("${e.from}", { lane: "...", stack: 0, kind: "actor", title: "..." }) \u3092 edge \u5BA3\u8A00\u524D\u306B\u8FFD\u52A0`
15842
+ );
15843
+ }
15844
+ if (!nodeIds.has(e.to)) {
15845
+ errors.push(
15846
+ `[cdl] unknown-ref: edge "${e.id}" \u306E to "${e.to}" \u304C node \u306B\u5B58\u5728\u3057\u307E\u305B\u3093${suggest(e.to, nodeIds)}
15847
+ \u2192 \u4FEE\u6B63\u4F8B: .node("${e.to}", { lane: "...", stack: 0, kind: "...", title: "..." }) \u3092 edge \u5BA3\u8A00\u524D\u306B\u8FFD\u52A0`
15848
+ );
15849
+ }
15850
+ if (e.from === e.to) {
15851
+ errors.push(
15852
+ `[cdl] self-loop: edge "${e.id}" \u306E from \u3068 to \u304C\u540C\u3058 node "${e.from}" \u3067\u3059 (self-loop \u306F\u73FE\u72B6\u672A\u5BFE\u5FDC)
15853
+ \u2192 \u5225 node \u3092\u4F5C\u3063\u3066 2 \u6BB5\u306B\u5206\u3051\u308B\u304B\u3001 \u5F8C\u7D9A PR \u306E self-loop support \u3092\u5F85\u3063\u3066\u304F\u3060\u3055\u3044`
15854
+ );
15855
+ }
15856
+ }
15857
+ for (const p of diag.phases) {
15858
+ for (const id of p.activate) {
15859
+ if (!nodeIds.has(id) && !edgeIds.has(id)) {
15860
+ errors.push(`[cdl] unknown-ref: phase "${p.id}" \u306E activate "${id}" \u306F node \u3067\u3082 edge \u3067\u3082\u306A\u3044 (typo \u306E\u53EF\u80FD\u6027)`);
15861
+ }
15862
+ }
15863
+ for (const t of p.tweens) {
15864
+ if (!stateIds.has(t.stateId)) {
15865
+ errors.push(`[cdl] unknown-ref: phase "${p.id}" tween \u5BFE\u8C61 state "${t.stateId}" \u672A\u5B9A\u7FA9`);
15866
+ }
15867
+ if (typeof t.from === "number" && typeof t.to === "number" && t.from === t.to) {
15868
+ warnings.push(`phase "${p.id}" tween state "${t.stateId}" \u306E from === to (${t.from})\u3001 \u52D5\u304D\u306A\u3057`);
15869
+ }
15870
+ }
15871
+ for (const s of p.sets) {
15872
+ if (!stateIds.has(s.stateId)) {
15873
+ errors.push(`[cdl] unknown-ref: phase "${p.id}" set \u5BFE\u8C61 state "${s.stateId}" \u672A\u5B9A\u7FA9`);
15874
+ }
15875
+ }
15876
+ if (p.duration <= 0) {
15877
+ errors.push(`[cdl] invalid-duration: phase "${p.id}" duration "${p.duration}" \u306F\u6B63\u5024\u5FC5\u9808`);
15878
+ }
15879
+ }
15880
+ for (const n of diag.nodes) {
15881
+ const checks = [];
15882
+ if (n.value) checks.push(n.value);
15883
+ if (n.rows) checks.push(...n.rows);
15884
+ for (const text of checks) {
15885
+ for (const id of templateRefIds(text)) {
15886
+ if (!readableIds.has(id)) {
15887
+ warnings.push(`node "${n.id}" \u304C\u672A\u5B9A\u7FA9 state "${id}" \u3092\u53C2\u7167`);
15888
+ }
15889
+ }
15890
+ }
15891
+ }
15892
+ const allActivated = /* @__PURE__ */ new Set();
15893
+ for (const p of diag.phases) {
15894
+ for (const id of p.activate) allActivated.add(id);
15895
+ }
15896
+ for (const n of diag.nodes) {
15897
+ if (!allActivated.has(n.id)) {
15898
+ warnings.push(`node "${n.id}" \u304C\u3069\u306E phase \u3067\u3082 activate \u3055\u308C\u3066\u3044\u306A\u3044 (unused\u3001 typo \u306E\u53EF\u80FD\u6027)`);
15899
+ }
15900
+ }
15901
+ for (const e of diag.edges) {
15902
+ if (!allActivated.has(e.id)) {
15903
+ warnings.push(`edge "${e.id}" \u304C\u3069\u306E phase \u3067\u3082 activate \u3055\u308C\u3066\u3044\u306A\u3044 (unused\u3001 typo \u306E\u53EF\u80FD\u6027)`);
15904
+ }
15905
+ }
15906
+ const stateUsed = /* @__PURE__ */ new Set();
15907
+ for (const p of diag.phases) {
15908
+ for (const t of p.tweens) stateUsed.add(t.stateId);
15909
+ for (const s of p.sets) stateUsed.add(s.stateId);
15910
+ }
15911
+ for (const n of diag.nodes) {
15912
+ const texts2 = [n.value, ...n.rows ?? []].filter(Boolean);
15913
+ for (const text of texts2) {
15914
+ for (const id of templateRefIds(text)) stateUsed.add(id);
15915
+ }
15916
+ }
15917
+ const derivedById = new Map((diag.derived ?? []).map((dv) => [dv.id, dv]));
15918
+ const queue = [...stateUsed];
15919
+ while (queue.length > 0) {
15920
+ const id = queue.pop();
15921
+ const dv = derivedById.get(id);
15922
+ if (!dv) continue;
15923
+ for (const ref of derivedRefIds(dv.expression)) {
15924
+ if (!stateUsed.has(ref)) {
15925
+ stateUsed.add(ref);
15926
+ queue.push(ref);
15927
+ }
15928
+ }
15929
+ }
15930
+ for (const s of diag.states) {
15931
+ if (!stateUsed.has(s.id)) {
15932
+ warnings.push(`state "${s.id}" \u304C\u3069\u3053\u3067\u3082\u53C2\u7167\u3055\u308C\u3066\u3044\u306A\u3044 (unused)`);
15933
+ }
15934
+ }
15935
+ for (const dv of diag.derived ?? []) {
15936
+ if (!stateUsed.has(dv.id)) {
15937
+ warnings.push(`\u5024 "${dv.id}" \u304C\u3069\u3053\u3067\u3082\u53C2\u7167\u3055\u308C\u3066\u3044\u306A\u3044 (unused)`);
15938
+ }
15939
+ }
15940
+ const laneUsed = /* @__PURE__ */ new Set();
15941
+ for (const n of diag.nodes) laneUsed.add(n.lane);
15942
+ for (const l of diag.lanes) {
15943
+ if (!laneUsed.has(l.id)) {
15944
+ warnings.push(`lane "${l.id}" \u306B\u3069\u306E node \u3082\u914D\u7F6E\u3055\u308C\u3066\u3044\u306A\u3044 (unused)`);
15945
+ }
15946
+ }
15947
+ const texts = [];
15948
+ for (const l of diag.lanes) if (l.label) texts.push({ where: `lane "${l.id}".label`, text: l.label });
15949
+ for (const n of diag.nodes) {
15950
+ texts.push({ where: `node "${n.id}".title`, text: n.title });
15951
+ if (n.eyebrow) texts.push({ where: `node "${n.id}".eyebrow`, text: n.eyebrow });
15952
+ if (n.subtitle) texts.push({ where: `node "${n.id}".subtitle`, text: n.subtitle });
15953
+ }
15954
+ for (const e of diag.edges) {
15955
+ texts.push({ where: `edge "${e.id}".label`, text: e.label });
15956
+ if (e.sub) texts.push({ where: `edge "${e.id}".sub`, text: e.sub });
15957
+ }
15958
+ for (const { where, text } of texts) {
15959
+ if (ENG_BANNED.test(text)) {
15960
+ warnings.push(`${where} \u306B uppercase \u82F1\u5358\u8A9E\u304C\u691C\u51FA: "${text}"`);
15961
+ }
15962
+ }
15963
+ if (warnings.length > 0) {
15964
+ for (const w of warnings) console.warn(`[cdl validate] warn: ${w}`);
15965
+ }
15966
+ if (errors.length > 0) {
15967
+ throw new Error(`cdl validate failed:
15968
+ - ${errors.join("\n - ")}`);
15969
+ }
15970
+ }
15971
+ function levenshtein(a, b) {
15972
+ const m = a.length;
15973
+ const n = b.length;
15974
+ if (m === 0) return n;
15975
+ if (n === 0) return m;
15976
+ const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
15977
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
15978
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
15979
+ for (let i = 1; i <= m; i++) {
15980
+ for (let j = 1; j <= n; j++) {
15981
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
15982
+ dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
15983
+ }
15984
+ }
15985
+ return dp[m][n];
15986
+ }
15987
+
15988
+ // src/compile.ts
15989
+ function compile(diag) {
15990
+ validate(diag);
15991
+ return layout(diag);
15992
+ }
15993
+
15994
+ // src/layout/geometry.ts
15995
+ function pointRectEdgeDistance(px, py, rect) {
15996
+ const dx = Math.max(rect.x - px, 0, px - (rect.x + rect.w));
15997
+ const dy = Math.max(rect.y - py, 0, py - (rect.y + rect.h));
15998
+ return Math.hypot(dx, dy);
15999
+ }
16000
+ function rectRectOverlapArea(a, b) {
16001
+ const dx = Math.min(a.x + a.w, b.x + b.w) - Math.max(a.x, b.x);
16002
+ const dy = Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y);
16003
+ if (dx <= 0 || dy <= 0) return 0;
15344
16004
  return dx * dy;
15345
16005
  }
15346
16006
  function rectRectClearance(a, b) {
@@ -15443,7 +16103,6 @@ var SKIPPED_AXES = {
15443
16103
  var MIN_NODE_W = 80;
15444
16104
  var MIN_NODE_H = 40;
15445
16105
  var ROW_FORMAT = /^[^:]+:\s*.+$/;
15446
- var PLACEHOLDER_RE = /\{(\w+)\}/g;
15447
16106
  function emptyCounts() {
15448
16107
  return {
15449
16108
  "node-visibility": 0,
@@ -15553,7 +16212,7 @@ function blendOver(fg, bg, alpha) {
15553
16212
  return `#${[ch(0), ch(1), ch(2)].map((v) => v.toString(16).padStart(2, "0")).join("")}`;
15554
16213
  }
15555
16214
  function evaluateLabelContrast(opts) {
15556
- const { edges, phases, toneColor: toneColor6, labelBg, inactiveText, textSpec = EDGE_LABEL_TEXT } = opts;
16215
+ const { edges, phases, toneColor: toneColor5, labelBg, inactiveText, textSpec = EDGE_LABEL_TEXT } = opts;
15557
16216
  const isObject = (v) => typeof v === "object" && v !== null;
15558
16217
  const phaseList = (Array.isArray(phases) ? phases : []).filter(
15559
16218
  (p) => isObject(p) && Array.isArray(p.activate)
@@ -15574,7 +16233,7 @@ function evaluateLabelContrast(opts) {
15574
16233
  if (e.sub?.trim()) lines.push("sub");
15575
16234
  const states = [];
15576
16235
  if (everActive.has(e.id)) {
15577
- states.push({ state: "\u6D3B\u6027", fg: toneColor6(e.tone) });
16236
+ states.push({ state: "\u6D3B\u6027", fg: toneColor5(e.tone) });
15578
16237
  }
15579
16238
  if (everInactive(e.id)) states.push({ state: "\u975E\u6D3B\u6027", fg: inactiveText });
15580
16239
  for (const { state, fg } of states) {
@@ -16201,7 +16860,10 @@ function runAxes(laid, diag, violations, counts, push, profile, skippedAxes) {
16201
16860
  }
16202
16861
  }
16203
16862
  const DIVIDER_ONLY = /^[─━—-]+$/;
16204
- const stateIds = new Set(diag.states.map((s) => s.id));
16863
+ const stateIds = /* @__PURE__ */ new Set([
16864
+ ...diag.states.map((s) => s.id),
16865
+ ...(diag.derived ?? []).map((d) => d.id)
16866
+ ]);
16205
16867
  for (const n of diag.nodes) {
16206
16868
  if (!n.rows) continue;
16207
16869
  if (!rendersRows(n.kind)) continue;
@@ -16211,11 +16873,8 @@ function runAxes(laid, diag, violations, counts, push, profile, skippedAxes) {
16211
16873
  if (!ROW_FORMAT.test(row)) {
16212
16874
  push("row-format", `node "${n.id}" row "${row}" \u304C "key: value" \u5F62\u5F0F\u3067\u306A\u3044`);
16213
16875
  }
16214
- let m;
16215
- const re = new RegExp(PLACEHOLDER_RE);
16216
- while ((m = re.exec(row)) !== null) {
16217
- const id = m[1];
16218
- if (id && !stateIds.has(id)) {
16876
+ for (const id of templateRefIds(row)) {
16877
+ if (!stateIds.has(id)) {
16219
16878
  push("row-format", `node "${n.id}" row "${row}" placeholder "{${id}}" \u304C\u672A\u5B9A\u7FA9 state`);
16220
16879
  }
16221
16880
  }
@@ -18586,562 +19245,265 @@ async function verifyNodeText(page, laid, _options) {
18586
19245
  });
18587
19246
  }
18588
19247
  if (n.eyebrow && !n.visibleText.includes(n.eyebrow)) {
18589
- out.push({
18590
- kind: "node-eyebrow-missing",
18591
- diagramId,
18592
- elementId: n.id,
18593
- expected: `eyebrow "${n.eyebrow}" visible`,
18594
- actual: { visibleText: n.visibleText },
18595
- detail: `node "${n.id}" \u306E eyebrow "${n.eyebrow}" \u304C SVG <text> \u306B visible \u3067\u306A\u3044`
18596
- });
18597
- }
18598
- }
18599
- return out;
18600
- }
18601
- async function verifyEdgeLabel(page, laid, _options) {
18602
- const out = [];
18603
- const diagramId = laid.id;
18604
- const res = await page.evaluate(
18605
- ({ diagId }) => {
18606
- const root = document.querySelector(`[data-cdl-diagram="${diagId}"]`);
18607
- if (!root) return { ok: false, reason: "root not found" };
18608
- const edges = Array.from(root.querySelectorAll("[data-cdl-edge]"));
18609
- const labelGroups = Array.from(root.querySelectorAll("[data-cdl-edge-label-for]"));
18610
- const labelMap = /* @__PURE__ */ new Map();
18611
- for (const lg of labelGroups) {
18612
- const eid = lg.getAttribute("data-cdl-edge-label-for") ?? "";
18613
- const t = Array.from(lg.querySelectorAll("text")).map((tx) => tx.textContent ?? "").join(" ");
18614
- labelMap.set(eid, t);
18615
- }
18616
- return {
18617
- ok: true,
18618
- edges: edges.map((e) => ({
18619
- id: e.getAttribute("data-cdl-edge") ?? "",
18620
- label: e.getAttribute("data-cdl-edge-label") ?? "",
18621
- sub: e.getAttribute("data-cdl-edge-sub") ?? "",
18622
- visibleText: labelMap.get(e.getAttribute("data-cdl-edge") ?? "") ?? ""
18623
- }))
18624
- };
18625
- },
18626
- { diagId: diagramId }
18627
- );
18628
- if (!res.ok) return out;
18629
- for (const e of res.edges) {
18630
- if (e.label && !e.visibleText.includes(e.label)) {
18631
- out.push({
18632
- kind: "edge-label-missing",
18633
- diagramId,
18634
- elementId: e.id,
18635
- expected: `label "${e.label}" visible`,
18636
- actual: { visibleText: e.visibleText },
18637
- detail: `edge "${e.id}" \u306E label "${e.label}" \u304C SVG <text> \u306B visible \u3067\u306A\u3044`
18638
- });
18639
- }
18640
- if (e.sub && !e.visibleText.includes(e.sub)) {
18641
- out.push({
18642
- kind: "edge-sub-missing",
18643
- diagramId,
18644
- elementId: e.id,
18645
- expected: `sub "${e.sub}" visible`,
18646
- actual: { visibleText: e.visibleText },
18647
- detail: `edge "${e.id}" \u306E sub "${e.sub}" \u304C SVG <text> \u306B visible \u3067\u306A\u3044`
18648
- });
18649
- }
18650
- }
18651
- return out;
18652
- }
18653
- async function verifyEdgeConnected(page, laid, options) {
18654
- const out = [];
18655
- const tol = options.edgeConnectionTolerance ?? 80;
18656
- const diagramId = laid.id;
18657
- const res = await page.evaluate(
18658
- ({ diagId }) => {
18659
- const root = document.querySelector(`[data-cdl-diagram="${diagId}"]`);
18660
- if (!root) return { ok: false, reason: "root not found" };
18661
- const edges = Array.from(root.querySelectorAll("[data-cdl-edge]"));
18662
- const out2 = [];
18663
- for (const e of edges) {
18664
- const id = e.getAttribute("data-cdl-edge") ?? "";
18665
- const from = e.getAttribute("data-cdl-from") ?? "";
18666
- const to = e.getAttribute("data-cdl-to") ?? "";
18667
- const pathEl = root.querySelector(`#cdl-edge-${id}`);
18668
- if (!pathEl) {
18669
- out2.push({ id, from, to, startView: null, endView: null, fromCenter: null, toCenter: null });
18670
- continue;
18671
- }
18672
- const len = pathEl.getTotalLength();
18673
- const ctm = pathEl.getScreenCTM();
18674
- let startView = null;
18675
- let endView = null;
18676
- if (ctm && isFinite(len)) {
18677
- const sp = pathEl.getPointAtLength(0);
18678
- const ep = pathEl.getPointAtLength(len);
18679
- startView = { x: sp.x * ctm.a + sp.y * ctm.c + ctm.e, y: sp.x * ctm.b + sp.y * ctm.d + ctm.f };
18680
- endView = { x: ep.x * ctm.a + ep.y * ctm.c + ctm.e, y: ep.x * ctm.b + ep.y * ctm.d + ctm.f };
18681
- }
18682
- const fromEl = root.querySelector(`[data-cdl-node="${from}"]`);
18683
- const toEl = root.querySelector(`[data-cdl-node="${to}"]`);
18684
- const fromR = fromEl ? fromEl.getBoundingClientRect() : null;
18685
- const toR = toEl ? toEl.getBoundingClientRect() : null;
18686
- const fromCenter = fromR ? { x: fromR.left + fromR.width / 2, y: fromR.top + fromR.height / 2 } : null;
18687
- const toCenter = toR ? { x: toR.left + toR.width / 2, y: toR.top + toR.height / 2 } : null;
18688
- out2.push({ id, from, to, startView, endView, fromCenter, toCenter });
18689
- }
18690
- return { ok: true, edges: out2 };
18691
- },
18692
- { diagId: diagramId }
18693
- );
18694
- if (!res.ok) return out;
18695
- for (const e of res.edges) {
18696
- if (e.startView && e.fromCenter) {
18697
- const d = Math.hypot(e.startView.x - e.fromCenter.x, e.startView.y - e.fromCenter.y);
18698
- const nodeRadius = 200;
18699
- if (d > nodeRadius + tol) {
18700
- out.push({
18701
- kind: "edge-disconnected-from-source",
18702
- diagramId,
18703
- elementId: e.id,
18704
- expected: `from node "${e.from}" center \u307E\u3067 ${nodeRadius + tol}px \u4EE5\u5185`,
18705
- actual: { dist: d.toFixed(1) },
18706
- detail: `edge "${e.id}" \u306E path \u8D77\u70B9\u304C from node "${e.from}" \u304B\u3089 ${d.toFixed(1)}px \u96E2\u308C\u3066\u3044\u308B`
18707
- });
18708
- }
18709
- }
18710
- if (e.endView && e.toCenter) {
18711
- const d = Math.hypot(e.endView.x - e.toCenter.x, e.endView.y - e.toCenter.y);
18712
- const nodeRadius = 200;
18713
- if (d > nodeRadius + tol) {
18714
- out.push({
18715
- kind: "edge-disconnected-from-target",
18716
- diagramId,
18717
- elementId: e.id,
18718
- expected: `to node "${e.to}" center \u307E\u3067 ${nodeRadius + tol}px \u4EE5\u5185`,
18719
- actual: { dist: d.toFixed(1) },
18720
- detail: `edge "${e.id}" \u306E path \u7D42\u70B9\u304C to node "${e.to}" \u304B\u3089 ${d.toFixed(1)}px \u96E2\u308C\u3066\u3044\u308B`
18721
- });
18722
- }
18723
- }
18724
- }
18725
- return out;
18726
- }
18727
- async function verifyActivationVisible(page, diagram2, _options) {
18728
- const out = [];
18729
- const diagramId = diagram2.id;
18730
- if (diagram2.phases.length === 0) return out;
18731
- const res = await page.evaluate(
18732
- ({ diagId }) => {
18733
- const root = document.querySelector(`[data-cdl-diagram="${diagId}"]`);
18734
- if (!root) return { ok: false, reason: "root not found" };
18735
- const idxAttr = root.getAttribute("data-cdl-phase-index");
18736
- const phaseIdx = idxAttr === null ? -1 : parseInt(idxAttr, 10);
18737
- const activeNodes = Array.from(root.querySelectorAll('[data-cdl-node][data-cdl-active="true"]')).map((el) => el.getAttribute("data-cdl-node") ?? "");
18738
- const activeEdges = Array.from(root.querySelectorAll('[data-cdl-edge][data-cdl-active="true"]')).map((el) => el.getAttribute("data-cdl-edge") ?? "");
18739
- return { ok: true, phaseIdx, activeIds: [...activeNodes, ...activeEdges] };
18740
- },
18741
- { diagId: diagramId }
18742
- );
18743
- if (!res.ok || res.phaseIdx < 0 || res.phaseIdx >= diagram2.phases.length) return out;
18744
- const phase = diagram2.phases[res.phaseIdx];
18745
- const actualSet = new Set(res.activeIds);
18746
- for (const id of phase.activate) {
18747
- if (!actualSet.has(id)) {
18748
- out.push({
18749
- kind: "activation-not-visible",
18750
- diagramId,
18751
- elementId: id,
18752
- expected: `phase "${phase.id}" \u3067 active \u5F37\u8ABF\u8868\u793A`,
18753
- actual: "inactive",
18754
- detail: `phase "${phase.id}" \u3067 .activate("${id}") \u5BA3\u8A00\u3055\u308C\u3066\u3044\u308B\u304C\u3001 \u753B\u9762\u4E0A\u3067 active \u5F37\u8ABF (data-cdl-active=true) \u3055\u308C\u3066\u3044\u306A\u3044`
19248
+ out.push({
19249
+ kind: "node-eyebrow-missing",
19250
+ diagramId,
19251
+ elementId: n.id,
19252
+ expected: `eyebrow "${n.eyebrow}" visible`,
19253
+ actual: { visibleText: n.visibleText },
19254
+ detail: `node "${n.id}" \u306E eyebrow "${n.eyebrow}" \u304C SVG <text> \u306B visible \u3067\u306A\u3044`
18755
19255
  });
18756
19256
  }
18757
19257
  }
18758
19258
  return out;
18759
19259
  }
18760
- async function verifyToneColor(page, laid, options) {
19260
+ async function verifyEdgeLabel(page, laid, _options) {
18761
19261
  const out = [];
18762
- const tol = options.toneColorTolerance ?? 30;
18763
19262
  const diagramId = laid.id;
18764
19263
  const res = await page.evaluate(
18765
19264
  ({ diagId }) => {
18766
19265
  const root = document.querySelector(`[data-cdl-diagram="${diagId}"]`);
18767
19266
  if (!root) return { ok: false, reason: "root not found" };
18768
19267
  const edges = Array.from(root.querySelectorAll("[data-cdl-edge]"));
18769
- const out2 = [];
18770
- for (const e of edges) {
18771
- const id = e.getAttribute("data-cdl-edge") ?? "";
18772
- const tone = e.getAttribute("data-cdl-tone") ?? "";
18773
- const active = e.getAttribute("data-cdl-active") === "true";
18774
- const paths = Array.from(e.querySelectorAll("path"));
18775
- let strokeColor = "";
18776
- for (const p of paths) {
18777
- const s = p.getAttribute("stroke");
18778
- if (s && s !== "none" && s !== "transparent") {
18779
- strokeColor = s;
18780
- break;
18781
- }
18782
- const cs = window.getComputedStyle(p).stroke;
18783
- if (cs && cs !== "none") {
18784
- strokeColor = cs;
18785
- break;
18786
- }
18787
- }
18788
- out2.push({ id, tone, active, strokeColor });
19268
+ const labelGroups = Array.from(root.querySelectorAll("[data-cdl-edge-label-for]"));
19269
+ const labelMap = /* @__PURE__ */ new Map();
19270
+ for (const lg of labelGroups) {
19271
+ const eid = lg.getAttribute("data-cdl-edge-label-for") ?? "";
19272
+ const t = Array.from(lg.querySelectorAll("text")).map((tx) => tx.textContent ?? "").join(" ");
19273
+ labelMap.set(eid, t);
18789
19274
  }
18790
- return { ok: true, edges: out2 };
19275
+ return {
19276
+ ok: true,
19277
+ edges: edges.map((e) => ({
19278
+ id: e.getAttribute("data-cdl-edge") ?? "",
19279
+ label: e.getAttribute("data-cdl-edge-label") ?? "",
19280
+ sub: e.getAttribute("data-cdl-edge-sub") ?? "",
19281
+ visibleText: labelMap.get(e.getAttribute("data-cdl-edge") ?? "") ?? ""
19282
+ }))
19283
+ };
18791
19284
  },
18792
19285
  { diagId: diagramId }
18793
19286
  );
18794
19287
  if (!res.ok) return out;
18795
19288
  for (const e of res.edges) {
18796
- if (!e.active) continue;
18797
- const expected = TONE_COLOR_MAP[e.tone];
18798
- if (!expected) continue;
18799
- const actualRgb = parseColorToRgb(e.strokeColor);
18800
- const expectedRgb = parseColorToRgb(expected);
18801
- if (!actualRgb || !expectedRgb) continue;
18802
- const dist = rgbDistance(actualRgb, expectedRgb);
18803
- if (dist > tol) {
18804
- out.push({
18805
- kind: "tone-color-mismatch",
18806
- diagramId,
18807
- elementId: e.id,
18808
- expected: `tone="${e.tone}" \u2192 ${expected}`,
18809
- actual: { strokeColor: e.strokeColor, rgbDist: dist.toFixed(1) },
18810
- detail: `edge "${e.id}" \u306E active stroke \u8272\u304C tone="${e.tone}" \u306E\u671F\u5F85\u5024 ${expected} \u3068 RGB \u8DDD\u96E2 ${dist.toFixed(1)} > tolerance ${tol}`
18811
- });
18812
- }
18813
- }
18814
- return out;
18815
- }
18816
- function parseColorToRgb(c) {
18817
- if (!c) return null;
18818
- if (c.startsWith("#")) {
18819
- const hex = c.slice(1);
18820
- if (hex.length === 3) {
18821
- const r = parseInt(hex[0] + hex[0], 16);
18822
- const g = parseInt(hex[1] + hex[1], 16);
18823
- const b = parseInt(hex[2] + hex[2], 16);
18824
- return [r, g, b];
18825
- }
18826
- if (hex.length === 6) {
18827
- return [parseInt(hex.slice(0, 2), 16), parseInt(hex.slice(2, 4), 16), parseInt(hex.slice(4, 6), 16)];
18828
- }
18829
- return null;
18830
- }
18831
- const m = c.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
18832
- if (m) return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)];
18833
- return null;
18834
- }
18835
- function rgbDistance(a, b) {
18836
- return Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
18837
- }
18838
- async function verifyAuthorIntentAll(page, diagrams, options) {
18839
- const all = [];
18840
- let fail = 0;
18841
- for (const d of diagrams) {
18842
- const ds = await verifyAuthorIntent(page, d, options);
18843
- if (ds.length > 0) fail++;
18844
- all.push(...ds);
18845
- }
18846
- return { total: diagrams.length, pass: diagrams.length - fail, fail, discrepancies: all };
18847
- }
18848
-
18849
- // src/anim/core/easing.ts
18850
- function cubicBezier(x1, y1, x2, y2) {
18851
- const A = (a, b) => 1 - 3 * b + 3 * a;
18852
- const B = (a, b) => 3 * b - 6 * a;
18853
- const C = (a) => 3 * a;
18854
- const sampleCurveX = (t) => ((A(x1, x2) * t + B(x1, x2)) * t + C(x1)) * t;
18855
- const sampleCurveY = (t) => ((A(y1, y2) * t + B(y1, y2)) * t + C(y1)) * t;
18856
- const sampleDerivativeX = (t) => 3 * A(x1, x2) * t * t + 2 * B(x1, x2) * t + C(x1);
18857
- function solveCurveX(x) {
18858
- if (x <= 0) return 0;
18859
- if (x >= 1) return 1;
18860
- let t = x;
18861
- for (let i = 0; i < 8; i += 1) {
18862
- const currentX = sampleCurveX(t) - x;
18863
- if (Math.abs(currentX) < 1e-5) return t;
18864
- const currentSlope = sampleDerivativeX(t);
18865
- if (Math.abs(currentSlope) < 1e-5) break;
18866
- t -= currentX / currentSlope;
18867
- }
18868
- let lo = 0;
18869
- let hi = 1;
18870
- let guess = x;
18871
- while (lo < hi) {
18872
- const currentX = sampleCurveX(guess);
18873
- if (Math.abs(currentX - x) < 1e-5) return guess;
18874
- if (x > currentX) lo = guess;
18875
- else hi = guess;
18876
- guess = (hi - lo) / 2 + lo;
18877
- }
18878
- return guess;
18879
- }
18880
- return (t) => {
18881
- if (t <= 0) return 0;
18882
- if (t >= 1) return 1;
18883
- return sampleCurveY(solveCurveX(t));
18884
- };
18885
- }
18886
- var easeOutQuint = cubicBezier(0.22, 1, 0.36, 1);
18887
- function clamp01(value) {
18888
- if (value < 0) return 0;
18889
- if (value > 1) return 1;
18890
- return value;
18891
- }
18892
- function lerp(start, end, t) {
18893
- return start + (end - start) * t;
18894
- }
18895
-
18896
- // src/anim/core/timeline.ts
18897
- var Timeline = class {
18898
- phases;
18899
- easing;
18900
- loop;
18901
- autoAdvanceMsOverride;
18902
- phaseHoldMs;
18903
- loopRestartDelayMs;
18904
- autoplay;
18905
- subscribers = /* @__PURE__ */ new Set();
18906
- holdTimerHandle = null;
18907
- rafHandle = null;
18908
- phaseIndex = 0;
18909
- phaseProgress = 0;
18910
- phaseStartedAt = 0;
18911
- status = "idle";
18912
- /**
18913
- * snapshot を「state が変わった時のみ」 新 object として返すためのキャッシュ。
18914
- * useSyncExternalStore は getSnapshot() の戻り値が前回と参照同値の時に
18915
- * re-render を skip する設計なので、 これがないと毎 frame 別 object で
18916
- * 無限 render → React #185 になる。
18917
- */
18918
- cachedSnapshot = null;
18919
- /**
18920
- * constructor 直後の state snapshot。 destroy() で復元して StrictMode 二重 mount 経路の
18921
- * autostart() が正しく初期状態から再開できるようにする (initialPhaseIndex + autoplay 組合せ保持)。
18922
- */
18923
- initialState;
18924
- constructor(phases, options = {}) {
18925
- if (phases.length === 0) {
18926
- throw new Error("Timeline requires at least one phase");
18927
- }
18928
- this.phases = phases;
18929
- this.easing = options.easing ?? easeOutQuint;
18930
- this.loop = options.loop ?? false;
18931
- this.autoAdvanceMsOverride = options.autoAdvanceMs;
18932
- this.phaseHoldMs = options.phaseHoldMs ?? 0;
18933
- this.loopRestartDelayMs = options.loopRestartDelayMs ?? this.phaseHoldMs;
18934
- this.autoplay = options.autoplay ?? false;
18935
- const initial = options.initialPhaseIndex;
18936
- if (initial != null && initial > 0 && initial < phases.length) {
18937
- this.phaseIndex = initial;
18938
- this.phaseProgress = 1;
18939
- this.status = initial === phases.length - 1 ? "settled" : "paused";
18940
- }
18941
- this.initialState = {
18942
- phaseIndex: this.phaseIndex,
18943
- phaseProgress: this.phaseProgress,
18944
- status: this.status
18945
- };
18946
- }
18947
- /**
18948
- * mount 後に呼んで autoplay opt-in が真なら play を発火する。
18949
- * constructor で呼ぶと SSR 環境で rAF が動いて失敗するので別経路に分離。
18950
- */
18951
- autostart() {
18952
- if (this.autoplay && this.status === "idle") {
18953
- this.play();
18954
- }
18955
- }
18956
- // ─── public API ──────────────────────────────────────────────
18957
- subscribe(subscriber) {
18958
- this.subscribers.add(subscriber);
18959
- return () => {
18960
- this.subscribers.delete(subscriber);
18961
- };
18962
- }
18963
- snapshot() {
18964
- const cached = this.cachedSnapshot;
18965
- if (cached && cached.status === this.status && cached.phaseIndex === this.phaseIndex && cached.phaseProgress === this.phaseProgress && cached.totalPhases === this.phases.length) {
18966
- return cached;
18967
- }
18968
- const next = {
18969
- status: this.status,
18970
- phaseIndex: this.phaseIndex,
18971
- phaseProgress: this.phaseProgress,
18972
- totalPhases: this.phases.length
18973
- };
18974
- this.cachedSnapshot = next;
18975
- return next;
18976
- }
18977
- getPhase() {
18978
- const phase = this.phases[this.phaseIndex];
18979
- if (!phase) {
18980
- throw new Error(`Invalid phase index ${this.phaseIndex}`);
18981
- }
18982
- return phase;
18983
- }
18984
- /** Phase ID 指定でジャンプ (見つからない場合は no-op) */
18985
- gotoId(id) {
18986
- const index = this.phases.findIndex((phase) => phase.id === id);
18987
- if (index >= 0) this.goto(index);
18988
- }
18989
- /** Phase index 指定でジャンプ */
18990
- goto(index) {
18991
- const clamped = Math.max(0, Math.min(this.phases.length - 1, index));
18992
- this.cancelRaf();
18993
- this.phaseIndex = clamped;
18994
- this.phaseProgress = 1;
18995
- this.status = clamped === this.phases.length - 1 ? "settled" : "paused";
18996
- this.emit();
18997
- }
18998
- play() {
18999
- if (this.status === "playing") return;
19000
- this.status = "playing";
19001
- if (this.phaseProgress >= 1 && this.phaseIndex < this.phases.length - 1) {
19002
- this.advancePhase();
19003
- } else {
19004
- this.beginPhaseTransition();
19289
+ if (e.label && !e.visibleText.includes(e.label)) {
19290
+ out.push({
19291
+ kind: "edge-label-missing",
19292
+ diagramId,
19293
+ elementId: e.id,
19294
+ expected: `label "${e.label}" visible`,
19295
+ actual: { visibleText: e.visibleText },
19296
+ detail: `edge "${e.id}" \u306E label "${e.label}" \u304C SVG <text> \u306B visible \u3067\u306A\u3044`
19297
+ });
19298
+ }
19299
+ if (e.sub && !e.visibleText.includes(e.sub)) {
19300
+ out.push({
19301
+ kind: "edge-sub-missing",
19302
+ diagramId,
19303
+ elementId: e.id,
19304
+ expected: `sub "${e.sub}" visible`,
19305
+ actual: { visibleText: e.visibleText },
19306
+ detail: `edge "${e.id}" \u306E sub "${e.sub}" \u304C SVG <text> \u306B visible \u3067\u306A\u3044`
19307
+ });
19005
19308
  }
19006
- this.emit();
19007
- }
19008
- pause() {
19009
- if (this.status !== "playing") return;
19010
- this.cancelRaf();
19011
- this.status = "paused";
19012
- this.emit();
19013
19309
  }
19014
- next() {
19015
- if (this.phaseIndex >= this.phases.length - 1) {
19016
- if (this.loop) {
19017
- this.goto(0);
19018
- this.play();
19310
+ return out;
19311
+ }
19312
+ async function verifyEdgeConnected(page, laid, options) {
19313
+ const out = [];
19314
+ const tol = options.edgeConnectionTolerance ?? 80;
19315
+ const diagramId = laid.id;
19316
+ const res = await page.evaluate(
19317
+ ({ diagId }) => {
19318
+ const root = document.querySelector(`[data-cdl-diagram="${diagId}"]`);
19319
+ if (!root) return { ok: false, reason: "root not found" };
19320
+ const edges = Array.from(root.querySelectorAll("[data-cdl-edge]"));
19321
+ const out2 = [];
19322
+ for (const e of edges) {
19323
+ const id = e.getAttribute("data-cdl-edge") ?? "";
19324
+ const from = e.getAttribute("data-cdl-from") ?? "";
19325
+ const to = e.getAttribute("data-cdl-to") ?? "";
19326
+ const pathEl = root.querySelector(`#cdl-edge-${id}`);
19327
+ if (!pathEl) {
19328
+ out2.push({ id, from, to, startView: null, endView: null, fromCenter: null, toCenter: null });
19329
+ continue;
19330
+ }
19331
+ const len = pathEl.getTotalLength();
19332
+ const ctm = pathEl.getScreenCTM();
19333
+ let startView = null;
19334
+ let endView = null;
19335
+ if (ctm && isFinite(len)) {
19336
+ const sp = pathEl.getPointAtLength(0);
19337
+ const ep = pathEl.getPointAtLength(len);
19338
+ startView = { x: sp.x * ctm.a + sp.y * ctm.c + ctm.e, y: sp.x * ctm.b + sp.y * ctm.d + ctm.f };
19339
+ endView = { x: ep.x * ctm.a + ep.y * ctm.c + ctm.e, y: ep.x * ctm.b + ep.y * ctm.d + ctm.f };
19340
+ }
19341
+ const fromEl = root.querySelector(`[data-cdl-node="${from}"]`);
19342
+ const toEl = root.querySelector(`[data-cdl-node="${to}"]`);
19343
+ const fromR = fromEl ? fromEl.getBoundingClientRect() : null;
19344
+ const toR = toEl ? toEl.getBoundingClientRect() : null;
19345
+ const fromCenter = fromR ? { x: fromR.left + fromR.width / 2, y: fromR.top + fromR.height / 2 } : null;
19346
+ const toCenter = toR ? { x: toR.left + toR.width / 2, y: toR.top + toR.height / 2 } : null;
19347
+ out2.push({ id, from, to, startView, endView, fromCenter, toCenter });
19348
+ }
19349
+ return { ok: true, edges: out2 };
19350
+ },
19351
+ { diagId: diagramId }
19352
+ );
19353
+ if (!res.ok) return out;
19354
+ for (const e of res.edges) {
19355
+ if (e.startView && e.fromCenter) {
19356
+ const d = Math.hypot(e.startView.x - e.fromCenter.x, e.startView.y - e.fromCenter.y);
19357
+ const nodeRadius = 200;
19358
+ if (d > nodeRadius + tol) {
19359
+ out.push({
19360
+ kind: "edge-disconnected-from-source",
19361
+ diagramId,
19362
+ elementId: e.id,
19363
+ expected: `from node "${e.from}" center \u307E\u3067 ${nodeRadius + tol}px \u4EE5\u5185`,
19364
+ actual: { dist: d.toFixed(1) },
19365
+ detail: `edge "${e.id}" \u306E path \u8D77\u70B9\u304C from node "${e.from}" \u304B\u3089 ${d.toFixed(1)}px \u96E2\u308C\u3066\u3044\u308B`
19366
+ });
19019
19367
  }
19020
- return;
19021
- }
19022
- this.cancelRaf();
19023
- this.phaseIndex += 1;
19024
- this.beginPhaseTransition();
19025
- if (this.status !== "playing") {
19026
- this.status = "paused";
19027
19368
  }
19028
- this.emit();
19029
- }
19030
- prev() {
19031
- if (this.phaseIndex <= 0) return;
19032
- this.cancelRaf();
19033
- this.phaseIndex -= 1;
19034
- this.phaseProgress = 1;
19035
- this.status = "paused";
19036
- this.emit();
19037
- }
19038
- reset() {
19039
- this.cancelRaf();
19040
- this.phaseIndex = 0;
19041
- this.phaseProgress = 0;
19042
- this.status = "idle";
19043
- this.emit();
19044
- }
19045
- /**
19046
- * cleanup + reset for-remount。 subscribers.clear() + cancelRaf() で active resource を
19047
- * 全解放した後、 (phaseIndex, phaseProgress, status) を constructor 直後の initialState に
19048
- * 復元する。 API 名は "destroy" だが object dead / GC-ready 契約ではなく、 destroy 後も
19049
- * subscribe / autostart / snapshot / play が正しく動作する reusable reset object として設計。
19050
- *
19051
- * 主 caller = React `useEffect` の cleanup で、 StrictMode の dev 二重 mount 経路で
19052
- * setup 再実行時に autostart() が正しく再発火するよう status を initialState に戻す。
19053
- *
19054
- * 詳細 = active edge が pathSubpath(d, 0) = "M x y" のみで空 line 描画される
19055
- * regression (dragon Issue #381) を防ぐため、 destroy() は「破棄」 でなく「再利用可能な reset」。
19056
- */
19057
- destroy() {
19058
- this.cancelRaf();
19059
- this.subscribers.clear();
19060
- this.status = this.initialState.status;
19061
- this.phaseIndex = this.initialState.phaseIndex;
19062
- this.phaseProgress = this.initialState.phaseProgress;
19063
- }
19064
- // ─── internals ───────────────────────────────────────────────
19065
- advancePhase() {
19066
- if (this.phaseIndex >= this.phases.length - 1) {
19067
- if (this.loop) {
19068
- this.scheduleHold(this.loopRestartDelayMs, () => {
19069
- this.phaseIndex = 0;
19070
- this.beginPhaseTransition();
19369
+ if (e.endView && e.toCenter) {
19370
+ const d = Math.hypot(e.endView.x - e.toCenter.x, e.endView.y - e.toCenter.y);
19371
+ const nodeRadius = 200;
19372
+ if (d > nodeRadius + tol) {
19373
+ out.push({
19374
+ kind: "edge-disconnected-from-target",
19375
+ diagramId,
19376
+ elementId: e.id,
19377
+ expected: `to node "${e.to}" center \u307E\u3067 ${nodeRadius + tol}px \u4EE5\u5185`,
19378
+ actual: { dist: d.toFixed(1) },
19379
+ detail: `edge "${e.id}" \u306E path \u7D42\u70B9\u304C to node "${e.to}" \u304B\u3089 ${d.toFixed(1)}px \u96E2\u308C\u3066\u3044\u308B`
19071
19380
  });
19072
- } else {
19073
- this.status = "settled";
19074
- this.emit();
19075
19381
  }
19076
- return;
19077
19382
  }
19078
- this.scheduleHold(this.phaseHoldMs, () => {
19079
- this.phaseIndex += 1;
19080
- this.beginPhaseTransition();
19081
- });
19082
19383
  }
19083
- scheduleHold(ms, after) {
19084
- if (this.holdTimerHandle != null) {
19085
- clearTimeout(this.holdTimerHandle);
19086
- this.holdTimerHandle = null;
19087
- }
19088
- if (ms <= 0) {
19089
- after();
19090
- return;
19384
+ return out;
19385
+ }
19386
+ async function verifyActivationVisible(page, diagram2, _options) {
19387
+ const out = [];
19388
+ const diagramId = diagram2.id;
19389
+ if (diagram2.phases.length === 0) return out;
19390
+ const res = await page.evaluate(
19391
+ ({ diagId }) => {
19392
+ const root = document.querySelector(`[data-cdl-diagram="${diagId}"]`);
19393
+ if (!root) return { ok: false, reason: "root not found" };
19394
+ const idxAttr = root.getAttribute("data-cdl-phase-index");
19395
+ const phaseIdx = idxAttr === null ? -1 : parseInt(idxAttr, 10);
19396
+ const activeNodes = Array.from(root.querySelectorAll('[data-cdl-node][data-cdl-active="true"]')).map((el) => el.getAttribute("data-cdl-node") ?? "");
19397
+ const activeEdges = Array.from(root.querySelectorAll('[data-cdl-edge][data-cdl-active="true"]')).map((el) => el.getAttribute("data-cdl-edge") ?? "");
19398
+ return { ok: true, phaseIdx, activeIds: [...activeNodes, ...activeEdges] };
19399
+ },
19400
+ { diagId: diagramId }
19401
+ );
19402
+ if (!res.ok || res.phaseIdx < 0 || res.phaseIdx >= diagram2.phases.length) return out;
19403
+ const phase = diagram2.phases[res.phaseIdx];
19404
+ const actualSet = new Set(res.activeIds);
19405
+ for (const id of phase.activate) {
19406
+ if (!actualSet.has(id)) {
19407
+ out.push({
19408
+ kind: "activation-not-visible",
19409
+ diagramId,
19410
+ elementId: id,
19411
+ expected: `phase "${phase.id}" \u3067 active \u5F37\u8ABF\u8868\u793A`,
19412
+ actual: "inactive",
19413
+ detail: `phase "${phase.id}" \u3067 .activate("${id}") \u5BA3\u8A00\u3055\u308C\u3066\u3044\u308B\u304C\u3001 \u753B\u9762\u4E0A\u3067 active \u5F37\u8ABF (data-cdl-active=true) \u3055\u308C\u3066\u3044\u306A\u3044`
19414
+ });
19091
19415
  }
19092
- this.holdTimerHandle = setTimeout(() => {
19093
- this.holdTimerHandle = null;
19094
- if (this.status === "playing") after();
19095
- }, ms);
19096
19416
  }
19097
- beginPhaseTransition() {
19098
- this.phaseProgress = 0;
19099
- this.phaseStartedAt = performance.now();
19100
- this.cancelRaf();
19101
- const step = (now) => {
19102
- const duration = this.autoAdvanceMsOverride ?? this.getPhase().durationMs ?? 600;
19103
- const elapsed = now - this.phaseStartedAt;
19104
- const linearT = clamp01(elapsed / duration);
19105
- this.phaseProgress = this.easing(linearT);
19106
- this.emit();
19107
- if (linearT < 1) {
19108
- this.rafHandle = requestAnimationFrame(step);
19109
- return;
19110
- }
19111
- this.phaseProgress = 1;
19112
- this.emit();
19113
- if (this.status === "playing") {
19114
- if (this.phaseIndex < this.phases.length - 1) {
19115
- this.advancePhase();
19116
- } else if (this.loop) {
19117
- this.advancePhase();
19118
- } else {
19119
- this.status = "settled";
19120
- this.emit();
19417
+ return out;
19418
+ }
19419
+ async function verifyToneColor(page, laid, options) {
19420
+ const out = [];
19421
+ const tol = options.toneColorTolerance ?? 30;
19422
+ const diagramId = laid.id;
19423
+ const res = await page.evaluate(
19424
+ ({ diagId }) => {
19425
+ const root = document.querySelector(`[data-cdl-diagram="${diagId}"]`);
19426
+ if (!root) return { ok: false, reason: "root not found" };
19427
+ const edges = Array.from(root.querySelectorAll("[data-cdl-edge]"));
19428
+ const out2 = [];
19429
+ for (const e of edges) {
19430
+ const id = e.getAttribute("data-cdl-edge") ?? "";
19431
+ const tone = e.getAttribute("data-cdl-tone") ?? "";
19432
+ const active = e.getAttribute("data-cdl-active") === "true";
19433
+ const paths = Array.from(e.querySelectorAll("path"));
19434
+ let strokeColor = "";
19435
+ for (const p of paths) {
19436
+ const s = p.getAttribute("stroke");
19437
+ if (s && s !== "none" && s !== "transparent") {
19438
+ strokeColor = s;
19439
+ break;
19440
+ }
19441
+ const cs = window.getComputedStyle(p).stroke;
19442
+ if (cs && cs !== "none") {
19443
+ strokeColor = cs;
19444
+ break;
19445
+ }
19121
19446
  }
19447
+ out2.push({ id, tone, active, strokeColor });
19122
19448
  }
19123
- };
19124
- this.rafHandle = requestAnimationFrame(step);
19449
+ return { ok: true, edges: out2 };
19450
+ },
19451
+ { diagId: diagramId }
19452
+ );
19453
+ if (!res.ok) return out;
19454
+ for (const e of res.edges) {
19455
+ if (!e.active) continue;
19456
+ const expected = TONE_COLOR_MAP[e.tone];
19457
+ if (!expected) continue;
19458
+ const actualRgb = parseColorToRgb(e.strokeColor);
19459
+ const expectedRgb = parseColorToRgb(expected);
19460
+ if (!actualRgb || !expectedRgb) continue;
19461
+ const dist = rgbDistance(actualRgb, expectedRgb);
19462
+ if (dist > tol) {
19463
+ out.push({
19464
+ kind: "tone-color-mismatch",
19465
+ diagramId,
19466
+ elementId: e.id,
19467
+ expected: `tone="${e.tone}" \u2192 ${expected}`,
19468
+ actual: { strokeColor: e.strokeColor, rgbDist: dist.toFixed(1) },
19469
+ detail: `edge "${e.id}" \u306E active stroke \u8272\u304C tone="${e.tone}" \u306E\u671F\u5F85\u5024 ${expected} \u3068 RGB \u8DDD\u96E2 ${dist.toFixed(1)} > tolerance ${tol}`
19470
+ });
19471
+ }
19125
19472
  }
19126
- cancelRaf() {
19127
- if (this.rafHandle != null) {
19128
- cancelAnimationFrame(this.rafHandle);
19129
- this.rafHandle = null;
19473
+ return out;
19474
+ }
19475
+ function parseColorToRgb(c) {
19476
+ if (!c) return null;
19477
+ if (c.startsWith("#")) {
19478
+ const hex = c.slice(1);
19479
+ if (hex.length === 3) {
19480
+ const r = parseInt(hex[0] + hex[0], 16);
19481
+ const g = parseInt(hex[1] + hex[1], 16);
19482
+ const b = parseInt(hex[2] + hex[2], 16);
19483
+ return [r, g, b];
19130
19484
  }
19131
- if (this.holdTimerHandle != null) {
19132
- clearTimeout(this.holdTimerHandle);
19133
- this.holdTimerHandle = null;
19485
+ if (hex.length === 6) {
19486
+ return [parseInt(hex.slice(0, 2), 16), parseInt(hex.slice(2, 4), 16), parseInt(hex.slice(4, 6), 16)];
19134
19487
  }
19488
+ return null;
19135
19489
  }
19136
- emit() {
19137
- const snapshot = this.snapshot();
19138
- for (const subscriber of this.subscribers) {
19139
- subscriber(snapshot);
19140
- }
19490
+ const m = c.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
19491
+ if (m) return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)];
19492
+ return null;
19493
+ }
19494
+ function rgbDistance(a, b) {
19495
+ return Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
19496
+ }
19497
+ async function verifyAuthorIntentAll(page, diagrams, options) {
19498
+ const all = [];
19499
+ let fail = 0;
19500
+ for (const d of diagrams) {
19501
+ const ds = await verifyAuthorIntent(page, d, options);
19502
+ if (ds.length > 0) fail++;
19503
+ all.push(...ds);
19141
19504
  }
19142
- };
19143
-
19144
- // src/anim/react/useTimeline.ts
19505
+ return { total: diagrams.length, pass: diagrams.length - fail, fail, discrepancies: all };
19506
+ }
19145
19507
  function useTimeline(phases, options = {}) {
19146
19508
  const optionsRef = react.useRef(options);
19147
19509
  optionsRef.current = options;
@@ -19310,179 +19672,6 @@ function StateDiffCard({
19310
19672
  }
19311
19673
  );
19312
19674
  }
19313
-
19314
- // src/render/utils.ts
19315
- function computeStateValues(laid, phaseIdx, progress) {
19316
- const values = {};
19317
- for (const s of laid.states) values[s.id] = s.initial;
19318
- for (let i = 0; i < laid.phases.length; i++) {
19319
- const phase = laid.phases[i];
19320
- if (!phase) continue;
19321
- if (i <= phaseIdx) {
19322
- for (const s of phase.sets) {
19323
- values[s.stateId] = s.value;
19324
- }
19325
- }
19326
- for (const t of phase.tweens) {
19327
- if (i < phaseIdx) {
19328
- values[t.stateId] = t.to;
19329
- } else if (i === phaseIdx) {
19330
- values[t.stateId] = Math.round(lerp(t.from, t.to, progress));
19331
- }
19332
- }
19333
- }
19334
- const out = {};
19335
- for (const [k, v] of Object.entries(values)) out[k] = String(v);
19336
- return out;
19337
- }
19338
- function interpolate(template, values) {
19339
- return template.replace(/\{(\w+)(\.length|\.sum|\.max|\.min|\.avg|\[\d+\])?\}/g, (_, id, accessor) => {
19340
- const raw = values[id];
19341
- if (raw === void 0) return `{${id}${accessor ?? ""}}`;
19342
- if (!accessor) return raw;
19343
- const arr = parseArraySignal(raw);
19344
- if (!arr) return `{${id}${accessor}}`;
19345
- if (accessor === ".length") return String(arr.length);
19346
- const nums = arr.map((v) => Number.isFinite(Number(v)) ? Number(v) : 0);
19347
- if (accessor === ".sum") return String(nums.reduce((s, v) => s + v, 0));
19348
- if (accessor === ".max") return nums.length ? String(nums.reduce((a, b) => a > b ? a : b, nums[0])) : "0";
19349
- if (accessor === ".min") return nums.length ? String(nums.reduce((a, b) => a < b ? a : b, nums[0])) : "0";
19350
- if (accessor === ".avg") {
19351
- if (!nums.length) return "0";
19352
- const sum = nums.reduce((s, v) => s + v, 0);
19353
- return String(Math.round(sum / nums.length * 100) / 100);
19354
- }
19355
- if (accessor.startsWith("[")) {
19356
- const idx = Number(accessor.slice(1, -1));
19357
- if (Number.isInteger(idx) && idx >= 0 && idx < arr.length) return String(arr[idx]);
19358
- return `{${id}${accessor}}`;
19359
- }
19360
- return raw;
19361
- });
19362
- }
19363
- function parseArraySignal(raw) {
19364
- const trimmed = raw.trim();
19365
- if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return null;
19366
- try {
19367
- const parsed = JSON.parse(trimmed);
19368
- if (Array.isArray(parsed)) return parsed;
19369
- } catch {
19370
- const inner = trimmed.slice(1, -1).trim();
19371
- if (!inner) return [];
19372
- if (/^[\s\d.,+\-eE]+$/.test(inner)) return inner.split(",").map((s) => Number(s.trim()));
19373
- }
19374
- return null;
19375
- }
19376
- function resolveNumericAttr(value, values, fallback) {
19377
- if (typeof value === "number") return value;
19378
- if (typeof value !== "string") return fallback;
19379
- const trimmed = value.trim();
19380
- if (trimmed === "") return fallback;
19381
- const resolved = interpolate(trimmed, values);
19382
- const parsed = Number(resolved);
19383
- return Number.isFinite(parsed) ? parsed : fallback;
19384
- }
19385
- function shrinkPathEnd(d, shrinkPx) {
19386
- const commandRegex = /([MLCQZmlcqz])([^MLCQZmlcqz]*)/g;
19387
- const matches = [];
19388
- let m;
19389
- while ((m = commandRegex.exec(d)) !== null) {
19390
- const cmd = m[1];
19391
- const args = (m[2] ?? "").trim().split(/[\s,]+/).filter(Boolean).map(parseFloat);
19392
- matches.push({ cmd, args });
19393
- }
19394
- if (matches.length < 2) return d;
19395
- let prevEnd = { x: 0, y: 0 };
19396
- for (let i = 0; i < matches.length - 1; i++) {
19397
- const { cmd, args } = matches[i];
19398
- if (cmd === "M" || cmd === "L") {
19399
- prevEnd = { x: args[0], y: args[1] };
19400
- } else if (cmd === "Q") {
19401
- prevEnd = { x: args[2], y: args[3] };
19402
- } else if (cmd === "C") {
19403
- prevEnd = { x: args[4], y: args[5] };
19404
- }
19405
- }
19406
- const last = matches[matches.length - 1];
19407
- let endX, endY;
19408
- if (last.cmd === "L" || last.cmd === "M") {
19409
- endX = last.args[0];
19410
- endY = last.args[1];
19411
- } else if (last.cmd === "Q") {
19412
- endX = last.args[2];
19413
- endY = last.args[3];
19414
- } else if (last.cmd === "C") {
19415
- endX = last.args[4];
19416
- endY = last.args[5];
19417
- } else {
19418
- return d;
19419
- }
19420
- const dx = endX - prevEnd.x;
19421
- const dy = endY - prevEnd.y;
19422
- const len = Math.hypot(dx, dy);
19423
- if (len < shrinkPx + 1) return d;
19424
- const ratio = (len - shrinkPx) / len;
19425
- const newEndX = prevEnd.x + dx * ratio;
19426
- const newEndY = prevEnd.y + dy * ratio;
19427
- const tokens = d.trim().split(/\s+/);
19428
- if (tokens.length < 2) return d;
19429
- tokens[tokens.length - 2] = newEndX.toFixed(1);
19430
- tokens[tokens.length - 1] = newEndY.toFixed(1);
19431
- return tokens.join(" ");
19432
- }
19433
- function pathSubpath(d, progress) {
19434
- if (progress >= 1) return d;
19435
- if (progress <= 0) {
19436
- const first = d.trim().match(/^M\s+([-\d.]+)\s+([-\d.]+)/);
19437
- if (!first) return d;
19438
- return `M ${first[1]} ${first[2]}`;
19439
- }
19440
- const tokens = d.trim().split(/\s+/);
19441
- const points = [];
19442
- let i = 0;
19443
- while (i < tokens.length) {
19444
- const t = tokens[i];
19445
- if (t === "M" || t === "L") {
19446
- const x = parseFloat(tokens[i + 1]);
19447
- const y = parseFloat(tokens[i + 2]);
19448
- if (!Number.isFinite(x) || !Number.isFinite(y)) return d;
19449
- points.push({ x, y });
19450
- i += 3;
19451
- } else {
19452
- return d;
19453
- }
19454
- }
19455
- if (points.length < 2) return d;
19456
- const segLens = [];
19457
- let total = 0;
19458
- for (let k = 1; k < points.length; k++) {
19459
- const dx = points[k].x - points[k - 1].x;
19460
- const dy = points[k].y - points[k - 1].y;
19461
- const len = Math.hypot(dx, dy);
19462
- segLens.push(len);
19463
- total += len;
19464
- }
19465
- if (total === 0) return d;
19466
- const target = total * progress;
19467
- let acc = 0;
19468
- for (let k = 0; k < segLens.length; k++) {
19469
- const segLen = segLens[k];
19470
- if (acc + segLen >= target) {
19471
- const remain = target - acc;
19472
- const ratio = segLen === 0 ? 0 : remain / segLen;
19473
- const sx = points[k].x;
19474
- const sy = points[k].y;
19475
- const ex = points[k + 1].x;
19476
- const ey = points[k + 1].y;
19477
- const tx = sx + (ex - sx) * ratio;
19478
- const ty = sy + (ey - sy) * ratio;
19479
- const head = points.slice(0, k + 1).map((p, idx) => `${idx === 0 ? "M" : "L"} ${p.x} ${p.y}`).join(" ");
19480
- return `${head} L ${tx.toFixed(1)} ${ty.toFixed(1)}`;
19481
- }
19482
- acc += segLen;
19483
- }
19484
- return d;
19485
- }
19486
19675
  function opacityAttr(opacity) {
19487
19676
  return opacity < 1 ? opacity : void 0;
19488
19677
  }
@@ -20315,7 +20504,7 @@ function GenericNode({
20315
20504
  }
20316
20505
  );
20317
20506
  })();
20318
- const resolvedValue = value?.replace(/\{(\w+)\}/g, (_, id) => stateValues[id] ?? "");
20507
+ const resolvedValue = value !== void 0 ? interpolate(value, stateValues) : void 0;
20319
20508
  const centered = shape === "diamond" || shape === "cloud";
20320
20509
  const textAnchor = centered ? "middle" : "start";
20321
20510
  const textX = centered ? w / 2 : 20;
@@ -20349,9 +20538,7 @@ function GenericNode({
20349
20538
  }
20350
20539
  ),
20351
20540
  node.rows && node.rows.length > 0 && (() => {
20352
- const resolved = node.rows.map(
20353
- (row) => row.replace(/\{(\w+)\}/g, (_, id) => stateValues[id] ?? "")
20354
- );
20541
+ const resolved = node.rows.map((row) => interpolate(row, stateValues));
20355
20542
  const splits = resolved.map(splitRow);
20356
20543
  const leftMaxWidth = splits.reduce(
20357
20544
  (m, s) => Math.max(m, rowColumnWidth(s.left, GENERIC_ROW_LEFT_SIZE_PX, false)),
@@ -20658,14 +20845,97 @@ function arcPath(cx, cy, innerR, outerR, startAngle, endAngle) {
20658
20845
  "Z"
20659
20846
  ].join(" ");
20660
20847
  }
20661
- function interpolateColor(template, stateValues, fallback) {
20662
- if (!template) return fallback;
20663
- return template.replace(/\{(\w+)\}/g, (_, id) => stateValues[id] ?? `{${id}}`);
20848
+ function interpolateColor(template, stateValues, fallback) {
20849
+ if (!template) return fallback;
20850
+ return interpolate(template, stateValues);
20851
+ }
20852
+
20853
+ // src/render/payload-binding.ts
20854
+ var QUADRANT_KEYS = [
20855
+ "topLeft",
20856
+ "topRight",
20857
+ "bottomLeft",
20858
+ "bottomRight"
20859
+ ];
20860
+ var JOURNEY_EMOTIONS = [
20861
+ "delighted",
20862
+ "happy",
20863
+ "neutral",
20864
+ "frustrated",
20865
+ "angry"
20866
+ ];
20867
+ var QUADRANT_FALLBACK = "bottomLeft";
20868
+ var EMOTION_FALLBACK = "neutral";
20869
+ function resolveBoundNumber(raw, values, fallback) {
20870
+ if (typeof raw === "number") {
20871
+ return Number.isFinite(raw) ? { value: raw, ok: true } : { value: fallback, ok: false };
20872
+ }
20873
+ const resolved = interpolate(raw, values).trim();
20874
+ if (resolved === "") return { value: fallback, ok: false };
20875
+ const parsed = Number(resolved);
20876
+ return Number.isFinite(parsed) ? { value: parsed, ok: true } : { value: fallback, ok: false };
20877
+ }
20878
+ function resolveBoundEnum(raw, values, allowed, fallback) {
20879
+ const resolved = interpolate(raw, values).trim();
20880
+ if (allowed.includes(resolved)) return { value: resolved, ok: true };
20881
+ return { value: fallback, ok: false };
20882
+ }
20883
+ function needsResolve(raw) {
20884
+ return typeof raw !== "number" || !Number.isFinite(raw);
20885
+ }
20886
+ function resolveChartData(data, values) {
20887
+ if (!data.some((d) => needsResolve(d.value))) return data;
20888
+ return data.map((d) => {
20889
+ const v = resolveBoundNumber(d.value, values, 0);
20890
+ return v.ok ? { ...d, value: v.value } : { ...d, value: v.value, unresolved: true };
20891
+ });
20892
+ }
20893
+ function resolveFunnelData(stages, values) {
20894
+ if (!stages.some((s) => needsResolve(s.count))) return stages;
20895
+ return stages.map((s) => {
20896
+ const c = resolveBoundNumber(s.count, values, 0);
20897
+ return c.ok ? { ...s, count: c.value } : { ...s, count: c.value, unresolved: true };
20898
+ });
20899
+ }
20900
+ function resolveGanttData(tasks, values) {
20901
+ if (!tasks.some((t) => needsResolve(t.startIdx) || needsResolve(t.endIdx))) return tasks;
20902
+ return tasks.map((t) => {
20903
+ const start = resolveBoundNumber(t.startIdx, values, 0);
20904
+ const end = resolveBoundNumber(t.endIdx, values, start.value);
20905
+ const next = { ...t, startIdx: start.value, endIdx: end.value };
20906
+ return start.ok && end.ok ? next : { ...next, unresolved: true };
20907
+ });
20908
+ }
20909
+ function resolveQuadrantData(data, values) {
20910
+ const items = data.items;
20911
+ if (items.every((i) => QUADRANT_KEYS.includes(i.quadrant))) {
20912
+ return data;
20913
+ }
20914
+ return {
20915
+ ...data,
20916
+ items: items.map((i) => {
20917
+ const q = resolveBoundEnum(i.quadrant, values, QUADRANT_KEYS, QUADRANT_FALLBACK);
20918
+ return q.ok ? { ...i, quadrant: q.value } : { ...i, quadrant: q.value, unresolved: true };
20919
+ })
20920
+ };
20921
+ }
20922
+ function resolveJourneyData(steps, values) {
20923
+ if (steps.every((s) => JOURNEY_EMOTIONS.includes(s.emotion))) {
20924
+ return steps;
20925
+ }
20926
+ return steps.map((s) => {
20927
+ const e = resolveBoundEnum(s.emotion, values, JOURNEY_EMOTIONS, EMOTION_FALLBACK);
20928
+ return e.ok ? { ...s, emotion: e.value } : { ...s, emotion: e.value, unresolved: true };
20929
+ });
20664
20930
  }
20665
- function ChartLineNode({ node, active }) {
20931
+ function ChartLineNode({
20932
+ node,
20933
+ active,
20934
+ stateValues = {}
20935
+ }) {
20666
20936
  const x0 = node.cx - node.w / 2;
20667
20937
  const y0 = node.cy - node.h / 2;
20668
- const data = node.chartData ?? [];
20938
+ const data = resolveChartData(node.chartData ?? [], stateValues);
20669
20939
  const PAD_TOP = 36;
20670
20940
  const PAD_RIGHT = 40;
20671
20941
  const PAD_BOTTOM = 56;
@@ -20770,7 +21040,7 @@ function ChartLineNode({ node, active }) {
20770
21040
  const isValley = (prev === void 0 || d.value <= prev) && (next === void 0 || d.value <= next);
20771
21041
  const labelAbove = !isValley;
20772
21042
  const labelY = labelAbove ? cy - 12 : cy + 18;
20773
- return /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
21043
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { "data-cdl-unresolved": d.unresolved ? "true" : void 0, children: [
20774
21044
  /* @__PURE__ */ jsxRuntime.jsx(
20775
21045
  "circle",
20776
21046
  {
@@ -20819,10 +21089,14 @@ function ChartLineNode({ node, active }) {
20819
21089
  })
20820
21090
  ] });
20821
21091
  }
20822
- function ChartPieNode({ node, active }) {
21092
+ function ChartPieNode({
21093
+ node,
21094
+ active,
21095
+ stateValues = {}
21096
+ }) {
20823
21097
  const x0 = node.cx - node.w / 2;
20824
21098
  const y0 = node.cy - node.h / 2;
20825
- const data = node.chartData ?? [];
21099
+ const data = resolveChartData(node.chartData ?? [], stateValues);
20826
21100
  const total = data.reduce((acc, d) => acc + d.value, 0) || 1;
20827
21101
  const PAD_TOP = 20;
20828
21102
  const PAD_BOTTOM = 20;
@@ -20868,6 +21142,7 @@ function ChartPieNode({ node, active }) {
20868
21142
  "path",
20869
21143
  {
20870
21144
  "data-cdl-role": "chart-pie-slice",
21145
+ "data-cdl-unresolved": s.d.unresolved ? "true" : void 0,
20871
21146
  d: s.path,
20872
21147
  fill: sliceColor(s.d.tone, idx),
20873
21148
  fillOpacity: active ? 0.95 : 0.85,
@@ -20919,10 +21194,14 @@ function sliceColor(tone, idx) {
20919
21194
  if (tone) return TONE[tone];
20920
21195
  return TONE[FALLBACK_ORDER[idx % FALLBACK_ORDER.length]];
20921
21196
  }
20922
- function ChartBarNode({ node, active }) {
21197
+ function ChartBarNode({
21198
+ node,
21199
+ active,
21200
+ stateValues = {}
21201
+ }) {
20923
21202
  const x0 = node.cx - node.w / 2;
20924
21203
  const y0 = node.cy - node.h / 2;
20925
- const data = node.chartData ?? [];
21204
+ const data = resolveChartData(node.chartData ?? [], stateValues);
20926
21205
  const PAD_TOP = 20;
20927
21206
  const PAD_RIGHT = 24;
20928
21207
  const PAD_BOTTOM = 44;
@@ -21002,6 +21281,7 @@ function ChartBarNode({ node, active }) {
21002
21281
  "rect",
21003
21282
  {
21004
21283
  "data-cdl-role": "chart-bar",
21284
+ "data-cdl-unresolved": d.unresolved ? "true" : void 0,
21005
21285
  x: bx,
21006
21286
  y: by,
21007
21287
  width: barW,
@@ -21037,10 +21317,14 @@ function ChartBarNode({ node, active }) {
21037
21317
  })
21038
21318
  ] });
21039
21319
  }
21040
- function GanttNode({ node, active }) {
21320
+ function GanttNode({
21321
+ node,
21322
+ active,
21323
+ stateValues = {}
21324
+ }) {
21041
21325
  const x0 = node.cx - node.w / 2;
21042
21326
  const y0 = node.cy - node.h / 2;
21043
- const tasks = node.ganttData ?? [];
21327
+ const tasks = resolveGanttData(node.ganttData ?? [], stateValues);
21044
21328
  const PAD_TOP = 32;
21045
21329
  const PAD_RIGHT = 48;
21046
21330
  const PAD_BOTTOM = 44;
@@ -21050,7 +21334,7 @@ function GanttNode({ node, active }) {
21050
21334
  const rowGap = 20;
21051
21335
  const rowCount = tasks.length || 1;
21052
21336
  const rowH = Math.max(28, (canvasH - rowGap * (rowCount + 1)) / rowCount);
21053
- const maxIdx = Math.max(1, ...tasks.map((t) => t.endIdx + 1));
21337
+ const maxIdx = Math.max(1, Math.ceil(Math.max(...tasks.map((t) => t.endIdx + 1), 1)));
21054
21338
  const cellW = canvasW / maxIdx;
21055
21339
  const xAt = (idx) => PAD_LEFT + idx * cellW;
21056
21340
  const yAt = (row) => PAD_TOP + rowGap + row * (rowH + rowGap);
@@ -21135,6 +21419,7 @@ function GanttNode({ node, active }) {
21135
21419
  "rect",
21136
21420
  {
21137
21421
  "data-cdl-role": "gantt-bar",
21422
+ "data-cdl-unresolved": t.unresolved ? "true" : void 0,
21138
21423
  x: bx,
21139
21424
  y: by,
21140
21425
  width: bw,
@@ -21461,181 +21746,14 @@ function toneColor4(tone, alpha = 1) {
21461
21746
  const b = parseInt(hex.slice(5, 7), 16);
21462
21747
  return `rgba(${r}, ${g}, ${b}, ${alpha})`;
21463
21748
  }
21464
- var MIND_TONES2 = ["accent", "teal", "success", "warning", "info", "error"];
21465
- function MindRadialNode({ node, active }) {
21466
- const x0 = node.cx - node.w / 2;
21467
- const y0 = node.cy - node.h / 2;
21468
- const mindData = node.mindData;
21469
- if (!mindData) return /* @__PURE__ */ jsxRuntime.jsx("g", { transform: `translate(${x0} ${y0})` });
21470
- const canvasCx = node.w / 2;
21471
- const canvasCy = node.h / 2;
21472
- const maxRadius = Math.min(node.w, node.h) / 2 - 100;
21473
- const gradientId = `mind-radial-bg-${node.id}`;
21474
- const shadowId = `mind-radial-shadow-${node.id}`;
21475
- const layout2 = computeRadialLayout(mindData, canvasCx, canvasCy, maxRadius);
21476
- return /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: `translate(${x0} ${y0})`, children: [
21477
- /* @__PURE__ */ jsxRuntime.jsxs("defs", { children: [
21478
- /* @__PURE__ */ jsxRuntime.jsxs("radialGradient", { id: gradientId, cx: "50%", cy: "50%", r: "60%", children: [
21479
- /* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "var(--cdl-tone-accent, #2d6a8f)", stopOpacity: "0.14" }),
21480
- /* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "55%", stopColor: "var(--cdl-tone-accent, #2d6a8f)", stopOpacity: "0.04" }),
21481
- /* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "var(--cdl-tone-accent, #2d6a8f)", stopOpacity: "0" })
21482
- ] }),
21483
- /* @__PURE__ */ jsxRuntime.jsxs("filter", { id: shadowId, x: "-50%", y: "-50%", width: "200%", height: "200%", children: [
21484
- /* @__PURE__ */ jsxRuntime.jsx("feGaussianBlur", { in: "SourceAlpha", stdDeviation: "4" }),
21485
- /* @__PURE__ */ jsxRuntime.jsx("feOffset", { dx: "0", dy: "2", result: "offsetblur" }),
21486
- /* @__PURE__ */ jsxRuntime.jsx("feComponentTransfer", { children: /* @__PURE__ */ jsxRuntime.jsx("feFuncA", { type: "linear", slope: "0.4" }) }),
21487
- /* @__PURE__ */ jsxRuntime.jsxs("feMerge", { children: [
21488
- /* @__PURE__ */ jsxRuntime.jsx("feMergeNode", {}),
21489
- /* @__PURE__ */ jsxRuntime.jsx("feMergeNode", { in: "SourceGraphic" })
21490
- ] })
21491
- ] })
21492
- ] }),
21493
- /* @__PURE__ */ jsxRuntime.jsx(
21494
- "rect",
21495
- {
21496
- "data-cdl-role": "node-body",
21497
- x: 0,
21498
- y: 0,
21499
- width: node.w,
21500
- height: node.h,
21501
- rx: 16,
21502
- fill: "var(--cdl-node-fill, #ffffff)",
21503
- stroke: active ? "var(--cdl-tone-accent, #2d6a8f)" : "var(--cdl-divider, #d4d4d8)",
21504
- strokeWidth: active ? 3 : 1.25
21505
- }
21506
- ),
21507
- /* @__PURE__ */ jsxRuntime.jsx(
21508
- "circle",
21509
- {
21510
- cx: canvasCx,
21511
- cy: canvasCy,
21512
- r: maxRadius + 40,
21513
- fill: `url(#${gradientId})`,
21514
- pointerEvents: "none"
21515
- }
21516
- ),
21517
- layout2.edges.map((e, i) => /* @__PURE__ */ jsxRuntime.jsx(
21518
- "line",
21519
- {
21520
- "data-cdl-role": "mind-radial-edge",
21521
- x1: e.x1,
21522
- y1: e.y1,
21523
- x2: e.x2,
21524
- y2: e.y2,
21525
- stroke: toneColor5(e.tone),
21526
- strokeWidth: 2.25,
21527
- strokeOpacity: 0.7,
21528
- strokeLinecap: "round"
21529
- },
21530
- `edge-${i}`
21531
- )),
21532
- /* @__PURE__ */ jsxRuntime.jsx(
21533
- "circle",
21534
- {
21535
- "data-cdl-role": "mind-radial-halo",
21536
- cx: canvasCx,
21537
- cy: canvasCy,
21538
- r: 56,
21539
- fill: "var(--cdl-tone-accent, #2d6a8f)",
21540
- fillOpacity: 0.18
21541
- }
21542
- ),
21543
- /* @__PURE__ */ jsxRuntime.jsx(
21544
- "circle",
21545
- {
21546
- cx: canvasCx,
21547
- cy: canvasCy,
21548
- r: 42,
21549
- fill: "var(--cdl-tone-accent, #2d6a8f)",
21550
- filter: `url(#${shadowId})`
21551
- }
21552
- ),
21553
- /* @__PURE__ */ jsxRuntime.jsx(
21554
- "text",
21555
- {
21556
- x: canvasCx,
21557
- y: canvasCy + 5,
21558
- fontSize: 14,
21559
- fontWeight: 700,
21560
- textAnchor: "middle",
21561
- fill: "var(--cdl-text-on-tone, #ffffff)",
21562
- children: mindData.rootTitle
21563
- }
21564
- ),
21565
- layout2.nodes.filter((n) => !n.isRoot).map((n) => /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
21566
- /* @__PURE__ */ jsxRuntime.jsx(
21567
- "rect",
21568
- {
21569
- x: n.x - 62,
21570
- y: n.y - 22,
21571
- width: 124,
21572
- height: 44,
21573
- rx: 12,
21574
- fill: "var(--cdl-chip-fill, #f4f6fb)",
21575
- stroke: toneColor5(n.tone),
21576
- strokeWidth: 2
21577
- }
21578
- ),
21579
- /* @__PURE__ */ jsxRuntime.jsx(
21580
- "circle",
21581
- {
21582
- cx: n.x - 62 + 12,
21583
- cy: n.y,
21584
- r: 5,
21585
- fill: toneColor5(n.tone)
21586
- }
21587
- ),
21588
- /* @__PURE__ */ jsxRuntime.jsx(
21589
- "text",
21590
- {
21591
- x: n.x + 6,
21592
- y: n.y + 5,
21593
- fontSize: 13,
21594
- fontWeight: 600,
21595
- textAnchor: "middle",
21596
- fill: "var(--cdl-chip-text, #1a1f2a)",
21597
- children: n.title
21598
- }
21599
- )
21600
- ] }, `n-${n.id}`))
21601
- ] });
21602
- }
21603
- function computeRadialLayout(mindData, cx, cy, maxRadius) {
21604
- const nodes = [];
21605
- const edges = [];
21606
- nodes.push({ id: mindData.rootId, title: mindData.rootTitle, x: cx, y: cy, isRoot: true });
21607
- const childrenOf = /* @__PURE__ */ new Map();
21608
- for (const br of mindData.branches) {
21609
- if (!childrenOf.has(br.parent)) childrenOf.set(br.parent, []);
21610
- childrenOf.get(br.parent).push(br);
21611
- }
21612
- const firstLevel = childrenOf.get(mindData.rootId) ?? [];
21613
- const angleStep = Math.PI * 2 / 8;
21614
- firstLevel.slice(0, 8).forEach((br, idx) => {
21615
- const angle = -Math.PI / 2 + angleStep * idx;
21616
- const tone = br.tone ?? MIND_TONES2[idx % MIND_TONES2.length];
21617
- const r = maxRadius;
21618
- const x = cx + r * Math.cos(angle);
21619
- const y = cy + r * Math.sin(angle);
21620
- nodes.push({ id: br.id, title: br.title, x, y, tone, isRoot: false });
21621
- const rootR = 42;
21622
- const branchR = 62;
21623
- const x1 = cx + rootR * Math.cos(angle);
21624
- const y1 = cy + rootR * Math.sin(angle);
21625
- const x2 = x - branchR * Math.cos(angle);
21626
- const y2 = y - 22 * Math.sin(angle);
21627
- edges.push({ x1, y1, x2, y2, tone });
21628
- });
21629
- return { nodes, edges };
21630
- }
21631
- function toneColor5(tone) {
21632
- if (!tone) return TONE.accent;
21633
- return TONE[tone];
21634
- }
21635
- function FunnelNode({ node, active }) {
21749
+ function FunnelNode({
21750
+ node,
21751
+ active,
21752
+ stateValues = {}
21753
+ }) {
21636
21754
  const x0 = node.cx - node.w / 2;
21637
21755
  const y0 = node.cy - node.h / 2;
21638
- const stages = node.funnelData ?? [];
21756
+ const stages = resolveFunnelData(node.funnelData ?? [], stateValues);
21639
21757
  const PAD_TOP = 32;
21640
21758
  const PAD_RIGHT = 24;
21641
21759
  const PAD_BOTTOM = 32;
@@ -21669,7 +21787,7 @@ function FunnelNode({ node, active }) {
21669
21787
  ),
21670
21788
  stages.map((s, idx) => {
21671
21789
  const prev = idx > 0 ? stages[idx - 1] : null;
21672
- const dropRate = prev ? (prev.count - s.count) / prev.count * 100 : 0;
21790
+ const dropRate = prev && prev.count > 0 ? (prev.count - s.count) / prev.count * 100 : 0;
21673
21791
  const y = PAD_TOP + idx * (stageH + gap);
21674
21792
  const topW = widthAtStep(idx);
21675
21793
  const bottomW = widthAtStep(idx + 1);
@@ -21684,6 +21802,7 @@ function FunnelNode({ node, active }) {
21684
21802
  "polygon",
21685
21803
  {
21686
21804
  "data-cdl-role": "funnel-stage",
21805
+ "data-cdl-unresolved": s.unresolved ? "true" : void 0,
21687
21806
  points,
21688
21807
  fill: tone,
21689
21808
  fillOpacity: 0.9,
@@ -21739,11 +21858,16 @@ var TONE_ORDER = [
21739
21858
  function toneByIndex(idx) {
21740
21859
  return TONE_ORDER[idx % TONE_ORDER.length];
21741
21860
  }
21742
- function QuadrantNode({ node, active }) {
21861
+ function QuadrantNode({
21862
+ node,
21863
+ active,
21864
+ stateValues = {}
21865
+ }) {
21743
21866
  const x0 = node.cx - node.w / 2;
21744
21867
  const y0 = node.cy - node.h / 2;
21745
- const data = node.quadrantData;
21746
- if (!data) return /* @__PURE__ */ jsxRuntime.jsx("g", { transform: `translate(${x0} ${y0})` });
21868
+ const raw = node.quadrantData;
21869
+ if (!raw) return /* @__PURE__ */ jsxRuntime.jsx("g", { transform: `translate(${x0} ${y0})` });
21870
+ const data = resolveQuadrantData(raw, stateValues);
21747
21871
  const PAD_TOP = 40;
21748
21872
  const PAD_BOTTOM = 56;
21749
21873
  const AXIS_LABEL_FONT = 12;
@@ -21854,6 +21978,7 @@ function QuadrantNode({ node, active }) {
21854
21978
  "rect",
21855
21979
  {
21856
21980
  "data-cdl-role": "quadrant-item",
21981
+ "data-cdl-unresolved": it.unresolved ? "true" : void 0,
21857
21982
  x: qxStart,
21858
21983
  y: py,
21859
21984
  width: qHalfW,
@@ -22079,10 +22204,14 @@ function computeTreeLayout(treeNodes, canvasW, canvasH) {
22079
22204
  }
22080
22205
  return { nodes, edges };
22081
22206
  }
22082
- function UserJourneyNode({ node, active }) {
22207
+ function UserJourneyNode({
22208
+ node,
22209
+ active,
22210
+ stateValues = {}
22211
+ }) {
22083
22212
  const x0 = node.cx - node.w / 2;
22084
22213
  const y0 = node.cy - node.h / 2;
22085
- const steps = node.journeyData ?? [];
22214
+ const steps = resolveJourneyData(node.journeyData ?? [], stateValues);
22086
22215
  const PAD_TOP = 20;
22087
22216
  const PAD_RIGHT = 32;
22088
22217
  const PAD_BOTTOM = 108;
@@ -22212,99 +22341,107 @@ function UserJourneyNode({ node, active }) {
22212
22341
  }
22213
22342
  )
22214
22343
  ] }),
22215
- steps.map((s, idx) => /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
22216
- /* @__PURE__ */ jsxRuntime.jsx(
22217
- "circle",
22218
- {
22219
- cx: xAt(idx),
22220
- cy: yAt(s.emotion),
22221
- r: 9,
22222
- fill: "var(--cdl-node-fill, #ffffff)",
22223
- stroke: emotionColor[s.emotion],
22224
- strokeWidth: 2.5
22225
- }
22226
- ),
22227
- /* @__PURE__ */ jsxRuntime.jsx(
22228
- "circle",
22229
- {
22230
- cx: xAt(idx),
22231
- cy: yAt(s.emotion),
22232
- r: 4,
22233
- fill: emotionColor[s.emotion]
22234
- }
22235
- ),
22236
- /* @__PURE__ */ jsxRuntime.jsx(
22237
- "text",
22238
- {
22239
- x: xAt(idx),
22240
- y: PAD_TOP + canvasH + 24,
22241
- fontSize: 12,
22242
- fontWeight: 700,
22243
- textAnchor: "middle",
22244
- fill: "var(--cdl-text, #1a1f2a)",
22245
- children: `${idx + 1}. ${s.title}`
22246
- }
22247
- ),
22248
- s.touchpoint && /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
22249
- /* @__PURE__ */ jsxRuntime.jsx(
22250
- "rect",
22251
- {
22252
- "data-cdl-role": "journey-chip",
22253
- x: xAt(idx) - 40,
22254
- y: PAD_TOP + canvasH + 34,
22255
- width: 80,
22256
- height: 20,
22257
- rx: 10,
22258
- fill: "var(--cdl-tone-accent, #2d6a8f)",
22259
- fillOpacity: 0.12,
22260
- stroke: "var(--cdl-tone-accent, #2d6a8f)",
22261
- strokeOpacity: 0.6,
22262
- strokeWidth: 1
22263
- }
22264
- ),
22265
- /* @__PURE__ */ jsxRuntime.jsx(
22266
- "text",
22267
- {
22268
- x: xAt(idx),
22269
- y: PAD_TOP + canvasH + 48,
22270
- fontSize: 10,
22271
- fontWeight: 600,
22272
- textAnchor: "middle",
22273
- fill: "var(--cdl-tone-accent, #2d6a8f)",
22274
- children: s.touchpoint
22275
- }
22276
- )
22277
- ] }),
22278
- s.opportunity && /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
22279
- /* @__PURE__ */ jsxRuntime.jsx(
22280
- "rect",
22281
- {
22282
- x: xAt(idx) - 55,
22283
- y: PAD_TOP + canvasH + 62,
22284
- width: 110,
22285
- height: 26,
22286
- rx: 13,
22287
- fill: "var(--cdl-note-fill, #fef3c7)",
22288
- stroke: TONE.warning,
22289
- strokeWidth: 1
22290
- }
22291
- ),
22292
- /* @__PURE__ */ jsxRuntime.jsxs(
22293
- "text",
22294
- {
22295
- x: xAt(idx),
22296
- y: PAD_TOP + canvasH + 79,
22297
- fontSize: 10,
22298
- textAnchor: "middle",
22299
- fill: "var(--cdl-note-text, #7a5320)",
22300
- children: [
22301
- "\u{1F4A1} ",
22302
- s.opportunity
22303
- ]
22304
- }
22305
- )
22306
- ] })
22307
- ] }, `step-${s.id}`))
22344
+ steps.map((s, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
22345
+ "g",
22346
+ {
22347
+ "data-cdl-role": "journey-step",
22348
+ "data-cdl-unresolved": s.unresolved ? "true" : void 0,
22349
+ children: [
22350
+ /* @__PURE__ */ jsxRuntime.jsx(
22351
+ "circle",
22352
+ {
22353
+ cx: xAt(idx),
22354
+ cy: yAt(s.emotion),
22355
+ r: 9,
22356
+ fill: "var(--cdl-node-fill, #ffffff)",
22357
+ stroke: emotionColor[s.emotion],
22358
+ strokeWidth: 2.5
22359
+ }
22360
+ ),
22361
+ /* @__PURE__ */ jsxRuntime.jsx(
22362
+ "circle",
22363
+ {
22364
+ cx: xAt(idx),
22365
+ cy: yAt(s.emotion),
22366
+ r: 4,
22367
+ fill: emotionColor[s.emotion]
22368
+ }
22369
+ ),
22370
+ /* @__PURE__ */ jsxRuntime.jsx(
22371
+ "text",
22372
+ {
22373
+ x: xAt(idx),
22374
+ y: PAD_TOP + canvasH + 24,
22375
+ fontSize: 12,
22376
+ fontWeight: 700,
22377
+ textAnchor: "middle",
22378
+ fill: "var(--cdl-text, #1a1f2a)",
22379
+ children: `${idx + 1}. ${s.title}`
22380
+ }
22381
+ ),
22382
+ s.touchpoint && /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
22383
+ /* @__PURE__ */ jsxRuntime.jsx(
22384
+ "rect",
22385
+ {
22386
+ "data-cdl-role": "journey-chip",
22387
+ x: xAt(idx) - 40,
22388
+ y: PAD_TOP + canvasH + 34,
22389
+ width: 80,
22390
+ height: 20,
22391
+ rx: 10,
22392
+ fill: "var(--cdl-tone-accent, #2d6a8f)",
22393
+ fillOpacity: 0.12,
22394
+ stroke: "var(--cdl-tone-accent, #2d6a8f)",
22395
+ strokeOpacity: 0.6,
22396
+ strokeWidth: 1
22397
+ }
22398
+ ),
22399
+ /* @__PURE__ */ jsxRuntime.jsx(
22400
+ "text",
22401
+ {
22402
+ x: xAt(idx),
22403
+ y: PAD_TOP + canvasH + 48,
22404
+ fontSize: 10,
22405
+ fontWeight: 600,
22406
+ textAnchor: "middle",
22407
+ fill: "var(--cdl-tone-accent, #2d6a8f)",
22408
+ children: s.touchpoint
22409
+ }
22410
+ )
22411
+ ] }),
22412
+ s.opportunity && /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
22413
+ /* @__PURE__ */ jsxRuntime.jsx(
22414
+ "rect",
22415
+ {
22416
+ x: xAt(idx) - 55,
22417
+ y: PAD_TOP + canvasH + 62,
22418
+ width: 110,
22419
+ height: 26,
22420
+ rx: 13,
22421
+ fill: "var(--cdl-note-fill, #fef3c7)",
22422
+ stroke: TONE.warning,
22423
+ strokeWidth: 1
22424
+ }
22425
+ ),
22426
+ /* @__PURE__ */ jsxRuntime.jsxs(
22427
+ "text",
22428
+ {
22429
+ x: xAt(idx),
22430
+ y: PAD_TOP + canvasH + 79,
22431
+ fontSize: 10,
22432
+ textAnchor: "middle",
22433
+ fill: "var(--cdl-note-text, #7a5320)",
22434
+ children: [
22435
+ "\u{1F4A1} ",
22436
+ s.opportunity
22437
+ ]
22438
+ }
22439
+ )
22440
+ ] })
22441
+ ]
22442
+ },
22443
+ `step-${s.id}`
22444
+ ))
22308
22445
  ] });
22309
22446
  }
22310
22447
  var TITLE_MAX = 18;
@@ -26148,7 +26285,7 @@ function CdlNodeView({
26148
26285
  const boundOpacity = node.opacity !== void 0 ? Math.min(1, Math.max(0, resolveNumericAttr(node.opacity, stateValues, 1))) : void 0;
26149
26286
  if (node.visibleIf) {
26150
26287
  const resolved = interpolate(node.visibleIf, stateValues).trim().toLowerCase();
26151
- const unresolved = /^\{\w+(?:[.\[][^}]*)?\}$/.test(resolved) || /\{\w+(?:[.\[][^}]*)?\}/.test(resolved);
26288
+ const unresolved = hasUnresolvedRef(resolved);
26152
26289
  const falsy = resolved === "" || resolved === "0" || resolved === "false" || resolved === "null" || resolved === "undefined" || resolved === "nan";
26153
26290
  if (falsy || unresolved) {
26154
26291
  return /* @__PURE__ */ jsxRuntime.jsx("g", { "data-cdl-node": node.id, "data-cdl-hidden": "true", style: { display: "none" } });
@@ -26181,26 +26318,27 @@ function CdlNodeView({
26181
26318
  return /* @__PURE__ */ jsxRuntime.jsx(EventNode, { node: resolvedNode, active, progress });
26182
26319
  case "card":
26183
26320
  return /* @__PURE__ */ jsxRuntime.jsx(CardNode, { node: resolvedNode, active });
26321
+ // 図表 7 種は payload の数と語が `{signal}` を取れるので stateValues を渡す
26322
+ // (`render/payload-binding.ts` が描画直前に解決する)。 動かさない 2 種
26323
+ // (mind-map / tree-hierarchy) は構造だけを持つため渡さない。
26184
26324
  case "chart-line":
26185
- return /* @__PURE__ */ jsxRuntime.jsx(ChartLineNode, { node: resolvedNode, active });
26325
+ return /* @__PURE__ */ jsxRuntime.jsx(ChartLineNode, { node: resolvedNode, active, stateValues });
26186
26326
  case "chart-pie":
26187
- return /* @__PURE__ */ jsxRuntime.jsx(ChartPieNode, { node: resolvedNode, active });
26327
+ return /* @__PURE__ */ jsxRuntime.jsx(ChartPieNode, { node: resolvedNode, active, stateValues });
26188
26328
  case "chart-bar":
26189
- return /* @__PURE__ */ jsxRuntime.jsx(ChartBarNode, { node: resolvedNode, active });
26329
+ return /* @__PURE__ */ jsxRuntime.jsx(ChartBarNode, { node: resolvedNode, active, stateValues });
26190
26330
  case "gantt-timeline":
26191
- return /* @__PURE__ */ jsxRuntime.jsx(GanttNode, { node: resolvedNode, active });
26331
+ return /* @__PURE__ */ jsxRuntime.jsx(GanttNode, { node: resolvedNode, active, stateValues });
26192
26332
  case "mind-map":
26193
26333
  return /* @__PURE__ */ jsxRuntime.jsx(MindMapNode, { node: resolvedNode, active });
26194
- case "mind-radial":
26195
- return /* @__PURE__ */ jsxRuntime.jsx(MindRadialNode, { node: resolvedNode, active });
26196
26334
  case "funnel-stages":
26197
- return /* @__PURE__ */ jsxRuntime.jsx(FunnelNode, { node: resolvedNode, active });
26335
+ return /* @__PURE__ */ jsxRuntime.jsx(FunnelNode, { node: resolvedNode, active, stateValues });
26198
26336
  case "quadrant-matrix":
26199
- return /* @__PURE__ */ jsxRuntime.jsx(QuadrantNode, { node: resolvedNode, active });
26337
+ return /* @__PURE__ */ jsxRuntime.jsx(QuadrantNode, { node: resolvedNode, active, stateValues });
26200
26338
  case "tree-hierarchy":
26201
26339
  return /* @__PURE__ */ jsxRuntime.jsx(TreeNode, { node: resolvedNode, active });
26202
26340
  case "journey-map":
26203
- return /* @__PURE__ */ jsxRuntime.jsx(UserJourneyNode, { node: resolvedNode, active });
26341
+ return /* @__PURE__ */ jsxRuntime.jsx(UserJourneyNode, { node: resolvedNode, active, stateValues });
26204
26342
  case "shape-file":
26205
26343
  return /* @__PURE__ */ jsxRuntime.jsx(ShapeFileNode, { node: resolvedNode, active });
26206
26344
  case "shape-folder":
@@ -27908,53 +28046,6 @@ function stateMachine2(preset) {
27908
28046
  };
27909
28047
  return api;
27910
28048
  }
27911
- function mindMapRadial(preset) {
27912
- const centerId = preset.centerId ?? "center";
27913
- const canvasW = 640;
27914
- const canvasH = 640;
27915
- const b = diagram(preset.id, { topic: preset.topic });
27916
- const branches = [];
27917
- const api = {
27918
- branch(br) {
27919
- if (branches.length >= 8) {
27920
- throw new Error(`cdl mindMapRadial: 8 \u65B9\u5411\u914D\u7F6E\u306E\u305F\u3081 branch \u306F\u6700\u5927 8 \u4EF6 (preset id "${preset.id}")`);
27921
- }
27922
- branches.push(br);
27923
- return api;
27924
- },
27925
- build() {
27926
- b.lane("radial", { width: canvasW });
27927
- const nodeId = `${preset.id}-radial`;
27928
- b.node(nodeId, {
27929
- lane: "radial",
27930
- stack: 0,
27931
- kind: "mind-radial",
27932
- title: preset.topic,
27933
- eyebrow: "mindMapRadial",
27934
- w: canvasW,
27935
- h: canvasH,
27936
- mindData: {
27937
- rootId: centerId,
27938
- rootTitle: preset.centerTitle,
27939
- branches: branches.map((br) => ({
27940
- id: br.id,
27941
- title: br.title,
27942
- parent: centerId,
27943
- tone: br.tone,
27944
- subtitle: br.subtitle
27945
- }))
27946
- }
27947
- });
27948
- b.phase(
27949
- "mindmap-radial",
27950
- { duration: 2400, title: preset.topic, body: "mindMapRadial \u5168 branch \u3092 visible \u5316\u3001 \u4E2D\u5FC3 + 8 \u65B9\u5411 45 \u5EA6\u3067\u63CF\u753B\u3002" },
27951
- (p) => p.activate(nodeId).badge("mindmap-radial")
27952
- );
27953
- return b.build();
27954
- }
27955
- };
27956
- return api;
27957
- }
27958
28049
  function slugify(s) {
27959
28050
  return s.toLowerCase().replace(/[^a-z0-9぀-ゟ゠-ヿ一-龯]+/g, "-").replace(/^-|-$/g, "");
27960
28051
  }
@@ -27998,6 +28089,7 @@ exports.TARGET_LABEL_PATH_DIST = TARGET_LABEL_PATH_DIST;
27998
28089
  exports.TONES = TONES;
27999
28090
  exports.WCAG_AA_LARGE = WCAG_AA_LARGE;
28000
28091
  exports.WCAG_AA_NORMAL = WCAG_AA_NORMAL;
28092
+ exports.applyDerivedValues = applyDerivedValues;
28001
28093
  exports.assertNever = assertNever;
28002
28094
  exports.attachEventHandlers = attachEventHandlers;
28003
28095
  exports.batch = batch;
@@ -28037,7 +28129,6 @@ exports.labelInitClearance = labelInitClearance;
28037
28129
  exports.layout = layout;
28038
28130
  exports.layoutWithValidation = layoutWithValidation;
28039
28131
  exports.mindMap = mindMap;
28040
- exports.mindMapRadial = mindMapRadial;
28041
28132
  exports.network = network;
28042
28133
  exports.parseFormula = parseFormula;
28043
28134
  exports.pointRectEdgeDistance = pointRectEdgeDistance;
@@ -28080,6 +28171,7 @@ exports.verifyDiagramDom = verifyDiagramDom;
28080
28171
  exports.visualValidate = visualValidate;
28081
28172
  exports.visualValidateAll = visualValidateAll;
28082
28173
  exports.visualValidateLaid = visualValidateLaid;
28174
+ exports.withDerivedValues = withDerivedValues;
28083
28175
  exports.worldToPx = worldToPx;
28084
28176
  //# sourceMappingURL=index.cjs.map
28085
28177
  //# sourceMappingURL=index.cjs.map