From 3fbad648de49af0947518382eb217d9afa17bc1d Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Mon, 8 May 2023 22:17:13 -0600 Subject: [PATCH] Convert src/runtime/motion/tweened.ts to JavaScript --- src/runtime/motion/tweened.ts | 249 ++++++++++++++++------------------ 1 file changed, 114 insertions(+), 135 deletions(-) diff --git a/src/runtime/motion/tweened.ts b/src/runtime/motion/tweened.ts index 1e51a1887b..7375d16a96 100644 --- a/src/runtime/motion/tweened.ts +++ b/src/runtime/motion/tweened.ts @@ -1,143 +1,122 @@ -import { Readable, writable } from '../store'; -import { assign, loop, now, Task } from '../internal'; +import { writable } from '../store'; +import { assign, loop, now } from '../internal'; import { linear } from '../easing'; import { is_date } from './utils'; - +/** @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`); + 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`); } - -interface Options { - delay?: number; - duration?: number | ((from: T, to: T) => number); - easing?: (t: number) => number; - interpolate?: (a: T, b: T) => (t: number) => T; -} - -type Updater = (target_value: T, value: T) => T; - -export interface Tweened extends Readable { - set(value: T, opts?: Options): Promise; - - update(updater: Updater, opts?: Options): Promise; +/** @param {T} value + * @param {Options} defaults + * @returns {import("/Users/elliottjohnson/dev/sveltejs/svelte/tweened.ts-to-jsdoc").Tweened} + */ +export function tweened(value, defaults = {}) { + const store = writable(value); + /** @type {Task} */ + let task; + let target_value = value; + /** @param {T} new_value + * @param {Options} opts + * @returns {any} + */ + 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 > 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 + }; } -export function tweened(value?: T, defaults: Options = {}): Tweened { - const store = writable(value); - - let task: Task; - let target_value = value; - - function set(new_value: T, opts?: Options) { - 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; +/** + * @typedef {(target_value: T, value: T) => T} Updater + * @template T + */ - 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 > 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?: Options) => set(fn(target_value, value), opts), - subscribe: store.subscribe - }; -} +/** @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 */ \ No newline at end of file