coily 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/css.js CHANGED
@@ -1,4 +1,4 @@
1
- import { i as State, n as OverdampedSolver, o as invariant, r as UnderdampedSolver, t as CriticallyDampedSolver } from "./solver-CVXOTDkZ.js";
1
+ import { i as State, n as OverdampedSolver, o as invariant, r as UnderdampedSolver, t as CriticallyDampedSolver } from "./solver-CAXuxdKj.js";
2
2
  //#region src/css.ts
3
3
  function pickSolver(config, state) {
4
4
  const solver = config.dampingRatio < 1 ? new UnderdampedSolver(state) : config.dampingRatio === 1 ? new CriticallyDampedSolver(state) : new OverdampedSolver(state);
@@ -119,11 +119,13 @@ function reconstructionError(dense, simplified) {
119
119
  return worst;
120
120
  }
121
121
  /**
122
- * Turns a resolved spring config into a CSS `linear()` easing and the
123
- * duration in ms to pair it with. A spring that settles returns a
124
- * `'settle'` easing. A pure undamped config (`dampingRatio: 0` with
125
- * passthrough `arrival`) has no friction and never settles, so it
126
- * returns a `'loop'` easing for an infinite alternating animation.
122
+ * Turns a spring config into a CSS `linear()` easing and the duration in
123
+ * ms to pair it with. A settling config returns a `'settle'` easing. A
124
+ * pure undamped config (`dampingRatio: 0` with passthrough `arrival`)
125
+ * never settles and returns a `'loop'` easing for an infinite alternating
126
+ * animation. Any other config that never settles, such as
127
+ * `dampingRatio: 0` with `arrival: -1`, returns a `'settle'` easing cut
128
+ * off at `maxDuration`.
127
129
  */
128
130
  function springToLinear(config, options = {}) {
129
131
  const displacement = options.displacement ?? 1;
@@ -137,15 +139,17 @@ function springToLinear(config, options = {}) {
137
139
  return buildSettle(config, displacement, Math.min(settleTime, maxDuration), maxError);
138
140
  }
139
141
  /**
140
- * Like `springToLinear`, but from a spring caught mid-flight rather than at
141
- * rest: `state` carries both the displacement from the target (`position`)
142
- * and the current `velocity`, so the easing continues the motion smoothly
143
- * instead of restarting. Use it to regenerate an animation when its target
144
- * changes — the interruption carries momentum instead of stalling. The
145
- * from-rest shape trick doesn't apply here (the curve now depends on the
146
- * velocity), so this is computed per interruption, not cached per config.
142
+ * Like `springToLinear`, but for a spring caught mid-flight. `state`
143
+ * carries the displacement from the target (`position`) and the current
144
+ * `velocity`, so the easing continues the motion instead of restarting it.
145
+ * Use it to regenerate an animation when its target changes. The curve
146
+ * depends on the velocity, so compute it per interruption instead of
147
+ * caching it per config.
147
148
  *
148
- * Always a settling easing; `position` must be nonzero.
149
+ * The result is always a `'settle'` easing, timed from `position`, so
150
+ * `options.displacement` is ignored. A config that never settles is cut
151
+ * off at `maxDuration`. Throws when `position` is 0 (within 1e-9), even if
152
+ * `velocity` is not.
149
153
  */
150
154
  function springFromState(config, state, options = {}) {
151
155
  const maxError = options.maxError ?? .0025;
@@ -155,11 +159,12 @@ function springFromState(config, state, options = {}) {
155
159
  return buildSettle(config, state.position, Math.min(settleTime, maxDuration), maxError, state.velocity);
156
160
  }
157
161
  /**
158
- * Evolves a spring `state` forward by `elapsedMs`, returning the new
159
- * displacement and velocity. Pair it with a running `element.animate` — read
160
- * the animation's `currentTime`, evolve the state it started from, and you
161
- * have the exact live velocity the Web Animations API never exposes, ready
162
- * to hand to `springFromState` on interruption.
162
+ * Advances a spring `state` by `elapsedMs` ms and returns the new
163
+ * displacement and velocity. `elapsedMs <= 0` returns a copy of `state`.
164
+ *
165
+ * The Web Animations API doesn't expose velocity, so to interrupt a running
166
+ * `element.animate`, advance the state it started from by its `currentTime`
167
+ * and hand the result to `springFromState`.
163
168
  */
164
169
  function springStateAt(config, state, elapsedMs) {
165
170
  if (elapsedMs <= 0) return {
@@ -207,7 +212,7 @@ function asArray(spec) {
207
212
  * Builds arguments for `element.animate(keyframes, options)`: two keyframes
208
213
  * carrying the endpoint values, and options carrying the spring's `linear()`
209
214
  * easing and duration. Pass an array of specs to drive several properties
210
- * from one spring. A settling spring fills forwards; an undamped one repeats
215
+ * from one spring. A settling spring fills forwards. An undamped one repeats
211
216
  * infinitely in alternating directions.
212
217
  *
213
218
  * @example
@@ -241,10 +246,11 @@ function springToWaapi(config, spec, options = {}) {
241
246
  };
242
247
  }
243
248
  /**
244
- * Builds CSS text for the animation: a `@keyframes` rule and an `animation`
245
- * shorthand value, driving one property or several from one spring. The
246
- * `name` option (default `'coily'`) names the `@keyframes` rule and is
247
- * referenced by the `animation` value.
249
+ * Builds CSS text for the animation: a `@keyframes` rule named by `name`
250
+ * (default `'coily'`) and an `animation` shorthand value that references
251
+ * it, driving one property or several from one spring. A settling spring
252
+ * fills forwards. An undamped one repeats infinitely in alternating
253
+ * directions.
248
254
  *
249
255
  * @example
250
256
  * ```ts
@@ -271,17 +277,35 @@ function springToCss(config, spec, options = {}) {
271
277
  };
272
278
  }
273
279
  /**
274
- * Builds a CSS `transition` value that springs a property change: set it on
275
- * the element, then change the property (on `:hover`, a toggled class, a
276
- * state attribute) to animate. Pass several specs for one `transition`
277
- * across properties.
280
+ * Builds a CSS `transition` value that animates a property change with the
281
+ * spring. Set it on the element, then change the property, for example on
282
+ * `:hover`, with a toggled class, or with a state attribute. Pass several
283
+ * specs to build one `transition` for several properties.
284
+ *
285
+ * The values come from your CSS rules. Each spec's `from` and `to` only
286
+ * size the move, which tunes the duration. An undamped (looping) config
287
+ * throws, because a transition cannot loop.
288
+ *
289
+ * A transition starts from rest unless its spec carries a `velocity`, in
290
+ * the property's units per second, such as a drag's speed at release. The
291
+ * velocity reshapes the curve and the duration, so build the transition at
292
+ * the handoff. A spec with a velocity must be the only spec in the call.
293
+ *
294
+ * ```ts
295
+ * // Release a dragged card back to 0 at the drag's speed.
296
+ * card.style.transition = springToTransition(config, {
297
+ * property: 'translate',
298
+ * from: dragX,
299
+ * to: 0,
300
+ * velocity: releaseVelocity,
301
+ * })
302
+ * card.style.translate = '0px'
303
+ * ```
278
304
  *
279
- * Only settling springs — a transition can't loop. It also can't carry
280
- * momentum: a change interrupted mid-flight restarts the spring from rest
281
- * instead of reversing through its current velocity, so it stalls at the
282
- * turn. For motion that's reversed often, drive a live `Spring` instead. The
283
- * specs' `from`/`to` only tune the duration; the actual values live in your
284
- * CSS rules.
305
+ * The browser carries no momentum between transitions, so a change that
306
+ * interrupts one mid-flight stalls at the turn. To keep the momentum, read
307
+ * the velocity with `springStateAt` and rebuild the transition with it.
308
+ * For motion that reverses often, drive a live `Spring` instead.
285
309
  */
286
310
  function springToTransition(config, spec, options = {}) {
287
311
  const specs = asArray(spec);
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { i as defineSpring, n as SpringDefinitionOptions, r as SpringState, t as SpringDefinition } from "./config-OOMMRdK8.js";
2
- import { _ as accelerationOf, a as createSpringSystem, b as SpringSourceSymbol, c as ConfigShape, g as KinematicSource, h as SpringTarget, i as SpringSystemOptions, m as Spring, n as SpringOptions, o as CompositeSpring, p as Purpose, r as SpringSystem, s as CompositeSpringTarget, t as CompositeSpringOptions, u as PurposeShape, v as velocityOf, x as mapSpring, y as SpringSource } from "./system-Du_fXXmO.js";
3
- export { type CompositeSpring, type CompositeSpringOptions, type CompositeSpringTarget, type ConfigShape, type KinematicSource, type Purpose, type PurposeShape, type Spring, type SpringDefinition, type SpringDefinitionOptions, type SpringOptions, type SpringSource, SpringSourceSymbol, type SpringState, type SpringSystem, type SpringSystemOptions, type SpringTarget, accelerationOf, createSpringSystem, defineSpring, mapSpring, velocityOf };
1
+ import { i as defineSpring, n as SpringDefinitionOptions, r as SpringState, t as SpringDefinition } from "./config-B8M2xp26.js";
2
+ import { A as SpringSourceSymbol, C as accelerationOf, D as SourceValues, E as SourceShape, O as SpringSource, S as TargetedSourceApi, T as velocityOf, _ as Spring, a as createSpringSystem, b as KinematicSourceApi, c as CompositeSpring, d as PartialShape, f as PurposeShape, g as Purpose, h as TargetShape, i as SpringSystemOptions, j as mapSpring, k as SpringSourceApi, l as CompositeSpringTarget, m as Shape, n as SpringOptions, o as Ramp, p as ReadonlyShape, r as SpringSystem, s as RampOptions, t as CompositeSpringOptions, u as ConfigShape, v as SpringTarget, w as targetOf, x as TargetedSource, y as KinematicSource } from "./system-C7PbI4L0.js";
3
+ export { type CompositeSpring, type CompositeSpringOptions, type CompositeSpringTarget, type ConfigShape, type KinematicSource, type KinematicSourceApi, type PartialShape, type Purpose, type PurposeShape, type Ramp, type RampOptions, type ReadonlyShape, type Shape, type SourceShape, type SourceValues, type Spring, SpringDefinition, type SpringDefinitionOptions, type SpringOptions, type SpringSource, type SpringSourceApi, SpringSourceSymbol, type SpringState, type SpringSystem, type SpringSystemOptions, type SpringTarget, type TargetShape, type TargetedSource, type TargetedSourceApi, accelerationOf, createSpringSystem, defineSpring, mapSpring, targetOf, velocityOf };
package/dist/index.js CHANGED
@@ -1,91 +1,124 @@
1
- import { a as registerRecipe, i as mapSpring, n as SpringSourceSymbol, r as isSpringSource, s as defineSpring, t as createSpringSystem } from "./system-Br_cPnaN.js";
2
- import { o as invariant } from "./solver-CVXOTDkZ.js";
1
+ import { a as SpringSourceSymbol, c as registerRecipe, i as defineSpring, o as isSpringSource, r as SpringDefinition, s as mapSpring, t as createSpringSystem } from "./system-WejqGkfv.js";
2
+ import { o as invariant } from "./solver-CAXuxdKj.js";
3
3
  //#region src/kinematic-source.ts
4
4
  function isKinematicSource(value) {
5
5
  if (!isSpringSource(value)) return false;
6
6
  const api = value[SpringSourceSymbol];
7
7
  return "velocity" in api && "acceleration" in api;
8
8
  }
9
- const NOT_KINEMATIC = "expected a source in motion (a Spring or CompositeSpring); a value derived with mapSpring has neither velocity nor acceleration";
10
- /**
11
- * Derives a source from another's velocity — the rate its value changes,
12
- * in value units per second. `velocityOf(motion)` is a source that reads
13
- * `motion`'s current velocity, so speed-driven effects fall out of the
14
- * same follow and `mapSpring` machinery as everything else: a spring can
15
- * follow it (`blur.target = velocityOf(motion)`) and a map can shape it
16
- * (`mapSpring(velocityOf(motion), (v) => 1 + Math.abs(v) * 0.001)` for
17
- * squash and stretch). A scalar source yields a scalar velocity a spring
18
- * can follow; a `CompositeSpring` or shape yields a velocity of the same
19
- * shape, mapped to a scalar the way the composite itself is.
20
- *
21
- * The result is a stateless view, not a spring: it holds no
22
- * subscriptions and needs no disposal, and reads report the source's
23
- * current velocity on the fly. It updates when the source does and is
24
- * released with it — followers detach then, keeping their current
25
- * target, as they would from a disposed spring.
26
- *
27
- * Only a source in motion qualifies — a `Spring` or a `CompositeSpring`.
28
- * A value derived with `mapSpring` has no velocity (its rate of change
29
- * would need the derivative of the map), so it is rejected at the type
30
- * level and at runtime; take `velocityOf` of the spring the map reads
31
- * instead.
32
- */
33
- function velocityOf(source) {
34
- invariant(isKinematicSource(source), NOT_KINEMATIC);
9
+ function deriveView(source, read) {
35
10
  const api = source[SpringSourceSymbol];
36
- const velocity = Object.freeze({ [SpringSourceSymbol]: Object.freeze({
11
+ const view = Object.freeze({ [SpringSourceSymbol]: Object.freeze({
37
12
  get value() {
38
- return api.velocity;
13
+ return read();
39
14
  },
40
15
  onUpdate: (callback) => api.onUpdate(callback),
41
16
  onDispose: (callback) => api.onDispose(callback)
42
17
  }) });
43
- registerRecipe(velocity, {
18
+ registerRecipe(view, {
44
19
  sources: [source],
45
- read: () => api.velocity,
20
+ read,
46
21
  fns: []
47
22
  });
48
- return velocity;
23
+ return view;
24
+ }
25
+ const NOT_KINEMATIC = "expected a source in motion (a Spring, CompositeSpring, or Ramp); a derived source, such as a mapSpring result, has neither velocity nor acceleration";
26
+ /**
27
+ * Derives a source that reads another source's velocity: the rate its
28
+ * value changes, in value units per second. Speed-driven effects then
29
+ * use the same tools as everything else. A spring can follow it
30
+ * (`blur.target = velocityOf(motion)`), and a map can shape it
31
+ * (`mapSpring(velocityOf(motion), (v) => 1 + Math.abs(v) * 0.001)` for
32
+ * squash and stretch). A scalar source yields a scalar velocity. A
33
+ * `CompositeSpring` yields a velocity of its own shape, which you map to
34
+ * a scalar as you would its value.
35
+ *
36
+ * The result is a stateless view that holds no subscriptions and needs
37
+ * no disposal. Each read reports the source's current velocity. It
38
+ * updates when the source does and is released with it, and followers
39
+ * then detach and keep their current target.
40
+ *
41
+ * Only a `Spring`, a `CompositeSpring`, a `Ramp`, or a Vue ref that wraps
42
+ * one qualifies. A `mapSpring` result has no velocity, and `velocityOf`
43
+ * rejects it at the type level and at runtime. Take `velocityOf` of the
44
+ * spring the map reads instead.
45
+ */
46
+ function velocityOf(source) {
47
+ invariant(isKinematicSource(source), NOT_KINEMATIC);
48
+ const api = source[SpringSourceSymbol];
49
+ return deriveView(source, () => api.velocity);
49
50
  }
50
51
  /**
51
- * Derives a source from another's acceleration — the rate its velocity
52
- * changes, in value units per second squared. `accelerationOf(motion)`
53
- * spikes when `motion` is pushed hardest and passes through zero at the
54
- * peak of a swing, so force-driven effects fall out of the same follow
55
- * and `mapSpring` machinery: a spring can follow it, and a map can turn
56
- * it into an impact flash or a jelly wobble
52
+ * Derives a source that reads another source's acceleration: the rate
53
+ * its velocity changes, in value units per second squared. On a spring it
54
+ * is largest just after a retarget and near the far end of each swing,
55
+ * and passes through zero where a swing moves fastest. A spring can
56
+ * follow it, and a map can turn it into an impact flash or a jelly wobble
57
57
  * (`mapSpring(accelerationOf(motion), (a) => Math.min(1, Math.abs(a) * 1e-4))`).
58
- * A scalar source yields a scalar acceleration; a `CompositeSpring` or
59
- * shape yields an acceleration of the same shape, mapped to a scalar the
60
- * way the composite itself is.
58
+ * A scalar source yields a scalar acceleration. A `CompositeSpring`
59
+ * yields an acceleration of its own shape, which you map to a scalar as
60
+ * you would its value. A `Ramp`'s acceleration is always 0.
61
61
  *
62
- * The result is a stateless view, not a spring: it holds no
63
- * subscriptions and needs no disposal, and reads report the source's
64
- * current acceleration on the fly. It updates when the source does and
65
- * is released with it, followers detaching then as from a disposed
66
- * spring.
62
+ * The result is a stateless view that holds no subscriptions and needs
63
+ * no disposal. Each read reports the source's current acceleration. It
64
+ * updates when the source does and is released with it, and followers
65
+ * then detach and keep their current target.
67
66
  *
68
- * Only a source in motion qualifies — a `Spring` or a `CompositeSpring`.
69
- * A value derived with `mapSpring` has no acceleration, so it is rejected
70
- * at the type level and at runtime; take `accelerationOf` of the spring
71
- * the map reads instead.
67
+ * Only a `Spring`, a `CompositeSpring`, a `Ramp`, or a Vue ref that wraps
68
+ * one qualifies. A `mapSpring` result has no acceleration, and
69
+ * `accelerationOf` rejects it at the type level and at runtime. Take
70
+ * `accelerationOf` of the spring the map reads instead.
72
71
  */
73
72
  function accelerationOf(source) {
74
73
  invariant(isKinematicSource(source), NOT_KINEMATIC);
75
74
  const api = source[SpringSourceSymbol];
76
- const acceleration = Object.freeze({ [SpringSourceSymbol]: Object.freeze({
77
- get value() {
78
- return api.acceleration;
79
- },
80
- onUpdate: (callback) => api.onUpdate(callback),
81
- onDispose: (callback) => api.onDispose(callback)
82
- }) });
83
- registerRecipe(acceleration, {
84
- sources: [source],
85
- read: () => api.acceleration,
86
- fns: []
87
- });
88
- return acceleration;
75
+ return deriveView(source, () => api.acceleration);
76
+ }
77
+ function isTargetedSource(value) {
78
+ return isKinematicSource(value) && "target" in value[SpringSourceSymbol];
79
+ }
80
+ const NO_TARGET = "expected a source with a target (a Spring, CompositeSpring, or Ramp); a derived source, such as a mapSpring result, has no target";
81
+ /**
82
+ * Derives a source that reads a spring's or ramp's target: where it is
83
+ * heading, not where it is. Pair it with the source in a `mapSpring`
84
+ * shape to measure against the destination, such as remaining travel,
85
+ * progress, or a lead that anticipates the move. A `Spring` or a bounded
86
+ * `Ramp` yields a scalar target that a spring can follow. An endless
87
+ * `Ramp` yields `Infinity` or `-Infinity`, and a spring that follows it
88
+ * throws. A `CompositeSpring` yields a target of its own shape, which you
89
+ * map to a scalar as you would its value.
90
+ *
91
+ * ```ts
92
+ * const system = createSpringSystem()
93
+ * const body = system.createSpring(0)
94
+ * // The eyes look ahead by the body's remaining travel, capped at 20.
95
+ * const lead = mapSpring({ value: body, target: targetOf(body) }, ({ value, target }) =>
96
+ * Math.max(-20, Math.min(20, target - value)),
97
+ * )
98
+ * const eyes = system.createSpring(lead)
99
+ *
100
+ * body.target = 100
101
+ * system.advance(100)
102
+ * eyes.target // 20: the body still has more than 20 to go
103
+ * ```
104
+ *
105
+ * The result is a stateless view that holds no subscriptions and needs
106
+ * no disposal. Each read reports the spring's current target. It updates
107
+ * when the spring does, but assigning `target` emits nothing by itself,
108
+ * so followers pick up a new target on the next frame, when the spring
109
+ * starts toward it. Under reduced motion, a `'motion'` spring jumps to
110
+ * its new target and notifies followers at once. The view is released
111
+ * with the spring, and followers then detach and keep their current
112
+ * target.
113
+ *
114
+ * Only a `Spring`, a `CompositeSpring`, a `Ramp`, or a Vue ref that wraps
115
+ * one has a target. `targetOf` rejects a `mapSpring` result at the type
116
+ * level and at runtime.
117
+ */
118
+ function targetOf(spring) {
119
+ invariant(isTargetedSource(spring), NO_TARGET);
120
+ const api = spring[SpringSourceSymbol];
121
+ return deriveView(spring, () => api.target);
89
122
  }
90
123
  //#endregion
91
- export { SpringSourceSymbol, accelerationOf, createSpringSystem, defineSpring, mapSpring, velocityOf };
124
+ export { SpringDefinition, SpringSourceSymbol, accelerationOf, createSpringSystem, defineSpring, mapSpring, targetOf, velocityOf };
package/dist/nuxt.d.ts CHANGED
@@ -11,47 +11,48 @@ interface CoilyModuleOptions {
11
11
  */
12
12
  debug?: boolean;
13
13
  /**
14
- * Frame-rate cap. 0 means uncapped: one tick per displayed frame.
15
- * Capped ticks land on whole display frames and receive the true
16
- * elapsed time.
14
+ * Frame-rate cap. 0 means uncapped: one tick per displayed frame. A
15
+ * capped tick lands on a whole display frame and receives the true
16
+ * elapsed time in ms.
17
17
  * @default 0
18
18
  */
19
19
  fps?: number;
20
20
  /**
21
- * Frame gap in milliseconds above which the gap is treated as lag and
22
- * replaced with `adjustedLag`, so springs don't teleport when frames
23
- * resume. 0 disables lag clamping.
21
+ * The frame gap in ms above which the system treats the gap as lag and
22
+ * replaces it with `adjustedLag`, so springs do not jump when frames
23
+ * resume. 0 turns off lag clamping.
24
24
  * @default 500
25
25
  */
26
26
  lagThreshold?: number;
27
27
  /**
28
- * The elapsed milliseconds a lagging frame is replaced with. Clamped
29
- * to at most `lagThreshold`.
28
+ * The elapsed time in ms that replaces a lagging frame. A frame uses
29
+ * at most `lagThreshold` ms of it.
30
30
  * @default 33
31
31
  */
32
32
  adjustedLag?: number;
33
33
  /**
34
- * How far a spring may trail a source it follows within one frame, in
35
- * the value's own units. Frames whose motion would exceed it split
36
- * into internal sub-steps; smaller is tighter and costs more solver
37
- * work.
34
+ * How far a spring can trail a source it follows within one frame, in
35
+ * the value's own units. Frames whose motion would exceed it run as
36
+ * sub-steps, so a smaller value follows more closely at more cost.
38
37
  * @default 0.1
39
38
  */
40
39
  couplingTolerance?: number;
41
40
  /**
42
- * When springs snap to their targets instead of animating: `'user'`
41
+ * When springs snap to their targets instead of animating. `'user'`
43
42
  * follows the OS prefers-reduced-motion setting, including live
44
- * changes; `'always'` and `'never'` force one behavior.
43
+ * changes. `'always'` and `'never'` force one behavior.
45
44
  * @default 'user'
46
45
  */
47
46
  reducedMotion?: 'user' | 'always' | 'never';
48
47
  }
49
48
  /**
50
- * The Nuxt module: creates an app-wide spring system (started on the
51
- * client), auto-imports the composables and helpers `useSpring`,
52
- * `useSpringSystem`, `useSpringPool`, `defineSpring`, `mapSpring`,
53
- * `velocityOf`, and `accelerationOf`, and registers the `SpringValue`
54
- * component. Types are left out — they would be noise in most apps.
49
+ * The Nuxt module. It creates one spring system for the app and starts
50
+ * it on the client only, so server rendering shows springs at their
51
+ * initial values. The module auto-imports
52
+ * `useSpring`, `useRamp`, `useSpringSystem`, `useSpringPool`,
53
+ * `defineSpring`, `mapSpring`, `velocityOf`, `accelerationOf`, and
54
+ * `targetOf`, and registers the `SpringValue` component. It does not
55
+ * auto-import types.
55
56
  */
56
57
  declare const coilyModule: NuxtModule;
57
58
  //#endregion
package/dist/nuxt.js CHANGED
@@ -23,11 +23,13 @@ export default defineNuxtPlugin((nuxtApp) => {
23
23
  })`;
24
24
  }
25
25
  /**
26
- * The Nuxt module: creates an app-wide spring system (started on the
27
- * client), auto-imports the composables and helpers `useSpring`,
28
- * `useSpringSystem`, `useSpringPool`, `defineSpring`, `mapSpring`,
29
- * `velocityOf`, and `accelerationOf`, and registers the `SpringValue`
30
- * component. Types are left out — they would be noise in most apps.
26
+ * The Nuxt module. It creates one spring system for the app and starts
27
+ * it on the client only, so server rendering shows springs at their
28
+ * initial values. The module auto-imports
29
+ * `useSpring`, `useRamp`, `useSpringSystem`, `useSpringPool`,
30
+ * `defineSpring`, `mapSpring`, `velocityOf`, `accelerationOf`, and
31
+ * `targetOf`, and registers the `SpringValue` component. It does not
32
+ * auto-import types.
31
33
  */
32
34
  const coilyModule = defineNuxtModule({
33
35
  meta: {
@@ -47,6 +49,10 @@ const coilyModule = defineNuxtModule({
47
49
  from: "coily/vue",
48
50
  name: "useSpring"
49
51
  },
52
+ {
53
+ from: "coily/vue",
54
+ name: "useRamp"
55
+ },
50
56
  {
51
57
  from: "coily/vue",
52
58
  name: "useSpringSystem"
@@ -70,6 +76,10 @@ const coilyModule = defineNuxtModule({
70
76
  {
71
77
  from: "coily",
72
78
  name: "accelerationOf"
79
+ },
80
+ {
81
+ from: "coily",
82
+ name: "targetOf"
73
83
  }
74
84
  ]);
75
85
  addComponent({
@@ -5,9 +5,8 @@ function invariant(condition, message) {
5
5
  throw new Error(resolved ?? "Invariant Failed");
6
6
  }
7
7
  /**
8
- * True for plain objects only. Class instances are excluded so shape
9
- * walks reject values like a `Spring` instead of descending into their
10
- * fields as if they were nested shapes.
8
+ * True for plain objects only. Excluding class instances makes shape walks
9
+ * reject a `Spring` instead of descending into it as a nested shape.
11
10
  */
12
11
  function isRecord(value) {
13
12
  if (typeof value !== "object" || value === null) return false;
@@ -27,8 +26,8 @@ const RESOLVED = Promise.resolve();
27
26
  //#endregion
28
27
  //#region src/state.ts
29
28
  /**
30
- * The mutable position/velocity pair a motion and its solvers share, in
31
- * displacement space: position 0 means at the target.
29
+ * The mutable position and velocity that a motion and its solvers share,
30
+ * in displacement space. Position 0 means at the target.
32
31
  */
33
32
  var State = class {
34
33
  #config;
@@ -55,9 +54,8 @@ var State = class {
55
54
  this.#velocity = value;
56
55
  }
57
56
  /**
58
- * The rest test: displacement, plus the displacement the current
59
- * velocity could still convert into (|x| + |v| / wn), is inside the
60
- * config's resting threshold.
57
+ * True when |x| + |v| / wn, the displacement plus what the velocity could
58
+ * still add, is within the config's resting threshold.
61
59
  */
62
60
  get isResting() {
63
61
  return Math.abs(this.#position) + Math.abs(this.#velocity) / this.#config.naturalFrequency <= this.#config.restingMagnitude;
@@ -174,6 +172,8 @@ var CriticallyDampedSolver = class {
174
172
  this.#state.velocity = scaleVelocity * decay + decayVelocity * scale;
175
173
  }
176
174
  };
175
+ /** The `wd * t` past which `OverdampedSolver` evaluates only its slow mode. */
176
+ const TAIL = 20;
177
177
  /** dampingRatio > 1: slow non-oscillating decay, x(t) = exp(-sigma*t) * (c1*sinh(wd*t) + c2*cosh(wd*t)) / wd. */
178
178
  var OverdampedSolver = class {
179
179
  #state;
@@ -220,15 +220,23 @@ var OverdampedSolver = class {
220
220
  this.configure();
221
221
  }
222
222
  #evaluate() {
223
+ const wd = this.#dampedFrequency;
224
+ const wdt = wd * this.#t;
225
+ if (wdt > TAIL) {
226
+ const slowRate = wd - this.#decayRate;
227
+ const position = (this.#c1 + this.#c2) / (2 * wd) * Math.exp(slowRate * this.#t);
228
+ this.#state.position = position;
229
+ this.#state.velocity = slowRate * position;
230
+ return;
231
+ }
223
232
  const decay = Math.exp(-this.#decayRate * this.#t);
224
233
  const decayVelocity = -this.#decayRate * decay;
225
- const clamped = Math.min(this.#dampedFrequency * this.#t, 300);
226
- const sinh = Math.sinh(clamped);
227
- const cosh = Math.cosh(clamped);
234
+ const sinh = Math.sinh(wdt);
235
+ const cosh = Math.cosh(wdt);
228
236
  const scale = this.#c1 * sinh + this.#c2 * cosh;
229
- const scaleVelocity = this.#c1 * this.#dampedFrequency * cosh + this.#c2 * this.#dampedFrequency * sinh;
230
- this.#state.position = scale * decay / this.#dampedFrequency;
231
- this.#state.velocity = (scale * decayVelocity + scaleVelocity * decay) / this.#dampedFrequency;
237
+ const scaleVelocity = this.#c1 * wd * cosh + this.#c2 * wd * sinh;
238
+ this.#state.position = scale * decay / wd;
239
+ this.#state.velocity = (scale * decayVelocity + scaleVelocity * decay) / wd;
232
240
  }
233
241
  };
234
242
  //#endregion