/
tweened.js
116 lines (114 loc) · 3.02 KB
/
tweened.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import { writable } from '../store/index.js';
import { assign, loop, now } from '../internal/index.js';
import { linear } from '../easing/index.js';
import { is_date } from './utils.js';
/** @returns {(t: any) => any} */
function get_interpolator(a, b) {
if (a === b || a !== a) return () => a;
const type = typeof a;
if (type !== typeof b || Array.isArray(a) !== Array.isArray(b)) {
throw new Error('Cannot interpolate values of different type');
}
if (Array.isArray(a)) {
const arr = b.map((bi, i) => {
return get_interpolator(a[i], bi);
});
return (t) => arr.map((fn) => fn(t));
}
if (type === 'object') {
if (!a || !b) throw new Error('Object cannot be null');
if (is_date(a) && is_date(b)) {
a = a.getTime();
b = b.getTime();
const delta = b - a;
return (t) => new Date(a + t * delta);
}
const keys = Object.keys(b);
const interpolators = {};
keys.forEach((key) => {
interpolators[key] = get_interpolator(a[key], b[key]);
});
return (t) => {
const result = {};
keys.forEach((key) => {
result[key] = interpolators[key](t);
});
return result;
};
}
if (type === 'number') {
const delta = b - a;
return (t) => a + t * delta;
}
throw new Error(`Cannot interpolate ${type} values`);
}
/**
* A tweened store in Svelte is a special type of store that provides smooth transitions between state values over time.
*
* https://svelte.dev/docs/svelte-motion#tweened
* @template T
* @param {T} [value]
* @param {import('./private.js').TweenedOptions<T>} [defaults]
* @returns {import('./public.js').Tweened<T>}
*/
export function tweened(value, defaults = {}) {
const store = writable(value);
/** @type {import('../internal/private.js').Task} */
let task;
let target_value = value;
/**
* @param {T} new_value
* @param {import('./private.js').TweenedOptions<T>} [opts]
*/
function set(new_value, opts) {
if (value == null) {
store.set((value = new_value));
return Promise.resolve();
}
target_value = new_value;
let previous_task = task;
let started = false;
let {
delay = 0,
duration = 400,
easing = linear,
interpolate = get_interpolator
} = assign(assign({}, defaults), opts);
if (duration === 0) {
if (previous_task) {
previous_task.abort();
previous_task = null;
}
store.set((value = target_value));
return Promise.resolve();
}
const start = now() + delay;
let fn;
task = loop((now) => {
if (now < start) return true;
if (!started) {
fn = interpolate(value, new_value);
if (typeof duration === 'function') duration = duration(value, new_value);
started = true;
}
if (previous_task) {
previous_task.abort();
previous_task = null;
}
const elapsed = now - start;
if (elapsed > /** @type {number} */ (duration)) {
store.set((value = new_value));
return false;
}
// @ts-ignore
store.set((value = fn(easing(elapsed / duration))));
return true;
});
return task.promise;
}
return {
set,
update: (fn, opts) => set(fn(target_value, value), opts),
subscribe: store.subscribe
};
}