Convert src/runtime/motion/tweened.ts to JavaScript

pull/8569/head
S. Elliott Johnson 3 years ago
parent 8225b5e3ff
commit 3fbad648de

@ -1,42 +1,35 @@
import { Readable, writable } from '../store'; import { writable } from '../store';
import { assign, loop, now, Task } from '../internal'; import { assign, loop, now } from '../internal';
import { linear } from '../easing'; import { linear } from '../easing';
import { is_date } from './utils'; import { is_date } from './utils';
/** @returns {(t: any) => any} */
function get_interpolator(a, b) { function get_interpolator(a, b) {
if (a === b || a !== a) return () => a; if (a === b || a !== a)
return () => a;
const type = typeof a; const type = typeof a;
if (type !== typeof b || Array.isArray(a) !== Array.isArray(b)) { if (type !== typeof b || Array.isArray(a) !== Array.isArray(b)) {
throw new Error('Cannot interpolate values of different type'); throw new Error('Cannot interpolate values of different type');
} }
if (Array.isArray(a)) { if (Array.isArray(a)) {
const arr = b.map((bi, i) => { const arr = b.map((bi, i) => {
return get_interpolator(a[i], bi); return get_interpolator(a[i], bi);
}); });
return (t) => arr.map((fn) => fn(t)); return (t) => arr.map((fn) => fn(t));
} }
if (type === 'object') { if (type === 'object') {
if (!a || !b) throw new Error('Object cannot be null'); if (!a || !b)
throw new Error('Object cannot be null');
if (is_date(a) && is_date(b)) { if (is_date(a) && is_date(b)) {
a = a.getTime(); a = a.getTime();
b = b.getTime(); b = b.getTime();
const delta = b - a; const delta = b - a;
return (t) => new Date(a + t * delta); return (t) => new Date(a + t * delta);
} }
const keys = Object.keys(b); const keys = Object.keys(b);
const interpolators = {}; const interpolators = {};
keys.forEach((key) => { keys.forEach((key) => {
interpolators[key] = get_interpolator(a[key], b[key]); interpolators[key] = get_interpolator(a[key], b[key]);
}); });
return (t) => { return (t) => {
const result = {}; const result = {};
keys.forEach((key) => { keys.forEach((key) => {
@ -45,99 +38,85 @@ function get_interpolator(a, b) {
return result; return result;
}; };
} }
if (type === 'number') { if (type === 'number') {
const delta = b - a; const delta = b - a;
return (t) => a + t * delta; return (t) => a + t * delta;
} }
throw new Error(`Cannot interpolate ${type} values`); throw new Error(`Cannot interpolate ${type} values`);
} }
/** @param {T} value
interface Options<T> { * @param {Options<T>} defaults
delay?: number; * @returns {import("/Users/elliottjohnson/dev/sveltejs/svelte/tweened.ts-to-jsdoc").Tweened<T>}
duration?: number | ((from: T, to: T) => number); */
easing?: (t: number) => number; export function tweened(value, defaults = {}) {
interpolate?: (a: T, b: T) => (t: number) => T;
}
type Updater<T> = (target_value: T, value: T) => T;
export interface Tweened<T> extends Readable<T> {
set(value: T, opts?: Options<T>): Promise<void>;
update(updater: Updater<T>, opts?: Options<T>): Promise<void>;
}
export function tweened<T>(value?: T, defaults: Options<T> = {}): Tweened<T> {
const store = writable(value); const store = writable(value);
/** @type {Task} */
let task: Task; let task;
let target_value = value; let target_value = value;
/** @param {T} new_value
function set(new_value: T, opts?: Options<T>) { * @param {Options<T>} opts
* @returns {any}
*/
function set(new_value, opts) {
if (value == null) { if (value == null) {
store.set((value = new_value)); store.set((value = new_value));
return Promise.resolve(); return Promise.resolve();
} }
target_value = new_value; target_value = new_value;
let previous_task = task; let previous_task = task;
let started = false; let started = false;
let { delay = 0, duration = 400, easing = linear, interpolate = get_interpolator } = assign(assign({}, defaults), opts);
let {
delay = 0,
duration = 400,
easing = linear,
interpolate = get_interpolator
} = assign(assign({}, defaults), opts);
if (duration === 0) { if (duration === 0) {
if (previous_task) { if (previous_task) {
previous_task.abort(); previous_task.abort();
previous_task = null; previous_task = null;
} }
store.set((value = target_value)); store.set((value = target_value));
return Promise.resolve(); return Promise.resolve();
} }
const start = now() + delay; const start = now() + delay;
let fn; let fn;
task = loop((now) => { task = loop((now) => {
if (now < start) return true; if (now < start)
return true;
if (!started) { if (!started) {
fn = interpolate(value, new_value); fn = interpolate(value, new_value);
if (typeof duration === 'function') duration = duration(value, new_value); if (typeof duration === 'function')
duration = duration(value, new_value);
started = true; started = true;
} }
if (previous_task) { if (previous_task) {
previous_task.abort(); previous_task.abort();
previous_task = null; previous_task = null;
} }
const elapsed = now - start; const elapsed = now - start;
if (elapsed > duration) {
if (elapsed > <number>duration) {
store.set((value = new_value)); store.set((value = new_value));
return false; return false;
} }
// @ts-ignore // @ts-ignore
store.set((value = fn(easing(elapsed / duration)))); store.set((value = fn(easing(elapsed / duration))));
return true; return true;
}); });
return task.promise; return task.promise;
} }
return { return {
set, set,
update: (fn, opts?: Options<T>) => set(fn(target_value, value), opts), update: (fn, opts) => set(fn(target_value, value), opts),
subscribe: store.subscribe subscribe: store.subscribe
}; };
} }
/**
* @typedef {(target_value: T, value: T) => T} Updater
* @template T
*/
/** @typedef {Object} Options
* @property {number} [delay]
* @property {number|((from:T,to:T)=>number)} [duration]
* @property {(t:number)=>number} [easing]
* @property {(a:T,b:T)=>(t:number)=>T} [interpolate]
*/
/** @typedef {Object} Tweened */
Loading…
Cancel
Save