coily 0.12.2 → 0.14.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 +132 -185
- package/dist/index.d.ts +2 -2
- package/dist/index.js +78 -2
- package/dist/nuxt.d.ts +42 -1
- package/dist/nuxt.js +24 -10
- package/dist/system-BXxAHM2a.js +1758 -0
- package/dist/system-Ci-R4RaI.d.ts +1032 -0
- package/dist/vue.d.ts +240 -25
- package/dist/vue.js +166 -270
- package/package.json +16 -16
- package/dist/system-Bzrx4uso.js +0 -976
- package/dist/system-coAFNYnb.d.ts +0 -350
package/README.md
CHANGED
|
@@ -1,254 +1,201 @@
|
|
|
1
|
-
#
|
|
1
|
+
# coily
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Simulate values using spring physics.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pnpm add coily
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## Quick start
|
|
16
16
|
|
|
17
|
-
```
|
|
18
|
-
import {
|
|
17
|
+
```ts
|
|
18
|
+
import { createSpringSystem, defineSpring } from 'coily'
|
|
19
19
|
|
|
20
|
-
const
|
|
21
|
-
|
|
20
|
+
const system = createSpringSystem()
|
|
21
|
+
system.start()
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
const spring = system.createSpring(0, defineSpring({ tension: 500, damping: 40 }))
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
spring.onUpdate(() => {
|
|
26
|
+
ball.style.setProperty('--x', String(spring.value))
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
// Animate toward a new target at any time. Position and velocity
|
|
30
|
+
// carry over, so interruptions look natural.
|
|
31
|
+
spring.target = 300
|
|
32
|
+
```
|
|
26
33
|
|
|
27
|
-
|
|
34
|
+
## Configuring springs
|
|
28
35
|
|
|
29
|
-
|
|
30
|
-
The initial value of the spring, and its target. All springs start in a resting state, but you can set `spring.target`
|
|
31
|
-
at any time
|
|
36
|
+
`defineSpring` accepts several input shapes and derives whatever you leave out:
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
```ts
|
|
39
|
+
defineSpring({ tension: 500, damping: 40 }) // direct physical parameters
|
|
40
|
+
defineSpring({ tension: 500, dampingRatio: 0.7 }) // damping derived
|
|
41
|
+
defineSpring({ tension: 500, bounce: 0.3 }) // bounce = 1 - dampingRatio
|
|
42
|
+
defineSpring({ duration: 750, dampingRatio: 1 }) // tuned to settle in ~750ms
|
|
43
|
+
defineSpring({ duration: 750, bounce: 0.5 })
|
|
44
|
+
```
|
|
38
45
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
- **`tension`**: stiffness (> 0)
|
|
47
|
+
- **`damping`**: friction (>= 0)
|
|
48
|
+
- **`dampingRatio`**: 0 = undamped, < 1 = bouncy, 1 = critically damped, > 1 = overdamped
|
|
49
|
+
- **`bounce`**: a friendlier alias for damping ratio, from -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). Coily never rounds values. Set `precision` to match your domain's resolution (see [PRECISION.md](https://github.com/tkofh/coily/blob/main/PRECISION.md))
|
|
42
53
|
|
|
43
|
-
|
|
44
|
-
How much dampening the spring experiences against its tension. A higher friction will make sure the spring doesn't
|
|
45
|
-
move too quickly, and slows down dramatically as it approaches the `target`
|
|
54
|
+
Without a config, springs are critically damped with a ~500ms settle time.
|
|
46
55
|
|
|
47
|
-
|
|
48
|
-
Additional options for the spring. `SpringOptions` takes the following parameters:
|
|
49
|
-
- `options.restingDistance: number`
|
|
50
|
-
The minimum difference between the `target` and `value` at which the spring will consider itself "at" the `target`
|
|
56
|
+
## Spring API
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
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 `SpringDefinition`, or `null` to revert to the default
|
|
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)
|
|
54
67
|
|
|
55
|
-
|
|
56
|
-
How the spring's `velocity` behaves when it arrives at its `target` value.
|
|
57
|
-
`velocity` is expressed in unit/second. Defaults to `none`.
|
|
58
|
-
- `'bounce'`: multiplies the velocity by -1, bouncing the value back in the direction from which it came
|
|
59
|
-
- `'clamp'`: multiplies the velocity by 0, instantly stopping the spring at its value
|
|
60
|
-
- `'none'`: multiplies the velocity by 1, allowing the spring to overshoot as necessary
|
|
61
|
-
- `number`: multiplies the velocity by the given number
|
|
62
|
-
- `(velocity: number) => number`: sets the velocity to the result of a function whose only argument is the
|
|
63
|
-
previous velocity
|
|
68
|
+
### Chaining
|
|
64
69
|
|
|
65
|
-
|
|
70
|
+
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):
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
```ts
|
|
73
|
+
const leader = system.createSpring(0)
|
|
68
74
|
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
const follower = system.createSpring(leader)
|
|
76
|
+
const trailing = system.createSpring(mapSpring(leader, (v) => v + 20))
|
|
77
|
+
const mirrored = system.createSpring(mapSpring(leader, (v) => -v))
|
|
71
78
|
```
|
|
72
79
|
|
|
73
|
-
|
|
74
|
-
configuration is more of an art than a science.
|
|
80
|
+
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.
|
|
75
81
|
|
|
76
|
-
|
|
82
|
+
`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:
|
|
77
83
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
```ts
|
|
85
|
+
const x = system.createSpring(0)
|
|
86
|
+
const y = system.createSpring(0)
|
|
81
87
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
between `value` and `target` is less than `restingDistance`, the spring sets its `value` to its `target` and enters
|
|
85
|
-
the `resting` state.
|
|
88
|
+
const distance = system.createSpring(mapSpring({ x, y }, ({ x, y }) => Math.hypot(x, y)))
|
|
89
|
+
```
|
|
86
90
|
|
|
87
|
-
|
|
91
|
+
Composite springs (below) are sources of their whole value shape, so one map can derive a scalar from every channel at once:
|
|
88
92
|
|
|
89
|
-
|
|
93
|
+
```ts
|
|
94
|
+
const point = system.createSpring({ x: 3, y: 4 })
|
|
90
95
|
|
|
91
|
-
|
|
96
|
+
const magnitude = system.createSpring(mapSpring(point, ({ x, y }) => Math.hypot(x, y)))
|
|
97
|
+
```
|
|
92
98
|
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* The current value of the spring
|
|
96
|
-
*/
|
|
97
|
-
spring.value
|
|
99
|
+
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.
|
|
98
100
|
|
|
99
|
-
|
|
100
|
-
* In which state the spring is currently. Will be one of:
|
|
101
|
-
* - "resting"
|
|
102
|
-
* - "moving"
|
|
103
|
-
* - "frozen"
|
|
104
|
-
*/
|
|
105
|
-
spring.state
|
|
101
|
+
Follow graphs may contain cycles: retargets never re-emit, so nothing loops within a frame. Mutual followers converge to a shared value. 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.
|
|
106
102
|
|
|
107
|
-
|
|
108
|
-
* How fast the spring is currently moving, and in which direction
|
|
109
|
-
*/
|
|
110
|
-
spring.velocity
|
|
111
|
-
```
|
|
103
|
+
### Composites
|
|
112
104
|
|
|
113
|
-
|
|
105
|
+
`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`:
|
|
114
106
|
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
* Get & Set the target of the spring
|
|
118
|
-
*/
|
|
119
|
-
console.log(spring.target)
|
|
120
|
-
spring.target = 10
|
|
107
|
+
```ts
|
|
108
|
+
const spring = system.createSpring({ position: { x: 0, y: 0 }, opacity: 1 })
|
|
121
109
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
*/
|
|
125
|
-
console.log(spring.config)
|
|
126
|
-
spring.config = { mass: 0.1, tension: 500, friction: 5 }
|
|
110
|
+
spring.target = { position: { x: 100 } } // a partial target leaves other channels alone
|
|
111
|
+
spring.value // { position: { x, y }, opacity } — a stable, read-only mirror
|
|
127
112
|
```
|
|
128
113
|
|
|
129
|
-
|
|
114
|
+
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.
|
|
130
115
|
|
|
131
|
-
|
|
132
|
-
last update:
|
|
116
|
+
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.
|
|
133
117
|
|
|
134
|
-
|
|
135
|
-
// Tickloop is a library that exposes a simple requestAnimationFrame based update loop
|
|
136
|
-
import { createTicker } from 'tickloop'
|
|
137
|
-
import { createSpring } from 'coily'
|
|
118
|
+
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:
|
|
138
119
|
|
|
139
|
-
|
|
120
|
+
```ts
|
|
121
|
+
spring.config = { position: stiff, opacity: defineSpring({ duration: 300, dampingRatio: 1 }) }
|
|
122
|
+
```
|
|
140
123
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
124
|
+
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.
|
|
125
|
+
|
|
126
|
+
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:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
follower.target = leader
|
|
130
|
+
|
|
131
|
+
// or mix numbers and live sources per channel
|
|
132
|
+
follower.target = {
|
|
133
|
+
opacity: 0.5,
|
|
134
|
+
position: { x: mapSpring(leader, ({ position }) => -position.x) },
|
|
135
|
+
}
|
|
145
136
|
```
|
|
146
137
|
|
|
147
|
-
|
|
138
|
+
## Reduced motion
|
|
148
139
|
|
|
149
|
-
|
|
150
|
-
Calling `freeze()` will set the spring's `state` to `'frozen'`
|
|
140
|
+
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.
|
|
151
141
|
|
|
152
|
-
|
|
153
|
-
const spring = createSpring(0, { mass: 1, tension: 100, friction: 20 })
|
|
142
|
+
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.
|
|
154
143
|
|
|
155
|
-
|
|
144
|
+
## Vue
|
|
156
145
|
|
|
157
|
-
|
|
146
|
+
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:
|
|
158
147
|
|
|
159
|
-
|
|
160
|
-
|
|
148
|
+
```ts
|
|
149
|
+
import { useSpringSystem } from 'coily/vue'
|
|
161
150
|
|
|
162
|
-
|
|
151
|
+
const system = useSpringSystem() // e.g. read system.reducedMotion to gate decorative effects
|
|
163
152
|
```
|
|
164
153
|
|
|
165
|
-
|
|
154
|
+
(`provideSpringSystem(system, app?)` remains for plugging in a system you created yourself — mostly useful in tests. You manage `start()`/`stop()` for it.)
|
|
166
155
|
|
|
167
|
-
|
|
168
|
-
using a `SpringSystem` to group springs together and provide a single call site for updating each spring:
|
|
156
|
+
Then animate anywhere below:
|
|
169
157
|
|
|
170
|
-
```
|
|
171
|
-
|
|
158
|
+
```vue
|
|
159
|
+
<script setup>
|
|
160
|
+
import { useSpring } from 'coily/vue'
|
|
172
161
|
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
// Then, create some springs
|
|
177
|
-
const spring1 = system.createSpring(0, { mass: 1, tension: 100, friction: 20 })
|
|
178
|
-
const spring2 = system.createSpring(0, { mass: 0.25, tension: 50, friction: 10 })
|
|
162
|
+
const target = ref(0)
|
|
163
|
+
const x = useSpring(target, { duration: 500, bounce: 0.3 })
|
|
164
|
+
</script>
|
|
179
165
|
|
|
180
|
-
|
|
181
|
-
|
|
166
|
+
<template>
|
|
167
|
+
<div class="ball" :style="{ translate: `${x}px 0` }" @click="target = 300" />
|
|
168
|
+
</template>
|
|
182
169
|
```
|
|
183
170
|
|
|
184
|
-
|
|
185
|
-
updating it:
|
|
171
|
+
`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.
|
|
186
172
|
|
|
187
|
-
|
|
188
|
-
import { createSpringSystem } from 'coily'
|
|
173
|
+
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.
|
|
189
174
|
|
|
190
|
-
|
|
175
|
+
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))`.
|
|
191
176
|
|
|
192
|
-
|
|
177
|
+
There's also a renderless `<SpringValue :target="n">` component exposing `{ value, velocity, isResting, timeRemaining, jumpTo }` through its default slot.
|
|
193
178
|
|
|
194
|
-
|
|
179
|
+
`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.
|
|
195
180
|
|
|
196
|
-
|
|
197
|
-
```
|
|
181
|
+
## Nuxt
|
|
198
182
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
values in the chain.
|
|
208
|
-
|
|
209
|
-
The links take one of two forms:
|
|
210
|
-
|
|
211
|
-
- A fixed, additive offset from the previous value, e.g. `50` will make Spring 2 target Spring 1's current value plus 50.
|
|
212
|
-
- A function with the following signature:
|
|
213
|
-
```typescript
|
|
214
|
-
const link = (
|
|
215
|
-
previousSpringValue: number,
|
|
216
|
-
previousSpringTarget: number,
|
|
217
|
-
currentSpringValue?: number,
|
|
218
|
-
currentSpringTarget?: number
|
|
219
|
-
): number => /* ... */
|
|
220
|
-
```
|
|
221
|
-
The `currentSpringTarget` and `currentSpringValue` will be `undefined` the first time the function is called, as the
|
|
222
|
-
targets are initialized for all springs.
|
|
223
|
-
|
|
224
|
-
#### Creating
|
|
225
|
-
|
|
226
|
-
```typescript
|
|
227
|
-
const chain = createSpringChain(
|
|
228
|
-
0, // initial
|
|
229
|
-
[50, 50, (value) => value * 1.25],
|
|
230
|
-
{ mass: 10, tension: 3000, friction: 300 }, // config
|
|
231
|
-
)
|
|
183
|
+
```ts
|
|
184
|
+
export default defineNuxtConfig({
|
|
185
|
+
modules: ['coily/nuxt'],
|
|
186
|
+
coily: {
|
|
187
|
+
debug: false, // debug logs active motion counts
|
|
188
|
+
reducedMotion: 'user', // 'user' | 'always' | 'never'
|
|
189
|
+
},
|
|
190
|
+
})
|
|
232
191
|
```
|
|
233
192
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
Spring Chains have almost the same interface as Springs do.
|
|
193
|
+
The module provides a spring system for the whole app (started on the client), auto-imports `useSpring`, `useSpringSystem`, `useSpringPool`, and `defineSpring`, and registers the `SpringValue` component.
|
|
237
194
|
|
|
238
|
-
|
|
239
|
-
and `velocities`. Each of these arrays are updated _in place_, which means it is safe to grab a reference to the value
|
|
240
|
-
and read from it later.
|
|
195
|
+
## Timing
|
|
241
196
|
|
|
242
|
-
|
|
197
|
+
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.
|
|
243
198
|
|
|
244
|
-
|
|
199
|
+
## License
|
|
245
200
|
|
|
246
|
-
|
|
247
|
-
chain.values // readonly number[]
|
|
248
|
-
chain.velocities // readonly number[]
|
|
249
|
-
chain.targets // readonly number[]
|
|
250
|
-
chain.states // readonly number[]
|
|
251
|
-
|
|
252
|
-
chain.target = 10 // get and set the first target
|
|
253
|
-
chain.state // if any children are 'moving', this will be moving. if the chain is frozen, it will be 'frozen'. otherwise, it is 'resting'.
|
|
254
|
-
```
|
|
201
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { type
|
|
1
|
+
import { C as SpringDefinitionOptions, S as SpringDefinition, T as defineSpring, _ 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, w as SpringState, x as mapSpring, y as SpringSource } from "./system-Ci-R4RaI.js";
|
|
2
|
+
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 };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,78 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { i as mapSpring, n as SpringSourceSymbol, o as defineSpring, r as isSpringSource, s as invariant, t as createSpringSystem } from "./system-BXxAHM2a.js";
|
|
2
|
+
//#region src/kinematic-source.ts
|
|
3
|
+
function isKinematicSource(value) {
|
|
4
|
+
if (!isSpringSource(value)) return false;
|
|
5
|
+
const api = value[SpringSourceSymbol];
|
|
6
|
+
return "velocity" in api && "acceleration" in api;
|
|
7
|
+
}
|
|
8
|
+
const NOT_KINEMATIC = "expected a source in motion (a Spring or CompositeSpring); a value derived with mapSpring has neither velocity nor acceleration";
|
|
9
|
+
/**
|
|
10
|
+
* Derives a source from another's velocity — the rate its value changes,
|
|
11
|
+
* in value units per second. `velocityOf(motion)` is a source that reads
|
|
12
|
+
* `motion`'s current velocity, so speed-driven effects fall out of the
|
|
13
|
+
* same follow and `mapSpring` machinery as everything else: a spring can
|
|
14
|
+
* follow it (`blur.target = velocityOf(motion)`) and a map can shape it
|
|
15
|
+
* (`mapSpring(velocityOf(motion), (v) => 1 + Math.abs(v) * 0.001)` for
|
|
16
|
+
* squash and stretch). A scalar source yields a scalar velocity a spring
|
|
17
|
+
* can follow; a `CompositeSpring` or shape yields a velocity of the same
|
|
18
|
+
* shape, mapped to a scalar the way the composite itself is.
|
|
19
|
+
*
|
|
20
|
+
* The result is a stateless view, not a spring: it holds no
|
|
21
|
+
* subscriptions and needs no disposal, and reads report the source's
|
|
22
|
+
* current velocity on the fly. It updates when the source does and is
|
|
23
|
+
* released with it — followers detach then, keeping their current
|
|
24
|
+
* target, as they would from a disposed spring.
|
|
25
|
+
*
|
|
26
|
+
* Only a source in motion qualifies — a `Spring` or a `CompositeSpring`.
|
|
27
|
+
* A value derived with `mapSpring` has no velocity (its rate of change
|
|
28
|
+
* would need the derivative of the map), so it is rejected at the type
|
|
29
|
+
* level and at runtime; take `velocityOf` of the spring the map reads
|
|
30
|
+
* instead.
|
|
31
|
+
*/
|
|
32
|
+
function velocityOf(source) {
|
|
33
|
+
invariant(isKinematicSource(source), NOT_KINEMATIC);
|
|
34
|
+
const api = source[SpringSourceSymbol];
|
|
35
|
+
return Object.freeze({ [SpringSourceSymbol]: Object.freeze({
|
|
36
|
+
get value() {
|
|
37
|
+
return api.velocity;
|
|
38
|
+
},
|
|
39
|
+
onUpdate: (callback) => api.onUpdate(callback),
|
|
40
|
+
onDispose: (callback) => api.onDispose(callback)
|
|
41
|
+
}) });
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Derives a source from another's acceleration — the rate its velocity
|
|
45
|
+
* changes, in value units per second squared. `accelerationOf(motion)`
|
|
46
|
+
* spikes when `motion` is pushed hardest and passes through zero at the
|
|
47
|
+
* peak of a swing, so force-driven effects fall out of the same follow
|
|
48
|
+
* and `mapSpring` machinery: a spring can follow it, and a map can turn
|
|
49
|
+
* it into an impact flash or a jelly wobble
|
|
50
|
+
* (`mapSpring(accelerationOf(motion), (a) => Math.min(1, Math.abs(a) * 1e-4))`).
|
|
51
|
+
* A scalar source yields a scalar acceleration; a `CompositeSpring` or
|
|
52
|
+
* shape yields an acceleration of the same shape, mapped to a scalar the
|
|
53
|
+
* way the composite itself is.
|
|
54
|
+
*
|
|
55
|
+
* The result is a stateless view, not a spring: it holds no
|
|
56
|
+
* subscriptions and needs no disposal, and reads report the source's
|
|
57
|
+
* current acceleration on the fly. It updates when the source does and
|
|
58
|
+
* is released with it, followers detaching then as from a disposed
|
|
59
|
+
* spring.
|
|
60
|
+
*
|
|
61
|
+
* Only a source in motion qualifies — a `Spring` or a `CompositeSpring`.
|
|
62
|
+
* A value derived with `mapSpring` has no acceleration, so it is rejected
|
|
63
|
+
* at the type level and at runtime; take `accelerationOf` of the spring
|
|
64
|
+
* the map reads instead.
|
|
65
|
+
*/
|
|
66
|
+
function accelerationOf(source) {
|
|
67
|
+
invariant(isKinematicSource(source), NOT_KINEMATIC);
|
|
68
|
+
const api = source[SpringSourceSymbol];
|
|
69
|
+
return Object.freeze({ [SpringSourceSymbol]: Object.freeze({
|
|
70
|
+
get value() {
|
|
71
|
+
return api.acceleration;
|
|
72
|
+
},
|
|
73
|
+
onUpdate: (callback) => api.onUpdate(callback),
|
|
74
|
+
onDispose: (callback) => api.onDispose(callback)
|
|
75
|
+
}) });
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
export { SpringSourceSymbol, accelerationOf, createSpringSystem, defineSpring, mapSpring, velocityOf };
|
package/dist/nuxt.d.ts
CHANGED
|
@@ -1,9 +1,50 @@
|
|
|
1
1
|
import { NuxtModule } from "@nuxt/schema";
|
|
2
|
-
|
|
3
2
|
//#region src/vue/nuxt.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Options for the `coily` key in `nuxt.config`, passed to the app-wide
|
|
5
|
+
* spring system the module creates.
|
|
6
|
+
*/
|
|
4
7
|
interface CoilyModuleOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Log active motion counts to the console whenever they change.
|
|
10
|
+
* @default false
|
|
11
|
+
*/
|
|
5
12
|
debug?: boolean;
|
|
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.
|
|
17
|
+
* @default 0
|
|
18
|
+
*/
|
|
19
|
+
fps?: number;
|
|
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.
|
|
24
|
+
* @default 500
|
|
25
|
+
*/
|
|
26
|
+
lagThreshold?: number;
|
|
27
|
+
/**
|
|
28
|
+
* The elapsed milliseconds a lagging frame is replaced with. Clamped
|
|
29
|
+
* to at most `lagThreshold`.
|
|
30
|
+
* @default 33
|
|
31
|
+
*/
|
|
32
|
+
adjustedLag?: number;
|
|
33
|
+
/**
|
|
34
|
+
* When springs snap to their targets instead of animating: `'user'`
|
|
35
|
+
* follows the OS prefers-reduced-motion setting, including live
|
|
36
|
+
* changes; `'always'` and `'never'` force one behavior.
|
|
37
|
+
* @default 'user'
|
|
38
|
+
*/
|
|
39
|
+
reducedMotion?: 'user' | 'always' | 'never';
|
|
6
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* The Nuxt module: creates an app-wide spring system (started on the
|
|
43
|
+
* client), auto-imports the composables and helpers `useSpring`,
|
|
44
|
+
* `useSpringSystem`, `useSpringPool`, `defineSpring`, `mapSpring`,
|
|
45
|
+
* `velocityOf`, and `accelerationOf`, and registers the `SpringValue`
|
|
46
|
+
* component. Types are left out — they would be noise in most apps.
|
|
47
|
+
*/
|
|
7
48
|
declare const coilyModule: NuxtModule;
|
|
8
49
|
//#endregion
|
|
9
50
|
export { CoilyModuleOptions, coilyModule as default };
|
package/dist/nuxt.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { addComponent, addImports, addPluginTemplate, defineNuxtModule } from "@nuxt/kit";
|
|
2
2
|
//#region src/vue/nuxt.ts
|
|
3
3
|
function getPluginTemplate(options) {
|
|
4
|
+
const systemOptions = {};
|
|
5
|
+
if (options.debug) systemOptions.debug = true;
|
|
6
|
+
if (options.fps !== void 0) systemOptions.fps = options.fps;
|
|
7
|
+
if (options.lagThreshold !== void 0) systemOptions.lagThreshold = options.lagThreshold;
|
|
8
|
+
if (options.adjustedLag !== void 0) systemOptions.adjustedLag = options.adjustedLag;
|
|
9
|
+
if (options.reducedMotion) systemOptions.reducedMotion = options.reducedMotion;
|
|
4
10
|
return `import { createSpringSystem } from 'coily'
|
|
5
11
|
import { provideSpringSystem } from 'coily/vue'
|
|
6
12
|
|
|
7
13
|
export default defineNuxtPlugin((nuxtApp) => {
|
|
8
14
|
nuxtApp.hook('app:created', (vueApp) => {
|
|
9
|
-
const system = createSpringSystem(${
|
|
15
|
+
const system = createSpringSystem(${Object.keys(systemOptions).length > 0 ? JSON.stringify(systemOptions) : ""})
|
|
10
16
|
provideSpringSystem(system, vueApp)
|
|
11
17
|
|
|
12
18
|
if(import.meta.client) {
|
|
@@ -15,6 +21,13 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
15
21
|
})
|
|
16
22
|
})`;
|
|
17
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* The Nuxt module: creates an app-wide spring system (started on the
|
|
26
|
+
* client), auto-imports the composables and helpers `useSpring`,
|
|
27
|
+
* `useSpringSystem`, `useSpringPool`, `defineSpring`, `mapSpring`,
|
|
28
|
+
* `velocityOf`, and `accelerationOf`, and registers the `SpringValue`
|
|
29
|
+
* component. Types are left out — they would be noise in most apps.
|
|
30
|
+
*/
|
|
18
31
|
const coilyModule = defineNuxtModule({
|
|
19
32
|
meta: {
|
|
20
33
|
name: "coily",
|
|
@@ -35,26 +48,27 @@ const coilyModule = defineNuxtModule({
|
|
|
35
48
|
},
|
|
36
49
|
{
|
|
37
50
|
from: "coily/vue",
|
|
38
|
-
name: "
|
|
51
|
+
name: "useSpringSystem"
|
|
39
52
|
},
|
|
40
53
|
{
|
|
41
54
|
from: "coily/vue",
|
|
42
|
-
name: "
|
|
43
|
-
type: true
|
|
55
|
+
name: "useSpringPool"
|
|
44
56
|
},
|
|
45
57
|
{
|
|
46
|
-
from: "coily
|
|
47
|
-
name: "
|
|
48
|
-
type: true
|
|
58
|
+
from: "coily",
|
|
59
|
+
name: "defineSpring"
|
|
49
60
|
},
|
|
50
61
|
{
|
|
51
62
|
from: "coily",
|
|
52
|
-
name: "
|
|
63
|
+
name: "mapSpring"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
from: "coily",
|
|
67
|
+
name: "velocityOf"
|
|
53
68
|
},
|
|
54
69
|
{
|
|
55
70
|
from: "coily",
|
|
56
|
-
name: "
|
|
57
|
-
type: true
|
|
71
|
+
name: "accelerationOf"
|
|
58
72
|
}
|
|
59
73
|
]);
|
|
60
74
|
addComponent({
|