@xtia/timeline 1.0.11 → 1.0.12

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
@@ -483,6 +483,13 @@ Creates a [`TimelineRange`](#timelinerange-interface) on the Timeline to which t
483
483
 
484
484
  Creates a `TimelinePoint` at an offset from the this point.
485
485
 
486
+ ##### `seek(): void`
487
+
488
+ Seeks the parent Timeline to this point.
489
+
490
+ ##### `seek(duration: number, easer?: Easer): Promise<void>`
491
+
492
+ Smooth-seeks the parent Timeline to this point over a specified duration and resolves the returned Promise on completion.
486
493
 
487
494
 
488
495
 
@@ -593,6 +600,10 @@ blend(from: this, to: this, progress: number): this
593
600
 
594
601
  Creates an emitter that quantises progression emitted by the parent to the nearest of `steps` discrete values.
595
602
 
603
+ ##### `sample<T>(values: ArrayLike<T>): `[`Emitter<T>`](#emittert-interface)
604
+
605
+ Creates an emitter that emits values from an array according to progression.
606
+
596
607
  ##### `threshold(threshold): RangeProgression`
597
608
 
598
609
  Creates an emitter that emits 0 when the parent emits a value below `threshold` and 1 when a parent emission is equal to or greater than `threshold`.
@@ -125,6 +125,20 @@ export declare class RangeProgression extends Emitter<number> {
125
125
  */
126
126
  tween<T extends Tweenable>(from: T, to: T): Emitter<T>;
127
127
  tween<T extends BlendableWith<T, R>, R>(from: T, to: R): Emitter<T>;
128
+ /**
129
+ * Creates a chainable emitter that takes a value from an array according to progression
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * range
134
+ * .sample(["a", "b", "c"])
135
+ * .listen(v => console.log(v));
136
+ * // logs 'b' when a seek lands halfway through range
137
+ * ```
138
+ * @param source array to sample
139
+ * @returns Listenable: emits the sampled values
140
+ */
141
+ sample<T>(source: ArrayLike<T>): Emitter<T>;
128
142
  /**
129
143
  * Creates a chainable progress emitter that quantises progress, as emitted by its parent, to the nearest of `steps` discrete values.
130
144
  *
@@ -141,6 +141,26 @@ export class RangeProgression extends Emitter {
141
141
  const tween = createTween(from, to);
142
142
  return new Emitter(handler => this.onListen(progress => handler(tween(progress))));
143
143
  }
144
+ /**
145
+ * Creates a chainable emitter that takes a value from an array according to progression
146
+ *
147
+ * @example
148
+ * ```ts
149
+ * range
150
+ * .sample(["a", "b", "c"])
151
+ * .listen(v => console.log(v));
152
+ * // logs 'b' when a seek lands halfway through range
153
+ * ```
154
+ * @param source array to sample
155
+ * @returns Listenable: emits the sampled values
156
+ */
157
+ sample(source) {
158
+ return new Emitter(handler => this.onListen(progress => {
159
+ const clampedProgress = clamp(progress);
160
+ const index = Math.floor(clampedProgress * (source.length - 1));
161
+ handler(source[index]);
162
+ }));
163
+ }
144
164
  /**
145
165
  * Creates a chainable progress emitter that quantises progress, as emitted by its parent, to the nearest of `steps` discrete values.
146
166
  *
@@ -154,7 +174,7 @@ export class RangeProgression extends Emitter {
154
174
  }
155
175
  return new RangeProgression(handler => this.onListen(progress => {
156
176
  const snapped = Math.round(progress * steps) / steps;
157
- handler(clamp(snapped, 0, 1));
177
+ handler(clamp(snapped));
158
178
  }));
159
179
  }
160
180
  /**
@@ -36,6 +36,9 @@ export declare class TimelinePoint extends Emitter<PointEvent> {
36
36
  * @returns Listenable: emits a PointEvent when the point is reached or passed by a Timeline seek
37
37
  */
38
38
  delta(timeOffset: number): TimelinePoint;
39
+ /**
40
+ * Seeks the parent Timeline to this point
41
+ */
39
42
  seek(): void;
40
- seek(duration?: number, easer?: Easer): void;
43
+ seek(duration: number, easer?: Easer): Promise<void>;
41
44
  }
package/internal/point.js CHANGED
@@ -39,6 +39,6 @@ export class TimelinePoint extends Emitter {
39
39
  return this.timeline.point(this.position + timeOffset);
40
40
  }
41
41
  seek(duration = 0, easer) {
42
- this.timeline.seek(this.position, duration, easer);
42
+ return this.timeline.seek(this.position, duration, easer);
43
43
  }
44
44
  }
