@xtia/timeline 1.1.6 → 1.1.8
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 +8 -8
- package/internal/emitters.d.ts +19 -15
- package/internal/emitters.js +77 -71
- package/internal/point.d.ts +7 -5
- package/internal/point.js +31 -25
- package/internal/range.d.ts +12 -1
- package/internal/range.js +18 -10
- package/internal/timeline.d.ts +36 -1
- package/internal/timeline.js +104 -41
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -515,6 +515,14 @@ Listeners will be invoked with a [`PointEvent`](#pointevent-interface) when a se
|
|
|
515
515
|
|
|
516
516
|
This point's position on the Timeline.
|
|
517
517
|
|
|
518
|
+
##### `forwardOnly: Emitter<PointEvent>`
|
|
519
|
+
|
|
520
|
+
Provides an emitter that forwards emissions triggered by forward-moving seeks.
|
|
521
|
+
|
|
522
|
+
##### `reverseOnly: Emitter<PointEvent>`
|
|
523
|
+
|
|
524
|
+
Provides an emitter that forwards emissions triggered by backward-moving seeks.
|
|
525
|
+
|
|
518
526
|
#### Methods
|
|
519
527
|
|
|
520
528
|
##### `range(duration): TimelineRange`
|
|
@@ -543,14 +551,6 @@ Creates a `Promise` that will be resolved when the Timeline first seeks to/past
|
|
|
543
551
|
|
|
544
552
|
The resolved value indicates the direction of the seek that triggered resolution.
|
|
545
553
|
|
|
546
|
-
##### `forwardOnly(): Emitter<PointEvent>`
|
|
547
|
-
|
|
548
|
-
Creates an emitter that forwards emissions triggered by forward-moving seeks.
|
|
549
|
-
|
|
550
|
-
##### `reverseOnly(): Emitter<PointEvent>`
|
|
551
|
-
|
|
552
|
-
Creates an emitter that forwards emissions triggered by backward-moving seeks.
|
|
553
|
-
|
|
554
554
|
##### `applyDirectional(apply, revert): UnsubscribeFunc`
|
|
555
555
|
|
|
556
556
|
Registers an emission handler that calls one function for forward seeks to or past the point, and another for backward seeks from or past the point.
|
package/internal/emitters.d.ts
CHANGED
|
@@ -6,15 +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
|
-
|
|
10
|
-
* Used by tap() to create a clone of an Emitter with a redirected onListen
|
|
11
|
-
*
|
|
12
|
-
* Should be overridden in all Emitter subclasses
|
|
13
|
-
* @see {@link TimelineRange.redirect}
|
|
14
|
-
* @param listen
|
|
15
|
-
* @returns {this}
|
|
16
|
-
*/
|
|
17
|
-
protected redirect(listen: ListenFunc<T>): Emitter<T>;
|
|
9
|
+
protected createTransformListen<R = T>(handler: (value: T, emit: (value: R) => void) => void): (fn: (v: R) => void) => UnsubscribeFunc;
|
|
18
10
|
/**
|
|
19
11
|
* Compatibility alias for `apply()` - registers a function to receive emitted values
|
|
20
12
|
* @param handler
|
|
@@ -58,7 +50,7 @@ export declare class Emitter<T> {
|
|
|
58
50
|
* @param cb A function to be called as a side effect for each value emitted by the parent emitter.
|
|
59
51
|
* @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
|
|
60
52
|
*/
|
|
61
|
-
tap(cb: Handler<T>):
|
|
53
|
+
tap(cb: Handler<T>): Emitter<T>;
|
|
62
54
|
/**
|
|
63
55
|
* Immediately passes this emitter to a callback and returns this emitter
|
|
64
56
|
*
|
|
@@ -79,7 +71,6 @@ export declare class Emitter<T> {
|
|
|
79
71
|
fork(cb: (branch: this) => void): this;
|
|
80
72
|
}
|
|
81
73
|
export declare class RangeProgression extends Emitter<number> {
|
|
82
|
-
protected redirect(listen: ListenFunc<number>): RangeProgression;
|
|
83
74
|
/**
|
|
84
75
|
* Creates a chainable progress emitter that applies an easing function to its parent's emitted values
|
|
85
76
|
*
|
|
@@ -91,7 +82,7 @@ export declare class RangeProgression extends Emitter<number> {
|
|
|
91
82
|
/**
|
|
92
83
|
* Creates a chainable emitter that interpolates two given values by progression emitted by its parent
|
|
93
84
|
*
|
|
94
|
-
* Can interpolate types `number`, `number[]`, string and objects with a `blend(from: this,
|
|
85
|
+
* Can interpolate types `number`, `number[]`, string and objects with a `blend(from: this, progression: number): this` method
|
|
95
86
|
*
|
|
96
87
|
* @param from Value to interpolate from
|
|
97
88
|
* @param to Value to interpolate to
|
|
@@ -101,7 +92,7 @@ export declare class RangeProgression extends Emitter<number> {
|
|
|
101
92
|
/**
|
|
102
93
|
* Creates a chainable emitter that interpolates two given values by progression emitted by its parent
|
|
103
94
|
*
|
|
104
|
-
* Can interpolate types `number`, `number[]`, string and objects with a `blend(from: this,
|
|
95
|
+
* Can interpolate types `number`, `number[]`, string and objects with a `blend(from: this, progression: number): this` method
|
|
105
96
|
*
|
|
106
97
|
* #### String interpolation
|
|
107
98
|
* * If the strings contain tweenable tokens (numbers, colour codes) and are otherwise identical, those tokens are interpolated
|
|
@@ -122,7 +113,7 @@ export declare class RangeProgression extends Emitter<number> {
|
|
|
122
113
|
/**
|
|
123
114
|
* Creates a chainable emitter that interpolates two given values by progression emitted by its parent
|
|
124
115
|
*
|
|
125
|
-
* Can interpolate types `number`, `number[]`, string and objects with a `blend(from: this,
|
|
116
|
+
* Can interpolate types `number`, `number[]`, string and objects with a `blend(from: this, progression: number): this` method
|
|
126
117
|
*
|
|
127
118
|
* @param from Value to interpolate from
|
|
128
119
|
* @param to Value to interpolate to
|
|
@@ -190,12 +181,13 @@ export declare class RangeProgression extends Emitter<number> {
|
|
|
190
181
|
* @param check Function that takes an emitted value and returns true if the emission should be forwarded along the chain
|
|
191
182
|
* @returns Listenable: emits values that pass the filter
|
|
192
183
|
*/
|
|
193
|
-
filter(check: (
|
|
184
|
+
filter(check: (progress: number) => boolean): RangeProgression;
|
|
194
185
|
/**
|
|
195
186
|
* Creates a chainable progress emitter that discards emitted values that are the same as the last value emitted by the new emitter
|
|
196
187
|
* @returns Listenable: emits non-repeating values
|
|
197
188
|
*/
|
|
198
189
|
dedupe(): RangeProgression;
|
|
190
|
+
private _dedupe?;
|
|
199
191
|
/**
|
|
200
192
|
* Creates a chainable progress emitter that offsets its parent's values by the given delta, wrapping at 1
|
|
201
193
|
*
|
|
@@ -214,6 +206,18 @@ export declare class RangeProgression extends Emitter<number> {
|
|
|
214
206
|
* @returns Listenable: emits offset values
|
|
215
207
|
*/
|
|
216
208
|
offset(delta: number): RangeProgression;
|
|
209
|
+
/**
|
|
210
|
+
* Creates a chainable emitter that mirrors emissions from the parent emitter, invoking the provided callback `cb` as a side effect for each emission.
|
|
211
|
+
*
|
|
212
|
+
* The callback `cb` is called exactly once per parent emission, regardless of how many listeners are attached to the returned emitter.
|
|
213
|
+
* All listeners attached to the returned emitter receive the same values as the parent emitter.
|
|
214
|
+
*
|
|
215
|
+
* *Note*, the side effect `cb` is only invoked when there is at least one listener attached to the returned emitter
|
|
216
|
+
*
|
|
217
|
+
* @param cb A function to be called as a side effect for each value emitted by the parent emitter.
|
|
218
|
+
* @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
|
|
219
|
+
*/
|
|
220
|
+
tap(cb: Handler<number>): RangeProgression;
|
|
217
221
|
}
|
|
218
222
|
export declare function createListenable<T>(onAddFirst?: () => void, onRemoveLast?: () => void): {
|
|
219
223
|
listen: (fn: (v: T) => void) => UnsubscribeFunc;
|
package/internal/emitters.js
CHANGED
|
@@ -5,16 +5,15 @@ export class Emitter {
|
|
|
5
5
|
constructor(onListen) {
|
|
6
6
|
this.onListen = onListen;
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return new Emitter(listen);
|
|
8
|
+
createTransformListen(handler) {
|
|
9
|
+
let parentUnsubscribe = null;
|
|
10
|
+
const { emit, listen } = createListenable(() => parentUnsubscribe = this.onListen(value => {
|
|
11
|
+
handler(value, emit);
|
|
12
|
+
}), () => {
|
|
13
|
+
parentUnsubscribe();
|
|
14
|
+
parentUnsubscribe = null;
|
|
15
|
+
});
|
|
16
|
+
return listen;
|
|
18
17
|
}
|
|
19
18
|
/**
|
|
20
19
|
* Compatibility alias for `apply()` - registers a function to receive emitted values
|
|
@@ -22,9 +21,7 @@ export class Emitter {
|
|
|
22
21
|
* @returns A function to deregister the handler
|
|
23
22
|
*/
|
|
24
23
|
listen(handler) {
|
|
25
|
-
return this.onListen(
|
|
26
|
-
handler(value);
|
|
27
|
-
});
|
|
24
|
+
return this.onListen(handler);
|
|
28
25
|
}
|
|
29
26
|
/**
|
|
30
27
|
* Registers a function to receive emitted values
|
|
@@ -32,9 +29,7 @@ export class Emitter {
|
|
|
32
29
|
* @returns A function to deregister the handler
|
|
33
30
|
*/
|
|
34
31
|
apply(handler) {
|
|
35
|
-
return this.onListen(
|
|
36
|
-
handler(value);
|
|
37
|
-
});
|
|
32
|
+
return this.onListen(handler);
|
|
38
33
|
}
|
|
39
34
|
/**
|
|
40
35
|
* Creates a chainable emitter that applies arbitrary transformation to values emitted by its parent
|
|
@@ -42,9 +37,8 @@ export class Emitter {
|
|
|
42
37
|
* @returns Listenable: emits transformed values
|
|
43
38
|
*/
|
|
44
39
|
map(mapFunc) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}));
|
|
40
|
+
const listen = this.createTransformListen((value, emit) => emit(mapFunc(value)));
|
|
41
|
+
return new Emitter(listen);
|
|
48
42
|
}
|
|
49
43
|
/**
|
|
50
44
|
* Creates a chainable emitter that selectively forwards emissions along the chain
|
|
@@ -52,10 +46,8 @@ export class Emitter {
|
|
|
52
46
|
* @returns Listenable: emits values that pass the filter
|
|
53
47
|
*/
|
|
54
48
|
filter(check) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
handler(value);
|
|
58
|
-
}));
|
|
49
|
+
const listen = this.createTransformListen((value, emit) => check(value) && emit(value));
|
|
50
|
+
return new Emitter(listen);
|
|
59
51
|
}
|
|
60
52
|
/**
|
|
61
53
|
* Creates a chainable emitter that discards emitted values that are the same as the last value emitted by the new emitter
|
|
@@ -66,17 +58,15 @@ export class Emitter {
|
|
|
66
58
|
*/
|
|
67
59
|
dedupe(compare) {
|
|
68
60
|
let previous = null;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
return this.onListen(filteredHandler);
|
|
61
|
+
const listen = this.createTransformListen((value, emit) => {
|
|
62
|
+
if (!previous || (compare
|
|
63
|
+
? !compare(previous.value, value)
|
|
64
|
+
: (previous.value !== value))) {
|
|
65
|
+
emit(value);
|
|
66
|
+
previous = { value };
|
|
67
|
+
}
|
|
79
68
|
});
|
|
69
|
+
return new Emitter(listen);
|
|
80
70
|
}
|
|
81
71
|
/**
|
|
82
72
|
* Creates a chainable emitter that mirrors emissions from the parent emitter, invoking the provided callback `cb` as a side effect for each emission.
|
|
@@ -90,15 +80,11 @@ export class Emitter {
|
|
|
90
80
|
* @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
|
|
91
81
|
*/
|
|
92
82
|
tap(cb) {
|
|
93
|
-
|
|
94
|
-
const { emit, listen } = createListenable(() => parentUnsubscribe = this.onListen(value => {
|
|
83
|
+
const listen = this.createTransformListen((value, emit) => {
|
|
95
84
|
cb(value);
|
|
96
85
|
emit(value);
|
|
97
|
-
}), () => {
|
|
98
|
-
parentUnsubscribe();
|
|
99
|
-
parentUnsubscribe = null;
|
|
100
86
|
});
|
|
101
|
-
return
|
|
87
|
+
return new Emitter(listen);
|
|
102
88
|
}
|
|
103
89
|
/**
|
|
104
90
|
* Immediately passes this emitter to a callback and returns this emitter
|
|
@@ -123,22 +109,19 @@ export class Emitter {
|
|
|
123
109
|
}
|
|
124
110
|
}
|
|
125
111
|
export class RangeProgression extends Emitter {
|
|
126
|
-
redirect(listen) {
|
|
127
|
-
return new RangeProgression(listen);
|
|
128
|
-
}
|
|
129
112
|
ease(easer) {
|
|
130
|
-
if (!easer)
|
|
131
|
-
return this;
|
|
132
113
|
const easerFunc = typeof easer == "string"
|
|
133
114
|
? easers[easer]
|
|
134
115
|
: easer;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
116
|
+
const listen = easerFunc
|
|
117
|
+
? this.createTransformListen((value, emit) => emit(easerFunc(value)))
|
|
118
|
+
: this.onListen;
|
|
119
|
+
return new RangeProgression(listen);
|
|
138
120
|
}
|
|
139
121
|
tween(from, to) {
|
|
140
122
|
const tween = createTween(from, to);
|
|
141
|
-
|
|
123
|
+
const listen = this.createTransformListen((progress, emit) => emit(tween(progress)));
|
|
124
|
+
return new Emitter(listen);
|
|
142
125
|
}
|
|
143
126
|
/**
|
|
144
127
|
* Creates a chainable emitter that takes a value from an array according to progression
|
|
@@ -154,11 +137,12 @@ export class RangeProgression extends Emitter {
|
|
|
154
137
|
* @returns Listenable: emits the sampled values
|
|
155
138
|
*/
|
|
156
139
|
sample(source) {
|
|
157
|
-
|
|
158
|
-
const clampedProgress = clamp(
|
|
140
|
+
const listen = this.createTransformListen((value, emit) => {
|
|
141
|
+
const clampedProgress = clamp(value);
|
|
159
142
|
const index = Math.floor(clampedProgress * (source.length - 1));
|
|
160
|
-
|
|
161
|
-
})
|
|
143
|
+
emit(source[index]);
|
|
144
|
+
});
|
|
145
|
+
return new Emitter(listen);
|
|
162
146
|
}
|
|
163
147
|
/**
|
|
164
148
|
* Creates a chainable progress emitter that quantises progress, as emitted by its parent, to the nearest of `steps` discrete values.
|
|
@@ -183,9 +167,8 @@ export class RangeProgression extends Emitter {
|
|
|
183
167
|
* @returns Listenable: emits 0 or 1 after comparing progress with a threshold
|
|
184
168
|
*/
|
|
185
169
|
threshold(threshold) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}));
|
|
170
|
+
const listen = this.createTransformListen((value, emit) => emit(value >= threshold ? 1 : 0));
|
|
171
|
+
return new RangeProgression(listen);
|
|
189
172
|
}
|
|
190
173
|
/**
|
|
191
174
|
* Creates a chainable progress emitter that clamps incoming values
|
|
@@ -215,11 +198,13 @@ export class RangeProgression extends Emitter {
|
|
|
215
198
|
* @returns Listenable: emits scaled and repeating values
|
|
216
199
|
*/
|
|
217
200
|
repeat(count) {
|
|
218
|
-
count
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
201
|
+
if (count <= 0)
|
|
202
|
+
throw new RangeError("Repeat count must be greater than 0");
|
|
203
|
+
const listen = this.createTransformListen((value, emit) => {
|
|
204
|
+
const out = (value * count) % 1;
|
|
205
|
+
emit(out);
|
|
206
|
+
});
|
|
207
|
+
return new RangeProgression(listen);
|
|
223
208
|
}
|
|
224
209
|
/**
|
|
225
210
|
* Creates a chainable progress emitter that selectively forwards emissions along the chain
|
|
@@ -227,25 +212,28 @@ export class RangeProgression extends Emitter {
|
|
|
227
212
|
* @returns Listenable: emits values that pass the filter
|
|
228
213
|
*/
|
|
229
214
|
filter(check) {
|
|
230
|
-
|
|
215
|
+
const listen = this.createTransformListen((value, emit) => {
|
|
231
216
|
if (check(value))
|
|
232
|
-
|
|
233
|
-
})
|
|
217
|
+
emit(value);
|
|
218
|
+
});
|
|
219
|
+
return new RangeProgression(listen);
|
|
234
220
|
}
|
|
235
221
|
/**
|
|
236
222
|
* Creates a chainable progress emitter that discards emitted values that are the same as the last value emitted by the new emitter
|
|
237
223
|
* @returns Listenable: emits non-repeating values
|
|
238
224
|
*/
|
|
239
225
|
dedupe() {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if (
|
|
244
|
-
|
|
226
|
+
if (!this._dedupe) {
|
|
227
|
+
let previous = null;
|
|
228
|
+
const listen = this.createTransformListen((value, emit) => {
|
|
229
|
+
if (previous !== value) {
|
|
230
|
+
emit(value);
|
|
245
231
|
previous = value;
|
|
246
232
|
}
|
|
247
233
|
});
|
|
248
|
-
|
|
234
|
+
this._dedupe = new RangeProgression(listen);
|
|
235
|
+
}
|
|
236
|
+
return this._dedupe;
|
|
249
237
|
}
|
|
250
238
|
/**
|
|
251
239
|
* Creates a chainable progress emitter that offsets its parent's values by the given delta, wrapping at 1
|
|
@@ -267,11 +255,29 @@ export class RangeProgression extends Emitter {
|
|
|
267
255
|
offset(delta) {
|
|
268
256
|
return new RangeProgression(handler => this.onListen(value => handler((value + delta) % 1)));
|
|
269
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Creates a chainable emitter that mirrors emissions from the parent emitter, invoking the provided callback `cb` as a side effect for each emission.
|
|
260
|
+
*
|
|
261
|
+
* The callback `cb` is called exactly once per parent emission, regardless of how many listeners are attached to the returned emitter.
|
|
262
|
+
* All listeners attached to the returned emitter receive the same values as the parent emitter.
|
|
263
|
+
*
|
|
264
|
+
* *Note*, the side effect `cb` is only invoked when there is at least one listener attached to the returned emitter
|
|
265
|
+
*
|
|
266
|
+
* @param cb A function to be called as a side effect for each value emitted by the parent emitter.
|
|
267
|
+
* @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
|
|
268
|
+
*/
|
|
269
|
+
tap(cb) {
|
|
270
|
+
const listen = this.createTransformListen((value, emit) => {
|
|
271
|
+
cb(value);
|
|
272
|
+
emit(value);
|
|
273
|
+
});
|
|
274
|
+
return new RangeProgression(listen);
|
|
275
|
+
}
|
|
270
276
|
}
|
|
271
277
|
export function createListenable(onAddFirst, onRemoveLast) {
|
|
272
278
|
const handlers = [];
|
|
273
279
|
const addListener = (fn) => {
|
|
274
|
-
const unique =
|
|
280
|
+
const unique = { fn };
|
|
275
281
|
handlers.push(unique);
|
|
276
282
|
if (onAddFirst && handlers.length == 1)
|
|
277
283
|
onAddFirst();
|
|
@@ -286,6 +292,6 @@ export function createListenable(onAddFirst, onRemoveLast) {
|
|
|
286
292
|
};
|
|
287
293
|
return {
|
|
288
294
|
listen: addListener,
|
|
289
|
-
emit: (value) => handlers.forEach(h => h(value)),
|
|
295
|
+
emit: (value) => handlers.forEach(h => h.fn(value)),
|
|
290
296
|
};
|
|
291
297
|
}
|
package/internal/point.d.ts
CHANGED
|
@@ -17,7 +17,6 @@ export declare class TimelinePoint extends Emitter<PointEvent> {
|
|
|
17
17
|
* The point's absolute position on the Timeline
|
|
18
18
|
*/
|
|
19
19
|
position: number);
|
|
20
|
-
protected redirect(listen: ListenFunc<PointEvent>): TimelinePoint;
|
|
21
20
|
/**
|
|
22
21
|
* Creates a range on the Timeline, with a given duration, starting at this point
|
|
23
22
|
* @param duration
|
|
@@ -47,15 +46,17 @@ export declare class TimelinePoint extends Emitter<PointEvent> {
|
|
|
47
46
|
*/
|
|
48
47
|
seek(duration: number, easer?: Easer): Promise<void>;
|
|
49
48
|
/**
|
|
50
|
-
*
|
|
49
|
+
* An point emitter that only emits on forward-moving seeks
|
|
51
50
|
* @returns Listenable: emits forward-seeking point events
|
|
52
51
|
*/
|
|
53
|
-
forwardOnly(): Emitter<PointEvent>;
|
|
52
|
+
get forwardOnly(): Emitter<PointEvent>;
|
|
53
|
+
private _forwardOnly?;
|
|
54
54
|
/**
|
|
55
|
-
*
|
|
55
|
+
* An point emitter that only emits on backward-moving seeks
|
|
56
56
|
* @returns Listenable: emits backward-seeking point events
|
|
57
57
|
*/
|
|
58
|
-
reverseOnly(): Emitter<PointEvent>;
|
|
58
|
+
get reverseOnly(): Emitter<PointEvent>;
|
|
59
|
+
private _reverseOnly?;
|
|
59
60
|
filter(check: (event: PointEvent) => boolean): Emitter<PointEvent>;
|
|
60
61
|
/**
|
|
61
62
|
* Creates an emitter that forwards events emitted by seeks of a specific direction
|
|
@@ -96,4 +97,5 @@ export declare class TimelinePoint extends Emitter<PointEvent> {
|
|
|
96
97
|
* @returns Listenable: emits non-repeating point events
|
|
97
98
|
*/
|
|
98
99
|
dedupe(): Emitter<PointEvent>;
|
|
100
|
+
private _dedupe?;
|
|
99
101
|
}
|
package/internal/point.js
CHANGED
|
@@ -10,9 +10,6 @@ export class TimelinePoint extends Emitter {
|
|
|
10
10
|
this.timeline = timeline;
|
|
11
11
|
this.position = position;
|
|
12
12
|
}
|
|
13
|
-
redirect(listen) {
|
|
14
|
-
return new TimelinePoint(listen, this.timeline, this.position);
|
|
15
|
-
}
|
|
16
13
|
/**
|
|
17
14
|
* Creates a range on the Timeline, with a given duration, starting at this point
|
|
18
15
|
* @param duration
|
|
@@ -44,29 +41,34 @@ export class TimelinePoint extends Emitter {
|
|
|
44
41
|
return this.timeline.seek(this.position, duration, easer);
|
|
45
42
|
}
|
|
46
43
|
/**
|
|
47
|
-
*
|
|
44
|
+
* An point emitter that only emits on forward-moving seeks
|
|
48
45
|
* @returns Listenable: emits forward-seeking point events
|
|
49
46
|
*/
|
|
50
|
-
forwardOnly() {
|
|
51
|
-
|
|
47
|
+
get forwardOnly() {
|
|
48
|
+
if (!this._forwardOnly)
|
|
49
|
+
this._forwardOnly = this.filter(1);
|
|
50
|
+
return this._forwardOnly;
|
|
52
51
|
}
|
|
53
52
|
/**
|
|
54
|
-
*
|
|
53
|
+
* An point emitter that only emits on backward-moving seeks
|
|
55
54
|
* @returns Listenable: emits backward-seeking point events
|
|
56
55
|
*/
|
|
57
|
-
reverseOnly() {
|
|
58
|
-
|
|
56
|
+
get reverseOnly() {
|
|
57
|
+
if (!this._reverseOnly)
|
|
58
|
+
this._reverseOnly = this.filter(-1);
|
|
59
|
+
return this._reverseOnly;
|
|
59
60
|
}
|
|
60
61
|
filter(arg) {
|
|
61
|
-
|
|
62
|
-
?
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
:
|
|
67
|
-
if (arg(
|
|
68
|
-
|
|
69
|
-
})
|
|
62
|
+
const listen = this.createTransformListen(typeof arg == "number"
|
|
63
|
+
? (value, emit) => {
|
|
64
|
+
if (value.direction === arg)
|
|
65
|
+
emit(value);
|
|
66
|
+
}
|
|
67
|
+
: (value, emit) => {
|
|
68
|
+
if (arg(value))
|
|
69
|
+
emit(value);
|
|
70
|
+
});
|
|
71
|
+
return new Emitter(listen);
|
|
70
72
|
}
|
|
71
73
|
/**
|
|
72
74
|
* Creates a Promise that will be resolved when the Timeline first seeks to/past this point
|
|
@@ -112,12 +114,16 @@ export class TimelinePoint extends Emitter {
|
|
|
112
114
|
* @returns Listenable: emits non-repeating point events
|
|
113
115
|
*/
|
|
114
116
|
dedupe() {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
117
|
+
if (!this._dedupe) {
|
|
118
|
+
let previous = 0;
|
|
119
|
+
const listen = this.createTransformListen((value, emit) => {
|
|
120
|
+
if (value.direction !== previous) {
|
|
121
|
+
previous = value.direction;
|
|
122
|
+
emit(value);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
this._dedupe = new Emitter(listen);
|
|
126
|
+
}
|
|
127
|
+
return this._dedupe;
|
|
122
128
|
}
|
|
123
129
|
}
|
package/internal/range.d.ts
CHANGED
|
@@ -18,7 +18,6 @@ export declare class TimelineRange extends RangeProgression {
|
|
|
18
18
|
start: TimelinePoint,
|
|
19
19
|
/** The point on the Timeline at which this range ends */
|
|
20
20
|
end: TimelinePoint);
|
|
21
|
-
protected redirect(listen: ListenFunc<number>): TimelineRange;
|
|
22
21
|
/**
|
|
23
22
|
* Creates two ranges by seperating one at a given point
|
|
24
23
|
* @param position Point of separation, relative to the range's start - if omitted, the range will be separated halfway
|
|
@@ -33,6 +32,18 @@ export declare class TimelineRange extends RangeProgression {
|
|
|
33
32
|
* @returns Array(count) of points
|
|
34
33
|
*/
|
|
35
34
|
spread(count: number): TimelinePoint[];
|
|
35
|
+
/**
|
|
36
|
+
* Creates a series of evenly-spread points across the range, optionally including the range's start and end
|
|
37
|
+
* @param count Number of Points to return, including head and tail
|
|
38
|
+
* @param includeEnds
|
|
39
|
+
*/
|
|
40
|
+
spread(count: number, includeEnds: boolean): TimelinePoint[];
|
|
41
|
+
/**
|
|
42
|
+
* Creates a series of evenly-spread points across the range, optionally including the range's start and end
|
|
43
|
+
* @param count Number of Points to return, including head and tail
|
|
44
|
+
* @param includeEnds
|
|
45
|
+
*/
|
|
46
|
+
spread(count: number, includeStart: boolean, includeEnd: boolean): TimelinePoint[];
|
|
36
47
|
/**
|
|
37
48
|
* Creates the specified number of ranges, each of `(parent.duration / count)` duration, spread
|
|
38
49
|
* evenly over this range
|
package/internal/range.js
CHANGED
|
@@ -15,9 +15,6 @@ export class TimelineRange extends RangeProgression {
|
|
|
15
15
|
this.endPosition = end.position;
|
|
16
16
|
this.duration = this.endPosition - this.startPosition;
|
|
17
17
|
}
|
|
18
|
-
redirect(listen) {
|
|
19
|
-
return new TimelineRange(listen, this.timeline, this.start, this.end);
|
|
20
|
-
}
|
|
21
18
|
/**
|
|
22
19
|
* Creates two ranges by seperating one at a given point
|
|
23
20
|
* @param position Point of separation, relative to the range's start - if omitted, the range will be separated halfway
|
|
@@ -34,15 +31,26 @@ export class TimelineRange extends RangeProgression {
|
|
|
34
31
|
this.timeline.range(position + this.startPosition, this.duration - position),
|
|
35
32
|
];
|
|
36
33
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
spread(count, includeStart = false, includeEnd = includeStart) {
|
|
35
|
+
let start = [];
|
|
36
|
+
let end = [];
|
|
37
|
+
if (includeStart) {
|
|
38
|
+
start = [this.start];
|
|
39
|
+
count--;
|
|
40
|
+
}
|
|
41
|
+
if (includeEnd) {
|
|
42
|
+
end = [this.end];
|
|
43
|
+
count--;
|
|
44
|
+
}
|
|
45
|
+
if (count == 0)
|
|
46
|
+
return [...start, ...end];
|
|
47
|
+
if (count < 0)
|
|
48
|
+
throw new Error("Invalid spread count");
|
|
43
49
|
const delta = this.duration / (count + 1);
|
|
44
50
|
return [
|
|
45
|
-
...
|
|
51
|
+
...start,
|
|
52
|
+
...Array(count).fill(0).map((_, idx) => this.timeline.point(idx * delta + this.startPosition + delta)),
|
|
53
|
+
...end
|
|
46
54
|
];
|
|
47
55
|
}
|
|
48
56
|
/**
|
package/internal/timeline.d.ts
CHANGED
|
@@ -27,19 +27,31 @@ export declare class Timeline {
|
|
|
27
27
|
* A value of 2 would double progression speed while .25 would slow it to a quarter
|
|
28
28
|
*/
|
|
29
29
|
timeScale: number;
|
|
30
|
+
/**
|
|
31
|
+
* The current position of this Timeline's 'play head'
|
|
32
|
+
*/
|
|
30
33
|
get currentTime(): number;
|
|
31
34
|
set currentTime(v: number);
|
|
35
|
+
/**
|
|
36
|
+
* Returns true if this Timeline is currently progressing via `play()`, otherwise false
|
|
37
|
+
*/
|
|
32
38
|
get isPlaying(): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Returns a fixed point at the current end of the Timeline
|
|
41
|
+
*/
|
|
33
42
|
get end(): TimelinePoint;
|
|
34
43
|
private _currentTime;
|
|
35
44
|
private _endPosition;
|
|
36
|
-
private
|
|
45
|
+
private _pause;
|
|
37
46
|
private points;
|
|
38
47
|
private endAction;
|
|
39
48
|
private ranges;
|
|
40
49
|
private currentSortDirection;
|
|
41
50
|
private smoothSeeker;
|
|
42
51
|
private seeking;
|
|
52
|
+
/**
|
|
53
|
+
* A fixed point representing the start of this Timeline (position 0)
|
|
54
|
+
*/
|
|
43
55
|
readonly start: TimelinePoint;
|
|
44
56
|
private _frameEvents;
|
|
45
57
|
/**
|
|
@@ -135,6 +147,9 @@ export declare class Timeline {
|
|
|
135
147
|
* Performs a smooth-seek through a range at (1000 × this.timeScale) units per second
|
|
136
148
|
*/
|
|
137
149
|
play(range: TimelineRange, easer?: Easer): Promise<void>;
|
|
150
|
+
private playWithInterval;
|
|
151
|
+
private playWithRAF;
|
|
152
|
+
private next;
|
|
138
153
|
/**
|
|
139
154
|
* Stops normal progression instigated by play()
|
|
140
155
|
*
|
|
@@ -154,9 +169,29 @@ export declare class Timeline {
|
|
|
154
169
|
* @deprecated Use timeline.position += n
|
|
155
170
|
*/
|
|
156
171
|
step(delta: number): void;
|
|
172
|
+
/**
|
|
173
|
+
* Adds a tweening range to the Timeline
|
|
174
|
+
*
|
|
175
|
+
* **Legacy API**
|
|
176
|
+
* @param start Range's start position
|
|
177
|
+
* @param duration Tween's duration
|
|
178
|
+
* @param apply Function to apply interpolated values
|
|
179
|
+
* @param from Value at start of range
|
|
180
|
+
* @param to Value at end of range
|
|
181
|
+
* @param easer Optional easing function
|
|
182
|
+
*/
|
|
157
183
|
tween<T extends Tweenable>(start: number | TimelinePoint, duration: number, apply: (v: Widen<T>) => void, from: T, to: T, easer?: Easer | keyof typeof easers): ChainingInterface;
|
|
158
184
|
tween<T extends Tweenable>(start: number | TimelinePoint, end: TimelinePoint, // ease migration for tl.tween(0, tl.end, ...)
|
|
159
185
|
apply: (v: Widen<T>) => void, from: T, to: T, easer?: Easer | keyof typeof easers): ChainingInterface;
|
|
186
|
+
/**
|
|
187
|
+
* Adds an event at a specific position
|
|
188
|
+
*
|
|
189
|
+
* **Legacy API**
|
|
190
|
+
* @param position Position of the event
|
|
191
|
+
* @param action Handler for forward seeking
|
|
192
|
+
* @param reverse Handler for backward seeking
|
|
193
|
+
* @returns A tween/event chaining interface
|
|
194
|
+
*/
|
|
160
195
|
at(position: number | TimelinePoint, action?: () => void, reverse?: boolean | (() => void)): ChainingInterface;
|
|
161
196
|
private createChainingInterface;
|
|
162
197
|
/**
|
package/internal/timeline.js
CHANGED
|
@@ -3,6 +3,8 @@ import { TimelinePoint } from "./point";
|
|
|
3
3
|
import { TimelineRange } from "./range";
|
|
4
4
|
import { clamp } from "./utils";
|
|
5
5
|
const default_fps = 60;
|
|
6
|
+
const requestAnimFrame = globalThis?.requestAnimationFrame;
|
|
7
|
+
const cancelAnimFrame = globalThis?.cancelAnimationFrame;
|
|
6
8
|
const EndAction = {
|
|
7
9
|
pause: 0,
|
|
8
10
|
continue: 1,
|
|
@@ -16,13 +18,22 @@ export function animate(durationMs) {
|
|
|
16
18
|
: durationMs.asMilliseconds);
|
|
17
19
|
}
|
|
18
20
|
export class Timeline {
|
|
21
|
+
/**
|
|
22
|
+
* The current position of this Timeline's 'play head'
|
|
23
|
+
*/
|
|
19
24
|
get currentTime() { return this._currentTime; }
|
|
20
25
|
set currentTime(v) {
|
|
21
26
|
this.seek(v);
|
|
22
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Returns true if this Timeline is currently progressing via `play()`, otherwise false
|
|
30
|
+
*/
|
|
23
31
|
get isPlaying() {
|
|
24
|
-
return this.
|
|
32
|
+
return !!this._pause;
|
|
25
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Returns a fixed point at the current end of the Timeline
|
|
36
|
+
*/
|
|
26
37
|
get end() {
|
|
27
38
|
return this.point(this._endPosition);
|
|
28
39
|
}
|
|
@@ -62,12 +73,15 @@ export class Timeline {
|
|
|
62
73
|
this.timeScale = 1;
|
|
63
74
|
this._currentTime = 0;
|
|
64
75
|
this._endPosition = 0;
|
|
65
|
-
this.
|
|
76
|
+
this._pause = null;
|
|
66
77
|
this.points = [];
|
|
67
78
|
this.ranges = [];
|
|
68
79
|
this.currentSortDirection = 0;
|
|
69
80
|
this.smoothSeeker = null;
|
|
70
81
|
this.seeking = false;
|
|
82
|
+
/**
|
|
83
|
+
* A fixed point representing the start of this Timeline (position 0)
|
|
84
|
+
*/
|
|
71
85
|
this.start = this.point(0);
|
|
72
86
|
this._frameEvents = null;
|
|
73
87
|
this._progression = null;
|
|
@@ -299,9 +313,8 @@ export class Timeline {
|
|
|
299
313
|
? sortTweens
|
|
300
314
|
: sortReverse);
|
|
301
315
|
}
|
|
302
|
-
play(arg
|
|
303
|
-
|
|
304
|
-
this.pause();
|
|
316
|
+
play(arg, easer) {
|
|
317
|
+
this._pause?.();
|
|
305
318
|
if (this.smoothSeeker) {
|
|
306
319
|
this.smoothSeeker.pause();
|
|
307
320
|
this.smoothSeeker.seek(this.smoothSeeker.end);
|
|
@@ -311,44 +324,72 @@ export class Timeline {
|
|
|
311
324
|
this.seek(arg.start);
|
|
312
325
|
return this.seek(arg.end, arg.duration / this.timeScale, easer);
|
|
313
326
|
}
|
|
327
|
+
if (arg !== undefined && requestAnimFrame) {
|
|
328
|
+
this.playWithRAF();
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
this.playWithInterval(arg ?? default_fps);
|
|
332
|
+
}
|
|
333
|
+
playWithInterval(fps) {
|
|
314
334
|
let previousTime = Date.now();
|
|
315
|
-
|
|
335
|
+
const interval = setInterval(() => {
|
|
316
336
|
const newTime = Date.now();
|
|
317
337
|
const elapsed = newTime - previousTime;
|
|
318
338
|
previousTime = newTime;
|
|
319
339
|
let delta = elapsed * this.timeScale;
|
|
320
|
-
|
|
321
|
-
|
|
340
|
+
this.next(delta);
|
|
341
|
+
}, 1000 / fps);
|
|
342
|
+
this._pause = () => clearInterval(interval);
|
|
343
|
+
}
|
|
344
|
+
playWithRAF() {
|
|
345
|
+
let previousTime = null;
|
|
346
|
+
let rafId;
|
|
347
|
+
const frame = (currentTime) => {
|
|
348
|
+
if (previousTime === null) {
|
|
349
|
+
previousTime = currentTime;
|
|
350
|
+
}
|
|
351
|
+
const elapsed = currentTime - previousTime;
|
|
352
|
+
previousTime = currentTime;
|
|
353
|
+
let delta = elapsed * this.timeScale;
|
|
354
|
+
this.next(delta);
|
|
355
|
+
rafId = requestAnimFrame(frame);
|
|
356
|
+
};
|
|
357
|
+
rafId = requestAnimFrame(frame);
|
|
358
|
+
this._pause = () => cancelAnimFrame(rafId);
|
|
359
|
+
}
|
|
360
|
+
next(delta) {
|
|
361
|
+
if (this._currentTime + delta <= this._endPosition) {
|
|
362
|
+
this.currentTime += delta;
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
// overshot; perform restart/pause endAction
|
|
366
|
+
if (this.endAction.type == EndAction.restart) {
|
|
367
|
+
const loopRange = this.endAction.at.to(this._endPosition);
|
|
368
|
+
const loopLen = loopRange.duration;
|
|
369
|
+
if (loopLen <= 0) {
|
|
370
|
+
const target = Math.min(this._currentTime + delta, this._endPosition);
|
|
371
|
+
this.seek(target);
|
|
322
372
|
return;
|
|
323
373
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (loopLen <= 0) {
|
|
329
|
-
const target = Math.min(this._currentTime + delta, this._endPosition);
|
|
330
|
-
this.seek(target);
|
|
374
|
+
while (delta > 0) {
|
|
375
|
+
const distanceToEnd = this._endPosition - this._currentTime;
|
|
376
|
+
if (delta < distanceToEnd) {
|
|
377
|
+
this.seek(this._currentTime + delta);
|
|
331
378
|
return;
|
|
332
379
|
}
|
|
333
|
-
while (delta > 0) {
|
|
334
|
-
const distanceToEnd = this._endPosition - this._currentTime;
|
|
335
|
-
if (delta < distanceToEnd) {
|
|
336
|
-
this.seek(this._currentTime + delta);
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
this.seek(this._endPosition);
|
|
340
|
-
delta -= distanceToEnd;
|
|
341
|
-
this.seek(this.endAction.at);
|
|
342
|
-
}
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
if (this.endAction.type == EndAction.pause) {
|
|
346
380
|
this.seek(this._endPosition);
|
|
347
|
-
|
|
348
|
-
|
|
381
|
+
delta -= distanceToEnd;
|
|
382
|
+
this.seek(this.endAction.at);
|
|
349
383
|
}
|
|
350
|
-
|
|
351
|
-
}
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (this.endAction.type == EndAction.pause) {
|
|
387
|
+
this.seek(this._endPosition);
|
|
388
|
+
this.pause();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
// endaction must be "continue" or "wrap"
|
|
392
|
+
this.currentTime += delta;
|
|
352
393
|
}
|
|
353
394
|
/**
|
|
354
395
|
* Stops normal progression instigated by play()
|
|
@@ -357,10 +398,10 @@ export class Timeline {
|
|
|
357
398
|
*
|
|
358
399
|
*/
|
|
359
400
|
pause() {
|
|
360
|
-
if (this.
|
|
401
|
+
if (this._pause === null)
|
|
361
402
|
return;
|
|
362
|
-
|
|
363
|
-
this.
|
|
403
|
+
this._pause();
|
|
404
|
+
this._pause = null;
|
|
364
405
|
}
|
|
365
406
|
step(delta = 1) {
|
|
366
407
|
this.currentTime += delta * this.timeScale;
|
|
@@ -375,14 +416,36 @@ export class Timeline {
|
|
|
375
416
|
this.range(startPosition, duration).ease(easer).tween(from, to).apply(apply);
|
|
376
417
|
return this.createChainingInterface(startPosition + duration);
|
|
377
418
|
}
|
|
419
|
+
/**
|
|
420
|
+
* Adds an event at a specific position
|
|
421
|
+
*
|
|
422
|
+
* **Legacy API**
|
|
423
|
+
* @param position Position of the event
|
|
424
|
+
* @param action Handler for forward seeking
|
|
425
|
+
* @param reverse Handler for backward seeking
|
|
426
|
+
* @returns A tween/event chaining interface
|
|
427
|
+
*/
|
|
378
428
|
at(position, action, reverse) {
|
|
379
429
|
const point = typeof position == "number" ? this.point(position) : position;
|
|
380
|
-
if (
|
|
381
|
-
reverse
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
430
|
+
if (!action) {
|
|
431
|
+
if (reverse) {
|
|
432
|
+
if (reverse === true)
|
|
433
|
+
throw new Error("Invalid call");
|
|
434
|
+
point.reverseOnly.apply(reverse);
|
|
435
|
+
}
|
|
436
|
+
return this.createChainingInterface(point.position);
|
|
437
|
+
}
|
|
438
|
+
if (reverse) {
|
|
439
|
+
if (reverse === true) {
|
|
440
|
+
point.apply(action);
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
point.applyDirectional(action, reverse);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
point.forwardOnly.apply(action);
|
|
448
|
+
}
|
|
386
449
|
return this.createChainingInterface(point.position);
|
|
387
450
|
}
|
|
388
451
|
createChainingInterface(position) {
|