@phin-tech/dottie 0.6.3 → 0.7.0
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/PARITY.md +3 -0
- package/README.md +8 -3
- package/dist/compile.js +30 -7
- package/dist/dottie-compile.mjs +31 -8
- package/dist/dottie-player.js +145 -27
- package/dist/dottie-player.min.js +46 -46
- package/dist/dottie.js +145 -27
- package/dist/types/clicks.d.ts +8 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/types/model.d.ts +10 -1
- package/dist/types/player.d.ts +19 -2
- package/dist/types/state.d.ts +20 -0
- package/dist/types/timeline.d.ts +5 -3
- package/package.json +1 -1
package/dist/dottie.js
CHANGED
|
@@ -1,15 +1,69 @@
|
|
|
1
|
-
/*! dottie.js 0.
|
|
1
|
+
/*! dottie.js 0.7.0 | Apache-2.0 | https://github.com/phin-tech/dottie */
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __defNormalProp = (obj, key, value2) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value: value2 }) : obj[key] = value2;
|
|
4
4
|
var __publicField = (obj, key, value2) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value2);
|
|
5
5
|
|
|
6
|
+
// src/state.ts
|
|
7
|
+
var MAX_NAME = 64;
|
|
8
|
+
function isName(name) {
|
|
9
|
+
return name.length <= MAX_NAME && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
|
|
10
|
+
}
|
|
11
|
+
function conditionNames(condition) {
|
|
12
|
+
const out = [];
|
|
13
|
+
for (const any of condition.split("||")) {
|
|
14
|
+
for (const all of any.split("&&")) {
|
|
15
|
+
let term = trim(all);
|
|
16
|
+
if (term.startsWith("!")) term = trim(term.slice(1));
|
|
17
|
+
if (!isName(term)) return null;
|
|
18
|
+
out.push(term);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
function holds(condition, state) {
|
|
24
|
+
if (conditionNames(condition) === null) return false;
|
|
25
|
+
return condition.split("||").some((any) => any.split("&&").every((all) => {
|
|
26
|
+
let term = trim(all);
|
|
27
|
+
let negated = false;
|
|
28
|
+
if (term.startsWith("!")) {
|
|
29
|
+
negated = true;
|
|
30
|
+
term = trim(term.slice(1));
|
|
31
|
+
}
|
|
32
|
+
return (state[term] ?? false) !== negated;
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
function stateValues(page, choice) {
|
|
36
|
+
if (choice === "all") return null;
|
|
37
|
+
if (choice === "initial") return { ...page.variables ?? {} };
|
|
38
|
+
return { ...page.variables ?? {}, ...choice };
|
|
39
|
+
}
|
|
40
|
+
function stateHidden(page, choice) {
|
|
41
|
+
const values = stateValues(page, choice);
|
|
42
|
+
const out = /* @__PURE__ */ new Set();
|
|
43
|
+
if (!values) return out;
|
|
44
|
+
for (const e of page.elements) if (e.when !== null && !holds(e.when, values)) out.add(e.id);
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
function changesState(c) {
|
|
48
|
+
return !!c && (Object.keys(c.set ?? {}).length > 0 || (c.toggle ?? []).length > 0);
|
|
49
|
+
}
|
|
50
|
+
function applyClick(c, state) {
|
|
51
|
+
const out = { ...state };
|
|
52
|
+
for (const [k, v] of Object.entries(c.set ?? {})) out[k] = v;
|
|
53
|
+
for (const k of c.toggle ?? []) out[k] = !(out[k] ?? false);
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
function trim(s) {
|
|
57
|
+
return s.replace(/^[ \t ]+|[ \t ]+$/g, "");
|
|
58
|
+
}
|
|
59
|
+
|
|
6
60
|
// src/model.ts
|
|
7
61
|
var SUPPORTED_VERSION = 2;
|
|
8
62
|
var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand", "path", "note", "image", "symbol"];
|
|
9
63
|
var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
|
|
10
64
|
var KNOWN_FIELDS = {
|
|
11
65
|
file: ["version", "pages", "images"],
|
|
12
|
-
page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols"],
|
|
66
|
+
page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables"],
|
|
13
67
|
symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
|
|
14
68
|
layer: ["id", "name", "visible", "locked"],
|
|
15
69
|
rgba: ["r", "g", "b", "a"],
|
|
@@ -37,6 +91,7 @@ var KNOWN_FIELDS = {
|
|
|
37
91
|
"anchor",
|
|
38
92
|
"click",
|
|
39
93
|
"hover",
|
|
94
|
+
"when",
|
|
40
95
|
"symbol",
|
|
41
96
|
"playback",
|
|
42
97
|
"offset",
|
|
@@ -62,8 +117,8 @@ var KNOWN_FIELDS = {
|
|
|
62
117
|
follow: ["path", "start", "end", "ease", "reverse", "orient", "laps"],
|
|
63
118
|
repeat: ["start", "end", "count"],
|
|
64
119
|
cameraKey: ["t", "x", "y", "width", "height", "ease"],
|
|
65
|
-
marker: ["t", "name"],
|
|
66
|
-
click: ["scene", "url"],
|
|
120
|
+
marker: ["t", "name", "loop"],
|
|
121
|
+
click: ["scene", "url", "set", "toggle"],
|
|
67
122
|
hover: ["stroke", "fill", "opacity"]
|
|
68
123
|
};
|
|
69
124
|
var MAX_DURATION = 3600;
|
|
@@ -222,6 +277,7 @@ var Reader = class {
|
|
|
222
277
|
anchor: str(v.anchor) ?? null,
|
|
223
278
|
click: this.click(v.click),
|
|
224
279
|
hover: this.hover(v.hover),
|
|
280
|
+
when: str(v.when) ?? null,
|
|
225
281
|
symbol: uuid(v.symbol) ?? null,
|
|
226
282
|
playback: this.playback(v.playback),
|
|
227
283
|
offset: num(v.offset) ?? null,
|
|
@@ -257,13 +313,27 @@ var Reader = class {
|
|
|
257
313
|
}
|
|
258
314
|
return { id, shape, version, params };
|
|
259
315
|
}
|
|
316
|
+
variables(v) {
|
|
317
|
+
if (!isObj(v)) return null;
|
|
318
|
+
const out = {};
|
|
319
|
+
for (const [k, b] of Object.entries(v)) if (typeof b === "boolean") out[k] = b;
|
|
320
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
321
|
+
}
|
|
260
322
|
click(v) {
|
|
261
323
|
if (!isObj(v)) return null;
|
|
262
324
|
this.fields(v, "click");
|
|
263
325
|
const url = str(v.url);
|
|
264
326
|
const safe = url === void 0 ? null : allowedURL(url);
|
|
265
327
|
if (url !== void 0 && safe === null) this.warn("Skipped a click link that isn't http, https or mailto");
|
|
266
|
-
|
|
328
|
+
let set = null;
|
|
329
|
+
if (isObj(v.set)) {
|
|
330
|
+
set = {};
|
|
331
|
+
for (const [k, b] of Object.entries(v.set)) if (typeof b === "boolean" && isName(k)) set[k] = b;
|
|
332
|
+
if (Object.keys(set).length === 0) set = null;
|
|
333
|
+
}
|
|
334
|
+
const raw = typeof v.toggle === "string" ? [v.toggle] : Array.isArray(v.toggle) && v.toggle.every((x) => typeof x === "string") ? v.toggle : null;
|
|
335
|
+
const toggle = raw ? raw.filter(isName) : null;
|
|
336
|
+
return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null };
|
|
267
337
|
}
|
|
268
338
|
playback(v) {
|
|
269
339
|
const s = str(v);
|
|
@@ -364,7 +434,7 @@ var Reader = class {
|
|
|
364
434
|
if (camera) stableSortBy(camera, (c) => c.t);
|
|
365
435
|
const markers = Array.isArray(v.markers) ? v.markers.filter(isObj).flatMap((m) => {
|
|
366
436
|
const t = num(m.t);
|
|
367
|
-
return t !== void 0 && t >= 0 ? [{ t, name: str(m.name) ?? "" }] : [];
|
|
437
|
+
return t !== void 0 && t >= 0 ? [{ t, name: str(m.name) ?? "", loop: bool(m.loop) ?? null }] : [];
|
|
368
438
|
}) : null;
|
|
369
439
|
if (markers) stableSortBy(markers, (m) => m.t);
|
|
370
440
|
const poster = num(v.poster);
|
|
@@ -397,7 +467,8 @@ var Reader = class {
|
|
|
397
467
|
elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
|
|
398
468
|
theme: str(v.theme) ?? null,
|
|
399
469
|
timeline: this.timeline(v.timeline),
|
|
400
|
-
symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null
|
|
470
|
+
symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
|
|
471
|
+
variables: this.variables(v.variables)
|
|
401
472
|
};
|
|
402
473
|
}
|
|
403
474
|
};
|
|
@@ -2067,17 +2138,20 @@ function trackMap(tl) {
|
|
|
2067
2138
|
for (const t of tl.tracks) if (!m.has(t.element)) m.set(t.element, t);
|
|
2068
2139
|
return m;
|
|
2069
2140
|
}
|
|
2070
|
-
function resolved(page, t) {
|
|
2071
|
-
return expandingSymbols(posed(page, t), clampTime(t));
|
|
2141
|
+
function resolved(page, t, state = "initial") {
|
|
2142
|
+
return expandingSymbols(posed(page, t, state), clampTime(t));
|
|
2072
2143
|
}
|
|
2073
|
-
|
|
2074
|
-
|
|
2144
|
+
var EMPTY_TIMELINE = { fps: 12, duration: 0, boil: null, settle: null, poster: null, tracks: [], camera: null, markers: null };
|
|
2145
|
+
function posed(page, t, state = "initial") {
|
|
2146
|
+
const off = stateHidden(page, state);
|
|
2147
|
+
const timeline = page.timeline ?? (off.size === 0 ? null : EMPTY_TIMELINE);
|
|
2075
2148
|
if (!timeline) return page;
|
|
2076
2149
|
t = clampTime(t);
|
|
2077
2150
|
const tracks = trackMap(timeline);
|
|
2078
2151
|
const reseed = timeline.boil === null ? null : Math.trunc(Math.floor(t * clampFPS(timeline.fps) + 1e-9) / Math.max(1, timeline.boil)) % BOIL_DRAWINGS >>> 0;
|
|
2079
2152
|
const hidden = /* @__PURE__ */ new Set();
|
|
2080
2153
|
for (const tr of tracks.values()) if (!isVisible(tr, t)) hidden.add(tr.element);
|
|
2154
|
+
for (const id of off) hidden.add(id);
|
|
2081
2155
|
let moving = null;
|
|
2082
2156
|
if (timeline.settle === true) {
|
|
2083
2157
|
moving = /* @__PURE__ */ new Set();
|
|
@@ -2116,7 +2190,7 @@ function posed(page, t) {
|
|
|
2116
2190
|
elements = elements.map((e) => {
|
|
2117
2191
|
const c = e.connector;
|
|
2118
2192
|
if (!c || ![c.start.elementID, c.end.elementID].some((id) => id !== null && hidden.has(id))) return e;
|
|
2119
|
-
if (!(tracks.get(e.id)?.keys.some((k) => k.visible !== void 0) ?? false)) {
|
|
2193
|
+
if (!(tracks.get(e.id)?.keys.some((k) => k.visible !== void 0) ?? false) || [c.start.elementID, c.end.elementID].some((id) => id !== null && off.has(id))) {
|
|
2120
2194
|
gone.add(e.id);
|
|
2121
2195
|
return e;
|
|
2122
2196
|
}
|
|
@@ -2132,7 +2206,7 @@ function posterTime(page) {
|
|
|
2132
2206
|
return page.timeline ? page.timeline.poster ?? page.timeline.duration : 0;
|
|
2133
2207
|
}
|
|
2134
2208
|
function still(page) {
|
|
2135
|
-
return page.timeline ? resolved(page, posterTime(page)) : expandingSymbols(page, 0);
|
|
2209
|
+
return page.timeline ? resolved(page, posterTime(page)) : expandingSymbols(posed(page, 0), 0);
|
|
2136
2210
|
}
|
|
2137
2211
|
function cameraRect(tl, t) {
|
|
2138
2212
|
const keys = tl.camera ?? [];
|
|
@@ -2241,7 +2315,7 @@ function filled(s, args) {
|
|
|
2241
2315
|
}) };
|
|
2242
2316
|
}
|
|
2243
2317
|
function symbolPage(s) {
|
|
2244
|
-
return { id: s.id, name: s.name, layers: s.layers, elements: s.elements, theme: null, timeline: s.timeline, symbols: null };
|
|
2318
|
+
return { id: s.id, name: s.name, layers: s.layers, elements: s.elements, theme: null, timeline: s.timeline, symbols: null, variables: null };
|
|
2245
2319
|
}
|
|
2246
2320
|
function hasInstances(page) {
|
|
2247
2321
|
return page.elements.some((e) => e.kind === "symbol");
|
|
@@ -2370,16 +2444,21 @@ function clickTargets(page, file) {
|
|
|
2370
2444
|
let pageOf = null;
|
|
2371
2445
|
for (const e of page.elements) {
|
|
2372
2446
|
const c = e.click;
|
|
2447
|
+
const state = c && changesState(c) ? { ...c.set ? { set: c.set } : {}, ...c.toggle ? { toggle: c.toggle } : {} } : null;
|
|
2373
2448
|
if (c?.scene) {
|
|
2374
2449
|
const i = sceneNamed(c.scene, markers);
|
|
2375
2450
|
if (i >= 0) {
|
|
2376
|
-
out.set(e.id, { scene: i });
|
|
2451
|
+
out.set(e.id, { scene: i, ...state });
|
|
2377
2452
|
continue;
|
|
2378
2453
|
}
|
|
2379
2454
|
}
|
|
2380
2455
|
const url = (c?.url ? allowedURL(c.url) : null) ?? webLink(e.url);
|
|
2381
2456
|
if (url) {
|
|
2382
|
-
out.set(e.id, { url });
|
|
2457
|
+
out.set(e.id, { url, ...state });
|
|
2458
|
+
continue;
|
|
2459
|
+
}
|
|
2460
|
+
if (state) {
|
|
2461
|
+
out.set(e.id, { state: true, ...state });
|
|
2383
2462
|
continue;
|
|
2384
2463
|
}
|
|
2385
2464
|
if (e.link && file && file.pages.length > 1) {
|
|
@@ -3293,6 +3372,10 @@ var Playback = class {
|
|
|
3293
3372
|
__publicField(this, "plan");
|
|
3294
3373
|
__publicField(this, "scenes");
|
|
3295
3374
|
__publicField(this, "sceneNames");
|
|
3375
|
+
/** Whether each scene loops (Marker.loop). */
|
|
3376
|
+
__publicField(this, "sceneLoops");
|
|
3377
|
+
/** The page's variables now: their starting values until clicks change them. */
|
|
3378
|
+
__publicField(this, "state");
|
|
3296
3379
|
__publicField(this, "background");
|
|
3297
3380
|
__publicField(this, "ink");
|
|
3298
3381
|
/** What clicking each clickable element does, by element id. */
|
|
@@ -3352,6 +3435,8 @@ var Playback = class {
|
|
|
3352
3435
|
};
|
|
3353
3436
|
this.scenes = (page.timeline?.markers ?? []).map((m) => near(m.t));
|
|
3354
3437
|
this.sceneNames = (page.timeline?.markers ?? []).map((m) => m.name);
|
|
3438
|
+
this.sceneLoops = (page.timeline?.markers ?? []).map((m) => m.loop === true);
|
|
3439
|
+
this.state = { ...page.variables ?? {} };
|
|
3355
3440
|
const theme = themeNamed(page.theme);
|
|
3356
3441
|
this.background = theme?.background ?? WHITE;
|
|
3357
3442
|
this.ink = theme?.ink ?? { r: 0.12, g: 0.12, b: 0.13, a: 1 };
|
|
@@ -3402,7 +3487,7 @@ var Playback = class {
|
|
|
3402
3487
|
const times = /* @__PURE__ */ new Map();
|
|
3403
3488
|
for (const [id, start] of this.replayStarts) times.set(id, Math.min((now - start) / 1e3, this.replays.get(id) ?? 0));
|
|
3404
3489
|
const tc = clampTime(t);
|
|
3405
|
-
const pose = posed(this.page, tc);
|
|
3490
|
+
const pose = posed(this.page, tc, this.state);
|
|
3406
3491
|
const frame2 = expandingSymbols(pose, tc, true, times);
|
|
3407
3492
|
const { parts } = svgParts(frame2, this.background, r);
|
|
3408
3493
|
const owned = (m, parts2) => {
|
|
@@ -3524,6 +3609,20 @@ ${h}</g>
|
|
|
3524
3609
|
});
|
|
3525
3610
|
return s;
|
|
3526
3611
|
}
|
|
3612
|
+
/** The frame after scene `s`'s last: the next scene's, or the frame count after the last scene. */
|
|
3613
|
+
sceneEnd(s) {
|
|
3614
|
+
return this.scenes.find((f, j) => j > s && f > this.scenes[s]) ?? this.frameCount;
|
|
3615
|
+
}
|
|
3616
|
+
/** Where playing goes from frame `i`, and whether it stops there: on into a looping scene, round to the start of
|
|
3617
|
+
* the looping scene `i` ends, or a stop at a scene or the end (`loops`: whether the timeline loops at its end). */
|
|
3618
|
+
step(i, loops) {
|
|
3619
|
+
const next = i + 1;
|
|
3620
|
+
const s = this.sceneAt(i);
|
|
3621
|
+
if (s >= 0 && this.sceneLoops[s] && next >= this.sceneEnd(s)) return { frame: this.scenes[s], stop: false };
|
|
3622
|
+
if (next >= this.frameCount) return { frame: 0, stop: !loops || this.scenes.length > 0 };
|
|
3623
|
+
const entered = this.scenes.indexOf(next);
|
|
3624
|
+
return { frame: next, stop: entered >= 0 && !this.sceneLoops[entered] };
|
|
3625
|
+
}
|
|
3527
3626
|
};
|
|
3528
3627
|
var ElementBase = typeof HTMLElement === "undefined" ? class {
|
|
3529
3628
|
} : HTMLElement;
|
|
@@ -3677,7 +3776,7 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3677
3776
|
this.setPlaying(false);
|
|
3678
3777
|
return;
|
|
3679
3778
|
}
|
|
3680
|
-
this.setPlaying(!this.hasAttribute("no-autoplay") && !p.scenes.includes(0));
|
|
3779
|
+
this.setPlaying(!this.hasAttribute("no-autoplay") && (!p.scenes.includes(0) || p.sceneLoops[p.scenes.indexOf(0)]));
|
|
3681
3780
|
}
|
|
3682
3781
|
// MARK: Playback
|
|
3683
3782
|
get paused() {
|
|
@@ -3703,11 +3802,13 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3703
3802
|
pause() {
|
|
3704
3803
|
this.setPlaying(false);
|
|
3705
3804
|
}
|
|
3706
|
-
/** Plays on to the next scene (from the start after the last). */
|
|
3805
|
+
/** Plays on to the next scene (from the start after the last). Out of a looping scene it jumps to the next. */
|
|
3707
3806
|
nextScene() {
|
|
3708
3807
|
const p = this.playback;
|
|
3709
3808
|
if (!p) return;
|
|
3710
|
-
|
|
3809
|
+
const s = p.sceneAt(p.frame);
|
|
3810
|
+
if (s >= 0 && p.sceneLoops[s]) p.show(s + 1 < p.scenes.length ? p.scenes[s + 1] : 0);
|
|
3811
|
+
else if (p.frame >= p.frameCount - 1) p.show(0);
|
|
3711
3812
|
this.setPlaying(true);
|
|
3712
3813
|
}
|
|
3713
3814
|
/** Back to the start of this scene, or the one before if already there. */
|
|
@@ -3736,6 +3837,8 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3736
3837
|
this.scheduleReplay();
|
|
3737
3838
|
}
|
|
3738
3839
|
if (!click) return;
|
|
3840
|
+
if (click.set || click.toggle) this.setState(applyClick({ scene: null, url: null, set: click.set ?? null, toggle: click.toggle ?? null }, p.state));
|
|
3841
|
+
if ("state" in click) return;
|
|
3739
3842
|
if ("url" in click) {
|
|
3740
3843
|
if (/^(https?:|mailto:)/i.test(click.url)) window.open(click.url, "_blank", "noopener");
|
|
3741
3844
|
} else if ("page" in click) {
|
|
@@ -3743,13 +3846,27 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3743
3846
|
} else if (click.scene >= 0 && click.scene < p.scenes.length) {
|
|
3744
3847
|
if (reduced || this.hasAttribute("at") || p.frameCount < 2) {
|
|
3745
3848
|
this.setPlaying(false);
|
|
3746
|
-
p.show(click.scene + 1 < p.scenes.length ? p.scenes[click.scene + 1] : p.frameCount - 1);
|
|
3849
|
+
p.show(p.sceneLoops[click.scene] ? p.scenes[click.scene] : click.scene + 1 < p.scenes.length ? p.scenes[click.scene + 1] : p.frameCount - 1);
|
|
3747
3850
|
} else {
|
|
3748
3851
|
p.show(p.scenes[click.scene]);
|
|
3749
3852
|
this.setPlaying(true);
|
|
3750
3853
|
}
|
|
3751
3854
|
}
|
|
3752
3855
|
}
|
|
3856
|
+
/** The page's variables now. */
|
|
3857
|
+
get variables() {
|
|
3858
|
+
return { ...this.playback?.state ?? {} };
|
|
3859
|
+
}
|
|
3860
|
+
/** Gives the page's variables these values (the others keep theirs), redraws, and fires `state`. */
|
|
3861
|
+
setState(values) {
|
|
3862
|
+
const p = this.playback;
|
|
3863
|
+
if (!p) return;
|
|
3864
|
+
const next = { ...p.state, ...values };
|
|
3865
|
+
if (Object.keys(next).every((k) => next[k] === p.state[k])) return;
|
|
3866
|
+
p.state = next;
|
|
3867
|
+
p.redraw();
|
|
3868
|
+
this.dispatchEvent(new CustomEvent("state", { detail: { variables: { ...next } } }));
|
|
3869
|
+
}
|
|
3753
3870
|
/** Redraws, while the page itself stands still, as long as a clicked copy is playing through. */
|
|
3754
3871
|
scheduleReplay() {
|
|
3755
3872
|
if (this.replayRaf) return;
|
|
@@ -3775,7 +3892,8 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3775
3892
|
schedule() {
|
|
3776
3893
|
if (this.playing && this.visible && !this.raf && this.isConnected) this.raf = requestAnimationFrame((now) => this.tick(now));
|
|
3777
3894
|
}
|
|
3778
|
-
/** Steps whole frames at the timeline's rate; with scenes, playing stops at each one and at the end
|
|
3895
|
+
/** Steps whole frames at the timeline's rate; with scenes, playing stops at each one and at the end, except that a
|
|
3896
|
+
* looping scene plays round (Playback.step). */
|
|
3779
3897
|
tick(now) {
|
|
3780
3898
|
this.raf = 0;
|
|
3781
3899
|
const p = this.playback;
|
|
@@ -3786,14 +3904,14 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3786
3904
|
let advanced = false;
|
|
3787
3905
|
while (this.playing && this.acc >= step) {
|
|
3788
3906
|
this.acc -= step;
|
|
3789
|
-
const
|
|
3790
|
-
if (
|
|
3907
|
+
const { frame: frame2, stop } = p.step(p.frame, !this.hasAttribute("no-loop"));
|
|
3908
|
+
if (stop && frame2 === 0 && p.frame + 1 >= p.frameCount) {
|
|
3791
3909
|
this.setPlaying(false);
|
|
3792
3910
|
break;
|
|
3793
3911
|
}
|
|
3794
|
-
p.frame =
|
|
3912
|
+
p.frame = frame2;
|
|
3795
3913
|
advanced = true;
|
|
3796
|
-
if (
|
|
3914
|
+
if (stop) {
|
|
3797
3915
|
this.setPlaying(false);
|
|
3798
3916
|
break;
|
|
3799
3917
|
}
|
|
@@ -3943,7 +4061,7 @@ function render(source, options = {}) {
|
|
|
3943
4061
|
function isFile(v) {
|
|
3944
4062
|
return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
|
|
3945
4063
|
}
|
|
3946
|
-
var VERSION = "0.
|
|
4064
|
+
var VERSION = "0.7.0";
|
|
3947
4065
|
export {
|
|
3948
4066
|
BOIL_DRAWINGS,
|
|
3949
4067
|
BUNDLED_FONTS,
|
package/dist/types/clicks.d.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { type DottieFile, type Element, type Marker, type Page } from "./model.ts";
|
|
2
2
|
/** Jump to scene `scene` (an index into the page's markers) and play on to the next; open `url` in a new tab; or,
|
|
3
|
-
* in <dottie-player> only, show page `page` (an index into the file's pages), where the element's link points.
|
|
4
|
-
|
|
3
|
+
* in <dottie-player> only, show page `page` (an index into the file's pages), where the element's link points. Any
|
|
4
|
+
* of them may first `set` and `toggle` page variables (ClickAction.set, .toggle), and a click may do only that. */
|
|
5
|
+
export type Click = ({
|
|
5
6
|
scene: number;
|
|
6
7
|
} | {
|
|
7
8
|
url: string;
|
|
8
9
|
} | {
|
|
9
10
|
page: number;
|
|
11
|
+
} | {
|
|
12
|
+
state: true;
|
|
13
|
+
}) & {
|
|
14
|
+
set?: Record<string, boolean>;
|
|
15
|
+
toggle?: string[];
|
|
10
16
|
};
|
|
11
17
|
/** The marker called `name`: an exact match, else one that differs only in case. */
|
|
12
18
|
export declare function sceneNamed(name: string, markers: Marker[]): number;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -20,4 +20,4 @@ export interface RenderOptions {
|
|
|
20
20
|
/** One page of a drawing as an SVG document, as Dottie's own SVG export draws it. */
|
|
21
21
|
export declare function render(source: string | DottieFile | unknown, options?: RenderOptions): string;
|
|
22
22
|
/** The library's version. */
|
|
23
|
-
export declare const VERSION = "0.
|
|
23
|
+
export declare const VERSION = "0.7.0";
|
package/dist/types/model.d.ts
CHANGED
|
@@ -68,10 +68,13 @@ export interface EmbeddedImage {
|
|
|
68
68
|
data: string;
|
|
69
69
|
format: string;
|
|
70
70
|
}
|
|
71
|
-
/** What clicking an element does in a played drawing: a scene to jump to and play on from, or a link
|
|
71
|
+
/** What clicking an element does in a played drawing: a scene to jump to and play on from, or a link; and page
|
|
72
|
+
* variables to set and flip (ClickAction.set, .toggle), which change what shows without touching the playhead. */
|
|
72
73
|
export interface ClickAction {
|
|
73
74
|
scene: string | null;
|
|
74
75
|
url: string | null;
|
|
76
|
+
set: Record<string, boolean> | null;
|
|
77
|
+
toggle: string[] | null;
|
|
75
78
|
}
|
|
76
79
|
/** How a clickable element looks under the pointer or with keyboard focus; null keeps its own. */
|
|
77
80
|
export interface HoverLook {
|
|
@@ -105,6 +108,8 @@ export interface Element {
|
|
|
105
108
|
anchor: string | null;
|
|
106
109
|
click: ClickAction | null;
|
|
107
110
|
hover: HoverLook | null;
|
|
111
|
+
/** A condition on the page's variables: drawn only while it holds (Element.when; see state.ts). */
|
|
112
|
+
when: string | null;
|
|
108
113
|
/** For a `symbol` element: the page's symbol it shows, how it plays it, and from how far in. */
|
|
109
114
|
symbol: string | null;
|
|
110
115
|
playback: SymbolPlayback | null;
|
|
@@ -179,9 +184,11 @@ export interface CameraKey {
|
|
|
179
184
|
height: number;
|
|
180
185
|
ease: Ease | null;
|
|
181
186
|
}
|
|
187
|
+
/** A scene; `loop` plays it round until something leaves it, instead of stopping on it (Marker.loop). */
|
|
182
188
|
export interface Marker {
|
|
183
189
|
t: number;
|
|
184
190
|
name: string;
|
|
191
|
+
loop: boolean | null;
|
|
185
192
|
}
|
|
186
193
|
export interface Timeline {
|
|
187
194
|
fps: number;
|
|
@@ -201,6 +208,8 @@ export interface Page {
|
|
|
201
208
|
theme: string | null;
|
|
202
209
|
timeline: Timeline | null;
|
|
203
210
|
symbols: Symbol[] | null;
|
|
211
|
+
/** The page's variables and their starting values (DottieDocument.variables). */
|
|
212
|
+
variables: Record<string, boolean> | null;
|
|
204
213
|
}
|
|
205
214
|
/** A drawing defined once on a page and placed by `symbol` elements, with its own timeline (Symbol). */
|
|
206
215
|
export interface Symbol {
|
package/dist/types/player.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ export declare class Playback {
|
|
|
10
10
|
readonly plan: Plan;
|
|
11
11
|
readonly scenes: number[];
|
|
12
12
|
readonly sceneNames: string[];
|
|
13
|
+
/** Whether each scene loops (Marker.loop). */
|
|
14
|
+
readonly sceneLoops: boolean[];
|
|
15
|
+
/** The page's variables now: their starting values until clicks change them. */
|
|
16
|
+
state: Record<string, boolean>;
|
|
13
17
|
readonly background: RGBA;
|
|
14
18
|
readonly ink: RGBA;
|
|
15
19
|
/** What clicking each clickable element does, by element id. */
|
|
@@ -56,6 +60,14 @@ export declare class Playback {
|
|
|
56
60
|
private buttonPart;
|
|
57
61
|
/** The scene frame `i` is in, or -1 before the first. */
|
|
58
62
|
sceneAt(i: number): number;
|
|
63
|
+
/** The frame after scene `s`'s last: the next scene's, or the frame count after the last scene. */
|
|
64
|
+
sceneEnd(s: number): number;
|
|
65
|
+
/** Where playing goes from frame `i`, and whether it stops there: on into a looping scene, round to the start of
|
|
66
|
+
* the looping scene `i` ends, or a stop at a scene or the end (`loops`: whether the timeline loops at its end). */
|
|
67
|
+
step(i: number, loops: boolean): {
|
|
68
|
+
frame: number;
|
|
69
|
+
stop: boolean;
|
|
70
|
+
};
|
|
59
71
|
}
|
|
60
72
|
/** HTMLElement where there is a DOM; a stand-in elsewhere, so the library still loads in Node (to render SVG). */
|
|
61
73
|
declare const ElementBase: typeof HTMLElement;
|
|
@@ -95,18 +107,23 @@ export declare class DottiePlayer extends ElementBase {
|
|
|
95
107
|
get duration(): number;
|
|
96
108
|
play(): void;
|
|
97
109
|
pause(): void;
|
|
98
|
-
/** Plays on to the next scene (from the start after the last). */
|
|
110
|
+
/** Plays on to the next scene (from the start after the last). Out of a looping scene it jumps to the next. */
|
|
99
111
|
nextScene(): void;
|
|
100
112
|
/** Back to the start of this scene, or the one before if already there. */
|
|
101
113
|
prevScene(): void;
|
|
102
114
|
/** Does what clicking element `id` does: jumps to its scene and plays on to the next (with reduced motion, or in a
|
|
103
115
|
* still, straight to where that stops), opens its link in a new tab, or shows its page. */
|
|
104
116
|
activate(id: string): void;
|
|
117
|
+
/** The page's variables now. */
|
|
118
|
+
get variables(): Record<string, boolean>;
|
|
119
|
+
/** Gives the page's variables these values (the others keep theirs), redraws, and fires `state`. */
|
|
120
|
+
setState(values: Record<string, boolean>): void;
|
|
105
121
|
/** Redraws, while the page itself stands still, as long as a clicked copy is playing through. */
|
|
106
122
|
private scheduleReplay;
|
|
107
123
|
private setPlaying;
|
|
108
124
|
private schedule;
|
|
109
|
-
/** Steps whole frames at the timeline's rate; with scenes, playing stops at each one and at the end
|
|
125
|
+
/** Steps whole frames at the timeline's rate; with scenes, playing stops at each one and at the end, except that a
|
|
126
|
+
* looping scene plays round (Playback.step). */
|
|
110
127
|
private tick;
|
|
111
128
|
private key;
|
|
112
129
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ClickAction, Page } from "./model.ts";
|
|
2
|
+
/** Whether `name` can name a variable: a letter or underscore, then letters, digits and underscores. */
|
|
3
|
+
export declare function isName(name: string): boolean;
|
|
4
|
+
/** The variable names `condition` reads, or null when it isn't one: names, each optionally after `!`, joined by
|
|
5
|
+
* `&&` and `||` (`&&` binds tighter). */
|
|
6
|
+
export declare function conditionNames(condition: string): string[] | null;
|
|
7
|
+
/** Whether `condition` holds with these values. A missing variable is false; a condition that can't be read never
|
|
8
|
+
* holds. */
|
|
9
|
+
export declare function holds(condition: string, state: Record<string, boolean>): boolean;
|
|
10
|
+
/** Which elements a page shows as far as its variables go: their starting values ("initial"), these values (a
|
|
11
|
+
* missing one takes its starting value), or every element ("all"). */
|
|
12
|
+
export type StateChoice = "initial" | "all" | Record<string, boolean>;
|
|
13
|
+
/** The values `choice` gives each variable; null for "all". */
|
|
14
|
+
export declare function stateValues(page: Page, choice: StateChoice): Record<string, boolean> | null;
|
|
15
|
+
/** The ids of the elements whose `when` doesn't hold under `choice`. */
|
|
16
|
+
export declare function stateHidden(page: Page, choice: StateChoice): Set<string>;
|
|
17
|
+
/** Whether a click sets or flips a variable. */
|
|
18
|
+
export declare function changesState(c: ClickAction | null): boolean;
|
|
19
|
+
/** `state` after the click: its `set` values, then each `toggle` flipped (a missing variable starts false). */
|
|
20
|
+
export declare function applyClick(c: ClickAction, state: Record<string, boolean>): Record<string, boolean>;
|
package/dist/types/timeline.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Ease, type Element, type Follow, type Page, type Timeline, type Track } from "./model.ts";
|
|
2
2
|
import { Polyline, type Point, type Rect } from "./path.ts";
|
|
3
|
+
import { type StateChoice } from "./state.ts";
|
|
3
4
|
export declare function applyEase(ease: Ease, u: number): number;
|
|
4
5
|
/** The time inside the track's own keys that `t` plays (repeats folded back). */
|
|
5
6
|
export declare function localTime(track: Track, t: number): number;
|
|
@@ -20,9 +21,10 @@ export declare function following(track: Track, e: Element, t: number, page: Pag
|
|
|
20
21
|
export declare function marchOffset(tl: Timeline, speed: number, period: number, t: number): number;
|
|
21
22
|
/** The page as a still drawing at `t` seconds, symbol instances expanded (DottieDocument.resolved(at:)). A page with
|
|
22
23
|
* no timeline and no instances is returned as is. */
|
|
23
|
-
export declare function resolved(page: Page, t: number): Page;
|
|
24
|
-
/** The page posed at `t` seconds, instances kept
|
|
25
|
-
|
|
24
|
+
export declare function resolved(page: Page, t: number, state?: StateChoice): Page;
|
|
25
|
+
/** The page posed at `t` seconds, instances kept, and the elements whose `when` doesn't hold under `state` left out
|
|
26
|
+
* with their connectors (DottieDocument.posed(at:state:)). */
|
|
27
|
+
export declare function posed(page: Page, t: number, state?: StateChoice): Page;
|
|
26
28
|
/** The moment a still picture of the page shows: its poster, else the end. */
|
|
27
29
|
export declare function posterTime(page: Page): number;
|
|
28
30
|
/** The page as a still picture (its poster frame when animated). */
|