@xtia/timeline 1.1.12 → 1.1.14

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
@@ -107,8 +107,8 @@ const filenameEmitter = range
107
107
  .dedupe()
108
108
  .map(n => `animation-frame-${n}.png`);
109
109
 
110
- // filenameEmitter will emit filenames as the Timeline passes through 'range'.
111
- // it can be listened directly or further transformed
110
+ // filenameEmitter will emit filenames as the Timeline passes through
111
+ // 'range'. it can be listened directly or further transformed
112
112
  const urlEmitter = filenameEmitter
113
113
  .map(filename => `http://www.example.com/${filename}`);
114
114
 
@@ -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,7 +5,7 @@ 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
10
  const parentListen = this.onListen;
11
11
  const { emit, listen } = createListenable(() => parentUnsubscribe = parentListen(value => {
@@ -38,7 +38,7 @@ export class Emitter {
38
38
  * @returns Listenable: emits transformed values
39
39
  */
40
40
  map(mapFunc) {
41
- const listen = this.createTransformListen((value, emit) => emit(mapFunc(value)));
41
+ const listen = this.transform((value, emit) => emit(mapFunc(value)));
42
42
  return new Emitter(listen);
43
43
  }
44
44
  /**
@@ -47,7 +47,7 @@ export class Emitter {
47
47
  * @returns Listenable: emits values that pass the filter
48
48
  */
49
49
  filter(check) {
50
- const listen = this.createTransformListen((value, emit) => check(value) && emit(value));
50
+ const listen = this.transform((value, emit) => check(value) && emit(value));
51
51
  return new Emitter(listen);
52
52
  }
53
53
  /**
@@ -59,7 +59,7 @@ export class Emitter {
59
59
  */
60
60
  dedupe(compare) {
61
61
  let previous = null;
62
- const listen = this.createTransformListen((value, emit) => {
62
+ const listen = this.transform((value, emit) => {
63
63
  if (!previous || (compare
64
64
  ? !compare(previous.value, value)
65
65
  : (previous.value !== value))) {
@@ -81,7 +81,7 @@ export class Emitter {
81
81
  * @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
82
82
  */
83
83
  tap(cb) {
84
- const listen = this.createTransformListen((value, emit) => {
84
+ const listen = this.transform((value, emit) => {
85
85
  cb(value);
86
86
  emit(value);
87
87
  });
@@ -115,13 +115,13 @@ export class RangeProgression extends Emitter {
115
115
  ? easers[easer]
116
116
  : easer;
117
117
  const listen = easerFunc
118
- ? this.createTransformListen((value, emit) => emit(easerFunc(value)))
118
+ ? this.transform((value, emit) => emit(easerFunc(value)))
119
119
  : this.onListen;
120
120
  return new RangeProgression(listen);
121
121
  }
122
122
  tween(from, to) {
123
123
  const tween = createTween(from, to);
124
- const listen = this.createTransformListen((progress, emit) => emit(tween(progress)));
124
+ const listen = this.transform((progress, emit) => emit(tween(progress)));
125
125
  return new Emitter(listen);
126
126
  }
127
127
  /**
@@ -138,7 +138,7 @@ export class RangeProgression extends Emitter {
138
138
  * @returns Listenable: emits the sampled values
139
139
  */
140
140
  sample(source) {
141
- const listen = this.createTransformListen((value, emit) => {
141
+ const listen = this.transform((value, emit) => {
142
142
  const clampedProgress = clamp(value);
143
143
  const index = Math.floor(clampedProgress * (source.length - 1));
144
144
  emit(source[index]);
@@ -168,7 +168,7 @@ export class RangeProgression extends Emitter {
168
168
  * @returns Listenable: emits 0 or 1 after comparing progress with a threshold
169
169
  */
170
170
  threshold(threshold) {
171
- const listen = this.createTransformListen((value, emit) => emit(value >= threshold ? 1 : 0));
171
+ const listen = this.transform((value, emit) => emit(value >= threshold ? 1 : 0));
172
172
  return new RangeProgression(listen);
173
173
  }
174
174
  /**
@@ -201,7 +201,7 @@ export class RangeProgression extends Emitter {
201
201
  repeat(count) {
202
202
  if (count <= 0)
203
203
  throw new RangeError("Repeat count must be greater than 0");
204
- const listen = this.createTransformListen((value, emit) => {
204
+ const listen = this.transform((value, emit) => {
205
205
  const out = (value * count) % 1;
206
206
  emit(out);
207
207
  });
@@ -213,7 +213,7 @@ export class RangeProgression extends Emitter {
213
213
  * @returns Listenable: emits values that pass the filter
214
214
  */
215
215
  filter(check) {
216
- const listen = this.createTransformListen((value, emit) => {
216
+ const listen = this.transform((value, emit) => {
217
217
  if (check(value))
218
218
  emit(value);
219
219
  });
@@ -226,7 +226,7 @@ export class RangeProgression extends Emitter {
226
226
  dedupe() {
227
227
  if (!this._dedupe) {
228
228
  let previous = null;
229
- const listen = this.createTransformListen((value, emit) => {
229
+ const listen = this.transform((value, emit) => {
230
230
  if (previous !== value) {
231
231
  emit(value);
232
232
  previous = value;
@@ -268,7 +268,7 @@ export class RangeProgression extends Emitter {
268
268
  * @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
269
269
  */
270
270
  tap(cb) {
271
- const listen = this.createTransformListen((value, emit) => {
271
+ const listen = this.transform((value, emit) => {
272
272
  cb(value);
273
273
  emit(value);
274
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) {
package/internal/tween.js CHANGED
@@ -134,7 +134,19 @@ function parseColour(code) {
134
134
  }
135
135
  function blendColours(from, to, bias) {
136
136
  const blended = from.map((val, i) => clamp(blendNumbers(val, to[i], bias), 0, 255));
137
- return ("#" + blended.map(n => Math.round(n).toString(16).padStart(2, "0")).join("")).replace(/ff$/, "");
137
+ if (blended[3] === 255) {
138
+ return "#" +
139
+ Math.round(blended[0]).toString(16).padStart(2, "0") +
140
+ Math.round(blended[1]).toString(16).padStart(2, "0") +
141
+ Math.round(blended[2]).toString(16).padStart(2, "0");
142
+ }
143
+ else {
144
+ return "#" +
145
+ Math.round(blended[0]).toString(16).padStart(2, "0") +
146
+ Math.round(blended[1]).toString(16).padStart(2, "0") +
147
+ Math.round(blended[2]).toString(16).padStart(2, "0") +
148
+ Math.round(blended[3]).toString(16).padStart(2, "0");
149
+ }
138
150
  }
139
151
  const tweenableTokenRegex = /(#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\b|[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)/g;
140
152
  function tokenise(s) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtia/timeline",
3
- "version": "1.1.12",
3
+ "version": "1.1.14",
4
4
  "repository": {
5
5
  "url": "https://github.com/tiadrop/timeline",
6
6
  "type": "github"