@@ -2,6 +2,10 @@ import { writable } from '../store/index.js';
22import { loop } from '../internal/client/loop.js' ;
33import { raf } from '../internal/client/timing.js' ;
44import { is_date } from './utils.js' ;
5+ import { set , source } from '../internal/client/reactivity/sources.js' ;
6+ import { render_effect } from '../internal/client/reactivity/effects.js' ;
7+ import { get } from '../internal/client/runtime.js' ;
8+ import { deferred , noop } from '../internal/shared/utils.js' ;
59
610/**
711 * @template T
@@ -136,3 +140,155 @@ export function spring(value, opts = {}) {
136140 } ;
137141 return spring ;
138142}
143+
144+ /**
145+ * @template T
146+ */
147+ export class Spring {
148+ #stiffness = source ( 0.15 ) ;
149+ #damping = source ( 0.8 ) ;
150+ #precision = source ( 0.01 ) ;
151+
152+ #current = source ( /** @type {T } */ ( undefined ) ) ;
153+
154+ #target_value = /** @type {T } */ ( undefined ) ;
155+ #last_value = /** @type {T } */ ( undefined ) ;
156+ #last_time = 0 ;
157+
158+ #inverse_mass = 1 ;
159+ #momentum = 0 ;
160+
161+ /** @type {import('../internal/client/types').Task | null } */
162+ #task = null ;
163+
164+ /** @type {PromiseWithResolvers<any> | null } */
165+ #deferred = null ;
166+
167+ /**
168+ * @param {T | (() => T) } value
169+ * @param {{ stiffness?: number, damping?: number, precision?: number } } [options]
170+ */
171+ constructor ( value , options = { } ) {
172+ if ( typeof value === 'function' ) {
173+ render_effect ( ( ) => {
174+ this . #update( /** @type {() => T } */ ( value ) ( ) ) ;
175+ } ) ;
176+ } else {
177+ this . #current. v = this . #target_value = value ;
178+ }
179+
180+ if ( typeof options . stiffness === 'number' ) this . #stiffness. v = clamp ( options . stiffness , 0 , 1 ) ;
181+ if ( typeof options . damping === 'number' ) this . #damping. v = clamp ( options . damping , 0 , 1 ) ;
182+ if ( typeof options . precision === 'number' ) this . #precision. v = options . precision ;
183+ }
184+
185+ /** @param {T } value */
186+ #update( value ) {
187+ this . #target_value = value ;
188+
189+ this . #current. v ??= value ;
190+ this . #last_value ??= this . #current. v ;
191+
192+ if ( ! this . #task) {
193+ this . #last_time = raf . now ( ) ;
194+
195+ var inv_mass_recovery_rate = 1 / ( this . #momentum * 60 ) ;
196+
197+ this . #task ??= loop ( ( now ) => {
198+ this . #inverse_mass = Math . min ( this . #inverse_mass + inv_mass_recovery_rate , 1 ) ;
199+
200+ /** @type {import('./private').TickContext<T> } */
201+ const ctx = {
202+ inv_mass : this . #inverse_mass,
203+ opts : {
204+ stiffness : this . #stiffness. v ,
205+ damping : this . #damping. v ,
206+ precision : this . #precision. v
207+ } ,
208+ settled : true ,
209+ dt : ( ( now - this . #last_time) * 60 ) / 1000
210+ } ;
211+
212+ var next = tick_spring ( ctx , this . #last_value, this . #current. v , this . #target_value) ;
213+ this . #last_value = this . #current. v ;
214+ this . #last_time = now ;
215+ set ( this . #current, next ) ;
216+
217+ if ( ctx . settled ) {
218+ this . #task = null ;
219+ }
220+
221+ return ! ctx . settled ;
222+ } ) ;
223+ }
224+
225+ return this . #task. promise ;
226+ }
227+
228+ /**
229+ * @param {T } value
230+ * @param {{ instant?: boolean; preserveMomentum?: number } } [options]
231+ */
232+ set ( value , options ) {
233+ this . #deferred?. reject ( new Error ( 'Aborted' ) ) ;
234+
235+ if ( options ?. instant || this . #current. v === undefined ) {
236+ this . #task?. abort ( ) ;
237+ this . #task = null ;
238+ set ( this . #current, ( this . #target_value = value ) ) ;
239+ return Promise . resolve ( ) ;
240+ }
241+
242+ if ( options ?. preserveMomentum ) {
243+ this . #inverse_mass = 0 ;
244+ this . #momentum = options . preserveMomentum ;
245+ }
246+
247+ var d = ( this . #deferred = deferred ( ) ) ;
248+ d . promise . catch ( noop ) ;
249+
250+ this . #update( value ) . then ( ( ) => {
251+ if ( d !== this . #deferred) return ;
252+ d . resolve ( undefined ) ;
253+ } ) ;
254+
255+ return d . promise ;
256+ }
257+
258+ get current ( ) {
259+ return get ( this . #current) ;
260+ }
261+
262+ get damping ( ) {
263+ return get ( this . #damping) ;
264+ }
265+
266+ set damping ( v ) {
267+ set ( this . #damping, clamp ( v , 0 , 1 ) ) ;
268+ }
269+
270+ get precision ( ) {
271+ return get ( this . #precision) ;
272+ }
273+
274+ set precision ( v ) {
275+ set ( this . #precision, v ) ;
276+ }
277+
278+ get stiffness ( ) {
279+ return get ( this . #stiffness) ;
280+ }
281+
282+ set stiffness ( v ) {
283+ set ( this . #stiffness, clamp ( v , 0 , 1 ) ) ;
284+ }
285+ }
286+
287+ /**
288+ * @param {number } n
289+ * @param {number } min
290+ * @param {number } max
291+ */
292+ function clamp ( n , min , max ) {
293+ return Math . max ( min , Math . min ( max , n ) ) ;
294+ }
0 commit comments