fix: export TweenOptions, SpringOptions, SpringUpdateOptions and Updater from svelte/motion (#17967)

Exports `TweenedOptions`, `SpringOpts`, `SpringUpdateOpts`, and
`Updater` from `svelte/motion`.

These types are required for the public method signatures of `spring`
and `tweened` (e.g., as parameters for `.set()` and `.update()`). This
PR makes them accessible to TypeScript users, following the established
pattern in modules like `svelte/store` and `svelte/transition`.

Internal implementation details like `TickContext` remain private as
they do not appear in any public-facing signatures.

Fixes #16151

### Before submitting the PR, please make sure you do the following

- [x] It's really useful if your PR references an issue where it is
discussed ahead of time. In many cases, features are absent for a
reason. For large changes, please create an RFC:
https://github.com/sveltejs/rfcs
- [x] Prefix your PR title with `feat:`, `fix:`, `chore:`, or `docs:`.
- [x] This message body should clearly illustrate what problems it
solves.
- [x] Ideally, include a test that fails without this PR but passes with
it.
- [x] If this PR changes code within `packages/svelte/src`, add a
changeset (`npx changeset`).

### Tests and linting

- [x] Run the tests with `pnpm test` and lint the project with `pnpm
lint`

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
Co-authored-by: Rich Harris <hello@rich-harris.dev>
pull/17985/head
moaaz 6 months ago committed by GitHub
parent 0c669d9bc9
commit a94924b677
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': minor
---
feat: export TweenOptions, SpringOptions, SpringUpdateOptions and Updater from svelte/motion

@ -8,37 +8,3 @@ export interface TickContext {
}; };
settled: boolean; settled: boolean;
} }
export interface SpringOpts {
stiffness?: number;
damping?: number;
precision?: number;
}
export interface SpringUpdateOpts {
/**
* @deprecated Only use this for the spring store; does nothing when set on the Spring class
*/
hard?: any;
/**
* @deprecated Only use this for the spring store; does nothing when set on the Spring class
*/
soft?: string | number | boolean;
/**
* Only use this for the Spring class; does nothing when set on the spring store
*/
instant?: boolean;
/**
* Only use this for the Spring class; does nothing when set on the spring store
*/
preserveMomentum?: number;
}
export type Updater<T> = (target_value: T, value: T) => T;
export interface TweenedOptions<T> {
delay?: number;
duration?: number | ((from: T, to: T) => number);
easing?: (t: number) => number;
interpolate?: (a: T, b: T) => (t: number) => T;
}

