From f0a6b6d49673cc5546bf94f693bbc56b396c9c22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramirez=20Vargas=2C=20Jos=C3=A9=20Pablo?= Date: Sun, 29 Dec 2024 10:52:15 -0600 Subject: [PATCH] feat: Restrict types for Spring and Tween Closes Improve Spring data typing, or improving it so it doesn't bother about unsupported data types #14851 --- packages/svelte/src/motion/public.d.ts | 16 ++++++++++++++-- packages/svelte/src/motion/spring.js | 18 ++++++++++-------- packages/svelte/src/motion/tweened.js | 12 +++++++++--- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/packages/svelte/src/motion/public.d.ts b/packages/svelte/src/motion/public.d.ts index 4e74d4b76f06..116193e269d1 100644 --- a/packages/svelte/src/motion/public.d.ts +++ b/packages/svelte/src/motion/public.d.ts @@ -20,6 +20,18 @@ export interface Spring extends Readable { stiffness: number; } +/** + * Defines the primitive data types that Spring and Tween objects can calculate on. + */ +export type MotionPrimitive = number | Date; + +/** + * Defines the type of objects Spring and Tween objects can work on. + */ +export interface MotionRecord { + [x: string]: MotionPrimitive | MotionRecord | (MotionPrimitive | MotionRecord)[]; +} + /** * A wrapper for a value that behaves in a spring-like fashion. Changes to `spring.target` will cause `spring.current` to * move towards it over time, taking account of the `spring.stiffness` and `spring.damping` parameters. @@ -36,7 +48,7 @@ export interface Spring extends Readable { * ``` * @since 5.8.0 */ -export class Spring { +export class Spring { constructor(value: T, options?: SpringOpts); /** @@ -53,7 +65,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. diff --git a/packages/svelte/src/motion/spring.js b/packages/svelte/src/motion/spring.js index bc30ce957854..ace6ac44cb8f 100644 --- a/packages/svelte/src/motion/spring.js +++ b/packages/svelte/src/motion/spring.js @@ -1,6 +1,6 @@ /** @import { Task } from '#client' */ /** @import { SpringOpts, SpringUpdateOpts, TickContext } from './private.js' */ -/** @import { Spring as SpringStore } from './public.js' */ +/** @import { MotionRecord, Spring as SpringStore } from './public.js' */ import { writable } from '../store/shared/index.js'; import { loop } from '../internal/client/loop.js'; import { raf } from '../internal/client/timing.js'; @@ -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 {MotionRecord[string]} [T=any] * @param {T} [value] * @param {SpringOpts} [opts] * @returns {SpringStore} @@ -159,7 +159,7 @@ export function spring(value, opts = {}) { * * * ``` - * @template T + * @template {MotionRecord[string]} T * @since 5.8.0 */ export class Spring { @@ -167,9 +167,10 @@ export class Spring { #damping = source(0.8); #precision = source(0.01); - #current = source(/** @type {T} */ (undefined)); - #target = source(/** @type {T} */ (undefined)); - + #current; + #target; + + // @ts-expect-error Undefined doesn't satisfy the constraint of T. #last_value = /** @type {T} */ (undefined); #last_time = 0; @@ -187,7 +188,8 @@ export class Spring { * @param {SpringOpts} [options] */ constructor(value, options = {}) { - this.#current.v = this.#target.v = value; + this.#current = source(value); + this.#target = source(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); @@ -207,7 +209,7 @@ export class Spring { * const spring = Spring.of(() => number); * * ``` - * @template U + * @template {MotionRecord[string]} U * @param {() => U} fn * @param {SpringOpts} [options] */ diff --git a/packages/svelte/src/motion/tweened.js b/packages/svelte/src/motion/tweened.js index d732dbc2831a..8e9c1a4825d7 100644 --- a/packages/svelte/src/motion/tweened.js +++ b/packages/svelte/src/motion/tweened.js @@ -1,5 +1,5 @@ /** @import { Task } from '../internal/client/types' */ -/** @import { Tweened } from './public' */ +/** @import { MotionRecord, Tweened } from './public' */ /** @import { TweenedOptions } from './private' */ import { writable } from '../store/shared/index.js'; import { raf } from '../internal/client/timing.js'; @@ -170,11 +170,15 @@ export function tweened(value, defaults = {}) { * * * ``` - * @template T + * + * Refer to the `MotionRecord` type to understand all possible value types that Tween can handle. + * @template {MotionRecord[string]} T * @since 5.8.0 */ export class Tween { + // @ts-expect-error Undefined doesn't satisfy the constraint of T. #current = source(/** @type {T} */ (undefined)); + // @ts-expect-error Undefined doesn't satisfy the constraint of T. #target = source(/** @type {T} */ (undefined)); /** @type {TweenedOptions} */ @@ -205,7 +209,9 @@ export class Tween { * const tween = Tween.of(() => number); * * ``` - * @template U + * + * Refer to the `MotionRecord` type to understand all possible value types that Tween can handle. + * @template {MotionRecord[string]} U * @param {() => U} fn * @param {TweenedOptions} [options] */