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} [defaults] * @returns {import('./public.js').Tweened} */ 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} [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 }; }