@xtia/timeline 1.0.1 → 1.0.2

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.
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.easers = void 0;
4
+ const overshoot = 1.70158;
5
+ exports.easers = {
6
+ linear: (x) => x,
7
+ easeIn: (x) => x * x,
8
+ easeIn4: (x) => Math.pow(x, 4),
9
+ easeOut: (x) => 1 - Math.pow((1 - x), 2),
10
+ easeOut4: (x) => 1 - Math.pow((1 - x), 4),
11
+ circleIn: (x) => 1 - Math.sqrt(1 - Math.pow(x, 2)),
12
+ circleIn4: (x) => 1 - Math.sqrt(1 - Math.pow(x, 4)),
13
+ circleOut: (x) => Math.sqrt(1 - Math.pow((1 - x), 2)),
14
+ circleOut4: (x) => Math.sqrt(1 - Math.pow((1 - x), 4)),
15
+ easeInOut: (x) => -Math.cos(x * Math.PI) / 2 + .5,
16
+ elastic: (x) => 1 - Math.cos(4 * Math.PI * x) * (1 - x),
17
+ overshootIn: (x) => --x * x * ((overshoot + 1) * x + overshoot) + 1,
18
+ sine: (x) => (Math.sin(2 * Math.PI * x) + 1) / 2,
19
+ invert: (x) => 1 - x,
20
+ bounce: (x) => {
21
+ if (x < 4 / 11.0) {
22
+ return (121 * x * x) / 16.0;
23
+ }
24
+ else if (x < 8 / 11.0) {
25
+ return (363 / 40.0 * x * x) - (99 / 10.0 * x) + 17 / 5.0;
26
+ }
27
+ else if (x < 9 / 10.0) {
28
+ return (4356 / 361.0 * x * x) - (35442 / 1805.0 * x) + 16061 / 1805.0;
29
+ }
30
+ else {
31
+ return (54 / 5.0 * x * x) - (513 / 25.0 * x) + 268 / 25.0;
32
+ }
33
+ },
34
+ noise: (x) => x == 0 ? 0 : (x >= 1 ? 1 : Math.random()),
35
+ pingpong: (x) => x < .5 ? x * 2 : 1 - (x - .5) * 2,
36
+ };
@@ -0,0 +1,52 @@
1
+ import { RangeProgression } from "./range";
2
+ /** @internal */
3
+ export declare function createEmitter<T>(onListen: (handler: Handler<T>) => UnsubscribeFunc): Emitter<T>;
4
+ /** @internal */
5
+ export declare function createEmitter<T, API extends object>(onListen: (handler: Handler<T>) => UnsubscribeFunc, api: Omit<API, keyof Emitter<T>>): Emitter<T> & API;
6
+ /** @internal */
7
+ export declare function createProgressEmitter<API extends object>(onListen: (handler: Handler<number>) => UnsubscribeFunc, api: Omit<API, keyof RangeProgression>): RangeProgression & API;
8
+ /** @internal */
9
+ export declare function createProgressEmitter(onListen: (handler: Handler<number>) => UnsubscribeFunc): RangeProgression;
10
+ type Handler<T> = (value: T) => void;
11
+ export type UnsubscribeFunc = () => void;
12
+ export interface Emitter<T> {
13
+ /**
14
+ * Registers a function to receive emitted values
15
+ * @param handler
16
+ * @returns A function to deregister the handler
17
+ */
18
+ listen(handler: Handler<T>): UnsubscribeFunc;
19
+ /**
20
+ * Creates a chainable emitter that applies arbitrary transformation to values emitted by its parent
21
+ * @param mapFunc
22
+ * @returns Listenable: emits transformed values
23
+ */
24
+ map<R>(mapFunc: (value: T) => R): Emitter<R>;
25
+ /**
26
+ * Creates a chainable emitter that selectively forwards emissions along the chain
27
+ * @param check Function that takes an emitted value and returns true if the emission should be forwarded along the chain
28
+ * @returns Listenable: emits values that pass the filter
29
+ */
30
+ filter(check: (value: T) => boolean): Emitter<T>;
31
+ /**
32
+ * Creates a chainable emitter that discards emitted values that are the same as the last value emitted by the new emitter
33
+ * @param compare Optional function that takes the previous and next values and returns true if they should be considered equal
34
+ *
35
+ * If no `compare` function is provided, values will be compared via `===`
36
+ * @returns Listenable: emits non-repeating values
37
+ */
38
+ noRepeat(compare?: (a: T, b: T) => boolean): Emitter<T>;
39
+ /**
40
+ * Creates a chainable emitter that mirrors emissions from the parent emitter, invoking the provided callback `cb` as a side effect for each emission.
41
+ *
42
+ * The callback `cb` is called exactly once per parent emission, regardless of how many listeners are attached to the returned emitter.
43
+ * All listeners attached to the returned emitter receive the same values as the parent emitter.
44
+ *
45
+ * *Note*, the side effect `cb` is only invoked when there is at least one listener attached to the returned emitter
46
+ *
47
+ * @param cb A function to be called as a side effect for each value emitted by the parent emitter.
48
+ * @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
49
+ */
50
+ tap(cb: Handler<T>): Emitter<T>;
51
+ }
52
+ export {};
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createEmitter = createEmitter;
4
+ exports.createProgressEmitter = createProgressEmitter;
5
+ const easing_1 = require("./easing");
6
+ const tween_1 = require("./tween");
7
+ const utils_1 = require("./utils");
8
+ /** @internal */
9
+ function createEmitter(onListen, api) {
10
+ const propertyDescriptor = Object.fromEntries(Object.entries({
11
+ listen: (handler) => {
12
+ const uniqueHandler = (value) => {
13
+ handler(value);
14
+ };
15
+ return onListen(uniqueHandler);
16
+ },
17
+ map: (mapFunc) => {
18
+ return createEmitter(handler => {
19
+ const pipedHandler = (value) => {
20
+ handler(mapFunc(value));
21
+ };
22
+ return onListen(pipedHandler);
23
+ });
24
+ },
25
+ filter: (filterFunc) => {
26
+ return createEmitter(handler => {
27
+ const filteredHandler = (value) => {
28
+ if (filterFunc(value))
29
+ handler(value);
30
+ };
31
+ return onListen(filteredHandler);
32
+ });
33
+ },
34
+ noRepeat: (compare) => {
35
+ let previous = null;
36
+ return createEmitter(handler => {
37
+ const filteredHandler = (value) => {
38
+ if (!previous || (compare
39
+ ? !compare(previous.value, value)
40
+ : (previous.value !== value))) {
41
+ handler(value);
42
+ previous = { value };
43
+ }
44
+ };
45
+ return onListen(filteredHandler);
46
+ });
47
+ },
48
+ tap: (cb) => {
49
+ let listeners = [];
50
+ let parentUnsubscribe = null;
51
+ const tapOnListen = (handler) => {
52
+ listeners.push(handler);
53
+ if (listeners.length === 1) {
54
+ parentUnsubscribe = onListen(value => {
55
+ cb(value);
56
+ listeners.slice().forEach(fn => fn(value));
57
+ });
58
+ }
59
+ return () => {
60
+ listeners = listeners.filter(l => l !== handler);
61
+ if (listeners.length === 0 && parentUnsubscribe) {
62
+ parentUnsubscribe();
63
+ parentUnsubscribe = null;
64
+ }
65
+ };
66
+ };
67
+ return createEmitter(tapOnListen);
68
+ },
69
+ }).map(([key, value]) => [
70
+ key,
71
+ { value }
72
+ ]));
73
+ return Object.create(api ?? {}, propertyDescriptor);
74
+ }
75
+ /** @internal */
76
+ function createProgressEmitter(onListen, api = {}) {
77
+ const propertyDescriptor = Object.fromEntries(Object.entries({
78
+ ease: (easer) => {
79
+ const easerFunc = typeof easer == "string"
80
+ ? easing_1.easers[easer]
81
+ : easer;
82
+ return createProgressEmitter(easer ? (handler => onListen((progress) => {
83
+ handler(easerFunc(progress));
84
+ })) : onListen);
85
+ },
86
+ tween: (from, to) => createEmitter(handler => onListen(progress => handler((0, tween_1.tweenValue)(from, to, progress)))),
87
+ snap: (steps) => {
88
+ if (!Number.isInteger(steps) || steps <= 0) {
89
+ throw new RangeError('snap(steps) requires a positive integer');
90
+ }
91
+ return createProgressEmitter(handler => onListen(progress => {
92
+ const snapped = Math.round(progress * steps) / steps;
93
+ handler((0, utils_1.clamp)(snapped, 0, 1));
94
+ }));
95
+ },
96
+ threshold: (threshold) => createProgressEmitter(handler => onListen(progress => {
97
+ handler(progress >= threshold ? 1 : 0);
98
+ })),
99
+ clamp: (min = 0, max = 1) => createProgressEmitter(handler => onListen(progress => handler((0, utils_1.clamp)(progress, min, max)))),
100
+ repeat: (repetitions) => {
101
+ repetitions = Math.max(0, repetitions);
102
+ return createProgressEmitter(handler => onListen(progress => {
103
+ const out = (progress * repetitions) % 1;
104
+ handler(out);
105
+ }));
106
+ },
107
+ }).map(([key, value]) => [
108
+ key,
109
+ { value }
110
+ ]));
111
+ return createEmitter(onListen, Object.create(api, propertyDescriptor));
112
+ }
@@ -0,0 +1,26 @@
1
+ import { Emitter } from "./emitters";
2
+ import { TimelineRange } from "./range";
3
+ export type PointEvent = {
4
+ direction: -1 | 1;
5
+ };
6
+ export interface TimelinePoint extends Emitter<PointEvent> {
7
+ /**
8
+ * Creates a range on the Timeline, with a given duration, starting at this point
9
+ * @param duration
10
+ */
11
+ range(duration: number): TimelineRange;
12
+ /**
13
+ * Creates a range on the Timeline, with a given end point, starting at this point
14
+ * @param endPoint
15
+ */
16
+ to(endPoint: number | TimelinePoint): TimelineRange;
17
+ /**
18
+ * Creates a point on the Timeline at an offset position from this one
19
+ * @param timeOffset
20
+ */
21
+ delta(timeOffset: number): TimelinePoint;
22
+ /**
23
+ * The point's absolute position on the Timeline
24
+ */
25
+ readonly position: number;
26
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,120 @@
1
+ import { Easer, easers } from "./easing";
2
+ import { Emitter } from "./emitters";
3
+ import { TimelinePoint } from "./point";
4
+ import { Blendable } from "./tween";
5
+ export interface TimelineRange extends RangeProgression {
6
+ /**
7
+ * Creates two ranges by seperating one at a given point
8
+ * @param position Point of separation, relative to the range's start - if omitted, the range will be separated halfway
9
+ *
10
+ * Must be greater than 0 and less than the range's duration
11
+ */
12
+ bisect(position?: number): [TimelineRange, TimelineRange];
13
+ /**
14
+ * Creates a series of evenly-spread points across the range, excluding the range's start and end
15
+ * @param count Number of Points to return
16
+ */
17
+ spread(count: number): TimelinePoint[];
18
+ /**
19
+ * Progresses the Timeline across the range
20
+ * @param easer
21
+ */
22
+ play(easer?: Easer): Promise<void>;
23
+ /**
24
+ * Creates a new range representing a direct expansion of this one
25
+ * @param delta Amount to grow by (in time units)
26
+ * @param anchor Normalised position at which to expand (0 being the start, expanding right, 1 being the end, expanding left, 0.5 expanding evenly)
27
+ * @returns Listenable: this range will emit a progression value (0..1) when a `seek()` passes or intersects it
28
+ */
29
+ grow(delta: number, anchor?: number): TimelineRange;
30
+ /**
31
+ * Creates a new range representing a multiplicative expansion of this one
32
+ * @param factor Size multiplier
33
+ * @param anchor Normalised position at which to expand (0 being the start, expanding right, 1 being the end, expanding left, 0.5 expanding evenly)
34
+ * @returns Listenable: this range will emit a progression value (0..1) when a `seek()` passes or intersects it
35
+ */
36
+ scale(factor: number, anchor?: number): TimelineRange;
37
+ /** The point on the Timeline at which this range begins */
38
+ readonly start: TimelinePoint;
39
+ /** The point on the Timeline at which this range ends */
40
+ readonly end: TimelinePoint;
41
+ /** The duration of this range */
42
+ readonly duration: number;
43
+ }
44
+ export interface RangeProgression extends Emitter<number> {
45
+ /**
46
+ * Creates a chainable progress emitter that applies an easing function to its parent's emitted values
47
+ *
48
+ * @param easer An easing function of the form `(progression: number) => number`
49
+ * @returns Listenable: emits eased progression values
50
+ */
51
+ ease(easer?: Easer | keyof typeof easers): RangeProgression;
52
+ /**
53
+ * Creates a chainable emitter that interpolates two given values by progression emitted by its parent
54
+ *
55
+ * Can interpolate types `number`, `number[]`, string and objects with a `blend(from: this, to: this): this` method
56
+ *
57
+ * @param from Value to interpolate from
58
+ * @param to Value to interpolate to
59
+ * @returns Listenable: emits interpolated values
60
+ */
61
+ tween(from: number, to: number): Emitter<number>;
62
+ /**
63
+ * Creates a chainable emitter that interpolates two given values by progression emitted by its parent
64
+ *
65
+ * Can interpolate types `number`, `number[]`, string and objects with a `blend(from: this, to: this): this` method
66
+ *
67
+ * #### String interpolation
68
+ * * If the strings contain tweenable tokens (numbers, colour codes) and are otherwise identical, those tokens are interpolated
69
+ * * Otherwise the `from` string is progressively replaced, left-to-right, with the `to` string
70
+ *
71
+ * eg
72
+ * ```ts
73
+ * range
74
+ * .tween("0px 0px 0px #0000", "4px 4px 8px #0005")
75
+ * .listen(s => element.style.textShadow = s);
76
+ * ```
77
+ *
78
+ * @param from Value to interpolate from
79
+ * @param to Value to interpolate to
80
+ * @returns Listenable: emits interpolated values
81
+ */
82
+ tween(from: string, to: string): Emitter<string>;
83
+ /**
84
+ * Creates a chainable emitter that interpolates two given values by progression emitted by its parent
85
+ *
86
+ * Can interpolate types `number`, `number[]`, string and objects with a `blend(from: this, to: this): this` method
87
+ *
88
+ * @param from Value to interpolate from
89
+ * @param to Value to interpolate to
90
+ * @returns Listenable: emits interpolated values
91
+ */
92
+ tween<T extends Blendable | number[]>(from: T, to: T): Emitter<T>;
93
+ /**
94
+ * Creates a chainable progress emitter that quantises progress, as emitted by its parent, to the nearest of `steps` discrete values.
95
+ *
96
+ * @param steps – positive integer (e.g. 10 → 0, .1, .2 … 1)
97
+ * @throws RangeError if steps is not a positive integer
98
+ * @returns Listenable: emits quantised progression values
99
+ */
100
+ snap(steps: number): RangeProgression;
101
+ /**
102
+ * Creates a chainable progress emitter that emits `1` when the incoming progress value is greater‑than‑or‑equal to the supplied `threshold`, otherwise emits `0`
103
+ *
104
+ * @param threshold the cut‑off value
105
+ * @returns Listenable: emits 0 or 1 after comparing progress with a threshold
106
+ */
107
+ threshold(threshold: number): RangeProgression;
108
+ /**
109
+ * Creates a chainable progress emitter that clamps incoming values
110
+ * @param min default 0
111
+ * @param max default 1
112
+ * @returns Listenable: emits clamped progression values
113
+ */
114
+ clamp(min?: number, max?: number): RangeProgression;
115
+ /**
116
+ * Creates a chainable progress emitter that maps incoming values to a repeating linear scale
117
+ * @param count Number of repetitions
118
+ */
119
+ repeat(count: number): RangeProgression;
120
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,147 @@
1
+ import { Easer, easers } from "./easing";
2
+ import { TimelinePoint } from "./point";
3
+ import { TimelineRange } from "./range";
4
+ import { Tweenable } from "./tween";
5
+ import { Widen } from "./utils";
6
+ declare const EndAction: {
7
+ readonly pause: 0;
8
+ readonly continue: 1;
9
+ readonly wrap: 2;
10
+ readonly restart: 3;
11
+ };
12
+ /**
13
+ * Creates an autoplaying Timeline and returns a range from it
14
+ * @param duration
15
+ * @returns Object representing a range on a single-use, autoplaying Timeline
16
+ */
17
+ export declare function animate(duration: number): TimelineRange;
18
+ export declare class Timeline {
19
+ /**
20
+ * Multiplies the speed at which `play()` progresses through the Timeline
21
+ *
22
+ * A value of 2 would double progression speed while .25 would slow it to a quarter
23
+ */
24
+ timeScale: number;
25
+ get currentTime(): number;
26
+ set currentTime(v: number);
27
+ get isPlaying(): boolean;
28
+ get end(): TimelinePoint;
29
+ private _currentTime;
30
+ private _endPosition;
31
+ private interval;
32
+ private points;
33
+ private endAction;
34
+ private ranges;
35
+ private currentSortDirection;
36
+ private smoothSeeker;
37
+ private seeking;
38
+ readonly start: TimelinePoint;
39
+ private positionHandlers;
40
+ constructor();
41
+ /**
42
+ * @param autoplay Pass `true` to begin playing at (1000 x this.timeScale) units per second immediately on creation
43
+ */
44
+ constructor(autoplay: boolean);
45
+ /**
46
+ * Creates a Timeline that begins playing immediately at (1000 x this.timeScale) units per second
47
+ * @param autoplayFps Specifies frames per second
48
+ */
49
+ constructor(autoplayFps: number);
50
+ /**
51
+ * @param autoplay If this argument is `true`, the Timeline will begin playing immediately on creation. If the argument is a number, the Timeline will begin playing at the specified frames per second
52
+ * @param endAction Specifies what should happen when the final position is passed by `play()`/`autoplay`
53
+ *
54
+ * `"pause"`: **(default)** the Timeline will pause at its final position
55
+ * `"continue"`: The Timeline will continue progressing beyond its final position
56
+ * `"restart"`: The Timeline will seek back to 0 then forward to account for any overshoot and continue progressing
57
+ * `"wrap"`: The Timeline's position will continue to increase beyond the final position, but Points and Ranges will be activated as if looping
58
+ * `{restartAt: number}`: Like `"restart"` but seeking back to `restartAt` instead of 0
59
+ * `{wrapAt: number}`: Like `"wrap"` but as if restarting at `wrapAt` instead of 0
60
+ */
61
+ constructor(autoplay: boolean | number, endAction: {
62
+ wrapAt: number;
63
+ } | {
64
+ restartAt: number;
65
+ } | keyof typeof EndAction);
66
+ /**
67
+ * @deprecated "loop" endAction will be removed; use "restart" or `{restartAt: 0}` (disambiguates new looping strategies)
68
+ */
69
+ constructor(autoplay: boolean | number, endAction: "loop");
70
+ /**
71
+ * Defines a single point on the Timeline
72
+ *
73
+ * @param position
74
+ * @returns A point on the Timeline as specified
75
+ *
76
+ * Listenable: this point will emit a PointEvent whenever a `seek()` reaches or passes it
77
+ */
78
+ point(position: number): TimelinePoint;
79
+ /**
80
+ * Defines a range on this Timeline
81
+ *
82
+ * @param start The position on this Timeline at which the range starts
83
+ * @param duration Length of the resulting range - if omitted, the range will end at the Timeline's **current** final position
84
+ * @returns A range on the Timeline
85
+ *
86
+ * Listenable: this range will emit a progression value (0..1) when a `seek()` passes or intersects it
87
+ */
88
+ range(start: number | TimelinePoint, duration?: number): TimelineRange;
89
+ /**
90
+ * Creates an observable range from position 0 to the Timeline's **current** final position
91
+ */
92
+ range(): TimelineRange;
93
+ private getWrappedPosition;
94
+ /**
95
+ * Seeks the Timeline to a specified position, triggering in order any point and range subscriptions between its current and new positions
96
+ * @param toPosition
97
+ */
98
+ seek(toPosition: number | TimelinePoint): void;
99
+ /**
100
+ * Smooth-seeks to a specified position
101
+ *
102
+ * Aborts and replaces any on-going smooth-seek process on this Timeline
103
+ * @param toPosition
104
+ * @param duration Duration of the smooth-seek process in milliseconds
105
+ * @param easer Optional easing function for the smooth-seek process
106
+ * @returns A promise, resolved when the smooth-seek process finishes
107
+ */
108
+ seek(toPosition: number | TimelinePoint, duration: number, easer?: Easer | keyof typeof easers): Promise<void>;
109
+ private seekDirect;
110
+ private seekPoints;
111
+ private seekRanges;
112
+ private sortEntries;
113
+ /**
114
+ * Starts progression of the Timeline from its current position at (1000 x this.timeScale) units per second
115
+ */
116
+ play(): void;
117
+ play(fps: number): void;
118
+ pause(): void;
119
+ /**
120
+ * Progresses the Timeline by 1 unit
121
+ * @param delta
122
+ * @deprecated Use timeline.position++
123
+ */
124
+ step(): void;
125
+ /**
126
+ * Progresses the Timeline by a given delta
127
+ * @param delta
128
+ * @deprecated Use timeline.position += n
129
+ */
130
+ step(delta: number): void;
131
+ tween<T extends Tweenable>(start: number | TimelinePoint, duration: number, apply: (v: Widen<T>) => void, from: T, to: T, easer?: Easer): ChainingInterface;
132
+ tween<T extends Tweenable>(start: number | TimelinePoint, end: TimelinePoint, // ease migration for tl.tween(0, tl.end, ...)
133
+ apply: (v: Widen<T>) => void, from: T, to: T, easer?: Easer): ChainingInterface;
134
+ at(position: number | TimelinePoint, action?: () => void, reverse?: boolean | (() => void)): ChainingInterface;
135
+ private createChainingInterface;
136
+ /**
137
+ * @deprecated use `timeline.currentTime`
138
+ */
139
+ get position(): number;
140
+ }
141
+ export interface ChainingInterface {
142
+ thenTween(duration: number, apply: (v: number) => void, from?: number, to?: number, easer?: Easer): ChainingInterface;
143
+ then(action: () => void): ChainingInterface;
144
+ thenWait(duration: number): ChainingInterface;
145
+ readonly end: TimelinePoint;
146
+ }
147
+ export {};