@ -1,16 +1,49 @@
import { Readable, type Unsubscriber } from '../store/public.js'; import { Readable, type Unsubscriber } from '../store/public.js';
import { SpringUpdateOpts, TweenedOptions, Updater, SpringOpts } from './private.js';
export interface SpringOptions {
stiffness?: number;
damping?: number;
precision?: number;
}
export interface SpringUpdateOptions {
/**
* @deprecated Only use this for the spring store; does nothing when set on the Spring class
*/
hard?: any;
/**
* @deprecated Only use this for the spring store; does nothing when set on the Spring class
*/
soft?: string | number | boolean;
/**
* Only use this for the Spring class; does nothing when set on the spring store
*/
instant?: boolean;
/**
* Only use this for the Spring class; does nothing when set on the spring store
*/
preserveMomentum?: number;
}
export type Updater<T> = (target_value: T, value: T) => T;
export interface TweenOptions<T> {
delay?: number;
duration?: number | ((from: T, to: T) => number);
easing?: (t: number) => number;
interpolate?: (a: T, b: T) => (t: number) => T;
}
// TODO we do declaration merging here in order to not have a breaking change (renaming the Spring interface) // TODO we do declaration merging here in order to not have a breaking change (renaming the Spring interface)
// this means both the Spring class and the Spring interface are merged into one with some things only // 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 // existing on one side. In Svelte 6, remove the type definition and move the jsdoc onto the class in spring.js
export interface Spring<T> extends Readable<T> { export interface Spring<T> extends Readable<T> {
set(new_value: T, opts?: SpringUpdateOpts): Promise<void>; set(new_value: T, opts?: SpringUpdateOptions): Promise<void>;
/** /**
* @deprecated Only exists on the legacy `spring` store, not the `Spring` class * @deprecated Only exists on the legacy `spring` store, not the `Spring` class
*/ */
update: (fn: Updater<T>, opts?: SpringUpdateOpts) => Promise<void>; update: (fn: Updater<T>, opts?: SpringUpdateOptions) => Promise<void>;
/** /**
* @deprecated Only exists on the legacy `spring` store, not the `Spring` class * @deprecated Only exists on the legacy `spring` store, not the `Spring` class
*/ */
@ -37,7 +70,7 @@ export interface Spring<T> extends Readable<T> {
* @since 5.8.0 * @since 5.8.0
*/ */
export class Spring<T> { export class Spring<T> {
constructor(value: T, options?: SpringOpts); constructor(value: T, options?: SpringOptions);
/** /**
* Create a spring whose value is bound to the return value of `fn`. This must be called * Create a spring whose value is bound to the return value of `fn`. This must be called
@ -53,7 +86,7 @@ export class Spring<T> {
* </script> * </script>
* ``` * ```
*/ */
static of<U>(fn: () => U, options?: SpringOpts): Spring<U>; static of<U>(fn: () => U, options?: SpringOptions): Spring<U>;
/** /**
* Sets `spring.target` to `value` and returns a `Promise` that resolves if and when `spring.current` catches up to it. * Sets `spring.target` to `value` and returns a `Promise` that resolves if and when `spring.current` catches up to it.
@ -63,7 +96,7 @@ export class Spring<T> {
* If `options.preserveMomentum` is provided, the spring will continue on its current trajectory for * 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. * the specified number of milliseconds. This is useful for things like 'fling' gestures.
*/ */
set(value: T, options?: SpringUpdateOpts): Promise<void>; set(value: T, options?: SpringUpdateOptions): Promise<void>;
damping: number; damping: number;
precision: number; precision: number;
@ -81,8 +114,8 @@ export class Spring<T> {
} }
export interface Tweened<T> extends Readable<T> { export interface Tweened<T> extends Readable<T> {
set(value: T, opts?: TweenedOptions<T>): Promise<void>; set(value: T, opts?: TweenOptions<T>): Promise<void>;
update(updater: Updater<T>, opts?: TweenedOptions<T>): Promise<void>; update(updater: Updater<T>, opts?: TweenOptions<T>): Promise<void>;
} }
export { prefersReducedMotion, spring, tweened, Tween } from './index.js'; export { prefersReducedMotion, spring, tweened, Tween } from './index.js';

@ -1,6 +1,6 @@
/** @import { Task } from '#client' */ /** @import { Task } from '#client' */
/** @import { SpringOpts, SpringUpdateOpts, TickContext } from './private.js' */ /** @import { TickContext } from './private.js' */
/** @import { Spring as SpringStore } from './public.js' */ /** @import { Spring as SpringStore, SpringOptions, SpringUpdateOptions } from './public.js' */
import { writable } from '../store/shared/index.js'; import { writable } from '../store/shared/index.js';
import { loop } from '../internal/client/loop.js'; import { loop } from '../internal/client/loop.js';
import { raf } from '../internal/client/timing.js'; import { raf } from '../internal/client/timing.js';
@ -62,7 +62,7 @@ function tick_spring(ctx, last_value, current_value, target_value) {
* @deprecated Use [`Spring`](https://svelte.dev/docs/svelte/svelte-motion#Spring) instead * @deprecated Use [`Spring`](https://svelte.dev/docs/svelte/svelte-motion#Spring) instead
* @template [T=any] * @template [T=any]
* @param {T} [value] * @param {T} [value]
* @param {SpringOpts} [opts] * @param {SpringOptions} [opts]
* @returns {SpringStore<T>} * @returns {SpringStore<T>}
*/ */
export function spring(value, opts = {}) { export function spring(value, opts = {}) {
@ -83,7 +83,7 @@ export function spring(value, opts = {}) {
let cancel_task = false; let cancel_task = false;
/** /**
* @param {T} new_value * @param {T} new_value
* @param {SpringUpdateOpts} opts * @param {SpringUpdateOptions} opts
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
function set(new_value, opts = {}) { function set(new_value, opts = {}) {
@ -191,7 +191,7 @@ export class Spring {
/** /**
* @param {T} value * @param {T} value
* @param {SpringOpts} [options] * @param {SpringOptions} [options]
*/ */
constructor(value, options = {}) { constructor(value, options = {}) {
this.#current = DEV ? tag(state(value), 'Spring.current') : state(value); this.#current = DEV ? tag(state(value), 'Spring.current') : state(value);
@ -225,7 +225,7 @@ export class Spring {
* ``` * ```
* @template U * @template U
* @param {() => U} fn * @param {() => U} fn
* @param {SpringOpts} [options] * @param {SpringOptions} [options]
*/ */
static of(fn, options) { static of(fn, options) {
const spring = new Spring(fn(), options); const spring = new Spring(fn(), options);
@ -293,7 +293,7 @@ export class Spring {
* the specified number of milliseconds. This is useful for things like 'fling' gestures. * the specified number of milliseconds. This is useful for things like 'fling' gestures.
* *
* @param {T} value * @param {T} value
* @param {SpringUpdateOpts} [options] * @param {SpringUpdateOptions} [options]
*/ */
set(value, options) { set(value, options) {
this.#deferred?.reject(new Error('Aborted')); this.#deferred?.reject(new Error('Aborted'));

@ -1,6 +1,5 @@
/** @import { Task } from '../internal/client/types' */ /** @import { Task } from '../internal/client/types' */
/** @import { Tweened } from './public' */ /** @import { Tweened, TweenOptions } from './public' */
/** @import { TweenedOptions } from './private' */
import { writable } from '../store/shared/index.js'; import { writable } from '../store/shared/index.js';
import { raf } from '../internal/client/timing.js'; import { raf } from '../internal/client/timing.js';
import { loop } from '../internal/client/loop.js'; import { loop } from '../internal/client/loop.js';
@ -84,7 +83,7 @@ function get_interpolator(a, b) {
* @deprecated Use [`Tween`](https://svelte.dev/docs/svelte/svelte-motion#Tween) instead * @deprecated Use [`Tween`](https://svelte.dev/docs/svelte/svelte-motion#Tween) instead
* @template T * @template T
* @param {T} [value] * @param {T} [value]
* @param {TweenedOptions<T>} [defaults] * @param {TweenOptions<T>} [defaults]
* @returns {Tweened<T>} * @returns {Tweened<T>}
*/ */
export function tweened(value, defaults = {}) { export function tweened(value, defaults = {}) {
@ -94,7 +93,7 @@ export function tweened(value, defaults = {}) {
let target_value = value; let target_value = value;
/** /**
* @param {T} new_value * @param {T} new_value
* @param {TweenedOptions<T>} [opts] * @param {TweenOptions<T>} [opts]
*/ */
function set(new_value, opts) { function set(new_value, opts) {
target_value = new_value; target_value = new_value;
@ -180,7 +179,7 @@ export class Tween {
#current; #current;
#target; #target;
/** @type {TweenedOptions<T>} */ /** @type {TweenOptions<T>} */
#defaults; #defaults;
/** @type {import('../internal/client/types').Task | null} */ /** @type {import('../internal/client/types').Task | null} */
@ -188,7 +187,7 @@ export class Tween {
/** /**
* @param {T} value * @param {T} value
* @param {TweenedOptions<T>} options * @param {TweenOptions<T>} options
*/ */
constructor(value, options = {}) { constructor(value, options = {}) {
this.#current = state(value); this.#current = state(value);
@ -216,7 +215,7 @@ export class Tween {
* ``` * ```
* @template U * @template U
* @param {() => U} fn * @param {() => U} fn
* @param {TweenedOptions<U>} [options] * @param {TweenOptions<U>} [options]
*/ */
static of(fn, options) { static of(fn, options) {
const tween = new Tween(fn(), options); const tween = new Tween(fn(), options);
@ -233,7 +232,7 @@ export class Tween {
* *
* If `options` are provided, they will override the tween's defaults. * If `options` are provided, they will override the tween's defaults.
* @param {T} value * @param {T} value
* @param {TweenedOptions<T>} [options] * @param {TweenOptions<T>} [options]
* @returns * @returns
*/ */
set(value, options) { set(value, options) {

@ -0,0 +1,22 @@
import {
type TweenOptions,
type SpringOptions,
type SpringUpdateOptions,
type Updater
} from 'svelte/motion';
let tweenOptions: TweenOptions<number> = {
delay: 100,
duration: 400
};
let springOptions: SpringOptions = {
stiffness: 0.1,
damping: 0.5
};
let springUpdateOptions: SpringUpdateOptions = {
instant: true
};
let updater: Updater<number> = (target, value) => target + value;

@ -2005,16 +2005,50 @@ declare module 'svelte/legacy' {
declare module 'svelte/motion' { declare module 'svelte/motion' {
import type { MediaQuery } from 'svelte/reactivity'; import type { MediaQuery } from 'svelte/reactivity';
export interface SpringOptions {
stiffness?: number;
damping?: number;
precision?: number;
}
export interface SpringUpdateOptions {
/**
* @deprecated Only use this for the spring store; does nothing when set on the Spring class
*/
hard?: any;
/**
* @deprecated Only use this for the spring store; does nothing when set on the Spring class
*/
soft?: string | number | boolean;
/**
* Only use this for the Spring class; does nothing when set on the spring store
*/
instant?: boolean;
/**
* Only use this for the Spring class; does nothing when set on the spring store
*/
preserveMomentum?: number;
}
export type Updater<T> = (target_value: T, value: T) => T;
export interface TweenOptions<T> {
delay?: number;
duration?: number | ((from: T, to: T) => number);
easing?: (t: number) => number;
interpolate?: (a: T, b: T) => (t: number) => T;
}
// TODO we do declaration merging here in order to not have a breaking change (renaming the Spring interface) // TODO we do declaration merging here in order to not have a breaking change (renaming the Spring interface)
// this means both the Spring class and the Spring interface are merged into one with some things only // 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 // existing on one side. In Svelte 6, remove the type definition and move the jsdoc onto the class in spring.js
export interface Spring<T> extends Readable<T> { export interface Spring<T> extends Readable<T> {
set(new_value: T, opts?: SpringUpdateOpts): Promise<void>; set(new_value: T, opts?: SpringUpdateOptions): Promise<void>;
/** /**
* @deprecated Only exists on the legacy `spring` store, not the `Spring` class * @deprecated Only exists on the legacy `spring` store, not the `Spring` class
*/ */
update: (fn: Updater<T>, opts?: SpringUpdateOpts) => Promise<void>; update: (fn: Updater<T>, opts?: SpringUpdateOptions) => Promise<void>;
/** /**
* @deprecated Only exists on the legacy `spring` store, not the `Spring` class * @deprecated Only exists on the legacy `spring` store, not the `Spring` class
*/ */
@ -2041,7 +2075,7 @@ declare module 'svelte/motion' {
* @since 5.8.0 * @since 5.8.0
*/ */
export class Spring<T> { export class Spring<T> {
constructor(value: T, options?: SpringOpts); constructor(value: T, options?: SpringOptions);
/** /**
* Create a spring whose value is bound to the return value of `fn`. This must be called * Create a spring whose value is bound to the return value of `fn`. This must be called
@ -2057,7 +2091,7 @@ declare module 'svelte/motion' {
* </script> * </script>
* ``` * ```
*/ */
static of<U>(fn: () => U, options?: SpringOpts): Spring<U>; static of<U>(fn: () => U, options?: SpringOptions): Spring<U>;
/** /**
* Sets `spring.target` to `value` and returns a `Promise` that resolves if and when `spring.current` catches up to it. * Sets `spring.target` to `value` and returns a `Promise` that resolves if and when `spring.current` catches up to it.
@ -2067,7 +2101,7 @@ declare module 'svelte/motion' {
* If `options.preserveMomentum` is provided, the spring will continue on its current trajectory for * 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. * the specified number of milliseconds. This is useful for things like 'fling' gestures.
*/ */
set(value: T, options?: SpringUpdateOpts): Promise<void>; set(value: T, options?: SpringUpdateOptions): Promise<void>;
damping: number; damping: number;
precision: number; precision: number;
@ -2085,8 +2119,8 @@ declare module 'svelte/motion' {
} }
export interface Tweened<T> extends Readable<T> { export interface Tweened<T> extends Readable<T> {
set(value: T, opts?: TweenedOptions<T>): Promise<void>; set(value: T, opts?: TweenOptions<T>): Promise<void>;
update(updater: Updater<T>, opts?: TweenedOptions<T>): Promise<void>; update(updater: Updater<T>, opts?: TweenOptions<T>): Promise<void>;
} }
/** Callback to inform of a value updates. */ /** Callback to inform of a value updates. */
type Subscriber<T> = (value: T) => void; type Subscriber<T> = (value: T) => void;
@ -2103,39 +2137,6 @@ declare module 'svelte/motion' {
*/ */
subscribe(this: void, run: Subscriber<T>, invalidate?: () => void): Unsubscriber; subscribe(this: void, run: Subscriber<T>, invalidate?: () => void): Unsubscriber;
} }
interface SpringOpts {
stiffness?: number;
damping?: number;
precision?: number;
}
interface SpringUpdateOpts {
/**
* @deprecated Only use this for the spring store; does nothing when set on the Spring class
*/
hard?: any;
/**
* @deprecated Only use this for the spring store; does nothing when set on the Spring class
*/
soft?: string | number | boolean;
/**
* Only use this for the Spring class; does nothing when set on the spring store
*/
instant?: boolean;
/**
* Only use this for the Spring class; does nothing when set on the spring store
*/
preserveMomentum?: number;
}
type Updater<T> = (target_value: T, value: T) => T;
interface TweenedOptions<T> {
delay?: number;
duration?: number | ((from: T, to: T) => number);
easing?: (t: number) => number;
interpolate?: (a: T, b: T) => (t: number) => T;
}
/** /**
* A [media query](https://svelte.dev/docs/svelte/svelte-reactivity#MediaQuery) that matches if the user [prefers reduced motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion). * A [media query](https://svelte.dev/docs/svelte/svelte-reactivity#MediaQuery) that matches if the user [prefers reduced motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion).
* *
@ -2165,13 +2166,13 @@ declare module 'svelte/motion' {
* *
* @deprecated Use [`Spring`](https://svelte.dev/docs/svelte/svelte-motion#Spring) instead * @deprecated Use [`Spring`](https://svelte.dev/docs/svelte/svelte-motion#Spring) instead
* */ * */
export function spring<T = any>(value?: T | undefined, opts?: SpringOpts | undefined): Spring<T>; export function spring<T = any>(value?: T | undefined, opts?: SpringOptions | undefined): Spring<T>;
/** /**
* A tweened store in Svelte is a special type of store that provides smooth transitions between state values over time. * 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 * @deprecated Use [`Tween`](https://svelte.dev/docs/svelte/svelte-motion#Tween) instead
* */ * */
export function tweened<T>(value?: T | undefined, defaults?: TweenedOptions<T> | undefined): Tweened<T>; export function tweened<T>(value?: T | undefined, defaults?: TweenOptions<T> | undefined): Tweened<T>;
/** /**
* A wrapper for a value that tweens smoothly to its target value. Changes to `tween.target` will cause `tween.current` to * A wrapper for a value that tweens smoothly to its target value. Changes to `tween.target` will cause `tween.current` to
* move towards it over time, taking account of the `delay`, `duration` and `easing` options. * move towards it over time, taking account of the `delay`, `duration` and `easing` options.
@ -2204,15 +2205,15 @@ declare module 'svelte/motion' {
* ``` * ```
* *
*/ */
static of<U>(fn: () => U, options?: TweenedOptions<U> | undefined): Tween<U>; static of<U>(fn: () => U, options?: TweenOptions<U> | undefined): Tween<U>;
constructor(value: T, options?: TweenedOptions<T>); constructor(value: T, options?: TweenOptions<T>);
/** /**
* Sets `tween.target` to `value` and returns a `Promise` that resolves if and when `tween.current` catches up to it. * 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. * If `options` are provided, they will override the tween's defaults.
* */ * */
set(value: T, options?: TweenedOptions<T> | undefined): Promise<void>; set(value: T, options?: TweenOptions<T> | undefined): Promise<void>;
get current(): T; get current(): T;
set target(v: T); set target(v: T);
get target(): T; get target(): T;

Loading…
Cancel
Save