sketchmark 1.3.5 → 1.3.7

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
@@ -149,7 +149,7 @@ const embed = new SketchmarkEmbed({
149
149
  });
150
150
  ```
151
151
 
152
- Use `SketchmarkCanvas` for the full playground-style surface, and `SketchmarkEmbed` for fixed-size embeds that clip overflow, auto-fit large diagrams, support drag-to-pan plus wheel/trackpad zoom, and expose built-in zoom, playback, caption, and TTS controls.
152
+ Use `SketchmarkCanvas` for the full playground-style surface, and `SketchmarkEmbed` for fixed-size embeds that clip overflow, auto-fit large diagrams, support drag-to-pan plus wheel/trackpad zoom, and expose built-in zoom, playback, caption, and TTS controls. While autoplay is running, their built-in `Play` control switches to `Stop` so you can hard-stop the sequence immediately.
153
153
 
154
154
  ---
155
155
 
@@ -854,17 +854,19 @@ anim.total // number of steps
854
854
  anim.currentStep // current step index (-1 = before start)
855
855
  anim.canNext // boolean
856
856
  anim.canPrev // boolean
857
- anim.atEnd // boolean
857
+ anim.atEnd // boolean
858
+ anim.isPlaying // boolean
858
859
  anim.captionElement // HTMLDivElement | null — the narration caption element
859
860
  anim.tts // boolean — text-to-speech enabled/disabled
860
861
 
861
862
  // Methods
862
- anim.next() // advance one step (returns bool)
863
- anim.prev() // go back one step (returns bool)
864
- anim.reset() // reset to before step 0
865
- anim.goTo(index) // jump to step N
866
- await anim.play(700) // play all remaining steps (700ms between)
867
- anim.destroy() // remove caption, annotations, pointer from DOM
863
+ anim.next() // advance one step (returns bool)
864
+ anim.prev() // go back one step (returns bool)
865
+ anim.reset() // reset to before step 0
866
+ anim.goTo(index) // jump to step N
867
+ await anim.play(700) // play all remaining steps (700ms between)
868
+ anim.stop() // hard-stop autoplay without resetting the current step
869
+ anim.destroy() // remove caption, annotations, pointer from DOM
868
870
 
869
871
  // Toggle TTS programmatically
870
872
  anim.tts = true; // enable browser speech
@@ -936,19 +938,19 @@ The pointer only appears during annotation steps — it follows the guide path a
936
938
 
937
939
  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.
938
940
 
939
- For groups, this applies to the whole subtree:
940
-
941
- - `step draw group1` pre-hides the group and all descendant nodes, nested groups, tables, charts, notes, and markdown blocks.
942
- - When the group step fires, descendants without their own later `draw` step are revealed immediately.
943
- - Descendants with an explicit later `draw` step stay hidden until that later step.
944
- - Edges are still independent; a group draw does not automatically reveal connected edges.
941
+ For groups, this applies to the whole subtree:
942
+
943
+ - `step draw group1` pre-hides the group and all descendant nodes, nested groups, tables, charts, notes, markdown blocks, and any edge whose endpoints stay inside that group subtree.
944
+ - When the group step fires, descendants without their own later `draw` step are revealed immediately.
945
+ - Descendants with an explicit later `draw` step stay hidden until that later step.
946
+ - Boundary-crossing edges are still independent; a group draw only cascades to edges whose endpoints share that group subtree.
945
947
 
946
948
  For group targets, these actions also apply recursively to the same subtree:
947
949
 
948
- - `fade` / `unfade`
949
- - `show` / `hide`
950
- - `erase`
951
- - Edges still remain explicit for these actions too.
950
+ - `fade` / `unfade`
951
+ - `show` / `hide`
952
+ - `erase`
953
+ - The same internal-edge rule applies here too; boundary-crossing edges remain explicit.
952
954
 
953
955
  ---
954
956
 
@@ -19,8 +19,12 @@ export declare class AnimationController {
19
19
  private _rc?;
20
20
  private _config?;
21
21
  private _step;
22
+ private _isPlaying;
23
+ private _playRunId;
22
24
  private _pendingStepTimers;
23
25
  private _pendingNarrationTimers;
26
+ private _playbackDelayTimerId;
27
+ private _resolvePlaybackDelay;
24
28
  private _transforms;
25
29
  private _listeners;
26
30
  readonly drawTargetEdges: Set<string>;
@@ -43,6 +47,7 @@ export declare class AnimationController {
43
47
  private _pointerType;
44
48
  private _tts;
45
49
  private _speechDone;
50
+ private _resolveSpeechDone;
46
51
  get drawTargets(): Set<string>;
47
52
  constructor(svg: SVGSVGElement, steps: ASTStepItem[], _container?: HTMLElement | undefined, _rc?: any | undefined, _config?: Record<string, string | number | boolean> | undefined);
48
53
  private _buildDrawStepIndex;
@@ -62,6 +67,7 @@ export declare class AnimationController {
62
67
  get canNext(): boolean;
63
68
  get canPrev(): boolean;
64
69
  get atEnd(): boolean;
70
+ get isPlaying(): boolean;
65
71
  on(listener: AnimationListener): () => void;
66
72
  private emit;
67
73
  reset(): void;
@@ -71,11 +77,15 @@ export declare class AnimationController {
71
77
  prev(): boolean;
72
78
  play(msPerStep?: number): Promise<void>;
73
79
  goTo(index: number): void;
80
+ stop(): void;
81
+ private _advanceNext;
74
82
  private _clearTimerBucket;
75
83
  private _clearPendingStepTimers;
76
84
  private _cancelNarrationTyping;
77
85
  private _scheduleTimer;
78
86
  private _scheduleStep;
87
+ private _waitForPlaybackDelay;
88
+ private _cancelPlaybackDelay;
79
89
  private _stepWaitMs;
80
90
  private _playbackWaitMs;
81
91
  private _clearAll;
@@ -116,5 +126,5 @@ export declare class AnimationController {
116
126
  private _doAnnotationBracket;
117
127
  private _initPointer;
118
128
  }
119
- export declare const ANIMATION_CSS = "\n.ng, .gg, .tg, .ntg, .cg, .eg, .mdg {\n transform-box: fill-box;\n transform-origin: center;\n transition: filter 0.3s, opacity 0.35s;\n}\n\n/* highlight */\n.ng.hl path, .ng.hl rect, .ng.hl ellipse, .ng.hl polygon,\n.tg.hl path, .tg.hl rect,\n.ntg.hl path, .ntg.hl polygon,\n.cg.hl path, .cg.hl rect,\n.mdg.hl text,\n.eg.hl path, .eg.hl line, .eg.hl polygon { stroke-width: 2.8 !important; }\n\n.ng.hl, .tg.hl, .ntg.hl, .cg.hl, .mdg.hl, .eg.hl {\n animation: ng-pulse 1.4s ease-in-out infinite;\n}\n@keyframes ng-pulse {\n 0%, 100% { filter: drop-shadow(0 0 7px rgba(200,84,40,.6)); }\n 50% { filter: drop-shadow(0 0 14px rgba(200,84,40,.9)); }\n}\n\n/* fade */\n.ng.faded, .gg.faded, .tg.faded, .ntg.faded,\n.cg.faded, .eg.faded, .mdg.faded { opacity: 0.22; }\n\n.ng.hidden { opacity: 0; pointer-events: none; }\n.gg.gg-hidden { opacity: 0; }\n.tg.gg-hidden { opacity: 0; }\n.ntg.gg-hidden { opacity: 0; }\n.cg.gg-hidden { opacity: 0; }\n.mdg.gg-hidden { opacity: 0; }\n\n/* narration caption */\n.skm-caption { pointer-events: none; user-select: none; }\n";
129
+ export declare const ANIMATION_CSS = "\n.ng, .gg, .tg, .ntg, .cg, .eg, .mdg {\n transform-box: fill-box;\n transform-origin: center;\n transition: filter 0.3s, opacity 0.35s;\n}\n\n/* highlight */\n.ng.hl path, .ng.hl rect, .ng.hl ellipse, .ng.hl polygon,\n.tg.hl path, .tg.hl rect,\n.ntg.hl path, .ntg.hl polygon,\n.cg.hl path, .cg.hl rect,\n.mdg.hl text,\n.eg.hl path, .eg.hl line, .eg.hl polygon { stroke-width: 2.8 !important; }\n\n.ng.hl, .tg.hl, .ntg.hl, .cg.hl, .mdg.hl, .eg.hl {\n animation: ng-pulse 1.4s ease-in-out infinite;\n}\n@keyframes ng-pulse {\n 0%, 100% { filter: drop-shadow(0 0 7px rgba(200,84,40,.6)); }\n 50% { filter: drop-shadow(0 0 14px rgba(200,84,40,.9)); }\n}\n\n/* fade */\n.ng.faded, .gg.faded, .tg.faded, .ntg.faded,\n.cg.faded, .eg.faded, .mdg.faded { opacity: 0.22; }\n\n.ng.hidden { opacity: 0; pointer-events: none; }\n.gg.gg-hidden { opacity: 0; }\n.tg.gg-hidden { opacity: 0; }\n.ntg.gg-hidden { opacity: 0; }\n.cg.gg-hidden { opacity: 0; }\n.eg.gg-hidden { opacity: 0; }\n.mdg.gg-hidden { opacity: 0; }\n\n/* narration caption */\n.skm-caption { pointer-events: none; user-select: none; }\n";
120
130
  //# sourceMappingURL=index.d.ts.map
@@ -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
@@ -8280,6 +8280,36 @@ function setParentGroupData(el, groupId) {
8280
8280
  if (groupId)
8281
8281
  el.dataset.parentGroup = groupId;
8282
8282
  }
8283
+ function resolveEdgeEndpointKind(id, nm, tm, gm, cm) {
8284
+ if (nm.has(id))
8285
+ return "node";
8286
+ if (gm.has(id))
8287
+ return "group";
8288
+ if (tm.has(id))
8289
+ return "table";
8290
+ if (cm.has(id))
8291
+ return "chart";
8292
+ return null;
8293
+ }
8294
+ function collectEdgeGroupLineage(endpointId, endpointKind, parentGroups) {
8295
+ const lineage = [];
8296
+ let groupId = endpointKind === "group"
8297
+ ? endpointId
8298
+ : parentGroups.get(`${endpointKind}:${endpointId}`);
8299
+ while (groupId) {
8300
+ lineage.push(groupId);
8301
+ groupId = parentGroups.get(`group:${groupId}`);
8302
+ }
8303
+ return lineage;
8304
+ }
8305
+ function resolveEdgeParentGroupId(fromId, toId, nm, tm, gm, cm, parentGroups) {
8306
+ const fromKind = resolveEdgeEndpointKind(fromId, nm, tm, gm, cm);
8307
+ const toKind = resolveEdgeEndpointKind(toId, nm, tm, gm, cm);
8308
+ if (!fromKind || !toKind)
8309
+ return undefined;
8310
+ const toLineage = new Set(collectEdgeGroupLineage(toId, toKind, parentGroups));
8311
+ return collectEdgeGroupLineage(fromId, fromKind, parentGroups).find((groupId) => toLineage.has(groupId));
8312
+ }
8283
8313
  // ── Node shapes ───────────────────────────────────────────────────────────
8284
8314
  function renderShape$1(rc, n, palette) {
8285
8315
  const s = n.style ?? {};
@@ -8437,6 +8467,7 @@ function renderToSVG(sg, container, options = {}) {
8437
8467
  const [x1, y1] = getConnPoint(src, dstCX, dstCY, e.fromAnchor);
8438
8468
  const [x2, y2] = getConnPoint(dst, srcCX, srcCY, e.toAnchor);
8439
8469
  const eg = mkGroup(`edge-${e.from}-${e.to}`, "eg");
8470
+ setParentGroupData(eg, resolveEdgeParentGroupId(e.from, e.to, nm, tm, gmMap, cm, parentGroups));
8440
8471
  if (e.style?.opacity != null)
8441
8472
  eg.setAttribute("opacity", String(e.style.opacity));
8442
8473
  const len = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) || 1;
@@ -9483,7 +9514,7 @@ const getTableEl = (svg, id) => getEl(svg, `table-${id}`);
9483
9514
  const getNoteEl = (svg, id) => getEl(svg, `note-${id}`);
9484
9515
  const getChartEl = (svg, id) => getEl(svg, `chart-${id}`);
9485
9516
  const getMarkdownEl = (svg, id) => getEl(svg, `markdown-${id}`);
9486
- const POSITIONABLE_SELECTOR = ".ng, .gg, .tg, .ntg, .cg, .mdg";
9517
+ const POSITIONABLE_SELECTOR = ".ng, .gg, .tg, .ntg, .cg, .eg, .mdg";
9487
9518
  function resolveNonEdgeDrawEl(svg, target) {
9488
9519
  return (getGroupEl(svg, target) ??
9489
9520
  getTableEl(svg, target) ??
@@ -9991,8 +10022,12 @@ class AnimationController {
9991
10022
  this._rc = _rc;
9992
10023
  this._config = _config;
9993
10024
  this._step = -1;
10025
+ this._isPlaying = false;
10026
+ this._playRunId = 0;
9994
10027
  this._pendingStepTimers = new Set();
9995
10028
  this._pendingNarrationTimers = new Set();
10029
+ this._playbackDelayTimerId = null;
10030
+ this._resolvePlaybackDelay = null;
9996
10031
  this._transforms = new Map();
9997
10032
  this._listeners = [];
9998
10033
  // ── Narration caption ──
@@ -10008,6 +10043,7 @@ class AnimationController {
10008
10043
  // ── TTS ──
10009
10044
  this._tts = false;
10010
10045
  this._speechDone = null;
10046
+ this._resolveSpeechDone = null;
10011
10047
  this.drawTargetEdges = getDrawTargetEdgeIds(steps);
10012
10048
  this.drawTargetNodes = getDrawTargetNodeIds(steps);
10013
10049
  // Groups: non-edge draw steps whose target has a #group-{id} element in the SVG.
@@ -10072,8 +10108,16 @@ class AnimationController {
10072
10108
  _buildDrawStepIndex() {
10073
10109
  const drawStepIndexByElementId = new Map();
10074
10110
  forEachPlaybackStep(this.steps, (step, stepIndex) => {
10075
- if (step.action !== "draw" || parseEdgeTarget(step.target))
10111
+ if (step.action !== "draw")
10112
+ return;
10113
+ const edge = parseEdgeTarget(step.target);
10114
+ if (edge) {
10115
+ const edgeEl = getEdgeEl(this.svg, edge.from, edge.to);
10116
+ if (edgeEl && !drawStepIndexByElementId.has(edgeEl.id)) {
10117
+ drawStepIndexByElementId.set(edgeEl.id, stepIndex);
10118
+ }
10076
10119
  return;
10120
+ }
10077
10121
  const el = resolveNonEdgeDrawEl(this.svg, step.target);
10078
10122
  if (el && !drawStepIndexByElementId.has(el.id)) {
10079
10123
  drawStepIndexByElementId.set(el.id, stepIndex);
@@ -10231,6 +10275,9 @@ class AnimationController {
10231
10275
  get atEnd() {
10232
10276
  return this._step === this.steps.length - 1;
10233
10277
  }
10278
+ get isPlaying() {
10279
+ return this._isPlaying;
10280
+ }
10234
10281
  on(listener) {
10235
10282
  this._listeners.push(listener);
10236
10283
  return () => {
@@ -10248,12 +10295,14 @@ class AnimationController {
10248
10295
  l(e);
10249
10296
  }
10250
10297
  reset() {
10298
+ this.stop();
10251
10299
  this._step = -1;
10252
10300
  this._clearAll();
10253
10301
  this.emit("animation-reset");
10254
10302
  }
10255
10303
  /** Remove caption and annotation layer from the DOM */
10256
10304
  destroy() {
10305
+ this.stop();
10257
10306
  this._clearAll();
10258
10307
  this._captionEl?.remove();
10259
10308
  this._captionEl = null;
@@ -10264,16 +10313,11 @@ class AnimationController {
10264
10313
  this._pointerEl = null;
10265
10314
  }
10266
10315
  next() {
10267
- if (!this.canNext)
10268
- return false;
10269
- this._step++;
10270
- this._applyStep(this._step, false);
10271
- this.emit("step-change");
10272
- if (!this.canNext)
10273
- this.emit("animation-end");
10274
- return true;
10316
+ this.stop();
10317
+ return this._advanceNext();
10275
10318
  }
10276
10319
  prev() {
10320
+ this.stop();
10277
10321
  if (!this.canPrev)
10278
10322
  return false;
10279
10323
  this._step--;
@@ -10284,18 +10328,33 @@ class AnimationController {
10284
10328
  return true;
10285
10329
  }
10286
10330
  async play(msPerStep = 900) {
10331
+ if (this._isPlaying || !this.canNext)
10332
+ return;
10333
+ const runId = ++this._playRunId;
10334
+ this._isPlaying = true;
10287
10335
  this.emit("animation-start");
10288
- while (this.canNext) {
10289
- const nextStep = this.steps[this._step + 1];
10290
- this.next();
10291
- // Wait for timer AND speech to finish (whichever is longer)
10292
- await Promise.all([
10293
- new Promise((r) => setTimeout(r, this._playbackWaitMs(nextStep, msPerStep))),
10294
- this._speechDone ?? Promise.resolve(),
10295
- ]);
10336
+ try {
10337
+ while (this.canNext && this._playRunId === runId) {
10338
+ const nextStep = this.steps[this._step + 1];
10339
+ if (!this._advanceNext())
10340
+ break;
10341
+ if (this._playRunId !== runId)
10342
+ break;
10343
+ await Promise.all([
10344
+ this._waitForPlaybackDelay(this._playbackWaitMs(nextStep, msPerStep)),
10345
+ this._speechDone ?? Promise.resolve(),
10346
+ ]);
10347
+ }
10348
+ }
10349
+ finally {
10350
+ if (this._playRunId === runId) {
10351
+ this._isPlaying = false;
10352
+ this._cancelPlaybackDelay();
10353
+ }
10296
10354
  }
10297
10355
  }
10298
10356
  goTo(index) {
10357
+ this.stop();
10299
10358
  index = Math.max(-1, Math.min(this.steps.length - 1, index));
10300
10359
  if (index === this._step)
10301
10360
  return;
@@ -10309,6 +10368,30 @@ class AnimationController {
10309
10368
  }
10310
10369
  this.emit("step-change");
10311
10370
  }
10371
+ stop() {
10372
+ if (!this._isPlaying && !this._resolvePlaybackDelay) {
10373
+ this._clearPendingStepTimers();
10374
+ this._cancelNarrationTyping();
10375
+ this._cancelSpeech();
10376
+ return;
10377
+ }
10378
+ this._isPlaying = false;
10379
+ this._playRunId += 1;
10380
+ this._cancelPlaybackDelay();
10381
+ this._clearPendingStepTimers();
10382
+ this._cancelNarrationTyping();
10383
+ this._cancelSpeech();
10384
+ }
10385
+ _advanceNext() {
10386
+ if (!this.canNext)
10387
+ return false;
10388
+ this._step++;
10389
+ this._applyStep(this._step, false);
10390
+ this.emit("step-change");
10391
+ if (!this.canNext)
10392
+ this.emit("animation-end");
10393
+ return true;
10394
+ }
10312
10395
  _clearTimerBucket(bucket) {
10313
10396
  bucket.forEach((id) => window.clearTimeout(id));
10314
10397
  bucket.clear();
@@ -10334,6 +10417,34 @@ class AnimationController {
10334
10417
  _scheduleStep(fn, delayMs) {
10335
10418
  this._scheduleTimer(fn, delayMs, this._pendingStepTimers);
10336
10419
  }
10420
+ _waitForPlaybackDelay(delayMs) {
10421
+ this._cancelPlaybackDelay();
10422
+ return new Promise((resolve) => {
10423
+ let settled = false;
10424
+ const finish = () => {
10425
+ if (settled)
10426
+ return;
10427
+ settled = true;
10428
+ if (this._playbackDelayTimerId !== null) {
10429
+ window.clearTimeout(this._playbackDelayTimerId);
10430
+ this._playbackDelayTimerId = null;
10431
+ }
10432
+ if (this._resolvePlaybackDelay === finish) {
10433
+ this._resolvePlaybackDelay = null;
10434
+ }
10435
+ resolve();
10436
+ };
10437
+ this._resolvePlaybackDelay = finish;
10438
+ if (delayMs <= 0) {
10439
+ finish();
10440
+ return;
10441
+ }
10442
+ this._playbackDelayTimerId = window.setTimeout(finish, delayMs);
10443
+ });
10444
+ }
10445
+ _cancelPlaybackDelay() {
10446
+ this._resolvePlaybackDelay?.();
10447
+ }
10337
10448
  _stepWaitMs(step, fallbackMs) {
10338
10449
  const delay = Math.max(0, step.delay ?? 0);
10339
10450
  const duration = Math.max(0, step.duration ?? 0);
@@ -10375,6 +10486,7 @@ class AnimationController {
10375
10486
  return this._stepWaitMs(step, fallbackMs);
10376
10487
  }
10377
10488
  _clearAll() {
10489
+ this._cancelPlaybackDelay();
10378
10490
  this._clearPendingStepTimers();
10379
10491
  this._cancelNarrationTyping();
10380
10492
  this._cancelSpeech();
@@ -10705,6 +10817,7 @@ class AnimationController {
10705
10817
  const el = getEdgeEl(this.svg, edge.from, edge.to);
10706
10818
  if (!el)
10707
10819
  return;
10820
+ showDrawEl(el);
10708
10821
  if (silent) {
10709
10822
  revealEdgeInstant(el);
10710
10823
  requestAnimationFrame(() => requestAnimationFrame(() => {
@@ -10986,16 +11099,30 @@ class AnimationController {
10986
11099
  utter.rate = 0.95;
10987
11100
  utter.pitch = 1;
10988
11101
  utter.lang = "en-US";
10989
- // Track when speech actually finishes
11102
+ // Track when speech actually finishes so play() can block until the utterance ends.
10990
11103
  this._speechDone = new Promise((resolve) => {
10991
- utter.onend = () => resolve();
10992
- utter.onerror = () => resolve();
11104
+ let settled = false;
11105
+ const finish = () => {
11106
+ if (settled)
11107
+ return;
11108
+ settled = true;
11109
+ if (this._resolveSpeechDone === finish) {
11110
+ this._resolveSpeechDone = null;
11111
+ this._speechDone = null;
11112
+ }
11113
+ resolve();
11114
+ };
11115
+ this._resolveSpeechDone = finish;
11116
+ utter.onend = finish;
11117
+ utter.onerror = finish;
10993
11118
  });
10994
11119
  speechSynthesis.speak(utter);
10995
11120
  }
10996
11121
  _cancelSpeech() {
10997
11122
  if (typeof speechSynthesis !== "undefined")
10998
11123
  speechSynthesis.cancel();
11124
+ this._resolveSpeechDone?.();
11125
+ this._resolveSpeechDone = null;
10999
11126
  this._speechDone = null;
11000
11127
  }
11001
11128
  /** Pre-warm the speech engine with a silent utterance to eliminate cold-start delay */
@@ -11304,11 +11431,12 @@ const ANIMATION_CSS = `
11304
11431
  .cg.faded, .eg.faded, .mdg.faded { opacity: 0.22; }