@@ -7,5 +7,7 @@ export interface Blendable {
7
7
  export interface BlendableWith<T, R> {
8
8
  blend(target: R, progress: number): T;
9
9
  }
10
- export declare function createTween<T extends Tweenable>(from: T, to: T): ((progress: number) => T);
11
- export declare function createTween<T extends BlendableWith<T, R>, R>(from: T, to: R): ((progress: number) => T);
10
+ type TweenFunc<T> = (progress: number) => T;
11
+ export declare function createTween<T extends Tweenable>(from: T, to: T): TweenFunc<T>;
12
+ export declare function createTween<T extends BlendableWith<T, R>, R>(from: T, to: R): TweenFunc<T>;
13
+ export {};
package/internal/tween.js CHANGED
@@ -1,4 +1,9 @@
1
1
  import { clamp } from "./utils";
2
+ const tokenTypes = {
3
+ none: 0,
4
+ number: 1,
5
+ colour: 2,
6
+ };
2
7
  export function createTween(from, to) {
3
8
  if (from === to)
4
9
  return () => from;
@@ -24,8 +29,9 @@ function createStringTween(from, to) {
24
29
  if (tokenCount !== toChunks.filter(c => c.token).length) {
25
30
  return createStringMerge(from, to);
26
31
  }
27
- // where token prefix mismatch, use merging
28
- if (fromChunks.some((chunk, i) => toChunks[i].prefix !== chunk.prefix)) {
32
+ // where token prefix/type mismatch, use merging
33
+ if (fromChunks.some((chunk, i) => toChunks[i].prefix !== chunk.prefix ||
34
+ toChunks[i].type !== chunk.type)) {
29
35
  return createStringMerge(from, to);
30
36
  }
31
37
  // convert token chunks to individual string tween funcs
@@ -33,9 +39,9 @@ function createStringTween(from, to) {
33
39
  const fromToken = chunk.token;
34
40
  const toToken = toChunks[i].token;
35
41
  const prefix = chunk.prefix;
36
- if (!fromToken)
42
+ if (chunk.type === tokenTypes.none)
37
43
  return () => prefix;
38
- if (fromToken.startsWith("#")) {
44
+ if (chunk.type === tokenTypes.colour) {
39
45
  const fromColour = parseColour(fromToken);
40
46
  const toColour = parseColour(toToken);
41
47
  return progress => prefix + blendColours(fromColour, toColour, progress);
@@ -65,7 +71,7 @@ function createStringMerge(from, to) {
65
71
  if (!to)
66
72
  return () => from;
67
73
  const split = (s) => {
68
- // prefer Intl.Segmenter if available (Node ≥ 14, modern browsers)
74
+ // prefer Intl.Segmenter if available (Node 14, modern browsers)
69
75
  if (typeof Intl !== "undefined" && Intl.Segmenter) {
70
76
  const seg = new Intl.Segmenter(undefined, { granularity: "grapheme" });
71
77
  return Array.from(seg.segment(s), (seg) => seg.segment);
@@ -86,7 +92,7 @@ function createStringMerge(from, to) {
86
92
  const fromP = pad(a);
87
93
  const toP = pad(b);
88
94
  return (progress) => {
89
- const clampedProgress = clamp(progress, 0, 1);
95
+ const clampedProgress = clamp(progress);
90
96
  const replaceCount = Math.floor(clampedProgress * maxLen);
91
97
  const result = new Array(maxLen);
92
98
  for (let i = 0; i < maxLen; ++i) {
@@ -122,20 +128,27 @@ function blendColours(from, to, bias) {
122
128
  return ("#" + blended.map(n => Math.round(n).toString(16).padStart(2, "0")).join("")).replace(/ff$/, "");
123
129
  }
124
130
  const tweenableTokenRegex = /(#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\b|[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)/g;
125
- const tokenise = (s) => {
131
+ function tokenise(s) {
126
132
  const chunks = [];
127
133
  let lastIdx = 0;
128
134
  let m;
129
135
  while ((m = tweenableTokenRegex.exec(s))) {
130
136
  const token = m[0];
131
137
  const prefix = s.slice(lastIdx, m.index); // literal before token
132
- chunks.push({ prefix, token });
138
+ const type = getTokenType(token);
139
+ chunks.push({ prefix, token, type });
133
140
  lastIdx = m.index + token.length;
134
141
  }
135
142
  // trailing literal after the last token – stored as a final chunk
136
143
  const tail = s.slice(lastIdx);
137
144
  if (tail.length) {
138
- chunks.push({ prefix: tail, token: "" });
145
+ chunks.push({ prefix: tail, token: "", type: tokenTypes.none });
139
146
  }
140
147
  return chunks;
141
- };
148
+ }
149
+ ;
150
+ function getTokenType(token) {
151
+ if (token.startsWith("#"))
152
+ return tokenTypes.colour;
153
+ return tokenTypes.number;
154
+ }
@@ -1,4 +1,4 @@
1
1
  /** @internal */
2
- export declare const clamp: (value: number, min: number, max: number) => number;
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;
package/internal/utils.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /** @internal */
2
- export const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
2
+ export const clamp = (value, min = 0, max = 1) => Math.min(Math.max(value, min), max);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtia/timeline",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "repository": {
5
5
  "url": "https://github.com/tiadrop/timeline",
6
6
  "type": "github"