@xtia/timeline 1.0.4 → 1.0.6

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
@@ -93,7 +93,7 @@ eased.listen(
93
93
  const frames = eased
94
94
  .tween(0, 30)
95
95
  .map(Math.floor)
96
- .noRepeat()
96
+ .dedupe()
97
97
  .tap(n => console.log("Showing frame #", n))
98
98
  .map(n => `animation-frame-${n}.png`)
99
99
  .listen(filename => img.src = filename);
@@ -164,7 +164,6 @@ Tween emitters can interpolate numbers, arrays of numbers, strings, and objects
164
164
 
165
165
  ```ts
166
166
  // tween four values in a CSS string
167
- // see this live: https://codepen.io/xtaltia/pen/PwZYbKY
168
167
  timeline
169
168
  .range(0, 2000)
170
169
  .ease("elastic")
@@ -178,6 +177,8 @@ timeline
178
177
  .listen(v => document.title = v);
179
178
  ```
180
179
 
180
+ You can try out the [shadow tweening example at StackBlitz](https://stackblitz.com/edit/timeline-string-tween?file=src%2Fmain.ts)
181
+
181
182
  ## Autoplay and Looping Strategies
182
183
 
183
184
  To create a Timeline that immediately starts playing, pass `true` to its constructor:
@@ -1,13 +1,14 @@
1
1
  import { Easer, easers } from "./easing";
2
2
  import { Blendable } from "./tween";
3
+ import { OptionalIfKeyIn } from "./utils";
3
4
  /** @internal */
4
5
  export declare function createEmitter<T>(listen: ListenFunc<T>): Emitter<T>;
5
6
  /** @internal */
6
- export declare function createEmitter<T, API extends object>(onListen: ListenFunc<T>, api: Omit<API, keyof Emitter<T>>): Emitter<T> & API;
7
+ export declare function createEmitter<T, API extends object>(onListen: ListenFunc<T>, api: OptionalIfKeyIn<API, Emitter<T>>): Emitter<T> & API;
7
8
  /** @internal */
8
- export declare function createProgressEmitter<API extends object>(onListen: ListenFunc<number>, api: Omit<API, keyof RangeProgression>): RangeProgression & API;
9
+ export declare function createProgressEmitter<API extends object>(listen: ListenFunc<number>, api: Omit<API, keyof RangeProgression>): RangeProgression & API;
9
10
  /** @internal */
10
- export declare function createProgressEmitter(onListen: ListenFunc<number>): RangeProgression;
11
+ export declare function createProgressEmitter(listen: ListenFunc<number>): RangeProgression;
11
12
  type Handler<T> = (value: T) => void;
12
13
  type ListenFunc<T> = (handler: Handler<T>) => UnsubscribeFunc;
13
14
  export type UnsubscribeFunc = () => void;
@@ -37,7 +38,7 @@ export interface Emitter<T> {
37
38
  * If no `compare` function is provided, values will be compared via `===`
38
39
  * @returns Listenable: emits non-repeating values
39
40
  */
40
- noRepeat(compare?: (a: T, b: T) => boolean): Emitter<T>;
41
+ dedupe(compare?: (a: T, b: T) => boolean): Emitter<T>;
41
42
  /**
42
43
  * Creates a chainable emitter that mirrors emissions from the parent emitter, invoking the provided callback `cb` as a side effect for each emission.
43
44
  *
@@ -50,6 +51,25 @@ export interface Emitter<T> {
50
51
  * @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
51
52
  */
52
53
  tap(cb: Handler<T>): Emitter<T>;
54
+ /**
55
+ * Immediately passes this emitter to a callback and returns this emitter
56
+ *
57
+ * Allows branching without breaking a composition chain
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * range
62
+ * .tween("0%", "100%")
63
+ * .fork(branch => {
64
+ * branch
65
+ * .map(s => `Loading: ${s}`)
66
+ * .listen(s => document.title = s)
67
+ * })
68
+ * .listen(v => progressBar.style.width = v);
69
+ * ```
70
+ * @param cb
71
+ */
72
+ fork(cb: (branch: Emitter<T>) => void): Emitter<T>;
53
73
  }
54
74
  export interface RangeProgression extends Emitter<number> {
55
75
  /**
@@ -124,7 +144,21 @@ export interface RangeProgression extends Emitter<number> {
124
144
  clamp(min?: number, max?: number): RangeProgression;
125
145
  /**
126
146
  * Creates a chainable progress emitter that maps incoming values to a repeating linear scale
147
+ *
148
+ * ```plain
149
+ * count=2
150
+ *‎ 1
151
+ *‎ | / /
152
+ *‎ o| / /
153
+ *‎ u| / /
154
+ *‎ t| / /
155
+ *‎ | / /
156
+ *‎ |/_____/_____
157
+ *‎ 0 in 1
158
+ * ```
159
+ *
127
160
  * @param count Number of repetitions
161
+ * @returns Listenable: emits scaled and repeating values
128
162
  */
129
163
  repeat(count: number): RangeProgression;
130
164
  /**
@@ -149,9 +183,9 @@ export interface RangeProgression extends Emitter<number> {
149
183
  * Creates a chainable progress emitter that discards emitted values that are the same as the last value emitted by the new emitter
150
184
  * @returns Listenable: emits non-repeating values
151
185
  */
152
- noRepeat(): RangeProgression;
186
+ dedupe(): RangeProgression;
153
187
  /**
154
- * Creates a chainable progress emitter that offsets its parent's values by the given delta, wrapping between 0 and 1
188
+ * Creates a chainable progress emitter that offsets its parent's values by the given delta, wrapping at 1
155
189
  *
156
190
  * ```plain
157
191
  *‎ 1
@@ -165,7 +199,9 @@ export interface RangeProgression extends Emitter<number> {
165
199
  * ```
166
200
  *
167
201
  * @param delta
202
+ * @returns Listenable: emits offset values
168
203
  */
169
204
  offset(delta: number): RangeProgression;
205
+ fork(cb: (branch: RangeProgression) => void): RangeProgression;
170
206
  }
171
207
  export {};
@@ -7,7 +7,7 @@ const tween_1 = require("./tween");
7
7
  const utils_1 = require("./utils");
8
8
  /** @internal */
9
9
  function createEmitter(listen, api) {
10
- const propertyDescriptor = Object.fromEntries(Object.entries({
10
+ const methods = {
11
11
  listen: (handler) => listen((value) => {
12
12
  handler(value);
13
13
  }),
@@ -18,7 +18,7 @@ function createEmitter(listen, api) {
18
18
  if (filterFunc(value))
19
19
  handler(value);
20
20
  })),
21
- noRepeat: (compare) => {
21
+ dedupe: (compare) => {
22
22
  let previous = null;
23
23
  return createEmitter(handler => {
24
24
  const filteredHandler = (value) => {
@@ -30,18 +30,20 @@ function createEmitter(listen, api) {
30
30
  }
31
31
  };
32
32
  return listen(filteredHandler);
33
- });
33
+ }, api ?? {});
34
34
  },
35
- tap: (cb) => createTap(cb, createEmitter, listen),
36
- }).map(([key, value]) => [
37
- key,
38
- { value }
39
- ]));
40
- return Object.create(api ?? {}, propertyDescriptor);
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;
41
43
  }
