@xtia/timeline 1.1.1 → 1.1.3
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 +5 -5
- package/internal/point.d.ts +21 -4
- package/internal/point.js +30 -11
- package/internal/range.d.ts +1 -0
- package/internal/range.js +6 -24
- package/internal/timeline.d.ts +14 -3
- package/internal/timeline.js +33 -9
- package/internal/tween.js +12 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
### Not Just Another Animation Library
|
|
4
4
|
|
|
5
|
-
Timeline is a type-safe, deterministic choreography system that can control state transitions in any environment, whether that's a simple or complex CSS animation, managing a microcontroller's output, or synchronising complex hardware sequences.
|
|
5
|
+
Timeline is a type-safe, seekable, deterministic choreography system that can control state transitions in any environment, whether that's a simple or complex CSS animation, managing a microcontroller's output, or synchronising complex hardware sequences.
|
|
6
6
|
|
|
7
7
|
* [API Reference](#reference)
|
|
8
8
|
* [Playground](https://stackblitz.com/edit/timeline-string-tween?file=src%2Fmain.ts)
|
|
@@ -17,7 +17,7 @@ import { Timeline } from "@xtia/timeline";
|
|
|
17
17
|
// create a Timeline
|
|
18
18
|
const timeline = new Timeline();
|
|
19
19
|
|
|
20
|
-
// over the first second, fade
|
|
20
|
+
// over the first second, fade an element's background colour
|
|
21
21
|
timeline
|
|
22
22
|
.range(0, 1000)
|
|
23
23
|
.tween("#646", "#000")
|
|
@@ -40,7 +40,7 @@ timeline
|
|
|
40
40
|
timeline
|
|
41
41
|
.range(1000, 2000)
|
|
42
42
|
.tween(0, 255)
|
|
43
|
-
.
|
|
43
|
+
.apply(value => microcontroller.setPWM(value))
|
|
44
44
|
|
|
45
45
|
// make it go
|
|
46
46
|
timeline.play();
|
|
@@ -510,8 +510,8 @@ Registers an emission handler that calls one function for forward seeks to or pa
|
|
|
510
510
|
```ts
|
|
511
511
|
point
|
|
512
512
|
.applyDirectional(
|
|
513
|
-
element.classList.add("faded"),
|
|
514
|
-
element.classList.remove("faded"),
|
|
513
|
+
() => element.classList.add("faded"),
|
|
514
|
+
() => element.classList.remove("faded"),
|
|
515
515
|
);
|
|
516
516
|
```
|
|
517
517
|
|
package/internal/point.d.ts
CHANGED
|
@@ -38,19 +38,31 @@ export declare class TimelinePoint extends Emitter<PointEvent> {
|
|
|
38
38
|
delta(timeOffset: number): TimelinePoint;
|
|
39
39
|
/**
|
|
40
40
|
* Seeks the parent Timeline to this point
|
|
41
|
+
* @deprecated Use timeline.seek(point)
|
|
41
42
|
*/
|
|
42
43
|
seek(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Smooth-seeks the parent Timeline to this point
|
|
46
|
+
* @deprecated Use timeline.seek(point)
|
|
47
|
+
*/
|
|
43
48
|
seek(duration: number, easer?: Easer): Promise<void>;
|
|
44
49
|
/**
|
|
45
50
|
* Creates an emitter that only emits on forward-moving seeks
|
|
46
|
-
* @returns
|
|
51
|
+
* @returns Listenable: emits forward-seeking point events
|
|
47
52
|
*/
|
|
48
53
|
forwardOnly(): Emitter<PointEvent>;
|
|
49
54
|
/**
|
|
50
55
|
* Creates an emitter that only emits on backward-moving seeks
|
|
51
|
-
* @returns
|
|
56
|
+
* @returns Listenable: emits backward-seeking point events
|
|
52
57
|
*/
|
|
53
58
|
reverseOnly(): Emitter<PointEvent>;
|
|
59
|
+
filter(check: (event: PointEvent) => boolean): Emitter<PointEvent>;
|
|
60
|
+
/**
|
|
61
|
+
* Creates an emitter that forwards events emitted by seeks of a specific direction
|
|
62
|
+
* @param allow Direction to allow
|
|
63
|
+
* @returns Listenable: emits point events that match the given direction
|
|
64
|
+
*/
|
|
65
|
+
filter(allow: -1 | 1): Emitter<PointEvent>;
|
|
54
66
|
/**
|
|
55
67
|
* Creates a Promise that will be resolved when the Timeline first seeks to/past this point
|
|
56
68
|
*
|
|
@@ -66,8 +78,8 @@ export declare class TimelinePoint extends Emitter<PointEvent> {
|
|
|
66
78
|
* ```
|
|
67
79
|
* point
|
|
68
80
|
* .applyDirectional(
|
|
69
|
-
* element.classList.add("faded"),
|
|
70
|
-
* element.classList.remove("faded"),
|
|
81
|
+
* () => element.classList.add("faded"),
|
|
82
|
+
* () => element.classList.remove("faded"),
|
|
71
83
|
* );
|
|
72
84
|
* ```
|
|
73
85
|
*
|
|
@@ -79,4 +91,9 @@ export declare class TimelinePoint extends Emitter<PointEvent> {
|
|
|
79
91
|
* @returns A function to deregister both handlers
|
|
80
92
|
*/
|
|
81
93
|
applyDirectional(apply: () => void, revert: () => void): UnsubscribeFunc;
|
|
94
|
+
/**
|
|
95
|
+
* Creates an emitter that forwards point events whose direction differs from the previous emission
|
|
96
|
+
* @returns Listenable: emits non-repeating point events
|
|
97
|
+
*/
|
|
98
|
+
dedupe(): Emitter<PointEvent>;
|
|
82
99
|
}
|
package/internal/point.js
CHANGED
|
@@ -43,24 +43,30 @@ export class TimelinePoint extends Emitter {
|
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
45
|
* Creates an emitter that only emits on forward-moving seeks
|
|
46
|
-
* @returns
|
|
46
|
+
* @returns Listenable: emits forward-seeking point events
|
|
47
47
|
*/
|
|
48
48
|
forwardOnly() {
|
|
49
|
-
return
|
|
50
|
-
return this.onListen((ev) => {
|
|
51
|
-
if (ev.direction > 0)
|
|
52
|
-
handler(ev);
|
|
53
|
-
});
|
|
54
|
-
});
|
|
49
|
+
return this.filter(1);
|
|
55
50
|
}
|
|
56
51
|
/**
|
|
57
52
|
* Creates an emitter that only emits on backward-moving seeks
|
|
58
|
-
* @returns
|
|
53
|
+
* @returns Listenable: emits backward-seeking point events
|
|
59
54
|
*/
|
|
60
55
|
reverseOnly() {
|
|
56
|
+
return this.filter(-1);
|
|
57
|
+
}
|
|
58
|
+
filter(arg) {
|
|
59
|
+
if (typeof arg == "number") {
|
|
60
|
+
return new Emitter(handler => {
|
|
61
|
+
return this.onListen((ev) => {
|
|
62
|
+
if (ev.direction === arg)
|
|
63
|
+
handler(ev);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
61
67
|
return new Emitter(handler => {
|
|
62
68
|
return this.onListen((ev) => {
|
|
63
|
-
if (ev
|
|
69
|
+
if (arg(ev))
|
|
64
70
|
handler(ev);
|
|
65
71
|
});
|
|
66
72
|
});
|
|
@@ -87,8 +93,8 @@ export class TimelinePoint extends Emitter {
|
|
|
87
93
|
* ```
|
|
88
94
|
* point
|
|
89
95
|
* .applyDirectional(
|
|
90
|
-
* element.classList.add("faded"),
|
|
91
|
-
* element.classList.remove("faded"),
|
|
96
|
+
* () => element.classList.add("faded"),
|
|
97
|
+
* () => element.classList.remove("faded"),
|
|
92
98
|
* );
|
|
93
99
|
* ```
|
|
94
100
|
*
|
|
@@ -104,4 +110,17 @@ export class TimelinePoint extends Emitter {
|
|
|
104
110
|
? apply()
|
|
105
111
|
: revert());
|
|
106
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Creates an emitter that forwards point events whose direction differs from the previous emission
|
|
115
|
+
* @returns Listenable: emits non-repeating point events
|
|
116
|
+
*/
|
|
117
|
+
dedupe() {
|
|
118
|
+
let previous = 0;
|
|
119
|
+
return new Emitter(handler => this.onListen(event => {
|
|
120
|
+
if (event.direction !== previous) {
|
|
121
|
+
handler(event);
|
|
122
|
+
previous = event.direction;
|
|
123
|
+
}
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
107
126
|
}
|
package/internal/range.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ export declare class TimelineRange extends RangeProgression {
|
|
|
48
48
|
* Progresses the Timeline across the range at 1000 units per second
|
|
49
49
|
* @param easer Optional easing function
|
|
50
50
|
* @returns Promise, resolved when the end is reached
|
|
51
|
+
* @deprecated Use timeline.play(range, easer?)
|
|
51
52
|
*/
|
|
52
53
|
play(easer?: Easer | keyof typeof easers): Promise<void>;
|
|
53
54
|
/**
|
package/internal/range.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { RangeProgression } from "./emitters";
|
|
2
2
|
import { TimelinePoint } from "./point";
|
|
3
|
-
import { clamp } from "./utils";
|
|
4
3
|
export class TimelineRange extends RangeProgression {
|
|
5
4
|
/** @internal Manual construction of RangeProgression is outside of the API contract and subject to undocumented change */
|
|
6
5
|
constructor(onListen, timeline, startPosition,
|
|
@@ -28,8 +27,8 @@ export class TimelineRange extends RangeProgression {
|
|
|
28
27
|
*/
|
|
29
28
|
bisect(position = this.duration / 2) {
|
|
30
29
|
return [
|
|
31
|
-
this.timeline.range(
|
|
32
|
-
this.timeline.range(position + this.startPosition, this.duration -
|
|
30
|
+
this.timeline.range(this.startPosition, position),
|
|
31
|
+
this.timeline.range(position + this.startPosition, this.duration - position),
|
|
33
32
|
];
|
|
34
33
|
}
|
|
35
34
|
/**
|
|
@@ -65,11 +64,12 @@ export class TimelineRange extends RangeProgression {
|
|
|
65
64
|
* Progresses the Timeline across the range at 1000 units per second
|
|
66
65
|
* @param easer Optional easing function
|
|
67
66
|
* @returns Promise, resolved when the end is reached
|
|
67
|
+
* @deprecated Use timeline.play(range, easer?)
|
|
68
68
|
*/
|
|
69
69
|
play(easer) {
|
|
70
70
|
this.timeline.pause();
|
|
71
71
|
this.timeline.currentTime = this.startPosition;
|
|
72
|
-
return this.timeline.seek(this.
|
|
72
|
+
return this.timeline.seek(this.end, this.duration, easer);
|
|
73
73
|
}
|
|
74
74
|
/**
|
|
75
75
|
* Creates a new range representing a direct expansion of this one
|
|
@@ -78,16 +78,7 @@ export class TimelineRange extends RangeProgression {
|
|
|
78
78
|
* @returns Listenable: this range will emit a progression value (0..1) when a `seek()` passes or intersects it
|
|
79
79
|
*/
|
|
80
80
|
grow(delta, anchor = 0) {
|
|
81
|
-
|
|
82
|
-
const leftDelta = -delta * (1 - clampedAnchor);
|
|
83
|
-
const rightDelta = delta * clampedAnchor;
|
|
84
|
-
const newStart = this.startPosition + leftDelta;
|
|
85
|
-
const newEnd = this.startPosition + this.duration + rightDelta;
|
|
86
|
-
if (newEnd < newStart) {
|
|
87
|
-
const mid = (newStart + newEnd) / 2;
|
|
88
|
-
return this.timeline.range(mid, 0);
|
|
89
|
-
}
|
|
90
|
-
return this.timeline.range(newStart, newEnd - newStart);
|
|
81
|
+
return this.timeline.range(this.startPosition - (delta * anchor), this.duration + delta);
|
|
91
82
|
}
|
|
92
83
|
/**
|
|
93
84
|
* Creates a new range representing a multiplicative expansion of this one
|
|
@@ -99,16 +90,7 @@ export class TimelineRange extends RangeProgression {
|
|
|
99
90
|
if (factor <= 0) {
|
|
100
91
|
throw new RangeError('Scale factor must be > 0');
|
|
101
92
|
}
|
|
102
|
-
|
|
103
|
-
const oldLen = this.endPosition - this.startPosition;
|
|
104
|
-
const pivot = this.startPosition + oldLen * clampedAnchor;
|
|
105
|
-
const newStart = pivot - (pivot - this.startPosition) * factor;
|
|
106
|
-
const newEnd = pivot + (this.endPosition - pivot) * factor;
|
|
107
|
-
if (newEnd < newStart) {
|
|
108
|
-
const mid = (newStart + newEnd) / 2;
|
|
109
|
-
return this.timeline.range(mid, 0);
|
|
110
|
-
}
|
|
111
|
-
return this.timeline.range(newStart, newEnd - newStart);
|
|
93
|
+
return this.grow((factor - 1) * this.duration, anchor);
|
|
112
94
|
}
|
|
113
95
|
contains(target) {
|
|
114
96
|
const [targetStart, targetEnd] = target instanceof TimelinePoint
|
package/internal/timeline.d.ts
CHANGED
|
@@ -89,12 +89,12 @@ export declare class Timeline {
|
|
|
89
89
|
* Defines a range on this Timeline
|
|
90
90
|
*
|
|
91
91
|
* @param start The position on this Timeline at which the range starts
|
|
92
|
-
* @param duration Length of the resulting range
|
|
92
|
+
* @param duration Length of the resulting range
|
|
93
93
|
* @returns A range on the Timeline
|
|
94
94
|
*
|
|
95
95
|
* Listenable: this range will emit a progression value (0..1) when a `seek()` passes or intersects it
|
|
96
96
|
*/
|
|
97
|
-
range(start: number | TimelinePoint, duration
|
|
97
|
+
range(start: number | TimelinePoint, duration: number): TimelineRange;
|
|
98
98
|
/**
|
|
99
99
|
* Creates an observable range from position 0 to the Timeline's **current** final position
|
|
100
100
|
*/
|
|
@@ -124,6 +124,16 @@ export declare class Timeline {
|
|
|
124
124
|
*/
|
|
125
125
|
play(): void;
|
|
126
126
|
play(fps: number): void;
|
|
127
|
+
/**
|
|
128
|
+
* Performs a smooth-seek through a range at (1000 × this.timeScale) units per second
|
|
129
|
+
*/
|
|
130
|
+
play(range: TimelineRange, easer?: Easer): Promise<void>;
|
|
131
|
+
/**
|
|
132
|
+
* Stops normal progression instigated by play()
|
|
133
|
+
*
|
|
134
|
+
* Does not affect ongoing smooth-seek operations or play(range)
|
|
135
|
+
*
|
|
136
|
+
*/
|
|
127
137
|
pause(): void;
|
|
128
138
|
/**
|
|
129
139
|
* Progresses the Timeline by 1 unit
|
|
@@ -148,9 +158,10 @@ export declare class Timeline {
|
|
|
148
158
|
get position(): number;
|
|
149
159
|
}
|
|
150
160
|
export interface ChainingInterface {
|
|
151
|
-
thenTween<T extends Tweenable>(duration: number, apply: (v: Widen<T>) => void, from: T, to: T, easer
|
|
161
|
+
thenTween<T extends Tweenable>(duration: number, apply: (v: Widen<T>) => void, from: T, to: T, easer?: Easer): ChainingInterface;
|
|
152
162
|
then(action: () => void): ChainingInterface;
|
|
153
163
|
thenWait(duration: number): ChainingInterface;
|
|
164
|
+
fork(fn: (chain: ChainingInterface) => void): ChainingInterface;
|
|
154
165
|
readonly end: TimelinePoint;
|
|
155
166
|
}
|
|
156
167
|
export {};
|
package/internal/timeline.js
CHANGED
|
@@ -179,12 +179,12 @@ export class Timeline {
|
|
|
179
179
|
? to
|
|
180
180
|
: to.position;
|
|
181
181
|
if (this.seeking) {
|
|
182
|
-
throw new Error("Can't seek while
|
|
182
|
+
throw new Error("Can't seek while a seek event is processed");
|
|
183
183
|
}
|
|
184
184
|
if (this.smoothSeeker !== null) {
|
|
185
185
|
this.smoothSeeker.pause();
|
|
186
186
|
// ensure any awaits are resolved for the previous seek
|
|
187
|
-
this.smoothSeeker.
|
|
187
|
+
this.smoothSeeker.seek(this.smoothSeeker.end);
|
|
188
188
|
this.smoothSeeker = null;
|
|
189
189
|
}
|
|
190
190
|
if (duration === 0) {
|
|
@@ -193,8 +193,12 @@ export class Timeline {
|
|
|
193
193
|
}
|
|
194
194
|
const seeker = new Timeline(true);
|
|
195
195
|
this.smoothSeeker = seeker;
|
|
196
|
-
seeker
|
|
197
|
-
|
|
196
|
+
seeker
|
|
197
|
+
.range(0, duration)
|
|
198
|
+
.ease(easer)
|
|
199
|
+
.tween(this.currentTime, toPosition)
|
|
200
|
+
.apply(v => this.seekDirect(v));
|
|
201
|
+
return seeker.end.promise();
|
|
198
202
|
}
|
|
199
203
|
seekDirect(toPosition) {
|
|
200
204
|
const fromPosition = this._currentTime;
|
|
@@ -252,7 +256,7 @@ export class Timeline {
|
|
|
252
256
|
range.handlers.slice().forEach(h => h(progress));
|
|
253
257
|
}
|
|
254
258
|
});
|
|
255
|
-
this.progressionHandlers.slice().forEach(h => h(
|
|
259
|
+
this.progressionHandlers.slice().forEach(h => h(toTime / this._endPosition));
|
|
256
260
|
}
|
|
257
261
|
sortEntries(direction) {
|
|
258
262
|
this.currentSortDirection = direction;
|
|
@@ -263,9 +267,18 @@ export class Timeline {
|
|
|
263
267
|
? sortTweens
|
|
264
268
|
: sortReverse);
|
|
265
269
|
}
|
|
266
|
-
play(
|
|
270
|
+
play(arg = default_fps, easer) {
|
|
267
271
|
if (this.interval !== null)
|
|
268
272
|
this.pause();
|
|
273
|
+
if (this.smoothSeeker) {
|
|
274
|
+
this.smoothSeeker.pause();
|
|
275
|
+
this.smoothSeeker.seek(this.smoothSeeker.end);
|
|
276
|
+
this.smoothSeeker = null;
|
|
277
|
+
}
|
|
278
|
+
if (arg instanceof TimelineRange) {
|
|
279
|
+
this.seek(arg.start);
|
|
280
|
+
return this.seek(arg.end, arg.duration / this.timeScale, easer);
|
|
281
|
+
}
|
|
269
282
|
let previousTime = Date.now();
|
|
270
283
|
this.interval = setInterval(() => {
|
|
271
284
|
const newTime = Date.now();
|
|
@@ -303,8 +316,14 @@ export class Timeline {
|
|
|
303
316
|
return;
|
|
304
317
|
}
|
|
305
318
|
this.currentTime += delta;
|
|
306
|
-
}, 1000 /
|
|
319
|
+
}, 1000 / arg);
|
|
307
320
|
}
|
|
321
|
+
/**
|
|
322
|
+
* Stops normal progression instigated by play()
|
|
323
|
+
*
|
|
324
|
+
* Does not affect ongoing smooth-seek operations or play(range)
|
|
325
|
+
*
|
|
326
|
+
*/
|
|
308
327
|
pause() {
|
|
309
328
|
if (this.interval === null)
|
|
310
329
|
return;
|
|
@@ -330,12 +349,12 @@ export class Timeline {
|
|
|
330
349
|
reverse = action;
|
|
331
350
|
if (action)
|
|
332
351
|
point.apply(reverse
|
|
333
|
-
? (event => event.direction < 0 ? reverse() : action)
|
|
352
|
+
? (event => event.direction < 0 ? reverse() : action())
|
|
334
353
|
: action);
|
|
335
354
|
return this.createChainingInterface(point.position);
|
|
336
355
|
}
|
|
337
356
|
createChainingInterface(position) {
|
|
338
|
-
|
|
357
|
+
const chain = {
|
|
339
358
|
thenTween: (duration, apply, from, to, easer) => {
|
|
340
359
|
return this.tween(position, duration, apply, from, to, easer);
|
|
341
360
|
},
|
|
@@ -344,8 +363,13 @@ export class Timeline {
|
|
|
344
363
|
this.point(position + delay);
|
|
345
364
|
return this.createChainingInterface(position + delay);
|
|
346
365
|
},
|
|
366
|
+
fork: fn => {
|
|
367
|
+
fn(chain);
|
|
368
|
+
return chain;
|
|
369
|
+
},
|
|
347
370
|
end: this.point(position),
|
|
348
371
|
};
|
|
372
|
+
return chain;
|
|
349
373
|
}
|
|
350
374
|
/**
|
|
351
375
|
* @deprecated use `timeline.currentTime`
|
package/internal/tween.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { clamp } from "./utils";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
var TokenTypes;
|
|
3
|
+
(function (TokenTypes) {
|
|
4
|
+
TokenTypes[TokenTypes["none"] = 0] = "none";
|
|
5
|
+
TokenTypes[TokenTypes["number"] = 1] = "number";
|
|
6
|
+
TokenTypes[TokenTypes["colour"] = 2] = "colour";
|
|
7
|
+
})(TokenTypes || (TokenTypes = {}));
|
|
8
|
+
;
|
|
7
9
|
export function createTween(from, to) {
|
|
8
10
|
if (from === to)
|
|
9
11
|
return () => from;
|
|
@@ -39,9 +41,9 @@ function createStringTween(from, to) {
|
|
|
39
41
|
const fromToken = chunk.token;
|
|
40
42
|
const toToken = toChunks[i].token;
|
|
41
43
|
const prefix = chunk.prefix;
|
|
42
|
-
if (chunk.type ===
|
|
44
|
+
if (chunk.type === TokenTypes.none)
|
|
43
45
|
return () => prefix;
|
|
44
|
-
if (chunk.type ===
|
|
46
|
+
if (chunk.type === TokenTypes.colour) {
|
|
45
47
|
const fromColour = parseColour(fromToken);
|
|
46
48
|
const toColour = parseColour(toToken);
|
|
47
49
|
return progress => prefix + blendColours(fromColour, toColour, progress);
|
|
@@ -142,13 +144,13 @@ function tokenise(s) {
|
|
|
142
144
|
// trailing literal after the last token – stored as a final chunk
|
|
143
145
|
const tail = s.slice(lastIdx);
|
|
144
146
|
if (tail.length) {
|
|
145
|
-
chunks.push({ prefix: tail, token: "", type:
|
|
147
|
+
chunks.push({ prefix: tail, token: "", type: TokenTypes.none });
|
|
146
148
|
}
|
|
147
149
|
return chunks;
|
|
148
150
|
}
|
|
149
151
|
;
|
|
150
152
|
function getTokenType(token) {
|
|
151
153
|
if (token.startsWith("#"))
|
|
152
|
-
return
|
|
153
|
-
return
|
|
154
|
+
return TokenTypes.colour;
|
|
155
|
+
return TokenTypes.number;
|
|
154
156
|
}
|