Skip to content

Commit 80ffcc3

Browse files
feat: add Spring and Tween classes (#11519)
* feat: add Spring class * add some docs, Spring.of static method * add Tween class * lint * preserveMomentum in milliseconds * deprecate tweened * changeset * wrestle with types * more consolidation * flesh out the distinction a bit more, deprecate `subscribe` --------- Co-authored-by: Simon Holthausen <[email protected]>
1 parent 60c0dc7 commit 80ffcc3

File tree

7 files changed

+603
-39
lines changed

7 files changed

+603
-39
lines changed

.changeset/tame-bottles-switch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': minor
3+
---
4+
5+
feat: add `Spring` and `Tween` classes to `svelte/motion`

packages/svelte/src/internal/shared/utils.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,27 @@ export function run_all(arr) {
4444
}
4545
}
4646

47+
/**
48+
* TODO replace with Promise.withResolvers once supported widely enough
49+
* @template T
50+
*/
51+
export function deferred() {
52+
/** @type {(value: T) => void} */
53+
var resolve;
54+
55+
/** @type {(reason: any) => void} */
56+
var reject;
57+
58+
/** @type {Promise<T>} */
59+
var promise = new Promise((res, rej) => {
60+
resolve = res;
61+
reject = rej;
62+
});
63+
64+
// @ts-expect-error
65+
return { promise, resolve, reject };
66+
}
67+
4768
/**
4869
* @template V
4970
* @param {V} value

packages/svelte/src/motion/private.d.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { Spring } from './public.js';
2-
3-
export interface TickContext<T> {
1+
export interface TickContext {
42
inv_mass: number;
53
dt: number;
6-
opts: Spring<T>;
4+
opts: {
5+
stiffness: number;
6+
damping: number;
7+
precision: number;
8+
};
79
settled: boolean;
810
}
911

@@ -14,8 +16,22 @@ export interface SpringOpts {
1416
}
1517

1618
export interface SpringUpdateOpts {
19+
/**
20+
* @deprecated Only use this for the spring store; does nothing when set on the Spring class
21+
*/
1722
hard?: any;
23+
/**
24+
* @deprecated Only use this for the spring store; does nothing when set on the Spring class
25+
*/
1826
soft?: string | number | boolean;
27+
/**
28+
* Only use this for the Spring class; does nothing when set on the spring store
29+
*/
30+
instant?: boolean;
31+
/**
32+
* Only use this for the Spring class; does nothing when set on the spring store
33+
*/
34+
preserveMomentum?: number;
1935
}
2036

2137
export type Updater<T> = (target_value: T, value: T) => T;
Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,87 @@
1-
import { Readable } from '../store/public.js';
2-
import { SpringUpdateOpts, TweenedOptions, Updater } from './private.js';
1+
import { Readable, type Unsubscriber } from '../store/public.js';
2+
import { SpringUpdateOpts, TweenedOptions, Updater, SpringOpts } from './private.js';
3+
4+
// TODO we do declaration merging here in order to not have a breaking change (renaming the Spring interface)
5+
// this means both the Spring class and the Spring interface are merged into one with some things only
6+
// existing on one side. In Svelte 6, remove the type definition and move the jsdoc onto the class in spring.js
37

48
export interface Spring<T> extends Readable<T> {
5-
set: (new_value: T, opts?: SpringUpdateOpts) => Promise<void>;
9+
set(new_value: T, opts?: SpringUpdateOpts): Promise<void>;
10+
/**
11+
* @deprecated Only exists on the legacy `spring` store, not the `Spring` class
12+
*/
613
update: (fn: Updater<T>, opts?: SpringUpdateOpts) => Promise<void>;
14+
/**
15+
* @deprecated Only exists on the legacy `spring` store, not the `Spring` class
16+
*/
17+
subscribe(fn: (value: T) => void): Unsubscriber;
718
precision: number;
819
damping: number;
920
stiffness: number;
1021
}
1122

23+
/**
24+
* A wrapper for a value that behaves in a spring-like fashion. Changes to `spring.target` will cause `spring.current` to
25+
* move towards it over time, taking account of the `spring.stiffness` and `spring.damping` parameters.
26+
*
27+
* ```svelte
28+
* <script>
29+
* import { Spring } from 'svelte/motion';
30+
*
31+
* const spring = new Spring(0);
32+
* </script>
33+
*
34+
* <input type="range" bind:value={spring.target} />
35+
* <input type="range" bind:value={spring.current} disabled />
36+
* ```
37+
*/
38+
export class Spring<T> {
39+
constructor(value: T, options?: SpringOpts);
40+
41+
/**
42+
* Create a spring whose value is bound to the return value of `fn`. This must be called
43+
* inside an effect root (for example, during component initialisation).
44+
*
45+
* ```svelte
46+
* <script>
47+
* import { Spring } from 'svelte/motion';
48+
*
49+
* let { number } = $props();
50+
*
51+
* const spring = Spring.of(() => number);
52+
* </script>
53+
* ```
54+
*/
55+
static of<U>(fn: () => U, options?: SpringOpts): Spring<U>;
56+
57+
/**
58+
* Sets `spring.target` to `value` and returns a `Promise` that resolves if and when `spring.current` catches up to it.
59+
*
60+
* If `options.instant` is `true`, `spring.current` immediately matches `spring.target`.
61+
*
62+
* If `options.preserveMomentum` is provided, the spring will continue on its current trajectory for
63+
* the specified number of milliseconds. This is useful for things like 'fling' gestures.
64+
*/
65+
set(value: T, options?: SpringUpdateOpts): Promise<void>;
66+
67+
damping: number;
68+
precision: number;
69+
stiffness: number;
70+
/**
71+
* The end value of the spring.
72+
* This property only exists on the `Spring` class, not the legacy `spring` store.
73+
*/
74+
target: T;
75+
/**
76+
* The current value of the spring.
77+
* This property only exists on the `Spring` class, not the legacy `spring` store.
78+
*/
79+
get current(): T;
80+
}
81+
1282
export interface Tweened<T> extends Readable<T> {
1383
set(value: T, opts?: TweenedOptions<T>): Promise<void>;
1484
update(updater: Updater<T>, opts?: TweenedOptions<T>): Promise<void>;
1585
}
1686

17-
export * from './index.js';
87+
export { spring, tweened, Tween } from './index.js';

0 commit comments

Comments
 (0)