UNPKG

7.01 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 /* eslint-disable @typescript-eslint/no-use-before-define */
61 function set(new_value, opts = {}) {
62 target_value = new_value;
63 const token = current_token = {};
64 if (opts.hard || (spring.stiffness >= 1 && spring.damping >= 1)) {
65 cancel_task = true; // cancel any running animation
66 last_time = internal.now();
67 last_value = value;
68 store$$1.set(value = target_value);
69 return new Promise(f => f()); // fulfil immediately
70 }
71 else if (opts.soft) {
72 const rate = opts.soft === true ? .5 : +opts.soft;
73 inv_mass_recovery_rate = 1 / (rate * 60);
74 inv_mass = 0; // infinite mass, unaffected by spring forces
75 }
76 if (!task) {
77 last_time = internal.now();
78 cancel_task = false;
79 task = internal.loop(now => {
80 if (cancel_task) {
81 cancel_task = false;
82 task = null;
83 return false;
84 }
85 inv_mass = Math.min(inv_mass + inv_mass_recovery_rate, 1);
86 const ctx = {
87 inv_mass,
88 opts: spring,
89 settled: true,
90 dt: (now - last_time) * 60 / 1000
91 };
92 const next_value = tick_spring(ctx, last_value, value, target_value);
93 last_time = now;
94 last_value = value;
95 store$$1.set(value = next_value);
96 if (ctx.settled)
97 task = null;
98 return !ctx.settled;
99 });
100 }
101 return new Promise(fulfil => {
102 task.promise.then(() => {
103 if (token === current_token)
104 fulfil();
105 });
106 });
107 }
108 /* eslint-enable @typescript-eslint/no-use-before-define */
109 const spring = {
110 set,
111 update: (fn, opts) => set(fn(target_value, value), opts),
112 subscribe: store$$1.subscribe,
113 stiffness,
114 damping,
115 precision
116 };
117 return spring;
118}
119
120function get_interpolator(a, b) {
121 if (a === b || a !== a)
122 return () => a;
123 const type = typeof a;
124 if (type !== typeof b || Array.isArray(a) !== Array.isArray(b)) {
125 throw new Error('Cannot interpolate values of different type');
126 }
127 if (Array.isArray(a)) {
128 const arr = b.map((bi, i) => {
129 return get_interpolator(a[i], bi);
130 });
131 return t => arr.map(fn => fn(t));
132 }
133 if (type === 'object') {
134 if (!a || !b)
135 throw new Error('Object cannot be null');
136 if (is_date(a) && is_date(b)) {
137 a = a.getTime();
138 b = b.getTime();
139 const delta = b - a;
140 return t => new Date(a + t * delta);
141 }
142 const keys = Object.keys(b);
143 const interpolators = {};
144 keys.forEach(key => {
145 interpolators[key] = get_interpolator(a[key], b[key]);
146 });
147 return t => {
148 const result = {};
149 keys.forEach(key => {
150 result[key] = interpolators[key](t);
151 });
152 return result;
153 };
154 }
155 if (type === 'number') {
156 const delta = b - a;
157 return t => a + t * delta;
158 }
159 throw new Error(`Cannot interpolate ${type} values`);
160}
161function tweened(value, defaults = {}) {
162 const store$$1 = store.writable(value);
163 let task;
164 let target_value = value;
165 function set(new_value, opts) {
166 target_value = new_value;
167 let previous_task = task;
168 let started = false;
169 let { delay = 0, duration = 400, easing: easing$$1 = easing.linear, interpolate = get_interpolator } = internal.assign(internal.assign({}, defaults), opts);
170 const start = internal.now() + delay;
171 let fn;
172 task = internal.loop(now => {
173 if (now < start)
174 return true;
175 if (!started) {
176 fn = interpolate(value, new_value);
177 if (typeof duration === 'function')
178 duration = duration(value, new_value);
179 started = true;
180 }
181 if (previous_task) {
182 previous_task.abort();
183 previous_task = null;
184 }
185 const elapsed = now - start;
186 if (elapsed > duration) {
187 store$$1.set(value = new_value);
188 return false;
189 }
190 // @ts-ignore
191 store$$1.set(value = fn(easing$$1(elapsed / duration)));
192 return true;
193 });
194 return task.promise;
195 }
196 return {
197 set,
198 update: (fn, opts) => set(fn(target_value, value), opts),
199 subscribe: store$$1.subscribe
200 };
201}
202
203exports.spring = spring;
204exports.tweened = tweened;