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