sketchmark 1.3.5 → 1.3.6
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 +10 -8
- package/dist/animation/index.d.ts +10 -0
- package/dist/animation/index.d.ts.map +1 -1
- package/dist/index.cjs +152 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +152 -22
- package/dist/index.js.map +1 -1
- package/dist/sketchmark.iife.js +152 -22
- package/dist/ui/canvas.d.ts +1 -0
- package/dist/ui/canvas.d.ts.map +1 -1
- package/dist/ui/embed.d.ts +1 -0
- package/dist/ui/embed.d.ts.map +1 -1
- package/package.json +1 -1
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.
|
|
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
|
|
@@ -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;
|
|
@@ -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;AA+a5D,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAQtE;AACD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAOtE;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAOvE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAOtE;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAOvE;AAsKD,qBAAa,mBAAmB;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/animation/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -9991,8 +9991,12 @@ class AnimationController {
|
|
|
9991
9991
|
this._rc = _rc;
|
|
9992
9992
|
this._config = _config;
|
|
9993
9993
|
this._step = -1;
|
|
9994
|
+
this._isPlaying = false;
|
|
9995
|
+
this._playRunId = 0;
|
|
9994
9996
|
this._pendingStepTimers = new Set();
|
|
9995
9997
|
this._pendingNarrationTimers = new Set();
|
|
9998
|
+
this._playbackDelayTimerId = null;
|
|
9999
|
+
this._resolvePlaybackDelay = null;
|
|
9996
10000
|
this._transforms = new Map();
|
|
9997
10001
|
this._listeners = [];
|
|
9998
10002
|
// ── Narration caption ──
|
|
@@ -10008,6 +10012,7 @@ class AnimationController {
|
|
|
10008
10012
|
// ── TTS ──
|
|
10009
10013
|
this._tts = false;
|
|
10010
10014
|
this._speechDone = null;
|
|
10015
|
+
this._resolveSpeechDone = null;
|
|
10011
10016
|
this.drawTargetEdges = getDrawTargetEdgeIds(steps);
|
|
10012
10017
|
this.drawTargetNodes = getDrawTargetNodeIds(steps);
|
|
10013
10018
|
// Groups: non-edge draw steps whose target has a #group-{id} element in the SVG.
|
|
@@ -10231,6 +10236,9 @@ class AnimationController {
|
|
|
10231
10236
|
get atEnd() {
|
|
10232
10237
|
return this._step === this.steps.length - 1;
|
|
10233
10238
|
}
|
|
10239
|
+
get isPlaying() {
|
|
10240
|
+
return this._isPlaying;
|
|
10241
|
+
}
|
|
10234
10242
|
on(listener) {
|
|
10235
10243
|
this._listeners.push(listener);
|
|
10236
10244
|
return () => {
|
|
@@ -10248,12 +10256,14 @@ class AnimationController {
|
|
|
10248
10256
|
l(e);
|
|
10249
10257
|
}
|
|
10250
10258
|
reset() {
|
|
10259
|
+
this.stop();
|
|
10251
10260
|
this._step = -1;
|
|
10252
10261
|
this._clearAll();
|
|
10253
10262
|
this.emit("animation-reset");
|
|
10254
10263
|
}
|
|
10255
10264
|
/** Remove caption and annotation layer from the DOM */
|
|
10256
10265
|
destroy() {
|
|
10266
|
+
this.stop();
|
|
10257
10267
|
this._clearAll();
|
|
10258
10268
|
this._captionEl?.remove();
|
|
10259
10269
|
this._captionEl = null;
|
|
@@ -10264,16 +10274,11 @@ class AnimationController {
|
|
|
10264
10274
|
this._pointerEl = null;
|
|
10265
10275
|
}
|
|
10266
10276
|
next() {
|
|
10267
|
-
|
|
10268
|
-
|
|
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;
|
|
10277
|
+
this.stop();
|
|
10278
|
+
return this._advanceNext();
|
|
10275
10279
|
}
|
|
10276
10280
|
prev() {
|
|
10281
|
+
this.stop();
|
|
10277
10282
|
if (!this.canPrev)
|
|
10278
10283
|
return false;
|
|
10279
10284
|
this._step--;
|
|
@@ -10284,18 +10289,33 @@ class AnimationController {
|
|
|
10284
10289
|
return true;
|
|
10285
10290
|
}
|
|
10286
10291
|
async play(msPerStep = 900) {
|
|
10292
|
+
if (this._isPlaying || !this.canNext)
|
|
10293
|
+
return;
|
|
10294
|
+
const runId = ++this._playRunId;
|
|
10295
|
+
this._isPlaying = true;
|
|
10287
10296
|
this.emit("animation-start");
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
|
|
10297
|
+
try {
|
|
10298
|
+
while (this.canNext && this._playRunId === runId) {
|
|
10299
|
+
const nextStep = this.steps[this._step + 1];
|
|
10300
|
+
if (!this._advanceNext())
|
|
10301
|
+
break;
|
|
10302
|
+
if (this._playRunId !== runId)
|
|
10303
|
+
break;
|
|
10304
|
+
await Promise.all([
|
|
10305
|
+
this._waitForPlaybackDelay(this._playbackWaitMs(nextStep, msPerStep)),
|
|
10306
|
+
this._speechDone ?? Promise.resolve(),
|
|
10307
|
+
]);
|
|
10308
|
+
}
|
|
10309
|
+
}
|
|
10310
|
+
finally {
|
|
10311
|
+
if (this._playRunId === runId) {
|
|
10312
|
+
this._isPlaying = false;
|
|
10313
|
+
this._cancelPlaybackDelay();
|
|
10314
|
+
}
|
|
10296
10315
|
}
|
|
10297
10316
|
}
|
|
10298
10317
|
goTo(index) {
|
|
10318
|
+
this.stop();
|
|
10299
10319
|
index = Math.max(-1, Math.min(this.steps.length - 1, index));
|
|
10300
10320
|
if (index === this._step)
|
|
10301
10321
|
return;
|
|
@@ -10309,6 +10329,30 @@ class AnimationController {
|
|
|
10309
10329
|
}
|
|
10310
10330
|
this.emit("step-change");
|
|
10311
10331
|
}
|
|
10332
|
+
stop() {
|
|
10333
|
+
if (!this._isPlaying && !this._resolvePlaybackDelay) {
|
|
10334
|
+
this._clearPendingStepTimers();
|
|
10335
|
+
this._cancelNarrationTyping();
|
|
10336
|
+
this._cancelSpeech();
|
|
10337
|
+
return;
|
|
10338
|
+
}
|
|
10339
|
+
this._isPlaying = false;
|
|
10340
|
+
this._playRunId += 1;
|
|
10341
|
+
this._cancelPlaybackDelay();
|
|
10342
|
+
this._clearPendingStepTimers();
|
|
10343
|
+
this._cancelNarrationTyping();
|
|
10344
|
+
this._cancelSpeech();
|
|
10345
|
+
}
|
|
10346
|
+
_advanceNext() {
|
|
10347
|
+
if (!this.canNext)
|
|
10348
|
+
return false;
|
|
10349
|
+
this._step++;
|
|
10350
|
+
this._applyStep(this._step, false);
|
|
10351
|
+
this.emit("step-change");
|
|
10352
|
+
if (!this.canNext)
|
|
10353
|
+
this.emit("animation-end");
|
|
10354
|
+
return true;
|
|
10355
|
+
}
|
|
10312
10356
|
_clearTimerBucket(bucket) {
|
|
10313
10357
|
bucket.forEach((id) => window.clearTimeout(id));
|
|
10314
10358
|
bucket.clear();
|
|
@@ -10334,6 +10378,34 @@ class AnimationController {
|
|
|
10334
10378
|
_scheduleStep(fn, delayMs) {
|
|
10335
10379
|
this._scheduleTimer(fn, delayMs, this._pendingStepTimers);
|
|
10336
10380
|
}
|
|
10381
|
+
_waitForPlaybackDelay(delayMs) {
|
|
10382
|
+
this._cancelPlaybackDelay();
|
|
10383
|
+
return new Promise((resolve) => {
|
|
10384
|
+
let settled = false;
|
|
10385
|
+
const finish = () => {
|
|
10386
|
+
if (settled)
|
|
10387
|
+
return;
|
|
10388
|
+
settled = true;
|
|
10389
|
+
if (this._playbackDelayTimerId !== null) {
|
|
10390
|
+
window.clearTimeout(this._playbackDelayTimerId);
|
|
10391
|
+
this._playbackDelayTimerId = null;
|
|
10392
|
+
}
|
|
10393
|
+
if (this._resolvePlaybackDelay === finish) {
|
|
10394
|
+
this._resolvePlaybackDelay = null;
|
|
10395
|
+
}
|
|
10396
|
+
resolve();
|
|
10397
|
+
};
|
|
10398
|
+
this._resolvePlaybackDelay = finish;
|
|
10399
|
+
if (delayMs <= 0) {
|
|
10400
|
+
finish();
|
|
10401
|
+
return;
|
|
10402
|
+
}
|
|
10403
|
+
this._playbackDelayTimerId = window.setTimeout(finish, delayMs);
|
|
10404
|
+
});
|
|
10405
|
+
}
|
|
10406
|
+
_cancelPlaybackDelay() {
|
|
10407
|
+
this._resolvePlaybackDelay?.();
|
|
10408
|
+
}
|
|
10337
10409
|
_stepWaitMs(step, fallbackMs) {
|
|
10338
10410
|
const delay = Math.max(0, step.delay ?? 0);
|
|
10339
10411
|
const duration = Math.max(0, step.duration ?? 0);
|
|
@@ -10375,6 +10447,7 @@ class AnimationController {
|
|
|
10375
10447
|
return this._stepWaitMs(step, fallbackMs);
|
|
10376
10448
|
}
|
|
10377
10449
|
_clearAll() {
|
|
10450
|
+
this._cancelPlaybackDelay();
|
|
10378
10451
|
this._clearPendingStepTimers();
|
|
10379
10452
|
this._cancelNarrationTyping();
|
|
10380
10453
|
this._cancelSpeech();
|
|
@@ -10986,16 +11059,30 @@ class AnimationController {
|
|
|
10986
11059
|
utter.rate = 0.95;
|
|
10987
11060
|
utter.pitch = 1;
|
|
10988
11061
|
utter.lang = "en-US";
|
|
10989
|
-
// Track when speech actually finishes
|
|
11062
|
+
// Track when speech actually finishes so play() can block until the utterance ends.
|
|
10990
11063
|
this._speechDone = new Promise((resolve) => {
|
|
10991
|
-
|
|
10992
|
-
|
|
11064
|
+
let settled = false;
|
|
11065
|
+
const finish = () => {
|
|
11066
|
+
if (settled)
|
|
11067
|
+
return;
|
|
11068
|
+
settled = true;
|
|
11069
|
+
if (this._resolveSpeechDone === finish) {
|
|
11070
|
+
this._resolveSpeechDone = null;
|
|
11071
|
+
this._speechDone = null;
|
|
11072
|
+
}
|
|
11073
|
+
resolve();
|
|
11074
|
+
};
|
|
11075
|
+
this._resolveSpeechDone = finish;
|
|
11076
|
+
utter.onend = finish;
|
|
11077
|
+
utter.onerror = finish;
|
|
10993
11078
|
});
|
|
10994
11079
|
speechSynthesis.speak(utter);
|
|
10995
11080
|
}
|
|
10996
11081
|
_cancelSpeech() {
|
|
10997
11082
|
if (typeof speechSynthesis !== "undefined")
|
|
10998
11083
|
speechSynthesis.cancel();
|
|
11084
|
+
this._resolveSpeechDone?.();
|
|
11085
|
+
this._resolveSpeechDone = null;
|
|
10999
11086
|
this._speechDone = null;
|
|
11000
11087
|
}
|
|
11001
11088
|
/** Pre-warm the speech engine with a silent utterance to eliminate cold-start delay */
|
|
@@ -11804,7 +11891,13 @@ class SketchmarkCanvas {
|
|
|
11804
11891
|
this.resetButton.addEventListener("click", () => this.resetAnimation());
|
|
11805
11892
|
this.prevButton.addEventListener("click", () => this.prevStep());
|
|
11806
11893
|
this.nextButton.addEventListener("click", () => this.nextStep());
|
|
11807
|
-
this.playButton.addEventListener("click", () =>
|
|
11894
|
+
this.playButton.addEventListener("click", () => {
|
|
11895
|
+
if (this.playInFlight) {
|
|
11896
|
+
this.stopPlayback();
|
|
11897
|
+
return;
|
|
11898
|
+
}
|
|
11899
|
+
void this.play();
|
|
11900
|
+
});
|
|
11808
11901
|
this.captionButton.addEventListener("click", () => this.setCaptionVisible(!this.showCaption));
|
|
11809
11902
|
this.ttsButton.addEventListener("click", () => this.setTtsEnabled(!this.getTtsEnabled()));
|
|
11810
11903
|
this.viewport.addEventListener("pointerdown", this.onPointerDown);
|
|
@@ -11868,6 +11961,7 @@ class SketchmarkCanvas {
|
|
|
11868
11961
|
this.dsl = normalizeNewlines(nextDsl);
|
|
11869
11962
|
this.clearError();
|
|
11870
11963
|
this.mirroredEditor?.clearError();
|
|
11964
|
+
this.playInFlight = false;
|
|
11871
11965
|
this.animUnsub?.();
|
|
11872
11966
|
this.animUnsub = null;
|
|
11873
11967
|
this.instance?.anim?.destroy();
|
|
@@ -11938,9 +12032,16 @@ class SketchmarkCanvas {
|
|
|
11938
12032
|
this.syncAnimationUi();
|
|
11939
12033
|
}
|
|
11940
12034
|
}
|
|
12035
|
+
stopPlayback() {
|
|
12036
|
+
this.playInFlight = false;
|
|
12037
|
+
if (this.renderer === "svg")
|
|
12038
|
+
this.instance?.anim.stop();
|
|
12039
|
+
this.syncAnimationUi();
|
|
12040
|
+
}
|
|
11941
12041
|
nextStep() {
|
|
11942
12042
|
if (!this.instance || this.renderer !== "svg")
|
|
11943
12043
|
return;
|
|
12044
|
+
this.playInFlight = false;
|
|
11944
12045
|
this.instance.anim.next();
|
|
11945
12046
|
this.syncAnimationUi();
|
|
11946
12047
|
this.focusCurrentStep();
|
|
@@ -11948,6 +12049,7 @@ class SketchmarkCanvas {
|
|
|
11948
12049
|
prevStep() {
|
|
11949
12050
|
if (!this.instance || this.renderer !== "svg")
|
|
11950
12051
|
return;
|
|
12052
|
+
this.playInFlight = false;
|
|
11951
12053
|
this.instance.anim.prev();
|
|
11952
12054
|
this.syncAnimationUi();
|
|
11953
12055
|
this.focusCurrentStep();
|
|
@@ -11955,6 +12057,7 @@ class SketchmarkCanvas {
|
|
|
11955
12057
|
resetAnimation() {
|
|
11956
12058
|
if (!this.instance || this.renderer !== "svg")
|
|
11957
12059
|
return;
|
|
12060
|
+
this.playInFlight = false;
|
|
11958
12061
|
this.instance.anim.reset();
|
|
11959
12062
|
this.syncAnimationUi();
|
|
11960
12063
|
}
|
|
@@ -11983,6 +12086,7 @@ class SketchmarkCanvas {
|
|
|
11983
12086
|
this.render();
|
|
11984
12087
|
}
|
|
11985
12088
|
destroy() {
|
|
12089
|
+
this.playInFlight = false;
|
|
11986
12090
|
this.editorCleanup?.();
|
|
11987
12091
|
this.animUnsub?.();
|
|
11988
12092
|
this.instance?.anim?.destroy();
|
|
@@ -12057,6 +12161,9 @@ class SketchmarkCanvas {
|
|
|
12057
12161
|
this.prevButton.disabled = true;
|
|
12058
12162
|
this.nextButton.disabled = true;
|
|
12059
12163
|
this.resetButton.disabled = true;
|
|
12164
|
+
this.playButton.textContent = "Play";
|
|
12165
|
+
this.playButton.classList.remove("is-active");
|
|
12166
|
+
this.playButton.setAttribute("aria-pressed", "false");
|
|
12060
12167
|
this.playButton.disabled = true;
|
|
12061
12168
|
this.syncToggleUi();
|
|
12062
12169
|
return;
|
|
@@ -12066,7 +12173,10 @@ class SketchmarkCanvas {
|
|
|
12066
12173
|
this.prevButton.disabled = !anim.canPrev;
|
|
12067
12174
|
this.nextButton.disabled = !anim.canNext;
|
|
12068
12175
|
this.resetButton.disabled = false;
|
|
12069
|
-
this.playButton.
|
|
12176
|
+
this.playButton.textContent = this.playInFlight ? "Stop" : "Play";
|
|
12177
|
+
this.playButton.classList.toggle("is-active", this.playInFlight);
|
|
12178
|
+
this.playButton.setAttribute("aria-pressed", this.playInFlight ? "true" : "false");
|
|
12179
|
+
this.playButton.disabled = this.playInFlight ? false : !anim.canNext;
|
|
12070
12180
|
this.syncToggleUi();
|
|
12071
12181
|
}
|
|
12072
12182
|
getStepTarget(stepItem) {
|
|
@@ -12972,6 +13082,10 @@ class SketchmarkEmbed {
|
|
|
12972
13082
|
this.btnPrev.addEventListener("click", () => this.prevStep());
|
|
12973
13083
|
this.btnNext.addEventListener("click", () => this.nextStep());
|
|
12974
13084
|
this.btnPlay.addEventListener("click", () => {
|
|
13085
|
+
if (this.playInFlight) {
|
|
13086
|
+
this.stopPlayback();
|
|
13087
|
+
return;
|
|
13088
|
+
}
|
|
12975
13089
|
void this.play();
|
|
12976
13090
|
});
|
|
12977
13091
|
this.btnCaption.addEventListener("click", () => this.setCaptionVisible(!this.showCaption));
|
|
@@ -13029,6 +13143,7 @@ class SketchmarkEmbed {
|
|
|
13029
13143
|
}
|
|
13030
13144
|
this.clearError();
|
|
13031
13145
|
this.stopMotion();
|
|
13146
|
+
this.playInFlight = false;
|
|
13032
13147
|
this.animUnsub?.();
|
|
13033
13148
|
this.animUnsub = null;
|
|
13034
13149
|
this.instance?.anim?.destroy();
|
|
@@ -13101,9 +13216,15 @@ class SketchmarkEmbed {
|
|
|
13101
13216
|
this.syncControls();
|
|
13102
13217
|
}
|
|
13103
13218
|
}
|
|
13219
|
+
stopPlayback() {
|
|
13220
|
+
this.playInFlight = false;
|
|
13221
|
+
this.instance?.anim.stop();
|
|
13222
|
+
this.syncControls();
|
|
13223
|
+
}
|
|
13104
13224
|
nextStep() {
|
|
13105
13225
|
if (!this.instance)
|
|
13106
13226
|
return;
|
|
13227
|
+
this.playInFlight = false;
|
|
13107
13228
|
this.instance.anim.next();
|
|
13108
13229
|
this.syncControls();
|
|
13109
13230
|
if (this.options.autoFocus !== false && this.options.autoFocusOnStep !== false) {
|
|
@@ -13113,6 +13234,7 @@ class SketchmarkEmbed {
|
|
|
13113
13234
|
prevStep() {
|
|
13114
13235
|
if (!this.instance)
|
|
13115
13236
|
return;
|
|
13237
|
+
this.playInFlight = false;
|
|
13116
13238
|
this.instance.anim.prev();
|
|
13117
13239
|
this.syncControls();
|
|
13118
13240
|
if (this.options.autoFocus !== false && this.options.autoFocusOnStep !== false) {
|
|
@@ -13122,6 +13244,7 @@ class SketchmarkEmbed {
|
|
|
13122
13244
|
resetAnimation() {
|
|
13123
13245
|
if (!this.instance)
|
|
13124
13246
|
return;
|
|
13247
|
+
this.playInFlight = false;
|
|
13125
13248
|
this.instance.anim.reset();
|
|
13126
13249
|
this.syncControls();
|
|
13127
13250
|
}
|
|
@@ -13148,6 +13271,7 @@ class SketchmarkEmbed {
|
|
|
13148
13271
|
}
|
|
13149
13272
|
destroy() {
|
|
13150
13273
|
this.stopMotion();
|
|
13274
|
+
this.playInFlight = false;
|
|
13151
13275
|
this.animUnsub?.();
|
|
13152
13276
|
this.instance?.anim?.destroy();
|
|
13153
13277
|
this.instance = null;
|
|
@@ -13180,6 +13304,9 @@ class SketchmarkEmbed {
|
|
|
13180
13304
|
this.btnRestart.disabled = true;
|
|
13181
13305
|
this.btnPrev.disabled = true;
|
|
13182
13306
|
this.btnNext.disabled = true;
|
|
13307
|
+
this.btnPlay.textContent = "Play";
|
|
13308
|
+
this.btnPlay.classList.remove("is-active");
|
|
13309
|
+
this.btnPlay.setAttribute("aria-pressed", "false");
|
|
13183
13310
|
this.btnPlay.disabled = true;
|
|
13184
13311
|
return;
|
|
13185
13312
|
}
|
|
@@ -13188,7 +13315,10 @@ class SketchmarkEmbed {
|
|
|
13188
13315
|
this.btnRestart.disabled = false;
|
|
13189
13316
|
this.btnPrev.disabled = !anim.canPrev;
|
|
13190
13317
|
this.btnNext.disabled = !anim.canNext;
|
|
13191
|
-
this.btnPlay.
|
|
13318
|
+
this.btnPlay.textContent = this.playInFlight ? "Stop" : "Play";
|
|
13319
|
+
this.btnPlay.classList.toggle("is-active", this.playInFlight);
|
|
13320
|
+
this.btnPlay.setAttribute("aria-pressed", this.playInFlight ? "true" : "false");
|
|
13321
|
+
this.btnPlay.disabled = this.playInFlight ? false : !anim.canNext;
|
|
13192
13322
|
}
|
|
13193
13323
|
syncViewControls() {
|
|
13194
13324
|
const hasView = !!this.instance?.svg;
|