@xtia/timeline 1.1.12 → 1.1.14
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 +15 -6
- package/internal/emitters.d.ts +1 -1
- package/internal/emitters.js +13 -13
- package/internal/point.js +3 -3
- package/internal/range.js +2 -2
- package/internal/timeline.d.ts +15 -0
- package/internal/timeline.js +17 -4
- package/internal/tween.js +13 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,8 +107,8 @@ const filenameEmitter = range
|
|
|
107
107
|
.dedupe()
|
|
108
108
|
.map(n => `animation-frame-${n}.png`);
|
|
109
109
|
|
|
110
|
-
// filenameEmitter will emit filenames as the Timeline passes through
|
|
111
|
-
// it can be listened directly or further transformed
|
|
110
|
+
// filenameEmitter will emit filenames as the Timeline passes through
|
|
111
|
+
// 'range'. it can be listened directly or further transformed
|
|
112
112
|
const urlEmitter = filenameEmitter
|
|
113
113
|
.map(filename => `http://www.example.com/${filename}`);
|
|
114
114
|
|
|
@@ -145,12 +145,21 @@ Points emit `PointEvent` objects when their position is reached or passed.
|
|
|
145
145
|
```ts
|
|
146
146
|
twoSecondsIn.apply(event => {
|
|
147
147
|
// event.direction (-1 | 1) tells us the direction of the seek that
|
|
148
|
-
// triggered the point. This allows for reversible
|
|
149
|
-
|
|
148
|
+
// triggered the point. This allows for reversible effects:
|
|
149
|
+
element.classList.toggle("someClass", event.direction > 0);
|
|
150
150
|
});
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
-
*Note*, point events will be triggered in order, depending on the direction of the seek that passes over them.
|
|
153
|
+
*Note*, point events will be triggered in order, depending on the direction of the seek that passes over them. To ensure consistent reversible behaviour, a point is triggered with `direction = 1` when a forward seek *passes or lands on* it, and with `direction = -1` when a backward seek *passes or departs from* it.
|
|
154
|
+
|
|
155
|
+
Directionality can also be leveraged with `point.applyDirectional()`:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
twoSecondsIn.applyDirectional(
|
|
159
|
+
parent.append(element), // do
|
|
160
|
+
element.remove() // undo
|
|
161
|
+
);
|
|
162
|
+
```
|
|
154
163
|
|
|
155
164
|
We can also create ranges from points:
|
|
156
165
|
|
|
@@ -165,7 +174,7 @@ timeline
|
|
|
165
174
|
.tween(/*...*/);
|
|
166
175
|
```
|
|
167
176
|
|
|
168
|
-
*Note*, points and ranges
|
|
177
|
+
*Note*, points and ranges are transient interfaces for adding behaviour to their Timelines; they can be garbage-collected if unreferenced even while their listeners persist.
|
|
169
178
|
|
|
170
179
|
## More on tweening
|
|
171
180
|
|
package/internal/emitters.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export type UnsubscribeFunc = () => void;
|
|
|
6
6
|
export declare class Emitter<T> {
|
|
7
7
|
protected onListen: ListenFunc<T>;
|
|
8
8
|
protected constructor(onListen: ListenFunc<T>);
|
|
9
|
-
protected
|
|
9
|
+
protected transform<R = T>(handler: (value: T, emit: (value: R) => void) => void): (fn: (v: R) => void) => UnsubscribeFunc;
|
|
10
10
|
/**
|
|
11
11
|
* Compatibility alias for `apply()` - registers a function to receive emitted values
|
|
12
12
|
* @param handler
|
package/internal/emitters.js
CHANGED
|
@@ -5,7 +5,7 @@ export class Emitter {
|
|
|
5
5
|
constructor(onListen) {
|
|
6
6
|
this.onListen = onListen;
|
|
7
7
|
}
|
|
8
|
-
|
|
8
|
+
transform(handler) {
|
|
9
9
|
let parentUnsubscribe = null;
|
|
10
10
|
const parentListen = this.onListen;
|
|
11
11
|
const { emit, listen } = createListenable(() => parentUnsubscribe = parentListen(value => {
|
|
@@ -38,7 +38,7 @@ export class Emitter {
|
|
|
38
38
|
* @returns Listenable: emits transformed values
|
|
39
39
|
*/
|
|
40
40
|
map(mapFunc) {
|
|
41
|
-
const listen = this.
|
|
41
|
+
const listen = this.transform((value, emit) => emit(mapFunc(value)));
|
|
42
42
|
return new Emitter(listen);
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
@@ -47,7 +47,7 @@ export class Emitter {
|
|
|
47
47
|
* @returns Listenable: emits values that pass the filter
|
|
48
48
|
*/
|
|
49
49
|
filter(check) {
|
|
50
|
-
const listen = this.
|
|
50
|
+
const listen = this.transform((value, emit) => check(value) && emit(value));
|
|
51
51
|
return new Emitter(listen);
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
@@ -59,7 +59,7 @@ export class Emitter {
|
|
|
59
59
|
*/
|
|
60
60
|
dedupe(compare) {
|
|
61
61
|
let previous = null;
|
|
62
|
-
const listen = this.
|
|
62
|
+
const listen = this.transform((value, emit) => {
|
|
63
63
|
if (!previous || (compare
|
|
64
64
|
? !compare(previous.value, value)
|
|
65
65
|
: (previous.value !== value))) {
|
|
@@ -81,7 +81,7 @@ export class Emitter {
|
|
|
81
81
|
* @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
|
|
82
82
|
*/
|
|
83
83
|
tap(cb) {
|
|
84
|
-
const listen = this.
|
|
84
|
+
const listen = this.transform((value, emit) => {
|
|
85
85
|
cb(value);
|
|
86
86
|
emit(value);
|
|
87
87
|
});
|
|
@@ -115,13 +115,13 @@ export class RangeProgression extends Emitter {
|
|
|
115
115
|
? easers[easer]
|
|
116
116
|
: easer;
|
|
117
117
|
const listen = easerFunc
|
|
118
|
-
? this.
|
|
118
|
+
? this.transform((value, emit) => emit(easerFunc(value)))
|
|
119
119
|
: this.onListen;
|
|
120
120
|
return new RangeProgression(listen);
|
|
121
121
|
}
|
|
122
122
|
tween(from, to) {
|
|
123
123
|
const tween = createTween(from, to);
|
|
124
|
-
const listen = this.
|
|
124
|
+
const listen = this.transform((progress, emit) => emit(tween(progress)));
|
|
125
125
|
return new Emitter(listen);
|
|
126
126
|
}
|
|
127
127
|
/**
|
|
@@ -138,7 +138,7 @@ export class RangeProgression extends Emitter {
|
|
|
138
138
|
* @returns Listenable: emits the sampled values
|
|
139
139
|
*/
|
|
140
140
|
sample(source) {
|
|
141
|
-
const listen = this.
|
|
141
|
+
const listen = this.transform((value, emit) => {
|
|
142
142
|
const clampedProgress = clamp(value);
|
|
143
143
|
const index = Math.floor(clampedProgress * (source.length - 1));
|
|
144
144
|
emit(source[index]);
|
|
@@ -168,7 +168,7 @@ export class RangeProgression extends Emitter {
|
|
|
168
168
|
* @returns Listenable: emits 0 or 1 after comparing progress with a threshold
|
|
169
169
|
*/
|
|
170
170
|
threshold(threshold) {
|
|
171
|
-
const listen = this.
|
|
171
|
+
const listen = this.transform((value, emit) => emit(value >= threshold ? 1 : 0));
|
|
172
172
|
return new RangeProgression(listen);
|
|
173
173
|
}
|
|
174
174
|
/**
|
|
@@ -201,7 +201,7 @@ export class RangeProgression extends Emitter {
|
|
|
201
201
|
repeat(count) {
|
|
202
202
|
if (count <= 0)
|
|
203
203
|
throw new RangeError("Repeat count must be greater than 0");
|
|
204
|
-
const listen = this.
|
|
204
|
+
const listen = this.transform((value, emit) => {
|
|
205
205
|
const out = (value * count) % 1;
|
|
206
206
|
emit(out);
|
|
207
207
|
});
|
|
@@ -213,7 +213,7 @@ export class RangeProgression extends Emitter {
|
|
|
213
213
|
* @returns Listenable: emits values that pass the filter
|
|
214
214
|
*/
|
|
215
215
|
filter(check) {
|
|
216
|
-
const listen = this.
|
|
216
|
+
const listen = this.transform((value, emit) => {
|
|
217
217
|
if (check(value))
|
|
218
218
|
emit(value);
|
|
219
219
|
});
|
|
@@ -226,7 +226,7 @@ export class RangeProgression extends Emitter {
|
|
|
226
226
|
dedupe() {
|
|
227
227
|
if (!this._dedupe) {
|
|
228
228
|
let previous = null;
|
|
229
|
-
const listen = this.
|
|
229
|
+
const listen = this.transform((value, emit) => {
|
|
230
230
|
if (previous !== value) {
|
|
231
231
|
emit(value);
|
|
232
232
|
previous = value;
|
|
@@ -268,7 +268,7 @@ export class RangeProgression extends Emitter {
|
|
|
268
268
|
* @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
|
|
269
269
|
*/
|
|
270
270
|
tap(cb) {
|
|
271
|
-
const listen = this.
|
|
271
|
+
const listen = this.transform((value, emit) => {
|
|
272
272
|
cb(value);
|
|
273
273
|
emit(value);
|
|
274
274
|
});
|
package/internal/point.js
CHANGED
|
@@ -59,7 +59,7 @@ export class TimelinePoint extends Emitter {
|
|
|
59
59
|
return this._reverseOnly;
|
|
60
60
|
}
|
|
61
61
|
filter(arg) {
|
|
62
|
-
const listen = this.
|
|
62
|
+
const listen = this.transform(typeof arg == "number"
|
|
63
63
|
? (value, emit) => {
|
|
64
64
|
if (value.direction === arg)
|
|
65
65
|
emit(value);
|
|
@@ -79,7 +79,7 @@ export class TimelinePoint extends Emitter {
|
|
|
79
79
|
*/
|
|
80
80
|
promise() {
|
|
81
81
|
return new Promise(resolve => {
|
|
82
|
-
let remove = this.
|
|
82
|
+
let remove = this.onListen((ev) => {
|
|
83
83
|
remove();
|
|
84
84
|
resolve(ev.direction);
|
|
85
85
|
});
|
|
@@ -116,7 +116,7 @@ export class TimelinePoint extends Emitter {
|
|
|
116
116
|
dedupe() {
|
|
117
117
|
if (!this._dedupe) {
|
|
118
118
|
let previous = 0;
|
|
119
|
-
const listen = this.
|
|
119
|
+
const listen = this.transform((value, emit) => {
|
|
120
120
|
if (value.direction !== previous) {
|
|
121
121
|
previous = value.direction;
|
|
122
122
|
emit(value);
|
package/internal/range.js
CHANGED
|
@@ -23,8 +23,8 @@ export class TimelineRange extends RangeProgression {
|
|
|
23
23
|
* @returns Tuple of two ranges
|
|
24
24
|
*/
|
|
25
25
|
bisect(position = this.duration / 2) {
|
|
26
|
-
if (position >= this.
|
|
27
|
-
throw new RangeError("Bisection position is beyond end of range");
|
|
26
|
+
if (position >= this.duration) {
|
|
27
|
+
throw new RangeError("Bisection position is at or beyond end of range");
|
|
28
28
|
}
|
|
29
29
|
return [
|
|
30
30
|
this.timeline.range(this.startPosition, position),
|
package/internal/timeline.d.ts
CHANGED
|
@@ -20,6 +20,20 @@ type Period = {
|
|
|
20
20
|
*/
|
|
21
21
|
export declare function animate(durationMs: number): TimelineRange;
|
|
22
22
|
export declare function animate(period: Period): TimelineRange;
|
|
23
|
+
type TimelineOptions = {
|
|
24
|
+
atEnd?: {
|
|
25
|
+
wrapAt: number;
|
|
26
|
+
} | {
|
|
27
|
+
restartAt: number;
|
|
28
|
+
} | keyof typeof EndAction;
|
|
29
|
+
timeScale?: number;
|
|
30
|
+
} & ({
|
|
31
|
+
autoplay: true;
|
|
32
|
+
fps?: number;
|
|
33
|
+
} | ({
|
|
34
|
+
autoplay?: false;
|
|
35
|
+
fps?: never;
|
|
36
|
+
}));
|
|
23
37
|
export declare class Timeline {
|
|
24
38
|
/**
|
|
25
39
|
* Multiplies the speed at which `play()` progresses through the Timeline
|
|
@@ -90,6 +104,7 @@ export declare class Timeline {
|
|
|
90
104
|
} | {
|
|
91
105
|
restartAt: number;
|
|
92
106
|
} | keyof typeof EndAction);
|
|
107
|
+
constructor(options: TimelineOptions);
|
|
93
108
|
/**
|
|
94
109
|
* @deprecated "loop" endAction will be removed; use "restart" or `{restartAt: 0}` (disambiguates new looping strategies)
|
|
95
110
|
*/
|
package/internal/timeline.js
CHANGED
|
@@ -64,7 +64,7 @@ export class Timeline {
|
|
|
64
64
|
}
|
|
65
65
|
return this._progression.emitter;
|
|
66
66
|
}
|
|
67
|
-
constructor(
|
|
67
|
+
constructor(optionsOrAutoplay = false, endAction = "pause") {
|
|
68
68
|
/**
|
|
69
69
|
* Multiplies the speed at which `play()` progresses through the Timeline
|
|
70
70
|
*
|
|
@@ -85,13 +85,26 @@ export class Timeline {
|
|
|
85
85
|
this.start = this.point(0);
|
|
86
86
|
this._frameEvents = null;
|
|
87
87
|
this._progression = null;
|
|
88
|
+
// "loop" is temporary alias for "restart":
|
|
88
89
|
if (endAction == "loop")
|
|
89
90
|
endAction = "restart";
|
|
90
|
-
if (
|
|
91
|
+
if (typeof optionsOrAutoplay == "object") {
|
|
92
|
+
endAction = optionsOrAutoplay.atEnd ?? "pause";
|
|
93
|
+
this.timeScale = optionsOrAutoplay.timeScale ?? 1;
|
|
94
|
+
if ("autoplay" in optionsOrAutoplay && optionsOrAutoplay.autoplay) {
|
|
95
|
+
if ("fps" in optionsOrAutoplay && optionsOrAutoplay.fps) {
|
|
96
|
+
this.play(optionsOrAutoplay.fps);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
this.play();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else if (optionsOrAutoplay === true) {
|
|
91
104
|
this.play();
|
|
92
105
|
}
|
|
93
|
-
else if (typeof
|
|
94
|
-
this.play(
|
|
106
|
+
else if (typeof optionsOrAutoplay == "number") {
|
|
107
|
+
this.play(optionsOrAutoplay);
|
|
95
108
|
}
|
|
96
109
|
if (typeof endAction == "object"
|
|
97
110
|
&& "restartAt" in endAction) {
|
package/internal/tween.js
CHANGED
|
@@ -134,7 +134,19 @@ function parseColour(code) {
|
|
|
134
134
|
}
|
|
135
135
|
function blendColours(from, to, bias) {
|
|
136
136
|
const blended = from.map((val, i) => clamp(blendNumbers(val, to[i], bias), 0, 255));
|
|
137
|
-
|
|
137
|
+
if (blended[3] === 255) {
|
|
138
|
+
return "#" +
|
|
139
|
+
Math.round(blended[0]).toString(16).padStart(2, "0") +
|
|
140
|
+
Math.round(blended[1]).toString(16).padStart(2, "0") +
|
|
141
|
+
Math.round(blended[2]).toString(16).padStart(2, "0");
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
return "#" +
|
|
145
|
+
Math.round(blended[0]).toString(16).padStart(2, "0") +
|
|
146
|
+
Math.round(blended[1]).toString(16).padStart(2, "0") +
|
|
147
|
+
Math.round(blended[2]).toString(16).padStart(2, "0") +
|
|
148
|
+
Math.round(blended[3]).toString(16).padStart(2, "0");
|
|
149
|
+
}
|
|
138
150
|
}
|
|
139
151
|
const tweenableTokenRegex = /(#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\b|[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)/g;
|
|
140
152
|
function tokenise(s) {
|