@xtia/timeline 1.1.11 → 1.1.13

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 CHANGED
@@ -145,12 +145,21 @@ Points emit `PointEvent` objects when their position is reached or passed.
145
145
  ```ts
146
146
  twoSecondsIn.apply(event => {
147
147
  // event.direction (-1 | 1) tells us the direction of the seek that
148
- // triggered the point. This allows for reversible point events
149
- document.body.classList.toggle("someClass", event.direction > 0);
148
+ // triggered the point. This allows for reversible effects:
149
+ element.classList.toggle("someClass", event.direction > 0);
150
150
  });
151
151
  ```
152
152
 
153
- *Note*, point events will be triggered in order, depending on the direction of the seek that passes over them.
153
+ *Note*, point events will be triggered in order, depending on the direction of the seek that passes over them. To ensure consistent reversible behaviour, a point is triggered with `direction = 1` when a forward seek *passes or lands on* it, and with `direction = -1` when a backward seek *passes or departs from* it.
154
+
155
+ Directionality can also be leveraged with `point.applyDirectional()`:
156
+
157
+ ```ts
158
+ twoSecondsIn.applyDirectional(
159
+ parent.append(element), // do
160
+ element.remove() // undo
161
+ );
162
+ ```
154
163
 
155
164
  We can also create ranges from points:
156
165
 
@@ -165,7 +174,7 @@ timeline
165
174
  .tween(/*...*/);
