@xtia/timeline 1.1.11 → 1.1.13
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 +13 -4
- package/internal/emitters.d.ts +1 -1
- package/internal/emitters.js +15 -14
- package/internal/point.js +3 -3
- package/internal/range.js +2 -2
- package/internal/timeline.d.ts +15 -0
- package/internal/timeline.js +21 -5
- package/internal/tween.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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,9 +5,10 @@ export class Emitter {
|
|
|
5
5
|
constructor(onListen) {
|
|
6
6
|
this.onListen = onListen;
|
|
7
7
|
}
|
|
8
|
-
|
|
8
|
+
transform(handler) {
|
|
9
9
|
let parentUnsubscribe = null;
|
|
10
|
-
const
|
|
10
|
+
const parentListen = this.onListen;
|
|
11
|
+
const { emit, listen } = createListenable(() => parentUnsubscribe = parentListen(value => {
|
|
11
12
|
handler(value, emit);
|
|
12
13
|
}), () => {
|
|
13
14
|
parentUnsubscribe();
|
|
@@ -37,7 +38,7 @@ export class Emitter {
|
|
|
37
38
|
* @returns Listenable: emits transformed values
|
|
38
39
|
*/
|
|
39
40
|
map(mapFunc) {
|
|
40
|
-
const listen = this.
|
|
41
|
+
const listen = this.transform((value, emit) => emit(mapFunc(value)));
|
|
41
42
|
return new Emitter(listen);
|
|
42
43
|
}
|
|
43
44
|
/**
|
|
@@ -46,7 +47,7 @@ export class Emitter {
|
|
|
46
47
|
* @returns Listenable: emits values that pass the filter
|
|
47
48
|
*/
|
|
48
49
|
filter(check) {
|
|
49
|
-
const listen = this.
|
|
50
|
+
const listen = this.transform((value, emit) => check(value) && emit(value));
|
|
50
51
|
return new Emitter(listen);
|
|
51
52
|
}
|
|
52
53
|
/**
|
|
@@ -58,7 +59,7 @@ export class Emitter {
|
|
|
58
59
|
*/
|
|
59
60
|
dedupe(compare) {
|
|
60
61
|
let previous = null;
|
|
61
|
-
const listen = this.
|
|
62
|
+
const listen = this.transform((value, emit) => {
|
|
62
63
|
if (!previous || (compare
|
|
63
64
|
? !compare(previous.value, value)
|
|
64
65
|
: (previous.value !== value))) {
|
|
@@ -80,7 +81,7 @@ export class Emitter {
|
|
|
80
81
|
* @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
|
|
81
82
|
*/
|
|
82
83
|
tap(cb) {
|
|
83
|
-
const listen = this.
|
|
84
|
+
const listen = this.transform((value, emit) => {
|
|
84
85
|
cb(value);
|
|
85
86
|
emit(value);
|
|
86
87
|
});
|
|
@@ -114,13 +115,13 @@ export class RangeProgression extends Emitter {
|
|
|
114
115
|
? easers[easer]
|
|
115
116
|
: easer;
|
|
116
117
|
const listen = easerFunc
|
|
117
|
-
? this.
|
|
118
|
+
? this.transform((value, emit) => emit(easerFunc(value)))
|
|
118
119
|
: this.onListen;
|
|
119
120
|
return new RangeProgression(listen);
|
|
120
121
|
}
|
|
121
122
|
tween(from, to) {
|
|
122
123
|
const tween = createTween(from, to);
|
|
123
|
-
const listen = this.
|
|
124
|
+
const listen = this.transform((progress, emit) => emit(tween(progress)));
|
|
124
125
|
return new Emitter(listen);
|
|
125
126
|
}
|
|
126
127
|
/**
|
|
@@ -137,7 +138,7 @@ export class RangeProgression extends Emitter {
|
|
|
137
138
|
* @returns Listenable: emits the sampled values
|
|
138
139
|
*/
|
|
139
140
|
sample(source) {
|
|
140
|
-
const listen = this.
|
|
141
|
+
const listen = this.transform((value, emit) => {
|
|
141
142
|
const clampedProgress = clamp(value);
|
|
142
143
|
const index = Math.floor(clampedProgress * (source.length - 1));
|
|
143
144
|
emit(source[index]);
|
|
@@ -167,7 +168,7 @@ export class RangeProgression extends Emitter {
|
|
|
167
168
|
* @returns Listenable: emits 0 or 1 after comparing progress with a threshold
|
|
168
169
|
*/
|
|
169
170
|
threshold(threshold) {
|
|
170
|
-
const listen = this.
|
|
171
|
+
const listen = this.transform((value, emit) => emit(value >= threshold ? 1 : 0));
|
|
171
172
|
return new RangeProgression(listen);
|
|
172
173
|
}
|
|
173
174
|
/**
|
|
@@ -200,7 +201,7 @@ export class RangeProgression extends Emitter {
|
|
|
200
201
|
repeat(count) {
|
|
201
202
|
if (count <= 0)
|
|
202
203
|
throw new RangeError("Repeat count must be greater than 0");
|
|
203
|
-
const listen = this.
|
|
204
|
+
const listen = this.transform((value, emit) => {
|
|
204
205
|
const out = (value * count) % 1;
|
|
205
206
|
emit(out);
|
|
206
207
|
});
|
|
@@ -212,7 +213,7 @@ export class RangeProgression extends Emitter {
|
|
|
212
213
|
* @returns Listenable: emits values that pass the filter
|
|
213
214
|
*/
|
|
214
215
|
filter(check) {
|
|
215
|
-
const listen = this.
|
|
216
|
+
const listen = this.transform((value, emit) => {
|
|
216
217
|
if (check(value))
|
|
217
218
|
emit(value);
|
|
218
219
|
});
|
|
@@ -225,7 +226,7 @@ export class RangeProgression extends Emitter {
|
|
|
225
226
|
dedupe() {
|
|
226
227
|
if (!this._dedupe) {
|
|
227
228
|
let previous = null;
|
|
228
|
-
const listen = this.
|
|
229
|
+
const listen = this.transform((value, emit) => {
|
|
229
230
|
if (previous !== value) {
|
|
230
231
|
emit(value);
|
|
231
232
|
previous = value;
|
|
@@ -267,7 +268,7 @@ export class RangeProgression extends Emitter {
|
|
|
267
268
|
* @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
|
|
268
269
|
*/
|
|
269
270
|
tap(cb) {
|
|
270
|
-
const listen = this.
|
|
271
|
+
const listen = this.transform((value, emit) => {
|
|
271
272
|
cb(value);
|
|
272
273
|
emit(value);
|
|
273
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) {
|
|
@@ -213,7 +226,10 @@ export class Timeline {
|
|
|
213
226
|
.range(0, durationMs)
|
|
214
227
|
.ease(easer)
|
|
215
228
|
.tween(this._currentTime, toPosition)
|
|
216
|
-
.apply(v =>
|
|
229
|
+
.apply(v => {
|
|
230
|
+
this.seekDirect(v);
|
|
231
|
+
this._frameEvents?.emit();
|
|
232
|
+
});
|
|
217
233
|
return seeker.end.promise();
|
|
218
234
|
}
|
|
219
235
|
seekDirect(toPosition) {
|
package/internal/tween.js
CHANGED
|
@@ -143,13 +143,13 @@ function tokenise(s) {
|
|
|
143
143
|
let m;
|
|
144
144
|
while ((m = tweenableTokenRegex.exec(s))) {
|
|
145
145
|
const token = m[0];
|
|
146
|
-
const prefix = s.
|
|
146
|
+
const prefix = s.substring(lastIdx, m.index); // literal before token
|
|
147
147
|
const type = getTokenType(token);
|
|
148
148
|
chunks.push({ prefix, token, type });
|
|
149
149
|
lastIdx = m.index + token.length;
|
|
150
150
|
}
|
|
151
151
|
// trailing literal after the last token – stored as a final chunk
|
|
152
|
-
const tail = s.
|
|
152
|
+
const tail = s.substring(lastIdx);
|
|
153
153
|
if (tail.length) {
|
|
154
154
|
chunks.push({ prefix: tail, token: "", type: TokenTypes.none });
|
|
155
155
|
}
|