@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.
@@ -1,18 +1,19 @@
1
1
  import { Easer, easers } from "./easing";
2
- import { Blendable } from "./tween";
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 interface Emitter<T> {
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>): Emitter<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: Emitter<T>) => void): Emitter<T>;
73
+ fork(cb: (branch: this) => void): this;
73
74
  }
74
- export interface RangeProgression extends Emitter<number> {
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 Blendable | number[]>(from: T, to: T): Emitter<T>;
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|/ __ delta=.5
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 {};
@@ -1,108 +1,249 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createEmitter = createEmitter;
4
- exports.createProgressEmitter = createProgressEmitter;
5
- const easing_1 = require("./easing");
6
- const tween_1 = require("./tween");
7
- const utils_1 = require("./utils");
8
- /** @internal */
9
- function createEmitter(listen, api) {
10
- const methods = {
11
- listen: (handler) => listen((value) => {
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
- map: (mapFunc) => createEmitter(handler => listen((value) => {
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
- filter: (filterFunc) => createEmitter(handler => listen((value) => {
18
- if (filterFunc(value))
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
- dedupe: (compare) => {
22
- let previous = null;
23
- return createEmitter(handler => {
24
- const filteredHandler = (value) => {
25
- if (!previous || (compare
26
- ? !compare(previous.value, value)
27
- : (previous.value !== value))) {
28
- handler(value);
29
- previous = { value };
30
- }
31
- };
32
- return listen(filteredHandler);
33
- }, api ?? {});
34
- },
35
- tap: (cb) => createTap((createEmitter), listen, cb),
36
- fork: (cb) => {
37
- cb(emitter);
38
- return emitter;
39
- }
40
- };
41
- const emitter = (0, utils_1.prototypify)(methods, api ?? {});
42
- return emitter;
43
- }
44
- /** @internal */
45
- function createProgressEmitter(listen, api) {
46
- const methods = {
47
- ease: (easer) => {
48
- const easerFunc = typeof easer == "string"
49
- ? easing_1.easers[easer]
50
- : easer;
51
- return createProgressEmitter(easer ? (handler => listen((progress) => {
52
- handler(easerFunc(progress));
53
- })) : listen);
54
- },
55
- tween: (from, to) => createEmitter(handler => listen(progress => handler((0, tween_1.tweenValue)(from, to, progress)))),
56
- snap: (steps) => {
57
- if (!Number.isInteger(steps) || steps <= 0) {
58
- throw new RangeError('snap(steps) requires a positive integer');
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 createProgressEmitter(handler => listen(progress => {
61
- const snapped = Math.round(progress * steps) / steps;
62
- handler((0, utils_1.clamp)(snapped, 0, 1));
63
- }));
64
- },
65
- threshold: (threshold) => createProgressEmitter(handler => listen(progress => {
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
- clamp: (min = 0, max = 1) => createProgressEmitter(handler => listen(progress => handler((0, utils_1.clamp)(progress, min, max)))),
69
- repeat: (repetitions) => {
70
- repetitions = Math.max(0, repetitions);
71
- return createProgressEmitter(handler => listen(progress => {
72
- const out = (progress * repetitions) % 1;
73
- handler(out);
74
- }));
75
- },
76
- tap: (cb) => createTap(createProgressEmitter, listen, cb),
77
- filter: (filterFunc) => createProgressEmitter(handler => listen((value) => {
78
- if (filterFunc(value))
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
- offset: (delta) => createProgressEmitter(handler => listen(value => handler((value + delta) % 1))),
82
- };
83
- const baseEmitter = createEmitter(listen, methods);
84
- const emitter = (0, utils_1.prototypify)(baseEmitter, api ?? {});
85
- return emitter;
86
- }
87
- function createTap(create, parentListen, cb) {
88
- const listeners = [];
89
- let parentUnsubscribe = null;
90
- const tappedListen = (handler) => {
91
- listeners.push(handler);
92
- if (listeners.length === 1) {
93
- parentUnsubscribe = parentListen(value => {
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
- return () => {
99
- const idx = listeners.indexOf(handler);
100
- listeners.splice(idx, 1);
101
- if (listeners.length === 0 && parentUnsubscribe) {
102
- parentUnsubscribe();
103
- parentUnsubscribe = null;
104
- }
105
- };
106
- };
107
- return create(tappedListen);
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
  }
@@ -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,41 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
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
+ }
@@ -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,96 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
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
+ }