From 28d556347cd28f1ecb0d08869f9107ceb8aaa096 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Thu, 26 Dec 2024 12:27:42 -0800 Subject: [PATCH 01/14] Update spring.js --- packages/svelte/src/motion/spring.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/svelte/src/motion/spring.js b/packages/svelte/src/motion/spring.js index bc30ce957854..1a0f46f4eb04 100644 --- a/packages/svelte/src/motion/spring.js +++ b/packages/svelte/src/motion/spring.js @@ -11,7 +11,7 @@ import { get } from '../internal/client/runtime.js'; import { deferred, noop } from '../internal/shared/utils.js'; /** - * @template T + * @template {number} T * @param {TickContext} ctx * @param {T} last_value * @param {T} current_value @@ -58,7 +58,7 @@ function tick_spring(ctx, last_value, current_value, target_value) { * The spring function in Svelte creates a store whose value is animated, with a motion that simulates the behavior of a spring. This means when the value changes, instead of transitioning at a steady rate, it "bounces" like a spring would, depending on the physics parameters provided. This adds a level of realism to the transitions and can enhance the user experience. * * @deprecated Use [`Spring`](https://svelte.dev/docs/svelte/svelte-motion#Spring) instead - * @template [T=any] + * @template {number} T * @param {T} [value] * @param {SpringOpts} [opts] * @returns {SpringStore} @@ -159,7 +159,7 @@ export function spring(value, opts = {}) { * * * ``` - * @template T + * @template {number} T * @since 5.8.0 */ export class Spring { @@ -207,7 +207,7 @@ export class Spring { * const spring = Spring.of(() => number); * * ``` - * @template U + * @template {number} U * @param {() => U} fn * @param {SpringOpts} [options] */ From a50187804123d4ae1d251a806249282cbcb6122c Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Thu, 26 Dec 2024 12:28:57 -0800 Subject: [PATCH 02/14] Update tweened.js --- packages/svelte/src/motion/tweened.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/svelte/src/motion/tweened.js b/packages/svelte/src/motion/tweened.js index d732dbc2831a..f0d3989e6a0c 100644 --- a/packages/svelte/src/motion/tweened.js +++ b/packages/svelte/src/motion/tweened.js @@ -79,7 +79,7 @@ function get_interpolator(a, b) { * A tweened store in Svelte is a special type of store that provides smooth transitions between state values over time. * * @deprecated Use [`Tween`](https://svelte.dev/docs/svelte/svelte-motion#Tween) instead - * @template T + * @template {number} T * @param {T} [value] * @param {TweenedOptions} [defaults] * @returns {Tweened} @@ -170,7 +170,7 @@ export function tweened(value, defaults = {}) { * * * ``` - * @template T + * @template {number} T * @since 5.8.0 */ export class Tween { @@ -205,7 +205,7 @@ export class Tween { * const tween = Tween.of(() => number); * * ``` - * @template U + * @template {number} U * @param {() => U} fn * @param {TweenedOptions} [options] */ From bd25271c2fd433a68f23fbac1e9325cb463e133e Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Thu, 26 Dec 2024 12:30:04 -0800 Subject: [PATCH 03/14] Update public.d.ts --- packages/svelte/src/motion/public.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/svelte/src/motion/public.d.ts b/packages/svelte/src/motion/public.d.ts index 4e74d4b76f06..4eea1204c869 100644 --- a/packages/svelte/src/motion/public.d.ts +++ b/packages/svelte/src/motion/public.d.ts @@ -5,7 +5,7 @@ import { SpringUpdateOpts, TweenedOptions, Updater, SpringOpts } from './private // this means both the Spring class and the Spring interface are merged into one with some things only // existing on one side. In Svelte 6, remove the type definition and move the jsdoc onto the class in spring.js -export interface Spring extends Readable { +export interface Spring extends Readable { set(new_value: T, opts?: SpringUpdateOpts): Promise; /** * @deprecated Only exists on the legacy `spring` store, not the `Spring` class @@ -36,7 +36,7 @@ export interface Spring extends Readable { * ``` * @since 5.8.0 */ -export class Spring { +export class Spring { constructor(value: T, options?: SpringOpts); /** @@ -53,7 +53,7 @@ export class Spring { * * ``` */ - static of(fn: () => U, options?: SpringOpts): Spring; + static of(fn: () => U, options?: SpringOpts): Spring; /** * Sets `spring.target` to `value` and returns a `Promise` that resolves if and when `spring.current` catches up to it. @@ -80,7 +80,7 @@ export class Spring { get current(): T; } -export interface Tweened extends Readable { +export interface Tweened extends Readable { set(value: T, opts?: TweenedOptions): Promise; update(updater: Updater, opts?: TweenedOptions): Promise; } From 8f977bf1cf8e7420e62641cb9e82f8fc5ccdb840 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Thu, 26 Dec 2024 12:40:47 -0800 Subject: [PATCH 04/14] Update .changeset --- .changeset/random-changeset-name.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/random-changeset-name.md diff --git a/.changeset/random-changeset-name.md b/.changeset/random-changeset-name.md new file mode 100644 index 000000000000..97c3d8bd2627 --- /dev/null +++ b/.changeset/random-changeset-name.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: enforce number type on spring and tweened \ No newline at end of file From 124c94db46599eed5165bab4edd05406c737891d Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Thu, 26 Dec 2024 12:48:17 -0800 Subject: [PATCH 05/14] hopefully this'll fix it --- packages/svelte/src/motion/spring.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/motion/spring.js b/packages/svelte/src/motion/spring.js index 1a0f46f4eb04..15481483d615 100644 --- a/packages/svelte/src/motion/spring.js +++ b/packages/svelte/src/motion/spring.js @@ -11,7 +11,7 @@ import { get } from '../internal/client/runtime.js'; import { deferred, noop } from '../internal/shared/utils.js'; /** - * @template {number} T + * @template T * @param {TickContext} ctx * @param {T} last_value * @param {T} current_value From d4d397d19b9bf0f69884ee3ba570e4bbf2c6278f Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Thu, 26 Dec 2024 12:51:58 -0800 Subject: [PATCH 06/14] Update spring.js --- packages/svelte/src/motion/spring.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/motion/spring.js b/packages/svelte/src/motion/spring.js index 15481483d615..d14c025d6a38 100644 --- a/packages/svelte/src/motion/spring.js +++ b/packages/svelte/src/motion/spring.js @@ -66,14 +66,14 @@ function tick_spring(ctx, last_value, current_value, target_value) { export function spring(value, opts = {}) { const store = writable(value); const { stiffness = 0.15, damping = 0.8, precision = 0.01 } = opts; - /** @type {number} */ + /** @type {number | undefined} */ let last_time; /** @type {Task | null} */ let task; /** @type {object} */ let current_token; - let last_value = /** @type {T} */ (value); + let last_value = /** @type {T | undefined} */ (value); let target_value = /** @type {T | undefined} */ (value); let inv_mass = 1; From 1415eeef388145dd02aafd8faee1c60cda9244a8 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Thu, 26 Dec 2024 12:57:27 -0800 Subject: [PATCH 07/14] Update spring.js --- packages/svelte/src/motion/spring.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/svelte/src/motion/spring.js b/packages/svelte/src/motion/spring.js index d14c025d6a38..7a74c28a8537 100644 --- a/packages/svelte/src/motion/spring.js +++ b/packages/svelte/src/motion/spring.js @@ -167,10 +167,10 @@ export class Spring { #damping = source(0.8); #precision = source(0.01); - #current = source(/** @type {T} */ (undefined)); - #target = source(/** @type {T} */ (undefined)); + #current = source(/** @type {T | undefined} */ (undefined)); + #target = source(/** @type {T | undefined} */ (undefined)); - #last_value = /** @type {T} */ (undefined); + #last_value = /** @type {T | undefined} */ (undefined); #last_time = 0; #inverse_mass = 1; From 6f13a8b4b787733857f076abb8cfb57cdf4b97f3 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Thu, 26 Dec 2024 12:59:23 -0800 Subject: [PATCH 08/14] Update tweened.js --- packages/svelte/src/motion/tweened.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/motion/tweened.js b/packages/svelte/src/motion/tweened.js index f0d3989e6a0c..ee79f64f58b9 100644 --- a/packages/svelte/src/motion/tweened.js +++ b/packages/svelte/src/motion/tweened.js @@ -174,8 +174,8 @@ export function tweened(value, defaults = {}) { * @since 5.8.0 */ export class Tween { - #current = source(/** @type {T} */ (undefined)); - #target = source(/** @type {T} */ (undefined)); + #current = source(/** @type {T | undefined} */ (undefined)); + #target = source(/** @type {T | undefined} */ (undefined)); /** @type {TweenedOptions} */ #defaults; From 5a89e006a9c68900cf55e35e6e942d390a1b10d7 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:00:12 -0800 Subject: [PATCH 09/14] Update spring.js --- packages/svelte/src/motion/spring.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/svelte/src/motion/spring.js b/packages/svelte/src/motion/spring.js index 7a74c28a8537..956a8a677f5f 100644 --- a/packages/svelte/src/motion/spring.js +++ b/packages/svelte/src/motion/spring.js @@ -113,6 +113,7 @@ export function spring(value, opts = {}) { inv_mass, opts: spring, settled: true, + //@ts-ignore dt: ((now - last_time) * 60) / 1000 }; // @ts-ignore From 5220ee7342716ffb39314e93108d8133de4448fe Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:01:45 -0800 Subject: [PATCH 10/14] Update spring.js --- packages/svelte/src/motion/spring.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/svelte/src/motion/spring.js b/packages/svelte/src/motion/spring.js index 956a8a677f5f..3e25000b2f1a 100644 --- a/packages/svelte/src/motion/spring.js +++ b/packages/svelte/src/motion/spring.js @@ -336,6 +336,7 @@ export class Spring { } set target(v) { + //@ts-ignore this.set(v); } } From d1f1ae0cb80e0d416d938cbeed9f0a1c9699351c Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:02:16 -0800 Subject: [PATCH 11/14] Update tweened.js --- packages/svelte/src/motion/tweened.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/svelte/src/motion/tweened.js b/packages/svelte/src/motion/tweened.js index ee79f64f58b9..cc4bcd0e526e 100644 --- a/packages/svelte/src/motion/tweened.js +++ b/packages/svelte/src/motion/tweened.js @@ -286,6 +286,7 @@ export class Tween { } set target(v) { + //@ts-ignore this.set(v); } } From 5ce7289ca2d411c718a1a640f855b54aa5623c2c Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:07:48 -0800 Subject: [PATCH 12/14] update spring.js --- packages/svelte/src/motion/spring.js | 60 ++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/packages/svelte/src/motion/spring.js b/packages/svelte/src/motion/spring.js index 3e25000b2f1a..d32f6cde7e97 100644 --- a/packages/svelte/src/motion/spring.js +++ b/packages/svelte/src/motion/spring.js @@ -28,12 +28,17 @@ function tick_spring(ctx, last_value, current_value, target_value) { const damper = ctx.opts.damping * velocity; const acceleration = (spring - damper) * ctx.inv_mass; const d = (velocity + acceleration) * ctx.dt; - if (Math.abs(d) < ctx.opts.precision && Math.abs(delta) < ctx.opts.precision) { + if ( + Math.abs(d) < ctx.opts.precision && + Math.abs(delta) < ctx.opts.precision + ) { return target_value; // settled } else { ctx.settled = false; // signal loop to keep ticking // @ts-ignore - return is_date(current_value) ? new Date(current_value.getTime() + d) : current_value + d; + return is_date(current_value) + ? new Date(current_value.getTime() + d) + : current_value + d; } } else if (Array.isArray(current_value)) { // @ts-ignore @@ -45,7 +50,12 @@ function tick_spring(ctx, last_value, current_value, target_value) { const next_value = {}; for (const k in current_value) { // @ts-ignore - next_value[k] = tick_spring(ctx, last_value[k], current_value[k], target_value[k]); + next_value[k] = tick_spring( + ctx, + last_value[k], + current_value[k], + target_value[k] + ); } // @ts-ignore return next_value; @@ -87,7 +97,11 @@ export function spring(value, opts = {}) { function set(new_value, opts = {}) { target_value = new_value; const token = (current_token = {}); - if (value == null || opts.hard || (spring.stiffness >= 1 && spring.damping >= 1)) { + if ( + value == null || + opts.hard || + (spring.stiffness >= 1 && spring.damping >= 1) + ) { cancel_task = true; // cancel any running animation last_time = raf.now(); last_value = new_value; @@ -113,11 +127,16 @@ export function spring(value, opts = {}) { inv_mass, opts: spring, settled: true, - //@ts-ignore + //@ts-ignore dt: ((now - last_time) * 60) / 1000 }; // @ts-ignore - const next_value = tick_spring(ctx, last_value, value, target_value); + const next_value = tick_spring( + ctx, + last_value, + value, + target_value + ); last_time = now; last_value = /** @type {T} */ (value); store.set((value = /** @type {T} */ (next_value))); @@ -137,7 +156,11 @@ export function spring(value, opts = {}) { // @ts-expect-error - class-only properties are missing const spring = { set, - update: (fn, opts) => set(fn(/** @type {T} */ (target_value), /** @type {T} */ (value)), opts), + update: (fn, opts) => + set( + fn(/** @type {T} */ (target_value), /** @type {T} */ (value)), + opts + ), subscribe: store.subscribe, stiffness, damping, @@ -190,9 +213,12 @@ export class Spring { constructor(value, options = {}) { this.#current.v = this.#target.v = value; - if (typeof options.stiffness === 'number') this.#stiffness.v = clamp(options.stiffness, 0, 1); - if (typeof options.damping === 'number') this.#damping.v = clamp(options.damping, 0, 1); - if (typeof options.precision === 'number') this.#precision.v = options.precision; + if (typeof options.stiffness === 'number') + this.#stiffness.v = clamp(options.stiffness, 0, 1); + if (typeof options.damping === 'number') + this.#damping.v = clamp(options.damping, 0, 1); + if (typeof options.precision === 'number') + this.#precision.v = options.precision; } /** @@ -235,7 +261,10 @@ export class Spring { var inv_mass_recovery_rate = 1000 / (this.#momentum * 60); this.#task ??= loop((now) => { - this.#inverse_mass = Math.min(this.#inverse_mass + inv_mass_recovery_rate, 1); + this.#inverse_mass = Math.min( + this.#inverse_mass + inv_mass_recovery_rate, + 1 + ); /** @type {import('./private').TickContext} */ const ctx = { @@ -249,7 +278,12 @@ export class Spring { dt: ((now - this.#last_time) * 60) / 1000 }; - var next = tick_spring(ctx, this.#last_value, this.#current.v, this.#target.v); + var next = tick_spring( + ctx, + this.#last_value, + this.#current.v, + this.#target.v + ); this.#last_value = this.#current.v; this.#last_time = now; set(this.#current, next); @@ -336,7 +370,7 @@ export class Spring { } set target(v) { - //@ts-ignore + //@ts-ignore this.set(v); } } From b3990303062beaf16e2ebfa7f52bcf2b0c660e2f Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:09:31 -0800 Subject: [PATCH 13/14] Update tweened.js --- packages/svelte/src/motion/tweened.js | 494 +++++++++++++------------- 1 file changed, 253 insertions(+), 241 deletions(-) diff --git a/packages/svelte/src/motion/tweened.js b/packages/svelte/src/motion/tweened.js index cc4bcd0e526e..e247c99702b9 100644 --- a/packages/svelte/src/motion/tweened.js +++ b/packages/svelte/src/motion/tweened.js @@ -16,63 +16,63 @@ import { get, render_effect } from 'svelte/internal/client'; * @returns {(t: number) => T} */ 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 = /** @type {Array} */ (b).map((bi, i) => { - return get_interpolator(/** @type {Array} */ (a)[i], bi); - }); - - // @ts-ignore - 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)) { - const an = a.getTime(); - const bn = b.getTime(); - const delta = bn - an; - - // @ts-ignore - return (t) => new Date(an + t * delta); - } - - const keys = Object.keys(b); - - /** @type {Record T>} */ - const interpolators = {}; - keys.forEach((key) => { - // @ts-ignore - interpolators[key] = get_interpolator(a[key], b[key]); - }); - - // @ts-ignore - return (t) => { - /** @type {Record} */ - const result = {}; - keys.forEach((key) => { - result[key] = interpolators[key](t); - }); - return result; - }; - } - - if (type === 'number') { - const delta = /** @type {number} */ (b) - /** @type {number} */ (a); - // @ts-ignore - 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 = /** @type {Array} */ (b).map((bi, i) => { + return get_interpolator(/** @type {Array} */ (a)[i], bi); + }); + + // @ts-ignore + 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)) { + const an = a.getTime(); + const bn = b.getTime(); + const delta = bn - an; + + // @ts-ignore + return (t) => new Date(an + t * delta); + } + + const keys = Object.keys(b); + + /** @type {Record T>} */ + const interpolators = {}; + keys.forEach((key) => { + // @ts-ignore + interpolators[key] = get_interpolator(a[key], b[key]); + }); + + // @ts-ignore + return (t) => { + /** @type {Record} */ + const result = {}; + keys.forEach((key) => { + result[key] = interpolators[key](t); + }); + return result; + }; + } + + if (type === 'number') { + const delta = /** @type {number} */ (b) - /** @type {number} */ (a); + // @ts-ignore + return (t) => a + t * delta; + } + + throw new Error(`Cannot interpolate ${type} values`); } /** @@ -85,75 +85,81 @@ function get_interpolator(a, b) { * @returns {Tweened} */ export function tweened(value, defaults = {}) { - const store = writable(value); - /** @type {Task} */ - let task; - let target_value = value; - /** - * @param {T} new_value - * @param {TweenedOptions} [opts] - */ - function set(new_value, opts) { - target_value = new_value; - - if (value == null) { - store.set((value = new_value)); - return Promise.resolve(); - } - - /** @type {Task | null} */ - let previous_task = task; - - let started = false; - let { - delay = 0, - duration = 400, - easing = linear, - interpolate = get_interpolator - } = { ...defaults, ...opts }; - - if (duration === 0) { - if (previous_task) { - previous_task.abort(); - previous_task = null; - } - store.set((value = target_value)); - return Promise.resolve(); - } - - const start = raf.now() + delay; - - /** @type {(t: number) => T} */ - let fn; - task = loop((now) => { - if (now < start) return true; - if (!started) { - fn = interpolate(/** @type {any} */ (value), new_value); - if (typeof duration === 'function') - duration = duration(/** @type {any} */ (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(/** @type {any} */ (target_value), /** @type {any} */ (value)), opts), - subscribe: store.subscribe - }; + const store = writable(value); + /** @type {Task} */ + let task; + let target_value = value; + /** + * @param {T} new_value + * @param {TweenedOptions} [opts] + */ + function set(new_value, opts) { + target_value = new_value; + + if (value == null) { + store.set((value = new_value)); + return Promise.resolve(); + } + + /** @type {Task | null} */ + let previous_task = task; + + let started = false; + let { + delay = 0, + duration = 400, + easing = linear, + interpolate = get_interpolator + } = { ...defaults, ...opts }; + + if (duration === 0) { + if (previous_task) { + previous_task.abort(); + previous_task = null; + } + store.set((value = target_value)); + return Promise.resolve(); + } + + const start = raf.now() + delay; + + /** @type {(t: number) => T} */ + let fn; + task = loop((now) => { + if (now < start) return true; + if (!started) { + fn = interpolate(/** @type {any} */ (value), new_value); + if (typeof duration === 'function') + duration = duration(/** @type {any} */ (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( + /** @type {any} */ (target_value), + /** @type {any} */ (value) + ), + opts + ), + subscribe: store.subscribe + }; } /** @@ -174,119 +180,125 @@ export function tweened(value, defaults = {}) { * @since 5.8.0 */ export class Tween { - #current = source(/** @type {T | undefined} */ (undefined)); - #target = source(/** @type {T | undefined} */ (undefined)); - - /** @type {TweenedOptions} */ - #defaults; - - /** @type {import('../internal/client/types').Task | null} */ - #task = null; - - /** - * @param {T} value - * @param {TweenedOptions} options - */ - constructor(value, options = {}) { - this.#current.v = this.#target.v = value; - this.#defaults = options; - } - - /** - * Create a tween whose value is bound to the return value of `fn`. This must be called - * inside an effect root (for example, during component initialisation). - * - * ```svelte - * - * ``` - * @template {number} U - * @param {() => U} fn - * @param {TweenedOptions} [options] - */ - static of(fn, options) { - const tween = new Tween(fn(), options); - - render_effect(() => { - tween.set(fn()); - }); - - return tween; - } - - /** - * Sets `tween.target` to `value` and returns a `Promise` that resolves if and when `tween.current` catches up to it. - * - * If `options` are provided, they will override the tween's defaults. - * @param {T} value - * @param {TweenedOptions} [options] - * @returns - */ - set(value, options) { - set(this.#target, value); - - let previous_value = this.#current.v; - let previous_task = this.#task; - - let started = false; - let { - delay = 0, - duration = 400, - easing = linear, - interpolate = get_interpolator - } = { ...this.#defaults, ...options }; - - const start = raf.now() + delay; - - /** @type {(t: number) => T} */ - let fn; - - this.#task = loop((now) => { - if (now < start) { - return true; - } - - if (!started) { - started = true; - - fn = interpolate(/** @type {any} */ (previous_value), value); - - if (typeof duration === 'function') { - duration = duration(/** @type {any} */ (previous_value), value); - } - - previous_task?.abort(); - } - - const elapsed = now - start; - - if (elapsed > /** @type {number} */ (duration)) { - set(this.#current, value); - return false; - } - - set(this.#current, fn(easing(elapsed / /** @type {number} */ (duration)))); - return true; - }); - - return this.#task.promise; - } - - get current() { - return get(this.#current); - } - - get target() { - return get(this.#target); - } - - set target(v) { - //@ts-ignore - this.set(v); - } + #current = source(/** @type {T | undefined} */ (undefined)); + #target = source(/** @type {T | undefined} */ (undefined)); + + /** @type {TweenedOptions} */ + #defaults; + + /** @type {import('../internal/client/types').Task | null} */ + #task = null; + + /** + * @param {T} value + * @param {TweenedOptions} options + */ + constructor(value, options = {}) { + this.#current.v = this.#target.v = value; + this.#defaults = options; + } + + /** + * Create a tween whose value is bound to the return value of `fn`. This must be called + * inside an effect root (for example, during component initialisation). + * + * ```svelte + * + * ``` + * @template {number} U + * @param {() => U} fn + * @param {TweenedOptions} [options] + */ + static of(fn, options) { + const tween = new Tween(fn(), options); + + render_effect(() => { + tween.set(fn()); + }); + + return tween; + } + + /** + * Sets `tween.target` to `value` and returns a `Promise` that resolves if and when `tween.current` catches up to it. + * + * If `options` are provided, they will override the tween's defaults. + * @param {T} value + * @param {TweenedOptions} [options] + * @returns + */ + set(value, options) { + set(this.#target, value); + + let previous_value = this.#current.v; + let previous_task = this.#task; + + let started = false; + let { + delay = 0, + duration = 400, + easing = linear, + interpolate = get_interpolator + } = { ...this.#defaults, ...options }; + + const start = raf.now() + delay; + + /** @type {(t: number) => T} */ + let fn; + + this.#task = loop((now) => { + if (now < start) { + return true; + } + + if (!started) { + started = true; + + fn = interpolate(/** @type {any} */ (previous_value), value); + + if (typeof duration === 'function') { + duration = duration( + /** @type {any} */ (previous_value), + value + ); + } + + previous_task?.abort(); + } + + const elapsed = now - start; + + if (elapsed > /** @type {number} */ (duration)) { + set(this.#current, value); + return false; + } + + set( + this.#current, + fn(easing(elapsed / /** @type {number} */ (duration))) + ); + return true; + }); + + return this.#task.promise; + } + + get current() { + return get(this.#current); + } + + get target() { + return get(this.#target); + } + + set target(v) { + //@ts-ignore + this.set(v); + } } From 84bcab90ab6c10348d65ecdf84e539512aaa34b4 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:10:00 -0800 Subject: [PATCH 14/14] Update spring.js --- packages/svelte/src/motion/spring.js | 646 +++++++++++++-------------- 1 file changed, 323 insertions(+), 323 deletions(-) diff --git a/packages/svelte/src/motion/spring.js b/packages/svelte/src/motion/spring.js index d32f6cde7e97..6e64e9fbd65a 100644 --- a/packages/svelte/src/motion/spring.js +++ b/packages/svelte/src/motion/spring.js @@ -19,49 +19,49 @@ import { deferred, noop } from '../internal/shared/utils.js'; * @returns {T} */ function tick_spring(ctx, last_value, current_value, target_value) { - if (typeof current_value === 'number' || is_date(current_value)) { - // @ts-ignore - const delta = target_value - current_value; - // @ts-ignore - const velocity = (current_value - last_value) / (ctx.dt || 1 / 60); // guard div by 0 - const spring = ctx.opts.stiffness * delta; - const damper = ctx.opts.damping * velocity; - const acceleration = (spring - damper) * ctx.inv_mass; - const d = (velocity + acceleration) * ctx.dt; - if ( - Math.abs(d) < ctx.opts.precision && - Math.abs(delta) < ctx.opts.precision - ) { - return target_value; // settled - } else { - ctx.settled = false; // signal loop to keep ticking - // @ts-ignore - return is_date(current_value) - ? new Date(current_value.getTime() + d) - : current_value + d; - } - } else if (Array.isArray(current_value)) { - // @ts-ignore - return current_value.map((_, i) => - // @ts-ignore - tick_spring(ctx, last_value[i], current_value[i], target_value[i]) - ); - } else if (typeof current_value === 'object') { - const next_value = {}; - for (const k in current_value) { - // @ts-ignore - next_value[k] = tick_spring( - ctx, - last_value[k], - current_value[k], - target_value[k] - ); - } - // @ts-ignore - return next_value; - } else { - throw new Error(`Cannot spring ${typeof current_value} values`); - } + if (typeof current_value === 'number' || is_date(current_value)) { + // @ts-ignore + const delta = target_value - current_value; + // @ts-ignore + const velocity = (current_value - last_value) / (ctx.dt || 1 / 60); // guard div by 0 + const spring = ctx.opts.stiffness * delta; + const damper = ctx.opts.damping * velocity; + const acceleration = (spring - damper) * ctx.inv_mass; + const d = (velocity + acceleration) * ctx.dt; + if ( + Math.abs(d) < ctx.opts.precision && + Math.abs(delta) < ctx.opts.precision + ) { + return target_value; // settled + } else { + ctx.settled = false; // signal loop to keep ticking + // @ts-ignore + return is_date(current_value) + ? new Date(current_value.getTime() + d) + : current_value + d; + } + } else if (Array.isArray(current_value)) { + // @ts-ignore + return current_value.map((_, i) => + // @ts-ignore + tick_spring(ctx, last_value[i], current_value[i], target_value[i]) + ); + } else if (typeof current_value === 'object') { + const next_value = {}; + for (const k in current_value) { + // @ts-ignore + next_value[k] = tick_spring( + ctx, + last_value[k], + current_value[k], + target_value[k] + ); + } + // @ts-ignore + return next_value; + } else { + throw new Error(`Cannot spring ${typeof current_value} values`); + } } /** @@ -74,99 +74,99 @@ function tick_spring(ctx, last_value, current_value, target_value) { * @returns {SpringStore} */ export function spring(value, opts = {}) { - const store = writable(value); - const { stiffness = 0.15, damping = 0.8, precision = 0.01 } = opts; - /** @type {number | undefined} */ - let last_time; - /** @type {Task | null} */ - let task; - /** @type {object} */ - let current_token; - - let last_value = /** @type {T | undefined} */ (value); - let target_value = /** @type {T | undefined} */ (value); - - let inv_mass = 1; - let inv_mass_recovery_rate = 0; - let cancel_task = false; - /** - * @param {T} new_value - * @param {SpringUpdateOpts} opts - * @returns {Promise} - */ - function set(new_value, opts = {}) { - target_value = new_value; - const token = (current_token = {}); - if ( - value == null || - opts.hard || - (spring.stiffness >= 1 && spring.damping >= 1) - ) { - cancel_task = true; // cancel any running animation - last_time = raf.now(); - last_value = new_value; - store.set((value = target_value)); - return Promise.resolve(); - } else if (opts.soft) { - const rate = opts.soft === true ? 0.5 : +opts.soft; - inv_mass_recovery_rate = 1 / (rate * 60); - inv_mass = 0; // infinite mass, unaffected by spring forces - } - if (!task) { - last_time = raf.now(); - cancel_task = false; - task = loop((now) => { - if (cancel_task) { - cancel_task = false; - task = null; - return false; - } - inv_mass = Math.min(inv_mass + inv_mass_recovery_rate, 1); - /** @type {TickContext} */ - const ctx = { - inv_mass, - opts: spring, - settled: true, - //@ts-ignore - dt: ((now - last_time) * 60) / 1000 - }; - // @ts-ignore - const next_value = tick_spring( - ctx, - last_value, - value, - target_value - ); - last_time = now; - last_value = /** @type {T} */ (value); - store.set((value = /** @type {T} */ (next_value))); - if (ctx.settled) { - task = null; - } - return !ctx.settled; - }); - } - return new Promise((fulfil) => { - /** @type {Task} */ (task).promise.then(() => { - if (token === current_token) fulfil(); - }); - }); - } - /** @type {SpringStore} */ - // @ts-expect-error - class-only properties are missing - const spring = { - set, - update: (fn, opts) => - set( - fn(/** @type {T} */ (target_value), /** @type {T} */ (value)), - opts - ), - subscribe: store.subscribe, - stiffness, - damping, - precision - }; - return spring; + const store = writable(value); + const { stiffness = 0.15, damping = 0.8, precision = 0.01 } = opts; + /** @type {number | undefined} */ + let last_time; + /** @type {Task | null} */ + let task; + /** @type {object} */ + let current_token; + + let last_value = /** @type {T | undefined} */ (value); + let target_value = /** @type {T | undefined} */ (value); + + let inv_mass = 1; + let inv_mass_recovery_rate = 0; + let cancel_task = false; + /** + * @param {T} new_value + * @param {SpringUpdateOpts} opts + * @returns {Promise} + */ + function set(new_value, opts = {}) { + target_value = new_value; + const token = (current_token = {}); + if ( + value == null || + opts.hard || + (spring.stiffness >= 1 && spring.damping >= 1) + ) { + cancel_task = true; // cancel any running animation + last_time = raf.now(); + last_value = new_value; + store.set((value = target_value)); + return Promise.resolve(); + } else if (opts.soft) { + const rate = opts.soft === true ? 0.5 : +opts.soft; + inv_mass_recovery_rate = 1 / (rate * 60); + inv_mass = 0; // infinite mass, unaffected by spring forces + } + if (!task) { + last_time = raf.now(); + cancel_task = false; + task = loop((now) => { + if (cancel_task) { + cancel_task = false; + task = null; + return false; + } + inv_mass = Math.min(inv_mass + inv_mass_recovery_rate, 1); + /** @type {TickContext} */ + const ctx = { + inv_mass, + opts: spring, + settled: true, + //@ts-ignore + dt: ((now - last_time) * 60) / 1000 + }; + // @ts-ignore + const next_value = tick_spring( + ctx, + last_value, + value, + target_value + ); + last_time = now; + last_value = /** @type {T} */ (value); + store.set((value = /** @type {T} */ (next_value))); + if (ctx.settled) { + task = null; + } + return !ctx.settled; + }); + } + return new Promise((fulfil) => { + /** @type {Task} */ (task).promise.then(() => { + if (token === current_token) fulfil(); + }); + }); + } + /** @type {SpringStore} */ + // @ts-expect-error - class-only properties are missing + const spring = { + set, + update: (fn, opts) => + set( + fn(/** @type {T} */ (target_value), /** @type {T} */ (value)), + opts + ), + subscribe: store.subscribe, + stiffness, + damping, + precision + }; + return spring; } /** @@ -187,192 +187,192 @@ export function spring(value, opts = {}) { * @since 5.8.0 */ export class Spring { - #stiffness = source(0.15); - #damping = source(0.8); - #precision = source(0.01); - - #current = source(/** @type {T | undefined} */ (undefined)); - #target = source(/** @type {T | undefined} */ (undefined)); - - #last_value = /** @type {T | undefined} */ (undefined); - #last_time = 0; - - #inverse_mass = 1; - #momentum = 0; - - /** @type {import('../internal/client/types').Task | null} */ - #task = null; - - /** @type {ReturnType | null} */ - #deferred = null; - - /** - * @param {T} value - * @param {SpringOpts} [options] - */ - constructor(value, options = {}) { - this.#current.v = this.#target.v = value; - - if (typeof options.stiffness === 'number') - this.#stiffness.v = clamp(options.stiffness, 0, 1); - if (typeof options.damping === 'number') - this.#damping.v = clamp(options.damping, 0, 1); - if (typeof options.precision === 'number') - this.#precision.v = options.precision; - } - - /** - * Create a spring whose value is bound to the return value of `fn`. This must be called - * inside an effect root (for example, during component initialisation). - * - * ```svelte - * - * ``` - * @template {number} U - * @param {() => U} fn - * @param {SpringOpts} [options] - */ - static of(fn, options) { - const spring = new Spring(fn(), options); - - render_effect(() => { - spring.set(fn()); - }); - - return spring; - } - - /** @param {T} value */ - #update(value) { - set(this.#target, value); - - this.#current.v ??= value; - this.#last_value ??= this.#current.v; - - if (!this.#task) { - this.#last_time = raf.now(); - - var inv_mass_recovery_rate = 1000 / (this.#momentum * 60); - - this.#task ??= loop((now) => { - this.#inverse_mass = Math.min( - this.#inverse_mass + inv_mass_recovery_rate, - 1 - ); - - /** @type {import('./private').TickContext} */ - const ctx = { - inv_mass: this.#inverse_mass, - opts: { - stiffness: this.#stiffness.v, - damping: this.#damping.v, - precision: this.#precision.v - }, - settled: true, - dt: ((now - this.#last_time) * 60) / 1000 - }; - - var next = tick_spring( - ctx, - this.#last_value, - this.#current.v, - this.#target.v - ); - this.#last_value = this.#current.v; - this.#last_time = now; - set(this.#current, next); - - if (ctx.settled) { - this.#task = null; - } - - return !ctx.settled; - }); - } - - return this.#task.promise; - } - - /** - * Sets `spring.target` to `value` and returns a `Promise` that resolves if and when `spring.current` catches up to it. - * - * If `options.instant` is `true`, `spring.current` immediately matches `spring.target`. - * - * If `options.preserveMomentum` is provided, the spring will continue on its current trajectory for - * the specified number of milliseconds. This is useful for things like 'fling' gestures. - * - * @param {T} value - * @param {SpringUpdateOpts} [options] - */ - set(value, options) { - this.#deferred?.reject(new Error('Aborted')); - - if (options?.instant || this.#current.v === undefined) { - this.#task?.abort(); - this.#task = null; - set(this.#current, set(this.#target, value)); - this.#last_value = value; - return Promise.resolve(); - } - - if (options?.preserveMomentum) { - this.#inverse_mass = 0; - this.#momentum = options.preserveMomentum; - } - - var d = (this.#deferred = deferred()); - d.promise.catch(noop); - - this.#update(value).then(() => { - if (d !== this.#deferred) return; - d.resolve(undefined); - }); - - return d.promise; - } - - get current() { - return get(this.#current); - } - - get damping() { - return get(this.#damping); - } - - set damping(v) { - set(this.#damping, clamp(v, 0, 1)); - } - - get precision() { - return get(this.#precision); - } - - set precision(v) { - set(this.#precision, v); - } - - get stiffness() { - return get(this.#stiffness); - } - - set stiffness(v) { - set(this.#stiffness, clamp(v, 0, 1)); - } - - get target() { - return get(this.#target); - } - - set target(v) { - //@ts-ignore - this.set(v); - } + #stiffness = source(0.15); + #damping = source(0.8); + #precision = source(0.01); + + #current = source(/** @type {T | undefined} */ (undefined)); + #target = source(/** @type {T | undefined} */ (undefined)); + + #last_value = /** @type {T | undefined} */ (undefined); + #last_time = 0; + + #inverse_mass = 1; + #momentum = 0; + + /** @type {import('../internal/client/types').Task | null} */ + #task = null; + + /** @type {ReturnType | null} */ + #deferred = null; + + /** + * @param {T} value + * @param {SpringOpts} [options] + */ + constructor(value, options = {}) { + this.#current.v = this.#target.v = value; + + if (typeof options.stiffness === 'number') + this.#stiffness.v = clamp(options.stiffness, 0, 1); + if (typeof options.damping === 'number') + this.#damping.v = clamp(options.damping, 0, 1); + if (typeof options.precision === 'number') + this.#precision.v = options.precision; + } + + /** + * Create a spring whose value is bound to the return value of `fn`. This must be called + * inside an effect root (for example, during component initialisation). + * + * ```svelte + * + * ``` + * @template {number} U + * @param {() => U} fn + * @param {SpringOpts} [options] + */ + static of(fn, options) { + const spring = new Spring(fn(), options); + + render_effect(() => { + spring.set(fn()); + }); + + return spring; + } + + /** @param {T} value */ + #update(value) { + set(this.#target, value); + + this.#current.v ??= value; + this.#last_value ??= this.#current.v; + + if (!this.#task) { + this.#last_time = raf.now(); + + var inv_mass_recovery_rate = 1000 / (this.#momentum * 60); + + this.#task ??= loop((now) => { + this.#inverse_mass = Math.min( + this.#inverse_mass + inv_mass_recovery_rate, + 1 + ); + + /** @type {import('./private').TickContext} */ + const ctx = { + inv_mass: this.#inverse_mass, + opts: { + stiffness: this.#stiffness.v, + damping: this.#damping.v, + precision: this.#precision.v + }, + settled: true, + dt: ((now - this.#last_time) * 60) / 1000 + }; + + var next = tick_spring( + ctx, + this.#last_value, + this.#current.v, + this.#target.v + ); + this.#last_value = this.#current.v; + this.#last_time = now; + set(this.#current, next); + + if (ctx.settled) { + this.#task = null; + } + + return !ctx.settled; + }); + } + + return this.#task.promise; + } + + /** + * Sets `spring.target` to `value` and returns a `Promise` that resolves if and when `spring.current` catches up to it. + * + * If `options.instant` is `true`, `spring.current` immediately matches `spring.target`. + * + * If `options.preserveMomentum` is provided, the spring will continue on its current trajectory for + * the specified number of milliseconds. This is useful for things like 'fling' gestures. + * + * @param {T} value + * @param {SpringUpdateOpts} [options] + */ + set(value, options) { + this.#deferred?.reject(new Error('Aborted')); + + if (options?.instant || this.#current.v === undefined) { + this.#task?.abort(); + this.#task = null; + set(this.#current, set(this.#target, value)); + this.#last_value = value; + return Promise.resolve(); + } + + if (options?.preserveMomentum) { + this.#inverse_mass = 0; + this.#momentum = options.preserveMomentum; + } + + var d = (this.#deferred = deferred()); + d.promise.catch(noop); + + this.#update(value).then(() => { + if (d !== this.#deferred) return; + d.resolve(undefined); + }); + + return d.promise; + } + + get current() { + return get(this.#current); + } + + get damping() { + return get(this.#damping); + } + + set damping(v) { + set(this.#damping, clamp(v, 0, 1)); + } + + get precision() { + return get(this.#precision); + } + + set precision(v) { + set(this.#precision, v); + } + + get stiffness() { + return get(this.#stiffness); + } + + set stiffness(v) { + set(this.#stiffness, clamp(v, 0, 1)); + } + + get target() { + return get(this.#target); + } + + set target(v) { + //@ts-ignore + this.set(v); + } } /** @@ -381,5 +381,5 @@ export class Spring { * @param {number} max */ function clamp(n, min, max) { - return Math.max(min, Math.min(max, n)); + return Math.max(min, Math.min(max, n)); }