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