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