@xtia/timeline 1.1.3 → 1.1.5
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 +86 -32
- package/internal/emitters.d.ts +8 -5
- package/internal/emitters.js +44 -34
- package/internal/point.d.ts +1 -1
- package/internal/point.js +10 -13
- package/internal/range.d.ts +11 -9
- package/internal/range.js +20 -14
- package/internal/timeline.d.ts +14 -7
- package/internal/timeline.js +85 -78
- package/internal/tween.d.ts +1 -1
- package/internal/tween.js +11 -4
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@ Timeline is a type-safe, seekable, deterministic choreography system that can co
|
|
|
6
6
|
|
|
7
7
|
* [API Reference](#reference)
|
|
8
8
|
* [Playground](https://stackblitz.com/edit/timeline-string-tween?file=src%2Fmain.ts)
|
|
9
|
+
* [Intro by HL](https://codepen.io/H-L-the-lessful/full/vELdyvB)
|
|
9
10
|
|
|
10
11
|
## Basic Use:
|
|
11
12
|
|
|
@@ -113,13 +114,11 @@ const urlEmitter = filenameEmitter
|
|
|
113
114
|
|
|
114
115
|
```
|
|
115
116
|
|
|
116
|
-
Range objects also
|
|
117
|
+
Range objects also be passed to `Timeline`'s `play()` method to play through that particular range:
|
|
117
118
|
|
|
118
119
|
```ts
|
|
119
|
-
// play through the first
|
|
120
|
-
await timeline
|
|
121
|
-
.range(0, 2000)
|
|
122
|
-
.play();
|
|
120
|
+
// play through the first 5 seconds of the Timeline at 1000 units/s
|
|
121
|
+
await timeline.play(firstFiveSeconds);
|
|
123
122
|
```
|
|
124
123
|
|
|
125
124
|
Custom easers can be passed to `ease()` as `(progress: number) => number`:
|
|
@@ -356,6 +355,27 @@ Despite the massive overhaul, the previous API is present and expanded and upgra
|
|
|
356
355
|
|
|
357
356
|
## Reference
|
|
358
357
|
|
|
358
|
+
### Contents
|
|
359
|
+
|
|
360
|
+
#### Functions
|
|
361
|
+
|
|
362
|
+
* [`animate`](#animateduration-function)
|
|
363
|
+
|
|
364
|
+
#### Classes
|
|
365
|
+
|
|
366
|
+
* [`Timeline`](#timeline-class)
|
|
367
|
+
* [`TimelinePoint`](#timelinepoint-class)
|
|
368
|
+
* [`TimelineRange`](#timelinerange-class)
|
|
369
|
+
* [`RangeProgression`](#rangeprogression-class)
|
|
370
|
+
* [`Emitter<T>`](#emittert-class)
|
|
371
|
+
|
|
372
|
+
#### Interfaces
|
|
373
|
+
|
|
374
|
+
* [`PointEvent`](#pointevent-interface)
|
|
375
|
+
* [`ChainingInterface`](#chaininginterface-interface)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
|
|
359
379
|
### `Timeline` class
|
|
360
380
|
|
|
361
381
|
A self-contained collection of points and ranges that trigger events as the Timeline seeks to and through them.
|
|
@@ -374,17 +394,17 @@ Controls the speed at which a Timeline will progress when driven by the `play()`
|
|
|
374
394
|
|
|
375
395
|
Returns true if the Timeline is actively being driven by the `play()` method (including by autoplay).
|
|
376
396
|
|
|
377
|
-
##### `end: `[`TimelinePoint`](#timelinepoint-
|
|
397
|
+
##### `end: `[`TimelinePoint`](#timelinepoint-class)
|
|
378
398
|
|
|
379
399
|
Returns the **current** final point in the Timeline.
|
|
380
400
|
|
|
381
|
-
##### `start: `[`TimelinePoint`](#timelinepoint-
|
|
401
|
+
##### `start: `[`TimelinePoint`](#timelinepoint-class)
|
|
382
402
|
|
|
383
403
|
Returns a point representing position 0.
|
|
384
404
|
|
|
385
405
|
#### Methods
|
|
386
406
|
|
|
387
|
-
##### `point(position): `[`TimelinePoint`](#timelinepoint-
|
|
407
|
+
##### `point(position): `[`TimelinePoint`](#timelinepoint-class)
|
|
388
408
|
|
|
389
409
|
Returns a point that represents a specific position on the Timeline.
|
|
390
410
|
|
|
@@ -392,7 +412,7 @@ If `position` is greater than that Timeline's end-position, the end-position wil
|
|
|
392
412
|
|
|
393
413
|
*Note*, for deterministic consistency, points will be triggered if a forward-moving seek lands exactly on the point's position (or passes it entirely), while a backward-moving seek will trigger points that are passed or moved from.
|
|
394
414
|
|
|
395
|
-
##### `range(start, duration): `[`TimelineRange`](#timelinerange-
|
|
415
|
+
##### `range(start, duration): `[`TimelineRange`](#timelinerange-class)
|
|
396
416
|
|
|
397
417
|
Returns a range that represents a section of the Timeline.
|
|
398
418
|
|
|
@@ -404,7 +424,9 @@ If `start` is omitted, the range will start at 0 and represent the full **curren
|
|
|
404
424
|
|
|
405
425
|
##### `seek(toPosition): void`
|
|
406
426
|
|
|
407
|
-
Sets the Timeline's internal position (`currentTime`), triggering in chronological order listeners attached to any [`TimelinePoint`](#timelinepoint-
|
|
427
|
+
Sets the Timeline's internal position (`currentTime`), triggering in chronological order listeners attached to any [`TimelinePoint`](#timelinepoint-class) or [`TimelineRange`](#timelinerange-class) that are passed or landed on.
|
|
428
|
+
|
|
429
|
+
`toPosition` may be a number or a [`TimelinePoint`](#timelinepoint-class).
|
|
408
430
|
|
|
409
431
|
##### `seek(toPosition, duration, easer?): Promise<void>`
|
|
410
432
|
|
|
@@ -422,9 +444,13 @@ Begins playing through the Timeline, from its current position, at (1000 × `tim
|
|
|
422
444
|
|
|
423
445
|
Begins playing through the Timeline, from its current position, at (1000 × `timeScale`) units per second, updating `fps` times per second.
|
|
424
446
|
|
|
447
|
+
##### `play(range, easer?): Promise<void>`
|
|
448
|
+
|
|
449
|
+
If a [`TimelineRange`](#timelinerange-class) is passed, the Timeline will play through that range at 1000 units per second, following the rules of a [smooth seek](#seektoposition-duration-easer-promisevoid).
|
|
450
|
+
|
|
425
451
|
##### `tween<T>(start, duration, apply, from, to, easer?): `[`ChainingInterface`](#chaininginterface-interface)
|
|
426
452
|
|
|
427
|
-
Creates a [`TimelineRange`](#timelinerange-
|
|
453
|
+
Creates a [`TimelineRange`](#timelinerange-class) and attaches a tweening listener.
|
|
428
454
|
|
|
429
455
|
Equivalent to
|
|
430
456
|
|
|
@@ -438,24 +464,46 @@ timeline
|
|
|
438
464
|
|
|
439
465
|
Returns a [`ChainingInterface`](#chaininginterface-interface) representing the point at which the tween ends.
|
|
440
466
|
|
|
467
|
+
##### `apply(handler)`
|
|
468
|
+
|
|
469
|
+
Registers a handler to be invoked on every seek, after points and ranges are applied.
|
|
470
|
+
|
|
471
|
+
This is useful for systems that use Timeline's point and range emissions to manipulate state that is to be applied *at once* to another system.
|
|
472
|
+
|
|
473
|
+
```ts
|
|
474
|
+
// don't wastefully render the scene for every entity update
|
|
475
|
+
timeline
|
|
476
|
+
.range(0, 1000)
|
|
477
|
+
.tween(10, 30)
|
|
478
|
+
.apply(v => scene.hero.x = v);
|
|
479
|
+
timeline
|
|
480
|
+
.range(500, 1000)
|
|
481
|
+
.tween(15, 50)
|
|
482
|
+
.apply(v => scene.monster.x = v);
|
|
483
|
+
// render when all updates for a frame are done:
|
|
484
|
+
timeline.apply(() => renderScene(scene));
|
|
485
|
+
```
|
|
486
|
+
|
|
441
487
|
##### `tween<T>(start, end, apply, from, to, easer?): `[`ChainingInterface`](#chaininginterface-interface)
|
|
442
488
|
|
|
443
|
-
As above, but if the second argument is a [`TimelinePoint`](#timelinepoint-
|
|
489
|
+
As above, but if the second argument is a [`TimelinePoint`](#timelinepoint-class), it will specify when on the Timeline the tween will *end*.
|
|
444
490
|
|
|
445
491
|
##### `at(position, apply, reverse?): `[`ChainingInterface`](#chaininginterface-interface)
|
|
446
492
|
|
|
447
|
-
Creates a [`TimelinePoint`](#timelinepoint-
|
|
493
|
+
Creates a [`TimelinePoint`](#timelinepoint-class) and attaches a listener that will trigger when the Timeline seeks past or to that point.
|
|
448
494
|
|
|
449
495
|
If `reverse` is a function, that will be called instead of `apply` when the seek that triggered the event was moving backwards. If `reverse` is `true`, `apply` will be called regardless of which direction the seek moved. If `reverse` is false or omitted, this listener will ignore backward-moving seeks.
|
|
450
496
|
|
|
451
497
|
|
|
452
498
|
|
|
453
499
|
|
|
454
|
-
### `TimelinePoint`
|
|
500
|
+
### `TimelinePoint` class
|
|
455
501
|
|
|
456
502
|
Represents a single point on a [`Timeline`](#timeline-class).
|
|
457
503
|
|
|
458
|
-
|
|
504
|
+
This class is not meant to be constructed directly; instances are created with [`Timeline.point()`](#pointposition-timelinepoint).
|
|
505
|
+
|
|
506
|
+
##### Inherits [`Emitter<PointEvent>`](#emittert-class)
|
|
459
507
|
|
|
460
508
|
Listeners will be invoked with a [`PointEvent`](#pointevent-interface) when a seek passes or lands on the point.
|
|
461
509
|
|
|
@@ -471,11 +519,11 @@ This point's position on the Timeline.
|
|
|
471
519
|
|
|
472
520
|
##### `range(duration): TimelineRange`
|
|
473
521
|
|
|
474
|
-
Creates a [`TimelineRange`](#timelinerange-
|
|
522
|
+
Creates a [`TimelineRange`](#timelinerange-class) on the Timeline to which the point belongs, of the specified duration.
|
|
475
523
|
|
|
476
524
|
##### `to(endPoint): TimelineRange`
|
|
477
525
|
|
|
478
|
-
Creates a [`TimelineRange`](#timelinerange-
|
|
526
|
+
Creates a [`TimelineRange`](#timelinerange-class) on the Timeline to which the point belongs, ending at the specified point.
|
|
479
527
|
|
|
480
528
|
##### `delta(timeOffset): TimelinePoint`
|
|
481
529
|
|
|
@@ -520,7 +568,7 @@ point
|
|
|
520
568
|
|
|
521
569
|
### `PointEvent` interface
|
|
522
570
|
|
|
523
|
-
Provides information relevant to [`TimelinePoint`](#timelinepoint-
|
|
571
|
+
Provides information relevant to [`TimelinePoint`](#timelinepoint-class) events.
|
|
524
572
|
|
|
525
573
|
#### Properties
|
|
526
574
|
|
|
@@ -544,21 +592,23 @@ timeline
|
|
|
544
592
|
|
|
545
593
|
|
|
546
594
|
|
|
547
|
-
### `TimelineRange`
|
|
595
|
+
### `TimelineRange` class
|
|
548
596
|
|
|
549
597
|
Represents a fixed-length, fixed position section of a [`Timeline`](#timeline-class).
|
|
550
598
|
|
|
551
|
-
|
|
599
|
+
This class is not meant to be constructed directly; instances are created with [`Timeline.range()`](#rangestart-duration-timelinerange).
|
|
600
|
+
|
|
601
|
+
##### Inherits [`RangeProgression`](#rangeprogression-class)
|
|
552
602
|
|
|
553
603
|
Emits a normalised progression (0..1) of the range when the parent Timeline seeks over or into it.
|
|
554
604
|
|
|
555
605
|
#### Properties
|
|
556
606
|
|
|
557
|
-
##### `start: `[`TimelinePoint`](#timelinepoint-
|
|
607
|
+
##### `start: `[`TimelinePoint`](#timelinepoint-class)
|
|
558
608
|
|
|
559
609
|
The point on the Timeline at which this range starts.
|
|
560
610
|
|
|
561
|
-
##### `end: `[`TimelinePoint`](#timelinepoint-
|
|
611
|
+
##### `end: `[`TimelinePoint`](#timelinepoint-class)
|
|
562
612
|
|
|
563
613
|
The point on the Timeline at which this range ends.
|
|
564
614
|
|
|
@@ -572,7 +622,7 @@ The length of the range.
|
|
|
572
622
|
|
|
573
623
|
Creates two ranges representing two distinct sections of the parent. `position` is relative to the parent's start.
|
|
574
624
|
|
|
575
|
-
##### `spread(count): `[`TimelinePoint`](#timelinepoint-
|
|
625
|
+
##### `spread(count): `[`TimelinePoint`](#timelinepoint-class)[]
|
|
576
626
|
|
|
577
627
|
Creates and returns `count` points spread evenly over the range.
|
|
578
628
|
|
|
@@ -600,7 +650,7 @@ Creates a new range by offsetting the parent by a given time delta.
|
|
|
600
650
|
|
|
601
651
|
##### `contains(point): boolean`
|
|
602
652
|
|
|
603
|
-
Returns true if the given [`TimelinePoint`](#timelinepoint-
|
|
653
|
+
Returns true if the given [`TimelinePoint`](#timelinepoint-class) sits within this range.
|
|
604
654
|
|
|
605
655
|
##### `overlaps(range): boolean`
|
|
606
656
|
|
|
@@ -609,11 +659,13 @@ Returns true if the given range overlaps with this range.
|
|
|
609
659
|
|
|
610
660
|
|
|
611
661
|
|
|
612
|
-
### `RangeProgression`
|
|
662
|
+
### `RangeProgression` class
|
|
663
|
+
|
|
664
|
+
Represents a step in an immutable [`TimelineRange`](#timelinerange-class) event transformation pipeline.
|
|
613
665
|
|
|
614
|
-
|
|
666
|
+
This class is not meant to be constructed directly; instances are created by various transformation methods of [`TimelineRange`](#timelinerange-class).
|
|
615
667
|
|
|
616
|
-
##### Inherits [`Emitter<number>`](#emittert-
|
|
668
|
+
##### Inherits [`Emitter<number>`](#emittert-class)
|
|
617
669
|
|
|
618
670
|
Listeners will be invoked when a seek passes or lands within a range.
|
|
619
671
|
|
|
@@ -623,7 +675,7 @@ Listeners will be invoked when a seek passes or lands within a range.
|
|
|
623
675
|
|
|
624
676
|
Creates an emitter that applies an easing function to parent emissions.
|
|
625
677
|
|
|
626
|
-
##### `tween<T>(from, to): `[`Emitter<T>`](#emittert-
|
|
678
|
+
##### `tween<T>(from, to): `[`Emitter<T>`](#emittert-class)
|
|
627
679
|
|
|
628
680
|
Creates an emitter blends two values, biased by progression emitted by the parent.
|
|
629
681
|
|
|
@@ -637,7 +689,7 @@ blend(from: this, to: this, progress: number): this
|
|
|
637
689
|
|
|
638
690
|
Creates an emitter that quantises progression emitted by the parent to the nearest of `steps` discrete values.
|
|
639
691
|
|
|
640
|
-
##### `sample<T>(values: ArrayLike<T>): `[`Emitter<T>`](#emittert-
|
|
692
|
+
##### `sample<T>(values: ArrayLike<T>): `[`Emitter<T>`](#emittert-class)
|
|
641
693
|
|
|
642
694
|
Creates an emitter that emits values from an array according to progression.
|
|
643
695
|
|
|
@@ -705,7 +757,7 @@ range
|
|
|
705
757
|
|
|
706
758
|
|
|
707
759
|
|
|
708
|
-
### `Emitter<T>`
|
|
760
|
+
### `Emitter<T>` class
|
|
709
761
|
|
|
710
762
|
#### Methods
|
|
711
763
|
|
|
@@ -713,6 +765,8 @@ range
|
|
|
713
765
|
|
|
714
766
|
Attaches a handler to the emitter and returns a function that will unsubscribe the handler.
|
|
715
767
|
|
|
768
|
+
This class is not meant to be constructed directly; instances are created by transformation methods.
|
|
769
|
+
|
|
716
770
|
##### `map<R>(mapFunc: (value: T) => R): Emitter<R>`
|
|
717
771
|
|
|
718
772
|
Creates an emitter that performs an arbitrary transformation.
|
|
@@ -753,7 +807,7 @@ range
|
|
|
753
807
|
|
|
754
808
|
### `animate(duration)` function
|
|
755
809
|
|
|
756
|
-
Creates and returns a [`TimelineRange`](#timelinerange-
|
|
810
|
+
Creates and returns a [`TimelineRange`](#timelinerange-class) that will automatically play over `duration` milliseconds.
|
|
757
811
|
|
|
758
812
|
### `ChainingInterface` interface
|
|
759
813
|
|
|
@@ -770,7 +824,7 @@ timeline
|
|
|
770
824
|
|
|
771
825
|
#### Properties
|
|
772
826
|
|
|
773
|
-
##### `end: `[`TimelinePoint`](#timelinepoint-
|
|
827
|
+
##### `end: `[`TimelinePoint`](#timelinepoint-class)
|
|
774
828
|
|
|
775
829
|
The point on the Timeline at which the effect of the previous chained call ends.
|
|
776
830
|
|
package/internal/emitters.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare class Emitter<T> {
|
|
|
14
14
|
* @param listen
|
|
15
15
|
* @returns {this}
|
|
16
16
|
*/
|
|
17
|
-
protected redirect
|
|
17
|
+
protected redirect(listen: ListenFunc<T>): Emitter<T>;
|
|
18
18
|
/**
|
|
19
19
|
* Compatibility alias for `apply()` - registers a function to receive emitted values
|
|
20
20
|
* @param handler
|
|
@@ -68,11 +68,10 @@ export declare class Emitter<T> {
|
|
|
68
68
|
* ```ts
|
|
69
69
|
* range
|
|
70
70
|
* .tween("0%", "100%")
|
|
71
|
-
* .fork(branch =>
|
|
72
|
-
* branch
|
|
71
|
+
* .fork(branch => branch
|
|
73
72
|
* .map(s => `Loading: ${s}`)
|
|
74
73
|
* .apply(s => document.title = s)
|
|
75
|
-
*
|
|
74
|
+
* )
|
|
76
75
|
* .apply(v => progressBar.style.width = v);
|
|
77
76
|
* ```
|
|
78
77
|
* @param cb
|
|
@@ -80,7 +79,7 @@ export declare class Emitter<T> {
|
|
|
80
79
|
fork(cb: (branch: this) => void): this;
|
|
81
80
|
}
|
|
82
81
|
export declare class RangeProgression extends Emitter<number> {
|
|
83
|
-
protected redirect
|
|
82
|
+
protected redirect(listen: ListenFunc<number>): RangeProgression;
|
|
84
83
|
/**
|
|
85
84
|
* Creates a chainable progress emitter that applies an easing function to its parent's emitted values
|
|
86
85
|
*
|
|
@@ -216,4 +215,8 @@ export declare class RangeProgression extends Emitter<number> {
|
|
|
216
215
|
*/
|
|
217
216
|
offset(delta: number): RangeProgression;
|
|
218
217
|
}
|
|
218
|
+
export declare function createListenable<T>(onAddFirst?: () => void, onRemoveLast?: () => void): {
|
|
219
|
+
listen: (fn: (v: T) => void) => UnsubscribeFunc;
|
|
220
|
+
emit: (value: T) => void;
|
|
221
|
+
};
|
|
219
222
|
export {};
|
package/internal/emitters.js
CHANGED
|
@@ -4,15 +4,17 @@ import { clamp } from "./utils";
|
|
|
4
4
|
export class Emitter {
|
|
5
5
|
constructor(onListen) {
|
|
6
6
|
this.onListen = onListen;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Used by tap() to create a clone of an Emitter with a redirected onListen
|
|
10
|
+
*
|
|
11
|
+
* Should be overridden in all Emitter subclasses
|
|
12
|
+
* @see {@link TimelineRange.redirect}
|
|
13
|
+
* @param listen
|
|
14
|
+
* @returns {this}
|
|
15
|
+
*/
|
|
16
|
+
redirect(listen) {
|
|
17
|
+
return new Emitter(listen);
|
|
16
18
|
}
|
|
17
19
|
/**
|
|
18
20
|
* Compatibility alias for `apply()` - registers a function to receive emitted values
|
|
@@ -88,26 +90,15 @@ export class Emitter {
|
|
|
88
90
|
* @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
|
|
89
91
|
*/
|
|
90
92
|
tap(cb) {
|
|
91
|
-
const listeners = [];
|
|
92
93
|
let parentUnsubscribe = null;
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return () => {
|
|
102
|
-
const idx = listeners.indexOf(handler);
|
|
103
|
-
listeners.splice(idx, 1);
|
|
104
|
-
if (listeners.length === 0 && parentUnsubscribe) {
|
|
105
|
-
parentUnsubscribe();
|
|
106
|
-
parentUnsubscribe = null;
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
};
|
|
110
|
-
return this.redirect(tappedListen);
|
|
94
|
+
const { emit, listen } = createListenable(() => parentUnsubscribe = this.onListen(value => {
|
|
95
|
+
cb(value);
|
|
96
|
+
emit(value);
|
|
97
|
+
}), () => {
|
|
98
|
+
parentUnsubscribe();
|
|
99
|
+
parentUnsubscribe = null;
|
|
100
|
+
});
|
|
101
|
+
return this.redirect(listen);
|
|
111
102
|
}
|
|
112
103
|
/**
|
|
113
104
|
* Immediately passes this emitter to a callback and returns this emitter
|
|
@@ -118,11 +109,10 @@ export class Emitter {
|
|
|
118
109
|
* ```ts
|
|
119
110
|
* range
|
|
120
111
|
* .tween("0%", "100%")
|
|
121
|
-
* .fork(branch =>
|
|
122
|
-
* branch
|
|
112
|
+
* .fork(branch => branch
|
|
123
113
|
* .map(s => `Loading: ${s}`)
|
|
124
114
|
* .apply(s => document.title = s)
|
|
125
|
-
*
|
|
115
|
+
* )
|
|
126
116
|
* .apply(v => progressBar.style.width = v);
|
|
127
117
|
* ```
|
|
128
118
|
* @param cb
|
|
@@ -133,9 +123,8 @@ export class Emitter {
|
|
|
133
123
|
}
|
|
134
124
|
}
|
|
135
125
|
export class RangeProgression extends Emitter {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
this.redirect = (listen) => new RangeProgression(listen);
|
|
126
|
+
redirect(listen) {
|
|
127
|
+
return new RangeProgression(listen);
|
|
139
128
|
}
|
|
140
129
|
ease(easer) {
|
|
141
130
|
if (!easer)
|
|
@@ -279,3 +268,24 @@ export class RangeProgression extends Emitter {
|
|
|
279
268
|
return new RangeProgression(handler => this.onListen(value => handler((value + delta) % 1)));
|
|
280
269
|
}
|
|
281
270
|
}
|
|
271
|
+
export function createListenable(onAddFirst, onRemoveLast) {
|
|
272
|
+
const handlers = [];
|
|
273
|
+
const addListener = (fn) => {
|
|
274
|
+
const unique = (v) => fn(v);
|
|
275
|
+
handlers.push(unique);
|
|
276
|
+
if (onAddFirst && handlers.length == 1)
|
|
277
|
+
onAddFirst();
|
|
278
|
+
return () => {
|
|
279
|
+
const idx = handlers.indexOf(unique);
|
|
280
|
+
if (idx === -1)
|
|
281
|
+
throw new Error("Handler already unsubscribed");
|
|
282
|
+
handlers.splice(idx, 1);
|
|
283
|
+
if (onRemoveLast && handlers.length == 0)
|
|
284
|
+
onRemoveLast();
|
|
285
|
+
};
|
|
286
|
+
};
|
|
287
|
+
return {
|
|
288
|
+
listen: addListener,
|
|
289
|
+
emit: (value) => handlers.forEach(h => h(value)),
|
|
290
|
+
};
|
|
291
|
+
}
|
package/internal/point.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export declare class TimelinePoint extends Emitter<PointEvent> {
|
|
|
17
17
|
* The point's absolute position on the Timeline
|
|
18
18
|
*/
|
|
19
19
|
position: number);
|
|
20
|
-
protected redirect
|
|
20
|
+
protected redirect(listen: ListenFunc<PointEvent>): TimelinePoint;
|
|
21
21
|
/**
|
|
22
22
|
* Creates a range on the Timeline, with a given duration, starting at this point
|
|
23
23
|
* @param duration
|
package/internal/point.js
CHANGED
|
@@ -9,7 +9,9 @@ export class TimelinePoint extends Emitter {
|
|
|
9
9
|
super(onListen);
|
|
10
10
|
this.timeline = timeline;
|
|
11
11
|
this.position = position;
|
|
12
|
-
|
|
12
|
+
}
|
|
13
|
+
redirect(listen) {
|
|
14
|
+
return new TimelinePoint(listen, this.timeline, this.position);
|
|
13
15
|
}
|
|
14
16
|
/**
|
|
15
17
|
* Creates a range on the Timeline, with a given duration, starting at this point
|
|
@@ -56,20 +58,15 @@ export class TimelinePoint extends Emitter {
|
|
|
56
58
|
return this.filter(-1);
|
|
57
59
|
}
|
|
58
60
|
filter(arg) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
return new Emitter(handler => {
|
|
68
|
-
return this.onListen((ev) => {
|
|
61
|
+
return new Emitter(typeof arg == "number"
|
|
62
|
+
? handler => this.onListen(ev => {
|
|
63
|
+
if (ev.direction === arg)
|
|
64
|
+
handler(ev);
|
|
65
|
+
})
|
|
66
|
+
: handler => this.onListen((ev) => {
|
|
69
67
|
if (arg(ev))
|
|
70
68
|
handler(ev);
|
|
71
|
-
});
|
|
72
|
-
});
|
|
69
|
+
}));
|
|
73
70
|
}
|
|
74
71
|
/**
|
|
75
72
|
* Creates a Promise that will be resolved when the Timeline first seeks to/past this point
|
package/internal/range.d.ts
CHANGED
|
@@ -4,19 +4,21 @@ import { TimelinePoint } from "./point";
|
|
|
4
4
|
import { Timeline } from "./timeline";
|
|
5
5
|
export declare class TimelineRange extends RangeProgression {
|
|
6
6
|
private timeline;
|
|
7
|
-
private startPosition;
|
|
8
|
-
/** The duration of this range */
|
|
9
|
-
readonly duration: number;
|
|
10
|
-
private endPosition;
|
|
11
7
|
/** The point on the Timeline at which this range begins */
|
|
12
8
|
readonly start: TimelinePoint;
|
|
13
9
|
/** The point on the Timeline at which this range ends */
|
|
14
10
|
readonly end: TimelinePoint;
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
private startPosition;
|
|
12
|
+
private endPosition;
|
|
17
13
|
/** The duration of this range */
|
|
18
|
-
duration: number
|
|
19
|
-
|
|
14
|
+
readonly duration: number;
|
|
15
|
+
/** @internal Manual construction of RangeProgression is outside of the API contract and subject to undocumented change */
|
|
16
|
+
constructor(onListen: ListenFunc<number>, timeline: Timeline,
|
|
17
|
+
/** The point on the Timeline at which this range begins */
|
|
18
|
+
start: TimelinePoint,
|
|
19
|
+
/** The point on the Timeline at which this range ends */
|
|
20
|
+
end: TimelinePoint);
|
|
21
|
+
protected redirect(listen: ListenFunc<number>): TimelineRange;
|
|
20
22
|
/**
|
|
21
23
|
* Creates two ranges by seperating one at a given point
|
|
22
24
|
* @param position Point of separation, relative to the range's start - if omitted, the range will be separated halfway
|
|
@@ -70,7 +72,7 @@ export declare class TimelineRange extends RangeProgression {
|
|
|
70
72
|
* @param point The point to check
|
|
71
73
|
* @returns true if the provided point is within the range
|
|
72
74
|
*/
|
|
73
|
-
contains(point: TimelinePoint): boolean;
|
|
75
|
+
contains(point: TimelinePoint | number): boolean;
|
|
74
76
|
/**
|
|
75
77
|
* Checks if a range is fully within this range
|
|
76
78
|
* @param range The range to check
|
package/internal/range.js
CHANGED
|
@@ -2,21 +2,21 @@ import { RangeProgression } from "./emitters";
|
|
|
2
2
|
import { TimelinePoint } from "./point";
|
|
3
3
|
export class TimelineRange extends RangeProgression {
|
|
4
4
|
/** @internal Manual construction of RangeProgression is outside of the API contract and subject to undocumented change */
|
|
5
|
-
constructor(onListen, timeline,
|
|
6
|
-
/** The
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
: onListen);
|
|
5
|
+
constructor(onListen, timeline,
|
|
6
|
+
/** The point on the Timeline at which this range begins */
|
|
7
|
+
start,
|
|
8
|
+
/** The point on the Timeline at which this range ends */
|
|
9
|
+
end) {
|
|
10
|
+
super(onListen);
|
|
13
11
|
this.timeline = timeline;
|
|
14
|
-
this.
|
|
15
|
-
this.
|
|
16
|
-
this.
|
|
17
|
-
this.endPosition =
|
|
18
|
-
this.
|
|
19
|
-
|
|
12
|
+
this.start = start;
|
|
13
|
+
this.end = end;
|
|
14
|
+
this.startPosition = start.position;
|
|
15
|
+
this.endPosition = end.position;
|
|
16
|
+
this.duration = this.endPosition - this.startPosition;
|
|
17
|
+
}
|
|
18
|
+
redirect(listen) {
|
|
19
|
+
return new TimelineRange(listen, this.timeline, this.start, this.end);
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
22
|
* Creates two ranges by seperating one at a given point
|
|
@@ -26,6 +26,9 @@ export class TimelineRange extends RangeProgression {
|
|
|
26
26
|
* @returns Tuple of two ranges
|
|
27
27
|
*/
|
|
28
28
|
bisect(position = this.duration / 2) {
|
|
29
|
+
if (position >= this.endPosition) {
|
|
30
|
+
throw new RangeError("Bisection position is beyond end of range");
|
|
31
|
+
}
|
|
29
32
|
return [
|
|
30
33
|
this.timeline.range(this.startPosition, position),
|
|
31
34
|
this.timeline.range(position + this.startPosition, this.duration - position),
|
|
@@ -93,6 +96,9 @@ export class TimelineRange extends RangeProgression {
|
|
|
93
96
|
return this.grow((factor - 1) * this.duration, anchor);
|
|
94
97
|
}
|
|
95
98
|
contains(target) {
|
|
99
|
+
if (typeof target == "number") {
|
|
100
|
+
return target >= this.startPosition && target < this.endPosition;
|
|
101
|
+
}
|
|
96
102
|
const [targetStart, targetEnd] = target instanceof TimelinePoint
|
|
97
103
|
? [target.position, target.position]
|
|
98
104
|
: [target.startPosition, target.startPosition + target.duration];
|
package/internal/timeline.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Easer, easers } from "./easing";
|
|
2
|
-
import { RangeProgression } from "./emitters";
|
|
2
|
+
import { RangeProgression, UnsubscribeFunc } from "./emitters";
|
|
3
3
|
import { TimelinePoint } from "./point";
|
|
4
4
|
import { TimelineRange } from "./range";
|
|
5
5
|
import { Tweenable } from "./tween";
|
|
@@ -10,12 +10,16 @@ declare const EndAction: {
|
|
|
10
10
|
readonly wrap: 2;
|
|
11
11
|
readonly restart: 3;
|
|
12
12
|
};
|
|
13
|
+
type Period = {
|
|
14
|
+
asMilliseconds: number;
|
|
15
|
+
};
|
|
13
16
|
/**
|
|
14
17
|
* Creates an autoplaying Timeline and returns a range from it
|
|
15
|
-
* @param
|
|
18
|
+
* @param durationMs Animation duration, in milliseconds
|
|
16
19
|
* @returns Object representing a range on a single-use, autoplaying Timeline
|
|
17
20
|
*/
|
|
18
|
-
export declare function animate(
|
|
21
|
+
export declare function animate(durationMs: number): TimelineRange;
|
|
22
|
+
export declare function animate(period: Period): TimelineRange;
|
|
19
23
|
export declare class Timeline {
|
|
20
24
|
/**
|
|
21
25
|
* Multiplies the speed at which `play()` progresses through the Timeline
|
|
@@ -37,13 +41,15 @@ export declare class Timeline {
|
|
|
37
41
|
private smoothSeeker;
|
|
38
42
|
private seeking;
|
|
39
43
|
readonly start: TimelinePoint;
|
|
40
|
-
private
|
|
44
|
+
private _frameEvents;
|
|
45
|
+
/**
|
|
46
|
+
* Registers a handler to be invoked on every seek, after points and ranges are applied
|
|
47
|
+
*/
|
|
48
|
+
apply(handler: () => void): UnsubscribeFunc;
|
|
41
49
|
private _progression;
|
|
42
50
|
/**
|
|
43
51
|
* Listenable: emits a progression value (0..1) when the Timeline's internal
|
|
44
52
|
* position changes, and when the Timeline's total duration is extended
|
|
45
|
-
*
|
|
46
|
-
* **Experimental**
|
|
47
53
|
*/
|
|
48
54
|
get progression(): RangeProgression;
|
|
49
55
|
constructor();
|
|
@@ -114,7 +120,8 @@ export declare class Timeline {
|
|
|
114
120
|
* @param easer Optional easing function for the smooth-seek process
|
|
115
121
|
* @returns A promise, resolved when the smooth-seek process finishes
|
|
116
122
|
*/
|
|
117
|
-
seek(toPosition: number | TimelinePoint,
|
|
123
|
+
seek(toPosition: number | TimelinePoint, durationMs: number, easer?: Easer | keyof typeof easers): Promise<void>;
|
|
124
|
+
seek(toPosition: number | TimelinePoint, duration: Period, easer?: Easer | keyof typeof easers): Promise<void>;
|
|
118
125
|
private seekDirect;
|
|
119
126
|
private seekPoints;
|
|
120
127
|
private seekRanges;
|
package/internal/timeline.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RangeProgression } from "./emitters";
|
|
1
|
+
import { createListenable, RangeProgression } from "./emitters";
|
|
2
2
|
import { TimelinePoint } from "./point";
|
|
3
3
|
import { TimelineRange } from "./range";
|
|
4
4
|
import { clamp } from "./utils";
|
|
@@ -9,13 +9,11 @@ const EndAction = {
|
|
|
9
9
|
wrap: 2,
|
|
10
10
|
restart: 3,
|
|
11
11
|
};
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export function animate(duration) {
|
|
18
|
-
return new Timeline(true).range(0, duration);
|
|
12
|
+
export function animate(durationMs) {
|
|
13
|
+
return new Timeline(true)
|
|
14
|
+
.range(0, typeof durationMs == "number"
|
|
15
|
+
? durationMs
|
|
16
|
+
: durationMs.asMilliseconds);
|
|
19
17
|
}
|
|
20
18
|
export class Timeline {
|
|
21
19
|
get currentTime() { return this._currentTime; }
|
|
@@ -28,16 +26,32 @@ export class Timeline {
|
|
|
28
26
|
get end() {
|
|
29
27
|
return this.point(this._endPosition);
|
|
30
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Registers a handler to be invoked on every seek, after points and ranges are applied
|
|
31
|
+
*/
|
|
32
|
+
apply(handler) {
|
|
33
|
+
if (this._frameEvents === null) {
|
|
34
|
+
const { emit, listen } = createListenable();
|
|
35
|
+
this._frameEvents = {
|
|
36
|
+
listen,
|
|
37
|
+
emit,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return this._frameEvents.listen(handler);
|
|
41
|
+
}
|
|
31
42
|
/**
|
|
32
43
|
* Listenable: emits a progression value (0..1) when the Timeline's internal
|
|
33
44
|
* position changes, and when the Timeline's total duration is extended
|
|
34
|
-
*
|
|
35
|
-
* **Experimental**
|
|
36
45
|
*/
|
|
37
46
|
get progression() {
|
|
38
|
-
if (this._progression === null)
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
if (this._progression === null) {
|
|
48
|
+
const { emit, listen } = createListenable();
|
|
49
|
+
this._progression = {
|
|
50
|
+
emitter: new TimelineProgressionEmitter(listen),
|
|
51
|
+
emit,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return this._progression.emitter;
|
|
41
55
|
}
|
|
42
56
|
constructor(autoplay = false, endAction = "pause") {
|
|
43
57
|
/**
|
|
@@ -55,7 +69,7 @@ export class Timeline {
|
|
|
55
69
|
this.smoothSeeker = null;
|
|
56
70
|
this.seeking = false;
|
|
57
71
|
this.start = this.point(0);
|
|
58
|
-
this.
|
|
72
|
+
this._frameEvents = null;
|
|
59
73
|
this._progression = null;
|
|
60
74
|
if (endAction == "loop")
|
|
61
75
|
endAction = "restart";
|
|
@@ -93,32 +107,25 @@ export class Timeline {
|
|
|
93
107
|
point(position) {
|
|
94
108
|
if (position > this._endPosition) {
|
|
95
109
|
this._endPosition = position;
|
|
96
|
-
this.
|
|
110
|
+
this._progression?.emit(this._currentTime / position);
|
|
97
111
|
}
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
};
|
|
112
|
+
const { emit, listen } = createListenable(() => this.points.push(data), () => {
|
|
113
|
+
const idx = this.points.indexOf(data);
|
|
114
|
+
this.points.splice(idx, 1);
|
|
115
|
+
});
|
|
103
116
|
const addHandler = (handler) => {
|
|
104
117
|
if (this.seeking)
|
|
105
118
|
throw new Error("Can't add a listener while seeking");
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
119
|
+
if (position == this._currentTime) {
|
|
120
|
+
emit({
|
|
121
|
+
direction: 1
|
|
122
|
+
});
|
|
110
123
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
handlers.splice(idx, 1);
|
|
117
|
-
if (handlers.length == 0) {
|
|
118
|
-
const idx = this.points.indexOf(data);
|
|
119
|
-
this.points.splice(idx, 1);
|
|
120
|
-
}
|
|
121
|
-
};
|
|
124
|
+
return listen(handler);
|
|
125
|
+
};
|
|
126
|
+
const data = {
|
|
127
|
+
emit,
|
|
128
|
+
position,
|
|
122
129
|
};
|
|
123
130
|
return new TimelinePoint(addHandler, this, position);
|
|
124
131
|
}
|
|
@@ -128,35 +135,31 @@ export class Timeline {
|
|
|
128
135
|
: start;
|
|
129
136
|
const startPosition = startPoint.position;
|
|
130
137
|
const duration = optionalDuration ?? this._endPosition - startPosition;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
138
|
+
const endPoint = this.point(startPosition + duration);
|
|
139
|
+
const { emit, listen } = createListenable(() => this.ranges.push(rangeData), () => {
|
|
140
|
+
const idx = this.ranges.indexOf(rangeData);
|
|
141
|
+
this.ranges.splice(idx, 1);
|
|
142
|
+
});
|
|
143
|
+
const rangeData = {
|
|
136
144
|
position: startPosition,
|
|
137
145
|
duration,
|
|
138
|
-
|
|
146
|
+
emit,
|
|
139
147
|
};
|
|
140
|
-
const addHandler =
|
|
141
|
-
|
|
142
|
-
throw new Error("
|
|
143
|
-
if (handlers.length == 0) {
|
|
144
|
-
this.ranges.push(range);
|
|
145
|
-
this.currentSortDirection = 0;
|
|
148
|
+
const addHandler = duration == 0
|
|
149
|
+
? () => {
|
|
150
|
+
throw new Error("Zero-duration ranges may not be listened");
|
|
146
151
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (handlers.length == 0) {
|
|
154
|
-
const idx = this.ranges.indexOf(range);
|
|
155
|
-
this.ranges.splice(idx, 1);
|
|
152
|
+
: (handler) => {
|
|
153
|
+
if (this.seeking)
|
|
154
|
+
throw new Error("Can't add a listener while seeking");
|
|
155
|
+
if (range.contains(this._currentTime)) {
|
|
156
|
+
let progress = clamp((this._currentTime - startPosition) / duration, 0, 1);
|
|
157
|
+
handler(progress);
|
|
156
158
|
}
|
|
159
|
+
return listen(handler);
|
|
157
160
|
};
|
|
158
|
-
|
|
159
|
-
return
|
|
161
|
+
const range = new TimelineRange(addHandler, this, startPoint, endPoint);
|
|
162
|
+
return range;
|
|
160
163
|
}
|
|
161
164
|
getWrappedPosition(n) {
|
|
162
165
|
if (this.endAction.type !== EndAction.wrap)
|
|
@@ -174,7 +177,10 @@ export class Timeline {
|
|
|
174
177
|
const remainder = overflow % segment;
|
|
175
178
|
return loopStart + remainder;
|
|
176
179
|
}
|
|
177
|
-
seek(to, duration
|
|
180
|
+
seek(to, duration, easer) {
|
|
181
|
+
const durationMs = typeof duration == "object"
|
|
182
|
+
? duration.asMilliseconds
|
|
183
|
+
: duration;
|
|
178
184
|
const toPosition = typeof to == "number"
|
|
179
185
|
? to
|
|
180
186
|
: to.position;
|
|
@@ -183,20 +189,26 @@ export class Timeline {
|
|
|
183
189
|
}
|
|
184
190
|
if (this.smoothSeeker !== null) {
|
|
185
191
|
this.smoothSeeker.pause();
|
|
186
|
-
// ensure any awaits are resolved for the
|
|
187
|
-
this.
|
|
192
|
+
// ensure any awaits are resolved for the interrupted seek
|
|
193
|
+
const interruptPosition = this._currentTime;
|
|
194
|
+
this.smoothSeeker.seekDirect(this.smoothSeeker.end.position);
|
|
188
195
|
this.smoothSeeker = null;
|
|
196
|
+
// and jump back to where we were interrupted
|
|
197
|
+
this.seekDirect(interruptPosition);
|
|
189
198
|
}
|
|
190
|
-
if (
|
|
199
|
+
if (!durationMs) {
|
|
200
|
+
const fromTime = this._currentTime;
|
|
191
201
|
this.seekDirect(toPosition);
|
|
192
|
-
|
|
202
|
+
this._frameEvents?.emit();
|
|
203
|
+
// only add Promise overhead if duration is explicitly 0
|
|
204
|
+
return durationMs === 0 ? Promise.resolve() : undefined;
|
|
193
205
|
}
|
|
194
206
|
const seeker = new Timeline(true);
|
|
195
207
|
this.smoothSeeker = seeker;
|
|
196
208
|
seeker
|
|
197
|
-
.range(0,
|
|
209
|
+
.range(0, durationMs)
|
|
198
210
|
.ease(easer)
|
|
199
|
-
.tween(this.
|
|
211
|
+
.tween(this._currentTime, toPosition)
|
|
200
212
|
.apply(v => this.seekDirect(v));
|
|
201
213
|
return seeker.end.promise();
|
|
202
214
|
}
|
|
@@ -242,10 +254,12 @@ export class Timeline {
|
|
|
242
254
|
pointsBetween.slice().forEach(p => {
|
|
243
255
|
this.seekRanges(p.position);
|
|
244
256
|
this._currentTime = p.position;
|
|
245
|
-
p.
|
|
257
|
+
p.emit(eventData);
|
|
246
258
|
});
|
|
247
259
|
}
|
|
248
260
|
seekRanges(to) {
|
|
261
|
+
if (this._currentTime === to)
|
|
262
|
+
return;
|
|
249
263
|
const fromTime = Math.min(this._currentTime, to);
|
|
250
264
|
const toTime = Math.max(this._currentTime, to);
|
|
251
265
|
this.ranges.slice().forEach((range) => {
|
|
@@ -253,10 +267,10 @@ export class Timeline {
|
|
|
253
267
|
const overlaps = fromTime <= rangeEnd && toTime >= range.position;
|
|
254
268
|
if (overlaps) {
|
|
255
269
|
let progress = clamp((to - range.position) / range.duration, 0, 1);
|
|
256
|
-
range.
|
|
270
|
+
range.emit(progress);
|
|
257
271
|
}
|
|
258
272
|
});
|
|
259
|
-
this.
|
|
273
|
+
this._progression?.emit(toTime / this._endPosition);
|
|
260
274
|
}
|
|
261
275
|
sortEntries(direction) {
|
|
262
276
|
this.currentSortDirection = direction;
|
|
@@ -289,7 +303,7 @@ export class Timeline {
|
|
|
289
303
|
this.currentTime += delta;
|
|
290
304
|
return;
|
|
291
305
|
}
|
|
292
|
-
// overshot; perform endAction
|
|
306
|
+
// overshot; perform restart/pause endAction
|
|
293
307
|
if (this.endAction.type == EndAction.restart) {
|
|
294
308
|
const loopRange = this.endAction.at.to(this._endPosition);
|
|
295
309
|
const loopLen = loopRange.duration;
|
|
@@ -379,15 +393,8 @@ export class Timeline {
|
|
|
379
393
|
}
|
|
380
394
|
}
|
|
381
395
|
class TimelineProgressionEmitter extends RangeProgression {
|
|
382
|
-
constructor(
|
|
383
|
-
super(
|
|
384
|
-
const unique = (n) => handler(n);
|
|
385
|
-
handlers.push(unique);
|
|
386
|
-
return () => {
|
|
387
|
-
const idx = handlers.indexOf(unique);
|
|
388
|
-
handlers.splice(idx, 1);
|
|
389
|
-
};
|
|
390
|
-
});
|
|
396
|
+
constructor(listen) {
|
|
397
|
+
super(listen);
|
|
391
398
|
}
|
|
392
399
|
}
|
|
393
400
|
const sortEvents = (a, b) => {
|
package/internal/tween.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** @internal */
|
|
2
|
-
export type Tweenable = number | number[] | string | string[] | Blendable | Blendable[];
|
|
2
|
+
export type Tweenable = number | number[] | string | string[] | Blendable | Date | Blendable[];
|
|
3
3
|
/** @internal */
|
|
4
4
|
export interface Blendable {
|
|
5
5
|
blend(target: this, progress: number): this;
|
package/internal/tween.js
CHANGED
|
@@ -18,7 +18,14 @@ export function createTween(from, to) {
|
|
|
18
18
|
}
|
|
19
19
|
switch (typeof from) {
|
|
20
20
|
case "number": return progress => blendNumbers(from, to, progress);
|
|
21
|
-
case "object":
|
|
21
|
+
case "object": {
|
|
22
|
+
if (from instanceof Date) {
|
|
23
|
+
const fromStamp = from.getTime();
|
|
24
|
+
const toStamp = to.getTime();
|
|
25
|
+
return progress => new Date(blendNumbers(fromStamp, toStamp, progress));
|
|
26
|
+
}
|
|
27
|
+
return progress => from.blend(to, progress);
|
|
28
|
+
}
|
|
22
29
|
case "string": return createStringTween(from, to);
|
|
23
30
|
default: throw new Error("Invalid tweening type");
|
|
24
31
|
}
|
|
@@ -26,9 +33,9 @@ export function createTween(from, to) {
|
|
|
26
33
|
function createStringTween(from, to) {
|
|
27
34
|
const fromChunks = tokenise(from);
|
|
28
35
|
const toChunks = tokenise(to);
|
|
29
|
-
const tokenCount = fromChunks.
|
|
30
|
-
// where
|
|
31
|
-
if (tokenCount !== toChunks.
|
|
36
|
+
const tokenCount = fromChunks.length;
|
|
37
|
+
// where token count mismatch, use merging
|
|
38
|
+
if (tokenCount !== toChunks.length) {
|
|
32
39
|
return createStringMerge(from, to);
|
|
33
40
|
}
|
|
34
41
|
// where token prefix/type mismatch, use merging
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xtia/timeline",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"repository": {
|
|
5
5
|
"url": "https://github.com/tiadrop/timeline",
|
|
6
6
|
"type": "github"
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
},
|
|
23
23
|
"keywords": [
|
|
24
24
|
"animation",
|
|
25
|
-
"timeline"
|
|
25
|
+
"timeline",
|
|
26
|
+
"choreography"
|
|
26
27
|
],
|
|
27
28
|
"author": "Aleta Lovelace",
|
|
28
29
|
"license": "MIT"
|