coily 0.13.1 → 0.15.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/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  Simulate values using spring physics.
4
4
 
5
- Coily animates numbers — and whole numeric shapes — with damped spring motion. Each frame is computed from the closed-form solution of the spring equation — underdamped, critically damped, or overdamped — rather than numerical integration, so motion doesn't accumulate error and springs can be retargeted mid-flight without losing momentum.
5
+ Coily animates numbers and whole numeric shapes with damped spring motion. It computes each frame from the closed-form solution of the spring equation (underdamped, critically damped, or overdamped) rather than numerical integration, so motion doesn't accumulate error and you can retarget a spring mid-flight without losing momentum.
6
6
 
7
- The core has zero dependencies and runs anywhere (it falls back from `requestAnimationFrame` to `setTimeout` outside the browser). Vue and Nuxt integrations ship as separate entry points; `vue` and `@nuxt/kit` are optional peer dependencies.
7
+ The core has zero dependencies and runs anywhere (it falls back from `requestAnimationFrame` to `setTimeout` outside the browser). Vue and Nuxt integrations ship as separate entry points, with `vue` and `@nuxt/kit` as optional peer dependencies.
8
8
 
9
9
  ## Install
10
10
 
@@ -33,7 +33,7 @@ spring.target = 300
33
33
 
34
34
  ## Configuring springs
35
35
 
36
- `defineSpring` accepts several input shapes; whatever you leave out is derived:
36
+ `defineSpring` accepts several input shapes and derives whatever you leave out:
37
37
 
38
38
  ```ts
39
39
  defineSpring({ tension: 500, damping: 40 }) // direct physical parameters
@@ -41,81 +41,115 @@ defineSpring({ tension: 500, dampingRatio: 0.7 }) // damping derived
41
41
  defineSpring({ tension: 500, bounce: 0.3 }) // bounce = 1 - dampingRatio
42
42
  defineSpring({ duration: 750, dampingRatio: 1 }) // tuned to settle in ~750ms
43
43
  defineSpring({ duration: 750, bounce: 0.5 })
44
+ defineSpring({ tension: 500, bounce: 0.9, arrival: 'stop' }) // dead-stop at the target, no overshoot
44
45
  ```
45
46
 
