UNPKG

6.99 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5var store = require('../store');
6var internal = require('../internal');
7var easing = require('../easing');
8
9function is_date(obj) {
10 return Object.prototype.toString.call(obj) === '[object Date]';
11}
12
13function tick_spring(ctx, last_value, current_value, target_value) {
14 if (typeof current_value === 'number' || is_date(current_value)) {
15 // @ts-ignore
16 const delta = target_value - current_value;
17 // @ts-ignore
18 const velocity = (current_value - last_value) / (ctx.dt || 1 / 60); // guard div by 0
19 const spring = ctx.opts.stiffness * delta;
20 const damper = ctx.opts.damping * velocity;
21 const acceleration = (spring - damper) * ctx.inv_mass;
22 const d = (velocity + acceleration) * ctx.dt;
23 if (Math.abs(d) < ctx.opts.precision && Math.abs(delta) < ctx.opts.precision) {
24 return target_value; // settled
25 }
26 else {
27 ctx.settled = false; // signal loop to keep ticking
28 // @ts-ignore
29 return is_date(current_value) ?
30 new Date(current_value.getTime() + d) : current_value + d;
31 }
32 }
33 else if (Array.isArray(current_value)) {
34 // @ts-ignore
35 return current_value.map((_, i) => tick_spring(ctx, last_value[i], current_value[i], target_value[i]));
36 }
37 else if (typeof current_value === 'object') {
38 const next_value = {};
39 for (const k in current_value)
40 // @ts-ignore
41 next_value[k] = tick_spring(ctx, last_value[k], current_value[k], target_value[k]);
42 // @ts-ignore
43 return next_value;
44 }
45 else {
46 throw new Error(`Cannot spring ${typeof current_value} values`);
47 }
48}
49function spring(value, opts = {}) {
50 const store$1 = store.writable(value);
51 const { stiffness = 0.15, damping = 0.8, precision = 0.01 } = opts;
52 let last_time;
53 let task;
54 let current_token;
55 let last_value = value;
56 let target_value = value;
57 let inv_mass = 1;
58 let inv_mass_recovery_rate = 0;
59 let cancel_task = false;
60 function set(new_value, opts = {}) {
61 target_value = new_value;
62 const token = current_token = {};
63 if (value == null || opts.hard || (spring.stiffness >= 1 && spring.damping >= 1)) {
64 cancel_task = true; // cancel any running animation
65 last_time = internal.now();
66 last_value = new_value;
67 store$1.set(value = target_value);
68 return Promise.resolve();
69 }
70 else if (opts.soft) {
71 const rate = opts.soft === true ? .5 : +opts.soft;
72 inv_mass_recovery_rate = 1 / (rate * 60);
73 inv_mass = 0; // infinite mass, unaffected by spring forces
74 }
75 if (!task) {
76 last_time = internal.now();
77 cancel_task = false;
78 task = internal.loop(now => {
79 if (cancel_task) {
80 cancel_task = false;
81 task = null;
82 return false;
83 }
84 inv_mass = Math.min(inv_mass + inv_mass_recovery_rate, 1);
85 const ctx = {
86 inv_mass,
87 opts: spring,
88 settled: true,
89 dt: (now - last_time) * 60 / 1000
90 };
91 const next_value = tick_spring(ctx, last_value, value, target_value);
92 last_time = now;
93 last_value = value;
94 store$1.set(value = next_value);
95 if (ctx.settled)
96 task = null;
97 return !ctx.settled;
98 });
99 }
100 return new Promise(fulfil => {
101 task.promise.then(() => {
102 if (token === current_token)
103 fulfil();
104 });
105 });
106 }
107 const spring = {
108 set,
109 update: (fn, opts) => set(fn(target_value, value), opts),
110 subscribe: store$1.subscribe,
111 stiffness,
112 damping,
113 precision
114 };
115 return spring;
116}
117
118function get_interpolator(a, b) {
119 if (a === b || a !== a)
120 return () => a;
121 const type = typeof a;
122 if (type !== typeof b || Array.isArray(a) !== Array.isArray(b)) {
123 throw new Error('Cannot interpolate values of different type');
124 }
125 if (Array.isArray(a)) {
126 const arr = b.map((bi, i) => {
127 return get_interpolator(a[i], bi);
128 });
129 return t => arr.map(fn => fn(t));
130 }
131 if (type === 'object') {
132 if (!a || !b)
133 throw new Error('Object cannot be null');
134 if (is_date(a) && is_date(b)) {
135 a = a.getTime();
136 b = b.getTime();
137 const delta = b - a;
138 return t => new Date(a + t * delta);
139 }
140 const keys = Object.keys(b);
141 const interpolators = {};
142 keys.forEach(key => {
143 interpolators[key] = get_interpolator(a[key], b[key]);
144 });
145 return t => {
146 const result = {};
147 keys.forEach(key => {
148 result[key] = interpolators[key](t);
149 });
150 return result;
151 };
152 }
153 if (type === 'number') {
154 const delta = b - a;
155 return t => a + t * delta;
156 }
157 throw new Error(`Cannot interpolate ${type} values`);
158}
159function tweened(value, defaults = {}) {
160 const store$1 = store.writable(value);
161 let task;
162 let target_value = value;
163 function set(new_value, opts) {
164 if (value == null) {
165 store$1.set(value = new_value);
166 return Promise.resolve();
167 }
168 target_value = new_value;
169 let previous_task = task;
170 let started = false;
171 let { delay = 0, duration = 400, easing: easing$1 = easing.linear, interpolate = get_interpolator } = internal.assign(internal.assign({}, defaults), opts);
172 const start = internal.now() + delay;
173 let fn;
174 task = internal.loop(now => {
175 if (now < start)
176 return true;
177 if (!started) {
178 fn = interpolate(value, new_value);
179 if (typeof duration === 'function')
180 duration = duration(value, new_value);
181 started = true;
182 }
183 if (previous_task) {
184 previous_task.abort();
185 previous_task = null;
186 }
187 const elapsed = now - start;
188 if (elapsed > duration) {
189 store$1.set(value = new_value);
190 return false;
191 }
192 // @ts-ignore
193 store$1.set(value = fn(easing$1(elapsed / duration)));
194 return true;
195 });
196 return task.promise;
197 }
198 return {
199 set,
200 update: (fn, opts) => set(fn(target_value, value), opts),
201 subscribe: store$1.subscribe
202 };
203}
204
205exports.spring = spring;
206exports.tweened = tweened;