166
175
  ```
167
176
 
168
- *Note*, points and ranges without active listeners are not stored, so will be garbage-collected if unreferenced.
177
+ *Note*, points and ranges are transient interfaces for adding behaviour to their Timelines; they can be garbage-collected if unreferenced even while their listeners persist.
169
178
 
170
179
  ## More on tweening
171
180
 
@@ -6,7 +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
- protected createTransformListen<R = T>(handler: (value: T, emit: (value: R) => void) => void): (fn: (v: R) => void) => UnsubscribeFunc;
9
+ protected transform<R = T>(handler: (value: T, emit: (value: R) => void) => void): (fn: (v: R) => void) => UnsubscribeFunc;
10
10
  /**
11
11
  * Compatibility alias for `apply()` - registers a function to receive emitted values
12
12
  * @param handler
@@ -5,9 +5,10 @@ export class Emitter {
5
5
  constructor(onListen) {
6
6
  this.onListen = onListen;
7
7
  }
8
- createTransformListen(handler) {
8
+ transform(handler) {
9
9
  let parentUnsubscribe = null;
10
- const { emit, listen } = createListenable(() => parentUnsubscribe = this.onListen(value => {
10
+ const parentListen = this.onListen;
11
+ const { emit, listen } = createListenable(() => parentUnsubscribe = parentListen(value => {
11
12
  handler(value, emit);
12
13
  }), () => {
13
14
  parentUnsubscribe();
@@ -37,7 +38,7 @@ export class Emitter {
37
38
  * @returns Listenable: emits transformed values
38
39
  */
39
40
  map(mapFunc) {
40
- const listen = this.createTransformListen((value, emit) => emit(mapFunc(value)));
41
+ const listen = this.transform((value, emit) => emit(mapFunc(value)));
41
42
  return new Emitter(listen);
42
43
  }
43
44
  /**
@@ -46,7 +47,7 @@ export class Emitter {
46
47
  * @returns Listenable: emits values that pass the filter
47
48
  */
48
49
  filter(check) {
49
- const listen = this.createTransformListen((value, emit) => check(value) && emit(value));
50
+ const listen = this.transform((value, emit) => check(value) && emit(value));
50
51
  return new Emitter(listen);
51
52
  }
52
53
  /**
@@ -58,7 +59,7 @@ export class Emitter {
58
59
  */
59
60
  dedupe(compare) {
60
61
  let previous = null;
61
- const listen = this.createTransformListen((value, emit) => {
62
+ const listen = this.transform((value, emit) => {
62
63
  if (!previous || (compare
63
64
  ? !compare(previous.value, value)
64
65
  : (previous.value !== value))) {
@@ -80,7 +81,7 @@ export class Emitter {
80
81
  * @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
81
82
  */
82
83
  tap(cb) {
83
- const listen = this.createTransformListen((value, emit) => {
84
+ const listen = this.transform((value, emit) => {
84
85
  cb(value);
85
86
  emit(value);
86
87
  });
@@ -114,13 +115,13 @@ export class RangeProgression extends Emitter {
114
115
  ? easers[easer]
115
116
  : easer;
116
117
  const listen = easerFunc
117
- ? this.createTransformListen((value, emit) => emit(easerFunc(value)))
118
+ ? this.transform((value, emit) => emit(easerFunc(value)))
118
119
  : this.onListen;
119
120
  return new RangeProgression(listen);
120
121
  }
121
122
  tween(from, to) {
122
123
  const tween = createTween(from, to);
123
- const listen = this.createTransformListen((progress, emit) => emit(tween(progress)));
124
+ const listen = this.transform((progress, emit) => emit(tween(progress)));
124
125
  return new Emitter(listen);
125
126
  }
126
127
  /**
@@ -137,7 +138,7 @@ export class RangeProgression extends Emitter {
137
138
  * @returns Listenable: emits the sampled values
138
139
  */
139
140
  sample(source) {
140
- const listen = this.createTransformListen((value, emit) => {
141
+ const listen = this.transform((value, emit) => {
141
142
  const clampedProgress = clamp(value);
142
143
  const index = Math.floor(clampedProgress * (source.length - 1));
143
144
  emit(source[index]);
@@ -167,7 +168,7 @@ export class RangeProgression extends Emitter {
167
168
  * @returns Listenable: emits 0 or 1 after comparing progress with a threshold
168
169
  */
169
170
  threshold(threshold) {
170
- const listen = this.createTransformListen((value, emit) => emit(value >= threshold ? 1 : 0));
171
+ const listen = this.transform((value, emit) => emit(value >= threshold ? 1 : 0));
171
172
  return new RangeProgression(listen);
172
173
  }
173
174
  /**
@@ -200,7 +201,7 @@ export class RangeProgression extends Emitter {
200
201
  repeat(count) {
201
202
  if (count <= 0)
202
203
  throw new RangeError("Repeat count must be greater than 0");
203
- const listen = this.createTransformListen((value, emit) => {
204
+ const listen = this.transform((value, emit) => {
204
205
  const out = (value * count) % 1;
205
206
  emit(out);
206
207
  });
@@ -212,7 +213,7 @@ export class RangeProgression extends Emitter {
212
213
  * @returns Listenable: emits values that pass the filter
213
214
  */
214
215
  filter(check) {
215
- const listen = this.createTransformListen((value, emit) => {
216
+ const listen = this.transform((value, emit) => {
216
217
  if (check(value))
217
218
  emit(value);
218
219
  });
@@ -225,7 +226,7 @@ export class RangeProgression extends Emitter {
225
226
  dedupe() {
226
227
  if (!this._dedupe) {
227
228
  let previous = null;
228
- const listen = this.createTransformListen((value, emit) => {
229
+ const listen = this.transform((value, emit) => {
229
230
  if (previous !== value) {
230
231
  emit(value);
231
232
  previous = value;
@@ -267,7 +268,7 @@ export class RangeProgression extends Emitter {
267
268
  * @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
268
269
  */
269
270
  tap(cb) {
270
- const listen = this.createTransformListen((value, emit) => {
271
+ const listen = this.transform((value, emit) => {
271
272
  cb(value);
272
273
  emit(value);
273
274
  });
package/internal/point.js CHANGED
@@ -59,7 +59,7 @@ export class TimelinePoint extends Emitter {
59
59
  return this._reverseOnly;
60
60
  }
61
61
  filter(arg) {
62
- const listen = this.createTransformListen(typeof arg == "number"
62
+ const listen = this.transform(typeof arg == "number"
63
63
  ? (value, emit) => {
64
64
  if (value.direction === arg)
65
65
  emit(value);
@@ -79,7 +79,7 @@ export class TimelinePoint extends Emitter {
79
79
  */
80
80
  promise() {
81
81
  return new Promise(resolve => {
82
- let remove = this.apply((ev) => {
82
+ let remove = this.onListen((ev) => {
83
83
  remove();
84
84
  resolve(ev.direction);
85
85
  });
@@ -116,7 +116,7 @@ export class TimelinePoint extends Emitter {
116
116
  dedupe() {
117
117
  if (!this._dedupe) {
118
118
  let previous = 0;
119
- const listen = this.createTransformListen((value, emit) => {
119
+ const listen = this.transform((value, emit) => {
120
120
  if (value.direction !== previous) {
121
121
  previous = value.direction;
122
122
  emit(value);
package/internal/range.js CHANGED
@@ -23,8 +23,8 @@ export class TimelineRange extends RangeProgression {
23
23
  * @returns Tuple of two ranges
24
24
  */
25
25
  bisect(position = this.duration / 2) {
26
- if (position >= this.endPosition) {
27
- throw new RangeError("Bisection position is beyond end of range");
26
+ if (position >= this.duration) {
27
+ throw new RangeError("Bisection position is at or beyond end of range");
28
28
  }
29
29
  return [
30
30
  this.timeline.range(this.startPosition, position),
@@ -20,6 +20,20 @@ type Period = {
20
20
  */
21
21
  export declare function animate(durationMs: number): TimelineRange;
22
22
  export declare function animate(period: Period): TimelineRange;
23
+ type TimelineOptions = {
24
+ atEnd?: {
25
+ wrapAt: number;
26
+ } | {
27
+ restartAt: number;
28
+ } | keyof typeof EndAction;
29
+ timeScale?: number;
30
+ } & ({
31
+ autoplay: true;
32
+ fps?: number;
33
+ } | ({
34
+ autoplay?: false;
35
+ fps?: never;
36
+ }));
23
37
  export declare class Timeline {
24
38
  /**
25
39
  * Multiplies the speed at which `play()` progresses through the Timeline
@@ -90,6 +104,7 @@ export declare class Timeline {
90
104
  } | {
91
105
  restartAt: number;
92
106
  } | keyof typeof EndAction);
107
+ constructor(options: TimelineOptions);
93
108
  /**
94
109
  * @deprecated "loop" endAction will be removed; use "restart" or `{restartAt: 0}` (disambiguates new looping strategies)
95
110
  */
@@ -64,7 +64,7 @@ export class Timeline {
64
64
  }
65
65
  return this._progression.emitter;
66
66
  }
67
- constructor(autoplay = false, endAction = "pause") {
67
+ constructor(optionsOrAutoplay = false, endAction = "pause") {
68
68
  /**
69
69
  * Multiplies the speed at which `play()` progresses through the Timeline
70
70
  *
@@ -85,13 +85,26 @@ export class Timeline {
85
85
  this.start = this.point(0);
86
86
  this._frameEvents = null;
87
87
  this._progression = null;
88
+ // "loop" is temporary alias for "restart":
88
89
  if (endAction == "loop")
89
90
  endAction = "restart";
90
- if (autoplay === true) {
91
+ if (typeof optionsOrAutoplay == "object") {
92
+ endAction = optionsOrAutoplay.atEnd ?? "pause";
93
+ this.timeScale = optionsOrAutoplay.timeScale ?? 1;
94
+ if ("autoplay" in optionsOrAutoplay && optionsOrAutoplay.autoplay) {
95
+ if ("fps" in optionsOrAutoplay && optionsOrAutoplay.fps) {
96
+ this.play(optionsOrAutoplay.fps);
97
+ }
98
+ else {
99
+ this.play();
100
+ }
101
+ }
102
+ }
103
+ else if (optionsOrAutoplay === true) {
91
104
  this.play();
92
105
  }
93
- else if (typeof autoplay == "number") {
94
- this.play(autoplay);
106
+ else if (typeof optionsOrAutoplay == "number") {
107
+ this.play(optionsOrAutoplay);
95
108
  }
96
109
  if (typeof endAction == "object"
97
110
  && "restartAt" in endAction) {
@@ -213,7 +226,10 @@ export class Timeline {
213
226
  .range(0, durationMs)
214
227
  .ease(easer)
215
228
  .tween(this._currentTime, toPosition)
216
- .apply(v => this.seekDirect(v));
229
+ .apply(v => {
230
+ this.seekDirect(v);
231
+ this._frameEvents?.emit();
232
+ });
217
233
  return seeker.end.promise();
218
234
  }
219
235
  seekDirect(toPosition) {
package/internal/tween.js CHANGED
@@ -143,13 +143,13 @@ function tokenise(s) {
143
143
  let m;
144
144
  while ((m = tweenableTokenRegex.exec(s))) {
145
145
  const token = m[0];
146
- const prefix = s.slice(lastIdx, m.index); // literal before token
146
+ const prefix = s.substring(lastIdx, m.index); // literal before token
147
147
  const type = getTokenType(token);
148
148
  chunks.push({ prefix, token, type });
149
149
  lastIdx = m.index + token.length;
150
150
  }
151
151
  // trailing literal after the last token – stored as a final chunk
152
- const tail = s.slice(lastIdx);
152
+ const tail = s.substring(lastIdx);
153
153
  if (tail.length) {
154
154
  chunks.push({ prefix: tail, token: "", type: TokenTypes.none });
155
155
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtia/timeline",
3
- "version": "1.1.11",
3
+ "version": "1.1.13",
4
4
  "repository": {
5
5
  "url": "https://github.com/tiadrop/timeline",
6
6
  "type": "github"