46
- - **`tension`** — stiffness (> 0)
47
- - **`damping`** — friction (≥ 0)
48
- - **`dampingRatio`** — 0 = undamped, < 1 = bouncy, 1 = critically damped, > 1 = overdamped
49
- - **`bounce`** — friendlier alias for damping ratio: −1 (overdamped) to 1 (max bounce)
50
- - **`duration`** — target settle time in ms. Assumes an initial displacement of 1; pass `displacement` matching your animation range for accurate timing
51
- - **`mass`** — defaults to 1
52
- - **`precision`** — decimal places of the resting threshold (default 2). A spring is _resting_ once its remaining motion cannot reach half a unit in the last place — 0.005 at the default. Values are never rounded; set precision to match your domain's resolution (see [PRECISION.md](https://github.com/tkofh/coily/blob/main/PRECISION.md))
47
+ - **`tension`**: stiffness (> 0)
48
+ - **`damping`**: friction (>= 0)
49
+ - **`dampingRatio`**: 0 = undamped, < 1 = bouncy, 1 = critically damped, > 1 = overdamped
50
+ - **`bounce`**: a friendlier alias for damping ratio, from -1 (overdamped) to 1 (max bounce)
51
+ - **`duration`**: settle time in ms — the spring rests at this time, within a frame (a bouncy config can rest up to one oscillation earlier). Assumes an initial displacement of 1. Pass `displacement` matching your animation range for accurate timing
52
+ - **`mass`**: defaults to 1
53
+ - **`precision`**: decimal places of the resting threshold (default 2). A spring is _resting_ once its remaining motion cannot reach half a unit in the last place (0.005 at the default). Coily never rounds values. Set `precision` to match your domain's resolution (see [PRECISION.md](https://github.com/tkofh/coily/blob/main/PRECISION.md))
54
+ - **`arrival`**: what the motion does when the value reaches the target. `'passthrough'` (default) swings through and settles; `'stop'` ends the motion exactly on the target the first time it gets there, however bouncy the config; a number between -1 and 1 scales the velocity at every crossing — negative values rebound. Crossings are solved exactly, never sampled frame by frame (see [PRECISION.md](https://github.com/tkofh/coily/blob/main/PRECISION.md))
53
55
 
54
56
  Without a config, springs are critically damped with a ~500ms settle time.
55
57
 
56
58
  ## Spring API
57
59
 
58
- - `spring.target` — set to animate toward a new value
59
- - `spring.value` — current value; writable to displace the spring
60
- - `spring.velocity` — current velocity; writable to fling
61
- - `spring.jumpTo(v)` — snap to a value with no animation
62
- - `spring.config` — assign a new `SpringConfig`, or `null` to revert to the default (or the leader's, if following)
63
- - `spring.isResting`, `spring.timeRemaining` — settle state and estimated ms until rest
64
- - `spring.settled` — a promise that resolves when the spring next comes to rest (immediately if already resting). Retargeting mid-flight extends the wait; disposing resolves it. `await spring.settled` to sequence animations
65
- - `spring.onUpdate(cb)` / `onStart(cb)` / `onStop(cb)` / `onDispose(cb)` — subscribe; each returns an unsubscribe function. `start` fires when the spring leaves rest, `stop` when it settles — the two always alternate, and retargeting mid-flight fires neither
66
- - `spring.dispose()` — release the spring (calling it twice is a no-op)
60
+ - `spring.target`: set to animate toward a new value
61
+ - `spring.value`: current value, writable to displace the spring
62
+ - `spring.velocity`: current velocity, writable to fling
63
+ - `spring.jumpTo(v)`: snap to a value with no animation
64
+ - `spring.config`: assign a new `SpringDefinition`, or `null` to revert to the default
65
+ - `spring.isResting`, `spring.timeRemaining`: settle state and ms until rest — solved from the motion, not estimated (see [PRECISION.md](https://github.com/tkofh/coily/blob/main/PRECISION.md))
66
+ - `spring.settled`: a promise that resolves when the spring next comes to rest (immediately if already resting). Retargeting mid-flight extends the wait; disposing resolves it. `await spring.settled` to sequence animations
67
+ - `spring.onUpdate(cb)` / `onStart(cb)` / `onStop(cb)` / `onDispose(cb)`: subscribe. Each returns an unsubscribe function. `start` fires when the spring leaves rest, `stop` when it settles. The two always alternate, and retargeting mid-flight fires neither
68
+ - `spring.dispose()`: release the spring (calling it twice is a no-op)
67
69
 
68
70
  ### Chaining
69
71
 
70
- A spring can follow another spring's live value instead of a fixed number:
72
+ A spring can follow another spring's live value instead of a fixed number: assign a spring to `target`, or pass one straight to `createSpring` to follow from birth. `mapSpring` derives new followable values from existing ones (offsets, mirrors, clamps, any pure function of the value):
71
73
 
72
74
  ```ts
73
75
  const leader = system.createSpring(0)
74
- const follower = system.createSpring({ target: leader })
75
- const trailing = system.createSpring({ target: { spring: leader, offset: 20 } })
76
+
77
+ const follower = system.createSpring(leader)
78
+ const trailing = system.createSpring(mapSpring(leader, (v) => v + 20))
79
+ const mirrored = system.createSpring(mapSpring(leader, (v) => -v))
76
80
  ```
77
81
 
78
- Followers inherit the leader's config unless given their own. Assigning a number to `target` unfollows.
82
+ A follower's config is its own: following changes what a spring chases, never how it moves. To match a leader's feel, pass its config at creation — `system.createSpring(leader, leader.config)` — or share a `defineSpring` constant. Assigning a number to `target` unfollows.
83
+
84
+ `mapSpring` also combines several springs: pass a shape (a plain object or array with springs at the leaves, nested arbitrarily) and a function of their values:
79
85
 
80
- ### Objects
86
+ ```ts
87
+ const x = system.createSpring(0)
88
+ const y = system.createSpring(0)
89
+
90
+ const distance = system.createSpring(mapSpring({ x, y }, ({ x, y }) => Math.hypot(x, y)))
91
+ ```
81
92
 
82
- `system.createSpringObject(value, config?)` springs over any numeric shape — a plain object or array whose leaves are all numbers, nested arbitrarily. Each leaf becomes an independent channel behind one composite API:
93
+ Composite springs (below) are sources of their whole value shape, so one map can derive a scalar from every channel at once:
83
94
 
84
95
  ```ts
85
- const spring = system.createSpringObject({ position: { x: 0, y: 0 }, opacity: 1 })
96
+ const point = system.createSpring({ x: 3, y: 4 })
97
+
98
+ const magnitude = system.createSpring(mapSpring(point, ({ x, y }) => Math.hypot(x, y)))
99
+ ```
100
+
101
+ A mapped value is a `SpringSource`, the contract `target` accepts and every `Spring` implements. A `CompositeSpring` is a source _of its shape_: `mapSpring` reads it, alone or at the leaves of a shape, but only scalar sources can be followed directly.
102
+
103
+ Chains advance in dependency order: each frame, a spring moves after the springs it follows — whatever order anything was created or wired in — so a follower always chases its leader's current value rather than last frame's. Update events fire once per spring per frame, after every spring has moved, in that same leader-first order. A callback that reads another spring sees its final value for the frame, and a spring retargeted from inside a callback starts moving on the next frame.
86
104
 
87
- spring.target = { position: { x: 100 } } // partial — other channels are left alone
105
+ Following is accurate within the frame, too: a follower integrates against the path its leader actually traveled, not a value sampled once at the frame boundary, and when a single frame carries more motion than the system's `couplingTolerance` allows (0.1 value units by default), the system splits that frame into internal sub-steps. Dropped frames and teleporting targets refine themselves; update events still fire once per frame. The result is that a chain keeps the same shape at 30fps as at 120fps — see https://github.com/tkofh/coily/blob/main/PRECISION.md for the model.
106
+
107
+ Follow graphs may contain cycles: retargets never re-emit, so nothing loops within a frame. Mutual followers converge to a shared value — the same value at any frame rate. A cycle has no order that puts every member after its leaders, so members advance in creation order and the edge that closes the loop chases one frame behind. A cycle whose maps expand (`(v) => v + 10` in both directions) chases forever and never rests, keeping the system awake until you break the cycle.
108
+
109
+ ### Composites
110
+
111
+ `createSpring` also takes any numeric shape: a plain object or array whose leaves are all numbers, nested arbitrarily. Each leaf becomes an independent channel behind one composite API, a `CompositeSpring`:
112
+
113
+ ```ts
114
+ const spring = system.createSpring({ position: { x: 0, y: 0 }, opacity: 1 })
115
+
116
+ spring.target = { position: { x: 100 } } // a partial target leaves other channels alone
88
117
  spring.value // { position: { x, y }, opacity } — a stable, read-only mirror
89
118
  ```
90
119
 
91
- The shape is fixed at creation, and unknown channels throw with their path (`position.z`). `value`, `velocity`, and `jumpTo` take the same partial shapes. Composite events are coalesced: `update` fires at most once per frame with every channel in its final per-frame state, and `stop` always lands after that frame's final `update`. `settled` and reduced motion compose channel-wise.
120
+ The shape is fixed at creation, and unknown channels throw with their path (`position.z`). `value`, `velocity`, and `jumpTo` take the same partial shapes. Composites coalesce their events: `update` fires at most once per frame with every channel in its final per-frame state, and `stop` always lands after that frame's final `update`. `settled` and reduced motion compose channel-wise.
92
121
 
93
- Shapes are validated at compile time too (the `Shape` type — interfaces like your own `Vector2` work without index signatures): non-numeric, optional, or `undefined`-typed channels are rejected where they're declared.
122
+ The `Shape` type validates shapes at compile time too (interfaces like your own `Vector2` work without index signatures) and rejects non-numeric, optional, or `undefined`-typed channels where they're declared.
94
123
 
95
- Configs apply per channel. Pass a single `SpringConfig` for every channel, or a shape mirroring the value with configs (or `null` to revert) at any level — a config at a subtree covers every channel below it:
124
+ Configs apply per channel. Pass a single `SpringDefinition` for every channel, or a shape mirroring the value with configs (or `null` to revert) at any level. A config at a subtree covers every channel below it:
96
125
 
97
126
  ```ts
98
127
  spring.config = { position: stiff, opacity: defineSpring({ duration: 300, dampingRatio: 1 }) }
99
128
  ```
100
129
 
101
- Configs are `SpringConfig` instances — build them with `defineSpring`. Unlike the scalar `useSpring` above, config positions here don't accept bare option objects: a plain object is always a per-channel shape, so any non-config leaf it reaches throws with its path.
130
+ Configs are `SpringDefinition` instances, built with `defineSpring`. Config positions don't accept bare option objects: a plain object is always a per-channel shape, so any non-config leaf it reaches throws with its path.
102
131
 
103
- Spring objects chain channel-wise. Assign another spring object of the exact same shape (optionally with a partial offset shape), and a partial numeric target detaches only the channels it names:
132
+ Composite springs chain channel-wise. Assign another composite spring of the exact same shape to follow it whole, or name channels individually: each channel of a partial target takes a number or a scalar `SpringSource`. While following, a partial target detaches only the channels it names:
104
133
 
105
134
  ```ts
106
135
  follower.target = leader
107
- trailing.target = { spring: leader, offset: { position: { x: 20 } } }
136
+
137
+ // or mix numbers and live sources per channel
138
+ follower.target = {
139
+ opacity: 0.5,
140
+ position: { x: mapSpring(leader, ({ position }) => -position.x) },
141
+ }
108
142
  ```
109
143
 
110
144
  ## Reduced motion
111
145
 
112
- Coily respects `prefers-reduced-motion` by default. When it's active, springs snap to their targets instead of animating: retargets and value writes apply instantly, velocity impulses are ignored, and springs created displaced start at their target. Events stay coherent — one `update` per change, no `start`/`stop`, and `settled` resolves immediately — so code written against the animated path keeps working.
146
+ Coily respects `prefers-reduced-motion` by default. When it's active, springs snap to their targets instead of animating: retargets and value writes apply instantly, and springs ignore velocity impulses. Events stay coherent (one `update` per change, no `start`/`stop`, and `settled` resolves immediately), so code written against the animated path keeps working.
113
147
 
114
- Control it with the `reducedMotion` system option: `'user'` (default — follow the OS setting, including live changes, which finish in-flight animations instantly), `'always'`, or `'never'`. Read `system.reducedMotion` to gate purely decorative effects (particles, flourishes) in your own code.
148
+ Control it with the `reducedMotion` system option: `'always'`, `'never'`, or the default `'user'`, which follows the OS setting — a live change finishes in-flight animations instantly. Read `system.reducedMotion` to gate decorative effects (particles, flourishes) in your own code.
115
149
 
116
150
  ## Vue
117
151
 
118
- Call `useSpringSystem()` once near the root of your app. It returns the spring system provided by this component or an ancestor; if none exists, it creates one, provides it to descendants, and starts/stops it with the component lifecycle. It's idempotent, and options apply only when a system is actually created:
152
+ Call `useSpringSystem()` once near the root of your app. It returns the spring system provided by this component or an ancestor. If none exists, it creates one, provides it to descendants, and starts/stops it with the component lifecycle. It's idempotent, and options apply only when a system is created:
119
153
 
120
154
  ```ts
121
155
  import { useSpringSystem } from 'coily/vue'
@@ -140,13 +174,15 @@ const x = useSpring(target, { duration: 500, bounce: 0.3 })
140
174
  </template>
141
175
  ```
142
176
 
143
- `useSpring` returns a `SpringRef`: a writable ref of the animated value with `velocity`, `isResting`, and `timeRemaining` refs plus `jumpTo()` attached. The target can be a ref, a getter, or another `SpringRef` (which chains the springs). Options are also reactive — swap configs and the spring reconfigures in place.
177
+ `useSpring` returns a `SpringRef`: a writable ref of the animated value with `velocity`, `isResting`, and `timeRemaining` refs plus `jumpTo()` attached. The target can be a ref, a getter, or another `SpringRef` (which chains the springs). Options are also reactive: swap configs and the spring reconfigures in place.
178
+
179
+ A `SpringRef` is also a `SpringSource`, and `useSpring` follows any source: chain through `mapSpring` (`useSpring(mapSpring({ x, y }, ({ x, y }) => Math.hypot(x, y)))`), or pass a getter of a source to switch leaders reactively (`useSpring(() => (split.value ? left : right))`). Following bypasses Vue reactivity — followers subscribe to the backing spring directly — so an effect or getter never gains a dependency on a leader's animation.
144
180
 
145
- Numeric shapes work the same way: pass a record or array (plain, ref, or getter) and get a `SpringObjectRef`. Reads are the deep-readonly composite value, writes take partial shapes, options accept reactive per-channel config shapes, and passing another `SpringObjectRef` follows it channel-wise. Deeply reactive targets retarget on nested mutation. For several _independent_ scalar springs, map over the targets — composables are loop-safe: `targets.map((t) => useSpring(t))`.
181
+ Numeric shapes work the same way: pass a record or array (plain, ref, or getter) and get a `CompositeSpringRef`. Reads are the deep-readonly composite value, writes take partial shapes, options accept reactive per-channel config shapes, and passing another `CompositeSpringRef` follows it channel-wise. Deeply reactive targets retarget on nested mutation. For several _independent_ scalar springs, map over the targets (composables are loop-safe): `targets.map((t) => useSpring(t))`.
146
182
 
147
183
  There's also a renderless `<SpringValue :target="n">` component exposing `{ value, velocity, isResting, timeRemaining, jumpTo }` through its default slot.
148
184
 
149
- For imperative work — a dynamic set of springs created and disposed at arbitrary times (particles, per-item effects) — `useSpringPool()` returns `createSpring`/`createSpringObject` bound to the provided system. Every spring created through the pool is disposed automatically when the component's scope is torn down, so leaked motions are structurally impossible; disposing a spring manually before that is fine.
185
+ `useSpringPool()` covers imperative work: a dynamic set of springs created and disposed at arbitrary times (particles, per-item effects). It returns `createSpring` bound to the provided system. The pool disposes every spring created through it when the component's scope is torn down, so leaked motions are structurally impossible. Disposing a spring manually before that is fine.
150
186
 
151
187
  ## Nuxt
152
188
 
@@ -164,7 +200,7 @@ The module provides a spring system for the whole app (started on the client), a
164
200
 
165
201
  ## Timing
166
202
 
167
- The built-in ticker advances springs once per displayed frame, so motion is as smooth as the screen it runs on — 60Hz, 120Hz, or adaptive sync. The loop sleeps while every spring is at rest (an idle system schedules no frames) and wakes on the next write. Set `fps` (option/property) to cap the rate; caps are paced to whole display frames, and each tick still receives the true elapsed time. Large frame gaps — e.g. returning from a backgrounded tab — are clamped via `lagThreshold` (default 500ms) and `adjustedLag` (default 33ms), so springs don't teleport. For manual stepping, skip `system.start()` and call `system.advance(dtMs)` yourself.
203
+ The built-in ticker advances springs once per displayed frame, so motion is as smooth as the screen it runs on: 60Hz, 120Hz, or adaptive sync. The loop sleeps while every spring is at rest (an idle system schedules no frames) and wakes on the next write. Set `fps` (option/property) to cap the rate: capped ticks land on whole display frames and still receive the true elapsed time. The ticker clamps large frame gaps (e.g. returning from a backgrounded tab) via `lagThreshold` (default 500ms) and `adjustedLag` (default 33ms), so springs don't teleport. For manual stepping, skip `system.start()` and call `system.advance(dtMs)` yourself.
168
204
 
169
205
  ## License
170
206
 
@@ -0,0 +1,312 @@
1
+ //#region src/config.d.ts
2
+ /**
3
+ * A spring's instantaneous motion, measured relative to its target.
4
+ * `SpringDefinition.computeTimeRemaining` takes one; build it from a live
5
+ * spring as `{ position: spring.value - spring.target, velocity: spring.velocity }`.
6
+ */
7
+ interface SpringState {
8
+ /** Displacement from the target in value units. 0 means at the target. */
9
+ readonly position: number;
10
+ /** Rate of change of the displacement, in value units per second. */
11
+ readonly velocity: number;
12
+ }
13
+ interface BaseOptions {
14
+ /**
15
+ * Decimal places of the resting threshold. A spring rests once its
16
+ * remaining motion cannot reach half a unit in the last place —
17
+ * `0.5 * 10^-precision`, or 0.005 at the default.
18
+ *
19
+ * Values are never rounded; precision only decides rest. See
20
+ * https://github.com/tkofh/coily/blob/main/PRECISION.md.
21
+ *
22
+ * @default 2
23
+ */
24
+ readonly precision?: number | undefined;
25
+ /**
26
+ * What the motion does when the value reaches the target, as a
27
+ * multiplier applied to the velocity at every crossing.
28
+ *
29
+ * - `'passthrough'` — multiplier 1. The motion is untouched: the value
30
+ * swings through the target and settles as its damping dictates.
31
+ * - `'stop'` — multiplier 0. Motion ends at the target the first time
32
+ * the value gets there, with no overshoot, however bouncy the config.
33
+ * - A number between -1 and 1 sets the multiplier directly. Negative
34
+ * values rebound: `-0.75` reverses the motion with three quarters of
35
+ * its speed, at every return. Values between 0 and 1 pass through
36
+ * slowed.
37
+ *
38
+ * Crossings are solved from the motion's closed form, never sampled
39
+ * frame by frame, so a `'stop'` lands the value exactly on the target
40
+ * whatever the frame timing. See
41
+ * https://github.com/tkofh/coily/blob/main/PRECISION.md.
42
+ *
43
+ * @default 'passthrough'
44
+ */
45
+ readonly arrival?: 'passthrough' | 'stop' | number | undefined;
46
+ }
47
+ interface WithMass {
48
+ /**
49
+ * Mass of the moving value. Heavier springs accelerate more slowly and
50
+ * carry more momentum through the target. Must be greater than 0.
51
+ * @default 1
52
+ */
53
+ readonly mass?: number | undefined;
54
+ }
55
+ interface WithoutMass {
56
+ /**
57
+ * Mass is derived from the other parameters in this input shape
58
+ * and cannot be provided.
59
+ */
60
+ readonly mass?: undefined;
61
+ }
62
+ interface WithTension {
63
+ /**
64
+ * Spring stiffness. Higher tension pulls toward the target harder,
65
+ * making the whole motion faster. Must be greater than 0.
66
+ */
67
+ readonly tension: number;
68
+ }
69
+ interface WithDamping {
70
+ /**
71
+ * Friction strength. Higher damping bleeds off velocity faster and
72
+ * reduces bounce; at 0 the spring oscillates forever. Must be greater
73
+ * than or equal to 0.
74
+ */
75
+ readonly damping: number;
76
+ }
77
+ interface WithDampingRatio {
78
+ /**
79
+ * Damping as a fraction of critical damping — the character of the
80
+ * motion, independent of its speed. Below 1 the spring overshoots and
81
+ * bounces. At 1 it settles as fast as possible without overshooting.
82
+ * Above 1 it settles slower, still without overshooting. At 0 it never
83
+ * settles. Must be greater than or equal to 0.
84
+ */
85
+ readonly dampingRatio: number;
86
+ }
87
+ interface WithBounce {
88
+ /**
89
+ * Bounciness, converted to a damping ratio as `1 - bounce`. 0 settles
90
+ * as fast as possible without overshooting. Positive values overshoot
91
+ * and bounce; 1, maximum bounce, is clamped just short of undamped so
92
+ * the spring still settles. Negative values settle without bounce, more
93
+ * slowly. Must be between -1 and 1.
94
+ */
95
+ readonly bounce: number;
96
+ }
97
+ interface WithDuration {
98
+ /**
99
+ * Settle time in milliseconds. The spring is tuned so it rests at this
100
+ * time, assuming it starts `displacement` away from the target: rest
101
+ * lands within a frame of the requested duration, and a bouncy config
102
+ * can rest up to one oscillation earlier. Must be greater than 0.
103
+ */
104
+ readonly duration: number;
105
+ }
106
+ interface WithDisplacement {
107
+ /**
108
+ * The initial displacement the duration tuning assumes. Set it to your
109
+ * animation's range — 300 when animating 0 to 300 — so the settle
110
+ * timing holds; timing skews as the real displacement diverges from
111
+ * this value. Must not be 0.
112
+ * @default 1
113
+ */
114
+ readonly displacement?: number | undefined;
115
+ }
116
+ interface DirectOptions extends BaseOptions, WithMass, WithTension, WithDamping {}
117
+ interface TensionRatioOptions extends BaseOptions, WithMass, WithTension, WithDampingRatio {}
118
+ interface TensionBounceOptions extends BaseOptions, WithMass, WithTension, WithBounce {}
119
+ interface DampingRatioOptions extends BaseOptions, WithMass, WithDamping, WithDampingRatio {}
120
+ interface DampingBounceOptions extends BaseOptions, WithMass, WithDamping, WithBounce {}
121
+ interface TensionDampingRatioOptions extends BaseOptions, WithoutMass, WithTension, WithDamping, WithDampingRatio {}
122
+ interface TensionDampingBounceOptions extends BaseOptions, WithoutMass, WithTension, WithDamping, WithBounce {}
123
+ interface DurationOptions extends BaseOptions, WithMass, WithDampingRatio, WithDuration, WithDisplacement {}
124
+ interface BounceDurationOptions extends BaseOptions, WithMass, WithBounce, WithDuration, WithDisplacement {}
125
+ interface TensionDurationOptions extends BaseOptions, WithoutMass, WithTension, WithDampingRatio, WithDuration, WithDisplacement {}
126
+ interface TensionBounceDurationOptions extends BaseOptions, WithoutMass, WithTension, WithBounce, WithDuration, WithDisplacement {}
127
+ interface DampingDurationOptions extends BaseOptions, WithoutMass, WithDamping, WithDampingRatio, WithDuration, WithDisplacement {}
128
+ interface DampingBounceDurationOptions extends BaseOptions, WithoutMass, WithDamping, WithBounce, WithDuration, WithDisplacement {}
129
+ /** Every option key accepted by at least one `defineSpring` input shape. */
130
+ type SpringOptionKeys = 'mass' | 'tension' | 'damping' | 'dampingRatio' | 'bounce' | 'duration' | 'displacement' | 'precision' | 'arrival';
131
+ /**
132
+ * Marks every option key foreign to `T` as "absent or undefined", so mixing
133
+ * properties from different input shapes fails to type-check instead of
134
+ * silently resolving to an unintended shape at runtime.
135
+ */
136
+ type Exact<T> = T & { [K in Exclude<SpringOptionKeys, keyof T>]?: undefined; };
137
+ /**
138
+ * Every input `defineSpring` accepts: one of the documented parameter
139
+ * combinations, exactly as written — keys from different combinations
140
+ * don't mix.
141
+ */
142
+ type SpringDefinitionOptions = Exact<DirectOptions> | Exact<TensionRatioOptions> | Exact<TensionBounceOptions> | Exact<DampingRatioOptions> | Exact<DampingBounceOptions> | Exact<TensionDampingRatioOptions> | Exact<TensionDampingBounceOptions> | Exact<DurationOptions> | Exact<BounceDurationOptions> | Exact<TensionDurationOptions> | Exact<TensionBounceDurationOptions> | Exact<DampingDurationOptions> | Exact<DampingBounceDurationOptions>;
143
+ /**
144
+ * Immutable, resolved spring parameters. Whatever `defineSpring` input
145
+ * shape produced it, a config carries concrete `mass`, `tension`,
146
+ * `damping`, and `precision`, plus the quantities derived from them.
147
+ * Instances are frozen and safe to share between springs; to change a
148
+ * spring's behavior, assign a new config via `spring.config`.
149
+ */
150
+ declare class SpringDefinition {
151
+ /** The config for springs created without one: critically damped, settling in about 500ms. */
152
+ static readonly default: SpringDefinition;
153
+ /** Mass of the moving value, as provided or derived. */
154
+ readonly mass: number;
155
+ /** Spring stiffness, as provided or derived. */
156
+ readonly tension: number;
157
+ /** Friction strength, as provided or derived. */
158
+ readonly damping: number;
159
+ /** Decimal places of the resting threshold — see `restingMagnitude`. */
160
+ readonly precision: number;
161
+ /**
162
+ * Velocity multiplier applied when the value crosses the target, as
163
+ * provided or resolved from its named form. 1 (`'passthrough'`) leaves
164
+ * the motion untouched, 0 (`'stop'`) ends it at the target, negative
165
+ * values rebound.
166
+ */
167
+ readonly arrival: number;
168
+ /** Oscillation rate with no damping: `sqrt(tension / mass)`, in radians per second. */
169
+ readonly naturalFrequency: number;
170
+ /** The damping at which bounce disappears: `2 * mass * naturalFrequency`. */
171
+ readonly criticalDamping: number;
172
+ /**
173
+ * `damping / criticalDamping`. Below 1 the spring overshoots and
174
+ * bounces, at 1 it settles fastest without overshooting, above 1 it
175
+ * settles slower.
176
+ */
177
+ readonly dampingRatio: number;
178
+ /** Resting threshold in value units: half a unit in the last `precision` place. */
179
+ readonly restingMagnitude: number;
180
+ constructor(input: SpringDefinitionOptions);
181
+ /**
182
+ * Computes when a spring with this config comes to rest from `state`,
183
+ * in ms.
184
+ *
185
+ * The time is solved from the motion's decay bound rather than
186
+ * estimated: the spring is resting at the first tick at or after it.
187
+ * An underdamped spring can also rest earlier — its rest amplitude
188
+ * pulses, and a frame that samples a dip inside the resting threshold
189
+ * rests there — so read the value as "no later than", tight in
190
+ * practice. See
191
+ * https://github.com/tkofh/coily/blob/main/PRECISION.md.
192
+ *
193
+ * Returns 0 when `state` is already inside the resting threshold.
194
+ * Returns Infinity when the motion never settles: a `dampingRatio` of
195
+ * 0 whose `arrival` neither stops nor slows it. When `arrival` is 0
196
+ * the result is capped at the first target crossing, where such a
197
+ * spring rests exactly; other multipliers fold their per-crossing
198
+ * velocity loss into the time.
199
+ */
200
+ computeTimeRemaining(state: SpringState): number;
201
+ }
202
+ /**
203
+ * Creates a config from direct physical parameters: you set stiffness
204
+ * and friction exactly.
205
+ */
206
+ declare function defineSpring(input: Exact<DirectOptions>): SpringDefinition;
207
+ /**
208
+ * Creates a config from stiffness and motion character. Damping is
209
+ * derived as `2 * dampingRatio * sqrt(mass * tension)`.
210
+ */
211
+ declare function defineSpring(input: Exact<TensionRatioOptions>): SpringDefinition;
212
+ /**
213
+ * Creates a config from stiffness and bounciness. Damping is derived to
214
+ * match the requested bounce, from -1 (no bounce, slow settle) to 1
215
+ * (maximum bounce).
216
+ */
217
+ declare function defineSpring(input: Exact<TensionBounceOptions>): SpringDefinition;
218
+ /**
219
+ * Creates a config from friction and motion character. Tension is
220
+ * derived as `damping^2 / (4 * dampingRatio^2 * mass)`; `dampingRatio`
221
+ * must be greater than 0.
222
+ */
223
+ declare function defineSpring(input: Exact<DampingRatioOptions>): SpringDefinition;
224
+ /**
225
+ * Creates a config from friction and bounciness. Tension is derived to
226
+ * match the requested bounce; `bounce` must be less than 1.
227
+ */
228
+ declare function defineSpring(input: Exact<DampingBounceOptions>): SpringDefinition;
229
+ /**
230
+ * Creates a config from all three physical knobs. Mass is derived as
231
+ * `damping^2 / (4 * dampingRatio^2 * tension)` and cannot be provided;
232
+ * `dampingRatio` must be greater than 0.
233
+ */
234
+ declare function defineSpring(input: Exact<TensionDampingRatioOptions>): SpringDefinition;
235
+ /**
236
+ * Creates a config from all three physical knobs. Mass is derived from
237
+ * tension, damping, and the requested bounce, and cannot be provided;
238
+ * `bounce` must be less than 1.
239
+ */
240
+ declare function defineSpring(input: Exact<TensionDampingBounceOptions>): SpringDefinition;
241
+ /**
242
+ * Tunes the spring to rest at `duration` milliseconds, with
243
+ * `dampingRatio` setting the motion's character. Tension and damping are
244
+ * both derived.
245
+ *
246
+ * The timing assumes the motion starts `displacement` (default 1) away
247
+ * from the target — pass a `displacement` matching your animation range
248
+ * for accurate timing.
249
+ *
250
+ * @example
251
+ * ```ts
252
+ * // Tuned for a 300px move; rests within 750ms
253
+ * defineSpring({ duration: 750, dampingRatio: 1, displacement: 300 })
254
+ * ```
255
+ */
256
+ declare function defineSpring(input: Exact<DurationOptions>): SpringDefinition;
257
+ /**
258
+ * Tunes the spring to rest at `duration` milliseconds, with
259
+ * `bounce` setting how oscillatory the settling motion is. Tension and
260
+ * damping are both derived.
261
+ *
262
+ * The timing assumes the motion starts `displacement` (default 1) away
263
+ * from the target — pass a `displacement` matching your animation range
264
+ * for accurate timing.
265
+ */
266
+ declare function defineSpring(input: Exact<BounceDurationOptions>): SpringDefinition;
267
+ /**
268
+ * Tunes a spring of the given stiffness to rest at `duration`
269
+ * milliseconds. Mass is derived from tension and the computed natural
270
+ * frequency, then damping from mass and `dampingRatio`; mass cannot be
271
+ * provided.
272
+ *
273
+ * The timing assumes the motion starts `displacement` (default 1) away
274
+ * from the target — pass a `displacement` matching your animation range
275
+ * for accurate timing.
276
+ */
277
+ declare function defineSpring(input: Exact<TensionDurationOptions>): SpringDefinition;
278
+ /**
279
+ * Tunes a spring of the given stiffness to rest at `duration`
280
+ * milliseconds, with `bounce` setting how oscillatory the settling motion
281
+ * is. Mass is derived from tension and the computed natural frequency,
282
+ * and cannot be provided.
283
+ *
284
+ * The timing assumes the motion starts `displacement` (default 1) away
285
+ * from the target — pass a `displacement` matching your animation range
286
+ * for accurate timing.
287
+ */
288
+ declare function defineSpring(input: Exact<TensionBounceDurationOptions>): SpringDefinition;
289
+ /**
290
+ * Tunes a spring of the given friction to rest at `duration`
291
+ * milliseconds. Mass is derived from damping, `dampingRatio`, and the
292
+ * computed natural frequency, then tension from mass; mass cannot be
293
+ * provided.
294
+ *
295
+ * The timing assumes the motion starts `displacement` (default 1) away
296
+ * from the target — pass a `displacement` matching your animation range
297
+ * for accurate timing.
298
+ */
299
+ declare function defineSpring(input: Exact<DampingDurationOptions>): SpringDefinition;
300
+ /**
301
+ * Tunes a spring of the given friction to rest at `duration`
302
+ * milliseconds, with `bounce` setting how oscillatory the settling motion
303
+ * is. Mass is derived from damping and the computed natural frequency,
304
+ * and cannot be provided.
305
+ *
306
+ * The timing assumes the motion starts `displacement` (default 1) away
307
+ * from the target — pass a `displacement` matching your animation range
308
+ * for accurate timing.
309
+ */
310
+ declare function defineSpring(input: Exact<DampingBounceDurationOptions>): SpringDefinition;
311
+ //#endregion
312
+ export { defineSpring as i, SpringDefinitionOptions as n, SpringState as r, SpringDefinition as t };