sketchmark 1.0.2 → 1.0.3
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 +21 -7
- package/dist/animation/index.d.ts +9 -0
- package/dist/animation/index.d.ts.map +1 -1
- package/dist/index.cjs +177 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +177 -21
- package/dist/index.js.map +1 -1
- package/dist/renderer/svg/index.d.ts.map +1 -1
- package/dist/sketchmark.iife.js +177 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -587,7 +587,7 @@ step <action> <target> [options]
|
|
|
587
587
|
| Action | Target | Options | Description |
|
|
588
588
|
|--------|--------|---------|-------------|
|
|
589
589
|
| `highlight` | node/edge/group | — | Pulsing glow highlight (only one element at a time) |
|
|
590
|
-
| `draw` | node/edge/group/table/chart/markdown | `duration=N` | Animated
|
|
590
|
+
| `draw` | node/edge/group/table/chart/markdown | `duration=N` | Animated reveal; drawing a group also reveals its subtree unless descendants have their own later draw step |
|
|
591
591
|
| `fade` | node/edge/group | — | Fade to 22% opacity |
|
|
592
592
|
| `unfade` | node/edge/group | — | Restore from fade |
|
|
593
593
|
| `erase` | node/edge/group | `duration=N` | Fade to invisible (opacity 0) |
|
|
@@ -805,11 +805,25 @@ config pointer=hand # hand cursor
|
|
|
805
805
|
|
|
806
806
|
The pointer only appears during annotation steps — it follows the guide path as the annotation draws in, then fades out.
|
|
807
807
|
|
|
808
|
-
### Pre-hidden Elements (Draw Targets)
|
|
809
|
-
|
|
810
|
-
Any element targeted by a `step draw` action starts **hidden** and only appears when that step fires. Elements NOT targeted by `draw` are always visible.
|
|
811
|
-
|
|
812
|
-
|
|
808
|
+
### Pre-hidden Elements (Draw Targets)
|
|
809
|
+
|
|
810
|
+
Any element targeted by a `step draw` action starts **hidden** and only appears when that step fires. Elements NOT targeted by `draw` are always visible.
|
|
811
|
+
|
|
812
|
+
For groups, this applies to the whole subtree:
|
|
813
|
+
|
|
814
|
+
- `step draw group1` pre-hides the group and all descendant nodes, nested groups, tables, charts, notes, and markdown blocks.
|
|
815
|
+
- When the group step fires, descendants without their own later `draw` step are revealed immediately.
|
|
816
|
+
- Descendants with an explicit later `draw` step stay hidden until that later step.
|
|
817
|
+
- Edges are still independent; a group draw does not automatically reveal connected edges.
|
|
818
|
+
|
|
819
|
+
For group targets, these actions also apply recursively to the same subtree:
|
|
820
|
+
|
|
821
|
+
- `fade` / `unfade`
|
|
822
|
+
- `show` / `hide`
|
|
823
|
+
- `erase`
|
|
824
|
+
- Edges still remain explicit for these actions too.
|
|
825
|
+
|
|
826
|
+
---
|
|
813
827
|
|
|
814
828
|
## Config Options
|
|
815
829
|
|
|
@@ -1193,4 +1207,4 @@ interface DiagramInstance {
|
|
|
1193
1207
|
3. **Animation only works with SVG renderer** — the canvas renderer does not support animated steps.
|
|
1194
1208
|
4. **`step draw` makes elements start hidden** — any element you intend to `draw` will be invisible until its step fires.
|
|
1195
1209
|
5. **Node IDs must be unique** — duplicate IDs are silently deduplicated (only first definition kept).
|
|
1196
|
-
6. **Group children inherit group's coordinate space** — edges can connect across group boundaries using the node/group ID directly.
|
|
1210
|
+
6. **Group children inherit group's coordinate space** — edges can connect across group boundaries using the node/group ID directly.
|
|
@@ -29,6 +29,9 @@ export declare class AnimationController {
|
|
|
29
29
|
readonly drawTargetNotes: Set<string>;
|
|
30
30
|
readonly drawTargetCharts: Set<string>;
|
|
31
31
|
readonly drawTargetMarkdowns: Set<string>;
|
|
32
|
+
private readonly _drawStepIndexByElementId;
|
|
33
|
+
private readonly _parentGroupByElementId;
|
|
34
|
+
private readonly _groupDescendantIds;
|
|
32
35
|
private _captionEl;
|
|
33
36
|
private _captionTextEl;
|
|
34
37
|
private _annotationLayer;
|
|
@@ -39,6 +42,12 @@ export declare class AnimationController {
|
|
|
39
42
|
private _speechDone;
|
|
40
43
|
get drawTargets(): Set<string>;
|
|
41
44
|
constructor(svg: SVGSVGElement, steps: ASTStepItem[], _container?: HTMLElement | undefined, _rc?: any | undefined, _config?: Record<string, string | number | boolean> | undefined);
|
|
45
|
+
private _buildDrawStepIndex;
|
|
46
|
+
private _buildGroupVisibilityIndex;
|
|
47
|
+
private _hideGroupDescendants;
|
|
48
|
+
private _isDeferredForGroupReveal;
|
|
49
|
+
private _revealGroupSubtree;
|
|
50
|
+
private _resolveCascadeTargets;
|
|
42
51
|
/** The narration caption element — mount it anywhere via `yourContainer.appendChild(anim.captionElement)` */
|
|
43
52
|
get captionElement(): HTMLDivElement | null;
|
|
44
53
|
/** Enable/disable browser text-to-speech for narrate steps */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/animation/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAoB,WAAW,EAAE,MAAM,cAAc,CAAC;AAGlE,MAAM,MAAM,kBAAkB,GAC1B,aAAa,GACb,eAAe,GACf,iBAAiB,GACjB,iBAAiB,GACjB,eAAe,CAAC;AACpB,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,kBAAkB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AACD,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/animation/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -7703,6 +7703,21 @@ function mkGroup(id, cls) {
|
|
|
7703
7703
|
g.setAttribute("class", cls);
|
|
7704
7704
|
return g;
|
|
7705
7705
|
}
|
|
7706
|
+
function buildParentGroupLookup(sg) {
|
|
7707
|
+
const parentGroups = new Map();
|
|
7708
|
+
for (const g of sg.groups) {
|
|
7709
|
+
if (g.parentId)
|
|
7710
|
+
parentGroups.set(`group:${g.id}`, g.parentId);
|
|
7711
|
+
for (const child of g.children) {
|
|
7712
|
+
parentGroups.set(`${child.kind}:${child.id}`, g.id);
|
|
7713
|
+
}
|
|
7714
|
+
}
|
|
7715
|
+
return parentGroups;
|
|
7716
|
+
}
|
|
7717
|
+
function setParentGroupData(el, groupId) {
|
|
7718
|
+
if (groupId)
|
|
7719
|
+
el.dataset.parentGroup = groupId;
|
|
7720
|
+
}
|
|
7706
7721
|
// ── Node shapes ───────────────────────────────────────────────────────────
|
|
7707
7722
|
function renderShape$1(rc, n, palette) {
|
|
7708
7723
|
const s = n.style ?? {};
|
|
@@ -7799,6 +7814,7 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
7799
7814
|
}
|
|
7800
7815
|
// ── Groups ───────────────────────────────────────────────
|
|
7801
7816
|
const gmMap = new Map(sg.groups.map((g) => [g.id, g]));
|
|
7817
|
+
const parentGroups = buildParentGroupLookup(sg);
|
|
7802
7818
|
const sortedGroups = [...sg.groups].sort((a, b) => groupDepth(a, gmMap) - groupDepth(b, gmMap));
|
|
7803
7819
|
const GL = mkGroup("grp-layer");
|
|
7804
7820
|
for (const g of sortedGroups) {
|
|
@@ -7806,6 +7822,7 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
7806
7822
|
continue;
|
|
7807
7823
|
const gs = g.style ?? {};
|
|
7808
7824
|
const gg = mkGroup(`group-${g.id}`, "gg");
|
|
7825
|
+
setParentGroupData(gg, g.parentId);
|
|
7809
7826
|
if (gs.opacity != null)
|
|
7810
7827
|
gg.setAttribute("opacity", String(gs.opacity));
|
|
7811
7828
|
gg.appendChild(rc.rectangle(g.x, g.y, g.w, g.h, {
|
|
@@ -7910,6 +7927,7 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
7910
7927
|
const idPrefix = shapeDef?.idPrefix ?? "node";
|
|
7911
7928
|
const cssClass = shapeDef?.cssClass ?? "ng";
|
|
7912
7929
|
const ng = mkGroup(`${idPrefix}-${n.id}`, cssClass);
|
|
7930
|
+
setParentGroupData(ng, n.groupId ?? parentGroups.get(`node:${n.id}`));
|
|
7913
7931
|
ng.dataset.nodeShape = n.shape;
|
|
7914
7932
|
ng.dataset.x = String(n.x);
|
|
7915
7933
|
ng.dataset.y = String(n.y);
|
|
@@ -7991,6 +8009,7 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
7991
8009
|
const TL = mkGroup("table-layer");
|
|
7992
8010
|
for (const t of sg.tables) {
|
|
7993
8011
|
const tg = mkGroup(`table-${t.id}`, "tg");
|
|
8012
|
+
setParentGroupData(tg, parentGroups.get(`table:${t.id}`));
|
|
7994
8013
|
const gs = t.style ?? {};
|
|
7995
8014
|
const fill = String(gs.fill ?? palette.tableFill);
|
|
7996
8015
|
const strk = String(gs.stroke ?? palette.tableStroke);
|
|
@@ -8091,6 +8110,7 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
8091
8110
|
const MDL = mkGroup('markdown-layer');
|
|
8092
8111
|
for (const m of sg.markdowns) {
|
|
8093
8112
|
const mg = mkGroup(`markdown-${m.id}`, 'mdg');
|
|
8113
|
+
setParentGroupData(mg, parentGroups.get(`markdown:${m.id}`));
|
|
8094
8114
|
const gs = m.style ?? {};
|
|
8095
8115
|
const mFont = resolveStyleFont(gs, diagramFont);
|
|
8096
8116
|
const baseColor = String(gs.color ?? palette.nodeText);
|
|
@@ -8154,7 +8174,9 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
8154
8174
|
// ── Charts ────────────────────────────────────────────────
|
|
8155
8175
|
const CL = mkGroup("chart-layer");
|
|
8156
8176
|
for (const c of sg.charts) {
|
|
8157
|
-
|
|
8177
|
+
const cg = renderRoughChartSVG(rc, c, palette, themeName !== "light");
|
|
8178
|
+
setParentGroupData(cg, parentGroups.get(`chart:${c.id}`));
|
|
8179
|
+
CL.appendChild(cg);
|
|
8158
8180
|
}
|
|
8159
8181
|
svg.appendChild(CL);
|
|
8160
8182
|
return svg;
|
|
@@ -8867,18 +8889,33 @@ const getTableEl = (svg, id) => getEl(svg, `table-${id}`);
|
|
|
8867
8889
|
const getNoteEl = (svg, id) => getEl(svg, `note-${id}`);
|
|
8868
8890
|
const getChartEl = (svg, id) => getEl(svg, `chart-${id}`);
|
|
8869
8891
|
const getMarkdownEl = (svg, id) => getEl(svg, `markdown-${id}`);
|
|
8892
|
+
const POSITIONABLE_SELECTOR = ".ng, .gg, .tg, .ntg, .cg, .mdg";
|
|
8893
|
+
function resolveNonEdgeDrawEl(svg, target) {
|
|
8894
|
+
return (getGroupEl(svg, target) ??
|
|
8895
|
+
getTableEl(svg, target) ??
|
|
8896
|
+
getNoteEl(svg, target) ??
|
|
8897
|
+
getChartEl(svg, target) ??
|
|
8898
|
+
getMarkdownEl(svg, target) ??
|
|
8899
|
+
getNodeEl(svg, target) ??
|
|
8900
|
+
null);
|
|
8901
|
+
}
|
|
8902
|
+
function hideDrawEl(el) {
|
|
8903
|
+
if (el.classList.contains("ng")) {
|
|
8904
|
+
el.classList.add("hidden");
|
|
8905
|
+
return;
|
|
8906
|
+
}
|
|
8907
|
+
el.classList.add("gg-hidden");
|
|
8908
|
+
}
|
|
8909
|
+
function showDrawEl(el) {
|
|
8910
|
+
el.classList.remove("hidden", "gg-hidden");
|
|
8911
|
+
}
|
|
8870
8912
|
function resolveEl(svg, target) {
|
|
8871
8913
|
// check edge first — target contains connector like "a-->b"
|
|
8872
8914
|
const edge = parseEdgeTarget(target);
|
|
8873
8915
|
if (edge)
|
|
8874
8916
|
return getEdgeEl(svg, edge.from, edge.to);
|
|
8875
8917
|
// everything else resolved by prefixed id
|
|
8876
|
-
return (
|
|
8877
|
-
getGroupEl(svg, target) ??
|
|
8878
|
-
getTableEl(svg, target) ??
|
|
8879
|
-
getNoteEl(svg, target) ??
|
|
8880
|
-
getChartEl(svg, target) ??
|
|
8881
|
-
getMarkdownEl(svg, target) ??
|
|
8918
|
+
return (resolveNonEdgeDrawEl(svg, target) ??
|
|
8882
8919
|
null);
|
|
8883
8920
|
}
|
|
8884
8921
|
function pathLength(p) {
|
|
@@ -9068,6 +9105,7 @@ function prepareNodeForDraw(el) {
|
|
|
9068
9105
|
el.appendChild(guide);
|
|
9069
9106
|
}
|
|
9070
9107
|
function revealNodeInstant(el) {
|
|
9108
|
+
showDrawEl(el);
|
|
9071
9109
|
clearNodeDrawStyles(el);
|
|
9072
9110
|
}
|
|
9073
9111
|
// ── Text writing reveal (clipPath) ───────────────────────
|
|
@@ -9116,6 +9154,7 @@ function animateTextReveal(textEl, delayMs, durationMs = ANIMATION.textRevealMs)
|
|
|
9116
9154
|
}, delayMs);
|
|
9117
9155
|
}
|
|
9118
9156
|
function animateNodeDraw(el, strokeDur = ANIMATION.nodeStrokeDur) {
|
|
9157
|
+
showDrawEl(el);
|
|
9119
9158
|
const guide = nodeGuidePathEl(el);
|
|
9120
9159
|
if (!guide) {
|
|
9121
9160
|
const firstPath = el.querySelector("path");
|
|
@@ -9170,6 +9209,15 @@ function flattenSteps(items) {
|
|
|
9170
9209
|
}
|
|
9171
9210
|
return out;
|
|
9172
9211
|
}
|
|
9212
|
+
function forEachPlaybackStep(items, visit) {
|
|
9213
|
+
items.forEach((item, stepIndex) => {
|
|
9214
|
+
if (item.kind === "beat") {
|
|
9215
|
+
item.children.forEach((child) => visit(child, stepIndex));
|
|
9216
|
+
return;
|
|
9217
|
+
}
|
|
9218
|
+
visit(item, stepIndex);
|
|
9219
|
+
});
|
|
9220
|
+
}
|
|
9173
9221
|
// ── Draw target helpers ───────────────────────────────────
|
|
9174
9222
|
function getDrawTargetEdgeIds(steps) {
|
|
9175
9223
|
const ids = new Set();
|
|
@@ -9354,7 +9402,7 @@ class AnimationController {
|
|
|
9354
9402
|
for (const s of flattenSteps(steps)) {
|
|
9355
9403
|
if (s.action !== "draw" || parseEdgeTarget(s.target))
|
|
9356
9404
|
continue;
|
|
9357
|
-
if (svg.
|
|
9405
|
+
if (resolveNonEdgeDrawEl(svg, s.target)?.id === `group-${s.target}`) {
|
|
9358
9406
|
this.drawTargetGroups.add(`group-${s.target}`);
|
|
9359
9407
|
this.drawTargetNodes.delete(`node-${s.target}`);
|
|
9360
9408
|
}
|
|
@@ -9375,6 +9423,10 @@ class AnimationController {
|
|
|
9375
9423
|
this.drawTargetNodes.delete(`node-${s.target}`);
|
|
9376
9424
|
}
|
|
9377
9425
|
}
|
|
9426
|
+
this._drawStepIndexByElementId = this._buildDrawStepIndex();
|
|
9427
|
+
const { parentGroupByElementId, groupDescendantIds } = this._buildGroupVisibilityIndex();
|
|
9428
|
+
this._parentGroupByElementId = parentGroupByElementId;
|
|
9429
|
+
this._groupDescendantIds = groupDescendantIds;
|
|
9378
9430
|
this._clearAll();
|
|
9379
9431
|
// Init narration caption
|
|
9380
9432
|
if (this._container)
|
|
@@ -9393,6 +9445,104 @@ class AnimationController {
|
|
|
9393
9445
|
if (this._tts)
|
|
9394
9446
|
this._warmUpSpeech();
|
|
9395
9447
|
}
|
|
9448
|
+
_buildDrawStepIndex() {
|
|
9449
|
+
const drawStepIndexByElementId = new Map();
|
|
9450
|
+
forEachPlaybackStep(this.steps, (step, stepIndex) => {
|
|
9451
|
+
if (step.action !== "draw" || parseEdgeTarget(step.target))
|
|
9452
|
+
return;
|
|
9453
|
+
const el = resolveNonEdgeDrawEl(this.svg, step.target);
|
|
9454
|
+
if (el && !drawStepIndexByElementId.has(el.id)) {
|
|
9455
|
+
drawStepIndexByElementId.set(el.id, stepIndex);
|
|
9456
|
+
}
|
|
9457
|
+
});
|
|
9458
|
+
return drawStepIndexByElementId;
|
|
9459
|
+
}
|
|
9460
|
+
_buildGroupVisibilityIndex() {
|
|
9461
|
+
const parentGroupByElementId = new Map();
|
|
9462
|
+
const directChildIdsByGroup = new Map();
|
|
9463
|
+
this.svg.querySelectorAll(POSITIONABLE_SELECTOR).forEach((el) => {
|
|
9464
|
+
const parentGroupId = el.dataset.parentGroup;
|
|
9465
|
+
if (!parentGroupId)
|
|
9466
|
+
return;
|
|
9467
|
+
const parentGroupElId = `group-${parentGroupId}`;
|
|
9468
|
+
parentGroupByElementId.set(el.id, parentGroupElId);
|
|
9469
|
+
const children = directChildIdsByGroup.get(parentGroupElId) ?? new Set();
|
|
9470
|
+
children.add(el.id);
|
|
9471
|
+
directChildIdsByGroup.set(parentGroupElId, children);
|
|
9472
|
+
});
|
|
9473
|
+
const groupDescendantIds = new Map();
|
|
9474
|
+
const visit = (groupElId) => {
|
|
9475
|
+
if (groupDescendantIds.has(groupElId))
|
|
9476
|
+
return groupDescendantIds.get(groupElId);
|
|
9477
|
+
const descendants = new Set();
|
|
9478
|
+
const directChildren = directChildIdsByGroup.get(groupElId);
|
|
9479
|
+
if (directChildren) {
|
|
9480
|
+
for (const childId of directChildren) {
|
|
9481
|
+
descendants.add(childId);
|
|
9482
|
+
if (childId.startsWith("group-")) {
|
|
9483
|
+
visit(childId).forEach((nestedId) => descendants.add(nestedId));
|
|
9484
|
+
}
|
|
9485
|
+
}
|
|
9486
|
+
}
|
|
9487
|
+
groupDescendantIds.set(groupElId, descendants);
|
|
9488
|
+
return descendants;
|
|
9489
|
+
};
|
|
9490
|
+
this.svg.querySelectorAll(".gg").forEach((el) => {
|
|
9491
|
+
visit(el.id);
|
|
9492
|
+
});
|
|
9493
|
+
return { parentGroupByElementId, groupDescendantIds };
|
|
9494
|
+
}
|
|
9495
|
+
_hideGroupDescendants(groupElId) {
|
|
9496
|
+
const descendants = this._groupDescendantIds.get(groupElId);
|
|
9497
|
+
if (!descendants)
|
|
9498
|
+
return;
|
|
9499
|
+
for (const descendantId of descendants) {
|
|
9500
|
+
const el = getEl(this.svg, descendantId);
|
|
9501
|
+
if (el)
|
|
9502
|
+
hideDrawEl(el);
|
|
9503
|
+
}
|
|
9504
|
+
}
|
|
9505
|
+
_isDeferredForGroupReveal(elementId, stepIndex, groupElId) {
|
|
9506
|
+
let currentId = elementId;
|
|
9507
|
+
while (currentId) {
|
|
9508
|
+
const firstDrawStep = this._drawStepIndexByElementId.get(currentId);
|
|
9509
|
+
if (firstDrawStep != null && firstDrawStep > stepIndex)
|
|
9510
|
+
return true;
|
|
9511
|
+
if (currentId === groupElId)
|
|
9512
|
+
break;
|
|
9513
|
+
currentId = this._parentGroupByElementId.get(currentId);
|
|
9514
|
+
}
|
|
9515
|
+
return false;
|
|
9516
|
+
}
|
|
9517
|
+
_revealGroupSubtree(groupElId, stepIndex) {
|
|
9518
|
+
const descendants = this._groupDescendantIds.get(groupElId);
|
|
9519
|
+
if (!descendants)
|
|
9520
|
+
return;
|
|
9521
|
+
for (const descendantId of descendants) {
|
|
9522
|
+
if (this._isDeferredForGroupReveal(descendantId, stepIndex, groupElId))
|
|
9523
|
+
continue;
|
|
9524
|
+
const el = getEl(this.svg, descendantId);
|
|
9525
|
+
if (el)
|
|
9526
|
+
showDrawEl(el);
|
|
9527
|
+
}
|
|
9528
|
+
}
|
|
9529
|
+
_resolveCascadeTargets(target) {
|
|
9530
|
+
const edge = parseEdgeTarget(target);
|
|
9531
|
+
if (edge) {
|
|
9532
|
+
const el = getEdgeEl(this.svg, edge.from, edge.to);
|
|
9533
|
+
return el ? [el] : [];
|
|
9534
|
+
}
|
|
9535
|
+
const el = resolveEl(this.svg, target);
|
|
9536
|
+
if (!el)
|
|
9537
|
+
return [];
|
|
9538
|
+
if (!el.id.startsWith("group-"))
|
|
9539
|
+
return [el];
|
|
9540
|
+
const ids = new Set([el.id]);
|
|
9541
|
+
this._groupDescendantIds.get(el.id)?.forEach((id) => ids.add(id));
|
|
9542
|
+
return Array.from(ids)
|
|
9543
|
+
.map((id) => getEl(this.svg, id))
|
|
9544
|
+
.filter((candidate) => candidate != null);
|
|
9545
|
+
}
|
|
9396
9546
|
/** The narration caption element — mount it anywhere via `yourContainer.appendChild(anim.captionElement)` */
|
|
9397
9547
|
get captionElement() {
|
|
9398
9548
|
return this._captionEl;
|
|
@@ -9662,6 +9812,9 @@ class AnimationController {
|
|
|
9662
9812
|
el.style.opacity = "";
|
|
9663
9813
|
el.classList.remove("hl", "faded");
|
|
9664
9814
|
});
|
|
9815
|
+
for (const groupElId of this.drawTargetGroups) {
|
|
9816
|
+
this._hideGroupDescendants(groupElId);
|
|
9817
|
+
}
|
|
9665
9818
|
// Clear narration caption
|
|
9666
9819
|
if (this._captionEl) {
|
|
9667
9820
|
this._captionEl.style.opacity = "0";
|
|
@@ -9724,7 +9877,7 @@ class AnimationController {
|
|
|
9724
9877
|
this._doDraw(s, silent);
|
|
9725
9878
|
break;
|
|
9726
9879
|
case "erase":
|
|
9727
|
-
this._doErase(s.target, s.duration);
|
|
9880
|
+
this._doErase(s.target, silent, s.duration);
|
|
9728
9881
|
break;
|
|
9729
9882
|
case "show":
|
|
9730
9883
|
this._doShowHide(s.target, true, silent, s.duration);
|
|
@@ -9780,7 +9933,9 @@ class AnimationController {
|
|
|
9780
9933
|
}
|
|
9781
9934
|
// ── fade / unfade ─────────────────────────────────────────
|
|
9782
9935
|
_doFade(target, doFade) {
|
|
9783
|
-
|
|
9936
|
+
for (const el of this._resolveCascadeTargets(target)) {
|
|
9937
|
+
el.classList.toggle("faded", doFade);
|
|
9938
|
+
}
|
|
9784
9939
|
}
|
|
9785
9940
|
_writeTransform(el, target, silent, duration = 420) {
|
|
9786
9941
|
const t = this._transforms.get(target) ?? {
|
|
@@ -9879,11 +10034,12 @@ class AnimationController {
|
|
|
9879
10034
|
// Check if target is a group (has #group-{target} element)
|
|
9880
10035
|
const groupEl = getGroupEl(this.svg, target);
|
|
9881
10036
|
if (groupEl) {
|
|
10037
|
+
showDrawEl(groupEl);
|
|
10038
|
+
this._revealGroupSubtree(groupEl.id, this._step);
|
|
9882
10039
|
// ── Group draw ──────────────────────────────────────
|
|
9883
10040
|
if (silent) {
|
|
9884
10041
|
clearDrawStyles(groupEl);
|
|
9885
10042
|
groupEl.style.transition = "none";
|
|
9886
|
-
groupEl.classList.remove("gg-hidden");
|
|
9887
10043
|
groupEl.style.opacity = "1";
|
|
9888
10044
|
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
9889
10045
|
groupEl.style.transition = "";
|
|
@@ -9891,7 +10047,6 @@ class AnimationController {
|
|
|
9891
10047
|
}));
|
|
9892
10048
|
}
|
|
9893
10049
|
else {
|
|
9894
|
-
groupEl.classList.remove("gg-hidden");
|
|
9895
10050
|
// Groups use slightly longer stroke-draw (bigger box, dashed border = more paths)
|
|
9896
10051
|
const firstPath = groupEl.querySelector("path");
|
|
9897
10052
|
if (!firstPath?.style.strokeDasharray)
|
|
@@ -10002,6 +10157,7 @@ class AnimationController {
|
|
|
10002
10157
|
const nodeEl = getNodeEl(this.svg, target);
|
|
10003
10158
|
if (!nodeEl)
|
|
10004
10159
|
return;
|
|
10160
|
+
showDrawEl(nodeEl);
|
|
10005
10161
|
if (silent) {
|
|
10006
10162
|
revealNodeInstant(nodeEl);
|
|
10007
10163
|
}
|
|
@@ -10013,20 +10169,20 @@ class AnimationController {
|
|
|
10013
10169
|
}
|
|
10014
10170
|
}
|
|
10015
10171
|
// ── erase ─────────────────────────────────────────────────
|
|
10016
|
-
_doErase(target, duration = 400) {
|
|
10017
|
-
const el
|
|
10018
|
-
|
|
10019
|
-
el.style.transition = `opacity ${duration}ms`;
|
|
10172
|
+
_doErase(target, silent, duration = 400) {
|
|
10173
|
+
for (const el of this._resolveCascadeTargets(target)) {
|
|
10174
|
+
el.style.transition = silent ? "none" : `opacity ${duration}ms`;
|
|
10020
10175
|
el.style.opacity = "0";
|
|
10021
10176
|
}
|
|
10022
10177
|
}
|
|
10023
10178
|
// ── show / hide ───────────────────────────────────────────
|
|
10024
10179
|
_doShowHide(target, show, silent, duration = 400) {
|
|
10025
|
-
const el
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
|
|
10180
|
+
for (const el of this._resolveCascadeTargets(target)) {
|
|
10181
|
+
if (show)
|
|
10182
|
+
showDrawEl(el);
|
|
10183
|
+
el.style.transition = silent ? "none" : `opacity ${duration}ms`;
|
|
10184
|
+
el.style.opacity = show ? "1" : "0";
|
|
10185
|
+
}
|
|
10030
10186
|
}
|
|
10031
10187
|
// ── pulse ─────────────────────────────────────────────────
|
|
10032
10188
|
_doPulse(target, duration = 500) {
|