11305
11432
 
11306
11433
  .ng.hidden { opacity: 0; pointer-events: none; }
11307
- .gg.gg-hidden { opacity: 0; }
11308
- .tg.gg-hidden { opacity: 0; }
11309
- .ntg.gg-hidden { opacity: 0; }
11310
- .cg.gg-hidden { opacity: 0; }
11311
- .mdg.gg-hidden { opacity: 0; }
11434
+ .gg.gg-hidden { opacity: 0; }
11435
+ .tg.gg-hidden { opacity: 0; }
11436
+ .ntg.gg-hidden { opacity: 0; }
11437
+ .cg.gg-hidden { opacity: 0; }
11438
+ .eg.gg-hidden { opacity: 0; }
11439
+ .mdg.gg-hidden { opacity: 0; }
11312
11440
 
11313
11441
  /* narration caption */
11314
11442
  .skm-caption { pointer-events: none; user-select: none; }
@@ -11804,7 +11932,13 @@ class SketchmarkCanvas {
11804
11932
  this.resetButton.addEventListener("click", () => this.resetAnimation());
11805
11933
  this.prevButton.addEventListener("click", () => this.prevStep());
11806
11934
  this.nextButton.addEventListener("click", () => this.nextStep());
11807
- this.playButton.addEventListener("click", () => void this.play());
11935
+ this.playButton.addEventListener("click", () => {
11936
+ if (this.playInFlight) {
11937
+ this.stopPlayback();
11938
+ return;
11939
+ }
11940
+ void this.play();
11941
+ });
11808
11942
  this.captionButton.addEventListener("click", () => this.setCaptionVisible(!this.showCaption));
11809
11943
  this.ttsButton.addEventListener("click", () => this.setTtsEnabled(!this.getTtsEnabled()));
11810
11944
  this.viewport.addEventListener("pointerdown", this.onPointerDown);
@@ -11868,6 +12002,7 @@ class SketchmarkCanvas {
11868
12002
  this.dsl = normalizeNewlines(nextDsl);
11869
12003
  this.clearError();
11870
12004
  this.mirroredEditor?.clearError();
12005
+ this.playInFlight = false;
11871
12006
  this.animUnsub?.();
11872
12007
  this.animUnsub = null;
11873
12008
  this.instance?.anim?.destroy();
@@ -11938,9 +12073,16 @@ class SketchmarkCanvas {
11938
12073
  this.syncAnimationUi();
11939
12074
  }
11940
12075
  }
12076
+ stopPlayback() {
12077
+ this.playInFlight = false;
12078
+ if (this.renderer === "svg")
12079
+ this.instance?.anim.stop();
12080
+ this.syncAnimationUi();
12081
+ }
11941
12082
  nextStep() {
11942
12083
  if (!this.instance || this.renderer !== "svg")
11943
12084
  return;
12085
+ this.playInFlight = false;
11944
12086
  this.instance.anim.next();
11945
12087
  this.syncAnimationUi();
11946
12088
  this.focusCurrentStep();
@@ -11948,6 +12090,7 @@ class SketchmarkCanvas {
11948
12090
  prevStep() {
11949
12091
  if (!this.instance || this.renderer !== "svg")
11950
12092
  return;
12093
+ this.playInFlight = false;
11951
12094
  this.instance.anim.prev();
11952
12095
  this.syncAnimationUi();
11953
12096
  this.focusCurrentStep();
@@ -11955,6 +12098,7 @@ class SketchmarkCanvas {
11955
12098
  resetAnimation() {
11956
12099
  if (!this.instance || this.renderer !== "svg")
11957
12100
  return;
12101
+ this.playInFlight = false;
11958
12102
  this.instance.anim.reset();
11959
12103
  this.syncAnimationUi();
11960
12104
  }
@@ -11983,6 +12127,7 @@ class SketchmarkCanvas {
11983
12127
  this.render();
11984
12128
  }
11985
12129
  destroy() {
12130
+ this.playInFlight = false;
11986
12131
  this.editorCleanup?.();
11987
12132
  this.animUnsub?.();
11988
12133
  this.instance?.anim?.destroy();
@@ -12057,6 +12202,9 @@ class SketchmarkCanvas {
12057
12202
  this.prevButton.disabled = true;
12058
12203
  this.nextButton.disabled = true;
12059
12204
  this.resetButton.disabled = true;
12205
+ this.playButton.textContent = "Play";
12206
+ this.playButton.classList.remove("is-active");
12207
+ this.playButton.setAttribute("aria-pressed", "false");
12060
12208
  this.playButton.disabled = true;
12061
12209
  this.syncToggleUi();
12062
12210
  return;
@@ -12066,7 +12214,10 @@ class SketchmarkCanvas {
12066
12214
  this.prevButton.disabled = !anim.canPrev;
12067
12215
  this.nextButton.disabled = !anim.canNext;
12068
12216
  this.resetButton.disabled = false;
12069
- this.playButton.disabled = this.playInFlight || !anim.canNext;
12217
+ this.playButton.textContent = this.playInFlight ? "Stop" : "Play";
12218
+ this.playButton.classList.toggle("is-active", this.playInFlight);
12219
+ this.playButton.setAttribute("aria-pressed", this.playInFlight ? "true" : "false");
12220
+ this.playButton.disabled = this.playInFlight ? false : !anim.canNext;
12070
12221
  this.syncToggleUi();
12071
12222
  }
12072
12223
  getStepTarget(stepItem) {
@@ -12972,6 +13123,10 @@ class SketchmarkEmbed {
12972
13123
  this.btnPrev.addEventListener("click", () => this.prevStep());
12973
13124
  this.btnNext.addEventListener("click", () => this.nextStep());
12974
13125
  this.btnPlay.addEventListener("click", () => {
13126
+ if (this.playInFlight) {
13127
+ this.stopPlayback();
13128
+ return;
13129
+ }
12975
13130
  void this.play();
12976
13131
  });
12977
13132
  this.btnCaption.addEventListener("click", () => this.setCaptionVisible(!this.showCaption));
@@ -13029,6 +13184,7 @@ class SketchmarkEmbed {
13029
13184
  }
13030
13185
  this.clearError();
13031
13186
  this.stopMotion();
13187
+ this.playInFlight = false;
13032
13188
  this.animUnsub?.();
13033
13189
  this.animUnsub = null;
13034
13190
  this.instance?.anim?.destroy();
@@ -13101,9 +13257,15 @@ class SketchmarkEmbed {
13101
13257
  this.syncControls();
13102
13258
  }
13103
13259
  }
13260
+ stopPlayback() {
13261
+ this.playInFlight = false;
13262
+ this.instance?.anim.stop();
13263
+ this.syncControls();
13264
+ }
13104
13265
  nextStep() {
13105
13266
  if (!this.instance)
13106
13267
  return;
13268
+ this.playInFlight = false;
13107
13269
  this.instance.anim.next();
13108
13270
  this.syncControls();
13109
13271
  if (this.options.autoFocus !== false && this.options.autoFocusOnStep !== false) {
@@ -13113,6 +13275,7 @@ class SketchmarkEmbed {
13113
13275
  prevStep() {
13114
13276
  if (!this.instance)
13115
13277
  return;
13278
+ this.playInFlight = false;
13116
13279
  this.instance.anim.prev();
13117
13280
  this.syncControls();
13118
13281
  if (this.options.autoFocus !== false && this.options.autoFocusOnStep !== false) {
@@ -13122,6 +13285,7 @@ class SketchmarkEmbed {
13122
13285
  resetAnimation() {
13123
13286
  if (!this.instance)
13124
13287
  return;
13288
+ this.playInFlight = false;
13125
13289
  this.instance.anim.reset();
13126
13290
  this.syncControls();
13127
13291
  }
@@ -13148,6 +13312,7 @@ class SketchmarkEmbed {
13148
13312
  }
13149
13313
  destroy() {
13150
13314
  this.stopMotion();
13315
+ this.playInFlight = false;
13151
13316
  this.animUnsub?.();
13152
13317
  this.instance?.anim?.destroy();
13153
13318
  this.instance = null;
@@ -13180,6 +13345,9 @@ class SketchmarkEmbed {
13180
13345
  this.btnRestart.disabled = true;
13181
13346
  this.btnPrev.disabled = true;
13182
13347
  this.btnNext.disabled = true;
13348
+ this.btnPlay.textContent = "Play";
13349
+ this.btnPlay.classList.remove("is-active");
13350
+ this.btnPlay.setAttribute("aria-pressed", "false");
13183
13351
  this.btnPlay.disabled = true;
13184
13352
  return;
13185
13353
  }
@@ -13188,7 +13356,10 @@ class SketchmarkEmbed {
13188
13356
  this.btnRestart.disabled = false;
13189
13357
  this.btnPrev.disabled = !anim.canPrev;
13190
13358
  this.btnNext.disabled = !anim.canNext;
13191
- this.btnPlay.disabled = this.playInFlight || !anim.canNext;
13359
+ this.btnPlay.textContent = this.playInFlight ? "Stop" : "Play";
13360
+ this.btnPlay.classList.toggle("is-active", this.playInFlight);
13361
+ this.btnPlay.setAttribute("aria-pressed", this.playInFlight ? "true" : "false");
13362
+ this.btnPlay.disabled = this.playInFlight ? false : !anim.canNext;
13192
13363
  }
13193
13364
  syncViewControls() {
13194
13365
  const hasView = !!this.instance?.svg;