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 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 stroke-drawing reveal with text writing effect |
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":"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"}
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
- CL.appendChild(renderRoughChartSVG(rc, c, palette, themeName !== "light"));
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 (getNodeEl(svg, target) ??
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.querySelector(`#group-${s.target}`)) {
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
- resolveEl(this.svg, target)?.classList.toggle("faded", doFade);
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 = resolveEl(this.svg, target); // handles edges too now
10018
- if (el) {
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 = resolveEl(this.svg, target);
10026
- if (!el)
10027
- return;
10028
- el.style.transition = silent ? "none" : `opacity ${duration}ms`;
10029
- el.style.opacity = show ? "1" : "0";
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) {