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