@office-kit/pptx-editor 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +385 -2
- package/dist/agent-tools-emRGeP2H.js +23323 -0
- package/dist/agent-tools-emRGeP2H.js.map +1 -0
- package/dist/animation-player-D2mt2MI8.js +1400 -0
- package/dist/animation-player-D2mt2MI8.js.map +1 -0
- package/dist/deck-merge-XZc2oNxd.js +1304 -0
- package/dist/deck-merge-XZc2oNxd.js.map +1 -0
- package/dist/deck-merge-r5VCQNH_.d.ts +38 -0
- package/dist/index-BFQN6sI6.d.ts +243 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/internal/animation-player.d.ts +104 -0
- package/dist/internal/animation-player.js +2 -0
- package/dist/internal.d.ts +33 -0
- package/dist/internal.js +18 -0
- package/dist/internal.js.map +1 -0
- package/dist/merge.d.ts +2 -0
- package/dist/merge.js +2 -0
- package/dist/src-iNPXxN6v.js +77762 -0
- package/dist/src-iNPXxN6v.js.map +1 -0
- package/package.json +66 -3
|
@@ -0,0 +1,1400 @@
|
|
|
1
|
+
//#region src/core/animation-player.ts
|
|
2
|
+
const INSTANT_EFFECTS = ["appear", "disappear"];
|
|
3
|
+
const FILTER_FAMILIES = [
|
|
4
|
+
"blinds",
|
|
5
|
+
"checkerboard",
|
|
6
|
+
"peek",
|
|
7
|
+
"randomBars",
|
|
8
|
+
"shape",
|
|
9
|
+
"split",
|
|
10
|
+
"strips",
|
|
11
|
+
"wedge",
|
|
12
|
+
"wheel",
|
|
13
|
+
"wipe"
|
|
14
|
+
];
|
|
15
|
+
/** The `presetClass` of an effect the library names says which of the three it is. */
|
|
16
|
+
const PRESET_CLASS_KINDS = {
|
|
17
|
+
entr: "entrance",
|
|
18
|
+
exit: "exit",
|
|
19
|
+
emph: "emphasis"
|
|
20
|
+
};
|
|
21
|
+
const motionOf = (effect) => {
|
|
22
|
+
if (INSTANT_EFFECTS.includes(effect)) return "none";
|
|
23
|
+
const family = effect.replace(/(In|Out)$/, "");
|
|
24
|
+
if (family === "fly" || family === "credits") return "fly";
|
|
25
|
+
if (family === "peek") return "peek";
|
|
26
|
+
return FILTER_FAMILIES.includes(family) ? "filter" : "pose";
|
|
27
|
+
};
|
|
28
|
+
/** Where an entrance starts and an exit ends, by preset family. */
|
|
29
|
+
const AWAY = {
|
|
30
|
+
fade: { opacity: 0 },
|
|
31
|
+
dissolve: { opacity: 0 },
|
|
32
|
+
expand: {
|
|
33
|
+
opacity: 0,
|
|
34
|
+
scale: "0.7 1"
|
|
35
|
+
},
|
|
36
|
+
contract: {
|
|
37
|
+
opacity: 0,
|
|
38
|
+
scale: "0.7 1"
|
|
39
|
+
},
|
|
40
|
+
swivel: {
|
|
41
|
+
opacity: 0,
|
|
42
|
+
scale: "0 1"
|
|
43
|
+
},
|
|
44
|
+
zoom: {
|
|
45
|
+
opacity: 0,
|
|
46
|
+
scale: "0"
|
|
47
|
+
},
|
|
48
|
+
basicZoom: { scale: "0" },
|
|
49
|
+
float: {
|
|
50
|
+
opacity: 0,
|
|
51
|
+
translate: "0% 50%"
|
|
52
|
+
},
|
|
53
|
+
growTurn: {
|
|
54
|
+
opacity: 0,
|
|
55
|
+
scale: "0",
|
|
56
|
+
rotate: "90deg"
|
|
57
|
+
},
|
|
58
|
+
shrinkTurn: {
|
|
59
|
+
opacity: 0,
|
|
60
|
+
scale: "0",
|
|
61
|
+
rotate: "90deg"
|
|
62
|
+
},
|
|
63
|
+
spinner: {
|
|
64
|
+
opacity: 0,
|
|
65
|
+
scale: "0",
|
|
66
|
+
rotate: "-90deg"
|
|
67
|
+
},
|
|
68
|
+
stretch: { scale: "0 1" },
|
|
69
|
+
stretchy: { scale: "0 1" },
|
|
70
|
+
collapse: { scale: "0 1" },
|
|
71
|
+
pinwheel: {
|
|
72
|
+
opacity: 0,
|
|
73
|
+
scale: "0",
|
|
74
|
+
rotate: "720deg"
|
|
75
|
+
},
|
|
76
|
+
spiral: {
|
|
77
|
+
scale: "0",
|
|
78
|
+
translate: "-100% 100%"
|
|
79
|
+
},
|
|
80
|
+
basicSwivel: { scale: "0 1" }
|
|
81
|
+
};
|
|
82
|
+
/** Each property of a pose at the shape's own place. */
|
|
83
|
+
const AT_REST = {
|
|
84
|
+
opacity: 1,
|
|
85
|
+
scale: "1",
|
|
86
|
+
rotate: "0deg",
|
|
87
|
+
translate: "0% 0%"
|
|
88
|
+
};
|
|
89
|
+
const restOf = (away) => Object.fromEntries(Object.keys(away).map((name) => [name, AT_REST[name]]));
|
|
90
|
+
const turned = (from, to) => [{ filter: from }, { filter: to }];
|
|
91
|
+
const flash = (from, to) => [
|
|
92
|
+
{ filter: from },
|
|
93
|
+
{ filter: to },
|
|
94
|
+
{ filter: from }
|
|
95
|
+
];
|
|
96
|
+
const HUE_TURN = turned("hue-rotate(0deg)", "hue-rotate(180deg)");
|
|
97
|
+
const CONTRAST_FLASH = flash("contrast(1)", "contrast(1.8)");
|
|
98
|
+
/** The frames of each emphasis effect, which starts and ends with the shape as drawn. */
|
|
99
|
+
const EMPHASIS = {
|
|
100
|
+
pulse: [
|
|
101
|
+
{
|
|
102
|
+
offset: 0,
|
|
103
|
+
scale: "1",
|
|
104
|
+
opacity: 1
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
offset: .2,
|
|
108
|
+
opacity: .5
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
offset: .5,
|
|
112
|
+
scale: "1.05"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
offset: .8,
|
|
116
|
+
opacity: .5
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
offset: 1,
|
|
120
|
+
scale: "1",
|
|
121
|
+
opacity: 1
|
|
122
|
+
}
|
|
123
|
+
],
|
|
124
|
+
teeter: [
|
|
125
|
+
{
|
|
126
|
+
offset: 0,
|
|
127
|
+
rotate: "0deg"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
offset: .1,
|
|
131
|
+
rotate: "2deg"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
offset: .2,
|
|
135
|
+
rotate: "2deg"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
offset: .4,
|
|
139
|
+
rotate: "-2deg"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
offset: .6,
|
|
143
|
+
rotate: "2deg"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
offset: .8,
|
|
147
|
+
rotate: "-2deg"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
offset: 1,
|
|
151
|
+
rotate: "0deg"
|
|
152
|
+
}
|
|
153
|
+
],
|
|
154
|
+
wave: [
|
|
155
|
+
{
|
|
156
|
+
offset: 0,
|
|
157
|
+
translate: "0% 0%",
|
|
158
|
+
rotate: "0deg",
|
|
159
|
+
easing: "ease-in-out"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
offset: .25,
|
|
163
|
+
rotate: "25deg"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
offset: .5,
|
|
167
|
+
translate: "0% -20%",
|
|
168
|
+
rotate: "0deg",
|
|
169
|
+
easing: "ease-in-out"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
offset: .75,
|
|
173
|
+
rotate: "-25deg"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
offset: 1,
|
|
177
|
+
translate: "0% 0%",
|
|
178
|
+
rotate: "0deg"
|
|
179
|
+
}
|
|
180
|
+
],
|
|
181
|
+
shimmer: [
|
|
182
|
+
{
|
|
183
|
+
scale: "1",
|
|
184
|
+
translate: "0% 0%",
|
|
185
|
+
rotate: "0deg"
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
scale: "0.8 1",
|
|
189
|
+
translate: "10% -10%",
|
|
190
|
+
rotate: "-8deg"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
scale: "1",
|
|
194
|
+
translate: "0% 0%",
|
|
195
|
+
rotate: "0deg"
|
|
196
|
+
}
|
|
197
|
+
],
|
|
198
|
+
blink: [
|
|
199
|
+
{
|
|
200
|
+
offset: 0,
|
|
201
|
+
opacity: 0,
|
|
202
|
+
easing: "step-end"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
offset: .5,
|
|
206
|
+
opacity: 1
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
offset: 1,
|
|
210
|
+
opacity: 1
|
|
211
|
+
}
|
|
212
|
+
],
|
|
213
|
+
fillColor: HUE_TURN,
|
|
214
|
+
lineColor: HUE_TURN,
|
|
215
|
+
fontColor: HUE_TURN,
|
|
216
|
+
objectColor: HUE_TURN,
|
|
217
|
+
brushColor: HUE_TURN,
|
|
218
|
+
complementaryColor: HUE_TURN,
|
|
219
|
+
complementaryColor2: HUE_TURN,
|
|
220
|
+
contrastingColor: HUE_TURN,
|
|
221
|
+
darken: turned("brightness(1)", "brightness(0.6)"),
|
|
222
|
+
lighten: turned("brightness(1)", "brightness(1.4)"),
|
|
223
|
+
desaturate: turned("saturate(1)", "saturate(0)"),
|
|
224
|
+
colorPulse: flash("brightness(1)", "brightness(1.4)"),
|
|
225
|
+
growWithColor: [{
|
|
226
|
+
scale: "1",
|
|
227
|
+
filter: "hue-rotate(0deg)"
|
|
228
|
+
}, {
|
|
229
|
+
scale: "1.1",
|
|
230
|
+
filter: "hue-rotate(180deg)"
|
|
231
|
+
}],
|
|
232
|
+
boldFlash: [
|
|
233
|
+
{
|
|
234
|
+
offset: 0,
|
|
235
|
+
filter: "contrast(1)",
|
|
236
|
+
easing: "step-end"
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
offset: .5,
|
|
240
|
+
filter: "contrast(1.8)",
|
|
241
|
+
easing: "step-end"
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
offset: .6,
|
|
245
|
+
filter: "contrast(1)"
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
offset: 1,
|
|
249
|
+
filter: "contrast(1)"
|
|
250
|
+
}
|
|
251
|
+
],
|
|
252
|
+
boldReveal: CONTRAST_FLASH,
|
|
253
|
+
underline: CONTRAST_FLASH
|
|
254
|
+
};
|
|
255
|
+
/**
|
|
256
|
+
* The frames of an emphasis effect whose Effect Options decide how far it
|
|
257
|
+
* goes: Spin's angle and direction, Grow/Shrink's size and axes,
|
|
258
|
+
* Transparency's amount. `null` for every other effect.
|
|
259
|
+
*/
|
|
260
|
+
const optionedEmphasis = (step) => {
|
|
261
|
+
switch (step.effect) {
|
|
262
|
+
case "spin": return [{ rotate: "0deg" }, { rotate: `${(step.spinDirection === "counterclockwise" ? -1 : 1) * (step.spinDegrees ?? 360)}deg` }];
|
|
263
|
+
case "growShrink": {
|
|
264
|
+
const size = (step.scalePercent ?? 150) / 100;
|
|
265
|
+
return [{ scale: "1" }, { scale: step.scaleDirection === "horizontal" ? `${size} 1` : step.scaleDirection === "vertical" ? `1 ${size}` : `${size}` }];
|
|
266
|
+
}
|
|
267
|
+
case "transparency": return [{ opacity: 1 }, { opacity: 1 - (step.transparencyPercent ?? 50) / 100 }];
|
|
268
|
+
default: return null;
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
/** The time-to-progress curve of a `tmFilter`, read at `t`. */
|
|
272
|
+
const filtered = (points, t) => {
|
|
273
|
+
for (let at = 1; at < points.length; at += 1) {
|
|
274
|
+
const [t1, p1] = points[at];
|
|
275
|
+
const [t0, p0] = points[at - 1];
|
|
276
|
+
if (t <= t1) return p0 + (p1 - p0) * (t - t0) / (t1 - t0);
|
|
277
|
+
}
|
|
278
|
+
return 1;
|
|
279
|
+
};
|
|
280
|
+
const BOUNCE_DRIFT = [
|
|
281
|
+
[0, 0],
|
|
282
|
+
[.14, .36],
|
|
283
|
+
[.43, .73],
|
|
284
|
+
[.71, .91],
|
|
285
|
+
[1, 1]
|
|
286
|
+
];
|
|
287
|
+
const BOUNCE_LENGTH = 1822;
|
|
288
|
+
const BOUNCE_LANDINGS = [
|
|
289
|
+
664,
|
|
290
|
+
1324,
|
|
291
|
+
1656,
|
|
292
|
+
1822
|
|
293
|
+
];
|
|
294
|
+
const BOUNCE_HEIGHTS = [
|
|
295
|
+
1 / 9,
|
|
296
|
+
1 / 27,
|
|
297
|
+
1 / 81
|
|
298
|
+
];
|
|
299
|
+
const REVOLVE_ARC = [
|
|
300
|
+
[
|
|
301
|
+
0,
|
|
302
|
+
0,
|
|
303
|
+
.31
|
|
304
|
+
],
|
|
305
|
+
[
|
|
306
|
+
.25,
|
|
307
|
+
.1096,
|
|
308
|
+
.2646
|
|
309
|
+
],
|
|
310
|
+
[
|
|
311
|
+
.5,
|
|
312
|
+
.155,
|
|
313
|
+
.155
|
|
314
|
+
],
|
|
315
|
+
[
|
|
316
|
+
.75,
|
|
317
|
+
.1096,
|
|
318
|
+
.0454
|
|
319
|
+
],
|
|
320
|
+
[
|
|
321
|
+
1,
|
|
322
|
+
0,
|
|
323
|
+
0
|
|
324
|
+
]
|
|
325
|
+
];
|
|
326
|
+
const CURVE_POINTS = [
|
|
327
|
+
[-.46736, .92887],
|
|
328
|
+
[.0908, .66613],
|
|
329
|
+
[.23177, .40825],
|
|
330
|
+
[.18264, .09152],
|
|
331
|
+
[0, 0]
|
|
332
|
+
];
|
|
333
|
+
const frame = (offset, translate, rest = {}) => translate === null ? null : {
|
|
334
|
+
offset,
|
|
335
|
+
translate,
|
|
336
|
+
...rest
|
|
337
|
+
};
|
|
338
|
+
const all = (frames) => frames.every((f) => f !== null) ? frames : null;
|
|
339
|
+
/**
|
|
340
|
+
* The entrance presets made of several behaviours, as keyframes from where the
|
|
341
|
+
* shape starts to its own place. Each offset is where one of the reference desktop app's
|
|
342
|
+
* behaviours starts or ends, as a fraction of the preset's default length.
|
|
343
|
+
*/
|
|
344
|
+
const TRACKS = {
|
|
345
|
+
bounce: (g) => {
|
|
346
|
+
const at = (ms, height, easing) => frame(ms / BOUNCE_LENGTH, g.shift(-.25 * (1 - filtered(BOUNCE_DRIFT, ms / BOUNCE_LENGTH)), -height), { easing });
|
|
347
|
+
const frames = [at(0, 1 / 3, "ease-in")];
|
|
348
|
+
BOUNCE_LANDINGS.forEach((landing, i) => {
|
|
349
|
+
frames.push(at(landing, 0, "ease-out"));
|
|
350
|
+
const next = BOUNCE_LANDINGS[i + 1];
|
|
351
|
+
if (next !== void 0) frames.push(at((landing + next) / 2, BOUNCE_HEIGHTS[i], "ease-in"));
|
|
352
|
+
});
|
|
353
|
+
return all(frames);
|
|
354
|
+
},
|
|
355
|
+
boomerang: (g) => all([
|
|
356
|
+
frame(0, g.shift(.4, -.2), {
|
|
357
|
+
rotate: "-90deg",
|
|
358
|
+
scale: "1 1",
|
|
359
|
+
opacity: 0,
|
|
360
|
+
easing: "ease-out"
|
|
361
|
+
}),
|
|
362
|
+
frame(.5, g.shift(0, .1), {
|
|
363
|
+
rotate: "0deg",
|
|
364
|
+
scale: "0.05 1",
|
|
365
|
+
opacity: .5,
|
|
366
|
+
easing: "ease-in"
|
|
367
|
+
}),
|
|
368
|
+
frame(1, g.shift(0, 0), {
|
|
369
|
+
rotate: "0deg",
|
|
370
|
+
scale: "1 1",
|
|
371
|
+
opacity: 1
|
|
372
|
+
})
|
|
373
|
+
]),
|
|
374
|
+
centerRevolve: (g) => all([
|
|
375
|
+
frame(0, g.shift(0, .31), { opacity: 0 }),
|
|
376
|
+
frame(.1, g.shift(0, .31), { opacity: 1 }),
|
|
377
|
+
...REVOLVE_ARC.map(([t, x, y]) => frame(.4 + .6 * t, g.shift(x, y), { opacity: 1 }))
|
|
378
|
+
]),
|
|
379
|
+
riseUp: (g) => all([
|
|
380
|
+
frame(0, g.shift(0, 1), {
|
|
381
|
+
opacity: 0,
|
|
382
|
+
easing: "ease-out"
|
|
383
|
+
}),
|
|
384
|
+
frame(.9, g.shift(0, -.03), {
|
|
385
|
+
opacity: .9,
|
|
386
|
+
easing: "ease-in"
|
|
387
|
+
}),
|
|
388
|
+
frame(1, g.shift(0, 0), { opacity: 1 })
|
|
389
|
+
]),
|
|
390
|
+
floating: (g) => all([
|
|
391
|
+
frame(0, g.shift(.4, -.4), {
|
|
392
|
+
rotate: "-90deg",
|
|
393
|
+
opacity: 0,
|
|
394
|
+
easing: "ease-out"
|
|
395
|
+
}),
|
|
396
|
+
frame(.8, g.shift(-.05, .1), {
|
|
397
|
+
rotate: "0deg",
|
|
398
|
+
opacity: 1,
|
|
399
|
+
easing: "ease-in"
|
|
400
|
+
}),
|
|
401
|
+
frame(1, g.shift(0, 0), {
|
|
402
|
+
rotate: "0deg",
|
|
403
|
+
opacity: 1
|
|
404
|
+
})
|
|
405
|
+
]),
|
|
406
|
+
whip: (g) => all([
|
|
407
|
+
frame(0, g.shift(0, 0), {
|
|
408
|
+
scale: "0.1",
|
|
409
|
+
opacity: 0
|
|
410
|
+
}),
|
|
411
|
+
frame(.5, g.shift(.1, 0), {
|
|
412
|
+
scale: "1.02",
|
|
413
|
+
opacity: 1
|
|
414
|
+
}),
|
|
415
|
+
frame(1, g.shift(0, 0), {
|
|
416
|
+
scale: "1",
|
|
417
|
+
opacity: 1
|
|
418
|
+
})
|
|
419
|
+
]),
|
|
420
|
+
curveUp: (g) => all(CURVE_POINTS.map(([x, y], i) => frame(i / (CURVE_POINTS.length - 1), g.shift(x, y), {
|
|
421
|
+
scale: `${2.5 - 1.5 * i / (CURVE_POINTS.length - 1)}`,
|
|
422
|
+
opacity: i / (CURVE_POINTS.length - 1)
|
|
423
|
+
}))),
|
|
424
|
+
drop: (g) => all([
|
|
425
|
+
frame(0, g.shift(0, -1), {
|
|
426
|
+
rotate: "-45deg",
|
|
427
|
+
easing: "ease-in"
|
|
428
|
+
}),
|
|
429
|
+
frame(.455, g.shift(0, 0, 0, -10), {
|
|
430
|
+
rotate: "-45deg",
|
|
431
|
+
easing: "ease-out"
|
|
432
|
+
}),
|
|
433
|
+
frame(.611, g.shift(0, 0, 0, -50), {
|
|
434
|
+
rotate: "0deg",
|
|
435
|
+
easing: "ease-in"
|
|
436
|
+
}),
|
|
437
|
+
frame(.767, g.shift(0, 0, 0, -10), { rotate: "42deg" }),
|
|
438
|
+
frame(.864, g.shift(0, 0, 0, -10), { rotate: "15deg" }),
|
|
439
|
+
frame(.91, g.shift(0, 0, 0, -5), { rotate: "0deg" }),
|
|
440
|
+
frame(1, g.shift(0, 0), { rotate: "0deg" })
|
|
441
|
+
]),
|
|
442
|
+
flip: (g) => flipTrack(g, "top")
|
|
443
|
+
};
|
|
444
|
+
const flipTrack = (g, edge) => {
|
|
445
|
+
const past = g.past(edge);
|
|
446
|
+
if (past === null) return null;
|
|
447
|
+
const at = (share, own) => `calc(${past.dx * share}px + ${own}%) calc(${past.dy * share}px + 0%)`;
|
|
448
|
+
const entering = edge === "top";
|
|
449
|
+
const from = entering ? 1 : 0;
|
|
450
|
+
const to = entering ? 0 : 1;
|
|
451
|
+
return [
|
|
452
|
+
{
|
|
453
|
+
offset: 0,
|
|
454
|
+
translate: at(from, 0),
|
|
455
|
+
scale: "1 1",
|
|
456
|
+
rotate: "0deg"
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
offset: .5,
|
|
460
|
+
translate: at(.5, 50),
|
|
461
|
+
scale: "-1 1",
|
|
462
|
+
rotate: "180deg"
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
offset: 1,
|
|
466
|
+
translate: at(to, 0),
|
|
467
|
+
scale: "1 1",
|
|
468
|
+
rotate: "360deg"
|
|
469
|
+
}
|
|
470
|
+
];
|
|
471
|
+
};
|
|
472
|
+
/** The exits whose track is not their entrance's, played backwards. */
|
|
473
|
+
const EXIT_TRACKS = {
|
|
474
|
+
drop: (g) => all([frame(0, g.shift(0, 0), {
|
|
475
|
+
rotate: "0deg",
|
|
476
|
+
easing: "ease-in"
|
|
477
|
+
}), frame(1, g.shift(0, 1), { rotate: "45deg" })]),
|
|
478
|
+
flip: (g) => flipTrack(g, "bottom")
|
|
479
|
+
};
|
|
480
|
+
/** The entrance track whose reverse is an exit's: Sink Down is Rise Up backwards. */
|
|
481
|
+
const TRACK_OF_EXIT = {
|
|
482
|
+
sinkDown: "riseUp",
|
|
483
|
+
curveDown: "curveUp"
|
|
484
|
+
};
|
|
485
|
+
const SWAPPED_EASING = {
|
|
486
|
+
"ease-in": "ease-out",
|
|
487
|
+
"ease-out": "ease-in"
|
|
488
|
+
};
|
|
489
|
+
/**
|
|
490
|
+
* A track played backwards: the last frame first, each offset mirrored, and
|
|
491
|
+
* the easing of each stretch moved to the frame that now starts it, turned
|
|
492
|
+
* round — a stretch that slowed into a frame speeds out of it.
|
|
493
|
+
*/
|
|
494
|
+
const reversed = (frames) => {
|
|
495
|
+
const count = frames.length;
|
|
496
|
+
return frames.map((_, i) => {
|
|
497
|
+
const pose = { ...frames[count - 1 - i] };
|
|
498
|
+
delete pose.easing;
|
|
499
|
+
if (typeof pose.offset === "number") pose.offset = 1 - pose.offset;
|
|
500
|
+
const easing = frames[count - 2 - i]?.easing;
|
|
501
|
+
if (typeof easing === "string") pose.easing = SWAPPED_EASING[easing] ?? easing;
|
|
502
|
+
return pose;
|
|
503
|
+
});
|
|
504
|
+
};
|
|
505
|
+
/** The track of a preset, in the direction the step plays it; `undefined` when it has none. */
|
|
506
|
+
const trackOf = (item) => {
|
|
507
|
+
const family = item.step.effect?.replace(/(In|Out)$/, "");
|
|
508
|
+
if (family === void 0) return void 0;
|
|
509
|
+
if (item.kind === "entrance") return TRACKS[family];
|
|
510
|
+
const own = EXIT_TRACKS[family];
|
|
511
|
+
if (own !== void 0) return own;
|
|
512
|
+
const entrance = TRACKS[TRACK_OF_EXIT[family] ?? family];
|
|
513
|
+
return entrance === void 0 ? void 0 : (g) => {
|
|
514
|
+
const frames = entrance(g);
|
|
515
|
+
return frames === null ? null : reversed(frames);
|
|
516
|
+
};
|
|
517
|
+
};
|
|
518
|
+
const styled = (el) => el instanceof HTMLElement || el instanceof SVGElement ? el : null;
|
|
519
|
+
/** Whether a step reveals its target, hides it, or neither; null when we cannot tell. */
|
|
520
|
+
const kindOf = (step) => {
|
|
521
|
+
if (step.effect === null || step.presetClass === null) return null;
|
|
522
|
+
return PRESET_CLASS_KINDS[step.presetClass] ?? null;
|
|
523
|
+
};
|
|
524
|
+
/**
|
|
525
|
+
* How long an effect runs, or null when the deck does not say. Null is not
|
|
526
|
+
* zero: it is the reason nothing can be chained onto the end of this effect.
|
|
527
|
+
*/
|
|
528
|
+
const durationOf = (step) => {
|
|
529
|
+
if (step.durationMs !== null && Number.isFinite(step.durationMs) && step.durationMs >= 0) return step.durationMs;
|
|
530
|
+
if (step.effect !== null && INSTANT_EFFECTS.includes(step.effect)) return 0;
|
|
531
|
+
return null;
|
|
532
|
+
};
|
|
533
|
+
/**
|
|
534
|
+
* Groups the steps into the stops the viewer clicks through, with each step's
|
|
535
|
+
* start measured from the beginning of its stop, and lists the steps that are
|
|
536
|
+
* left out with the reason why.
|
|
537
|
+
*/
|
|
538
|
+
const buildAnimationStops = (steps) => {
|
|
539
|
+
const stops = [];
|
|
540
|
+
const unsupported = [];
|
|
541
|
+
const skip = (step, reason) => {
|
|
542
|
+
unsupported.push({
|
|
543
|
+
step,
|
|
544
|
+
reason
|
|
545
|
+
});
|
|
546
|
+
};
|
|
547
|
+
let stop = null;
|
|
548
|
+
let groupStart = 0;
|
|
549
|
+
let groupEnd = 0;
|
|
550
|
+
let seen = false;
|
|
551
|
+
for (const step of steps ?? []) {
|
|
552
|
+
if (step.sequence !== "mainSeq") {
|
|
553
|
+
skip(step, "notInMainSeq");
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
const leading = !seen;
|
|
557
|
+
seen = true;
|
|
558
|
+
if (step.start === "unknown") {
|
|
559
|
+
groupStart = null;
|
|
560
|
+
groupEnd = null;
|
|
561
|
+
skip(step, "notModelled");
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
if (step.start === "click" || leading) {
|
|
565
|
+
stop = {
|
|
566
|
+
items: [],
|
|
567
|
+
auto: leading && step.start !== "click"
|
|
568
|
+
};
|
|
569
|
+
stops.push(stop);
|
|
570
|
+
groupStart = 0;
|
|
571
|
+
groupEnd = 0;
|
|
572
|
+
} else if (step.start === "afterPrevious") {
|
|
573
|
+
groupStart = groupEnd;
|
|
574
|
+
groupEnd = groupStart;
|
|
575
|
+
}
|
|
576
|
+
const kind = step.playable ? kindOf(step) : null;
|
|
577
|
+
const duration = kind === null ? null : durationOf(step);
|
|
578
|
+
const delay = step.delayMs;
|
|
579
|
+
if (kind === null || delay === null || groupStart === null) {
|
|
580
|
+
skip(step, kind === null || delay === null ? "notModelled" : "unknownTiming");
|
|
581
|
+
groupEnd = null;
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
const begin = groupStart + delay;
|
|
585
|
+
stop?.items.push({
|
|
586
|
+
step,
|
|
587
|
+
kind,
|
|
588
|
+
begin,
|
|
589
|
+
duration
|
|
590
|
+
});
|
|
591
|
+
groupEnd = duration === null || groupEnd === null ? null : Math.max(groupEnd, begin + duration);
|
|
592
|
+
}
|
|
593
|
+
return {
|
|
594
|
+
stops,
|
|
595
|
+
unsupported
|
|
596
|
+
};
|
|
597
|
+
};
|
|
598
|
+
const shapeSelector = (id) => `[data-pptx-shape-id="${id}"]`;
|
|
599
|
+
/**
|
|
600
|
+
* The elements one step animates: a whole object, or paragraphs of its text.
|
|
601
|
+
* Empty when the step's target is one we do not act on.
|
|
602
|
+
*/
|
|
603
|
+
const targetsOf = (root, step, blocked) => {
|
|
604
|
+
const target = step.target;
|
|
605
|
+
if (target.kind !== "shape" && target.kind !== "paragraphs") return [];
|
|
606
|
+
if (!Number.isInteger(target.shapeId)) return [];
|
|
607
|
+
const id = String(target.shapeId);
|
|
608
|
+
if (blocked.has(id)) return [];
|
|
609
|
+
const shapes = root.querySelectorAll(shapeSelector(id));
|
|
610
|
+
const shape = shapes.length === 1 ? styled(shapes[0]) : null;
|
|
611
|
+
if (shape === null) return [];
|
|
612
|
+
if (target.kind !== "paragraphs") return [shape];
|
|
613
|
+
const out = [];
|
|
614
|
+
for (const marker of shape.querySelectorAll("[data-pptx-paragraph]")) {
|
|
615
|
+
if (marker.closest("[data-pptx-shape-id]") !== shape) continue;
|
|
616
|
+
const n = Number(marker.getAttribute("data-pptx-paragraph"));
|
|
617
|
+
if (n < target.firstParagraph || n > target.lastParagraph) continue;
|
|
618
|
+
const element = styled(marker);
|
|
619
|
+
if (element !== null) out.push(element);
|
|
620
|
+
}
|
|
621
|
+
return out;
|
|
622
|
+
};
|
|
623
|
+
const nowMs = () => typeof performance === "object" ? performance.now() : Date.now();
|
|
624
|
+
/**
|
|
625
|
+
* The slide's own box on screen. A fly is stated against the slide's edges
|
|
626
|
+
* (`1+#ppt_h/2` puts the shape's centre one half-height below the bottom of it),
|
|
627
|
+
* so that is what the distance has to be measured from — never the viewport,
|
|
628
|
+
* which is not where the slide ends.
|
|
629
|
+
*/
|
|
630
|
+
const slideBoxOf = (el, root) => {
|
|
631
|
+
const svg = el.closest("svg");
|
|
632
|
+
if (svg !== null) return svg.getBoundingClientRect();
|
|
633
|
+
return root instanceof Element ? root.getBoundingClientRect() : null;
|
|
634
|
+
};
|
|
635
|
+
/**
|
|
636
|
+
* The matrix taking the element's own coordinates to the screen, which is also
|
|
637
|
+
* the space a CSS transform written on it is read in.
|
|
638
|
+
*
|
|
639
|
+
* The shape and paragraph markers carry no transform of their own — the
|
|
640
|
+
* renderer wraps the drawn shape rather than transforming the wrapper — so the
|
|
641
|
+
* matrix does not have to be un-done first, and a shape the deck rotates or a
|
|
642
|
+
* group it scales is accounted for by the ancestors it comes through. Text laid
|
|
643
|
+
* out as HTML sits in a `<foreignObject>`, whose own matrix is the one its
|
|
644
|
+
* pixels are scaled by.
|
|
645
|
+
*/
|
|
646
|
+
const screenMatrixOf = (el) => {
|
|
647
|
+
if (el instanceof SVGGraphicsElement) return el.getScreenCTM();
|
|
648
|
+
const host = el.closest("foreignObject");
|
|
649
|
+
return host instanceof SVGGraphicsElement ? host.getScreenCTM() : null;
|
|
650
|
+
};
|
|
651
|
+
/**
|
|
652
|
+
* How far the element has to move to sit just outside the named edge, in the
|
|
653
|
+
* units its own CSS transform is written in. `null` when the slide has not been
|
|
654
|
+
* laid out yet, or its matrix cannot be inverted.
|
|
655
|
+
*/
|
|
656
|
+
const flyOffset = (el, direction, root) => {
|
|
657
|
+
const slide = slideBoxOf(el, root);
|
|
658
|
+
if (slide === null || slide.width <= 0 || slide.height <= 0) return null;
|
|
659
|
+
const box = el.getBoundingClientRect();
|
|
660
|
+
const lower = direction.toLowerCase();
|
|
661
|
+
return toOwnUnits(el, lower.includes("right") ? slide.right - box.left : lower.includes("left") ? slide.left - box.right : 0, lower.includes("bottom") ? slide.bottom - box.top : lower.includes("top") ? slide.top - box.bottom : 0);
|
|
662
|
+
};
|
|
663
|
+
/** A distance on screen in the units the element's own CSS transform is written in. */
|
|
664
|
+
const toOwnUnits = (el, dx, dy) => {
|
|
665
|
+
const matrix = screenMatrixOf(el);
|
|
666
|
+
if (matrix === null) return {
|
|
667
|
+
dx,
|
|
668
|
+
dy
|
|
669
|
+
};
|
|
670
|
+
const determinant = matrix.a * matrix.d - matrix.b * matrix.c;
|
|
671
|
+
if (determinant === 0) return null;
|
|
672
|
+
return {
|
|
673
|
+
dx: (matrix.d * dx - matrix.c * dy) / determinant,
|
|
674
|
+
dy: (matrix.a * dy - matrix.b * dx) / determinant
|
|
675
|
+
};
|
|
676
|
+
};
|
|
677
|
+
const geometryOf = (el, root) => ({
|
|
678
|
+
shift(slideX, slideY, ownX = 0, ownY = 0) {
|
|
679
|
+
const slide = slideBoxOf(el, root);
|
|
680
|
+
if (slide === null || slide.width <= 0 || slide.height <= 0) return null;
|
|
681
|
+
const own = toOwnUnits(el, slideX * slide.width, slideY * slide.height);
|
|
682
|
+
if (own === null) return null;
|
|
683
|
+
return `calc(${own.dx}px + ${ownX}%) calc(${own.dy}px + ${ownY}%)`;
|
|
684
|
+
},
|
|
685
|
+
past(edge) {
|
|
686
|
+
return flyOffset(el, edge, root);
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
/**
|
|
690
|
+
* Each motion animates a property of its own — `translate`, `scale`, `rotate`
|
|
691
|
+
* — rather than all of them sharing `transform`.
|
|
692
|
+
*
|
|
693
|
+
* Two reasons, both of them things the deck states and a single `transform`
|
|
694
|
+
* would lose. The first is that PresentationML animates a shape's position, its
|
|
695
|
+
* size and its rotation as separate attributes (`ppt_x`/`ppt_y`, `ppt_w`/
|
|
696
|
+
* `ppt_h`, `r`), so a slide may well run a fly and a spin over one shape at the
|
|
697
|
+
* same moment; written to one property, the animation that begins second
|
|
698
|
+
* replaces the first outright and the shape would only turn. The second is that
|
|
699
|
+
* the drawn slide uses `transform` itself, for the rotation and the group
|
|
700
|
+
* scaling the deck gives a shape — and the individual properties apply on top
|
|
701
|
+
* of it rather than in place of it, so nothing the renderer did is overwritten.
|
|
702
|
+
*/
|
|
703
|
+
/**
|
|
704
|
+
* Whether a step's motion has to be measured against the drawn slide, so that a
|
|
705
|
+
* movement of `null` means "not read yet" rather than "nothing to run".
|
|
706
|
+
*/
|
|
707
|
+
const needsGeometry = (item) => item.step.effect !== null && (motionOf(item.step.effect) === "fly" || trackOf(item) !== void 0);
|
|
708
|
+
/**
|
|
709
|
+
* The keyframes one step runs on one element, or `null` when there is nothing
|
|
710
|
+
* to run: an instant effect, or a fly whose distance cannot be measured. Either
|
|
711
|
+
* way the element is put where the step leaves it without motion, which for
|
|
712
|
+
* every effect modelled here is the element's own untransformed place.
|
|
713
|
+
*/
|
|
714
|
+
const movementFor = (item, el, root) => {
|
|
715
|
+
const effect = item.step.effect;
|
|
716
|
+
const entering = item.kind === "entrance";
|
|
717
|
+
if (effect === null) return null;
|
|
718
|
+
switch (motionOf(effect)) {
|
|
719
|
+
case "none": return null;
|
|
720
|
+
case "pose": {
|
|
721
|
+
if (item.kind === "emphasis") {
|
|
722
|
+
const frames = optionedEmphasis(item.step) ?? EMPHASIS[effect];
|
|
723
|
+
return frames === void 0 ? null : {
|
|
724
|
+
frames,
|
|
725
|
+
aboutOwnCentre: true
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
const track = trackOf(item);
|
|
729
|
+
if (track !== void 0) {
|
|
730
|
+
const frames = track(geometryOf(el, root));
|
|
731
|
+
return frames === null ? null : {
|
|
732
|
+
frames,
|
|
733
|
+
aboutOwnCentre: true
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
const away = AWAY[effect.replace(/(In|Out)$/, "")];
|
|
737
|
+
if (away === void 0) return null;
|
|
738
|
+
const rest = restOf(away);
|
|
739
|
+
return {
|
|
740
|
+
frames: entering ? [away, rest] : [rest, away],
|
|
741
|
+
aboutOwnCentre: true
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
case "fly": {
|
|
745
|
+
const direction = effect === "creditsIn" ? "bottom" : effect === "creditsOut" ? "top" : item.step.direction;
|
|
746
|
+
if (direction === null) return null;
|
|
747
|
+
const offset = flyOffset(el, direction, root);
|
|
748
|
+
if (offset === null) return null;
|
|
749
|
+
const away = { translate: `${offset.dx}px ${offset.dy}px` };
|
|
750
|
+
const place = { translate: "0px 0px" };
|
|
751
|
+
return {
|
|
752
|
+
frames: entering ? [away, place] : [place, away],
|
|
753
|
+
aboutOwnCentre: false
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
case "filter":
|
|
757
|
+
case "peek": {
|
|
758
|
+
const reveal = filterReveal(item.step);
|
|
759
|
+
if (reveal === null) return null;
|
|
760
|
+
const covered = { clipPath: reveal.covered };
|
|
761
|
+
const uncovered = { clipPath: reveal.uncovered };
|
|
762
|
+
const shift = item.step.direction === null ? void 0 : EDGE_VECTORS[item.step.direction];
|
|
763
|
+
if (effect === "peekIn" || effect === "peekOut") {
|
|
764
|
+
if (shift === void 0) return null;
|
|
765
|
+
covered.translate = `${shift[0] * 112.5}% ${shift[1] * 112.5}%`;
|
|
766
|
+
uncovered.translate = "0% 0%";
|
|
767
|
+
}
|
|
768
|
+
return {
|
|
769
|
+
frames: entering ? [covered, uncovered] : [uncovered, covered],
|
|
770
|
+
aboutOwnCentre: true
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
/** Which way each edge lies from the centre, as unit steps in x and y. */
|
|
776
|
+
const EDGE_VECTORS = {
|
|
777
|
+
top: [0, -1],
|
|
778
|
+
bottom: [0, 1],
|
|
779
|
+
left: [-1, 0],
|
|
780
|
+
right: [1, 0]
|
|
781
|
+
};
|
|
782
|
+
const OPPOSITE_EDGE = {
|
|
783
|
+
top: "bottom",
|
|
784
|
+
bottom: "top",
|
|
785
|
+
left: "right",
|
|
786
|
+
right: "left"
|
|
787
|
+
};
|
|
788
|
+
/** `inset()` with nothing showing but the named edge's zero-width strip. */
|
|
789
|
+
const COVERED_FROM_EDGE = {
|
|
790
|
+
top: "inset(0% 0% 100% 0%)",
|
|
791
|
+
bottom: "inset(100% 0% 0% 0%)",
|
|
792
|
+
left: "inset(0% 100% 0% 0%)",
|
|
793
|
+
right: "inset(0% 0% 0% 100%)"
|
|
794
|
+
};
|
|
795
|
+
const CORNERS = {
|
|
796
|
+
topLeft: [0, 0],
|
|
797
|
+
topRight: [100, 0],
|
|
798
|
+
bottomLeft: [0, 100],
|
|
799
|
+
bottomRight: [100, 100]
|
|
800
|
+
};
|
|
801
|
+
const polygon = (points, evenOdd = false) => `polygon(${evenOdd ? "evenodd, " : ""}${points.map(([x, y]) => `${x}% ${y}%`).join(", ")}) fill-box`;
|
|
802
|
+
const FULL_BOX = [
|
|
803
|
+
[0, 0],
|
|
804
|
+
[100, 0],
|
|
805
|
+
[100, 100],
|
|
806
|
+
[0, 100]
|
|
807
|
+
];
|
|
808
|
+
/**
|
|
809
|
+
* `count` bars across the box, each `fraction` of its slot wide. The bars are
|
|
810
|
+
* one polygon, joined along the box's edge by lines of no width.
|
|
811
|
+
*/
|
|
812
|
+
const bars = (count, horizontal, fraction) => {
|
|
813
|
+
const points = [];
|
|
814
|
+
for (let i = 0; i < count; i++) {
|
|
815
|
+
const from = 100 * i / count;
|
|
816
|
+
const to = from + 100 * fraction / count;
|
|
817
|
+
if (horizontal) points.push([0, from], [100, from], [100, to], [0, to]);
|
|
818
|
+
else points.push([from, 0], [from, 100], [to, 100], [to, 0]);
|
|
819
|
+
}
|
|
820
|
+
return polygon(points);
|
|
821
|
+
};
|
|
822
|
+
const CIRCLE_RADIUS = 50 * Math.SQRT2;
|
|
823
|
+
const CIRCLE_POINTS = 32;
|
|
824
|
+
const FAN_POINTS = 12;
|
|
825
|
+
/** An outline about the box's centre, `scale` 1 being large enough to cover the box. */
|
|
826
|
+
const outline = (shape, scale) => {
|
|
827
|
+
const at = (x, y) => [50 + x * scale, 50 + y * scale];
|
|
828
|
+
switch (shape) {
|
|
829
|
+
case "box": return [
|
|
830
|
+
at(-50, -50),
|
|
831
|
+
at(50, -50),
|
|
832
|
+
at(50, 50),
|
|
833
|
+
at(-50, 50)
|
|
834
|
+
];
|
|
835
|
+
case "diamond": return [
|
|
836
|
+
at(0, -100),
|
|
837
|
+
at(100, 0),
|
|
838
|
+
at(0, 100),
|
|
839
|
+
at(-100, 0)
|
|
840
|
+
];
|
|
841
|
+
case "plus": return [
|
|
842
|
+
at(-50, -100),
|
|
843
|
+
at(50, -100),
|
|
844
|
+
at(50, -50),
|
|
845
|
+
at(100, -50),
|
|
846
|
+
at(100, 50),
|
|
847
|
+
at(50, 50),
|
|
848
|
+
at(50, 100),
|
|
849
|
+
at(-50, 100),
|
|
850
|
+
at(-50, 50),
|
|
851
|
+
at(-100, 50),
|
|
852
|
+
at(-100, -50),
|
|
853
|
+
at(-50, -50)
|
|
854
|
+
];
|
|
855
|
+
default: return Array.from({ length: CIRCLE_POINTS }, (_, i) => {
|
|
856
|
+
const angle = 2 * Math.PI * i / CIRCLE_POINTS;
|
|
857
|
+
return at(CIRCLE_RADIUS * Math.cos(angle), CIRCLE_RADIUS * Math.sin(angle));
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
/**
|
|
862
|
+
* A fan from the centre, swept `sweep` of a turn from `start` — both in turns,
|
|
863
|
+
* clockwise from 12 o'clock — and long enough to reach every corner.
|
|
864
|
+
*/
|
|
865
|
+
const fan = (start, sweep) => [[50, 50], ...Array.from({ length: 13 }, (_, i) => {
|
|
866
|
+
const angle = 2 * Math.PI * (start + sweep * i / FAN_POINTS);
|
|
867
|
+
return [50 + 100 * Math.sin(angle), 50 - 100 * Math.cos(angle)];
|
|
868
|
+
})];
|
|
869
|
+
const filterReveal = (step) => {
|
|
870
|
+
const effect = step.effect;
|
|
871
|
+
if (effect === null) return null;
|
|
872
|
+
const family = effect.replace(/(In|Out)$/, "");
|
|
873
|
+
const horizontal = step.orientation !== "vertical";
|
|
874
|
+
switch (family) {
|
|
875
|
+
case "wipe":
|
|
876
|
+
case "peek": {
|
|
877
|
+
const edge = step.direction === null ? void 0 : family === "peek" ? OPPOSITE_EDGE[step.direction] : step.direction;
|
|
878
|
+
const covered = edge === void 0 ? void 0 : COVERED_FROM_EDGE[edge];
|
|
879
|
+
if (covered === void 0) return null;
|
|
880
|
+
return {
|
|
881
|
+
covered: `${covered} fill-box`,
|
|
882
|
+
uncovered: "inset(0% 0% 0% 0%) fill-box"
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
case "blinds": return {
|
|
886
|
+
covered: bars(6, horizontal, 0),
|
|
887
|
+
uncovered: bars(6, horizontal, 1)
|
|
888
|
+
};
|
|
889
|
+
case "randomBars": return {
|
|
890
|
+
covered: bars(16, horizontal, 0),
|
|
891
|
+
uncovered: bars(16, horizontal, 1)
|
|
892
|
+
};
|
|
893
|
+
case "checkerboard": return {
|
|
894
|
+
covered: bars(8, !horizontal, 0),
|
|
895
|
+
uncovered: bars(8, !horizontal, 1)
|
|
896
|
+
};
|
|
897
|
+
case "split": {
|
|
898
|
+
const vertical = step.orientation === "vertical";
|
|
899
|
+
if (step.inOut === "out") {
|
|
900
|
+
const line = (open) => {
|
|
901
|
+
const side = `${50 - open / 2}%`;
|
|
902
|
+
return vertical ? `inset(0% ${side} 0% ${side}) fill-box` : `inset(${side} 0% ${side} 0%) fill-box`;
|
|
903
|
+
};
|
|
904
|
+
return {
|
|
905
|
+
covered: line(0),
|
|
906
|
+
uncovered: line(100)
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
const edges = (p) => polygon(vertical ? [
|
|
910
|
+
[0, 0],
|
|
911
|
+
[p, 0],
|
|
912
|
+
[p, 100],
|
|
913
|
+
[100 - p, 100],
|
|
914
|
+
[100 - p, 0],
|
|
915
|
+
[100, 0],
|
|
916
|
+
[100, 100],
|
|
917
|
+
[0, 100]
|
|
918
|
+
] : [
|
|
919
|
+
[0, 0],
|
|
920
|
+
[100, 0],
|
|
921
|
+
[100, p],
|
|
922
|
+
[0, p],
|
|
923
|
+
[0, 100 - p],
|
|
924
|
+
[100, 100 - p],
|
|
925
|
+
[100, 100],
|
|
926
|
+
[0, 100]
|
|
927
|
+
]);
|
|
928
|
+
return {
|
|
929
|
+
covered: edges(0),
|
|
930
|
+
uncovered: edges(50)
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
case "shape": {
|
|
934
|
+
const shape = step.shape ?? "circle";
|
|
935
|
+
if (step.inOut === "out") return {
|
|
936
|
+
covered: polygon(outline(shape, 0)),
|
|
937
|
+
uncovered: polygon(outline(shape, 1))
|
|
938
|
+
};
|
|
939
|
+
return {
|
|
940
|
+
covered: polygon([...FULL_BOX, ...outline(shape, 1)], true),
|
|
941
|
+
uncovered: polygon([...FULL_BOX, ...outline(shape, 0)], true)
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
case "strips": {
|
|
945
|
+
const corner = step.direction === null ? void 0 : CORNERS[step.direction];
|
|
946
|
+
if (corner === void 0) return null;
|
|
947
|
+
const [cx, cy] = corner;
|
|
948
|
+
const sx = cx === 0 ? 1 : -1;
|
|
949
|
+
const sy = cy === 0 ? 1 : -1;
|
|
950
|
+
const triangle = (size) => polygon([
|
|
951
|
+
[cx, cy],
|
|
952
|
+
[cx + sx * size, cy],
|
|
953
|
+
[cx, cy + sy * size]
|
|
954
|
+
]);
|
|
955
|
+
return {
|
|
956
|
+
covered: triangle(0),
|
|
957
|
+
uncovered: triangle(200)
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
case "wedge": return {
|
|
961
|
+
covered: polygon([...fan(0, 0), ...fan(0, 0)]),
|
|
962
|
+
uncovered: polygon([...fan(0, .5), ...fan(0, -.5)])
|
|
963
|
+
};
|
|
964
|
+
case "wheel": {
|
|
965
|
+
const spokes = step.spokes ?? 1;
|
|
966
|
+
const sectors = (sweep) => polygon(Array.from({ length: spokes }, (_, i) => fan(i / spokes, sweep / spokes)).flat());
|
|
967
|
+
return {
|
|
968
|
+
covered: sectors(0),
|
|
969
|
+
uncovered: sectors(1)
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
default: return null;
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
/** Plays a slide's animations against a rendered SVG. */
|
|
976
|
+
const createAnimationPlayer = (options) => {
|
|
977
|
+
const root = () => typeof options.root === "function" ? options.root() : options.root;
|
|
978
|
+
const parsed = buildAnimationStops(options.steps);
|
|
979
|
+
const stops = parsed.stops;
|
|
980
|
+
const unsupported = [...parsed.unsupported];
|
|
981
|
+
const reduced = () => options.reducedMotion?.() === true;
|
|
982
|
+
const onChange = options.onChange ?? (() => {});
|
|
983
|
+
const animations = [];
|
|
984
|
+
const timers = [];
|
|
985
|
+
const touched = /* @__PURE__ */ new Set();
|
|
986
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
987
|
+
const frozen = /* @__PURE__ */ new Map();
|
|
988
|
+
const outstanding = /* @__PURE__ */ new Map();
|
|
989
|
+
const finished = /* @__PURE__ */ new Set();
|
|
990
|
+
let cursor = 0;
|
|
991
|
+
let disposed = false;
|
|
992
|
+
let startedAt = null;
|
|
993
|
+
let generation = 0;
|
|
994
|
+
const blocked = /* @__PURE__ */ new Set();
|
|
995
|
+
for (const entry of unsupported) for (const shapeId of entry.step.targetShapeIds) blocked.add(String(shapeId));
|
|
996
|
+
const drawn = /* @__PURE__ */ new Map();
|
|
997
|
+
for (const stop of stops) for (const item of stop.items) {
|
|
998
|
+
const id = String(item.step.target.shapeId);
|
|
999
|
+
if (!drawn.has(id)) drawn.set(id, root().querySelectorAll(shapeSelector(id)).length);
|
|
1000
|
+
}
|
|
1001
|
+
for (const stop of stops) for (const item of stop.items) if ((drawn.get(String(item.step.target.shapeId)) ?? 0) > 1) unsupported.push({
|
|
1002
|
+
step: item.step,
|
|
1003
|
+
reason: "ambiguousTarget"
|
|
1004
|
+
});
|
|
1005
|
+
for (const [id, count] of drawn) if (count > 1) blocked.add(id);
|
|
1006
|
+
/**
|
|
1007
|
+
* Where two effects animate one element at the same time, what the slide
|
|
1008
|
+
* shows when the first of them ends turns on whether its value stays above
|
|
1009
|
+
* the other or gives way to it. `valueAfterEnd` says which; where the tree
|
|
1010
|
+
* does not say, there is nothing to play, so the step is reported and what it
|
|
1011
|
+
* names is left as the renderer drew it. On its own such a step is no
|
|
1012
|
+
* question at all — nothing is underneath it, and its own visibility decides
|
|
1013
|
+
* where the shape is left — so it plays as usual.
|
|
1014
|
+
*
|
|
1015
|
+
* Read per element, which is where "the same thing" is actually settled: a
|
|
1016
|
+
* build's paragraphs are separate elements. Each element's effects are sorted
|
|
1017
|
+
* once and swept twice, for what begins before an earlier one has ended and
|
|
1018
|
+
* what is still running when a later one begins — the pairs themselves are
|
|
1019
|
+
* never enumerated.
|
|
1020
|
+
*
|
|
1021
|
+
* Run again whenever the slide is drawn again, because which elements a step
|
|
1022
|
+
* reaches is a fact about the drawing: a transition hands over a layer, and a
|
|
1023
|
+
* re-render replaces the nodes underneath us.
|
|
1024
|
+
*/
|
|
1025
|
+
const silence = (current) => {
|
|
1026
|
+
const meets = /* @__PURE__ */ new Set();
|
|
1027
|
+
for (const stop of stops) {
|
|
1028
|
+
const spans = /* @__PURE__ */ new Map();
|
|
1029
|
+
for (const item of stop.items) {
|
|
1030
|
+
const end = item.duration === null ? Number.POSITIVE_INFINITY : item.begin + item.duration;
|
|
1031
|
+
for (const el of targetsOf(current, item.step, blocked)) {
|
|
1032
|
+
const span = {
|
|
1033
|
+
item,
|
|
1034
|
+
begin: item.begin,
|
|
1035
|
+
end
|
|
1036
|
+
};
|
|
1037
|
+
const list = spans.get(el);
|
|
1038
|
+
if (list === void 0) spans.set(el, [span]);
|
|
1039
|
+
else list.push(span);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
for (const list of spans.values()) {
|
|
1043
|
+
if (list.length < 2) continue;
|
|
1044
|
+
list.sort((a, b) => a.begin - b.begin);
|
|
1045
|
+
let furthest = Number.NEGATIVE_INFINITY;
|
|
1046
|
+
for (const span of list) {
|
|
1047
|
+
if (span.begin < furthest) meets.add(span.item);
|
|
1048
|
+
furthest = Math.max(furthest, span.end);
|
|
1049
|
+
}
|
|
1050
|
+
let earliest = Number.POSITIVE_INFINITY;
|
|
1051
|
+
for (let at = list.length - 1; at >= 0; at -= 1) {
|
|
1052
|
+
const span = list[at];
|
|
1053
|
+
if (span === void 0) continue;
|
|
1054
|
+
if (span.end > earliest) meets.add(span.item);
|
|
1055
|
+
earliest = Math.min(earliest, span.begin);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
for (const stop of stops) {
|
|
1060
|
+
let kept = 0;
|
|
1061
|
+
for (const item of stop.items) {
|
|
1062
|
+
if (item.step.valueAfterEnd === "unstated" && meets.has(item)) {
|
|
1063
|
+
unsupported.push({
|
|
1064
|
+
step: item.step,
|
|
1065
|
+
reason: "notModelled"
|
|
1066
|
+
});
|
|
1067
|
+
for (const shapeId of item.step.targetShapeIds) blocked.add(String(shapeId));
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
stop.items[kept] = item;
|
|
1071
|
+
kept += 1;
|
|
1072
|
+
}
|
|
1073
|
+
stop.items.length = kept;
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
const clearPending = () => {
|
|
1077
|
+
for (const timer of timers) clearTimeout(timer);
|
|
1078
|
+
timers.length = 0;
|
|
1079
|
+
for (const animation of animations) animation.cancel();
|
|
1080
|
+
animations.length = 0;
|
|
1081
|
+
for (const list of frozen.values()) for (const animation of list) animation.cancel();
|
|
1082
|
+
frozen.clear();
|
|
1083
|
+
inFlight.clear();
|
|
1084
|
+
outstanding.clear();
|
|
1085
|
+
finished.clear();
|
|
1086
|
+
};
|
|
1087
|
+
const drop = (list, entry) => {
|
|
1088
|
+
const at = list.indexOf(entry);
|
|
1089
|
+
if (at >= 0) list.splice(at, 1);
|
|
1090
|
+
};
|
|
1091
|
+
const notifyChanged = () => {
|
|
1092
|
+
if (disposed) return;
|
|
1093
|
+
if (timers.length === 0 && animations.length === 0) startedAt = null;
|
|
1094
|
+
onChange(player);
|
|
1095
|
+
};
|
|
1096
|
+
const centreOn = (el) => {
|
|
1097
|
+
touched.add(el);
|
|
1098
|
+
el.style.transformBox = "fill-box";
|
|
1099
|
+
el.style.transformOrigin = "50% 50%";
|
|
1100
|
+
};
|
|
1101
|
+
const uncentre = (el) => {
|
|
1102
|
+
el.style.transformBox = "";
|
|
1103
|
+
el.style.transformOrigin = "";
|
|
1104
|
+
};
|
|
1105
|
+
const show = (el) => {
|
|
1106
|
+
touched.add(el);
|
|
1107
|
+
el.style.visibility = "";
|
|
1108
|
+
el.style.opacity = "";
|
|
1109
|
+
uncentre(el);
|
|
1110
|
+
};
|
|
1111
|
+
const hide = (el) => {
|
|
1112
|
+
touched.add(el);
|
|
1113
|
+
el.style.visibility = "hidden";
|
|
1114
|
+
el.style.opacity = "";
|
|
1115
|
+
uncentre(el);
|
|
1116
|
+
};
|
|
1117
|
+
let indexedRoot = null;
|
|
1118
|
+
let laidOut = true;
|
|
1119
|
+
let plans = /* @__PURE__ */ new Map();
|
|
1120
|
+
let placements = /* @__PURE__ */ new Map();
|
|
1121
|
+
/** Every stop below this has run; within the stop in hand, `finished` says. */
|
|
1122
|
+
let settledBelow = 0;
|
|
1123
|
+
const hasPlayed = (entry) => entry.stopIndex < settledBelow || finished.has(entry.item);
|
|
1124
|
+
/** Reads every element's standing from its effects, for a redraw or a seek. */
|
|
1125
|
+
const recompute = () => {
|
|
1126
|
+
for (const plan of plans.values()) {
|
|
1127
|
+
plan.top = null;
|
|
1128
|
+
for (const entry of plan.gates) if (hasPlayed(entry)) plan.top = entry;
|
|
1129
|
+
const first = plan.gates[0];
|
|
1130
|
+
plan.firstPlayed = first !== void 0 && hasPlayed(first);
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
const index = () => {
|
|
1134
|
+
const current = root();
|
|
1135
|
+
const sample = plans.keys().next();
|
|
1136
|
+
if (current === indexedRoot && sample.done !== true && sample.value.isConnected && laidOut) return;
|
|
1137
|
+
indexedRoot = current;
|
|
1138
|
+
laidOut = true;
|
|
1139
|
+
silence(current);
|
|
1140
|
+
plans = /* @__PURE__ */ new Map();
|
|
1141
|
+
placements = /* @__PURE__ */ new Map();
|
|
1142
|
+
stops.forEach((stop, stopIndex) => {
|
|
1143
|
+
for (const item of stop.items) {
|
|
1144
|
+
const spots = [];
|
|
1145
|
+
for (const el of targetsOf(current, item.step, blocked)) {
|
|
1146
|
+
const plan = plans.get(el) ?? {
|
|
1147
|
+
list: [],
|
|
1148
|
+
gates: [],
|
|
1149
|
+
top: null,
|
|
1150
|
+
firstPlayed: false
|
|
1151
|
+
};
|
|
1152
|
+
plans.set(el, plan);
|
|
1153
|
+
const entry = {
|
|
1154
|
+
item,
|
|
1155
|
+
stopIndex,
|
|
1156
|
+
order: 0
|
|
1157
|
+
};
|
|
1158
|
+
plan.list.push(entry);
|
|
1159
|
+
if (item.kind !== "emphasis") plan.gates.push(entry);
|
|
1160
|
+
const movement = movementFor(item, el, current);
|
|
1161
|
+
if (movement === null && needsGeometry(item)) laidOut = false;
|
|
1162
|
+
spots.push({
|
|
1163
|
+
el,
|
|
1164
|
+
plan,
|
|
1165
|
+
entry,
|
|
1166
|
+
movement
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
placements.set(item, spots);
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
for (const plan of plans.values()) {
|
|
1173
|
+
const byBegin = (a, b) => a.stopIndex - b.stopIndex || a.item.begin - b.item.begin;
|
|
1174
|
+
plan.list.sort(byBegin);
|
|
1175
|
+
plan.gates.sort(byBegin);
|
|
1176
|
+
plan.list.forEach((entry, at) => {
|
|
1177
|
+
entry.order = at;
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
recompute();
|
|
1181
|
+
};
|
|
1182
|
+
/** Where an element stands: the highest-priority effect that has run wins. */
|
|
1183
|
+
const visibilityOf = (plan) => {
|
|
1184
|
+
const first = plan.gates[0];
|
|
1185
|
+
if (first === void 0) return true;
|
|
1186
|
+
if (first.item.kind === "entrance" && !plan.firstPlayed) return false;
|
|
1187
|
+
return plan.top === null || plan.top.item.kind !== "exit";
|
|
1188
|
+
};
|
|
1189
|
+
/** An element an effect is animating is left to that effect until it ends. */
|
|
1190
|
+
const render = (el, plan) => {
|
|
1191
|
+
if (inFlight.has(el)) return;
|
|
1192
|
+
if (visibilityOf(plan)) show(el);
|
|
1193
|
+
else hide(el);
|
|
1194
|
+
};
|
|
1195
|
+
/**
|
|
1196
|
+
* Puts every animated element where it stands once `played` stops have run,
|
|
1197
|
+
* with no motion. Idempotent, so back, jump and a cancelled advance all land
|
|
1198
|
+
* on the same picture as playing forward to that point.
|
|
1199
|
+
*/
|
|
1200
|
+
const settle = (played) => {
|
|
1201
|
+
finished.clear();
|
|
1202
|
+
settledBelow = played;
|
|
1203
|
+
index();
|
|
1204
|
+
recompute();
|
|
1205
|
+
for (const [el, plan] of plans) render(el, plan);
|
|
1206
|
+
};
|
|
1207
|
+
/**
|
|
1208
|
+
* One effect has had its say on one element. Only that element can have
|
|
1209
|
+
* changed, and only against the effect deciding it so far — so a stop of many
|
|
1210
|
+
* effects costs one comparison each, not a re-reading of the slide each.
|
|
1211
|
+
*/
|
|
1212
|
+
const markPlayed = (spot) => {
|
|
1213
|
+
const { plan, entry } = spot;
|
|
1214
|
+
if (entry.item.kind !== "emphasis") {
|
|
1215
|
+
if (entry === plan.gates[0]) plan.firstPlayed = true;
|
|
1216
|
+
if (plan.top === null || entry.order > plan.top.order) plan.top = entry;
|
|
1217
|
+
}
|
|
1218
|
+
render(spot.el, plan);
|
|
1219
|
+
};
|
|
1220
|
+
/** One effect has had its say everywhere it reaches. */
|
|
1221
|
+
const complete = (item) => {
|
|
1222
|
+
finished.add(item);
|
|
1223
|
+
index();
|
|
1224
|
+
for (const spot of placements.get(item) ?? []) markPlayed(spot);
|
|
1225
|
+
};
|
|
1226
|
+
const play = (item, into) => {
|
|
1227
|
+
index();
|
|
1228
|
+
const spots = placements.get(item) ?? [];
|
|
1229
|
+
if (spots.length === 0) {
|
|
1230
|
+
complete(item);
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
const moving = spots.filter((spot) => spot.movement !== null);
|
|
1234
|
+
const duration = item.duration;
|
|
1235
|
+
const from = Math.max(0, into);
|
|
1236
|
+
const over = duration === null || duration === 0 || from >= duration;
|
|
1237
|
+
const holds = item.step.valueAfterEnd !== "removed";
|
|
1238
|
+
if (reduced() || moving.length === 0 || over && (!holds || !moving.some((spot) => inFlight.has(spot.el)))) {
|
|
1239
|
+
complete(item);
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
for (const spot of spots) if (spot.movement === null) markPlayed(spot);
|
|
1243
|
+
const length = over ? 0 : duration;
|
|
1244
|
+
outstanding.set(item, moving.length);
|
|
1245
|
+
for (const spot of moving) {
|
|
1246
|
+
const el = spot.el;
|
|
1247
|
+
const movement = spot.movement;
|
|
1248
|
+
touched.add(el);
|
|
1249
|
+
if (item.kind !== "emphasis") el.style.visibility = "";
|
|
1250
|
+
if (movement.aboutOwnCentre) centreOn(el);
|
|
1251
|
+
inFlight.set(el, (inFlight.get(el) ?? 0) + 1);
|
|
1252
|
+
const animation = el.animate(movement.frames, {
|
|
1253
|
+
duration: length,
|
|
1254
|
+
fill: "both"
|
|
1255
|
+
});
|
|
1256
|
+
animation.currentTime = over ? length : from;
|
|
1257
|
+
animations.push(animation);
|
|
1258
|
+
animation.finished.then(() => {
|
|
1259
|
+
if (disposed) return;
|
|
1260
|
+
drop(animations, animation);
|
|
1261
|
+
const left = (inFlight.get(el) ?? 1) - 1;
|
|
1262
|
+
if (left <= 0 || !holds) {
|
|
1263
|
+
if (left <= 0) {
|
|
1264
|
+
inFlight.delete(el);
|
|
1265
|
+
for (const other of frozen.get(el) ?? []) other.cancel();
|
|
1266
|
+
frozen.delete(el);
|
|
1267
|
+
} else inFlight.set(el, left);
|
|
1268
|
+
animation.cancel();
|
|
1269
|
+
} else {
|
|
1270
|
+
inFlight.set(el, left);
|
|
1271
|
+
const held = frozen.get(el);
|
|
1272
|
+
if (held === void 0) frozen.set(el, [animation]);
|
|
1273
|
+
else held.push(animation);
|
|
1274
|
+
}
|
|
1275
|
+
markPlayed(spot);
|
|
1276
|
+
const remaining = (outstanding.get(item) ?? 1) - 1;
|
|
1277
|
+
outstanding.set(item, remaining);
|
|
1278
|
+
if (remaining <= 0) complete(item);
|
|
1279
|
+
notifyChanged();
|
|
1280
|
+
}, () => {});
|
|
1281
|
+
}
|
|
1282
|
+
};
|
|
1283
|
+
const runStop = (stopIndex, elapsed) => {
|
|
1284
|
+
const stop = stops[stopIndex];
|
|
1285
|
+
if (stop === void 0) return;
|
|
1286
|
+
const since = Math.max(0, elapsed);
|
|
1287
|
+
startedAt = nowMs() - since;
|
|
1288
|
+
const ordered = [...stop.items].sort((a, b) => a.begin - b.begin);
|
|
1289
|
+
for (const item of ordered) {
|
|
1290
|
+
const wait = item.begin - since;
|
|
1291
|
+
if (reduced() || wait <= 0) {
|
|
1292
|
+
play(item, since - item.begin);
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
const timer = setTimeout(() => {
|
|
1296
|
+
drop(timers, timer);
|
|
1297
|
+
play(item, 0);
|
|
1298
|
+
notifyChanged();
|
|
1299
|
+
}, wait);
|
|
1300
|
+
timers.push(timer);
|
|
1301
|
+
}
|
|
1302
|
+
};
|
|
1303
|
+
const clamp = (next) => Math.max(0, Math.min(Number.isFinite(next) ? Math.round(next) : 0, stops.length));
|
|
1304
|
+
const player = {
|
|
1305
|
+
get cursor() {
|
|
1306
|
+
return cursor;
|
|
1307
|
+
},
|
|
1308
|
+
get stopCount() {
|
|
1309
|
+
return stops.length;
|
|
1310
|
+
},
|
|
1311
|
+
get pending() {
|
|
1312
|
+
return cursor < stops.length;
|
|
1313
|
+
},
|
|
1314
|
+
get running() {
|
|
1315
|
+
return timers.length > 0 || animations.length > 0;
|
|
1316
|
+
},
|
|
1317
|
+
get progress() {
|
|
1318
|
+
return {
|
|
1319
|
+
cursor,
|
|
1320
|
+
stops: stops.length,
|
|
1321
|
+
elapsed: startedAt === null ? null : Math.round(nowMs() - startedAt),
|
|
1322
|
+
generation
|
|
1323
|
+
};
|
|
1324
|
+
},
|
|
1325
|
+
get unsupported() {
|
|
1326
|
+
return [...unsupported];
|
|
1327
|
+
},
|
|
1328
|
+
reset() {
|
|
1329
|
+
generation += 1;
|
|
1330
|
+
clearPending();
|
|
1331
|
+
startedAt = null;
|
|
1332
|
+
cursor = 0;
|
|
1333
|
+
settle(0);
|
|
1334
|
+
onChange(player);
|
|
1335
|
+
if (stops[0]?.auto === true) player.advance();
|
|
1336
|
+
},
|
|
1337
|
+
advance() {
|
|
1338
|
+
if (disposed || cursor >= stops.length) return false;
|
|
1339
|
+
generation += 1;
|
|
1340
|
+
clearPending();
|
|
1341
|
+
settle(cursor);
|
|
1342
|
+
const stopIndex = cursor;
|
|
1343
|
+
cursor += 1;
|
|
1344
|
+
runStop(stopIndex, 0);
|
|
1345
|
+
onChange(player);
|
|
1346
|
+
return true;
|
|
1347
|
+
},
|
|
1348
|
+
back() {
|
|
1349
|
+
if (disposed || cursor <= 0) return false;
|
|
1350
|
+
generation += 1;
|
|
1351
|
+
clearPending();
|
|
1352
|
+
startedAt = null;
|
|
1353
|
+
cursor -= 1;
|
|
1354
|
+
settle(cursor);
|
|
1355
|
+
onChange(player);
|
|
1356
|
+
return true;
|
|
1357
|
+
},
|
|
1358
|
+
jumpTo(next) {
|
|
1359
|
+
if (disposed) return;
|
|
1360
|
+
generation += 1;
|
|
1361
|
+
clearPending();
|
|
1362
|
+
startedAt = null;
|
|
1363
|
+
cursor = clamp(next);
|
|
1364
|
+
settle(cursor);
|
|
1365
|
+
onChange(player);
|
|
1366
|
+
},
|
|
1367
|
+
resume(next, elapsed) {
|
|
1368
|
+
if (disposed) return;
|
|
1369
|
+
generation += 1;
|
|
1370
|
+
clearPending();
|
|
1371
|
+
startedAt = null;
|
|
1372
|
+
cursor = clamp(next);
|
|
1373
|
+
if (cursor > 0 && elapsed !== null && Number.isFinite(elapsed)) {
|
|
1374
|
+
settle(cursor - 1);
|
|
1375
|
+
runStop(cursor - 1, elapsed);
|
|
1376
|
+
} else settle(cursor);
|
|
1377
|
+
onChange(player);
|
|
1378
|
+
},
|
|
1379
|
+
finish() {
|
|
1380
|
+
player.jumpTo(stops.length);
|
|
1381
|
+
},
|
|
1382
|
+
dispose() {
|
|
1383
|
+
if (disposed) return;
|
|
1384
|
+
disposed = true;
|
|
1385
|
+
clearPending();
|
|
1386
|
+
for (const el of touched) {
|
|
1387
|
+
el.style.visibility = "";
|
|
1388
|
+
el.style.opacity = "";
|
|
1389
|
+
uncentre(el);
|
|
1390
|
+
}
|
|
1391
|
+
touched.clear();
|
|
1392
|
+
}
|
|
1393
|
+
};
|
|
1394
|
+
index();
|
|
1395
|
+
return player;
|
|
1396
|
+
};
|
|
1397
|
+
//#endregion
|
|
1398
|
+
export { createAnimationPlayer as n, buildAnimationStops as t };
|
|
1399
|
+
|
|
1400
|
+
//# sourceMappingURL=animation-player-D2mt2MI8.js.map
|