tensum 0.1.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/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/README.md +340 -0
- package/dist/coupled-system.d.ts +50 -0
- package/dist/coupled-system.d.ts.map +1 -0
- package/dist/coupled-system.js +269 -0
- package/dist/coupled-system.js.map +1 -0
- package/dist/coupled.d.ts +3 -0
- package/dist/coupled.d.ts.map +1 -0
- package/dist/coupled.js +2 -0
- package/dist/coupled.js.map +1 -0
- package/dist/css-linear.d.ts +20 -0
- package/dist/css-linear.d.ts.map +1 -0
- package/dist/css-linear.js +95 -0
- package/dist/css-linear.js.map +1 -0
- package/dist/css.d.ts +3 -0
- package/dist/css.d.ts.map +1 -0
- package/dist/css.js +2 -0
- package/dist/css.js.map +1 -0
- package/dist/gsap/active-tracks.d.ts +32 -0
- package/dist/gsap/active-tracks.d.ts.map +1 -0
- package/dist/gsap/active-tracks.js +177 -0
- package/dist/gsap/active-tracks.js.map +1 -0
- package/dist/gsap/gsap-time.d.ts +10 -0
- package/dist/gsap/gsap-time.d.ts.map +1 -0
- package/dist/gsap/gsap-time.js +62 -0
- package/dist/gsap/gsap-time.js.map +1 -0
- package/dist/gsap/plugin-tween-coordinator.d.ts +30 -0
- package/dist/gsap/plugin-tween-coordinator.d.ts.map +1 -0
- package/dist/gsap/plugin-tween-coordinator.js +72 -0
- package/dist/gsap/plugin-tween-coordinator.js.map +1 -0
- package/dist/gsap/plugin.d.ts +84 -0
- package/dist/gsap/plugin.d.ts.map +1 -0
- package/dist/gsap/plugin.js +552 -0
- package/dist/gsap/plugin.js.map +1 -0
- package/dist/gsap/spring-to.d.ts +93 -0
- package/dist/gsap/spring-to.d.ts.map +1 -0
- package/dist/gsap/spring-to.js +569 -0
- package/dist/gsap/spring-to.js.map +1 -0
- package/dist/gsap/spring-track-policy.d.ts +24 -0
- package/dist/gsap/spring-track-policy.d.ts.map +1 -0
- package/dist/gsap/spring-track-policy.js +76 -0
- package/dist/gsap/spring-track-policy.js.map +1 -0
- package/dist/gsap/track-lifecycle.d.ts +16 -0
- package/dist/gsap/track-lifecycle.d.ts.map +1 -0
- package/dist/gsap/track-lifecycle.js +33 -0
- package/dist/gsap/track-lifecycle.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/math.d.ts +9 -0
- package/dist/math.d.ts.map +1 -0
- package/dist/math.js +38 -0
- package/dist/math.js.map +1 -0
- package/dist/model.d.ts +14 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +38 -0
- package/dist/model.js.map +1 -0
- package/dist/motion-compatibility.d.ts +18 -0
- package/dist/motion-compatibility.d.ts.map +1 -0
- package/dist/motion-compatibility.js +83 -0
- package/dist/motion-compatibility.js.map +1 -0
- package/dist/parameters.d.ts +52 -0
- package/dist/parameters.d.ts.map +1 -0
- package/dist/parameters.js +124 -0
- package/dist/parameters.js.map +1 -0
- package/dist/settling.d.ts +12 -0
- package/dist/settling.d.ts.map +1 -0
- package/dist/settling.js +107 -0
- package/dist/settling.js.map +1 -0
- package/dist/solver.d.ts +17 -0
- package/dist/solver.d.ts.map +1 -0
- package/dist/solver.js +190 -0
- package/dist/solver.js.map +1 -0
- package/dist/spring.d.ts +9 -0
- package/dist/spring.d.ts.map +1 -0
- package/dist/spring.js +91 -0
- package/dist/spring.js.map +1 -0
- package/dist/support/additive-spring-value.d.ts +39 -0
- package/dist/support/additive-spring-value.d.ts.map +1 -0
- package/dist/support/additive-spring-value.js +281 -0
- package/dist/support/additive-spring-value.js.map +1 -0
- package/dist/support/inertia.d.ts +34 -0
- package/dist/support/inertia.d.ts.map +1 -0
- package/dist/support/inertia.js +293 -0
- package/dist/support/inertia.js.map +1 -0
- package/dist/support/keyframes.d.ts +34 -0
- package/dist/support/keyframes.d.ts.map +1 -0
- package/dist/support/keyframes.js +117 -0
- package/dist/support/keyframes.js.map +1 -0
- package/dist/support/spring-value.d.ts +38 -0
- package/dist/support/spring-value.d.ts.map +1 -0
- package/dist/support/spring-value.js +289 -0
- package/dist/support/spring-value.js.map +1 -0
- package/dist/support/velocity.d.ts +14 -0
- package/dist/support/velocity.d.ts.map +1 -0
- package/dist/support/velocity.js +75 -0
- package/dist/support/velocity.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/vector.d.ts +32 -0
- package/dist/vector.d.ts.map +1 -0
- package/dist/vector.js +121 -0
- package/dist/vector.js.map +1 -0
- package/package.json +68 -0
- package/src/coupled-system.ts +383 -0
- package/src/coupled.ts +10 -0
- package/src/css-linear.ts +148 -0
- package/src/css.ts +6 -0
- package/src/gsap/active-tracks.ts +244 -0
- package/src/gsap/gsap-time.ts +91 -0
- package/src/gsap/plugin-tween-coordinator.ts +112 -0
- package/src/gsap/plugin.ts +861 -0
- package/src/gsap/spring-to.ts +836 -0
- package/src/gsap/spring-track-policy.ts +133 -0
- package/src/gsap/track-lifecycle.ts +53 -0
- package/src/index.ts +130 -0
- package/src/math.ts +46 -0
- package/src/model.ts +70 -0
- package/src/motion-compatibility.ts +131 -0
- package/src/parameters.ts +226 -0
- package/src/settling.ts +141 -0
- package/src/solver.ts +262 -0
- package/src/spring.ts +119 -0
- package/src/support/additive-spring-value.ts +343 -0
- package/src/support/inertia.ts +367 -0
- package/src/support/keyframes.ts +188 -0
- package/src/support/spring-value.ts +399 -0
- package/src/support/velocity.ts +119 -0
- package/src/types.ts +70 -0
- package/src/vector.ts +191 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-08-26
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Analytical solutions for underdamped, critically damped, and overdamped
|
|
15
|
+
springs, with position, velocity, state, and retargeting APIs.
|
|
16
|
+
- Settlement detection with configurable position and velocity tolerances,
|
|
17
|
+
maximum duration, and refinement precision.
|
|
18
|
+
- Physical and perceptual parameter converters, spring presets, damping
|
|
19
|
+
classification, and spring characteristic inspection.
|
|
20
|
+
- GSAP timeline composition through `timeline.motionSpring()` and
|
|
21
|
+
`createMotionSpringTween()`, with solver-derived duration available before
|
|
22
|
+
timeline layout.
|
|
23
|
+
- Direct animation control through `springTo()`, including pause, resume, seek,
|
|
24
|
+
reverse playback, snapshots, and property-level cancellation.
|
|
25
|
+
- Position and velocity handoff between overlapping Tensum tracks, including
|
|
26
|
+
terminal-state retention and external-write detection.
|
|
27
|
+
- Multi-property and multi-target animation with unit validation, custom
|
|
28
|
+
property adapters, stagger, repeat, repeat delay, and yoyo playback.
|
|
29
|
+
- Lifecycle callbacks for logical completion, physical settlement, and
|
|
30
|
+
unsettled springs.
|
|
31
|
+
- `stop`, `snap`, `continue`, and `error` policies for springs that do not settle
|
|
32
|
+
within their configured maximum duration.
|
|
33
|
+
- Vector springs, reactive spring values, additive composition, analytical
|
|
34
|
+
keyframes, inertia, velocity helpers, and snapping utilities.
|
|
35
|
+
- CSS `linear()` generation through `tensum/css` and coupled spring systems
|
|
36
|
+
through `tensum/coupled`.
|
|
37
|
+
- ESM package exports with TypeScript declarations, declaration maps, source
|
|
38
|
+
maps, and included TypeScript sources.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Motion Core
|
|
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,340 @@
|
|
|
1
|
+
# `tensum`
|
|
2
|
+
|
|
3
|
+
Analytical spring physics for GSAP timelines and direct controllers.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add tensum gsap
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
GSAP is a peer dependency. The application and the plugin therefore use the
|
|
12
|
+
same GSAP instance.
|
|
13
|
+
|
|
14
|
+
The package is ESM-only:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { registerSpringPlugin, springTo } from "tensum";
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
CommonJS `require()` is not supported. A CommonJS module can use dynamic
|
|
21
|
+
`import('tensum')` when migration to ESM is not yet practical.
|
|
22
|
+
|
|
23
|
+
## Register the timeline integration
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { gsap } from "gsap";
|
|
27
|
+
import { registerSpringPlugin } from "tensum";
|
|
28
|
+
|
|
29
|
+
registerSpringPlugin(gsap);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
GSAP owns the clock, timeline lifecycle, and context. During each driver render,
|
|
33
|
+
Tensum calculates position and velocity from absolute elapsed time and writes
|
|
34
|
+
the sampled values through GSAP property setters. Seeking or sampling at a
|
|
35
|
+
different frame rate does not change the analytical trajectory.
|
|
36
|
+
|
|
37
|
+
## Compose derived-duration timelines
|
|
38
|
+
|
|
39
|
+
Use `timeline.motionSpring()` when spring duration affects the position or
|
|
40
|
+
duration of other timeline children. It is a registered GSAP effect with
|
|
41
|
+
`extendTimeline: true`. The effect resolves every spring track before returning
|
|
42
|
+
its tween, so GSAP receives the final duration before it lays out the timeline.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
const timeline = gsap.timeline();
|
|
46
|
+
|
|
47
|
+
timeline
|
|
48
|
+
.motionSpring(element, {
|
|
49
|
+
x: 320,
|
|
50
|
+
from: { x: 0 },
|
|
51
|
+
parameters: { mass: 1, stiffness: 180, damping: 24 },
|
|
52
|
+
})
|
|
53
|
+
.motionSpring(element, {
|
|
54
|
+
x: 80,
|
|
55
|
+
from: { x: 320 },
|
|
56
|
+
parameters: { mass: 1, stiffness: 240, damping: 26 },
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The second tween starts when the first tween's derived duration ends. No GSAP
|
|
61
|
+
`duration` is required.
|
|
62
|
+
|
|
63
|
+
### Starting state and velocity
|
|
64
|
+
|
|
65
|
+
Preflight reads each target when the effect is created. Add `from` when an
|
|
66
|
+
earlier timeline child will change that target before the spring starts:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
timeline.motionSpring(element, {
|
|
70
|
+
x: 80,
|
|
71
|
+
from: { x: 320 },
|
|
72
|
+
velocity: { x: -240 },
|
|
73
|
+
parameters: { mass: 1, stiffness: 240, damping: 26 },
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`from` accepts the same numeric values and single-unit strings as spring
|
|
78
|
+
destinations. It is one property map applied to every resolved target, so a
|
|
79
|
+
tween can snapshot different starting values for `x`, `rotation`, or custom
|
|
80
|
+
numeric properties. If `from` is omitted, preflight reads each target at effect
|
|
81
|
+
construction time and inherits any currently active Tensum track.
|
|
82
|
+
|
|
83
|
+
This distinction matters for timelines built before playback. A state created by
|
|
84
|
+
an earlier child does not exist yet, so the effect cannot read it automatically.
|
|
85
|
+
Pass both `from` and `velocity` when that future state must be exact.
|
|
86
|
+
|
|
87
|
+
### GSAP tween options
|
|
88
|
+
|
|
89
|
+
Put GSAP driver options in `tween`:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
const entrance = gsap.timeline().motionSpring(cards, {
|
|
93
|
+
y: 0,
|
|
94
|
+
from: { y: -24 },
|
|
95
|
+
parameters: { mass: 1, stiffness: 180, damping: 24 },
|
|
96
|
+
tween: {
|
|
97
|
+
stagger: 0.06,
|
|
98
|
+
repeat: 1,
|
|
99
|
+
yoyo: true,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
masterTimeline.add(entrance, 0.4);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The effect supports array targets, stagger, and nested timelines. Finite springs
|
|
107
|
+
also support native repeat, repeat delay, and yoyo playback. It owns `duration`
|
|
108
|
+
and `ease`; the helper discards those keys if they are present in `tween`.
|
|
109
|
+
|
|
110
|
+
An unsettled spring using `continue` has different timing. The effect sets a
|
|
111
|
+
one-second GSAP driver with `repeat: -1` and samples the spring from `totalTime`,
|
|
112
|
+
so the analytical trajectory keeps moving forward across driver cycles. Do not
|
|
113
|
+
use `repeat` or `yoyo` to choreograph a `continue` spring. Kill its property or
|
|
114
|
+
its tween to end the infinite driver; covering it with a newer track only changes
|
|
115
|
+
which track writes the property.
|
|
116
|
+
|
|
117
|
+
Use `createMotionSpringTween()` when code needs a preflighted tween without the
|
|
118
|
+
extended timeline method:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { createMotionSpringTween } from "tensum";
|
|
122
|
+
|
|
123
|
+
const tween = createMotionSpringTween(element, {
|
|
124
|
+
x: 320,
|
|
125
|
+
from: { x: 0 },
|
|
126
|
+
parameters: { mass: 1, stiffness: 180, damping: 24 },
|
|
127
|
+
tween: { paused: true },
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
timeline.add(tween, 0.5);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## GSAP lifecycle
|
|
134
|
+
|
|
135
|
+
Overlapping effects on the same target and property share a registry, and the
|
|
136
|
+
newer track becomes the only writer.
|
|
137
|
+
|
|
138
|
+
Pause, seek, reverse, and `timeScale` use GSAP's clock while Spring samples the
|
|
139
|
+
corresponding absolute or cycle-local time. Seeking directly across a handoff
|
|
140
|
+
produces the same state as playing through it.
|
|
141
|
+
|
|
142
|
+
### Kill, invalidate, repeat, and context
|
|
143
|
+
|
|
144
|
+
Property-level kill removes only the matching spring track. Killing the final
|
|
145
|
+
track returns control of that PropTween to GSAP. If a killed track was the
|
|
146
|
+
reason an unsettled `continue` tween was infinite, the driver recomputes its
|
|
147
|
+
duration from the remaining tracks and restores the repeat count supplied by
|
|
148
|
+
the application.
|
|
149
|
+
|
|
150
|
+
Calling `invalidate()` reuses the snapshot and duration captured at
|
|
151
|
+
construction. Create a new effect tween when `from`, destinations, or spring
|
|
152
|
+
parameters must be preflighted again.
|
|
153
|
+
|
|
154
|
+
Finite repeats use cycle-local time for handoff. During a yoyo cycle, the
|
|
155
|
+
inherited velocity changes sign with the playback direction. Kill, interrupt,
|
|
156
|
+
and context revert release active ownership; a killed newer track restores the
|
|
157
|
+
previous live track when one still exists.
|
|
158
|
+
|
|
159
|
+
### Terminal handoff and external writes
|
|
160
|
+
|
|
161
|
+
A normally completed owner remains as a terminal handoff baseline. Older tracks
|
|
162
|
+
that it covered are discarded, so they cannot reclaim the property during a
|
|
163
|
+
later forward tick. A settled spring stores the target with zero velocity.
|
|
164
|
+
`snap` does the same. An unsettled `stop` spring stores its analytical position
|
|
165
|
+
and velocity at `maxDuration`, even though its visible value stops changing.
|
|
166
|
+
The next Tensum track can therefore continue from that capped state.
|
|
167
|
+
|
|
168
|
+
Before an implicit handoff inherits a terminal baseline, Tensum compares it
|
|
169
|
+
with the value currently on the target. If application code wrote a different
|
|
170
|
+
value after completion, Tensum discards the terminal history and starts
|
|
171
|
+
from the external value with the configured velocity, or zero by default. This
|
|
172
|
+
reconciliation applies when `from` is omitted. An explicit `from` is the effect's
|
|
173
|
+
requested construction snapshot.
|
|
174
|
+
|
|
175
|
+
## Controller API
|
|
176
|
+
|
|
177
|
+
Use `springTo()` when code needs direct playback controls rather than a GSAP
|
|
178
|
+
timeline child:
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
import { springTo } from "tensum";
|
|
182
|
+
|
|
183
|
+
const animation = springTo(element, {
|
|
184
|
+
x: 600,
|
|
185
|
+
rotation: "30deg",
|
|
186
|
+
velocity: { x: 1250 },
|
|
187
|
+
spring: {
|
|
188
|
+
mass: 1,
|
|
189
|
+
stiffness: 180,
|
|
190
|
+
damping: 24,
|
|
191
|
+
settle: { position: 0.1, velocity: 0.1 },
|
|
192
|
+
},
|
|
193
|
+
properties: {
|
|
194
|
+
rotation: { damping: 30, velocity: 90 },
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
animation.pause();
|
|
199
|
+
animation.seek(0.2);
|
|
200
|
+
animation.resume();
|
|
201
|
+
animation.playbackReverse();
|
|
202
|
+
animation.kill();
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The built-in transform properties are `x`, `y`, `scale`, and `rotation`. Put
|
|
206
|
+
other numeric GSAP properties in `targets`:
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
const opacitySpring = springTo(element, {
|
|
210
|
+
targets: { opacity: 1, "--reveal": "100%" },
|
|
211
|
+
spring: { mass: 1, stiffness: 180, damping: 24 },
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Numeric strings may contain one unit, such as `24px`, `30deg`, or `100%`. A unit
|
|
216
|
+
mismatch throws before the animation starts. Use `adapters` when a property
|
|
217
|
+
needs custom read and write behavior.
|
|
218
|
+
|
|
219
|
+
Starting another `springTo()` animation or constructing a `motionSpring` effect
|
|
220
|
+
on the same target and property performs an automatic velocity-preserving
|
|
221
|
+
handoff from the state available at construction time.
|
|
222
|
+
|
|
223
|
+
## Parameters
|
|
224
|
+
|
|
225
|
+
Pass physical parameters directly:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
const parameters = {
|
|
229
|
+
mass: 1,
|
|
230
|
+
stiffness: 180,
|
|
231
|
+
damping: 24,
|
|
232
|
+
};
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Or derive them from controls that are easier to tune:
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
import { springParameters, springPresets } from "tensum";
|
|
239
|
+
|
|
240
|
+
const tuned = springParameters.fromPerceptualDuration({
|
|
241
|
+
duration: 0.5,
|
|
242
|
+
bounce: 0.15,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const snappy = springPresets.snappy();
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Time values are seconds. Velocity is measured in property units per second.
|
|
249
|
+
The solver itself is unit-agnostic.
|
|
250
|
+
|
|
251
|
+
## Completion and settlement
|
|
252
|
+
|
|
253
|
+
A damped spring approaches equilibrium without reaching it mathematically. The
|
|
254
|
+
runtime considers it settled when both the remaining distance and velocity stay
|
|
255
|
+
inside their configured tolerances.
|
|
256
|
+
|
|
257
|
+
The callbacks describe separate boundaries:
|
|
258
|
+
|
|
259
|
+
- `onLogicalComplete` fires at the requested perceptual duration or at an
|
|
260
|
+
earlier finite driver boundary. An unsettled `continue` track is not clamped;
|
|
261
|
+
- `onSettle` fires at physical settlement;
|
|
262
|
+
- `onUnsettled` fires when `maxDuration` is reached without settlement;
|
|
263
|
+
- `springTo()` also accepts `onUpdate` and `onComplete`.
|
|
264
|
+
|
|
265
|
+
The three effect lifecycle callbacks are target-scoped. A tween with an array
|
|
266
|
+
of targets invokes each callback once per target. Its `SpringToSnapshot`
|
|
267
|
+
contains the states for that target but does not include a target or index. Use
|
|
268
|
+
one tween per target when the callback must identify its source.
|
|
269
|
+
|
|
270
|
+
An undamped moving spring cannot settle. Choose an `unsettled` policy when that
|
|
271
|
+
case is possible:
|
|
272
|
+
|
|
273
|
+
- `stop` is the default. It freezes at the analytical state sampled at
|
|
274
|
+
`maxDuration` and retains that state's velocity for a later handoff;
|
|
275
|
+
- `snap` writes the target with zero velocity at `maxDuration`;
|
|
276
|
+
- `continue` uses an infinite GSAP driver and keeps sampling analytical
|
|
277
|
+
`totalTime` until the track is killed;
|
|
278
|
+
- `error` rejects the unsettled track during initialization.
|
|
279
|
+
|
|
280
|
+
`onSettle` does not run for `stop`, `snap`, or `continue` when the settling
|
|
281
|
+
solver reports an unsettled result. `onUnsettled` runs once per forward crossing
|
|
282
|
+
of `maxDuration`; a finite repeat or yoyo may create another crossing.
|
|
283
|
+
|
|
284
|
+
## Other exports
|
|
285
|
+
|
|
286
|
+
The root entry point also exports the analytical solver, parameter converters,
|
|
287
|
+
velocity helpers, spring values, keyframes, inertia, additive composition, and
|
|
288
|
+
vector springs.
|
|
289
|
+
|
|
290
|
+
CSS `linear()` generation and coupled systems use explicit subpaths:
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
import { springToCSSLinear } from "tensum/css";
|
|
294
|
+
import { createCoupledSpringSystem } from "tensum/coupled";
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Compatibility
|
|
298
|
+
|
|
299
|
+
- Runtime: Node.js 20.19.x or Node.js 22.12 and newer, plus browser
|
|
300
|
+
environments supported by the
|
|
301
|
+
installed GSAP version.
|
|
302
|
+
- Peer dependency: GSAP `^3.15.0`.
|
|
303
|
+
- Modules: ESM only; CommonJS `require()` is intentionally absent from the
|
|
304
|
+
export map.
|
|
305
|
+
- TypeScript: use `moduleResolution: "node16"`, `"nodenext"`, or `"bundler"`.
|
|
306
|
+
Classic `moduleResolution: "node"` cannot resolve the `css` and `coupled`
|
|
307
|
+
subpaths.
|
|
308
|
+
|
|
309
|
+
Compiled JavaScript, declarations, source maps, declaration maps, and the
|
|
310
|
+
corresponding TypeScript source are included in the package.
|
|
311
|
+
|
|
312
|
+
## Verify a release locally
|
|
313
|
+
|
|
314
|
+
From the repository root:
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
pnpm release:check
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
This builds and tests the workspaces, runs lint and type checks, validates the
|
|
321
|
+
manifest, then installs the packed tarball in a temporary consumer. The
|
|
322
|
+
consumer checks all three entry points at runtime and under TypeScript Node16,
|
|
323
|
+
NodeNext, and Bundler resolution. It also verifies the ESM-only contract. The
|
|
324
|
+
command does not publish or tag anything.
|
|
325
|
+
|
|
326
|
+
Run the optional GSAP compatibility matrix before changing the peer range or
|
|
327
|
+
cutting a release:
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
pnpm release:compat
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
It installs the exact peer lower bound and the current npm `latest` tag in
|
|
334
|
+
separate temporary consumers, then checks Node ESM, TypeScript, effect
|
|
335
|
+
registration, `springTo()`, and `timeline.motionSpring()`. This command may read
|
|
336
|
+
the npm registry, so it is intentionally separate from `release:check`.
|
|
337
|
+
|
|
338
|
+
## License
|
|
339
|
+
|
|
340
|
+
MIT, copyright 2026 Motion Core. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface CoupledAnchor {
|
|
2
|
+
target: number;
|
|
3
|
+
stiffness: number;
|
|
4
|
+
damping: number;
|
|
5
|
+
}
|
|
6
|
+
export interface CoupledParticle {
|
|
7
|
+
mass: number;
|
|
8
|
+
position: number;
|
|
9
|
+
velocity?: number;
|
|
10
|
+
fixed?: boolean;
|
|
11
|
+
anchor?: CoupledAnchor;
|
|
12
|
+
min?: number;
|
|
13
|
+
max?: number;
|
|
14
|
+
restitution?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface SpringConnection {
|
|
17
|
+
from: number;
|
|
18
|
+
to: number;
|
|
19
|
+
stiffness: number;
|
|
20
|
+
damping: number;
|
|
21
|
+
restOffset?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface CoupledSpringOptions {
|
|
24
|
+
particles: readonly CoupledParticle[];
|
|
25
|
+
connections: readonly SpringConnection[];
|
|
26
|
+
maxStep?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface CoupledSpringState {
|
|
29
|
+
position: readonly number[];
|
|
30
|
+
velocity: readonly number[];
|
|
31
|
+
}
|
|
32
|
+
export interface MutableCoupledSpringState {
|
|
33
|
+
position: number[];
|
|
34
|
+
velocity: number[];
|
|
35
|
+
}
|
|
36
|
+
export interface CoupledSpringSystem {
|
|
37
|
+
readonly dimension: number;
|
|
38
|
+
readonly particles: readonly Readonly<CoupledParticle>[];
|
|
39
|
+
readonly connections: readonly Readonly<SpringConnection>[];
|
|
40
|
+
readonly initialState: Readonly<CoupledSpringState>;
|
|
41
|
+
readonly maxStep: number;
|
|
42
|
+
forceAt(state: CoupledSpringState): readonly number[];
|
|
43
|
+
accelerationAt(state: CoupledSpringState): readonly number[];
|
|
44
|
+
advance(state: CoupledSpringState, deltaTime: number): CoupledSpringState;
|
|
45
|
+
advanceInto(state: CoupledSpringState, deltaTime: number, output: MutableCoupledSpringState): MutableCoupledSpringState;
|
|
46
|
+
stateAt(time: number): CoupledSpringState;
|
|
47
|
+
energyAt(state: CoupledSpringState): number;
|
|
48
|
+
}
|
|
49
|
+
export declare function createCoupledSpringSystem(options: CoupledSpringOptions): CoupledSpringSystem;
|
|
50
|
+
//# sourceMappingURL=coupled-system.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coupled-system.d.ts","sourceRoot":"","sources":["../src/coupled-system.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,SAAS,eAAe,EAAE,CAAC;IACtC,WAAW,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;IACzD,QAAQ,CAAC,WAAW,EAAE,SAAS,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;IAC5D,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,KAAK,EAAE,kBAAkB,GAAG,SAAS,MAAM,EAAE,CAAC;IACtD,cAAc,CAAC,KAAK,EAAE,kBAAkB,GAAG,SAAS,MAAM,EAAE,CAAC;IAC7D,OAAO,CAAC,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,GAAG,kBAAkB,CAAC;IAC1E,WAAW,CACT,KAAK,EAAE,kBAAkB,EACzB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,yBAAyB,GAChC,yBAAyB,CAAC;IAC7B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,CAAC;IAC1C,QAAQ,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,CAAC;CAC7C;AAoBD,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,oBAAoB,GAC5B,mBAAmB,CA+SrB"}
|