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/dist/index.js
CHANGED
|
@@ -9989,8 +9989,12 @@ class AnimationController {
|
|
|
9989
9989
|
this._rc = _rc;
|
|
9990
9990
|
this._config = _config;
|
|
9991
9991
|
this._step = -1;
|
|
9992
|
+
this._isPlaying = false;
|
|
9993
|
+
this._playRunId = 0;
|
|
9992
9994
|
this._pendingStepTimers = new Set();
|
|
9993
9995
|
this._pendingNarrationTimers = new Set();
|
|
9996
|
+
this._playbackDelayTimerId = null;
|
|
9997
|
+
this._resolvePlaybackDelay = null;
|
|
9994
9998
|
this._transforms = new Map();
|
|
9995
9999
|
this._listeners = [];
|
|
9996
10000
|
// ── Narration caption ──
|
|
@@ -10006,6 +10010,7 @@ class AnimationController {
|
|
|
10006
10010
|
// ── TTS ──
|
|
10007
10011
|
this._tts = false;
|
|
10008
10012
|
this._speechDone = null;
|
|
10013
|
+
this._resolveSpeechDone = null;
|
|
10009
10014
|
this.drawTargetEdges = getDrawTargetEdgeIds(steps);
|
|
10010
10015
|
this.drawTargetNodes = getDrawTargetNodeIds(steps);
|
|
10011
10016
|
// Groups: non-edge draw steps whose target has a #group-{id} element in the SVG.
|
|
@@ -10229,6 +10234,9 @@ class AnimationController {
|
|
|
10229
10234
|
get atEnd() {
|
|
10230
10235
|
return this._step === this.steps.length - 1;
|
|
10231
10236
|
}
|
|
10237
|
+
get isPlaying() {
|
|
10238
|
+
return this._isPlaying;
|
|
10239
|
+
}
|
|
10232
10240
|
on(listener) {
|
|
10233
10241
|
this._listeners.push(listener);
|
|
10234
10242
|
return () => {
|
|
@@ -10246,12 +10254,14 @@ class AnimationController {
|
|
|
10246
10254
|
l(e);
|
|
10247
10255
|
}
|
|
10248
10256
|
reset() {
|
|
10257
|
+
this.stop();
|
|
10249
10258
|
this._step = -1;
|
|
10250
10259
|
this._clearAll();
|
|
10251
10260
|
this.emit("animation-reset");
|
|
10252
10261
|
}
|
|
10253
10262
|
/** Remove caption and annotation layer from the DOM */
|
|
10254
10263
|
destroy() {
|
|
10264
|
+
this.stop();
|
|
10255
10265
|
this._clearAll();
|
|
10256
10266
|
this._captionEl?.remove();
|
|
10257
10267
|
this._captionEl = null;
|
|
@@ -10262,16 +10272,11 @@ class AnimationController {
|
|
|
10262
10272
|
this._pointerEl = null;
|
|
10263
10273
|
}
|
|
10264
10274
|
next() {
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
this._step++;
|
|
10268
|
-
this._applyStep(this._step, false);
|
|
10269
|
-
this.emit("step-change");
|
|
10270
|
-
if (!this.canNext)
|
|
10271
|
-
this.emit("animation-end");
|
|
10272
|
-
return true;
|
|
10275
|
+
this.stop();
|
|
10276
|
+
return this._advanceNext();
|
|
10273
10277
|
}
|
|
10274
10278
|
prev() {
|
|
10279
|
+
this.stop();
|
|
10275
10280
|
if (!this.canPrev)
|
|
10276
10281
|
return false;
|
|
10277
10282
|
this._step--;
|
|
@@ -10282,18 +10287,33 @@ class AnimationController {
|
|
|
10282
10287
|
return true;
|
|
10283
10288
|
}
|
|
10284
10289
|
async play(msPerStep = 900) {
|
|
10290
|
+
if (this._isPlaying || !this.canNext)
|
|
10291
|
+
return;
|
|
10292
|
+
const runId = ++this._playRunId;
|
|
10293
|
+
this._isPlaying = true;
|
|
10285
10294
|
this.emit("animation-start");
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10295
|
+
try {
|
|
10296
|
+
while (this.canNext && this._playRunId === runId) {
|
|
10297
|
+
const nextStep = this.steps[this._step + 1];
|
|
10298
|
+
if (!this._advanceNext())
|
|
10299
|
+
break;
|
|
10300
|
+
if (this._playRunId !== runId)
|
|
10301
|
+
break;
|
|
10302
|
+
await Promise.all([
|
|
10303
|
+
this._waitForPlaybackDelay(this._playbackWaitMs(nextStep, msPerStep)),
|
|
10304
|
+
this._speechDone ?? Promise.resolve(),
|
|
10305
|
+
]);
|
|
10306
|
+
}
|
|
10307
|
+
}
|
|
10308
|
+
finally {
|
|
10309
|
+
if (this._playRunId === runId) {
|
|
10310
|
+
this._isPlaying = false;
|
|
10311
|
+
this._cancelPlaybackDelay();
|
|
10312
|
+
}
|
|
10294
10313
|
}
|
|
10295
10314
|
}
|
|
10296
10315
|
goTo(index) {
|
|
10316
|
+
this.stop();
|
|
10297
10317
|
index = Math.max(-1, Math.min(this.steps.length - 1, index));
|
|
10298
10318
|
if (index === this._step)
|
|
10299
10319
|
return;
|
|
@@ -10307,6 +10327,30 @@ class AnimationController {
|
|
|
10307
10327
|
}
|
|
10308
10328
|
this.emit("step-change");
|
|
10309
10329
|
}
|
|
10330
|
+
stop() {
|
|
10331
|
+
if (!this._isPlaying && !this._resolvePlaybackDelay) {
|
|
10332
|
+
this._clearPendingStepTimers();
|
|
10333
|
+
this._cancelNarrationTyping();
|
|
10334
|
+
this._cancelSpeech();
|
|
10335
|
+
return;
|
|
10336
|
+
}
|
|
10337
|
+
this._isPlaying = false;
|
|
10338
|
+
this._playRunId += 1;
|
|
10339
|
+
this._cancelPlaybackDelay();
|
|
10340
|
+
this._clearPendingStepTimers();
|
|
10341
|
+
this._cancelNarrationTyping();
|
|
10342
|
+
this._cancelSpeech();
|
|
10343
|
+
}
|
|
10344
|
+
_advanceNext() {
|
|
10345
|
+
if (!this.canNext)
|
|
10346
|
+
return false;
|
|
10347
|
+
this._step++;
|
|
10348
|
+
this._applyStep(this._step, false);
|
|
10349
|
+
this.emit("step-change");
|
|
10350
|
+
if (!this.canNext)
|
|
10351
|
+
this.emit("animation-end");
|
|
10352
|
+
return true;
|
|
10353
|
+
}
|
|
10310
10354
|
_clearTimerBucket(bucket) {
|
|
10311
10355
|
bucket.forEach((id) => window.clearTimeout(id));
|
|
10312
10356
|
bucket.clear();
|
|
@@ -10332,6 +10376,34 @@ class AnimationController {
|
|
|
10332
10376
|
_scheduleStep(fn, delayMs) {
|
|
10333
10377
|
this._scheduleTimer(fn, delayMs, this._pendingStepTimers);
|
|
10334
10378
|
}
|
|
10379
|
+
_waitForPlaybackDelay(delayMs) {
|
|
10380
|
+
this._cancelPlaybackDelay();
|
|
10381
|
+
return new Promise((resolve) => {
|
|
10382
|
+
let settled = false;
|
|
10383
|
+
const finish = () => {
|
|
10384
|
+
if (settled)
|
|
10385
|
+
return;
|
|
10386
|
+
settled = true;
|
|
10387
|
+
if (this._playbackDelayTimerId !== null) {
|
|
10388
|
+
window.clearTimeout(this._playbackDelayTimerId);
|
|
10389
|
+
this._playbackDelayTimerId = null;
|
|
10390
|
+
}
|
|
10391
|
+
if (this._resolvePlaybackDelay === finish) {
|
|
10392
|
+
this._resolvePlaybackDelay = null;
|
|
10393
|
+
}
|
|
10394
|
+
resolve();
|
|
10395
|
+
};
|
|
10396
|
+
this._resolvePlaybackDelay = finish;
|
|
10397
|
+
if (delayMs <= 0) {
|
|
10398
|
+
finish();
|
|
10399
|
+
return;
|
|
10400
|
+
}
|
|
10401
|
+
this._playbackDelayTimerId = window.setTimeout(finish, delayMs);
|
|
10402
|
+
});
|
|
10403
|
+
}
|
|
10404
|
+
_cancelPlaybackDelay() {
|
|
10405
|
+
this._resolvePlaybackDelay?.();
|
|
10406
|
+
}
|
|
10335
10407
|
_stepWaitMs(step, fallbackMs) {
|
|
10336
10408
|
const delay = Math.max(0, step.delay ?? 0);
|
|
10337
10409
|
const duration = Math.max(0, step.duration ?? 0);
|
|
@@ -10373,6 +10445,7 @@ class AnimationController {
|
|
|
10373
10445
|
return this._stepWaitMs(step, fallbackMs);
|
|
10374
10446
|
}
|
|
10375
10447
|
_clearAll() {
|
|
10448
|
+
this._cancelPlaybackDelay();
|
|
10376
10449
|
this._clearPendingStepTimers();
|
|
10377
10450
|
this._cancelNarrationTyping();
|
|
10378
10451
|
this._cancelSpeech();
|
|
@@ -10984,16 +11057,30 @@ class AnimationController {
|
|
|
10984
11057
|
utter.rate = 0.95;
|
|
10985
11058
|
utter.pitch = 1;
|
|
10986
11059
|
utter.lang = "en-US";
|
|
10987
|
-
// Track when speech actually finishes
|
|
11060
|
+
// Track when speech actually finishes so play() can block until the utterance ends.
|
|
10988
11061
|
this._speechDone = new Promise((resolve) => {
|
|
10989
|
-
|
|
10990
|
-
|
|
11062
|
+
let settled = false;
|
|
11063
|
+
const finish = () => {
|
|
11064
|
+
if (settled)
|
|
11065
|
+
return;
|
|
11066
|
+
settled = true;
|
|
11067
|
+
if (this._resolveSpeechDone === finish) {
|
|
11068
|
+
this._resolveSpeechDone = null;
|
|
11069
|
+
this._speechDone = null;
|
|
11070
|
+
}
|
|
11071
|
+
resolve();
|
|
11072
|
+
};
|
|
11073
|
+
this._resolveSpeechDone = finish;
|
|
11074
|
+
utter.onend = finish;
|
|
11075
|
+
utter.onerror = finish;
|
|
10991
11076
|
});
|
|
10992
11077
|
speechSynthesis.speak(utter);
|
|
10993
11078
|
}
|
|
10994
11079
|
_cancelSpeech() {
|
|
10995
11080
|
if (typeof speechSynthesis !== "undefined")
|
|
10996
11081
|
speechSynthesis.cancel();
|
|
11082
|
+
this._resolveSpeechDone?.();
|
|
11083
|
+
this._resolveSpeechDone = null;
|
|
10997
11084
|
this._speechDone = null;
|
|
10998
11085
|
}
|
|
10999
11086
|
/** Pre-warm the speech engine with a silent utterance to eliminate cold-start delay */
|
|
@@ -11802,7 +11889,13 @@ class SketchmarkCanvas {
|
|
|
11802
11889
|
this.resetButton.addEventListener("click", () => this.resetAnimation());
|
|
11803
11890
|
this.prevButton.addEventListener("click", () => this.prevStep());
|
|
11804
11891
|
this.nextButton.addEventListener("click", () => this.nextStep());
|
|
11805
|
-
this.playButton.addEventListener("click", () =>
|
|
11892
|
+
this.playButton.addEventListener("click", () => {
|
|
11893
|
+
if (this.playInFlight) {
|
|
11894
|
+
this.stopPlayback();
|
|
11895
|
+
return;
|
|
11896
|
+
}
|
|
11897
|
+
void this.play();
|
|
11898
|
+
});
|
|
11806
11899
|
this.captionButton.addEventListener("click", () => this.setCaptionVisible(!this.showCaption));
|
|
11807
11900
|
this.ttsButton.addEventListener("click", () => this.setTtsEnabled(!this.getTtsEnabled()));
|
|
11808
11901
|
this.viewport.addEventListener("pointerdown", this.onPointerDown);
|
|
@@ -11866,6 +11959,7 @@ class SketchmarkCanvas {
|
|
|
11866
11959
|
this.dsl = normalizeNewlines(nextDsl);
|
|
11867
11960
|
this.clearError();
|
|
11868
11961
|
this.mirroredEditor?.clearError();
|
|
11962
|
+
this.playInFlight = false;
|
|
11869
11963
|
this.animUnsub?.();
|
|
11870
11964
|
this.animUnsub = null;
|
|
11871
11965
|
this.instance?.anim?.destroy();
|
|
@@ -11936,9 +12030,16 @@ class SketchmarkCanvas {
|
|
|
11936
12030
|
this.syncAnimationUi();
|
|
11937
12031
|
}
|
|
11938
12032
|
}
|
|
12033
|
+
stopPlayback() {
|
|
12034
|
+
this.playInFlight = false;
|
|
12035
|
+
if (this.renderer === "svg")
|
|
12036
|
+
this.instance?.anim.stop();
|
|
12037
|
+
this.syncAnimationUi();
|
|
12038
|
+
}
|
|
11939
12039
|
nextStep() {
|
|
11940
12040
|
if (!this.instance || this.renderer !== "svg")
|
|
11941
12041
|
return;
|
|
12042
|
+
this.playInFlight = false;
|
|
11942
12043
|
this.instance.anim.next();
|
|
11943
12044
|
this.syncAnimationUi();
|
|
11944
12045
|
this.focusCurrentStep();
|
|
@@ -11946,6 +12047,7 @@ class SketchmarkCanvas {
|
|
|
11946
12047
|
prevStep() {
|
|
11947
12048
|
if (!this.instance || this.renderer !== "svg")
|
|
11948
12049
|
return;
|
|
12050
|
+
this.playInFlight = false;
|
|
11949
12051
|
this.instance.anim.prev();
|
|
11950
12052
|
this.syncAnimationUi();
|
|
11951
12053
|
this.focusCurrentStep();
|
|
@@ -11953,6 +12055,7 @@ class SketchmarkCanvas {
|
|
|
11953
12055
|
resetAnimation() {
|
|
11954
12056
|
if (!this.instance || this.renderer !== "svg")
|
|
11955
12057
|
return;
|
|
12058
|
+
this.playInFlight = false;
|
|
11956
12059
|
this.instance.anim.reset();
|
|
11957
12060
|
this.syncAnimationUi();
|
|
11958
12061
|
}
|
|
@@ -11981,6 +12084,7 @@ class SketchmarkCanvas {
|
|
|
11981
12084
|
this.render();
|
|
11982
12085
|
}
|
|
11983
12086
|
destroy() {
|
|
12087
|
+
this.playInFlight = false;
|
|
11984
12088
|
this.editorCleanup?.();
|
|
11985
12089
|
this.animUnsub?.();
|
|
11986
12090
|
this.instance?.anim?.destroy();
|
|
@@ -12055,6 +12159,9 @@ class SketchmarkCanvas {
|
|
|
12055
12159
|
this.prevButton.disabled = true;
|
|
12056
12160
|
this.nextButton.disabled = true;
|
|
12057
12161
|
this.resetButton.disabled = true;
|
|
12162
|
+
this.playButton.textContent = "Play";
|
|
12163
|
+
this.playButton.classList.remove("is-active");
|
|
12164
|
+
this.playButton.setAttribute("aria-pressed", "false");
|
|
12058
12165
|
this.playButton.disabled = true;
|
|
12059
12166
|
this.syncToggleUi();
|
|
12060
12167
|
return;
|
|
@@ -12064,7 +12171,10 @@ class SketchmarkCanvas {
|
|
|
12064
12171
|
this.prevButton.disabled = !anim.canPrev;
|
|
12065
12172
|
this.nextButton.disabled = !anim.canNext;
|
|
12066
12173
|
this.resetButton.disabled = false;
|
|
12067
|
-
this.playButton.
|
|
12174
|
+
this.playButton.textContent = this.playInFlight ? "Stop" : "Play";
|
|
12175
|
+
this.playButton.classList.toggle("is-active", this.playInFlight);
|
|
12176
|
+
this.playButton.setAttribute("aria-pressed", this.playInFlight ? "true" : "false");
|
|
12177
|
+
this.playButton.disabled = this.playInFlight ? false : !anim.canNext;
|
|
12068
12178
|
this.syncToggleUi();
|
|
12069
12179
|
}
|
|
12070
12180
|
getStepTarget(stepItem) {
|
|
@@ -12970,6 +13080,10 @@ class SketchmarkEmbed {
|
|
|
12970
13080
|
this.btnPrev.addEventListener("click", () => this.prevStep());
|
|
12971
13081
|
this.btnNext.addEventListener("click", () => this.nextStep());
|
|
12972
13082
|
this.btnPlay.addEventListener("click", () => {
|
|
13083
|
+
if (this.playInFlight) {
|
|
13084
|
+
this.stopPlayback();
|
|
13085
|
+
return;
|
|
13086
|
+
}
|
|
12973
13087
|
void this.play();
|
|
12974
13088
|
});
|
|
12975
13089
|
this.btnCaption.addEventListener("click", () => this.setCaptionVisible(!this.showCaption));
|
|
@@ -13027,6 +13141,7 @@ class SketchmarkEmbed {
|
|
|
13027
13141
|
}
|
|
13028
13142
|
this.clearError();
|
|
13029
13143
|
this.stopMotion();
|
|
13144
|
+
this.playInFlight = false;
|
|
13030
13145
|
this.animUnsub?.();
|
|
13031
13146
|
this.animUnsub = null;
|
|
13032
13147
|
this.instance?.anim?.destroy();
|
|
@@ -13099,9 +13214,15 @@ class SketchmarkEmbed {
|
|
|
13099
13214
|
this.syncControls();
|
|
13100
13215
|
}
|
|
13101
13216
|
}
|
|
13217
|
+
stopPlayback() {
|
|
13218
|
+
this.playInFlight = false;
|
|
13219
|
+
this.instance?.anim.stop();
|
|
13220
|
+
this.syncControls();
|
|
13221
|
+
}
|
|
13102
13222
|
nextStep() {
|
|
13103
13223
|
if (!this.instance)
|
|
13104
13224
|
return;
|
|
13225
|
+
this.playInFlight = false;
|
|
13105
13226
|
this.instance.anim.next();
|
|
13106
13227
|
this.syncControls();
|
|
13107
13228
|
if (this.options.autoFocus !== false && this.options.autoFocusOnStep !== false) {
|
|
@@ -13111,6 +13232,7 @@ class SketchmarkEmbed {
|
|
|
13111
13232
|
prevStep() {
|
|
13112
13233
|
if (!this.instance)
|
|
13113
13234
|
return;
|
|
13235
|
+
this.playInFlight = false;
|
|
13114
13236
|
this.instance.anim.prev();
|
|
13115
13237
|
this.syncControls();
|
|
13116
13238
|
if (this.options.autoFocus !== false && this.options.autoFocusOnStep !== false) {
|
|
@@ -13120,6 +13242,7 @@ class SketchmarkEmbed {
|
|
|
13120
13242
|
resetAnimation() {
|
|
13121
13243
|
if (!this.instance)
|
|
13122
13244
|
return;
|
|
13245
|
+
this.playInFlight = false;
|
|
13123
13246
|
this.instance.anim.reset();
|
|
13124
13247
|
this.syncControls();
|
|
13125
13248
|
}
|
|
@@ -13146,6 +13269,7 @@ class SketchmarkEmbed {
|
|
|
13146
13269
|
}
|
|
13147
13270
|
destroy() {
|
|
13148
13271
|
this.stopMotion();
|
|
13272
|
+
this.playInFlight = false;
|
|
13149
13273
|
this.animUnsub?.();
|
|
13150
13274
|
this.instance?.anim?.destroy();
|
|
13151
13275
|
this.instance = null;
|
|
@@ -13178,6 +13302,9 @@ class SketchmarkEmbed {
|
|
|
13178
13302
|
this.btnRestart.disabled = true;
|
|
13179
13303
|
this.btnPrev.disabled = true;
|
|
13180
13304
|
this.btnNext.disabled = true;
|
|
13305
|
+
this.btnPlay.textContent = "Play";
|
|
13306
|
+
this.btnPlay.classList.remove("is-active");
|
|
13307
|
+
this.btnPlay.setAttribute("aria-pressed", "false");
|
|
13181
13308
|
this.btnPlay.disabled = true;
|
|
13182
13309
|
return;
|
|
13183
13310
|
}
|
|
@@ -13186,7 +13313,10 @@ class SketchmarkEmbed {
|
|
|
13186
13313
|
this.btnRestart.disabled = false;
|
|
13187
13314
|
this.btnPrev.disabled = !anim.canPrev;
|
|
13188
13315
|
this.btnNext.disabled = !anim.canNext;
|
|
13189
|
-
this.btnPlay.
|
|
13316
|
+
this.btnPlay.textContent = this.playInFlight ? "Stop" : "Play";
|
|
13317
|
+
this.btnPlay.classList.toggle("is-active", this.playInFlight);
|
|
13318
|
+
this.btnPlay.setAttribute("aria-pressed", this.playInFlight ? "true" : "false");
|
|
13319
|
+
this.btnPlay.disabled = this.playInFlight ? false : !anim.canNext;
|
|
13190
13320
|
}
|
|
13191
13321
|
syncViewControls() {
|
|
13192
13322
|
const hasView = !!this.instance?.svg;
|