@xtia/timeline 1.0.11 → 1.1.0

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
@@ -21,7 +21,7 @@ const timeline = new Timeline();
21
21
  timeline
22
22
  .range(0, 1000)
23
23
  .tween("#646", "#000")
24
- .listen(
24
+ .apply(
25
25
  value => element.style.background = value
26
26
  );
27
27
 
@@ -31,7 +31,7 @@ timeline
31
31
  .range(500, 2000)
32
32
  .tween(0, message.length)
33
33
  .map(n => message.substring(0, n))
34
- .listen(
34
+ .apply(
35
35
  s => element.textContent = s
36
36
  );
37
37
 
@@ -40,7 +40,7 @@ timeline
40
40
  .range(0, 3000)
41
41
  .ease("bounce")
42
42
  .tween("50%", "0%")
43
- .listen(
43
+ .apply(
44
44
  value => element.style.marginLeft = value
45
45
  );
46
46
 
@@ -56,11 +56,11 @@ timeline.play();
56
56
  const firstFiveSeconds = timeline.range(0, 5000);
57
57
  ```
58
58
 
59
- The range object is *listenable* and emits a progression value (between 0 and 1) when the Timeline's internal position passes through or over that period.
59
+ The range object is *applyable* and emits a progression value (between 0 and 1) when the Timeline's internal position passes through or over that period.
60
60
 
61
61
  ```ts
62
62
  firstFiveSeconds
63
- .listen(
63
+ .apply(
64
64
  value => console.log(`${value} is between 0 and 1`)
65
65
  );
66
66
  ```
@@ -74,20 +74,20 @@ const asPercent = firstFiveSeconds.map(n => n * 100);
74
74
  // use the result in a log message
75
75
  asPercent
76
76
  .map(n => n.toFixed(2))
77
- .listen(
77
+ .apply(
78
78
  n => console.log(`We are ${n}% through the first five seconds`)
79
79
  );
80
80
 
81
81
  // and in a css property
82
82
  asPercent
83
83
  .map(n => `${n}%`)
84
- .listen(
84
+ .apply(
85
85
  n => progressBar.style.width = n
86
86
  );
87
87
 
88
88
  // apply easing
89
89
  const eased = firstFiveSeconds.ease("easeInOut");
90
- eased.listen(
90
+ eased.apply(
91
91
  v => console.log(`Eased value: ${v}`)
92
92
  );
93
93
 
@@ -98,7 +98,7 @@ range
98
98
  .dedupe()
99
99
  .tap(n => console.log("Showing frame #", n))
100
100
  .map(n => `animation-frame-${n}.png`)
101
- .listen(filename => img.src = filename);
101
+ .apply(filename => img.src = filename);
102
102
 
103
103
  // each step in a chain is a 'pure', independent emitter that emits a
104
104
  // transformation of its parent's emissions
@@ -146,7 +146,7 @@ const sixSecondsIn = fiveSecondsdIn.delta(1000);
146
146
  Points emit `PointEvent` objects when their position is reached or passed.
147
147
 
148
148
  ```ts
149
- twoSecondsIn.listen(event => {
149
+ twoSecondsIn.apply(event => {
150
150
  // event.direction (-1 | 1) tells us the direction of the seek that
151
151
  // triggered the point. This allows for reversible point events
152
152
  document.body.classList.toggle("someClass", event.direction > 0);
@@ -181,30 +181,30 @@ const range = timeline.range(0, 2000);
181
181
  range
182
182
  .ease("overshootIn")
183
183
  .tween(300, 500)
184
- .listen(v => element.scrollTop = v);
184
+ .apply(v => element.scrollTop = v);
185
185
 
186
186
  // number arrays
187
187
  range
188
188
  .tween([0, 180], [360, 180])
189
- .listen((angles) => pieChart.setValues(angles));
189
+ .apply((angles) => pieChart.setValues(angles));
190
190
 
191
191
  // strings
192
192
  range
193
193
  .tween("#000000", "#ff00ff")
194
- .listen(v => element.style.color = v);
194
+ .apply(v => element.style.color = v);
195
195
 
196
196
  // blendable objects
197
197
  // (T extends { blend(from: this, to: this): this })
198
198
  import { RGBA } from "@xtia/rgba";
199
199
  range
200
200
  .tween(RGBA.parse("#c971a7"), RGBA.parse("#fff"))
201
- .listen(v => element.style.background = v);
201
+ .apply(v => element.style.background = v);
202
202
 
203
203
  import { Angle } from "@xtia/mezr";
204
204
  range
205
205
  .tween(Angle.degrees(45), Angle.turns(.5))
206
206
  .map(a => `rotate(${a.asDegrees}deg)`)
207
- .listen(v => element.style.transform = v);
207
+ .apply(v => element.style.transform = v);
208
208
 
209
209
  ```
210
210
 
@@ -218,14 +218,14 @@ timeline
218
218
  .range(0, 2000)
219
219
  .ease("elastic")
220
220
  .tween("0px 0px 0px #0000", "15px 15px 20px #0005")
221
- .listen(s => element.style.textShadow = s);
221
+ .apply(s => element.style.textShadow = s);
222
222
 
223
223
  // text progress bar
224
224
  timeline
225
225
  .range(0, 2000)
226
226
  .tween("--------", "########")
227
227
  .dedupe()
228
- .listen(v => document.title = v);
228
+ .apply(v => document.title = v);
229
229
  ```
230
230
 
231
231
  Try out the [shadow tweening example at StackBlitz](https://stackblitz.com/edit/timeline-string-tween?file=src%2Fmain.ts)
@@ -238,13 +238,13 @@ To create a Timeline that immediately starts playing, pass `true` to its constru
238
238
  // immediately fade in an element
239
239
  new Timeline(true)
240
240
  .range(0, 1000)
241
- .listen(v => element.style.opacity = v);
241
+ .apply(v => element.style.opacity = v);
242
242
 
243
243
  // note, an `animate(duration)` function is exported for
244
244
  // disposable, single-use animations such as this:
245
245
  import { animate } from "@xtia/timeline";
246
246
  animate(1000)
247
- .listen(v => element.style.opacity = v);
247
+ .apply(v => element.style.opacity = v);
248
248
  ```
249
249
 
250
250
  Normally a Timeline will simply stop playing when it reaches the end. This can be changed by passing a second argument (`endAction`) to the constructor.
@@ -302,19 +302,19 @@ window.addEventListener(
302
302
  setInterval(() => timeline.seek(Date.now()), 1000);
303
303
  timeline
304
304
  .point(new Date("2026-10-31").getTime())
305
- .listen(() => console.log("Happy anniversary 🏳️‍⚧️💗"));
305
+ .apply(() => console.log("Happy anniversary 🏳️‍⚧️💗"));
306
306
 
307
307
  // show a progress bar for loaded resources
308
308
  const loadingTimeline = new Timeline();
309
309
  loadingTimeline
310
310
  .range(0, resourceUrls.length)
311
311
  .tween("0%", "100%");
312
- .listen(v => progressBar.style.width = v);
312
+ .apply(v => progressBar.style.width = v);
313
313
 
314
314
  // and do something when they're loaded
315
315
  loadingTimeline
316
316
  .end
317
- .listen(startGame);
317
+ .apply(startGame);
318
318
 
319
319
  // to drive it, just seek forward by 1 for each loaded resource
320
320
  resourceUrls.forEach(url => {
@@ -435,7 +435,7 @@ timeline
435
435
  .range(start, duration)
436
436
  .ease(easer)
437
437
  .tween(from, to)
438
- .listen(apply);
438
+ .apply(apply);
439
439
  ```
440
440
 
441
441
  Returns a [`ChainingInterface`](#chaininginterface-interface) representing the point at which the tween ends.
@@ -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
 
@@ -501,7 +508,7 @@ Allows point listeners to undo effects when the Timeline is reversed.
501
508
  ```ts
502
509
  timeline
503
510
  .point(4000)
504
- .listen(
511
+ .apply(
505
512
  event => element.classList.toggle(
506
513
  "visible",
507
514
  event.direction > 0
@@ -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`.
@@ -639,9 +650,9 @@ range
639
650
  .fork(branch => {
640
651
  branch
641
652
  .map(s => `Loading: ${s}`)
642
- .listen(s => document.title = s)
653
+ .apply(s => document.title = s)
643
654
  })
644
- .listen(v => progressBar.style.width = v);
655
+ .apply(v => progressBar.style.width = v);
645
656
  ```
646
657
 
647
658
 
@@ -651,7 +662,7 @@ range
651
662
 
652
663
  #### Methods
653
664
 
654
- ##### `listen(handler: Handler<T>): UnsubscribeFunc`
665
+ ##### `apply(handler: Handler<T>): UnsubscribeFunc`
655
666
 
656
667
  Attaches a handler to the emitter and returns a function that will unsubscribe the handler.
657
668
 
@@ -685,9 +696,9 @@ range
685
696
  .fork(branch => {
686
697
  branch
687
698
  .map(s => `Loading: ${s}`)
688
- .listen(s => document.title = s)
699
+ .apply(s => document.title = s)
689
700
  })
690
- .listen(v => progressBar.style.width = v);
701
+ .apply(v => progressBar.style.width = v);
691
702
  ```
692
703
 
693
704
 
@@ -16,11 +16,17 @@ export declare class Emitter<T> {
16
16
  */
17
17
  protected redirect: (listen: ListenFunc<T>) => Emitter<T>;
18
18
  /**
19
- * Registers a function to receive emitted values
19
+ * Compatibility alias for `apply()` - registers a function to receive emitted values
20
20
  * @param handler
21
21
  * @returns A function to deregister the handler
22
22
  */
23
23
  listen(handler: Handler<T>): UnsubscribeFunc;
24
+ /**
25
+ * Registers a function to receive emitted values
26
+ * @param handler
27
+ * @returns A function to deregister the handler
28
+ */
29
+ apply(handler: Handler<T>): UnsubscribeFunc;
24
30
  /**
25
31
  * Creates a chainable emitter that applies arbitrary transformation to values emitted by its parent
26
32
  * @param mapFunc
@@ -65,9 +71,9 @@ export declare class Emitter<T> {
65
71
  * .fork(branch => {
66
72
  * branch
67
73
  * .map(s => `Loading: ${s}`)
68
- * .listen(s => document.title = s)
74
+ * .apply(s => document.title = s)
69
75
  * })
70
- * .listen(v => progressBar.style.width = v);
76
+ * .apply(v => progressBar.style.width = v);
71
77
  * ```
72
78
  * @param cb
73
79
  */
@@ -106,7 +112,7 @@ export declare class RangeProgression extends Emitter<number> {
106
112
  * ```ts
107
113
  * range
108
114
  * .tween("0px 0px 0px #0000", "4px 4px 8px #0005")
109
- * .listen(s => element.style.textShadow = s);
115
+ * .apply(s => element.style.textShadow = s);
110
116
  * ```
111
117
  *
112
118
  * @param from Value to interpolate from
@@ -125,6 +131,20 @@ export declare class RangeProgression extends Emitter<number> {
125
131
  */
126
132
  tween<T extends Tweenable>(from: T, to: T): Emitter<T>;
127
133
  tween<T extends BlendableWith<T, R>, R>(from: T, to: R): Emitter<T>;
134
+ /**
135
+ * Creates a chainable emitter that takes a value from an array according to progression
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * range
140
+ * .sample(["a", "b", "c"])
141
+ * .apply(v => console.log(v));
142
+ * // logs 'b' when a seek lands halfway through range
143
+ * ```
144
+ * @param source array to sample
145
+ * @returns Listenable: emits the sampled values
146
+ */
147
+ sample<T>(source: ArrayLike<T>): Emitter<T>;
128
148
  /**
129
149
  * Creates a chainable progress emitter that quantises progress, as emitted by its parent, to the nearest of `steps` discrete values.
130
150
  *
@@ -15,7 +15,7 @@ export class Emitter {
15
15
  this.redirect = (listen) => new Emitter(listen);
16
16
  }
17
17
  /**
18
- * Registers a function to receive emitted values
18
+ * Compatibility alias for `apply()` - registers a function to receive emitted values
19
19
  * @param handler
20
20
  * @returns A function to deregister the handler
21
21
  */
@@ -24,6 +24,16 @@ export class Emitter {
24
24
  handler(value);
25
25
  });
26
26
  }
27
+ /**
28
+ * Registers a function to receive emitted values
29
+ * @param handler
30
+ * @returns A function to deregister the handler
31
+ */
32
+ apply(handler) {
33
+ return this.onListen((value) => {
34
+ handler(value);
35
+ });
36
+ }
27
37
  /**
28
38
  * Creates a chainable emitter that applies arbitrary transformation to values emitted by its parent
29
39
  * @param mapFunc
@@ -111,9 +121,9 @@ export class Emitter {
111
121
  * .fork(branch => {
112
122
  * branch
113
123
  * .map(s => `Loading: ${s}`)
114
- * .listen(s => document.title = s)
124
+ * .apply(s => document.title = s)
115
125
  * })
116
- * .listen(v => progressBar.style.width = v);
126
+ * .apply(v => progressBar.style.width = v);
117
127
  * ```
118
128
  * @param cb
119
129
  */
@@ -141,6 +151,26 @@ export class RangeProgression extends Emitter {
141
151
  const tween = createTween(from, to);
142
152
  return new Emitter(handler => this.onListen(progress => handler(tween(progress))));
143
153
  }
154
+ /**
155
+ * Creates a chainable emitter that takes a value from an array according to progression
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * range
160
+ * .sample(["a", "b", "c"])
161
+ * .apply(v => console.log(v));
162
+ * // logs 'b' when a seek lands halfway through range
163
+ * ```
164
+ * @param source array to sample
165
+ * @returns Listenable: emits the sampled values
166
+ */
167
+ sample(source) {
168
+ return new Emitter(handler => this.onListen(progress => {
169
+ const clampedProgress = clamp(progress);
170
+ const index = Math.floor(clampedProgress * (source.length - 1));
171
+ handler(source[index]);
172
+ }));
173
+ }
144
174
  /**
145
175
  * Creates a chainable progress emitter that quantises progress, as emitted by its parent, to the nearest of `steps` discrete values.
146
176
  *
@@ -154,7 +184,7 @@ export class RangeProgression extends Emitter {
154
184
  }
155
185
  return new RangeProgression(handler => this.onListen(progress => {
156
186
  const snapped = Math.round(progress * steps) / steps;
157
- handler(clamp(snapped, 0, 1));
187
+ handler(snapped);
158
188
  }));
159
189
  }
160
190
  /**
@@ -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
  }
@@ -32,8 +32,22 @@ export declare class TimelineRange extends RangeProgression {
32
32
  */
33
33
  spread(count: number): TimelinePoint[];
34
34
  /**
35
- * Progresses the Timeline across the range
36
- * @param easer
35
+ * Creates the specified number of ranges, each of `(parent.duration / count)` duration, spread
36
+ * evenly over this range
37
+ * @param count Number of sub-ranges to create
38
+ * @returns Array of sub-ranges
39
+ */
40
+ subdivide(count: number): TimelineRange[];
41
+ /**
42
+ * Creates a new range by offsetting the parent by a given time delta
43
+ * @param delta
44
+ * @returns Offset range
45
+ */
46
+ shift(delta: number): TimelineRange;
47
+ /**
48
+ * Progresses the Timeline across the range at 1000 units per second
49
+ * @param easer Optional easing function
50
+ * @returns Promise, resolved when the end is reached
37
51
  */
38
52
  play(easer?: Easer | keyof typeof easers): Promise<void>;
39
53
  /**
package/internal/range.js CHANGED
@@ -44,8 +44,27 @@ export class TimelineRange extends RangeProgression {
44
44
  ];
45
45
  }
46
46
  /**
47
- * Progresses the Timeline across the range
48
- * @param easer
47
+ * Creates the specified number of ranges, each of `(parent.duration / count)` duration, spread
48
+ * evenly over this range
49
+ * @param count Number of sub-ranges to create
50
+ * @returns Array of sub-ranges
51
+ */
52
+ subdivide(count) {
53
+ const duration = this.duration / count;
54
+ return Array.from({ length: count }, (_, i) => this.timeline.range(this.startPosition + i * duration, duration));
55
+ }
56
+ /**
57
+ * Creates a new range by offsetting the parent by a given time delta
58
+ * @param delta
59
+ * @returns Offset range
60
+ */
61
+ shift(delta) {
62
+ return this.timeline.range(this.startPosition + delta, this.duration);
63
+ }
64
+ /**
65
+ * Progresses the Timeline across the range at 1000 units per second
66
+ * @param easer Optional easing function
67
+ * @returns Promise, resolved when the end is reached
49
68
  */
50
69
  play(easer) {
51
70
  this.timeline.pause();
@@ -78,7 +97,7 @@ export class TimelineRange extends RangeProgression {
78
97
  */
79
98
  scale(factor, anchor = 0) {
80
99
  if (factor <= 0) {
81
- throw new RangeError('scale factor must be > 0');
100
+ throw new RangeError('Scale factor must be > 0');
82
101
  }
83
102
  const clampedAnchor = clamp(anchor, 0, 1);
84
103
  const oldLen = this.endPosition - this.startPosition;
@@ -184,7 +184,7 @@ export class Timeline {
184
184
  if (this.smoothSeeker !== null) {
185
185
  this.smoothSeeker.pause();
186
186
  // ensure any awaits are resolved for the previous seek
187
- this.smoothSeeker.seek(this.smoothSeeker.end);
187
+ this.smoothSeeker.end.seek();
188
188
  this.smoothSeeker = null;
189
189
  }
190
190
  if (duration === 0) {
@@ -193,8 +193,8 @@ export class Timeline {
193
193
  }
194
194
  const seeker = new Timeline(true);
195
195
  this.smoothSeeker = seeker;
196
- seeker.range(0, duration).ease(easer).tween(this.currentTime, toPosition).listen(v => this.seekDirect(v));
197
- return new Promise(r => seeker.end.listen(() => r()));
196
+ seeker.range(0, duration).ease(easer).tween(this.currentTime, toPosition).apply(v => this.seekDirect(v));
197
+ return new Promise(r => seeker.end.apply(() => r()));
198
198
  }
199
199
  seekDirect(toPosition) {
200
200
  const fromPosition = this._currentTime;
@@ -321,7 +321,7 @@ export class Timeline {
321
321
  const duration = typeof durationOrToPoint == "number"
322
322
  ? durationOrToPoint
323
323
  : (durationOrToPoint.position - startPosition);
324
- this.range(startPosition, duration).ease(easer).tween(from, to).listen(apply);
324
+ this.range(startPosition, duration).ease(easer).tween(from, to).apply(apply);
325
325
  return this.createChainingInterface(startPosition + duration);
326
326
  }
327
327
  at(position, action, reverse) {
@@ -329,7 +329,7 @@ export class Timeline {
329
329
  if (reverse === true)
330
330
  reverse = action;
331
331
  if (action)
332
- point.listen(reverse
332
+ point.apply(reverse
333
333
  ? (event => event.direction < 0 ? reverse() : action)
334
334
  : action);
335
335
  return this.createChainingInterface(point.position);
@@ -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.1.0",
4
4
  "repository": {
5
5
  "url": "https://github.com/tiadrop/timeline",
6
6
  "type": "github"