@xtia/timeline 1.1.5 → 1.1.7
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/internal/emitters.d.ts +19 -15
- package/internal/emitters.js +77 -71
- package/internal/point.d.ts +3 -1
- package/internal/point.js +27 -21
- package/internal/range.d.ts +12 -1
- package/internal/range.js +18 -10
- package/internal/timeline.d.ts +36 -4
- package/internal/timeline.js +71 -32
- package/package.json +1 -1
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
|
|
@@ -51,11 +50,13 @@ export declare class TimelinePoint extends Emitter<PointEvent> {
|
|
|
51
50
|
* @returns Listenable: emits forward-seeking point events
|
|
52
51
|
*/
|
|
53
52
|
forwardOnly(): Emitter<PointEvent>;
|
|
53
|
+
private _forwardOnly?;
|
|
54
54
|
/**
|
|
55
55
|
* Creates an emitter that only emits on backward-moving seeks
|
|
56
56
|
* @returns Listenable: emits backward-seeking point events
|
|
57
57
|
*/
|
|
58
58
|
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
|
|
@@ -48,25 +45,30 @@ export class TimelinePoint extends Emitter {
|
|
|
48
45
|
* @returns Listenable: emits forward-seeking point events
|
|
49
46
|
*/
|
|
50
47
|
forwardOnly() {
|
|
51
|
-
|
|
48
|
+
if (!this._forwardOnly)
|
|
49
|
+
this._forwardOnly = this.filter(1);
|
|
50
|
+
return this._forwardOnly;
|
|
52
51
|
}
|
|
53
52
|
/**
|
|
54
53
|
* Creates an emitter that only emits on backward-moving seeks
|
|
55
54
|
* @returns Listenable: emits backward-seeking point events
|
|
56
55
|
*/
|
|
57
56
|
reverseOnly() {
|
|
58
|
-
|
|
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,9 +27,18 @@ 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;
|
|
@@ -40,6 +49,9 @@ export declare class Timeline {
|
|
|
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
|
/**
|
|
@@ -48,8 +60,8 @@ export declare class Timeline {
|
|
|
48
60
|
apply(handler: () => void): UnsubscribeFunc;
|
|
49
61
|
private _progression;
|
|
50
62
|
/**
|
|
51
|
-
* Listenable: emits a progression value (0..1)
|
|
52
|
-
* position changes, and when the Timeline's total duration is extended
|
|
63
|
+
* Listenable: emits a progression value (0..1), representing progression through the entire Timeline,
|
|
64
|
+
* when the Timeline's internal position changes, and when the Timeline's total duration is extended
|
|
53
65
|
*/
|
|
54
66
|
get progression(): RangeProgression;
|
|
55
67
|
constructor();
|
|
@@ -102,10 +114,9 @@ export declare class Timeline {
|
|
|
102
114
|
*/
|
|
103
115
|
range(start: number | TimelinePoint, duration: number): TimelineRange;
|
|
104
116
|
/**
|
|
105
|
-
*
|
|
117
|
+
* Defines a range from position 0 to the Timeline's **current** final position
|
|
106
118
|
*/
|
|
107
119
|
range(): TimelineRange;
|
|
108
|
-
private getWrappedPosition;
|
|
109
120
|
/**
|
|
110
121
|
* Seeks the Timeline to a specified position, triggering in order any point and range subscriptions between its current and new positions
|
|
111
122
|
* @param toPosition
|
|
@@ -123,6 +134,7 @@ export declare class Timeline {
|
|
|
123
134
|
seek(toPosition: number | TimelinePoint, durationMs: number, easer?: Easer | keyof typeof easers): Promise<void>;
|
|
124
135
|
seek(toPosition: number | TimelinePoint, duration: Period, easer?: Easer | keyof typeof easers): Promise<void>;
|
|
125
136
|
private seekDirect;
|
|
137
|
+
private seekWrapped;
|
|
126
138
|
private seekPoints;
|
|
127
139
|
private seekRanges;
|
|
128
140
|
private sortEntries;
|
|
@@ -154,9 +166,29 @@ export declare class Timeline {
|
|
|
154
166
|
* @deprecated Use timeline.position += n
|
|
155
167
|
*/
|
|
156
168
|
step(delta: number): void;
|
|
169
|
+
/**
|
|
170
|
+
* Adds a tweening range to the Timeline
|
|
171
|
+
*
|
|
172
|
+
* **Legacy API**
|
|
173
|
+
* @param start Range's start position
|
|
174
|
+
* @param duration Tween's duration
|
|
175
|
+
* @param apply Function to apply interpolated values
|
|
176
|
+
* @param from Value at start of range
|
|
177
|
+
* @param to Value at end of range
|
|
178
|
+
* @param easer Optional easing function
|
|
179
|
+
*/
|
|
157
180
|
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
181
|
tween<T extends Tweenable>(start: number | TimelinePoint, end: TimelinePoint, // ease migration for tl.tween(0, tl.end, ...)
|
|
159
182
|
apply: (v: Widen<T>) => void, from: T, to: T, easer?: Easer | keyof typeof easers): ChainingInterface;
|
|
183
|
+
/**
|
|
184
|
+
* Adds an event at a specific position
|
|
185
|
+
*
|
|
186
|
+
* **Legacy API**
|
|
187
|
+
* @param position Position of the event
|
|
188
|
+
* @param action Handler for forward seeking
|
|
189
|
+
* @param reverse Handler for backward seeking
|
|
190
|
+
* @returns A tween/event chaining interface
|
|
191
|
+
*/
|
|
160
192
|
at(position: number | TimelinePoint, action?: () => void, reverse?: boolean | (() => void)): ChainingInterface;
|
|
161
193
|
private createChainingInterface;
|
|
162
194
|
/**
|
package/internal/timeline.js
CHANGED
|
@@ -16,13 +16,22 @@ export function animate(durationMs) {
|
|
|
16
16
|
: durationMs.asMilliseconds);
|
|
17
17
|
}
|
|
18
18
|
export class Timeline {
|
|
19
|
+
/**
|
|
20
|
+
* The current position of this Timeline's 'play head'
|
|
21
|
+
*/
|
|
19
22
|
get currentTime() { return this._currentTime; }
|
|
20
23
|
set currentTime(v) {
|
|
21
24
|
this.seek(v);
|
|
22
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Returns true if this Timeline is currently progressing via `play()`, otherwise false
|
|
28
|
+
*/
|
|
23
29
|
get isPlaying() {
|
|
24
30
|
return this.interval !== null;
|
|
25
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns a fixed point at the current end of the Timeline
|
|
34
|
+
*/
|
|
26
35
|
get end() {
|
|
27
36
|
return this.point(this._endPosition);
|
|
28
37
|
}
|
|
@@ -40,8 +49,8 @@ export class Timeline {
|
|
|
40
49
|
return this._frameEvents.listen(handler);
|
|
41
50
|
}
|
|
42
51
|
/**
|
|
43
|
-
* Listenable: emits a progression value (0..1)
|
|
44
|
-
* position changes, and when the Timeline's total duration is extended
|
|
52
|
+
* Listenable: emits a progression value (0..1), representing progression through the entire Timeline,
|
|
53
|
+
* when the Timeline's internal position changes, and when the Timeline's total duration is extended
|
|
45
54
|
*/
|
|
46
55
|
get progression() {
|
|
47
56
|
if (this._progression === null) {
|
|
@@ -68,6 +77,9 @@ export class Timeline {
|
|
|
68
77
|
this.currentSortDirection = 0;
|
|
69
78
|
this.smoothSeeker = null;
|
|
70
79
|
this.seeking = false;
|
|
80
|
+
/**
|
|
81
|
+
* A fixed point representing the start of this Timeline (position 0)
|
|
82
|
+
*/
|
|
71
83
|
this.start = this.point(0);
|
|
72
84
|
this._frameEvents = null;
|
|
73
85
|
this._progression = null;
|
|
@@ -161,22 +173,6 @@ export class Timeline {
|
|
|
161
173
|
const range = new TimelineRange(addHandler, this, startPoint, endPoint);
|
|
162
174
|
return range;
|
|
163
175
|
}
|
|
164
|
-
getWrappedPosition(n) {
|
|
165
|
-
if (this.endAction.type !== EndAction.wrap)
|
|
166
|
-
return n;
|
|
167
|
-
const wrapAt = this.endAction.at?.position ?? 0;
|
|
168
|
-
if (wrapAt == 0)
|
|
169
|
-
return n % this._endPosition;
|
|
170
|
-
if (n <= this._endPosition)
|
|
171
|
-
return n;
|
|
172
|
-
const loopStart = wrapAt;
|
|
173
|
-
const segment = this._endPosition - loopStart;
|
|
174
|
-
if (segment <= 0)
|
|
175
|
-
return Math.min(n, this._endPosition);
|
|
176
|
-
const overflow = n - this._endPosition;
|
|
177
|
-
const remainder = overflow % segment;
|
|
178
|
-
return loopStart + remainder;
|
|
179
|
-
}
|
|
180
176
|
seek(to, duration, easer) {
|
|
181
177
|
const durationMs = typeof duration == "object"
|
|
182
178
|
? duration.asMilliseconds
|
|
@@ -216,31 +212,65 @@ export class Timeline {
|
|
|
216
212
|
const fromPosition = this._currentTime;
|
|
217
213
|
if (toPosition === fromPosition)
|
|
218
214
|
return;
|
|
219
|
-
const loopingTo = this.getWrappedPosition(toPosition);
|
|
220
|
-
const loopingFrom = this.getWrappedPosition(fromPosition);
|
|
221
|
-
let virtualFrom = loopingFrom;
|
|
222
|
-
let virtualTo = loopingTo;
|
|
223
215
|
const direction = toPosition > fromPosition ? 1 : -1;
|
|
224
216
|
if (direction !== this.currentSortDirection)
|
|
225
217
|
this.sortEntries(direction);
|
|
226
|
-
if (direction === 1 && loopingTo < loopingFrom) {
|
|
227
|
-
virtualFrom = loopingFrom - this._endPosition;
|
|
228
|
-
}
|
|
229
|
-
else if (direction === -1 && loopingTo > loopingFrom) {
|
|
230
|
-
virtualFrom = loopingFrom + this._endPosition;
|
|
231
|
-
}
|
|
232
218
|
this.seeking = true;
|
|
233
|
-
this._currentTime = virtualFrom;
|
|
234
219
|
try {
|
|
235
|
-
|
|
236
|
-
this.
|
|
220
|
+
// use wrapping logic?
|
|
221
|
+
if (this.endAction.type === EndAction.wrap && (fromPosition > this._endPosition || toPosition > this._endPosition)) {
|
|
222
|
+
this.seekWrapped(toPosition);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
this.seekPoints(toPosition);
|
|
226
|
+
this.seekRanges(toPosition);
|
|
227
|
+
}
|
|
237
228
|
}
|
|
238
229
|
catch (e) {
|
|
239
230
|
this.pause();
|
|
240
231
|
throw e;
|
|
241
232
|
}
|
|
233
|
+
finally {
|
|
234
|
+
this.seeking = false;
|
|
235
|
+
}
|
|
236
|
+
this._currentTime = toPosition;
|
|
237
|
+
}
|
|
238
|
+
seekWrapped(toPosition) {
|
|
239
|
+
const fromPosition = this._currentTime;
|
|
240
|
+
const timelineEnd = this._endPosition;
|
|
241
|
+
const wrapAt = "at" in this.endAction ? this.endAction.at.position : 0;
|
|
242
|
+
const loopLen = timelineEnd - wrapAt;
|
|
243
|
+
const getWrappedPosition = (pos) => ((pos - wrapAt) % loopLen + loopLen) % loopLen + wrapAt;
|
|
244
|
+
const realDelta = toPosition - fromPosition;
|
|
245
|
+
const direction = realDelta >= 0 ? 1 : -1;
|
|
246
|
+
let remaining = Math.abs(realDelta);
|
|
247
|
+
let virtualFrom = getWrappedPosition(fromPosition);
|
|
248
|
+
while (remaining > 0) {
|
|
249
|
+
let virtualTo;
|
|
250
|
+
if (direction > 0) {
|
|
251
|
+
const wrapSize = timelineEnd - virtualFrom;
|
|
252
|
+
virtualTo = remaining <= wrapSize
|
|
253
|
+
? virtualFrom + remaining
|
|
254
|
+
: timelineEnd;
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
const wrapSize = virtualFrom - wrapAt;
|
|
258
|
+
virtualTo = remaining <= wrapSize
|
|
259
|
+
? virtualFrom - remaining
|
|
260
|
+
: wrapAt;
|
|
261
|
+
}
|
|
262
|
+
this._currentTime = virtualFrom;
|
|
263
|
+
this.seekPoints(virtualTo);
|
|
264
|
+
remaining -= Math.abs(virtualTo - virtualFrom);
|
|
265
|
+
if (remaining > 0) {
|
|
266
|
+
virtualFrom = direction > 0 ? wrapAt : timelineEnd;
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
virtualFrom = virtualTo;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
this.seekRanges(getWrappedPosition(toPosition));
|
|
242
273
|
this._currentTime = toPosition;
|
|
243
|
-
this.seeking = false;
|
|
244
274
|
}
|
|
245
275
|
seekPoints(to) {
|
|
246
276
|
const from = this._currentTime;
|
|
@@ -357,6 +387,15 @@ export class Timeline {
|
|
|
357
387
|
this.range(startPosition, duration).ease(easer).tween(from, to).apply(apply);
|
|
358
388
|
return this.createChainingInterface(startPosition + duration);
|
|
359
389
|
}
|
|
390
|
+
/**
|
|
391
|
+
* Adds an event at a specific position
|
|
392
|
+
*
|
|
393
|
+
* **Legacy API**
|
|
394
|
+
* @param position Position of the event
|
|
395
|
+
* @param action Handler for forward seeking
|
|
396
|
+
* @param reverse Handler for backward seeking
|
|
397
|
+
* @returns A tween/event chaining interface
|
|
398
|
+
*/
|
|
360
399
|
at(position, action, reverse) {
|
|
361
400
|
const point = typeof position == "number" ? this.point(position) : position;
|
|
362
401
|
if (reverse === true)
|