coily 0.14.0 → 0.16.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,1213 @@
1
+ import { t as SpringDefinition } from "./config-B8M2xp26.js";
2
+ //#region src/flush-queue.d.ts
3
+ /**
4
+ * Defers callbacks while inside a batch or tick. At depth 0, `request`
5
+ * runs the callback immediately. Otherwise it queues the callback, once
6
+ * per callback, until the outermost exit. Composite springs use it to
7
+ * merge 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
+ * Something the system advances frame by frame: a spring's `SpringMotion`
20
+ * or a ramp's `RampMotion`. `MotionSet` ticks motions and `FollowGraph`
21
+ * orders them through the members declared here. Owners keep their motion
22
+ * in a private field, so none of this reaches the public api.
23
+ *
24
+ * A tick runs in two phases. Every moving motion first advances silently,
25
+ * so followers recouple against frame-current values. Then each motion
26
+ * that advanced delivers its events in `endFrame`.
27
+ */
28
+ declare abstract class Motion {
29
+ /**
30
+ * Creation order across every kind of motion. The lower id goes first
31
+ * wherever motions need a deterministic order and nothing else decides.
32
+ */
33
+ readonly id: number;
34
+ /**
35
+ * The last ordered pass that advanced the motion, written by
36
+ * `MotionSet`, so a motion queued twice in one pass advances once.
37
+ */
38
+ pass: number;
39
+ /**
40
+ * The last ordered frame that advanced the motion, written by
41
+ * `MotionSet`. Sub-steps share one frame, and the first advance in it
42
+ * queues the motion for `endFrame`.
43
+ */
44
+ frame: number;
45
+ /**
46
+ * Dependency rank, written by `FollowGraph`. Motions in the follow
47
+ * graph count up from 0, leaders before followers. -1 marks a motion
48
+ * outside the graph.
49
+ */
50
+ rank: number;
51
+ /**
52
+ * The edge this motion follows through, or null. `FollowGraph` writes
53
+ * it. Only a spring follows.
54
+ */
55
+ edge: FollowEdge | null;
56
+ /**
57
+ * The edges that read this motion, or null when none do. `FollowGraph`
58
+ * rebuilds them with the ranks, so they can lag a mid-pass edge change
59
+ * until the next recompute. The tick tolerates stale entries.
60
+ */
61
+ followers: FollowEdge[] | null;
62
+ /** Whether the motion has stopped moving. `MotionSet` drops it once true. */
63
+ abstract get isResting(): boolean;
64
+ /** The current velocity, in value units per second. */
65
+ abstract get velocity(): number;
66
+ /** The current acceleration, in value units per second squared. */
67
+ abstract get acceleration(): number;
68
+ /** Advances by `h` seconds and emits nothing. Events wait for `endFrame`. */
69
+ abstract advance(h: number): void;
70
+ /** Delivers the frame's events, after every motion has advanced. */
71
+ abstract endFrame(): void;
72
+ /**
73
+ * Applies a change of the system's reduced-motion setting, which
74
+ * `MotionSet.reduced` already holds. A motion whose purpose is
75
+ * `'nonmotion'` ignores it.
76
+ */
77
+ abstract applyReducedMotion(reduced: boolean): void;
78
+ /**
79
+ * How far the motion sits from steady tracking. A follower's `plan`
80
+ * reads it from its leader as the sub-step controller's shock signal.
81
+ * A motion that nothing can knock off its course returns 0.
82
+ */
83
+ abstract manifoldDeviation(): number;
84
+ /**
85
+ * Whether the motion's slope changes at a known moment within the next
86
+ * `dt` seconds. Planning from history can't foresee that, so a
87
+ * follower's `plan` demands the sub-step cap on that frame.
88
+ */
89
+ abstract kinksWithin(dt: number): boolean;
90
+ }
91
+ //#endregion
92
+ //#region src/follow-graph.d.ts
93
+ /**
94
+ * The sub-step controller's history of one followed value: its last
95
+ * observed value, `delta`, and `curvature`.
96
+ */
97
+ declare class TargetTrail {
98
+ #private;
99
+ constructor(value: number);
100
+ /** The change between the last two observed values. */
101
+ get delta(): number;
102
+ /** The latest `delta` minus the one before it. 0 while the value moves in a straight line. */
103
+ get curvature(): number;
104
+ /** Records this frame's value and returns the change since the last one. */
105
+ observe(value: number): number;
106
+ /** Restarts the trail at `value` without recording a change. */
107
+ anchor(value: number): void;
108
+ }
109
+ /**
110
+ * One follow relationship: the motion that retargets, the motions it
111
+ * reads, how to re-anchor it, and the sub-step controller's per-edge
112
+ * state. `Spring` registers one when it follows a source with resolvable
113
+ * motions, and unregisters it on unfollow and dispose.
114
+ */
115
+ interface FollowEdge {
116
+ /** The motion that retargets when its leaders advance. */
117
+ readonly follower: Motion;
118
+ /** The distinct motions behind the followed source, per `resolveLeaderMotions`. */
119
+ readonly leaders: readonly Motion[];
120
+ /**
121
+ * Whether the follower shares a Tarjan SCC with any leader, self-follow
122
+ * included. `FollowGraph` writes it on recompute. A cyclic edge stays on
123
+ * ZOH (zero-order hold) and never arms a slope, because FOH (first-order
124
+ * hold) destabilizes cycles that are stable under ZOH.
125
+ */
126
+ cyclic: boolean;
127
+ /** The follower's target as `plan` observes it once per frame. */
128
+ readonly trail: TargetTrail;
129
+ /**
130
+ * Re-anchors the follower to the followed source's current value before
131
+ * an advance of `h` seconds (the internal tick unit). A reduced-motion
132
+ * jump rests the follower on the new target and emits nothing. Its
133
+ * `endFrame` delivers the events in the settle sweep. An acyclic edge
134
+ * with passthrough arrival and `h > 0` retargets and arms the follower's
135
+ * slope at the delta over `h`, so the advance integrates against the
136
+ * leader's motion. Every other edge retargets exactly as the
137
+ * leader-update handler does (finite guard, rebase).
138
+ */
139
+ recouple(h: number): void;
140
+ /**
141
+ * Refreshes the estimator from the follower's current target and
142
+ * returns the edge's unclamped sub-step demand for a frame of `dt`
143
+ * seconds. The caller clamps the max over edges. Runs no user code:
144
+ * history comes from `trail`, freshness from leader motion state. Call
145
+ * it at most once per edge per frame, before any motion advances.
146
+ */
147
+ plan(dt: number): number;
148
+ }
149
+ /**
150
+ * The follow graph: one edge per following motion, and a cached
151
+ * dependency rank over every motion the edges touch. `MotionSet` owns
152
+ * one. This class writes `edge`, `followers`, and `rank` onto the
153
+ * motions, where the tick reads them.
154
+ */
155
+ declare class FollowGraph {
156
+ #private;
157
+ /** Whether no edges are registered. The tick then skips all ordering work. */
158
+ get isEmpty(): boolean;
159
+ /** Every registered edge, current as of the last `ensureOrder`. */
160
+ get edges(): readonly FollowEdge[];
161
+ /**
162
+ * Registers `edge`, replacing any edge with the same follower: a
163
+ * spring follows at most one source at a time.
164
+ */
165
+ addEdge(edge: FollowEdge): void;
166
+ /** Drops the edge whose follower is `follower`, if one is registered. */
167
+ removeEdge(follower: Motion): void;
168
+ /**
169
+ * Recomputes ranks if an edge was added or removed since the last call.
170
+ * Afterward, motions the graph touches rank up from 0, every leader
171
+ * before its followers, and motions outside it hold -1. Cycle members
172
+ * rank in ascending creation id. Ranks depend on graph structure and
173
+ * creation order, never on wiring order.
174
+ */
175
+ ensureOrder(): void;
176
+ }
177
+ //#endregion
178
+ //#region src/motion-set.d.ts
179
+ /**
180
+ * The set of motions the tick advances. A motion leaves when it rests and
181
+ * re-enters on any write that disturbs it. A ramp is in the set only
182
+ * while something watches it frame by frame. `onWake` fires when the set
183
+ * becomes non-empty, waking a sleeping ticker.
184
+ */
185
+ declare class MotionSet {
186
+ #private;
187
+ reduced: boolean;
188
+ onWake: (() => void) | null;
189
+ /**
190
+ * The coupling error budget, in value units: the local per-frame error
191
+ * the sub-step controller targets on each follow edge, floored by the
192
+ * follower's resting magnitude. `SpringSystem` validates and exposes it
193
+ * as the `couplingTolerance` option and property.
194
+ */
195
+ couplingTolerance: number;
196
+ readonly flushes: FlushQueue;
197
+ /** Follow edges registered by following springs, with the dependency rank over their motions. */
198
+ readonly graph: FollowGraph;
199
+ /**
200
+ * Motions a reduced-motion change must reach while out of the tick: a
201
+ * `'motion'` ramp that moves on the clock unwatched, or one holding
202
+ * still under reduced motion. Each member keeps its membership current.
203
+ */
204
+ readonly reducible: Set<Motion>;
205
+ /**
206
+ * Returns the idle wall time, in seconds, the clock has not counted yet.
207
+ * Null when no ticker counts idle time. The ticker installs it, and
208
+ * moves the total in with `credit` on its first frame after starting or
209
+ * waking, and when it stops.
210
+ */
211
+ idleTime: (() => number) | null;
212
+ constructor(debug?: boolean);
213
+ get size(): number;
214
+ /**
215
+ * The system clock in seconds, which ramps read their position from. It
216
+ * moves only forward: by each tick's `dt` when the advance phase ends,
217
+ * and by idle wall time while the ticker sleeps. Wall time is read once
218
+ * per microtask checkpoint, so the clock holds still through
219
+ * synchronous code.
220
+ */
221
+ get time(): number;
222
+ /**
223
+ * The id of the tick whose advance phase is running, or 0 between
224
+ * phases. A ramp steps a cached value through the sub-steps of the
225
+ * phase and reads its anchor formula everywhere else.
226
+ */
227
+ get advancing(): number;
228
+ /** Adds idle wall time to the clock, in seconds. */
229
+ credit(seconds: number): void;
230
+ add(motion: Motion): void;
231
+ remove(motion: Motion): void;
232
+ /**
233
+ * Whether the motion is in the set. A motion that rests mid-pass leaves
234
+ * before its own `endFrame`, so motions ask here rather than track it.
235
+ */
236
+ has(motion: Motion): boolean;
237
+ /**
238
+ * Applies a reduced-motion change to the `reducible` registry first,
239
+ * because a ramp's jump retargets its followers, then to every moving
240
+ * motion, leaders before followers. A motion reached twice changes once.
241
+ */
242
+ setReduced(reduced: boolean): void;
243
+ tick(dt: number): void;
244
+ }
245
+ //#endregion
246
+ //#region src/spring-source.d.ts
247
+ /**
248
+ * The property key that brands a `SpringSource` and holds the api its
249
+ * followers read. It is a registry symbol
250
+ * (`Symbol.for('coily/spring-source')`), so one copy of coily
251
+ * recognizes sources from another when a bundle duplicates the library.
252
+ */
253
+ declare const SpringSourceSymbol: unique symbol;
254
+ /**
255
+ * What a source exposes to its followers, held under `SpringSourceSymbol`
256
+ * on the source object. Followers read `value` on every update and
257
+ * detach on dispose.
258
+ */
259
+ interface SpringSourceApi<T = number> {
260
+ /** The current value, in value units. */
261
+ readonly value: T;
262
+ /** Subscribes to value changes. Returns an unsubscribe function. */
263
+ onUpdate(callback: () => void): () => void;
264
+ /** Subscribes to the source being released. Returns an unsubscribe function. */
265
+ onDispose(callback: () => void): () => void;
266
+ }
267
+ /**
268
+ * A live value of type `T` that a spring can animate toward or derive
269
+ * from. Assign a scalar source (`T = number`, the default) to
270
+ * `Spring.target`, and the spring tracks it with its momentum intact.
271
+ *
272
+ * Every `Spring` and `Ramp` is a `SpringSource`, and every
273
+ * `CompositeSpring` is a `SpringSource` of its value shape. `mapSpring`,
274
+ * `velocityOf`, `accelerationOf`, and `targetOf` derive new sources from
275
+ * existing ones. Use the type to name such a value in your own
276
+ * signatures.
277
+ */
278
+ interface SpringSource<T = number> {
279
+ readonly [SpringSourceSymbol]: SpringSourceApi<T>;
280
+ }
281
+ /**
282
+ * Compile-time validation of a shape of sources: every leaf must be a
283
+ * `SpringSource`, and every branch a plain object or array with at least
284
+ * one leaf. `mapSpring` takes `sources: T & SourceShape<T>`, so an
285
+ * invalid leaf errors on that property, not on the whole argument. The
286
+ * check is structural only, and the runtime validation repeats it for
287
+ * untyped callers. It rejects optional and `undefined`-typed leaves.
288
+ */
289
+ 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;
290
+ /**
291
+ * The value shape a shape of sources produces: the same nesting, with
292
+ * every `SpringSource` leaf replaced by its value. A scalar spring leaf
293
+ * becomes `number`, and a composite leaf becomes its read-only value
294
+ * shape. This is what `map` receives when you give `mapSpring` a shape.
295
+ */
296
+ type SourceValues<T> = T extends SpringSource<infer V> ? V : { readonly [K in keyof T]: SourceValues<T[K]>; };
297
+ /**
298
+ * Derives a source from another by a pure function of its value.
299
+ * `mapSpring(leader, (v) => v + 10)` follows 10 above the leader, and
300
+ * `(v) => -v` mirrors it. Like any `SpringSource`, the result can be
301
+ * mapped again, used as a leaf of a shape map, or followed by several
302
+ * springs at once.
303
+ *
304
+ * The result is a stateless view, not a spring. It holds no
305
+ * subscriptions and needs no disposal. `map` runs on every read, and
306
+ * each follower reads on every source update, so `map` must be pure.
307
+ * Nothing re-evaluates it when anything other than `source` changes.
308
+ * Mapping a mapped source extends its pipeline instead of wrapping it,
309
+ * so a chain costs one function call per map.
310
+ */
311
+ declare function mapSpring(source: SpringSource, map: (value: number) => number): SpringSource;
312
+ /**
313
+ * Derives a scalar source from several channels by a pure function of
314
+ * their values. `sources` is a single non-scalar source such as a
315
+ * `CompositeSpring`, or a shape: a plain object or array with
316
+ * `SpringSource` leaves, nested to any depth. `map` receives the
317
+ * matching values. For a shape, that is each leaf's current number
318
+ * (`mapSpring({ x, y }, ({ x, y }) => Math.hypot(x, y))`). For a
319
+ * composite, it is the live value shape
320
+ * (`mapSpring(point, ({ x, y }) => Math.hypot(x, y))`). An invalid leaf
321
+ * throws with its path (`position.z`).
322
+ *
323
+ * The result is a stateless view, not a spring. It holds no
324
+ * subscriptions and needs no disposal. `map` runs on every read, and
325
+ * each follower reads on every source update, so `map` must be pure.
326
+ * Every call hands it the same live mirror, refreshed in place, so `map`
327
+ * must not keep what it receives. The mapped source is released with the
328
+ * first of its sources, and followers then detach and keep their current
329
+ * target.
330
+ */
331
+ declare function mapSpring<const T extends object>(sources: T & SourceShape<T>, map: (values: SourceValues<T>) => number): SpringSource;
332
+ //#endregion
333
+ //#region src/kinematic-source.d.ts
334
+ /**
335
+ * A `SpringSourceApi` whose value is in motion, so it also reports
336
+ * `velocity` and `acceleration`. A `Spring`, a `CompositeSpring`, and a
337
+ * `Ramp` report both exactly, from the model that moves the value. A
338
+ * source you write yourself can report them if it tracks a value in
339
+ * motion. A `mapSpring` result cannot, since it holds no motion of its
340
+ * own. `velocityOf` and `accelerationOf` read this slot.
341
+ */
342
+ interface KinematicSourceApi<T = number> extends SpringSourceApi<T> {
343
+ /** The rate `value` is changing, in value units per second. */
344
+ readonly velocity: T;
345
+ /** The rate `velocity` is changing, in value units per second squared. */
346
+ readonly acceleration: T;
347
+ }
348
+ /**
349
+ * A `SpringSource` whose value is in motion, so it also carries
350
+ * `velocity` and `acceleration`. Every `Spring`, `CompositeSpring`, and
351
+ * `Ramp` is one, and `velocityOf` and `accelerationOf` accept these. A
352
+ * plain `SpringSource`, such as a `mapSpring` result, is not assignable.
353
+ */
354
+ interface KinematicSource<T = number> extends SpringSource<T> {
355
+ readonly [SpringSourceSymbol]: KinematicSourceApi<T>;
356
+ }
357
+ /**
358
+ * A `KinematicSourceApi` that also reports `target`, where its motion is
359
+ * heading. A `Spring`, a `CompositeSpring`, and a `Ramp` carry it, and
360
+ * `targetOf` reads it.
361
+ */
362
+ interface TargetedSourceApi<T = number> extends KinematicSourceApi<T> {
363
+ /** The value the motion is heading toward, in value units. */
364
+ readonly target: T;
365
+ }
366
+ /**
367
+ * A `KinematicSource` that also carries the `target` its motion is heading
368
+ * toward: every `Spring`, `CompositeSpring`, and `Ramp`, and the Vue refs
369
+ * that wrap them. `targetOf` accepts these. A `mapSpring` result is not
370
+ * assignable, because a value derivation has no target.
371
+ */
372
+ interface TargetedSource<T = number> extends KinematicSource<T> {
373
+ readonly [SpringSourceSymbol]: TargetedSourceApi<T>;
374
+ }
375
+ /**
376
+ * Derives a source that reads another source's velocity: the rate its
377
+ * value changes, in value units per second. Speed-driven effects then
378
+ * use the same tools as everything else. A spring can follow it
379
+ * (`blur.target = velocityOf(motion)`), and a map can shape it
380
+ * (`mapSpring(velocityOf(motion), (v) => 1 + Math.abs(v) * 0.001)` for
381
+ * squash and stretch). A scalar source yields a scalar velocity. A
382
+ * `CompositeSpring` yields a velocity of its own shape, which you map to
383
+ * a scalar as you would its value.
384
+ *
385
+ * The result is a stateless view that holds no subscriptions and needs
386
+ * no disposal. Each read reports the source's current velocity. It
387
+ * updates when the source does and is released with it, and followers
388
+ * then detach and keep their current target.
389
+ *
390
+ * Only a `Spring`, a `CompositeSpring`, a `Ramp`, or a Vue ref that wraps
391
+ * one qualifies. A `mapSpring` result has no velocity, and `velocityOf`
392
+ * rejects it at the type level and at runtime. Take `velocityOf` of the
393
+ * spring the map reads instead.
394
+ */
395
+ declare function velocityOf<T>(source: KinematicSource<T>): SpringSource<T>;
396
+ /**
397
+ * Derives a source that reads another source's acceleration: the rate
398
+ * its velocity changes, in value units per second squared. On a spring it
399
+ * is largest just after a retarget and near the far end of each swing,
400
+ * and passes through zero where a swing moves fastest. A spring can
401
+ * follow it, and a map can turn it into an impact flash or a jelly wobble
402
+ * (`mapSpring(accelerationOf(motion), (a) => Math.min(1, Math.abs(a) * 1e-4))`).
403
+ * A scalar source yields a scalar acceleration. A `CompositeSpring`
404
+ * yields an acceleration of its own shape, which you map to a scalar as
405
+ * you would its value. A `Ramp`'s acceleration is always 0.
406
+ *
407
+ * The result is a stateless view that holds no subscriptions and needs
408
+ * no disposal. Each read reports the source's current acceleration. It
409
+ * updates when the source does and is released with it, and followers
410
+ * then detach and keep their current target.
411
+ *
412
+ * Only a `Spring`, a `CompositeSpring`, a `Ramp`, or a Vue ref that wraps
413
+ * one qualifies. A `mapSpring` result has no acceleration, and
414
+ * `accelerationOf` rejects it at the type level and at runtime. Take
415
+ * `accelerationOf` of the spring the map reads instead.
416
+ */
417
+ declare function accelerationOf<T>(source: KinematicSource<T>): SpringSource<T>;
418
+ /**
419
+ * Derives a source that reads a spring's or ramp's target: where it is
420
+ * heading, not where it is. Pair it with the source in a `mapSpring`
421
+ * shape to measure against the destination, such as remaining travel,
422
+ * progress, or a lead that anticipates the move. A `Spring` or a bounded
423
+ * `Ramp` yields a scalar target that a spring can follow. An endless
424
+ * `Ramp` yields `Infinity` or `-Infinity`, and a spring that follows it
425
+ * throws. A `CompositeSpring` yields a target of its own shape, which you
426
+ * map to a scalar as you would its value.
427
+ *
428
+ * ```ts
429
+ * const system = createSpringSystem()
430
+ * const body = system.createSpring(0)
431
+ * // The eyes look ahead by the body's remaining travel, capped at 20.
432
+ * const lead = mapSpring({ value: body, target: targetOf(body) }, ({ value, target }) =>
433
+ * Math.max(-20, Math.min(20, target - value)),
434
+ * )
435
+ * const eyes = system.createSpring(lead)
436
+ *
437
+ * body.target = 100
438
+ * system.advance(100)
439
+ * eyes.target // 20: the body still has more than 20 to go
440
+ * ```
441
+ *
442
+ * The result is a stateless view that holds no subscriptions and needs
443
+ * no disposal. Each read reports the spring's current target. It updates
444
+ * when the spring does, but assigning `target` emits nothing by itself,
445
+ * so followers pick up a new target on the next frame, when the spring
446
+ * starts toward it. Under reduced motion, a `'motion'` spring jumps to
447
+ * its new target and notifies followers at once. The view is released
448
+ * with the spring, and followers then detach and keep their current
449
+ * target.
450
+ *
451
+ * Only a `Spring`, a `CompositeSpring`, a `Ramp`, or a Vue ref that wraps
452
+ * one has a target. `targetOf` rejects a `mapSpring` result at the type
453
+ * level and at runtime.
454
+ */
455
+ declare function targetOf<T>(spring: TargetedSource<T>): SpringSource<T>;
456
+ //#endregion
457
+ //#region src/spring.d.ts
458
+ /**
459
+ * What a spring can animate toward. A number is a fixed target. A
460
+ * `SpringSource` (another spring, or a value derived from one with
461
+ * `mapSpring`) is a live value that the spring follows.
462
+ */
463
+ type SpringTarget = number | SpringSource;
464
+ /**
465
+ * What a spring animates, which decides whether reduced motion applies.
466
+ * `'motion'` moves something on screen, such as position, scale, or
467
+ * rotation, and snaps to its target under reduced motion. `'nonmotion'`
468
+ * moves nothing, such as a cross-fade, a color, a blur, or a number that
469
+ * never reaches the screen, and keeps animating under reduced motion.
470
+ * Fixed at creation. Read it back from `Spring.purpose`.
471
+ */
472
+ type Purpose = 'motion' | 'nonmotion';
473
+ /**
474
+ * One animated number, driven toward its target with damped spring
475
+ * motion. Create springs with `SpringSystem.createSpring`. A spring moves
476
+ * only when its system advances it.
477
+ *
478
+ * Reads return the exact, unrounded simulated state. Numeric writes
479
+ * (`target`, `value`, `velocity`, `jumpTo`) throw on `NaN` or an
480
+ * infinity. See https://github.com/tkofh/coily/blob/main/PRECISION.md
481
+ * for the numerical contract.
482
+ */
483
+ declare class Spring implements TargetedSource {
484
+ #private;
485
+ /** Brands the spring as a `TargetedSource` whose api is the spring itself. */
486
+ get [SpringSourceSymbol](): TargetedSourceApi<number>;
487
+ constructor(motions: MotionSet, value: number, config?: SpringDefinition, purpose?: Purpose);
488
+ /**
489
+ * The value the spring is animating toward. While following a source,
490
+ * reads return the source's value as of its last update.
491
+ *
492
+ * Assignment accepts any `SpringTarget`:
493
+ * - A number retargets smoothly, keeping the current value and velocity.
494
+ * It also ends any following.
495
+ * - A `SpringSource` (a `Spring`, or a value derived with `mapSpring`)
496
+ * makes this spring follow the source's live value.
497
+ *
498
+ * Under reduced motion, retargets apply instantly unless `purpose` is
499
+ * `'nonmotion'`.
500
+ */
501
+ get target(): number;
502
+ set target(value: SpringTarget);
503
+ /**
504
+ * The current animated value: the target plus the remaining
505
+ * displacement.
506
+ *
507
+ * Writing displaces the spring. It keeps its target, springs back from
508
+ * the written value, and notifies update listeners synchronously.
509
+ * Writing the current value is a no-op. Under reduced motion, a write
510
+ * jumps value and target together unless `purpose` is `'nonmotion'`.
511
+ */
512
+ get value(): number;
513
+ set value(value: number);
514
+ /**
515
+ * The current velocity, in value units per second.
516
+ *
517
+ * Writing flings the spring from its current value, and it settles back
518
+ * to the target. Under reduced motion, the write is ignored unless
519
+ * `purpose` is `'nonmotion'`.
520
+ */
521
+ get velocity(): number;
522
+ set velocity(value: number);
523
+ /**
524
+ * The current acceleration, in value units per second squared.
525
+ * Read-only, because stiffness and friction derive it from the current
526
+ * displacement and velocity. To fling the spring, write `velocity`.
527
+ */
528
+ get acceleration(): number;
529
+ /**
530
+ * What the spring animates, fixed at creation. Under reduced motion, a
531
+ * `'motion'` spring snaps to its target and a `'nonmotion'` spring keeps
532
+ * animating. See `Purpose`.
533
+ */
534
+ get purpose(): Purpose;
535
+ /**
536
+ * The spring's `SpringDefinition`: the one it was created with or last
537
+ * assigned, otherwise `SpringDefinition.default`. Following a source
538
+ * never changes it.
539
+ *
540
+ * Assigning reconfigures the spring in place, keeping value and
541
+ * velocity, so mid-flight changes stay smooth. `null` reverts to the
542
+ * default.
543
+ */
544
+ get config(): SpringDefinition;
545
+ /** The config's mass. */
546
+ get mass(): number;
547
+ /** The config's tension (stiffness). */
548
+ get tension(): number;
549
+ /** The config's damping (friction). */
550
+ get damping(): number;
551
+ /** The config's damping ratio. */
552
+ get dampingRatio(): number;
553
+ /** The config's precision: decimal places of the resting threshold. */
554
+ get precision(): number;
555
+ /** The config's arrival multiplier, applied to the velocity at each target crossing. */
556
+ get arrival(): number;
557
+ set config(value: SpringDefinition | null);
558
+ /**
559
+ * The time until the spring rests, in ms, solved from the motion: 0
560
+ * while resting, `Infinity` when it never settles. The spring rests at
561
+ * the first tick at or after this time. A bouncy spring can rest up to
562
+ * one oscillation earlier, when a frame samples a low point of the
563
+ * bounce. With `arrival: 0`, the time is capped at the first target
564
+ * crossing, exact to the ms.
565
+ */
566
+ get timeRemaining(): number;
567
+ /**
568
+ * Whether the spring's remaining motion is inside its resting
569
+ * threshold. Resting springs cost nothing until the next write. See
570
+ * https://github.com/tkofh/coily/blob/main/PRECISION.md for the rest
571
+ * test.
572
+ */
573
+ get isResting(): boolean;
574
+ /**
575
+ * A promise that resolves when the spring next rests or is disposed.
576
+ * It is already resolved while the spring rests and after dispose.
577
+ * Retargeting mid-flight extends the wait.
578
+ *
579
+ * @example
580
+ * ```ts
581
+ * spring.target = 300
582
+ * await spring.settled
583
+ * // the spring is at 300 and resting
584
+ * ```
585
+ */
586
+ get settled(): Promise<void>;
587
+ /**
588
+ * Snaps target and value to `value` and zeroes the velocity, with no
589
+ * animation. Listeners fire synchronously: `update`, plus `stop` if the
590
+ * spring was moving.
591
+ */
592
+ jumpTo(value: number): void;
593
+ /**
594
+ * Releases the spring permanently. It stops following, detaches
595
+ * followers (each keeps its current target), resolves `settled`, and
596
+ * notifies dispose listeners. Calling it again is a no-op.
597
+ *
598
+ * A disposed spring keeps its final value readable. Writes throw.
599
+ */
600
+ dispose(): void;
601
+ /**
602
+ * Subscribes to value changes: every tick the spring moves, plus
603
+ * synchronous `value` writes and `jumpTo` calls. Returns an unsubscribe
604
+ * function.
605
+ */
606
+ onUpdate(callback: () => void): () => void;
607
+ /**
608
+ * Subscribes to the spring leaving rest. `start` and `stop` always
609
+ * alternate, and retargeting mid-flight fires neither. Returns an
610
+ * unsubscribe function.
611
+ */
612
+ onStart(callback: () => void): () => void;
613
+ /**
614
+ * Subscribes to the spring coming to rest, whether by settling or by
615
+ * `jumpTo`. Always alternates with `start`. Returns an unsubscribe
616
+ * function.
617
+ */
618
+ onStop(callback: () => void): () => void;
619
+ /** Subscribes to `dispose`, which fires once. Returns an unsubscribe function. */
620
+ onDispose(callback: () => void): () => void;
621
+ }
622
+ //#endregion
623
+ //#region src/composite-spring.d.ts
624
+ /**
625
+ * Compile-time validation of a value shape: every channel must be a `number`,
626
+ * every branch a plain object or array with at least one channel. Used as
627
+ * `value: T & Shape<T>` so an invalid channel errors on that property rather
628
+ * than on the whole argument. It is purely structural, so interfaces
629
+ * satisfy it without index signatures, and runtime validation mirrors it
630
+ * for untyped callers. Optional and `undefined`-typed channels
631
+ * are rejected: a channel that may be absent would make two springs of the
632
+ * same declared shape structurally incompatible at runtime.
633
+ */
634
+ 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;
635
+ /**
636
+ * A partial view of a value shape: the same nesting with any subset of the
637
+ * numeric channels present. A write leaves absent or `undefined` channels
638
+ * unchanged.
639
+ */
640
+ type PartialShape<T> = T extends number ? number : { readonly [K in keyof T]?: PartialShape<T[K]> | undefined; };
641
+ /**
642
+ * A deep-readonly view of a value shape. Composite reads (`target`, `value`,
643
+ * `velocity`, `acceleration`) return the spring's live mirror objects, and
644
+ * every read refreshes them in place, so their channels are typed read-only.
645
+ */
646
+ type ReadonlyShape<T> = T extends number ? number : { readonly [K in keyof T]: ReadonlyShape<T[K]>; };
647
+ /**
648
+ * Configuration for a composite spring: a single `SpringDefinition` applied to every
649
+ * channel, or an object mirroring the value shape with configs at any level.
650
+ * A config at a subtree applies to every channel below it.
651
+ * `null` reverts to the default config.
652
+ * Configs are always `SpringDefinition` instances (from `defineSpring`), so a
653
+ * plain object is unambiguously a config shape and any non-config leaf it
654
+ * reaches is an error.
655
+ */
656
+ type ConfigShape<T> = SpringDefinition | null | (T extends number ? never : { readonly [K in keyof T]?: ConfigShape<T[K]> | undefined; });
657
+ /**
658
+ * What each channel of a composite spring animates: a single `Purpose`
659
+ * for every channel, or a shape mirroring the value with purposes at any
660
+ * level. A purpose at a subtree covers every channel below it, and a
661
+ * channel that no purpose reaches defaults to `'motion'`. The purposes
662
+ * are fixed when the spring is created. See `Purpose`.
663
+ */
664
+ type PurposeShape<T> = Purpose | (T extends number ? never : { readonly [K in keyof T]?: PurposeShape<T[K]> | undefined; });
665
+ /**
666
+ * A partial target shape: the same nesting as the value with any subset
667
+ * of the channels present, each taking a number to animate toward or a
668
+ * scalar `SpringSource` to follow. A write leaves absent or `undefined`
669
+ * channels unchanged.
670
+ */
671
+ type TargetShape<T> = T extends number ? number | SpringSource : { readonly [K in keyof T]?: TargetShape<T[K]> | undefined; };
672
+ /**
673
+ * What a composite spring can animate toward: a partial shape mixing
674
+ * numbers and scalar `SpringSource`s per channel, or another composite
675
+ * spring of the same shape to follow channel by channel.
676
+ */
677
+ type CompositeSpringTarget<T extends object> = TargetShape<T> | CompositeSpring<T>;
678
+ /**
679
+ * A composite spring over a fixed numeric shape: a nested plain object
680
+ * or array whose leaves are all numbers. Each leaf runs an independent
681
+ * spring, called a channel. Targets, values, velocities, and configs read
682
+ * and write as shapes, writes can name any subset of the channels, and
683
+ * events coalesce across channels. Create one with
684
+ * `SpringSystem.createSpring`.
685
+ *
686
+ * A composite spring is a `SpringSource` of its value shape. Springs
687
+ * follow only scalar sources, so use `mapSpring` to derive one from the
688
+ * composite, alone or at the leaves of a shape.
689
+ *
690
+ * A write that names an unknown channel or gives one a non-finite value
691
+ * throws with the channel's path (`position.z`) and changes nothing.
692
+ */
693
+ declare class CompositeSpring<in out T extends object> implements TargetedSource<ReadonlyShape<T>> {
694
+ #private;
695
+ /** Brands the composite as a `TargetedSource` whose api is the composite itself. */
696
+ get [SpringSourceSymbol](): TargetedSourceApi<ReadonlyShape<T>>;
697
+ constructor(motions: MotionSet, value: T & Shape<T>, config?: ConfigShape<T>, purpose?: PurposeShape<T>);
698
+ /**
699
+ * The value each channel is animating toward, as a read-only mirror of
700
+ * the shape. The mirror is reused and refreshed in place on each read,
701
+ * so read it fresh instead of holding it across frames.
702
+ *
703
+ * Assignment accepts a `CompositeSpringTarget`:
704
+ * - A partial shape retargets the channels it names, each to a number
705
+ * or to a scalar `SpringSource` to follow, such as a `Spring` or a
706
+ * `mapSpring` result. Naming a channel detaches it from any leader.
707
+ * - A `CompositeSpring` of the same shape becomes the leader, and each
708
+ * channel follows the matching leader channel.
709
+ *
710
+ * Unknown channels throw with their path. Under reduced motion,
711
+ * retargets apply instantly, except on channels whose `purpose` is
712
+ * `'nonmotion'`.
713
+ */
714
+ get target(): ReadonlyShape<T>;
715
+ set target(value: CompositeSpringTarget<T>);
716
+ /**
717
+ * The current animated value of every channel, as a read-only mirror of
718
+ * the shape. The mirror is reused and refreshed in place on each read,
719
+ * so read it fresh instead of holding it across frames.
720
+ *
721
+ * Assignment displaces the channels a partial shape names. Each keeps
722
+ * its target and springs back from the written value. The write fires
723
+ * one coalesced `update` synchronously. Under reduced motion, the named
724
+ * channels jump to the written values, targets included, except on
725
+ * channels whose `purpose` is `'nonmotion'`.
726
+ */
727
+ get value(): ReadonlyShape<T>;
728
+ set value(value: PartialShape<T>);
729
+ /**
730
+ * The current velocity of every channel in value units per second, as a
731
+ * read-only mirror of the shape. The mirror is reused and refreshed in
732
+ * place on each read.
733
+ *
734
+ * Assignment flings the channels a partial shape names. Under reduced
735
+ * motion, writes are ignored, except on channels whose `purpose` is
736
+ * `'nonmotion'`.
737
+ */
738
+ get velocity(): ReadonlyShape<T>;
739
+ set velocity(value: PartialShape<T>);
740
+ /**
741
+ * The current acceleration of every channel in value units per second
742
+ * squared, as a read-only mirror of the shape. The mirror is reused and
743
+ * refreshed in place on each read. To fling channels, write `velocity`.
744
+ */
745
+ get acceleration(): ReadonlyShape<T>;
746
+ /**
747
+ * The shared config when every channel resolves to the same
748
+ * `SpringDefinition`, and `null` when they differ.
749
+ *
750
+ * Assignment accepts a `ConfigShape`: one config for every channel,
751
+ * `null` to revert every channel to the default, or a shape mirroring
752
+ * the value whose configs each cover their subtree. Non-config leaves
753
+ * throw with their path. Assigning a `null` read back reverts every
754
+ * channel to the default rather than restoring the mix.
755
+ */
756
+ get config(): SpringDefinition | null;
757
+ set config(value: ConfigShape<T>);
758
+ /**
759
+ * The shared purpose when every channel resolves to the same `Purpose`,
760
+ * and `null` when they differ. The purposes are fixed when the spring is
761
+ * created. A `'nonmotion'` channel opts out of reduced motion. See
762
+ * `Purpose`.
763
+ */
764
+ get purpose(): Purpose | null;
765
+ /** The largest `timeRemaining` across channels, in ms. */
766
+ get timeRemaining(): number;
767
+ /** Whether every channel is resting. */
768
+ get isResting(): boolean;
769
+ /**
770
+ * A promise that resolves when every channel next rests. It is already
771
+ * resolved while the composite rests or after dispose. Retargeting any
772
+ * channel mid-flight extends the wait, and disposing resolves it.
773
+ */
774
+ get settled(): Promise<void>;
775
+ /**
776
+ * Snaps the channels a partial shape names to the given values with no
777
+ * animation, setting both target and value. Fires one `update`
778
+ * synchronously, plus `stop` if the jump leaves every channel resting.
779
+ */
780
+ jumpTo(value: PartialShape<T>): void;
781
+ /**
782
+ * Releases every channel permanently. Each channel stops following, and
783
+ * its followers detach and keep their current targets. `settled`
784
+ * resolves and dispose listeners fire. Calling it again is a no-op.
785
+ * Afterward, final values stay readable and writes throw.
786
+ */
787
+ dispose(): void;
788
+ /**
789
+ * Subscribes to coalesced value changes. `update` fires at most once per
790
+ * tick, after every channel advances, and once per synchronous write that
791
+ * moves a channel, such as a `value` write or `jumpTo`. Returns an
792
+ * unsubscribe function.
793
+ */
794
+ onUpdate(callback: () => void): () => void;
795
+ /**
796
+ * Subscribes to the composite leaving rest. `start` fires when a channel
797
+ * starts to move and no other channel was moving. It always alternates
798
+ * with `stop`. Returns an unsubscribe function.
799
+ */
800
+ onStart(callback: () => void): () => void;
801
+ /**
802
+ * Subscribes to the composite coming to rest. `stop` fires when every
803
+ * channel rests, after that tick's final `update`. It always alternates
804
+ * with `start`. Returns an unsubscribe function.
805
+ */
806
+ onStop(callback: () => void): () => void;
807
+ /** Subscribes to `dispose`, which fires once. Returns an unsubscribe function. */
808
+ onDispose(callback: () => void): () => void;
809
+ }
810
+ //#endregion
811
+ //#region src/ramp.d.ts
812
+ /** Options for `SpringSystem.createRamp`. */
813
+ interface RampOptions {
814
+ /**
815
+ * The ramp's speed, in value units per second. Must be finite and
816
+ * >= 0. A rate of 0 rests the ramp where it is.
817
+ */
818
+ rate: number;
819
+ /**
820
+ * Where the ramp heads. `Infinity` or `-Infinity` makes it endless in
821
+ * that direction.
822
+ * @default the starting value, so the ramp starts at rest
823
+ */
824
+ target?: number | undefined;
825
+ /**
826
+ * What the ramp drives, which decides whether reduced motion applies.
827
+ * Under reduced motion, a `'motion'` ramp jumps to a bounded target and
828
+ * holds still on an endless one. A `'nonmotion'` ramp, such as a clock
829
+ * for logic, keeps moving. Fixed for the ramp's life. See `Purpose`.
830
+ * @default 'motion'
831
+ */
832
+ purpose?: Purpose | undefined;
833
+ }
834
+ /**
835
+ * A value that moves toward its target at a fixed speed, in a straight
836
+ * line, and stops there. Create ramps with `SpringSystem.createRamp`.
837
+ *
838
+ * A ramp is a `SpringSource`, so a spring can follow it. With the
839
+ * default `arrival`, the spring integrates against the ramp's steady
840
+ * motion exactly, where a target assigned once per frame would reach it
841
+ * as a staircase. With any other `arrival`, the spring holds the target
842
+ * still within each of a frame's sub-steps, one stair per sub-step.
843
+ * Typical uses are an endless spin, a heading that turns at a capped
844
+ * speed, and a clock in ms mapped into a shape with `mapSpring`.
845
+ *
846
+ * ```ts
847
+ * const system = createSpringSystem()
848
+ * const sweep = system.createRamp(0, { rate: 270, target: Infinity })
849
+ * const ring = system.createSpring(sweep, defineSpring({ duration: 300, dampingRatio: 1 }))
850
+ *
851
+ * system.advance(1000)
852
+ * sweep.value // 270
853
+ * ```
854
+ *
855
+ * A ramp costs nothing per frame unless something watches it: an
856
+ * `onUpdate` listener, a following spring, or, on a bounded ramp, an
857
+ * `onStop` listener or a pending `settled`. An unwatched ramp still
858
+ * moves, and a read reports where it is.
859
+ *
860
+ * Ramps move on the system clock. It counts frame time, `advance` calls,
861
+ * and the time a started system spends asleep, and stops while the
862
+ * system is stopped. A sleeping system skips the time the page is
863
+ * hidden. An awake system gets no frames in a hidden tab, so the hidden
864
+ * spell counts as one frame on return: `adjustedLag` ms if it lasted
865
+ * longer than `lagThreshold`, in full otherwise or with lag clamping off.
866
+ *
867
+ * The clock holds still through synchronous code, so every read in one
868
+ * event handler or render sees the same moment. A ramp created and read
869
+ * with no `await` in between reads its starting value.
870
+ *
871
+ * Under reduced motion, a `'motion'` ramp jumps to a bounded target, and
872
+ * an endless one holds still until reduced motion turns off, then
873
+ * resumes. A `'nonmotion'` ramp keeps moving.
874
+ */
875
+ declare class Ramp implements TargetedSource {
876
+ #private;
877
+ /** Brands the ramp as a `TargetedSource` whose api is the ramp itself. */
878
+ get [SpringSourceSymbol](): TargetedSourceApi<number>;
879
+ constructor(motions: MotionSet, from: number, options: RampOptions);
880
+ /**
881
+ * The current value.
882
+ *
883
+ * Assignment accepts a finite number. It moves the ramp there at once,
884
+ * keeping its target and rate, and notifies update listeners
885
+ * synchronously. Writing the
886
+ * current value does nothing. Under reduced motion, a `'motion'` ramp
887
+ * with a bounded target rests at the written value, which becomes its
888
+ * target too.
889
+ */
890
+ get value(): number;
891
+ set value(value: number);
892
+ /**
893
+ * Where the ramp is heading, or `Infinity` or `-Infinity` for an
894
+ * endless ramp.
895
+ *
896
+ * Assignment accepts any number but `NaN`, and sends the ramp toward the
897
+ * new target at its current rate.
898
+ * It emits `start` if the ramp was resting, and no `update`, since the
899
+ * value has not moved. Under reduced motion, a `'motion'` ramp jumps to
900
+ * a bounded target, notifying update listeners, and holds still toward
901
+ * an endless one.
902
+ */
903
+ get target(): number;
904
+ set target(target: number);
905
+ /**
906
+ * The ramp's speed, in value units per second.
907
+ *
908
+ * Assignment accepts any finite number >= 0 and applies from the
909
+ * current value on. A rate of 0 rests the ramp where it is.
910
+ */
911
+ get rate(): number;
912
+ set rate(rate: number);
913
+ /**
914
+ * The current velocity, in value units per second. It is `rate`
915
+ * signed toward the target while the ramp moves, and 0 at rest.
916
+ */
917
+ get velocity(): number;
918
+ /** Always 0, since a ramp moves at a constant speed. */
919
+ get acceleration(): number;
920
+ /**
921
+ * What the ramp drives, `'motion'` or `'nonmotion'`, fixed when the
922
+ * ramp was created. A `'nonmotion'` ramp ignores reduced motion. See
923
+ * `Purpose`.
924
+ */
925
+ get purpose(): Purpose;
926
+ /**
927
+ * Whether the ramp is still: at its target, at a rate of 0, held under
928
+ * reduced motion, or disposed.
929
+ */
930
+ get isResting(): boolean;
931
+ /**
932
+ * The time until the ramp reaches its target, in ms. It is 0 at rest,
933
+ * and `Infinity` while the ramp moves toward an endless target.
934
+ */
935
+ get timeRemaining(): number;
936
+ /**
937
+ * A promise that resolves when the ramp next comes to rest. Already
938
+ * resolved while the ramp rests and after dispose. A pending
939
+ * `settled` keeps a bounded ramp in the frame loop, so the promise
940
+ * resolves on the frame the ramp arrives.
941
+ *
942
+ * ```ts
943
+ * ramp.target = 100
944
+ * await ramp.settled
945
+ * // the ramp is at 100
946
+ * ```
947
+ */
948
+ get settled(): Promise<void>;
949
+ /**
950
+ * Rests the ramp at `value`, which becomes both its value and target.
951
+ * Notifies update listeners synchronously, and emits `stop` if the ramp
952
+ * was moving.
953
+ */
954
+ jumpTo(value: number): void;
955
+ /**
956
+ * Releases the ramp permanently. It stops where it is, `settled`
957
+ * resolves, and dispose listeners run. Followers detach and keep their
958
+ * current target. Calling it again does nothing.
959
+ *
960
+ * The final value stays readable, and writes throw.
961
+ */
962
+ dispose(): void;
963
+ /**
964
+ * Subscribes to value changes: every frame the ramp moves, plus
965
+ * synchronous `value` writes, `jumpTo` calls, and reduced-motion jumps.
966
+ * A subscribed listener keeps the ramp in the frame loop. Returns an
967
+ * unsubscribe function.
968
+ */
969
+ onUpdate(callback: () => void): () => void;
970
+ /**
971
+ * Subscribes to the ramp leaving rest. `start` and `stop` alternate.
972
+ * Returns an unsubscribe function.
973
+ */
974
+ onStart(callback: () => void): () => void;
975
+ /**
976
+ * Subscribes to the ramp coming to rest: arriving at its target,
977
+ * `jumpTo`, a rate of 0, or holding under reduced motion. A subscribed
978
+ * listener keeps a bounded ramp in the frame loop, so the event fires
979
+ * on the frame it arrives. Returns an unsubscribe function.
980
+ */
981
+ onStop(callback: () => void): () => void;
982
+ /** Subscribes to `dispose`, which fires once. Returns an unsubscribe function. */
983
+ onDispose(callback: () => void): () => void;
984
+ }
985
+ //#endregion
986
+ //#region src/ticker.d.ts
987
+ /** Frame timing options for a spring system. */
988
+ interface TickerOptions {
989
+ /**
990
+ * Frame-rate cap, in frames per second. 0 means uncapped: one tick per
991
+ * displayed frame, at whatever rate the screen runs. Capped ticks land
992
+ * on whole display frames near the requested rate, and each receives
993
+ * the true elapsed time. Must be >= 0.
994
+ * @default 0
995
+ */
996
+ readonly fps?: number | undefined;
997
+ /**
998
+ * The longest frame gap, in ms, that counts as a normal frame. A longer
999
+ * gap, such as a backgrounded tab or a blocking task, is lag, and the
1000
+ * ticker replaces it with `adjustedLag` so springs do not teleport. 0
1001
+ * disables lag clamping and reads back as 0. Must be >= 0.
1002
+ * @default 500
1003
+ */
1004
+ readonly lagThreshold?: number | undefined;
1005
+ /**
1006
+ * The elapsed time, in ms, that replaces a lagging frame, capped at
1007
+ * `lagThreshold` ms. It reads back as given, even above `lagThreshold`.
1008
+ * Must be >= 0.
1009
+ * @default 33
1010
+ */
1011
+ readonly adjustedLag?: number | undefined;
1012
+ }
1013
+ //#endregion
1014
+ //#region src/system.d.ts
1015
+ /**
1016
+ * Per-spring options for `SpringSystem.createSpring`, separate from the
1017
+ * physical `config`.
1018
+ */
1019
+ interface SpringOptions {
1020
+ /**
1021
+ * What the spring animates. Under reduced motion, a `'motion'` spring
1022
+ * snaps to its target and a `'nonmotion'` spring (a cross-fade, a color,
1023
+ * a blur) keeps animating. Fixed for the spring's life. See `Purpose`.
1024
+ * @default 'motion'
1025
+ */
1026
+ purpose?: Purpose | undefined;
1027
+ }
1028
+ /**
1029
+ * Per-spring options for a composite `SpringSystem.createSpring`, separate
1030
+ * from the physical `config`.
1031
+ */
1032
+ interface CompositeSpringOptions<T extends object> {
1033
+ /**
1034
+ * What each channel animates, which decides its reduced-motion behavior.
1035
+ * Give one `Purpose` for every channel, or a shape mirroring the value
1036
+ * with purposes at any level. A subtree's purpose covers every channel
1037
+ * below it, and unreached channels default to `'motion'`. Fixed for the
1038
+ * spring's life. See `PurposeShape`.
1039
+ * @default 'motion'
1040
+ */
1041
+ purpose?: PurposeShape<T> | undefined;
1042
+ }
1043
+ interface SpringSystemOptions extends TickerOptions {
1044
+ /**
1045
+ * How far a spring can trail a source it follows within one frame, in
1046
+ * the value's own units. Frames that would exceed it split into internal
1047
+ * sub-steps (at most 8), keeping followed chains accurate through slow
1048
+ * frames and sudden retargets. Smaller is tighter and costs more solver
1049
+ * work, but never finer than the follower's resting precision. Listeners
1050
+ * still fire once per frame, and springs that follow nothing are
1051
+ * unaffected.
1052
+ * @default 0.1
1053
+ */
1054
+ couplingTolerance?: number | undefined;
1055
+ /**
1056
+ * Logs the count of active motions to the console after each frame
1057
+ * where it changed.
1058
+ * @default false
1059
+ */
1060
+ debug?: boolean | undefined;
1061
+ /**
1062
+ * When reduced motion applies, making `'motion'` springs and ramps jump
1063
+ * to their targets instead of animating. `'user'` follows the OS
1064
+ * prefers-reduced-motion setting live, and a switch to reduced finishes
1065
+ * in-flight motion at once. `'always'` and `'never'` force one behavior.
1066
+ * @default 'user'
1067
+ */
1068
+ reducedMotion?: 'user' | 'always' | 'never' | undefined;
1069
+ }
1070
+ /**
1071
+ * Owns springs and ramps and advances them over time. Create one with
1072
+ * `createSpringSystem`. Then either `start()` it to animate on real
1073
+ * frames, or call `advance` yourself for manual stepping.
1074
+ */
1075
+ interface SpringSystem {
1076
+ /**
1077
+ * Creates a spring at rest at `value`. Without `config`, the spring
1078
+ * uses the default: critically damped, settling in about 500 ms. To
1079
+ * follow a source from creation, pass it in place of `value`.
1080
+ *
1081
+ * `options.purpose` marks what the spring animates: `'nonmotion'`
1082
+ * keeps animating under reduced motion, `'motion'` (the default) snaps.
1083
+ */
1084
+ createSpring(value: number, config?: SpringDefinition, options?: SpringOptions): Spring;
1085
+ /**
1086
+ * Creates a spring that already follows `source`, which can be any
1087
+ * scalar `SpringSource`: a `Spring`, a `Ramp`, or a value derived with
1088
+ * `mapSpring`, `velocityOf`, `accelerationOf`, or `targetOf`. Passing
1089
+ * a composite source throws, so derive a scalar from it with
1090
+ * `mapSpring`. The spring starts at the source's current value, the same
1091
+ * as creating it there and assigning `source` to its `target`.
1092
+ *
1093
+ * `options.purpose` marks what the spring animates: `'nonmotion'`
1094
+ * keeps animating under reduced motion, `'motion'` (the default) snaps.
1095
+ */
1096
+ createSpring(source: SpringSource, config?: SpringDefinition, options?: SpringOptions): Spring;
1097
+ /**
1098
+ * Creates a composite spring over a numeric shape: a plain object or
1099
+ * array, nested arbitrarily, whose leaves are all numbers. Each leaf
1100
+ * becomes an independently sprung channel.
1101
+ *
1102
+ * `config` applies per channel: a single `SpringDefinition` for every
1103
+ * channel, or a shape mirroring the value with configs at any level.
1104
+ * `options.purpose` marks reduced-motion behavior the same way, per
1105
+ * channel. A `'nonmotion'` channel keeps animating.
1106
+ */
1107
+ createSpring<T extends object>(value: T & Shape<T>, config?: ConfigShape<T>, options?: CompositeSpringOptions<T>): CompositeSpring<T>;
1108
+ /**
1109
+ * Creates a ramp at `from`: a value that moves in a straight line toward
1110
+ * `options.target` at `options.rate` value units per second, then stops.
1111
+ * Without a target it starts at rest, and `Infinity` or `-Infinity`
1112
+ * makes it endless. A spring following a ramp with the default `arrival`
1113
+ * integrates its steady motion exactly, not a per-frame staircase.
1114
+ *
1115
+ * ```ts
1116
+ * // An endless spin at 270 deg/s, with a spring lagging behind it.
1117
+ * const sweep = system.createRamp(0, { rate: 270, target: Infinity })
1118
+ * const ring = system.createSpring(sweep)
1119
+ *
1120
+ * // A clock: 0 to 600 over 600 ms, mapped into a pulse.
1121
+ * const clock = system.createRamp(0, { rate: 1000, target: 600 })
1122
+ * const pulse = system.createSpring(mapSpring(clock, (ms) => Math.sin((Math.PI * ms) / 600)))
1123
+ * ```
1124
+ *
1125
+ * Under reduced motion, a `'motion'` ramp (the default `purpose`) jumps
1126
+ * to a bounded target and holds still on an endless one. A `'nonmotion'`
1127
+ * ramp keeps moving.
1128
+ */
1129
+ createRamp(from: number, options: RampOptions): Ramp;
1130
+ /**
1131
+ * Advances every moving spring and ramp by `dt` ms, for manual stepping
1132
+ * in place of `start()`: tests, custom loops, offline rendering. Throws
1133
+ * unless `dt` is finite and >= 0.
1134
+ */
1135
+ advance(dt: number): void;
1136
+ /**
1137
+ * Begins animating on real frames: one tick per displayed frame, through
1138
+ * `requestAnimationFrame` in the browser and a `setTimeout` fallback
1139
+ * elsewhere. An idle system sleeps, scheduling no frames while every
1140
+ * spring rests and no ramp advances with the frame loop. Throws after
1141
+ * `dispose`.
1142
+ */
1143
+ start(): void;
1144
+ /** Stops animating. Springs and ramps hold their state until `start` or `advance`. */
1145
+ stop(): void;
1146
+ /**
1147
+ * Whether the system is between `start()` and `stop()`. Sleeping while
1148
+ * idle still counts as running.
1149
+ */
1150
+ readonly running: boolean;
1151
+ /**
1152
+ * Stops the system for good and removes the page listeners it added at
1153
+ * creation: visibility and, with `reducedMotion: 'user'`, the media
1154
+ * query. Dispose a system you no longer need. `useSpringSystem` disposes
1155
+ * the system it creates on unmount. Calling it again is a no-op.
1156
+ *
1157
+ * After dispose, `start()` throws. `advance` still steps the springs
1158
+ * and ramps, and `reducedMotion` no longer follows the user setting.
1159
+ */
1160
+ dispose(): void;
1161
+ /**
1162
+ * Whether reduced motion is on, per the `reducedMotion` option and, in
1163
+ * `'user'` mode, the live OS setting. While on, `'motion'` springs and
1164
+ * ramps jump instead of animating, and `'nonmotion'` ones still animate.
1165
+ * Read it to gate decorative effects of your own.
1166
+ */
1167
+ readonly reducedMotion: boolean;
1168
+ /**
1169
+ * How far a spring can trail a source it follows within one frame, in
1170
+ * the value's own units. See the `couplingTolerance` option.
1171
+ * Assignment accepts any finite number >= 0 and applies from the next
1172
+ * frame. 0 means as fine as the follower's resting precision allows.
1173
+ */
1174
+ couplingTolerance: number;
1175
+ /**
1176
+ * Frame-rate cap, in frames per second. 0 means uncapped: one tick per
1177
+ * displayed frame, whatever rate the screen runs at. Capped ticks land
1178
+ * on whole display frames and receive the true elapsed time. Assignment
1179
+ * accepts any number >= 0.
1180
+ */
1181
+ fps: number;
1182
+ /**
1183
+ * Frame gap, in ms, above which the system treats the gap as lag (a
1184
+ * backgrounded tab, a blocking task) and replaces it with `adjustedLag`,
1185
+ * so springs do not teleport when frames resume. Assignment accepts any
1186
+ * number >= 0, and 0 disables lag clamping.
1187
+ */
1188
+ lagThreshold: number;
1189
+ /**
1190
+ * The elapsed ms that replaces a lagging frame's gap. A frame uses at
1191
+ * most `lagThreshold` ms of it. Reads return the value last set, even
1192
+ * above `lagThreshold`. Assignment accepts any number >= 0.
1193
+ */
1194
+ adjustedLag: number;
1195
+ }
1196
+ /**
1197
+ * Creates a spring system: the entry point of the library.
1198
+ *
1199
+ * @example
1200
+ * ```ts
1201
+ * const system = createSpringSystem()
1202
+ * system.start()
1203
+ *
1204
+ * const spring = system.createSpring(0)
1205
+ * spring.onUpdate(() => {
1206
+ * element.style.translate = `${spring.value}px 0`
1207
+ * })
1208
+ * spring.target = 300
1209
+ * ```
1210
+ */
1211
+ declare function createSpringSystem(options?: SpringSystemOptions): SpringSystem;
1212
+ //#endregion
1213
+ export { SpringSourceSymbol as A, accelerationOf as C, SourceValues as D, SourceShape as E, SpringSource as O, TargetedSourceApi as S, velocityOf as T, Spring as _, createSpringSystem as a, KinematicSourceApi as b, CompositeSpring as c, PartialShape as d, PurposeShape as f, Purpose as g, TargetShape as h, SpringSystemOptions as i, mapSpring as j, SpringSourceApi as k, CompositeSpringTarget as l, Shape as m, SpringOptions as n, Ramp as o, ReadonlyShape as p, SpringSystem as r, RampOptions as s, CompositeSpringOptions as t, ConfigShape as u, SpringTarget as v, targetOf as w, TargetedSource as x, KinematicSource as y };