@xtia/timeline 1.0.6 → 1.0.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 +464 -13
- package/index.js +5 -8
- package/internal/easing.js +1 -4
- package/internal/emitters.d.ts +21 -30
- package/internal/emitters.js +241 -100
- package/internal/point.d.ts +15 -6
- package/internal/point.js +41 -2
- package/internal/range.d.ts +14 -4
- package/internal/range.js +96 -2
- package/internal/timeline.d.ts +3 -3
- package/internal/timeline.js +11 -75
- package/internal/tween.d.ts +3 -0
- package/internal/tween.js +3 -6
- package/internal/utils.d.ts +0 -2
- package/internal/utils.js +1 -13
- package/package.json +1 -1
package/internal/emitters.d.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { Easer, easers } from "./easing";
|
|
2
|
-
import {
|
|
3
|
-
import { OptionalIfKeyIn } from "./utils";
|
|
4
|
-
/** @internal */
|
|
5
|
-
export declare function createEmitter<T>(listen: ListenFunc<T>): Emitter<T>;
|
|
6
|
-
/** @internal */
|
|
7
|
-
export declare function createEmitter<T, API extends object>(onListen: ListenFunc<T>, api: OptionalIfKeyIn<API, Emitter<T>>): Emitter<T> & API;
|
|
8
|
-
/** @internal */
|
|
9
|
-
export declare function createProgressEmitter<API extends object>(listen: ListenFunc<number>, api: Omit<API, keyof RangeProgression>): RangeProgression & API;
|
|
10
|
-
/** @internal */
|
|
11
|
-
export declare function createProgressEmitter(listen: ListenFunc<number>): RangeProgression;
|
|
2
|
+
import { BlendableWith, Tweenable } from "./tween";
|
|
12
3
|
type Handler<T> = (value: T) => void;
|
|
13
|
-
type ListenFunc<T> = (handler: Handler<T>) => UnsubscribeFunc;
|
|
4
|
+
export type ListenFunc<T> = (handler: Handler<T>) => UnsubscribeFunc;
|
|
14
5
|
export type UnsubscribeFunc = () => void;
|
|
15
|
-
export
|
|
6
|
+
export declare class Emitter<T> {
|
|
7
|
+
protected onListen: ListenFunc<T>;
|
|
8
|
+
protected constructor(onListen: ListenFunc<T>);
|
|
9
|
+
/**
|
|
10
|
+
* Used by tap() to create a clone of an Emitter with a redirected onListen
|
|
11
|
+
* Should be overridden in all Emitter subclasses
|
|
12
|
+
* @see {@link TimelineRange.redirect}
|
|
13
|
+
* @param listen
|
|
14
|
+
* @returns {this}
|
|
15
|
+
*/
|
|
16
|
+
protected redirect: (listen: ListenFunc<T>) => Emitter<T>;
|
|
16
17
|
/**
|
|
17
18
|
* Registers a function to receive emitted values
|
|
18
19
|
* @param handler
|
|
@@ -50,7 +51,7 @@ export interface Emitter<T> {
|
|
|
50
51
|
* @param cb A function to be called as a side effect for each value emitted by the parent emitter.
|
|
51
52
|
* @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
|
|
52
53
|
*/
|
|
53
|
-
tap(cb: Handler<T>):
|
|
54
|
+
tap(cb: Handler<T>): this;
|
|
54
55
|
/**
|
|
55
56
|
* Immediately passes this emitter to a callback and returns this emitter
|
|
56
57
|
*
|
|
@@ -69,9 +70,10 @@ export interface Emitter<T> {
|
|
|
69
70
|
* ```
|
|
70
71
|
* @param cb
|
|
71
72
|
*/
|
|
72
|
-
fork(cb: (branch:
|
|
73
|
+
fork(cb: (branch: this) => void): this;
|
|
73
74
|
}
|
|
74
|
-
export
|
|
75
|
+
export declare class RangeProgression extends Emitter<number> {
|
|
76
|
+
protected redirect: (listen: ListenFunc<number>) => RangeProgression;
|
|
75
77
|
/**
|
|
76
78
|
* Creates a chainable progress emitter that applies an easing function to its parent's emitted values
|
|
77
79
|
*
|
|
@@ -79,6 +81,7 @@ export interface RangeProgression extends Emitter<number> {
|
|
|
79
81
|
* @returns Listenable: emits eased progression values
|
|
80
82
|
*/
|
|
81
83
|
ease(easer?: Easer | keyof typeof easers): RangeProgression;
|
|
84
|
+
ease(easer?: undefined): RangeProgression;
|
|
82
85
|
/**
|
|
83
86
|
* Creates a chainable emitter that interpolates two given values by progression emitted by its parent
|
|
84
87
|
*
|
|
@@ -119,7 +122,8 @@ export interface RangeProgression extends Emitter<number> {
|
|
|
119
122
|
* @param to Value to interpolate to
|
|
120
123
|
* @returns Listenable: emits interpolated values
|
|
121
124
|
*/
|
|
122
|
-
tween<T extends
|
|
125
|
+
tween<T extends Tweenable>(from: T, to: T): Emitter<T>;
|
|
126
|
+
tween<T extends BlendableWith<T, R>, R>(from: T, to: R): Emitter<T>;
|
|
123
127
|
/**
|
|
124
128
|
* Creates a chainable progress emitter that quantises progress, as emitted by its parent, to the nearest of `steps` discrete values.
|
|
125
129
|
*
|
|
@@ -161,18 +165,6 @@ export interface RangeProgression extends Emitter<number> {
|
|
|
161
165
|
* @returns Listenable: emits scaled and repeating values
|
|
162
166
|
*/
|
|
163
167
|
repeat(count: number): RangeProgression;
|
|
164
|
-
/**
|
|
165
|
-
* Creates a chainable progress emitter that mirrors emissions from the parent emitter, invoking the provided callback `cb` as a side effect for each emission.
|
|
166
|
-
*
|
|
167
|
-
* The callback `cb` is called exactly once per parent emission, regardless of how many listeners are attached to the returned emitter.
|
|
168
|
-
* All listeners attached to the returned emitter receive the same values as the parent emitter.
|
|
169
|
-
*
|
|
170
|
-
* *Note*, the side effect `cb` is only invoked when there is at least one listener attached to the returned emitter
|
|
171
|
-
*
|
|
172
|
-
* @param cb A function to be called as a side effect for each value emitted by the parent emitter.
|
|
173
|
-
* @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
|
|
174
|
-
*/
|
|
175
|
-
tap(cb: (value: number) => void): RangeProgression;
|
|
176
168
|
/**
|
|
177
169
|
* Creates a chainable progress emitter that selectively forwards emissions along the chain
|
|
178
170
|
* @param check Function that takes an emitted value and returns true if the emission should be forwarded along the chain
|
|
@@ -191,7 +183,7 @@ export interface RangeProgression extends Emitter<number> {
|
|
|
191
183
|
* 1
|
|
192
184
|
* | /
|
|
193
185
|
* o| /
|
|
194
|
-
* u|/
|
|
186
|
+
* u|/ __ delta=.5
|
|
195
187
|
* t| /
|
|
196
188
|
* | /
|
|
197
189
|
* |___/__
|
|
@@ -202,6 +194,5 @@ export interface RangeProgression extends Emitter<number> {
|
|
|
202
194
|
* @returns Listenable: emits offset values
|
|
203
195
|
*/
|
|
204
196
|
offset(delta: number): RangeProgression;
|
|
205
|
-
fork(cb: (branch: RangeProgression) => void): RangeProgression;
|
|
206
197
|
}
|
|
207
198
|
export {};
|
package/internal/emitters.js
CHANGED
|
@@ -1,108 +1,249 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import { easers } from "./easing";
|
|
2
|
+
import { tweenValue } from "./tween";
|
|
3
|
+
import { clamp } from "./utils";
|
|
4
|
+
export class Emitter {
|
|
5
|
+
constructor(onListen) {
|
|
6
|
+
this.onListen = onListen;
|
|
7
|
+
/**
|
|
8
|
+
* Used by tap() to create a clone of an Emitter with a redirected onListen
|
|
9
|
+
* Should be overridden in all Emitter subclasses
|
|
10
|
+
* @see {@link TimelineRange.redirect}
|
|
11
|
+
* @param listen
|
|
12
|
+
* @returns {this}
|
|
13
|
+
*/
|
|
14
|
+
this.redirect = (listen) => new Emitter(listen);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Registers a function to receive emitted values
|
|
18
|
+
* @param handler
|
|
19
|
+
* @returns A function to deregister the handler
|
|
20
|
+
*/
|
|
21
|
+
listen(handler) {
|
|
22
|
+
return this.onListen((value) => {
|
|
12
23
|
handler(value);
|
|
13
|
-
})
|
|
14
|
-
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Creates a chainable emitter that applies arbitrary transformation to values emitted by its parent
|
|
28
|
+
* @param mapFunc
|
|
29
|
+
* @returns Listenable: emits transformed values
|
|
30
|
+
*/
|
|
31
|
+
map(mapFunc) {
|
|
32
|
+
return new Emitter(handler => this.onListen((value) => {
|
|
15
33
|
handler(mapFunc(value));
|
|
16
|
-
}))
|
|
17
|
-
|
|
18
|
-
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Creates a chainable emitter that selectively forwards emissions along the chain
|
|
38
|
+
* @param check Function that takes an emitted value and returns true if the emission should be forwarded along the chain
|
|
39
|
+
* @returns Listenable: emits values that pass the filter
|
|
40
|
+
*/
|
|
41
|
+
filter(check) {
|
|
42
|
+
return new Emitter(handler => this.onListen((value) => {
|
|
43
|
+
if (check(value))
|
|
19
44
|
handler(value);
|
|
20
|
-
}))
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Creates a chainable emitter that discards emitted values that are the same as the last value emitted by the new emitter
|
|
49
|
+
* @param compare Optional function that takes the previous and next values and returns true if they should be considered equal
|
|
50
|
+
*
|
|
51
|
+
* If no `compare` function is provided, values will be compared via `===`
|
|
52
|
+
* @returns Listenable: emits non-repeating values
|
|
53
|
+
*/
|
|
54
|
+
dedupe(compare) {
|
|
55
|
+
let previous = null;
|
|
56
|
+
return new Emitter(handler => {
|
|
57
|
+
const filteredHandler = (value) => {
|
|
58
|
+
if (!previous || (compare
|
|
59
|
+
? !compare(previous.value, value)
|
|
60
|
+
: (previous.value !== value))) {
|
|
61
|
+
handler(value);
|
|
62
|
+
previous = { value };
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
return this.onListen(filteredHandler);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Creates a chainable emitter that mirrors emissions from the parent emitter, invoking the provided callback `cb` as a side effect for each emission.
|
|
70
|
+
*
|
|
71
|
+
* The callback `cb` is called exactly once per parent emission, regardless of how many listeners are attached to the returned emitter.
|
|
72
|
+
* All listeners attached to the returned emitter receive the same values as the parent emitter.
|
|
73
|
+
*
|
|
74
|
+
* *Note*, the side effect `cb` is only invoked when there is at least one listener attached to the returned emitter
|
|
75
|
+
*
|
|
76
|
+
* @param cb A function to be called as a side effect for each value emitted by the parent emitter.
|
|
77
|
+
* @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
|
|
78
|
+
*/
|
|
79
|
+
tap(cb) {
|
|
80
|
+
const listeners = [];
|
|
81
|
+
let parentUnsubscribe = null;
|
|
82
|
+
const tappedListen = (handler) => {
|
|
83
|
+
listeners.push(handler);
|
|
84
|
+
if (listeners.length === 1) {
|
|
85
|
+
parentUnsubscribe = this.onListen(value => {
|
|
86
|
+
cb(value);
|
|
87
|
+
listeners.slice().forEach(fn => fn(value));
|
|
88
|
+
});
|
|
59
89
|
}
|
|
60
|
-
return
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
90
|
+
return () => {
|
|
91
|
+
const idx = listeners.indexOf(handler);
|
|
92
|
+
listeners.splice(idx, 1);
|
|
93
|
+
if (listeners.length === 0 && parentUnsubscribe) {
|
|
94
|
+
parentUnsubscribe();
|
|
95
|
+
parentUnsubscribe = null;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
return this.redirect(tappedListen);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Immediately passes this emitter to a callback and returns this emitter
|
|
103
|
+
*
|
|
104
|
+
* Allows branching without breaking a composition chain
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* range
|
|
109
|
+
* .tween("0%", "100%")
|
|
110
|
+
* .fork(branch => {
|
|
111
|
+
* branch
|
|
112
|
+
* .map(s => `Loading: ${s}`)
|
|
113
|
+
* .listen(s => document.title = s)
|
|
114
|
+
* })
|
|
115
|
+
* .listen(v => progressBar.style.width = v);
|
|
116
|
+
* ```
|
|
117
|
+
* @param cb
|
|
118
|
+
*/
|
|
119
|
+
fork(cb) {
|
|
120
|
+
cb(this);
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export class RangeProgression extends Emitter {
|
|
125
|
+
constructor() {
|
|
126
|
+
super(...arguments);
|
|
127
|
+
this.redirect = (listen) => new RangeProgression(listen);
|
|
128
|
+
}
|
|
129
|
+
ease(easer) {
|
|
130
|
+
if (!easer)
|
|
131
|
+
return this;
|
|
132
|
+
const easerFunc = typeof easer == "string"
|
|
133
|
+
? easers[easer]
|
|
134
|
+
: easer;
|
|
135
|
+
return new RangeProgression(easer ? (handler => this.onListen((progress) => {
|
|
136
|
+
handler(easerFunc(progress));
|
|
137
|
+
})) : h => this.onListen(h));
|
|
138
|
+
}
|
|
139
|
+
tween(from, to) {
|
|
140
|
+
return new Emitter(handler => this.onListen(progress => handler(tweenValue(from, to, progress))));
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Creates a chainable progress emitter that quantises progress, as emitted by its parent, to the nearest of `steps` discrete values.
|
|
144
|
+
*
|
|
145
|
+
* @param steps – positive integer (e.g. 10 → 0, .1, .2 … 1)
|
|
146
|
+
* @throws RangeError if steps is not a positive integer
|
|
147
|
+
* @returns Listenable: emits quantised progression values
|
|
148
|
+
*/
|
|
149
|
+
snap(steps) {
|
|
150
|
+
if (!Number.isInteger(steps) || steps <= 0) {
|
|
151
|
+
throw new RangeError('snap(steps) requires a positive integer');
|
|
152
|
+
}
|
|
153
|
+
return new RangeProgression(handler => this.onListen(progress => {
|
|
154
|
+
const snapped = Math.round(progress * steps) / steps;
|
|
155
|
+
handler(clamp(snapped, 0, 1));
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Creates a chainable progress emitter that emits `1` when the incoming progress value is greater‑than‑or‑equal to the supplied `threshold`, otherwise emits `0`
|
|
160
|
+
*
|
|
161
|
+
* @param threshold the cut‑off value
|
|
162
|
+
* @returns Listenable: emits 0 or 1 after comparing progress with a threshold
|
|
163
|
+
*/
|
|
164
|
+
threshold(threshold) {
|
|
165
|
+
return new RangeProgression(handler => this.onListen(progress => {
|
|
66
166
|
handler(progress >= threshold ? 1 : 0);
|
|
67
|
-
}))
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Creates a chainable progress emitter that clamps incoming values
|
|
171
|
+
* @param min default 0
|
|
172
|
+
* @param max default 1
|
|
173
|
+
* @returns Listenable: emits clamped progression values
|
|
174
|
+
*/
|
|
175
|
+
clamp(min = 0, max = 1) {
|
|
176
|
+
return new RangeProgression(handler => this.onListen(progress => handler(clamp(progress, min, max))));
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Creates a chainable progress emitter that maps incoming values to a repeating linear scale
|
|
180
|
+
*
|
|
181
|
+
* ```plain
|
|
182
|
+
* count=2
|
|
183
|
+
* 1
|
|
184
|
+
* | / /
|
|
185
|
+
* o| / /
|
|
186
|
+
* u| / /
|
|
187
|
+
* t| / /
|
|
188
|
+
* | / /
|
|
189
|
+
* |/_____/_____
|
|
190
|
+
* 0 in 1
|
|
191
|
+
* ```
|
|
192
|
+
*
|
|
193
|
+
* @param count Number of repetitions
|
|
194
|
+
* @returns Listenable: emits scaled and repeating values
|
|
195
|
+
*/
|
|
196
|
+
repeat(count) {
|
|
197
|
+
count = Math.max(0, count);
|
|
198
|
+
return new RangeProgression(handler => this.onListen(progress => {
|
|
199
|
+
const out = (progress * count) % 1;
|
|
200
|
+
handler(out);
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Creates a chainable progress emitter that selectively forwards emissions along the chain
|
|
205
|
+
* @param check Function that takes an emitted value and returns true if the emission should be forwarded along the chain
|
|
206
|
+
* @returns Listenable: emits values that pass the filter
|
|
207
|
+
*/
|
|
208
|
+
filter(check) {
|
|
209
|
+
return new RangeProgression(handler => this.onListen((value) => {
|
|
210
|
+
if (check(value))
|
|
79
211
|
handler(value);
|
|
80
|
-
}))
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
cb(value);
|
|
95
|
-
listeners.slice().forEach(fn => fn(value));
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Creates a chainable progress emitter that discards emitted values that are the same as the last value emitted by the new emitter
|
|
216
|
+
* @returns Listenable: emits non-repeating values
|
|
217
|
+
*/
|
|
218
|
+
dedupe() {
|
|
219
|
+
let previous = null;
|
|
220
|
+
return new RangeProgression(handler => {
|
|
221
|
+
return this.onListen((value) => {
|
|
222
|
+
if (!previous === null || previous !== value) {
|
|
223
|
+
handler(value);
|
|
224
|
+
previous = value;
|
|
225
|
+
}
|
|
96
226
|
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Creates a chainable progress emitter that offsets its parent's values by the given delta, wrapping at 1
|
|
231
|
+
*
|
|
232
|
+
* ```plain
|
|
233
|
+
* 1
|
|
234
|
+
* | /
|
|
235
|
+
* o| /
|
|
236
|
+
* u|/ __ delta=.5
|
|
237
|
+
* t| /
|
|
238
|
+
* | /
|
|
239
|
+
* |___/__
|
|
240
|
+
* 0 in 1
|
|
241
|
+
* ```
|
|
242
|
+
*
|
|
243
|
+
* @param delta
|
|
244
|
+
* @returns Listenable: emits offset values
|
|
245
|
+
*/
|
|
246
|
+
offset(delta) {
|
|
247
|
+
return new RangeProgression(handler => this.onListen(value => handler((value + delta) % 1)));
|
|
248
|
+
}
|
|
108
249
|
}
|
package/internal/point.d.ts
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
|
-
import { Emitter } from "./emitters";
|
|
1
|
+
import { Emitter, ListenFunc } from "./emitters";
|
|
2
2
|
import { TimelineRange } from "./range";
|
|
3
|
+
import { Timeline } from "./timeline";
|
|
3
4
|
export type PointEvent = {
|
|
4
5
|
direction: -1 | 1;
|
|
5
6
|
};
|
|
6
|
-
export
|
|
7
|
+
export declare class TimelinePoint extends Emitter<PointEvent> {
|
|
8
|
+
private timeline;
|
|
9
|
+
/**
|
|
10
|
+
* The point's absolute position on the Timeline
|
|
11
|
+
*/
|
|
12
|
+
readonly position: number;
|
|
13
|
+
/** @internal Manual construction of TimelinePoint is outside of the API contract and subject to undocumented change */
|
|
14
|
+
constructor(onListen: ListenFunc<PointEvent>, timeline: Timeline,
|
|
15
|
+
/**
|
|
16
|
+
* The point's absolute position on the Timeline
|
|
17
|
+
*/
|
|
18
|
+
position: number);
|
|
19
|
+
protected redirect: (listen: ListenFunc<PointEvent>) => TimelinePoint;
|
|
7
20
|
/**
|
|
8
21
|
* Creates a range on the Timeline, with a given duration, starting at this point
|
|
9
22
|
* @param duration
|
|
@@ -22,8 +35,4 @@ export interface TimelinePoint extends Emitter<PointEvent> {
|
|
|
22
35
|
* @returns Listenable: emits a PointEvent when the point is reached or passed by a Timeline seek
|
|
23
36
|
*/
|
|
24
37
|
delta(timeOffset: number): TimelinePoint;
|
|
25
|
-
/**
|
|
26
|
-
* The point's absolute position on the Timeline
|
|
27
|
-
*/
|
|
28
|
-
readonly position: number;
|
|
29
38
|
}
|
package/internal/point.js
CHANGED
|
@@ -1,2 +1,41 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { Emitter } from "./emitters";
|
|
2
|
+
export class TimelinePoint extends Emitter {
|
|
3
|
+
/** @internal Manual construction of TimelinePoint is outside of the API contract and subject to undocumented change */
|
|
4
|
+
constructor(onListen, timeline,
|
|
5
|
+
/**
|
|
6
|
+
* The point's absolute position on the Timeline
|
|
7
|
+
*/
|
|
8
|
+
position) {
|
|
9
|
+
super(onListen);
|
|
10
|
+
this.timeline = timeline;
|
|
11
|
+
this.position = position;
|
|
12
|
+
this.redirect = (listen) => new TimelinePoint(listen, this.timeline, this.position);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Creates a range on the Timeline, with a given duration, starting at this point
|
|
16
|
+
* @param duration
|
|
17
|
+
* @returns Listenable: emits normalised (0..1) range progression
|
|
18
|
+
*/
|
|
19
|
+
range(duration) {
|
|
20
|
+
return this.timeline.range(this.position, duration);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Creates a range on the Timeline, with a given end point, starting at this point
|
|
24
|
+
* @param endPoint
|
|
25
|
+
* @returns Listenable: emits normalised (0..1) range progression
|
|
26
|
+
*/
|
|
27
|
+
to(endPoint) {
|
|
28
|
+
const endPosition = typeof endPoint == "number"
|
|
29
|
+
? endPoint
|
|
30
|
+
: endPoint.position;
|
|
31
|
+
return this.timeline.range(this.position, endPosition - this.position);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Creates a point on the Timeline at an offset position from this one
|
|
35
|
+
* @param timeOffset
|
|
36
|
+
* @returns Listenable: emits a PointEvent when the point is reached or passed by a Timeline seek
|
|
37
|
+
*/
|
|
38
|
+
delta(timeOffset) {
|
|
39
|
+
return this.timeline.point(this.position + timeOffset);
|
|
40
|
+
}
|
|
41
|
+
}
|
package/internal/range.d.ts
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import { Easer, easers } from "./easing";
|
|
2
|
-
import { RangeProgression } from "./emitters";
|
|
2
|
+
import { ListenFunc, RangeProgression } from "./emitters";
|
|
3
3
|
import { TimelinePoint } from "./point";
|
|
4
|
-
|
|
4
|
+
import { Timeline } from "./timeline";
|
|
5
|
+
export declare class TimelineRange extends RangeProgression {
|
|
6
|
+
private timeline;
|
|
7
|
+
private startPosition;
|
|
8
|
+
/** The duration of this range */
|
|
9
|
+
readonly duration: number;
|
|
10
|
+
private endPosition;
|
|
11
|
+
/** @internal Manual construction of RangeProgression is outside of the API contract and subject to undocumented change */
|
|
12
|
+
constructor(onListen: ListenFunc<number>, timeline: Timeline, startPosition: number,
|
|
13
|
+
/** The duration of this range */
|
|
14
|
+
duration: number);
|
|
15
|
+
protected redirect: (listen: ListenFunc<number>) => TimelineRange;
|
|
5
16
|
/**
|
|
6
17
|
* Creates two ranges by seperating one at a given point
|
|
7
18
|
* @param position Point of separation, relative to the range's start - if omitted, the range will be separated halfway
|
|
@@ -41,10 +52,9 @@ export interface TimelineRange extends RangeProgression {
|
|
|
41
52
|
* @returns true if the provided point is within the range
|
|
42
53
|
*/
|
|
43
54
|
contains(point: TimelinePoint): boolean;
|
|
55
|
+
contains(range: TimelineRange): boolean;
|
|
44
56
|
/** The point on the Timeline at which this range begins */
|
|
45
57
|
readonly start: TimelinePoint;
|
|
46
58
|
/** The point on the Timeline at which this range ends */
|
|
47
59
|
readonly end: TimelinePoint;
|
|
48
|
-
/** The duration of this range */
|
|
49
|
-
readonly duration: number;
|
|
50
60
|
}
|
package/internal/range.js
CHANGED
|
@@ -1,2 +1,96 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { RangeProgression } from "./emitters";
|
|
2
|
+
import { TimelinePoint } from "./point";
|
|
3
|
+
import { clamp } from "./utils";
|
|
4
|
+
export class TimelineRange extends RangeProgression {
|
|
5
|
+
/** @internal Manual construction of RangeProgression is outside of the API contract and subject to undocumented change */
|
|
6
|
+
constructor(onListen, timeline, startPosition,
|
|
7
|
+
/** The duration of this range */
|
|
8
|
+
duration) {
|
|
9
|
+
super(onListen);
|
|
10
|
+
this.timeline = timeline;
|
|
11
|
+
this.startPosition = startPosition;
|
|
12
|
+
this.duration = duration;
|
|
13
|
+
this.redirect = (listen) => new TimelineRange(listen, this.timeline, this.startPosition, this.duration);
|
|
14
|
+
this.start = timeline.point(startPosition);
|
|
15
|
+
this.endPosition = startPosition + duration;
|
|
16
|
+
this.end = timeline.point(this.endPosition);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Creates two ranges by seperating one at a given point
|
|
20
|
+
* @param position Point of separation, relative to the range's start - if omitted, the range will be separated halfway
|
|
21
|
+
*
|
|
22
|
+
* Must be greater than 0 and less than the range's duration
|
|
23
|
+
* @returns Tuple of two ranges
|
|
24
|
+
*/
|
|
25
|
+
bisect(position = this.duration / 2) {
|
|
26
|
+
return [
|
|
27
|
+
this.timeline.range(position, this.startPosition),
|
|
28
|
+
this.timeline.range(position + this.startPosition, this.duration - this.startPosition),
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Creates a series of evenly-spread points across the range, excluding the range's start and end
|
|
33
|
+
* @param count Number of Points to return
|
|
34
|
+
* @returns Array(count) of points
|
|
35
|
+
*/
|
|
36
|
+
spread(count) {
|
|
37
|
+
const delta = this.duration / (count + 1);
|
|
38
|
+
return [
|
|
39
|
+
...Array(count).fill(0).map((_, idx) => this.timeline.point(idx * delta + this.startPosition + delta))
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Progresses the Timeline across the range
|
|
44
|
+
* @param easer
|
|
45
|
+
*/
|
|
46
|
+
play(easer) {
|
|
47
|
+
this.timeline.pause();
|
|
48
|
+
this.timeline.currentTime = this.startPosition;
|
|
49
|
+
return this.timeline.seek(this.startPosition + this.duration, this.duration, easer);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Creates a new range representing a direct expansion of this one
|
|
53
|
+
* @param delta Amount to grow by (in time units)
|
|
54
|
+
* @param anchor Normalised position at which to expand (0 being the start, expanding right, 1 being the end, expanding left, 0.5 expanding evenly)
|
|
55
|
+
* @returns Listenable: this range will emit a progression value (0..1) when a `seek()` passes or intersects it
|
|
56
|
+
*/
|
|
57
|
+
grow(delta, anchor = 0) {
|
|
58
|
+
const clampedAnchor = clamp(anchor, 0, 1);
|
|
59
|
+
const leftDelta = -delta * (1 - clampedAnchor);
|
|
60
|
+
const rightDelta = delta * clampedAnchor;
|
|
61
|
+
const newStart = this.startPosition + leftDelta;
|
|
62
|
+
const newEnd = this.startPosition + this.duration + rightDelta;
|
|
63
|
+
if (newEnd < newStart) {
|
|
64
|
+
const mid = (newStart + newEnd) / 2;
|
|
65
|
+
return this.timeline.range(mid, 0);
|
|
66
|
+
}
|
|
67
|
+
return this.timeline.range(newStart, newEnd - newStart);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Creates a new range representing a multiplicative expansion of this one
|
|
71
|
+
* @param factor Size multiplier
|
|
72
|
+
* @param anchor Normalised position at which to expand (0 being the start, expanding right, 1 being the end, expanding left, 0.5 expanding evenly)
|
|
73
|
+
* @returns Listenable: this range will emit a progression value (0..1) when a `seek()` passes or intersects it
|
|
74
|
+
*/
|
|
75
|
+
scale(factor, anchor = 0) {
|
|
76
|
+
if (factor <= 0) {
|
|
77
|
+
throw new RangeError('scale factor must be > 0');
|
|
78
|
+
}
|
|
79
|
+
const clampedAnchor = clamp(anchor, 0, 1);
|
|
80
|
+
const oldLen = this.endPosition - this.startPosition;
|
|
81
|
+
const pivot = this.startPosition + oldLen * clampedAnchor;
|
|
82
|
+
const newStart = pivot - (pivot - this.startPosition) * factor;
|
|
83
|
+
const newEnd = pivot + (this.endPosition - pivot) * factor;
|
|
84
|
+
if (newEnd < newStart) {
|
|
85
|
+
const mid = (newStart + newEnd) / 2;
|
|
86
|
+
return this.timeline.range(mid, 0);
|
|
87
|
+
}
|
|
88
|
+
return this.timeline.range(newStart, newEnd - newStart);
|
|
89
|
+
}
|
|
90
|
+
contains(target) {
|
|
91
|
+
const [targetStart, targetEnd] = target instanceof TimelinePoint
|
|
92
|
+
? [target.position, target.position]
|
|
93
|
+
: [target.startPosition, target.startPosition + target.duration];
|
|
94
|
+
return targetStart >= this.startPosition && targetEnd < this.endPosition;
|
|
95
|
+
}
|
|
96
|
+
}
|