42
44
  /** @internal */
43
- function createProgressEmitter(listen, api = {}) {
44
- const propertyDescriptor = Object.fromEntries(Object.entries({
45
+ function createProgressEmitter(listen, api) {
46
+ const methods = {
45
47
  ease: (easer) => {
46
48
  const easerFunc = typeof easer == "string"
47
49
  ? easing_1.easers[easer]
@@ -71,37 +73,25 @@ function createProgressEmitter(listen, api = {}) {
71
73
  handler(out);
72
74
  }));
73
75
  },
74
- tap: (cb) => createTap(cb, createProgressEmitter, listen),
76
+ tap: (cb) => createTap(createProgressEmitter, listen, cb),
75
77
  filter: (filterFunc) => createProgressEmitter(handler => listen((value) => {
76
78
  if (filterFunc(value))
77
79
  handler(value);
78
80
  })),
79
- noRepeat: () => {
80
- let previous = null;
81
- return createProgressEmitter(handler => {
82
- return listen((value) => {
83
- if (!previous || (previous.value !== value)) {
84
- handler(value);
85
- previous = { value };
86
- }
87
- });
88
- });
89
- },
90
81
  offset: (delta) => createProgressEmitter(handler => listen(value => handler((value + delta) % 1))),
91
- }).map(([key, value]) => [
92
- key,
93
- { value }
94
- ]));
95
- return Object.create(api ?? {}, propertyDescriptor);
82
+ };
83
+ const baseEmitter = createEmitter(listen, methods);
84
+ const emitter = (0, utils_1.prototypify)(baseEmitter, api ?? {});
85
+ return emitter;
96
86
  }
97
- function createTap(callback, create, parentListen) {
87
+ function createTap(create, parentListen, cb) {
98
88
  const listeners = [];
99
89
  let parentUnsubscribe = null;
100
- const tapOnListen = (handler) => {
90
+ const tappedListen = (handler) => {
101
91
  listeners.push(handler);
102
92
  if (listeners.length === 1) {
103
93
  parentUnsubscribe = parentListen(value => {
104
- callback(value);
94
+ cb(value);
105
95
  listeners.slice().forEach(fn => fn(value));
106
96
  });
107
97
  }
@@ -114,5 +104,5 @@ function createTap(callback, create, parentListen) {
114
104
  }
115
105
  };
116
106
  };
117
- return create(tapOnListen);
107
+ return create(tappedListen);
118
108
  }
@@ -2,3 +2,5 @@
2
2
  export declare const clamp: (value: number, min: number, max: number) => number;
3
3
  /** @internal */
4
4
  export type Widen<T> = T extends number ? number : T extends string ? string : T;
5
+ export type OptionalIfKeyIn<T, U> = Omit<T, keyof U> & Partial<Pick<T, Extract<keyof T, keyof U>>>;
6
+ export declare function prototypify<Prototype extends object, Members extends object>(proto: Prototype, members: Members): Prototype & Members;
package/internal/utils.js CHANGED
@@ -1,6 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.clamp = void 0;
4
+ exports.prototypify = prototypify;
4
5
  /** @internal */
5
6
  const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
6
7
  exports.clamp = clamp;
8
+ function prototypify(proto, members) {
9
+ const propertyDescriptor = Object.fromEntries(Object.entries(members).map(([key, value]) => [
10
+ key,
11
+ { value }
12
+ ]));
13
+ return Object.create(proto, propertyDescriptor);
14
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtia/timeline",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "repository": {
5
5
  "url": "https://github.com/tiadrop/timeline",
6
6
  "type": "github"