coily 0.1.1
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/LICENSE +21 -0
- package/README.md +256 -0
- package/dist/index.cjs +317 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.js +285 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Tim Morris
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# `coily`
|
|
2
|
+
|
|
3
|
+
Spring Physics Utilities
|
|
4
|
+
|
|
5
|
+
## Installation & Setup
|
|
6
|
+
|
|
7
|
+
`coily` is available on npm and can be installed with your favorite package manager:
|
|
8
|
+
|
|
9
|
+
```shell
|
|
10
|
+
$ yarn add coily
|
|
11
|
+
# or
|
|
12
|
+
$ npm i coily
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Then import the creator from the package in your code:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { createSpring } from 'coily'
|
|
19
|
+
|
|
20
|
+
const spring = createSpring(0, { mass: 1, tension: 100, friction: 25 })
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### Creating a Spring
|
|
26
|
+
|
|
27
|
+
The `createSpring` function takes the following arguments:
|
|
28
|
+
|
|
29
|
+
- `initial: number`
|
|
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
|
|
32
|
+
|
|
33
|
+
- `config: SpringConfig`
|
|
34
|
+
The configuration for the spring. `SpringConfig` takes the following parameters:
|
|
35
|
+
|
|
36
|
+
- `config.mass: number`
|
|
37
|
+
How massive the spring is. A lower number will allow the spring to speed up and slow down more quickly than a
|
|
38
|
+
larger number. Larger numbers are more likely to overshoot the `target`
|
|
39
|
+
|
|
40
|
+
- `config.tension: number`
|
|
41
|
+
How much force the spring experiences toward its `target`. A higher value will move the spring more quickly, and
|
|
42
|
+
allow the spring to potentially overshoot.
|
|
43
|
+
|
|
44
|
+
- `config.friction: number`
|
|
45
|
+
How much dampening the spring experiences against its tension. A higher friction will make sure the spring doesn't
|
|
46
|
+
move too quickly, and slows down dramatically as it approaches the `target`
|
|
47
|
+
|
|
48
|
+
- `options: SpringOptions`
|
|
49
|
+
Additional options for the spring. `SpringOptions` takes the following parameters:
|
|
50
|
+
|
|
51
|
+
- `options.restingDistance: number`
|
|
52
|
+
The minimum difference between the `target` and `value` at which the spring will consider itself "at" the `target`
|
|
53
|
+
|
|
54
|
+
- `options.restingVelocity: number`
|
|
55
|
+
The minimum velocity at which the spring will consider itself "not moving"
|
|
56
|
+
|
|
57
|
+
- `options.arrivalBehavior: 'bounce' | 'clamp' | 'none' | number | ((velocity: number) => number)`
|
|
58
|
+
How the spring's `velocity` behaves when it arrives at its `target` value.
|
|
59
|
+
`velocity` is expressed in unit/second. Defaults to `none`.
|
|
60
|
+
- `'bounce'`: multiplies the velocity by -1, bouncing the value back in the direction from which it came
|
|
61
|
+
- `'clamp'`: multiplies the velocity by 0, instantly stopping the spring at its value
|
|
62
|
+
- `'none'`: multiplies the velocity by 1, allowing the spring to overshoot as necessary
|
|
63
|
+
- `number`: multiplies the velocity by the given number
|
|
64
|
+
- `(velocity: number) => number`: sets the velocity to the result of a function whose only argument is the
|
|
65
|
+
previous velocity
|
|
66
|
+
|
|
67
|
+
#### Spring Config Tips
|
|
68
|
+
|
|
69
|
+
The configuration object above provides a good starting point when picking your config:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
createSpring(0, { mass: 1, tension: 100, friction: 25 })
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Don't be afraid to make `mass` a decimal, and don't be afraid of large tensions and frictions either. Picking the
|
|
76
|
+
configuration is more of an art than a science.
|
|
77
|
+
|
|
78
|
+
#### Spring Options
|
|
79
|
+
|
|
80
|
+
The spring options object is helpful in reducing the amount of computation your springs perform. If the spring was
|
|
81
|
+
allowed to wait until its `velocity` and difference (`value` - `target`) were _at_ zero, it would imperceptibly update
|
|
82
|
+
every frame for a very long time.
|
|
83
|
+
|
|
84
|
+
The `restingDistance` and `restingVelocity` options help combat this, by telling the spring at what point it should
|
|
85
|
+
consider itself on target and resting. Once the velocity is less than `restingVelocity` and the difference
|
|
86
|
+
between `value` and `target` is less than `restingDistance`, the spring sets its `value` to its `target` and enters
|
|
87
|
+
the `resting` state.
|
|
88
|
+
|
|
89
|
+
### Working With Springs
|
|
90
|
+
|
|
91
|
+
#### Reading Information
|
|
92
|
+
|
|
93
|
+
The spring exposes some readonly information such as its `value`, `state` and `velocity`:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
/**
|
|
97
|
+
* The current value of the spring
|
|
98
|
+
*/
|
|
99
|
+
spring.value
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* In which state the spring is currently. Will be one of:
|
|
103
|
+
* - "resting"
|
|
104
|
+
* - "moving"
|
|
105
|
+
* - "frozen"
|
|
106
|
+
*/
|
|
107
|
+
spring.state
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* How fast the spring is currently moving, and in which direction
|
|
111
|
+
*/
|
|
112
|
+
spring.velocity
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Additionally, `target` and `config` can be updated whenever:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
/**
|
|
119
|
+
* Get & Set the target of the spring
|
|
120
|
+
*/
|
|
121
|
+
console.log(spring.target)
|
|
122
|
+
spring.target = 10
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get & Set the config of the spring
|
|
126
|
+
*/
|
|
127
|
+
console.log(spring.config)
|
|
128
|
+
spring.config = { mass: 0.1, tension: 500, friction: 5 }
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
#### Updating
|
|
132
|
+
|
|
133
|
+
To simulate the spring and update its value, call the `simulate` method and pass in the number of milliseconds since the
|
|
134
|
+
last update:
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
// Tickloop is a library that exposes a simple requestAnimationFrame based update loop
|
|
138
|
+
import { createTicker } from 'tickloop'
|
|
139
|
+
import { createSpring } from 'coily'
|
|
140
|
+
|
|
141
|
+
const spring = createSpring(0, { mass: 1, tension: 100, friction: 20 })
|
|
142
|
+
|
|
143
|
+
const ticker = createTicker()
|
|
144
|
+
ticker.add((_, delta) => {
|
|
145
|
+
spring.simulate(delta)
|
|
146
|
+
})
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### Freezing
|
|
150
|
+
|
|
151
|
+
To hold a spring in place (while maintaining its velocity), use the `freeze()` (and `unfreeze()`) methods.
|
|
152
|
+
Calling `freeze()` will set the spring's `state` to `'frozen'`
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
const spring = createSpring(0, { mass: 1, tension: 100, friction: 20 })
|
|
156
|
+
|
|
157
|
+
spring.target = 100
|
|
158
|
+
|
|
159
|
+
console.log(spring.value) // 0
|
|
160
|
+
|
|
161
|
+
spring.freeze()
|
|
162
|
+
spring.simulate(1000)
|
|
163
|
+
|
|
164
|
+
console.log(spring.value) // 0
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Creating a Spring System
|
|
168
|
+
|
|
169
|
+
Manually updating every spring with its `simulate` method would be tedious to say the least. Where possible, prefer
|
|
170
|
+
using a `SpringSystem` to group springs together and provide a single call site for updating each spring:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { createSpringSystem } from 'coily'
|
|
174
|
+
|
|
175
|
+
// First, create the system
|
|
176
|
+
const system = createSpringSystem()
|
|
177
|
+
|
|
178
|
+
// Then, create some springs
|
|
179
|
+
const spring1 = system.createSpring(0, { mass: 1, tension: 100, friction: 20 })
|
|
180
|
+
const spring2 = system.createSpring(0, { mass: 0.25, tension: 50, friction: 10 })
|
|
181
|
+
|
|
182
|
+
// Simulate all springs at once from the system
|
|
183
|
+
system.simulate(16)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Should a spring become irrelevant for some reason, remember to call the `cleanup` method so that the system stops
|
|
187
|
+
updating it:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { createSpringSystem } from 'coily'
|
|
191
|
+
|
|
192
|
+
const system = createSpringSystem()
|
|
193
|
+
|
|
194
|
+
const spring = system.createSpring(0, { mass: 1, tension: 100, friction: 20 })
|
|
195
|
+
|
|
196
|
+
// Later
|
|
197
|
+
|
|
198
|
+
system.cleanup(spring)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Spring Chains
|
|
202
|
+
|
|
203
|
+
Sometimes the `target` of one spring should be the `value` of another.
|
|
204
|
+
This can be tedious to set up manually and involves juggling multiple updates per frame to get a good look at the
|
|
205
|
+
current state.
|
|
206
|
+
|
|
207
|
+
Spring Chains help with this problem by building on the `createSpring` API. You'll still supply an `initial` target,
|
|
208
|
+
a `SpringConfig`, and an optional `SpringOptions`. You'll also pass an array of chain "links" that describe additional
|
|
209
|
+
values in the chain.
|
|
210
|
+
|
|
211
|
+
The links take one of two forms:
|
|
212
|
+
|
|
213
|
+
- A fixed, additive offset from the previous value, e.g. `50` will make Spring 2 target Spring 1's current value plus 50.
|
|
214
|
+
- A function with the following signature:
|
|
215
|
+
```typescript
|
|
216
|
+
const link = (
|
|
217
|
+
previousSpringValue: number,
|
|
218
|
+
previousSpringTarget: number,
|
|
219
|
+
currentSpringValue?: number,
|
|
220
|
+
currentSpringTarget?: number
|
|
221
|
+
): number => /* ... */
|
|
222
|
+
```
|
|
223
|
+
The `currentSpringTarget` and `currentSpringValue` will be `undefined` the first time the function is called, as the
|
|
224
|
+
targets are initialized for all springs.
|
|
225
|
+
|
|
226
|
+
#### Creating
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
const chain = createSpringChain(
|
|
230
|
+
0, // initial
|
|
231
|
+
[50, 50, (value) => value * 1.25],
|
|
232
|
+
{ mass: 10, tension: 3000, friction: 300 } // config
|
|
233
|
+
)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
#### Reading State
|
|
237
|
+
|
|
238
|
+
Spring Chains have almost the same interface as Springs do.
|
|
239
|
+
|
|
240
|
+
Instead of a single `value`, they have an array of numbers as their value. The same goes for `targets`, `states`,
|
|
241
|
+
and `velocities`. Each of these arrays are updated _in place_, which means it is safe to grab a reference to the value
|
|
242
|
+
and read from it later.
|
|
243
|
+
|
|
244
|
+
Additionally, Spring Chains have a singular `state` property which indicates if _any_ of its links are in a `'moving'` state or not.
|
|
245
|
+
|
|
246
|
+
There is also a singular `target` property which returns the first target in the chain. Setting this will update the springs as well.
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
chain.values // readonly number[]
|
|
250
|
+
chain.velocities // readonly number[]
|
|
251
|
+
chain.targets // readonly number[]
|
|
252
|
+
chain.states // readonly number[]
|
|
253
|
+
|
|
254
|
+
chain.target = 10 // get and set the first target
|
|
255
|
+
chain.state // if any children are 'moving', this will be moving. if the chain is frozen, it will be 'frozen'. otherwise, it is 'resting'.
|
|
256
|
+
```
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod));
|
|
21
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
22
|
+
|
|
23
|
+
// src/index.ts
|
|
24
|
+
var src_exports = {};
|
|
25
|
+
__export(src_exports, {
|
|
26
|
+
createSpring: () => createSpring,
|
|
27
|
+
createSpringChain: () => createSpringChain,
|
|
28
|
+
createSpringSystem: () => createSpringSystem
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(src_exports);
|
|
31
|
+
|
|
32
|
+
// src/lib/createSpring.ts
|
|
33
|
+
var import_mitt = __toESM(require("mitt"), 1);
|
|
34
|
+
var getArrivalFunction = (behavior) => {
|
|
35
|
+
let result;
|
|
36
|
+
if (typeof behavior === "function") {
|
|
37
|
+
result = behavior;
|
|
38
|
+
} else {
|
|
39
|
+
let scalar;
|
|
40
|
+
if (typeof behavior === "number") {
|
|
41
|
+
scalar = behavior;
|
|
42
|
+
} else if (behavior === "bounce") {
|
|
43
|
+
scalar = -1;
|
|
44
|
+
} else if (behavior === "clamp") {
|
|
45
|
+
scalar = 0;
|
|
46
|
+
} else if (behavior === "none") {
|
|
47
|
+
scalar = 1;
|
|
48
|
+
}
|
|
49
|
+
result = (velocity) => velocity * scalar;
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
};
|
|
53
|
+
var createSpringImpl = (initial, config, options) => {
|
|
54
|
+
let state = "resting";
|
|
55
|
+
let velocity = 0;
|
|
56
|
+
let target = initial;
|
|
57
|
+
let value = initial;
|
|
58
|
+
const restingDistance = Math.max((options == null ? void 0 : options.restingDistance) ?? 1e-4, 0);
|
|
59
|
+
const restingVelocity = Math.max((options == null ? void 0 : options.restingVelocity) ?? 1e-4, 0);
|
|
60
|
+
const arrivalFunction = getArrivalFunction((options == null ? void 0 : options.arrivalBehavior) ?? 1);
|
|
61
|
+
const emitter = (0, import_mitt.default)();
|
|
62
|
+
const { emit, ...emitterApi } = emitter;
|
|
63
|
+
const spring = {
|
|
64
|
+
...emitterApi,
|
|
65
|
+
get target() {
|
|
66
|
+
return target;
|
|
67
|
+
},
|
|
68
|
+
set target(val) {
|
|
69
|
+
target = val;
|
|
70
|
+
if (Math.abs(value - target) > restingDistance) {
|
|
71
|
+
state = "moving";
|
|
72
|
+
emit("update:state", state);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
get value() {
|
|
76
|
+
return value;
|
|
77
|
+
},
|
|
78
|
+
get velocity() {
|
|
79
|
+
return velocity;
|
|
80
|
+
},
|
|
81
|
+
get state() {
|
|
82
|
+
return state;
|
|
83
|
+
},
|
|
84
|
+
get config() {
|
|
85
|
+
return config;
|
|
86
|
+
},
|
|
87
|
+
set config(value2) {
|
|
88
|
+
config = value2;
|
|
89
|
+
},
|
|
90
|
+
freeze: () => {
|
|
91
|
+
state = "frozen";
|
|
92
|
+
emit("update:state", state);
|
|
93
|
+
},
|
|
94
|
+
unfreeze: () => {
|
|
95
|
+
state = Math.abs(velocity) > restingVelocity || Math.abs(value - target) > restingDistance ? "moving" : "resting";
|
|
96
|
+
emit("update:state", state);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const simulate = (deltaTime) => {
|
|
100
|
+
if (state === "moving") {
|
|
101
|
+
const iterations = Math.ceil(deltaTime);
|
|
102
|
+
for (let n = 0; n < iterations; n++) {
|
|
103
|
+
const previousDelta = value - target;
|
|
104
|
+
const springForce = -config.tension * 1e-6 * previousDelta;
|
|
105
|
+
const dampingForce = -config.friction * 1e-3 * velocity;
|
|
106
|
+
const acceleration = (springForce + dampingForce) / config.mass;
|
|
107
|
+
velocity = velocity + acceleration;
|
|
108
|
+
value = value + velocity;
|
|
109
|
+
const delta = value - target;
|
|
110
|
+
const absDelta = Math.abs(delta);
|
|
111
|
+
if (Math.sign(delta) !== Math.sign(previousDelta)) {
|
|
112
|
+
velocity = arrivalFunction(velocity * 1e3) * 1e-3;
|
|
113
|
+
value = target + velocity * (absDelta / (absDelta + Math.abs(previousDelta)));
|
|
114
|
+
}
|
|
115
|
+
if (absDelta < restingDistance && Math.abs(velocity) < restingVelocity) {
|
|
116
|
+
value = target;
|
|
117
|
+
state = "resting";
|
|
118
|
+
emit("update:state", state);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
emit("update:value", value);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
return [spring, simulate];
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// src/lib/createSpringChain.ts
|
|
129
|
+
var import_mitt2 = __toESM(require("mitt"), 1);
|
|
130
|
+
var createSpringChainImpl = (initial, links, config, options) => {
|
|
131
|
+
const simulateFns = [];
|
|
132
|
+
const springs = [];
|
|
133
|
+
const targets = [];
|
|
134
|
+
const values = [];
|
|
135
|
+
const velocities = [];
|
|
136
|
+
const states = [];
|
|
137
|
+
let currentTarget = initial;
|
|
138
|
+
let currentLinks = links;
|
|
139
|
+
let state = "resting";
|
|
140
|
+
const updateChainState = (target, links2) => {
|
|
141
|
+
if (springs.length === 0) {
|
|
142
|
+
const [spring, simulate2] = createSpringImpl(target, config, options);
|
|
143
|
+
springs.push(spring);
|
|
144
|
+
simulateFns.push(simulate2);
|
|
145
|
+
targets.push(spring.target);
|
|
146
|
+
values.push(spring.value);
|
|
147
|
+
velocities.push(spring.velocity);
|
|
148
|
+
states.push(spring.state);
|
|
149
|
+
} else {
|
|
150
|
+
springs[0].target = target;
|
|
151
|
+
targets[0] = target;
|
|
152
|
+
states[0] = springs[0].state;
|
|
153
|
+
if (state === "resting" && springs[0].state === "moving") {
|
|
154
|
+
state = "moving";
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
for (const [index, link] of links2.entries()) {
|
|
158
|
+
const previousSpring = springs[index];
|
|
159
|
+
if (springs[index + 1]) {
|
|
160
|
+
const spring = springs[index + 1];
|
|
161
|
+
if (typeof link === "number") {
|
|
162
|
+
spring.target = previousSpring.value + link;
|
|
163
|
+
} else {
|
|
164
|
+
spring.target = link(previousSpring.value, previousSpring.target, spring.value, spring.target);
|
|
165
|
+
}
|
|
166
|
+
targets[index + 1] = spring.target;
|
|
167
|
+
states[index + 1] = spring.state;
|
|
168
|
+
if (state === "resting" && spring.state === "moving") {
|
|
169
|
+
state = "moving";
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
const [spring, simulate2] = createSpringImpl(typeof link === "number" ? previousSpring.value + link : link(previousSpring.value, previousSpring.target), config, options);
|
|
173
|
+
springs.push(spring);
|
|
174
|
+
simulateFns.push(simulate2);
|
|
175
|
+
targets[index + 1] = spring.target;
|
|
176
|
+
values[index + 1] = spring.value;
|
|
177
|
+
velocities[index + 1] = spring.velocity;
|
|
178
|
+
states[index + 1] = spring.state;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const cutoff = springs.length + 1;
|
|
182
|
+
if (springs.length > cutoff) {
|
|
183
|
+
const deleteCount = springs.length - cutoff;
|
|
184
|
+
springs.splice(cutoff, deleteCount);
|
|
185
|
+
simulateFns.splice(cutoff, deleteCount);
|
|
186
|
+
targets.splice(cutoff, deleteCount);
|
|
187
|
+
values.splice(cutoff, deleteCount);
|
|
188
|
+
velocities.splice(cutoff, deleteCount);
|
|
189
|
+
states.splice(cutoff, deleteCount);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
updateChainState(currentTarget, currentLinks);
|
|
193
|
+
const emitter = (0, import_mitt2.default)();
|
|
194
|
+
const { emit, ...emitterApi } = emitter;
|
|
195
|
+
const simulate = (delta) => {
|
|
196
|
+
if (state === "moving") {
|
|
197
|
+
let moving = false;
|
|
198
|
+
for (let i = 0; i < springs.length; i++) {
|
|
199
|
+
simulateFns[i](delta);
|
|
200
|
+
if (i < springs.length - 1) {
|
|
201
|
+
const link = links[i];
|
|
202
|
+
springs[i + 1].target = typeof link === "number" ? springs[i].value + link : link(springs[i].value, springs[i].target, springs[i + 1].value);
|
|
203
|
+
}
|
|
204
|
+
targets[i] = springs[i].target;
|
|
205
|
+
values[i] = springs[i].value;
|
|
206
|
+
velocities[i] = springs[i].velocity;
|
|
207
|
+
states[i] = springs[i].state;
|
|
208
|
+
moving = moving || springs[i].state === "moving";
|
|
209
|
+
}
|
|
210
|
+
emit("update:value", values);
|
|
211
|
+
if (!moving) {
|
|
212
|
+
state = "resting";
|
|
213
|
+
emit("update:state", state);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
return [
|
|
218
|
+
{
|
|
219
|
+
...emitterApi,
|
|
220
|
+
get target() {
|
|
221
|
+
return targets[0];
|
|
222
|
+
},
|
|
223
|
+
set target(val) {
|
|
224
|
+
currentTarget = val;
|
|
225
|
+
updateChainState(currentTarget, currentLinks);
|
|
226
|
+
},
|
|
227
|
+
get targets() {
|
|
228
|
+
return targets;
|
|
229
|
+
},
|
|
230
|
+
get links() {
|
|
231
|
+
return links;
|
|
232
|
+
},
|
|
233
|
+
set links(val) {
|
|
234
|
+
currentLinks = val;
|
|
235
|
+
updateChainState(currentTarget, currentLinks);
|
|
236
|
+
},
|
|
237
|
+
get config() {
|
|
238
|
+
return config;
|
|
239
|
+
},
|
|
240
|
+
get values() {
|
|
241
|
+
return values;
|
|
242
|
+
},
|
|
243
|
+
get velocities() {
|
|
244
|
+
return velocities;
|
|
245
|
+
},
|
|
246
|
+
get state() {
|
|
247
|
+
return state;
|
|
248
|
+
},
|
|
249
|
+
get states() {
|
|
250
|
+
return states;
|
|
251
|
+
},
|
|
252
|
+
freeze: () => {
|
|
253
|
+
for (const spring of springs) {
|
|
254
|
+
spring.freeze();
|
|
255
|
+
}
|
|
256
|
+
state = "frozen";
|
|
257
|
+
emit("update:state", state);
|
|
258
|
+
},
|
|
259
|
+
unfreeze: () => {
|
|
260
|
+
let moving = false;
|
|
261
|
+
for (const spring of springs) {
|
|
262
|
+
spring.unfreeze();
|
|
263
|
+
moving = moving || spring.state === "moving";
|
|
264
|
+
}
|
|
265
|
+
state = moving ? "moving" : "resting";
|
|
266
|
+
emit("update:state", state);
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
simulate
|
|
270
|
+
];
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// src/createSpring.ts
|
|
274
|
+
var createSpring = (initial, config, options) => {
|
|
275
|
+
const [spring, simulate] = createSpringImpl(initial, config, options);
|
|
276
|
+
return Object.assign(spring, { simulate });
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// src/createSpringChain.ts
|
|
280
|
+
var createSpringChain = (initial, links, config, options) => {
|
|
281
|
+
const [chain, simulate] = createSpringChainImpl(initial, links, config, options);
|
|
282
|
+
return Object.assign(chain, { simulate });
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// src/createSpringSystem.ts
|
|
286
|
+
var createSpringSystem = () => {
|
|
287
|
+
const springs = /* @__PURE__ */ new Map();
|
|
288
|
+
return {
|
|
289
|
+
createSpring: (initial, config, options) => {
|
|
290
|
+
const [spring, simulate] = createSpringImpl(initial, config, options);
|
|
291
|
+
springs.set(spring, simulate);
|
|
292
|
+
return spring;
|
|
293
|
+
},
|
|
294
|
+
createSpringChain: (initial, links, config, options) => {
|
|
295
|
+
const [chain, simulate] = createSpringChainImpl(initial, links, config, options);
|
|
296
|
+
springs.set(chain, simulate);
|
|
297
|
+
return chain;
|
|
298
|
+
},
|
|
299
|
+
cleanup: (spring) => {
|
|
300
|
+
springs.delete(spring);
|
|
301
|
+
spring.off("*");
|
|
302
|
+
spring.off("update:state");
|
|
303
|
+
spring.off("update:value");
|
|
304
|
+
},
|
|
305
|
+
simulate: (delta) => {
|
|
306
|
+
for (const simulate of springs.values()) {
|
|
307
|
+
simulate(delta);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
};
|
|
312
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
313
|
+
0 && (module.exports = {
|
|
314
|
+
createSpring,
|
|
315
|
+
createSpringChain,
|
|
316
|
+
createSpringSystem
|
|
317
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Emitter } from 'mitt';
|
|
2
|
+
|
|
3
|
+
interface SpringConfig {
|
|
4
|
+
mass: number;
|
|
5
|
+
tension: number;
|
|
6
|
+
friction: number;
|
|
7
|
+
}
|
|
8
|
+
declare type SpringArrivalBehavior = 'bounce' | 'clamp' | 'none' | number | ((velocity: number) => number);
|
|
9
|
+
interface SpringOptions {
|
|
10
|
+
restingDistance?: number;
|
|
11
|
+
restingVelocity?: number;
|
|
12
|
+
arrivalBehavior?: SpringArrivalBehavior;
|
|
13
|
+
}
|
|
14
|
+
declare type SpringState = 'moving' | 'frozen' | 'resting';
|
|
15
|
+
declare type SpringEmitter = Emitter<{
|
|
16
|
+
'update:value': number;
|
|
17
|
+
'update:state': SpringState;
|
|
18
|
+
'*': number | SpringState;
|
|
19
|
+
}>;
|
|
20
|
+
interface Spring extends Omit<SpringEmitter, 'emit'> {
|
|
21
|
+
target: number;
|
|
22
|
+
config: Readonly<SpringConfig>;
|
|
23
|
+
readonly value: number;
|
|
24
|
+
readonly velocity: number;
|
|
25
|
+
readonly state: SpringState;
|
|
26
|
+
readonly freeze: () => void;
|
|
27
|
+
readonly unfreeze: () => void;
|
|
28
|
+
}
|
|
29
|
+
declare type SimulateFn = (delta: number) => void;
|
|
30
|
+
interface StandaloneSpring extends Spring {
|
|
31
|
+
readonly simulate: SimulateFn;
|
|
32
|
+
}
|
|
33
|
+
declare type SpringChainLinkGetter = number | ((previousValue: number, previousTarget: number, currentValue?: number, currentTarget?: number) => number);
|
|
34
|
+
declare type SpringChainEmitter = Emitter<{
|
|
35
|
+
'update:value': number[];
|
|
36
|
+
'update:state': SpringState;
|
|
37
|
+
'*': number[] | SpringState;
|
|
38
|
+
}>;
|
|
39
|
+
interface SpringChain extends Omit<SpringChainEmitter, 'emit'> {
|
|
40
|
+
target: number;
|
|
41
|
+
links: SpringChainLinkGetter[];
|
|
42
|
+
config: Readonly<SpringConfig>;
|
|
43
|
+
readonly targets: ReadonlyArray<number>;
|
|
44
|
+
readonly values: ReadonlyArray<number>;
|
|
45
|
+
readonly velocities: ReadonlyArray<number>;
|
|
46
|
+
readonly state: SpringState;
|
|
47
|
+
readonly states: ReadonlyArray<SpringState>;
|
|
48
|
+
readonly freeze: () => void;
|
|
49
|
+
readonly unfreeze: () => void;
|
|
50
|
+
}
|
|
51
|
+
interface StandaloneSpringChain extends SpringChain {
|
|
52
|
+
readonly simulate: SimulateFn;
|
|
53
|
+
}
|
|
54
|
+
interface SpringSystem {
|
|
55
|
+
createSpring: (initial: number, config: SpringConfig, options?: SpringOptions) => Spring;
|
|
56
|
+
createSpringChain: (initial: number, links: SpringChainLinkGetter[], config: SpringConfig, options?: SpringOptions) => SpringChain;
|
|
57
|
+
cleanup: (spring: Spring | SpringChain) => void;
|
|
58
|
+
simulate: SimulateFn;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
declare const createSpring: (initial: number, config: SpringConfig, options?: SpringOptions) => StandaloneSpring;
|
|
62
|
+
|
|
63
|
+
declare const createSpringChain: (initial: number, links: SpringChainLinkGetter[], config: SpringConfig, options?: SpringOptions) => StandaloneSpringChain;
|
|
64
|
+
|
|
65
|
+
declare const createSpringSystem: () => SpringSystem;
|
|
66
|
+
|
|
67
|
+
export { SimulateFn, Spring, SpringArrivalBehavior, SpringChain, SpringChainLinkGetter, SpringConfig, SpringOptions, SpringState, SpringSystem, StandaloneSpring, StandaloneSpringChain, createSpring, createSpringChain, createSpringSystem };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// src/lib/createSpring.ts
|
|
2
|
+
import mitt from "mitt";
|
|
3
|
+
var getArrivalFunction = (behavior) => {
|
|
4
|
+
let result;
|
|
5
|
+
if (typeof behavior === "function") {
|
|
6
|
+
result = behavior;
|
|
7
|
+
} else {
|
|
8
|
+
let scalar;
|
|
9
|
+
if (typeof behavior === "number") {
|
|
10
|
+
scalar = behavior;
|
|
11
|
+
} else if (behavior === "bounce") {
|
|
12
|
+
scalar = -1;
|
|
13
|
+
} else if (behavior === "clamp") {
|
|
14
|
+
scalar = 0;
|
|
15
|
+
} else if (behavior === "none") {
|
|
16
|
+
scalar = 1;
|
|
17
|
+
}
|
|
18
|
+
result = (velocity) => velocity * scalar;
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
};
|
|
22
|
+
var createSpringImpl = (initial, config, options) => {
|
|
23
|
+
let state = "resting";
|
|
24
|
+
let velocity = 0;
|
|
25
|
+
let target = initial;
|
|
26
|
+
let value = initial;
|
|
27
|
+
const restingDistance = Math.max((options == null ? void 0 : options.restingDistance) ?? 1e-4, 0);
|
|
28
|
+
const restingVelocity = Math.max((options == null ? void 0 : options.restingVelocity) ?? 1e-4, 0);
|
|
29
|
+
const arrivalFunction = getArrivalFunction((options == null ? void 0 : options.arrivalBehavior) ?? 1);
|
|
30
|
+
const emitter = mitt();
|
|
31
|
+
const { emit, ...emitterApi } = emitter;
|
|
32
|
+
const spring = {
|
|
33
|
+
...emitterApi,
|
|
34
|
+
get target() {
|
|
35
|
+
return target;
|
|
36
|
+
},
|
|
37
|
+
set target(val) {
|
|
38
|
+
target = val;
|
|
39
|
+
if (Math.abs(value - target) > restingDistance) {
|
|
40
|
+
state = "moving";
|
|
41
|
+
emit("update:state", state);
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
get value() {
|
|
45
|
+
return value;
|
|
46
|
+
},
|
|
47
|
+
get velocity() {
|
|
48
|
+
return velocity;
|
|
49
|
+
},
|
|
50
|
+
get state() {
|
|
51
|
+
return state;
|
|
52
|
+
},
|
|
53
|
+
get config() {
|
|
54
|
+
return config;
|
|
55
|
+
},
|
|
56
|
+
set config(value2) {
|
|
57
|
+
config = value2;
|
|
58
|
+
},
|
|
59
|
+
freeze: () => {
|
|
60
|
+
state = "frozen";
|
|
61
|
+
emit("update:state", state);
|
|
62
|
+
},
|
|
63
|
+
unfreeze: () => {
|
|
64
|
+
state = Math.abs(velocity) > restingVelocity || Math.abs(value - target) > restingDistance ? "moving" : "resting";
|
|
65
|
+
emit("update:state", state);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const simulate = (deltaTime) => {
|
|
69
|
+
if (state === "moving") {
|
|
70
|
+
const iterations = Math.ceil(deltaTime);
|
|
71
|
+
for (let n = 0; n < iterations; n++) {
|
|
72
|
+
const previousDelta = value - target;
|
|
73
|
+
const springForce = -config.tension * 1e-6 * previousDelta;
|
|
74
|
+
const dampingForce = -config.friction * 1e-3 * velocity;
|
|
75
|
+
const acceleration = (springForce + dampingForce) / config.mass;
|
|
76
|
+
velocity = velocity + acceleration;
|
|
77
|
+
value = value + velocity;
|
|
78
|
+
const delta = value - target;
|
|
79
|
+
const absDelta = Math.abs(delta);
|
|
80
|
+
if (Math.sign(delta) !== Math.sign(previousDelta)) {
|
|
81
|
+
velocity = arrivalFunction(velocity * 1e3) * 1e-3;
|
|
82
|
+
value = target + velocity * (absDelta / (absDelta + Math.abs(previousDelta)));
|
|
83
|
+
}
|
|
84
|
+
if (absDelta < restingDistance && Math.abs(velocity) < restingVelocity) {
|
|
85
|
+
value = target;
|
|
86
|
+
state = "resting";
|
|
87
|
+
emit("update:state", state);
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
emit("update:value", value);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
return [spring, simulate];
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// src/lib/createSpringChain.ts
|
|
98
|
+
import mitt2 from "mitt";
|
|
99
|
+
var createSpringChainImpl = (initial, links, config, options) => {
|
|
100
|
+
const simulateFns = [];
|
|
101
|
+
const springs = [];
|
|
102
|
+
const targets = [];
|
|
103
|
+
const values = [];
|
|
104
|
+
const velocities = [];
|
|
105
|
+
const states = [];
|
|
106
|
+
let currentTarget = initial;
|
|
107
|
+
let currentLinks = links;
|
|
108
|
+
let state = "resting";
|
|
109
|
+
const updateChainState = (target, links2) => {
|
|
110
|
+
if (springs.length === 0) {
|
|
111
|
+
const [spring, simulate2] = createSpringImpl(target, config, options);
|
|
112
|
+
springs.push(spring);
|
|
113
|
+
simulateFns.push(simulate2);
|
|
114
|
+
targets.push(spring.target);
|
|
115
|
+
values.push(spring.value);
|
|
116
|
+
velocities.push(spring.velocity);
|
|
117
|
+
states.push(spring.state);
|
|
118
|
+
} else {
|
|
119
|
+
springs[0].target = target;
|
|
120
|
+
targets[0] = target;
|
|
121
|
+
states[0] = springs[0].state;
|
|
122
|
+
if (state === "resting" && springs[0].state === "moving") {
|
|
123
|
+
state = "moving";
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (const [index, link] of links2.entries()) {
|
|
127
|
+
const previousSpring = springs[index];
|
|
128
|
+
if (springs[index + 1]) {
|
|
129
|
+
const spring = springs[index + 1];
|
|
130
|
+
if (typeof link === "number") {
|
|
131
|
+
spring.target = previousSpring.value + link;
|
|
132
|
+
} else {
|
|
133
|
+
spring.target = link(previousSpring.value, previousSpring.target, spring.value, spring.target);
|
|
134
|
+
}
|
|
135
|
+
targets[index + 1] = spring.target;
|
|
136
|
+
states[index + 1] = spring.state;
|
|
137
|
+
if (state === "resting" && spring.state === "moving") {
|
|
138
|
+
state = "moving";
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
const [spring, simulate2] = createSpringImpl(typeof link === "number" ? previousSpring.value + link : link(previousSpring.value, previousSpring.target), config, options);
|
|
142
|
+
springs.push(spring);
|
|
143
|
+
simulateFns.push(simulate2);
|
|
144
|
+
targets[index + 1] = spring.target;
|
|
145
|
+
values[index + 1] = spring.value;
|
|
146
|
+
velocities[index + 1] = spring.velocity;
|
|
147
|
+
states[index + 1] = spring.state;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const cutoff = springs.length + 1;
|
|
151
|
+
if (springs.length > cutoff) {
|
|
152
|
+
const deleteCount = springs.length - cutoff;
|
|
153
|
+
springs.splice(cutoff, deleteCount);
|
|
154
|
+
simulateFns.splice(cutoff, deleteCount);
|
|
155
|
+
targets.splice(cutoff, deleteCount);
|
|
156
|
+
values.splice(cutoff, deleteCount);
|
|
157
|
+
velocities.splice(cutoff, deleteCount);
|
|
158
|
+
states.splice(cutoff, deleteCount);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
updateChainState(currentTarget, currentLinks);
|
|
162
|
+
const emitter = mitt2();
|
|
163
|
+
const { emit, ...emitterApi } = emitter;
|
|
164
|
+
const simulate = (delta) => {
|
|
165
|
+
if (state === "moving") {
|
|
166
|
+
let moving = false;
|
|
167
|
+
for (let i = 0; i < springs.length; i++) {
|
|
168
|
+
simulateFns[i](delta);
|
|
169
|
+
if (i < springs.length - 1) {
|
|
170
|
+
const link = links[i];
|
|
171
|
+
springs[i + 1].target = typeof link === "number" ? springs[i].value + link : link(springs[i].value, springs[i].target, springs[i + 1].value);
|
|
172
|
+
}
|
|
173
|
+
targets[i] = springs[i].target;
|
|
174
|
+
values[i] = springs[i].value;
|
|
175
|
+
velocities[i] = springs[i].velocity;
|
|
176
|
+
states[i] = springs[i].state;
|
|
177
|
+
moving = moving || springs[i].state === "moving";
|
|
178
|
+
}
|
|
179
|
+
emit("update:value", values);
|
|
180
|
+
if (!moving) {
|
|
181
|
+
state = "resting";
|
|
182
|
+
emit("update:state", state);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
return [
|
|
187
|
+
{
|
|
188
|
+
...emitterApi,
|
|
189
|
+
get target() {
|
|
190
|
+
return targets[0];
|
|
191
|
+
},
|
|
192
|
+
set target(val) {
|
|
193
|
+
currentTarget = val;
|
|
194
|
+
updateChainState(currentTarget, currentLinks);
|
|
195
|
+
},
|
|
196
|
+
get targets() {
|
|
197
|
+
return targets;
|
|
198
|
+
},
|
|
199
|
+
get links() {
|
|
200
|
+
return links;
|
|
201
|
+
},
|
|
202
|
+
set links(val) {
|
|
203
|
+
currentLinks = val;
|
|
204
|
+
updateChainState(currentTarget, currentLinks);
|
|
205
|
+
},
|
|
206
|
+
get config() {
|
|
207
|
+
return config;
|
|
208
|
+
},
|
|
209
|
+
get values() {
|
|
210
|
+
return values;
|
|
211
|
+
},
|
|
212
|
+
get velocities() {
|
|
213
|
+
return velocities;
|
|
214
|
+
},
|
|
215
|
+
get state() {
|
|
216
|
+
return state;
|
|
217
|
+
},
|
|
218
|
+
get states() {
|
|
219
|
+
return states;
|
|
220
|
+
},
|
|
221
|
+
freeze: () => {
|
|
222
|
+
for (const spring of springs) {
|
|
223
|
+
spring.freeze();
|
|
224
|
+
}
|
|
225
|
+
state = "frozen";
|
|
226
|
+
emit("update:state", state);
|
|
227
|
+
},
|
|
228
|
+
unfreeze: () => {
|
|
229
|
+
let moving = false;
|
|
230
|
+
for (const spring of springs) {
|
|
231
|
+
spring.unfreeze();
|
|
232
|
+
moving = moving || spring.state === "moving";
|
|
233
|
+
}
|
|
234
|
+
state = moving ? "moving" : "resting";
|
|
235
|
+
emit("update:state", state);
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
simulate
|
|
239
|
+
];
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// src/createSpring.ts
|
|
243
|
+
var createSpring = (initial, config, options) => {
|
|
244
|
+
const [spring, simulate] = createSpringImpl(initial, config, options);
|
|
245
|
+
return Object.assign(spring, { simulate });
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// src/createSpringChain.ts
|
|
249
|
+
var createSpringChain = (initial, links, config, options) => {
|
|
250
|
+
const [chain, simulate] = createSpringChainImpl(initial, links, config, options);
|
|
251
|
+
return Object.assign(chain, { simulate });
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// src/createSpringSystem.ts
|
|
255
|
+
var createSpringSystem = () => {
|
|
256
|
+
const springs = /* @__PURE__ */ new Map();
|
|
257
|
+
return {
|
|
258
|
+
createSpring: (initial, config, options) => {
|
|
259
|
+
const [spring, simulate] = createSpringImpl(initial, config, options);
|
|
260
|
+
springs.set(spring, simulate);
|
|
261
|
+
return spring;
|
|
262
|
+
},
|
|
263
|
+
createSpringChain: (initial, links, config, options) => {
|
|
264
|
+
const [chain, simulate] = createSpringChainImpl(initial, links, config, options);
|
|
265
|
+
springs.set(chain, simulate);
|
|
266
|
+
return chain;
|
|
267
|
+
},
|
|
268
|
+
cleanup: (spring) => {
|
|
269
|
+
springs.delete(spring);
|
|
270
|
+
spring.off("*");
|
|
271
|
+
spring.off("update:state");
|
|
272
|
+
spring.off("update:value");
|
|
273
|
+
},
|
|
274
|
+
simulate: (delta) => {
|
|
275
|
+
for (const simulate of springs.values()) {
|
|
276
|
+
simulate(delta);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
};
|
|
281
|
+
export {
|
|
282
|
+
createSpring,
|
|
283
|
+
createSpringChain,
|
|
284
|
+
createSpringSystem
|
|
285
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "coily",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Simulate values using spring physics",
|
|
5
|
+
"author": "Tim Morris <timthewebguy@gmail.com>",
|
|
6
|
+
"homepage": "https://github.com/tkofh/coily#readme",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "./dist/index.cjs",
|
|
11
|
+
"module": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"require": "./dist/index.cjs",
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/tkofh/coily.git"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsup",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:watch": "vitest watch",
|
|
34
|
+
"test:ui": "vitest --ui",
|
|
35
|
+
"prepublish": "yarn test && yarn build"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/tkofh/coily/issues"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@vitest/ui": "^0.11.0",
|
|
42
|
+
"tsup": "^6.1.3",
|
|
43
|
+
"vitest": "^0.16.0"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"mitt": "^3.0.0"
|
|
47
|
+
},
|
|
48
|
+
"gitHead": "ed6733ca1768b44341f0ed05f8750466089ddbc1"
|
|
49
|
+
}
|