coily 0.13.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,940 @@
1
+ import { t as SpringDefinition } from "./config-OOMMRdK8.js";
2
+ //#region src/flush-queue.d.ts
3
+ /**
4
+ * Defers callbacks while inside a batch or tick: `request` runs the
5
+ * callback immediately at depth 0 and queues it — deduplicated — until
6
+ * the outermost exit otherwise. Composite springs use it to coalesce
7
+ * per-channel events into composite ones.
8
+ */
9
+ declare class FlushQueue {
10
+ #private;
11
+ request(callback: () => void): void;
12
+ batch(fn: () => void): void;
13
+ enter(): void;
14
+ exit(): void;
15
+ }
16
+ //#endregion
17
+ //#region src/motion.d.ts
18
+ /**
19
+ * One spring simulation in displacement space: position is measured from
20
+ * the target (0 means settled), so retargeting is a rebase performed by
21
+ * the owning `Spring`. Picks the closed-form solver for the config's
22
+ * damping regime and emits update/start/stop/dispose events.
23
+ */
24
+ declare class Motion {
25
+ #private;
26
+ /** Monotone creation id: breaks ties wherever motions need a deterministic order. */
27
+ readonly _id: number;
28
+ /** Tick-pass marker written by `MotionSet` so a motion re-added mid-pass isn't ticked twice. */
29
+ _pass: number;
30
+ /**
31
+ * Frame marker written by `MotionSet`: sub-steps share one frame, and
32
+ * the first advance in it queues the motion for the settle sweep.
33
+ */
34
+ _frame: number;
35
+ /**
36
+ * Dependency rank written by `FollowGraph`: motions the follow graph
37
+ * touches count up from 0, leaders before followers; -1 marks a motion
38
+ * outside the graph.
39
+ */
40
+ _rank: number;
41
+ /**
42
+ * The edge this motion follows through, or null. Written by
43
+ * `FollowGraph` on edge add/remove so the tick's hot loop reads a
44
+ * field instead of a map.
45
+ */
46
+ _edge: FollowEdge | null;
47
+ /**
48
+ * A first-order-hold ramp slope in value units per second, armed by a
49
+ * ramped `recouple` immediately before the `_advance` that consumes
50
+ * it (read-and-clear; no user code runs between the two). 0 means the
51
+ * target holds still across the step — every path but a ramped
52
+ * recouple.
53
+ */
54
+ _ramp: number;
55
+ /**
56
+ * The edges reading this motion, or null when none. Written by
57
+ * `FollowGraph` with the rank cache, so it can lag a mid-pass edge
58
+ * change until the next recompute; the tick tolerates stale entries.
59
+ */
60
+ _followers: FollowEdge[] | null;
61
+ respectsReducedMotion: boolean;
62
+ constructor(config: SpringDefinition, position: number, velocity: number);
63
+ get position(): number;
64
+ set position(value: number);
65
+ get velocity(): number;
66
+ set velocity(value: number);
67
+ get timeRemaining(): number;
68
+ get isResting(): boolean;
69
+ configure(config: SpringDefinition): void;
70
+ tick(dt: number, emit?: boolean): void;
71
+ /**
72
+ * Advances the simulation and emits nothing. The tick pass advances
73
+ * every motion through this, then delivers the frame's events with
74
+ * `_settleFrame`; the synchronous `tick` wraps the two.
75
+ *
76
+ * A pending `_ramp` integrates the step against a target moving at
77
+ * that slope instead of holding still: one affine transform around
78
+ * the unchanged solver. With the target at `p(t) = p0 + g*t`, the
79
+ * displacement u = x - p(t) obeys the homogeneous equation shifted by
80
+ * the constant u_ss = -(2*zeta/wn)*g = -(damping/tension)*g, so the
81
+ * solver ticks w = u - u_ss exactly and the results shift back.
82
+ * Measuring u from the moving target makes the ramp endpoint and the
83
+ * new target coincide — the armer assigns the target, no rebase
84
+ * arithmetic exists. The ramp is an argument of the step, never
85
+ * persistent solver state: g = 0 takes the plain step below, so a
86
+ * motion that never ramps steps bit-exactly like an isolated spring.
87
+ */
88
+ _advance(dt: number): void;
89
+ /**
90
+ * Emits the frame's `update`, then reconciles `stop`/`start`. The tick
91
+ * pass calls this in dependency order after every motion has advanced,
92
+ * so update callbacks read frame-final values everywhere.
93
+ */
94
+ _settleFrame(): void;
95
+ finish(): void;
96
+ /**
97
+ * The sub-step controller's shock signal, read by a follower's `plan`
98
+ * with this motion as its leader: deviation from the quasi-steady
99
+ * tracking manifold x = -(damping/tension) * v, where damping/tension
100
+ * is 2*zeta/wn. Near zero while chasing any smoothly moving target at
101
+ * any speed; the full displacement after a target teleport;
102
+ * (damping/tension) * |v| after a fling.
103
+ */
104
+ _manifoldDeviation(): number;
105
+ /** Acceleration from current state, for `plan`'s kinematic bound on a shock frame. */
106
+ _acceleration(): number;
107
+ onUpdate(callback: () => void): () => void;
108
+ onStart(callback: () => void): () => void;
109
+ onStop(callback: () => void): () => void;
110
+ onDispose(callback: () => void): () => void;
111
+ dispose(): void;
112
+ }
113
+ //#endregion
114
+ //#region src/follow-graph.d.ts
115
+ /**
116
+ * One follow relationship, as the system sees it: which motion
117
+ * retargets, which motions it reads, how to re-anchor it, and the
118
+ * sub-step controller's per-edge state. `Spring` registers an edge when
119
+ * a follow is wired to a source with resolvable motions, and
120
+ * unregisters it on unfollow and dispose.
121
+ */
122
+ interface FollowEdge {
123
+ /** The motion that retargets when its leaders advance. */
124
+ readonly follower: Motion;
125
+ /** The distinct motions behind the followed source, per `resolveLeaderMotions`. */
126
+ readonly leaders: readonly Motion[];
127
+ /**
128
+ * Whether the follower shares a Tarjan SCC with any of its leaders,
129
+ * self-follow included. Written by `FollowGraph` on recompute. Routes
130
+ * `plan` to the ZOH sub-step law: FOH destabilizes cycles that are
131
+ * stable under ZOH, so no ramp may ever arm inside an SCC.
132
+ */
133
+ _cyclic: boolean;
134
+ /** The follower's target as of the last `plan` call. */
135
+ _prevValue: number;
136
+ /** The followed value's frame delta observed by the last `plan` call. */
137
+ _d1: number;
138
+ /** The frame delta one older than `_d1`; their difference estimates curvature. */
139
+ _d2: number;
140
+ /**
141
+ * Re-anchors the follower to the followed source's current value.
142
+ * `h` is the step the follower is about to advance by, in seconds
143
+ * (the internal tick unit). On an edge where a ramp may arm —
144
+ * acyclic, passthrough arrival, `h > 0`, not a reduced-motion jump —
145
+ * it retargets and arms the follower's `_ramp` with the delta over
146
+ * `h`, so the coming advance integrates against the leader's motion.
147
+ * Every other edge retargets with the exact semantics of the
148
+ * leader-update handler (finite guard, reduced-motion jump).
149
+ */
150
+ recouple(h: number): void;
151
+ /**
152
+ * Refreshes the estimator from the follower's current target and
153
+ * returns this edge's sub-step demand for a frame of `dt` seconds —
154
+ * unclamped; the caller maxes over edges and clamps. Runs no user
155
+ * code: history is the target trail, freshness reads leader motion
156
+ * state. Call at most once per edge per frame, before any motion
157
+ * advances.
158
+ */
159
+ plan(dt: number): number;
160
+ }
161
+ /**
162
+ * The follow graph as a walkable structure: one edge per following
163
+ * motion, and a cached dependency rank over every motion the edges
164
+ * touch. `MotionSet` owns one; springs register and unregister edges as
165
+ * follows are wired and torn down. What the tick reads per motion —
166
+ * `_edge`, `_followers`, `_rank` — lives on the motions themselves;
167
+ * this class is their writer.
168
+ */
169
+ declare class FollowGraph {
170
+ #private;
171
+ /** Whether no edges are registered, letting the tick skip ordering work entirely. */
172
+ get isEmpty(): boolean;
173
+ /** Every registered edge, valid after `ensureOrder`; the plan pass walks this once per frame. */
174
+ get edges(): readonly FollowEdge[];
175
+ /**
176
+ * Registers `edge`, replacing any edge with the same follower: a
177
+ * spring follows at most one source at a time.
178
+ */
179
+ addEdge(edge: FollowEdge): void;
180
+ /** Drops the edge whose follower is `motion`, if one is registered. */
181
+ removeEdge(follower: Motion): void;
182
+ /**
183
+ * Recomputes ranks if an edge was added or removed since the last
184
+ * call; clean calls return immediately. Afterward, motions the graph
185
+ * touches hold ranks counting up from 0 with every leader ranked
186
+ * before its followers, members of a cycle rank in ascending creation
187
+ * id, and motions outside the graph hold -1. Ranks are canonical: a
188
+ * function of graph structure and creation order, never of wiring
189
+ * order.
190
+ */
191
+ ensureOrder(): void;
192
+ }
193
+ //#endregion
194
+ //#region src/motion-set.d.ts
195
+ /**
196
+ * The set of motions currently moving. Motions leave the set as they
197
+ * rest and re-enter on any write that disturbs them; `onWake` tells the
198
+ * ticker the set became non-empty so it can stop sleeping.
199
+ */
200
+ declare class MotionSet {
201
+ #private;
202
+ reduced: boolean;
203
+ onWake: (() => void) | null;
204
+ /**
205
+ * The coupling error budget, in value units: the local per-frame error
206
+ * the sub-step controller targets on each follow edge, floored by the
207
+ * follower's resting magnitude. `SpringSystem` exposes it as the
208
+ * `couplingTolerance` option and property; validation lives there.
209
+ */
210
+ couplingTolerance: number;
211
+ readonly flushes: FlushQueue;
212
+ /** Follow edges registered by following springs, with the dependency rank over their motions. */
213
+ readonly graph: FollowGraph;
214
+ constructor(debug?: boolean);
215
+ get size(): number;
216
+ add(motion: Motion): void;
217
+ remove(motion: Motion): void;
218
+ finishAll(): void;
219
+ tick(dt: number): void;
220
+ }
221
+ //#endregion
222
+ //#region src/spring-source.d.ts
223
+ /**
224
+ * The property key that brands a `SpringSource` and holds the channel its
225
+ * followers read. A registry symbol (`Symbol.for('coily/spring-source')`),
226
+ * so sources from one copy of coily are recognized by another when a
227
+ * bundle duplicates the library.
228
+ */
229
+ declare const SpringSourceSymbol: unique symbol;
230
+ /**
231
+ * What a source exposes to its followers, held under `SpringSourceSymbol`
232
+ * on the source object. Followers read `value` on every update and
233
+ * detach on dispose.
234
+ */
235
+ interface SpringSourceApi<T = number> {
236
+ /** The current value, in value units. */
237
+ readonly value: T;
238
+ /** Subscribes to value changes. Returns an unsubscribe function. */
239
+ onUpdate(callback: () => void): () => void;
240
+ /** Subscribes to the source being released. Returns an unsubscribe function. */
241
+ onDispose(callback: () => void): () => void;
242
+ }
243
+ /**
244
+ * A live value a spring can animate toward or derive from. `T` is the
245
+ * value's type: a scalar source (`T = number`, the default) can be
246
+ * assigned to `Spring.target`, which then tracks it momentum intact.
247
+ *
248
+ * Every `Spring` is a `SpringSource`, every `CompositeSpring` is a
249
+ * `SpringSource` of its value shape, and `mapSpring`, `velocityOf`, and
250
+ * `accelerationOf` derive fresh sources from existing ones. You take
251
+ * sources from those APIs and pass them wherever one is accepted; the
252
+ * type names such a value in your own signatures.
253
+ */
254
+ interface SpringSource<T = number> {
255
+ readonly [SpringSourceSymbol]: SpringSourceApi<T>;
256
+ }
257
+ /**
258
+ * Compile-time validation of a shape of sources: every leaf must be a
259
+ * `SpringSource`, every branch a plain object or array with at least one
260
+ * leaf. Used as `sources: T & SourceShape<T>` so an invalid leaf errors
261
+ * on that property rather than on the whole argument. Purely structural,
262
+ * and mirrored by the runtime validation for untyped callers. Optional
263
+ * and `undefined`-typed leaves are rejected.
264
+ */
265
+ type SourceShape<T> = 0 extends 1 & T ? T : T extends SpringSource<unknown> ? SpringSource<unknown> : T extends ((...args: never) => unknown) | (abstract new (...args: never) => unknown) ? never : T extends readonly never[] ? never : T extends object ? keyof T extends never ? never : { [K in keyof T]-?: 0 extends 1 & T[K] ? T[K] : undefined extends T[K] ? never : SourceShape<T[K]>; } : never;
266
+ /**
267
+ * The value shape a shape of sources produces: the same nesting with
268
+ * every `SpringSource` leaf replaced by its value — `number` for a
269
+ * scalar spring, the read-only value shape for a composite. What `map`
270
+ * receives when `mapSpring` is given a shape.
271
+ */
272
+ type SourceValues<T> = T extends SpringSource<infer V> ? V : { readonly [K in keyof T]: SourceValues<T[K]>; };
273
+ /**
274
+ * Derives a source from another by a pure function of its value:
275
+ * `mapSpring(leader, (v) => v + 10)` follows 10 above the leader,
276
+ * `(v) => -v` mirrors it. Maps compose — a mapped source is a
277
+ * `SpringSource` like any other, so it can be mapped again, used as a
278
+ * leaf of a shape map, or followed by several springs at once.
279
+ *
280
+ * The result is a stateless view, not a spring: it holds no
281
+ * subscriptions and needs no disposal, and reads compute `map` of the
282
+ * source's current value on the fly. `map` must be pure — it runs on
283
+ * every read and every source update, and nothing re-evaluates it when
284
+ * anything other than `source` changes. Composition is flat: mapping a
285
+ * mapped source extends its pipeline rather than wrapping it, so chains
286
+ * cost one function call per map however long they grow.
287
+ */
288
+ declare function mapSpring(source: SpringSource, map: (value: number) => number): SpringSource;
289
+ /**
290
+ * Derives a scalar source from several channels by a pure function of
291
+ * their values. `sources` is a shape — a plain object or array with
292
+ * `SpringSource` leaves, nested arbitrarily — or a single non-scalar
293
+ * source such as a `CompositeSpring`. `map` receives the matching
294
+ * values: each leaf's current number for a shape
295
+ * (`mapSpring({ x, y }, ({ x, y }) => Math.hypot(x, y))`), the
296
+ * live value shape for a composite
297
+ * (`mapSpring(point, ({ x, y }) => Math.hypot(x, y))`). Invalid
298
+ * leaves throw with their path (`position.z`).
299
+ *
300
+ * The result is a stateless view, not a spring: it holds no
301
+ * subscriptions and needs no disposal, and reads compute `map` over the
302
+ * sources' values on the fly. `map` must be pure — it runs on every
303
+ * read and every source update — and must not retain what it receives:
304
+ * every call hands it the same live mirror of the values, refreshed in
305
+ * place. The mapped source is released with the first of its sources:
306
+ * followers detach then, keeping their current target, as they would
307
+ * from a disposed spring.
308
+ */
309
+ declare function mapSpring<const T extends object>(sources: T & SourceShape<T>, map: (values: SourceValues<T>) => number): SpringSource;
310
+ //#endregion
311
+ //#region src/kinematic-source.d.ts
312
+ /**
313
+ * A `SpringSourceApi` whose value is in motion, so it also reports the
314
+ * value's derivatives: `velocity` (the rate `value` changes, in value
315
+ * units per second) and `acceleration` (the rate `velocity` changes, in
316
+ * value units per second squared). A `Spring` and a `CompositeSpring`
317
+ * carry both exactly — they fall out of the same equation that drives
318
+ * the motion — as does a source bridging a value whose motion it tracks.
319
+ * A value derived with `mapSpring` does not: it holds no motion of its
320
+ * own, so its derivatives would need the derivative of an arbitrary
321
+ * function. `velocityOf` and `accelerationOf` read this slot.
322
+ */
323
+ interface KinematicSourceApi<T = number> extends SpringSourceApi<T> {
324
+ /** The current velocity, in value units per second. */
325
+ readonly velocity: T;
326
+ /** The current acceleration, in value units per second squared. */
327
+ readonly acceleration: T;
328
+ }
329
+ /**
330
+ * A `SpringSource` whose value is in motion, so it also carries the
331
+ * value's `velocity` and `acceleration` — every `Spring` and
332
+ * `CompositeSpring` is one. `velocityOf` and `accelerationOf` accept
333
+ * these; a plain `SpringSource` (such as a `mapSpring` result) is not
334
+ * assignable, since a value derivation is not in motion.
335
+ */
336
+ interface KinematicSource<T = number> extends SpringSource<T> {
337
+ readonly [SpringSourceSymbol]: KinematicSourceApi<T>;
338
+ }
339
+ /**
340
+ * Derives a source from another's velocity — the rate its value changes,
341
+ * in value units per second. `velocityOf(motion)` is a source that reads
342
+ * `motion`'s current velocity, so speed-driven effects fall out of the
343
+ * same follow and `mapSpring` machinery as everything else: a spring can
344
+ * follow it (`blur.target = velocityOf(motion)`) and a map can shape it
345
+ * (`mapSpring(velocityOf(motion), (v) => 1 + Math.abs(v) * 0.001)` for
346
+ * squash and stretch). A scalar source yields a scalar velocity a spring
347
+ * can follow; a `CompositeSpring` or shape yields a velocity of the same
348
+ * shape, mapped to a scalar the way the composite itself is.
349
+ *
350
+ * The result is a stateless view, not a spring: it holds no
351
+ * subscriptions and needs no disposal, and reads report the source's
352
+ * current velocity on the fly. It updates when the source does and is
353
+ * released with it — followers detach then, keeping their current
354
+ * target, as they would from a disposed spring.
355
+ *
356
+ * Only a source in motion qualifies — a `Spring` or a `CompositeSpring`.
357
+ * A value derived with `mapSpring` has no velocity (its rate of change
358
+ * would need the derivative of the map), so it is rejected at the type
359
+ * level and at runtime; take `velocityOf` of the spring the map reads
360
+ * instead.
361
+ */
362
+ declare function velocityOf<T>(source: KinematicSource<T>): SpringSource<T>;
363
+ /**
364
+ * Derives a source from another's acceleration — the rate its velocity
365
+ * changes, in value units per second squared. `accelerationOf(motion)`
366
+ * spikes when `motion` is pushed hardest and passes through zero at the
367
+ * peak of a swing, so force-driven effects fall out of the same follow
368
+ * and `mapSpring` machinery: a spring can follow it, and a map can turn
369
+ * it into an impact flash or a jelly wobble
370
+ * (`mapSpring(accelerationOf(motion), (a) => Math.min(1, Math.abs(a) * 1e-4))`).
371
+ * A scalar source yields a scalar acceleration; a `CompositeSpring` or
372
+ * shape yields an acceleration of the same shape, mapped to a scalar the
373
+ * way the composite itself is.
374
+ *
375
+ * The result is a stateless view, not a spring: it holds no
376
+ * subscriptions and needs no disposal, and reads report the source's
377
+ * current acceleration on the fly. It updates when the source does and
378
+ * is released with it, followers detaching then as from a disposed
379
+ * spring.
380
+ *
381
+ * Only a source in motion qualifies — a `Spring` or a `CompositeSpring`.
382
+ * A value derived with `mapSpring` has no acceleration, so it is rejected
383
+ * at the type level and at runtime; take `accelerationOf` of the spring
384
+ * the map reads instead.
385
+ */
386
+ declare function accelerationOf<T>(source: KinematicSource<T>): SpringSource<T>;
387
+ //#endregion
388
+ //#region src/spring.d.ts
389
+ /**
390
+ * What a spring can animate toward: a fixed number, or a `SpringSource`
391
+ * — another spring, or a value derived from one with `mapSpring` —
392
+ * whose live value the spring follows.
393
+ */
394
+ type SpringTarget = number | SpringSource;
395
+ /**
396
+ * What a spring animates, which decides whether reduced motion applies to
397
+ * it. `'motion'` moves something on screen — position, scale, rotation —
398
+ * and snaps to its target under reduced motion. `'appearance'` changes
399
+ * how something looks without moving it — a cross-fade, a color, a blur —
400
+ * and keeps animating under reduced motion, since it carries no motion to
401
+ * reduce. Fixed when the spring is created; read it back from
402
+ * `Spring.purpose`.
403
+ */
404
+ type Purpose = 'motion' | 'appearance';
405
+ /**
406
+ * One animated number, driven toward its target with damped spring
407
+ * motion. Create springs with `SpringSystem.createSpring`; the owning
408
+ * system advances them, so a spring only moves while its system runs.
409
+ *
410
+ * Reads return the exact simulated state; nothing is rounded. Numeric
411
+ * writes — `target`, `value`, `velocity`, `jumpTo` — require finite
412
+ * numbers: `NaN` or an infinity throws rather than poisoning the
413
+ * simulation. See
414
+ * https://github.com/tkofh/coily/blob/main/PRECISION.md for the
415
+ * numerical contract.
416
+ */
417
+ declare class Spring implements KinematicSource {
418
+ #private;
419
+ /** Brands the spring as a `KinematicSource` whose api is the spring itself. */
420
+ get [SpringSourceSymbol](): KinematicSourceApi<number>;
421
+ constructor(motions: MotionSet, value: number, config?: SpringDefinition, purpose?: Purpose);
422
+ /**
423
+ * The value the spring is animating toward. While following a source,
424
+ * reads return the source's value as of its last update.
425
+ *
426
+ * Assignment accepts any `SpringTarget`:
427
+ * - A number retargets the spring. The current value and momentum carry
428
+ * over, so mid-flight retargets stay smooth. Assigning a number while
429
+ * following also stops the following.
430
+ * - A `SpringSource` — a `Spring`, or a value derived with `mapSpring` —
431
+ * makes this spring follow the source's live value.
432
+ *
433
+ * Under reduced motion, retargets apply instantly — unless `purpose` is
434
+ * `'appearance'`, in which case they animate as normal.
435
+ */
436
+ get target(): number;
437
+ set target(value: SpringTarget);
438
+ /**
439
+ * The current animated value: the target plus the remaining
440
+ * displacement.
441
+ *
442
+ * Writing displaces the spring: it keeps its target and springs back
443
+ * from the written value, notifying update listeners synchronously.
444
+ * Writing the current value is a no-op. Under reduced motion a write
445
+ * jumps the spring — target included — to the written value, unless
446
+ * `purpose` is `'appearance'`, in which case it displaces as normal.
447
+ */
448
+ get value(): number;
449
+ set value(value: number);
450
+ /**
451
+ * The current velocity, in value units per second.
452
+ *
453
+ * Writing flings the spring: motion continues from the current value
454
+ * with the written velocity, then settles back to the target. Under
455
+ * reduced motion writes are ignored, unless `purpose` is `'appearance'`,
456
+ * in which case the fling applies as normal.
457
+ */
458
+ get velocity(): number;
459
+ set velocity(value: number);
460
+ /**
461
+ * The current acceleration, in value units per second squared — how
462
+ * fast the velocity is changing. Read-only: acceleration is the
463
+ * spring's stiffness and friction acting on its current displacement
464
+ * and velocity, so it follows from the motion rather than being set.
465
+ * To fling the spring, write `velocity` instead.
466
+ */
467
+ get acceleration(): number;
468
+ /**
469
+ * What the spring animates — `'motion'` or `'appearance'` — fixed when
470
+ * it was created. An `'appearance'` spring opts out of reduced motion:
471
+ * it keeps animating where a `'motion'` spring snaps to its target. See
472
+ * `Purpose`.
473
+ */
474
+ get purpose(): Purpose;
475
+ /**
476
+ * The spring's `SpringDefinition`: the one it was created with or last
477
+ * assigned, otherwise `SpringDefinition.default`. Following a source
478
+ * never changes it — how a spring chases its target is the spring's
479
+ * own setting.
480
+ *
481
+ * Assigning a config reconfigures the spring in place — value and
482
+ * velocity are preserved, so mid-flight reconfigures stay smooth.
483
+ * Assigning `null` reverts to the default.
484
+ */
485
+ get config(): SpringDefinition;
486
+ /** The config's mass. */
487
+ get mass(): number;
488
+ /** The config's tension (stiffness). */
489
+ get tension(): number;
490
+ /** The config's damping (friction). */
491
+ get damping(): number;
492
+ /** The config's damping ratio. */
493
+ get dampingRatio(): number;
494
+ /** The config's resting precision. */
495
+ get precision(): number;
496
+ /** The config's arrival multiplier. */
497
+ get arrival(): number;
498
+ set config(value: SpringDefinition | null);
499
+ /**
500
+ * Milliseconds until the spring rests: 0 while resting, Infinity when
501
+ * the motion never settles. The time is solved from the motion, not
502
+ * estimated — the spring is resting at the first tick at or after it,
503
+ * and a bouncy spring can rest up to one oscillation earlier when a
504
+ * frame samples a low pulse. When the config's `arrival` is 0, it is
505
+ * capped at the exact ms of the first target crossing.
506
+ */
507
+ get timeRemaining(): number;
508
+ /**
509
+ * Whether the spring's remaining motion is inside its resting
510
+ * threshold. Resting springs cost nothing until the next write. See
511
+ * https://github.com/tkofh/coily/blob/main/PRECISION.md for the rest
512
+ * test.
513
+ */
514
+ get isResting(): boolean;
515
+ /**
516
+ * A promise that resolves when the spring next comes to rest — already
517
+ * resolved while resting or after dispose. Retargeting mid-flight
518
+ * extends the wait; disposing resolves it.
519
+ *
520
+ * @example
521
+ * ```ts
522
+ * spring.target = 300
523
+ * await spring.settled
524
+ * // the spring is at 300 and resting
525
+ * ```
526
+ */
527
+ get settled(): Promise<void>;
528
+ /**
529
+ * Snaps the spring to `value` with no animation: target and value both
530
+ * become `value` and velocity clears. Listeners are notified
531
+ * synchronously — `update`, plus `stop` if the spring was moving.
532
+ */
533
+ jumpTo(value: number): void;
534
+ /**
535
+ * Releases the spring permanently: stops following, detaches followers
536
+ * (each keeps its current target), resolves `settled`, and notifies
537
+ * dispose listeners. Calling it again is a no-op.
538
+ *
539
+ * A disposed spring keeps its final value readable; writes throw.
540
+ */
541
+ dispose(): void;
542
+ /**
543
+ * Subscribes to value changes: every tick the spring moves, plus
544
+ * synchronous `value` writes and `jumpTo` calls. Returns an unsubscribe
545
+ * function.
546
+ */
547
+ onUpdate(callback: () => void): () => void;
548
+ /**
549
+ * Subscribes to the spring leaving rest. `start` and `stop` always
550
+ * alternate; retargeting mid-flight fires neither. Returns an
551
+ * unsubscribe function.
552
+ */
553
+ onStart(callback: () => void): () => void;
554
+ /**
555
+ * Subscribes to the spring coming to rest, whether by settling or by
556
+ * `jumpTo`. Always alternates with `start`. Returns an unsubscribe
557
+ * function.
558
+ */
559
+ onStop(callback: () => void): () => void;
560
+ /** Subscribes to `dispose`, which fires once. Returns an unsubscribe function. */
561
+ onDispose(callback: () => void): () => void;
562
+ }
563
+ //#endregion
564
+ //#region src/composite-spring.d.ts
565
+ /**
566
+ * Compile-time validation of a value shape: every channel must be a `number`,
567
+ * every branch a plain object or array with at least one channel. Used as
568
+ * `value: T & Shape<T>` so an invalid channel errors on that property rather
569
+ * than on the whole argument. Purely structural — interfaces satisfy it
570
+ * without index signatures — and mirrored by the runtime
571
+ * validation for untyped callers. Optional and `undefined`-typed channels
572
+ * are rejected: a channel that may be absent would make two springs of the
573
+ * same declared shape structurally incompatible at runtime.
574
+ */
575
+ type Shape<T> = 0 extends 1 & T ? T : T extends number ? number : T extends ((...args: never) => unknown) | (abstract new (...args: never) => unknown) ? never : T extends readonly never[] ? never : T extends object ? keyof T extends never ? never : { [K in keyof T]-?: 0 extends 1 & T[K] ? T[K] : undefined extends T[K] ? never : Shape<T[K]>; } : never;
576
+ /**
577
+ * A partial view of a value shape: the same nesting with any subset of the
578
+ * numeric channels present. Absent (or `undefined`) channels are left alone.
579
+ */
580
+ type PartialShape<T> = T extends number ? number : { readonly [K in keyof T]?: PartialShape<T[K]> | undefined; };
581
+ /**
582
+ * A deep-readonly view of a value shape. Composite reads (`target`, `value`,
583
+ * `velocity`, `acceleration`) return the spring's live mirror objects —
584
+ * refreshed in place on every read — so their channels are typed read-only.
585
+ */
586
+ type ReadonlyShape<T> = T extends number ? number : { readonly [K in keyof T]: ReadonlyShape<T[K]>; };
587
+ /**
588
+ * Configuration for a composite spring: a single `SpringDefinition` applied to every
589
+ * channel, or an object mirroring the value shape with configs at any level.
590
+ * A config at a subtree applies to every channel below it.
591
+ * `null` reverts to the default config.
592
+ * Configs are always `SpringDefinition` instances (from `defineSpring`), so a
593
+ * plain object is unambiguously a config shape and any non-config leaf it
594
+ * reaches is an error.
595
+ */
596
+ type ConfigShape<T> = SpringDefinition | null | (T extends number ? never : { readonly [K in keyof T]?: ConfigShape<T[K]> | undefined; });
597
+ /**
598
+ * What each channel of a composite spring animates: a single `Purpose`
599
+ * for every channel, or a shape mirroring the value with purposes at any
600
+ * level. A purpose at a subtree covers every channel below it, and any
601
+ * channel it doesn't reach defaults to `'motion'`. Fixed when the spring
602
+ * is created. See `Purpose`.
603
+ */
604
+ type PurposeShape<T> = Purpose | (T extends number ? never : { readonly [K in keyof T]?: PurposeShape<T[K]> | undefined; });
605
+ /**
606
+ * A partial target shape: the same nesting as the value with any subset
607
+ * of the channels present, each taking a number to animate toward or a
608
+ * scalar `SpringSource` to follow. Absent (or `undefined`) channels are
609
+ * left alone.
610
+ */
611
+ type TargetShape<T> = T extends number ? number | SpringSource : { readonly [K in keyof T]?: TargetShape<T[K]> | undefined; };
612
+ /**
613
+ * What a composite spring can animate toward: a partial shape mixing
614
+ * numbers and scalar `SpringSource`s per channel, or another composite
615
+ * spring of the same shape to follow channel by channel.
616
+ */
617
+ type CompositeSpringTarget<T extends object> = TargetShape<T> | CompositeSpring<T>;
618
+ /**
619
+ * A composite spring over a fixed numeric shape — a nested plain object
620
+ * or array whose leaves are all numbers. Each leaf runs an independent
621
+ * spring, called a channel, behind one API: targets, values, velocities,
622
+ * and configs read and write as (partial) shapes, and events are
623
+ * coalesced across channels. Create composite springs with
624
+ * `SpringSystem.createSpring`.
625
+ *
626
+ * A composite spring is a `SpringSource` of its value shape: `mapSpring`
627
+ * derives scalar sources from it — alone or at the leaves of a shape —
628
+ * which springs can then follow. Only scalar sources are followable
629
+ * directly.
630
+ *
631
+ * The shape is fixed at construction. Writes naming unknown channels
632
+ * throw with the channel's path (`position.z`), and so do non-finite
633
+ * channel values.
634
+ */
635
+ declare class CompositeSpring<in out T extends object> implements KinematicSource<ReadonlyShape<T>> {
636
+ #private;
637
+ /** Brands the composite as a `KinematicSource` whose api is the composite itself. */
638
+ get [SpringSourceSymbol](): KinematicSourceApi<ReadonlyShape<T>>;
639
+ constructor(motions: MotionSet, value: T & Shape<T>, config?: ConfigShape<T>, purpose?: PurposeShape<T>);
640
+ /**
641
+ * The value each channel is animating toward, as a read-only mirror of
642
+ * the shape. The mirror object is reused: every read refreshes its
643
+ * numbers in place, so read it fresh rather than holding it across
644
+ * frames.
645
+ *
646
+ * Assignment accepts a `CompositeSpringTarget`:
647
+ * - A partial shape retargets the channels it names and leaves the
648
+ * others alone. Each named channel takes a number, or a scalar
649
+ * `SpringSource` to follow — a `Spring`, or a value derived with
650
+ * `mapSpring`. While following a leader, naming a channel also
651
+ * detaches that channel from it.
652
+ * - A `CompositeSpring` of the same shape follows the leader channel by
653
+ * channel.
654
+ *
655
+ * Unknown channels throw with their path.
656
+ */
657
+ get target(): ReadonlyShape<T>;
658
+ set target(value: CompositeSpringTarget<T>);
659
+ /**
660
+ * The current animated value of every channel, as a read-only mirror of
661
+ * the shape. The mirror object is reused: every read refreshes its
662
+ * numbers in place, so read it fresh rather than holding it across
663
+ * frames.
664
+ *
665
+ * Assignment takes a partial shape and displaces the channels it names:
666
+ * each keeps its target and springs back from the written value, with
667
+ * one coalesced `update` fired synchronously. Under reduced motion a
668
+ * write jumps the named channels — targets included — except channels
669
+ * whose `purpose` is `'appearance'`, which displace as normal.
670
+ */
671
+ get value(): ReadonlyShape<T>;
672
+ set value(value: PartialShape<T>);
673
+ /**
674
+ * The current velocity of every channel in value units per second, as a
675
+ * read-only mirror of the shape — reused and refreshed in place on each
676
+ * read.
677
+ *
678
+ * Assignment takes a partial shape and flings the channels it names;
679
+ * the rest are untouched. Under reduced motion writes are ignored,
680
+ * except on channels whose `purpose` is `'appearance'`.
681
+ */
682
+ get velocity(): ReadonlyShape<T>;
683
+ set velocity(value: PartialShape<T>);
684
+ /**
685
+ * The current acceleration of every channel in value units per second
686
+ * squared, as a read-only mirror of the shape — reused and refreshed in
687
+ * place on each read. Read-only, like each channel's: acceleration
688
+ * follows from the motion. To fling channels, write `velocity`.
689
+ */
690
+ get acceleration(): ReadonlyShape<T>;
691
+ /**
692
+ * The shared config when every channel resolves to the same
693
+ * `SpringDefinition`, and `null` when they differ.
694
+ *
695
+ * Assignment accepts a `ConfigShape`: one config for every channel,
696
+ * `null` to revert every channel to the default, or a shape mirroring
697
+ * the value with configs at any level — a config at a subtree covers
698
+ * every channel below it. Non-config leaves throw with their path.
699
+ */
700
+ get config(): SpringDefinition | null;
701
+ set config(value: ConfigShape<T>);
702
+ /**
703
+ * The shared purpose when every channel resolves to the same `Purpose`,
704
+ * and `null` when they differ. Fixed when the spring is created — an
705
+ * `'appearance'` channel opts out of reduced motion. See `Purpose`.
706
+ */
707
+ get purpose(): Purpose | null;
708
+ /** The largest `timeRemaining` across channels, in milliseconds. */
709
+ get timeRemaining(): number;
710
+ /** Whether every channel is resting. */
711
+ get isResting(): boolean;
712
+ /**
713
+ * A promise that resolves when every channel next rests — already
714
+ * resolved while resting or after dispose. Retargeting any channel
715
+ * mid-flight extends the wait; disposing resolves it.
716
+ */
717
+ get settled(): Promise<void>;
718
+ /**
719
+ * Snaps the channels the partial shape names to the given values with
720
+ * no animation, notifying listeners synchronously. Channels it leaves
721
+ * out are untouched.
722
+ */
723
+ jumpTo(value: PartialShape<T>): void;
724
+ /**
725
+ * Releases every channel permanently: resolves `settled` and notifies
726
+ * dispose listeners. Calling it again is a no-op.
727
+ *
728
+ * A disposed composite spring keeps its final values readable; writes
729
+ * throw.
730
+ */
731
+ dispose(): void;
732
+ /**
733
+ * Subscribes to coalesced value changes: at most one `update` per tick,
734
+ * fired after every channel has advanced, plus one per synchronous
735
+ * write. Returns an unsubscribe function.
736
+ */
737
+ onUpdate(callback: () => void): () => void;
738
+ /**
739
+ * Subscribes to the composite leaving rest: a channel starts moving
740
+ * while all were resting. Always alternates with `stop`. Returns an
741
+ * unsubscribe function.
742
+ */
743
+ onStart(callback: () => void): () => void;
744
+ /**
745
+ * Subscribes to the composite coming to rest: every channel resting,
746
+ * fired after that tick's final `update`. Always alternates with
747
+ * `start`. Returns an unsubscribe function.
748
+ */
749
+ onStop(callback: () => void): () => void;
750
+ /** Subscribes to `dispose`, which fires once. Returns an unsubscribe function. */
751
+ onDispose(callback: () => void): () => void;
752
+ }
753
+ //#endregion
754
+ //#region src/ticker.d.ts
755
+ /** Frame timing options for a spring system. */
756
+ interface TickerOptions {
757
+ /**
758
+ * Frame-rate cap. 0 means uncapped: one tick per displayed frame,
759
+ * whatever rate the screen runs at — 60Hz, 120Hz, or adaptive. Capped
760
+ * ticks are paced to whole display frames, near the requested rate, and
761
+ * each still receives the true elapsed time.
762
+ * @default 0
763
+ */
764
+ readonly fps?: number | undefined;
765
+ /**
766
+ * Frame gap in milliseconds above which the gap is treated as lag — a
767
+ * backgrounded tab, a blocking task — and replaced with `adjustedLag`,
768
+ * so springs don't teleport when frames resume. 0 disables lag
769
+ * clamping.
770
+ * @default 500
771
+ */
772
+ readonly lagThreshold?: number | undefined;
773
+ /**
774
+ * The elapsed milliseconds a lagging frame is replaced with. Clamped to
775
+ * at most `lagThreshold`.
776
+ * @default 33
777
+ */
778
+ readonly adjustedLag?: number | undefined;
779
+ }
780
+ //#endregion
781
+ //#region src/system.d.ts
782
+ /**
783
+ * Per-spring options for `SpringSystem.createSpring`, separate from the
784
+ * physical `config`.
785
+ */
786
+ interface SpringOptions {
787
+ /**
788
+ * What the spring animates, which decides whether reduced motion applies
789
+ * to it. `'appearance'` — a cross-fade, a color, a blur — keeps
790
+ * animating under reduced motion; `'motion'` snaps to the target. Fixed
791
+ * for the spring's life. See `Purpose`.
792
+ * @default 'motion'
793
+ */
794
+ purpose?: Purpose | undefined;
795
+ }
796
+ /**
797
+ * Per-spring options for a composite `SpringSystem.createSpring`, separate
798
+ * from the physical `config`.
799
+ */
800
+ interface CompositeSpringOptions<T extends object> {
801
+ /**
802
+ * What each channel animates, deciding reduced-motion behavior per
803
+ * channel: a single `Purpose` for every channel, or a shape mirroring
804
+ * the value with purposes at any level — a purpose at a subtree covers
805
+ * every channel below it. Channels it doesn't reach default to
806
+ * `'motion'`. Fixed for the spring's life. See `PurposeShape`.
807
+ * @default 'motion'
808
+ */
809
+ purpose?: PurposeShape<T> | undefined;
810
+ }
811
+ interface SpringSystemOptions extends TickerOptions {
812
+ /**
813
+ * How far a spring may trail a source it follows within one frame, in
814
+ * the value's own units. A frame whose motion would exceed it is split
815
+ * into internal sub-steps (at most 8), so followed chains stay
816
+ * accurate through slow frames and sudden retargets. Smaller is
817
+ * tighter and costs more solver work; it never goes finer than the
818
+ * follower's resting precision, and listeners still fire once per
819
+ * frame. Springs that follow nothing are unaffected.
820
+ * @default 0.1
821
+ */
822
+ couplingTolerance?: number | undefined;
823
+ /**
824
+ * Log active motion counts to the console whenever they change.
825
+ * @default false
826
+ */
827
+ debug?: boolean | undefined;
828
+ /**
829
+ * When springs snap to their targets instead of animating. `'user'`
830
+ * follows the OS prefers-reduced-motion setting, including live changes
831
+ * — switching it on finishes in-flight motion instantly. `'always'` and
832
+ * `'never'` force one behavior.
833
+ * @default 'user'
834
+ */
835
+ reducedMotion?: 'user' | 'always' | 'never' | undefined;
836
+ }
837
+ /**
838
+ * Owns springs and advances them over time. Create one with
839
+ * `createSpringSystem`, then either `start()` it to animate on real
840
+ * frames or call `advance` yourself for manual stepping.
841
+ */
842
+ interface SpringSystem {
843
+ /**
844
+ * Creates a spring at rest at `value`. Without `config`, springs use
845
+ * the default: critically damped, settling in about 500ms. To follow
846
+ * another spring from birth, pass it in place of `value`.
847
+ *
848
+ * `options.purpose` marks what the spring animates: `'appearance'`
849
+ * keeps animating under reduced motion, `'motion'` (the default) snaps.
850
+ */
851
+ createSpring(value: number, config?: SpringDefinition, options?: SpringOptions): Spring;
852
+ /**
853
+ * Creates a spring already following `source` — a `Spring`, or a value
854
+ * derived with `mapSpring`. It starts at the source's current value,
855
+ * so nothing moves until the source does; equivalent to creating a
856
+ * spring at that value and assigning `source` to its `target`.
857
+ *
858
+ * `options.purpose` marks what the spring animates: `'appearance'`
859
+ * keeps animating under reduced motion, `'motion'` (the default) snaps.
860
+ */
861
+ createSpring(source: SpringSource, config?: SpringDefinition, options?: SpringOptions): Spring;
862
+ /**
863
+ * Creates a composite spring over a numeric shape: a plain object or
864
+ * array, nested arbitrarily, whose leaves are all numbers. Each leaf
865
+ * becomes an independently sprung channel.
866
+ *
867
+ * `config` applies per channel: a single `SpringDefinition` for every
868
+ * channel, or a shape mirroring the value with configs at any level.
869
+ * `options.purpose` marks reduced-motion behavior the same way, per
870
+ * channel — an `'appearance'` channel keeps animating.
871
+ */
872
+ createSpring<T extends object>(value: T & Shape<T>, config?: ConfigShape<T>, options?: CompositeSpringOptions<T>): CompositeSpring<T>;
873
+ /**
874
+ * Advances every moving spring by `dt` milliseconds. For manual
875
+ * stepping in place of `start()` — tests, custom loops, offline
876
+ * rendering.
877
+ */
878
+ advance(dt: number): void;
879
+ /**
880
+ * Begins animating on real frames: one tick per displayed frame, via
881
+ * `requestAnimationFrame` in the browser and a `setTimeout` fallback
882
+ * elsewhere. An idle system sleeps — no frames are scheduled while
883
+ * every spring rests.
884
+ */
885
+ start(): void;
886
+ /** Stops animating. Springs hold their state until `start` or `advance`. */
887
+ stop(): void;
888
+ /**
889
+ * Whether the system is between `start()` and `stop()`. Sleeping while
890
+ * idle still counts as running.
891
+ */
892
+ readonly running: boolean;
893
+ /**
894
+ * Whether springs currently snap instead of animating, per the
895
+ * `reducedMotion` option and, in `'user'` mode, the live OS setting.
896
+ * Read it to gate purely decorative effects of your own. Springs
897
+ * created with `purpose: 'appearance'` animate regardless.
898
+ */
899
+ readonly reducedMotion: boolean;
900
+ /**
901
+ * How far a spring may trail a source it follows within one frame, in
902
+ * the value's own units — see the `couplingTolerance` option.
903
+ * Assignment accepts any finite number >= 0 and applies from the next
904
+ * frame; 0 means as fine as the follower's resting precision allows.
905
+ */
906
+ couplingTolerance: number;
907
+ /**
908
+ * Frame-rate cap. 0 means uncapped: one tick per displayed frame,
909
+ * whatever rate the screen runs at. Capped ticks land on whole display
910
+ * frames and receive the true elapsed time.
911
+ */
912
+ fps: number;
913
+ /**
914
+ * Frame gap in milliseconds above which the gap is treated as lag — a
915
+ * backgrounded tab, a blocking task — and replaced with `adjustedLag`,
916
+ * so springs don't teleport when frames resume. 0 disables lag
917
+ * clamping.
918
+ */
919
+ lagThreshold: number;
920
+ /** The elapsed milliseconds a lagging frame is replaced with. Clamped to at most `lagThreshold`. */
921
+ adjustedLag: number;
922
+ }
923
+ /**
924
+ * Creates a spring system: the entry point of the library.
925
+ *
926
+ * @example
927
+ * ```ts
928
+ * const system = createSpringSystem()
929
+ * system.start()
930
+ *
931
+ * const spring = system.createSpring(0)
932
+ * spring.onUpdate(() => {
933
+ * element.style.translate = `${spring.value}px 0`
934
+ * })
935
+ * spring.target = 300
936
+ * ```
937
+ */
938
+ declare function createSpringSystem(options?: SpringSystemOptions): SpringSystem;
939
+ //#endregion
940
+ export { accelerationOf as _, createSpringSystem as a, SpringSourceSymbol as b, ConfigShape as c, ReadonlyShape as d, Shape as f, KinematicSource as g, SpringTarget as h, SpringSystemOptions as i, PartialShape as l, Spring as m, SpringOptions as n, CompositeSpring as o, Purpose as p, SpringSystem as r, CompositeSpringTarget as s, CompositeSpringOptions as t, PurposeShape as u, velocityOf as v, mapSpring as x, SpringSource as y };