anim-engine 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL.md ADDED
@@ -0,0 +1,515 @@
1
+ ---
2
+ name: anim-engine
3
+ description: Renderer-agnostic numeric animation library for JavaScript runtimes. Use when a user has installed 'anim-engine' and needs help writing animation code: tweens, keyframes, timelines, springs, smooth damp, lerp, or color interpolation. Covers all exported primitives, common recipes, game engine integration, and the ticker setup pattern.
4
+ metadata:
5
+ triggers: "anim-engine, animation, createAnimation, createTimeline, createSpring, createSmoothDamp, createLerp, createSmoothClamp, lerpRgba, hexToRgba, animation library, js animation, tween, keyframe animation, timeline animation, spring physics"
6
+ ---
7
+
8
+ # anim-engine
9
+
10
+ A renderer-agnostic animation library for JavaScript runtimes. Pure-numeric, tree-shakeable, zero dependencies. Works with DOM, Canvas, WebGL, Three.js, PixiJS, or any other renderer.
11
+
12
+ ```
13
+ npm install anim-engine
14
+ ```
15
+
16
+ ---
17
+
18
+ ## Getting Started
19
+
20
+ Import the primitives you need and start a ticker once. The ticker drives all animations — it does **not** auto-start.
21
+
22
+ ```ts
23
+ import { createAnimation, getTicker } from "anim-engine";
24
+
25
+ // Start the ticker (rAF mode)
26
+ getTicker().start();
27
+
28
+ const anim = createAnimation({
29
+ from: 0,
30
+ to: 100,
31
+ durationMs: 1000,
32
+ ease: "outCubic",
33
+ onUpdate: (value) => (element.style.transform = `translateX(${value}px)`),
34
+ });
35
+
36
+ await anim.play();
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Ticker Setup
42
+
43
+ The ticker must be started before any animations will advance. There are two modes:
44
+
45
+ ### Automatic (rAF)
46
+
47
+ ```ts
48
+ import { getTicker } from "anim-engine";
49
+ getTicker().start(); // Drives via requestAnimationFrame
50
+ ```
51
+
52
+ ### Custom game loop for all animations
53
+
54
+ Pass `deltaMs` (milliseconds) each frame:
55
+
56
+ ```ts
57
+ const ticker = getTicker();
58
+ function loop(dt: number) {
59
+ ticker.update(dt);
60
+ }
61
+ ```
62
+
63
+ ### External ticker (game engine integration)
64
+
65
+ Pass an `ExternalTicker` to individual animations to use the engine's own tick:
66
+
67
+ ```ts
68
+ type ExternalTicker = {
69
+ add: (handler: TickHandler) => void;
70
+ remove: (handler: TickHandler) => void;
71
+ };
72
+ ```
73
+
74
+ **Three.js:**
75
+
76
+ ```ts
77
+ const clock = new THREE.Clock();
78
+ const ticker = getTicker();
79
+
80
+ function animate() {
81
+ requestAnimationFrame(animate);
82
+ ticker.update(clock.getDelta() * 1000);
83
+ renderer.render(scene, camera);
84
+ }
85
+ animate();
86
+ ```
87
+
88
+ **PixiJS:**
89
+
90
+ ```ts
91
+ const ticker = getTicker();
92
+ app.ticker.add((delta) => ticker.update(delta.deltaMS));
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Primitives
98
+
99
+ Two animation families:
100
+
101
+ | Family | Factories | What it does |
102
+ | ----------------- | ------------------------------------------------ | ------------------------------------------------------------ |
103
+ | **Timed** | `createAnimation`, `createTimeline` | Fixed motion path. Runs once per `play()`. |
104
+ | **Continuous** | `createSpring`, `createSmoothDamp`, `createLerp` | Chases a live target every frame until stopped. |
105
+ | **Pure function** | `createSmoothClamp` | Asymptotic saturation — returns `(input: number) => number`. |
106
+
107
+ ---
108
+
109
+ ## Timed: `createAnimation`
110
+
111
+ ### Single tween
112
+
113
+ Animates from one value to another over a duration with easing.
114
+
115
+ ```ts
116
+ const anim = createAnimation({
117
+ from: 0, // number or () => number
118
+ to: 100, // number or () => number
119
+ durationMs: 1000, // number or () => number
120
+ ease: "outCubic", // EaseName or custom EaseFunction
121
+ onUpdate: (value, velocity) => {
122
+ /* assign value */
123
+ },
124
+ onProgress: (progress) => {}, // 0 → 1
125
+ onStarted: () => {},
126
+ onEnded: () => {},
127
+ ticker: customTicker, // optional ExternalTicker
128
+ });
129
+
130
+ await anim.play(); // Promise resolves when animation completes
131
+ ```
132
+
133
+ ### Keyframes
134
+
135
+ Multiple segments, each with its own value, duration (`gap`), and easing.
136
+
137
+ ```ts
138
+ createAnimation({
139
+ keyframes: [{ value: 0 }, { value: 50, gap: 300, ease: "outCubic" }, { value: 100, gap: 400 }],
140
+ onUpdate: (v) => (sprite.x = v),
141
+ }).play();
142
+ ```
143
+
144
+ The first keyframe provides the starting value; `gap` is the duration from the previous keyframe.
145
+
146
+ ### Control handle
147
+
148
+ ```ts
149
+ anim.play() → Promise<void> // Start or restart from beginning
150
+ anim.pause() → void // Freeze at current position
151
+ anim.resume() → void // Continue from paused position
152
+ anim.stop() → void // Stop; promise resolves at current value
153
+ anim.skipToEnd() → void // Jump to end value; promise resolves
154
+ anim.setProgress(p) → void // Jump to progress [0, 1]; pauses if playing
155
+
156
+ anim.value → number // Current interpolated value (readonly)
157
+ anim.velocity → number // Current velocity in units/sec (readonly)
158
+ anim.progress → number // Progress 0→1 (readonly)
159
+ anim.status → "playing" | "paused" | "stopped"
160
+ anim.durationMs → number // Total duration (readonly)
161
+ ```
162
+
163
+ ### Repeat & yoyo
164
+
165
+ Since `DynamicValue` is re-evaluated each `play()` call, alternating the source toggles direction:
166
+
167
+ ```ts
168
+ let forward = true;
169
+
170
+ for (let i = 0; i < 6; i++) {
171
+ await createAnimation({
172
+ from: () => (forward ? 1 : 1.3),
173
+ to: () => (forward ? 1.3 : 1),
174
+ durationMs: 600,
175
+ ease: "inOutSine",
176
+ onUpdate: (v) => sprite.scale.set(v),
177
+ }).play();
178
+ forward = !forward;
179
+ }
180
+ ```
181
+
182
+ ---
183
+
184
+ ## Timed: `createTimeline`
185
+
186
+ Orchestrate multiple keyframe animations in parallel or sequence.
187
+
188
+ ```ts
189
+ import { createTimeline } from "anim-engine";
190
+
191
+ const timeline = createTimeline(
192
+ [
193
+ // Layer 1 — starts at time 0
194
+ {
195
+ at: 0,
196
+ animation: {
197
+ keyframes: [{ value: 0 }, { value: 1, gap: 500 }],
198
+ onEnded: () => console.log("layer 1 done"),
199
+ },
200
+ },
201
+ // Layer 2 — starts at time 0 (parallel)
202
+ {
203
+ at: 0,
204
+ animation: {
205
+ keyframes: [{ value: -100 }, { value: 0, gap: 800, ease: "outBack" }],
206
+ onUpdate: (v) => (sprite.y = v),
207
+ },
208
+ },
209
+ // Layer 3 — starts 200ms after layer 2 ends
210
+ {
211
+ gap: 200,
212
+ animation: {
213
+ keyframes: [{ value: 0 }, { value: 50, gap: 300 }],
214
+ onEnded: () => console.log("layer 3 done"),
215
+ },
216
+ },
217
+ ],
218
+ {
219
+ // values[i] / velocities[i] correspond to layer i in definition order
220
+ onUpdate: (values, velocities) => {
221
+ sprite.x = values[0]; // layer 1 (fade in)
222
+ sprite.y = values[1]; // layer 2 (slide up)
223
+ sprite.rotation = values[2]; // layer 3 (spin)
224
+ },
225
+ onProgress: (p) => console.log(p),
226
+ },
227
+ );
228
+ ```
229
+
230
+ - Layer positions: `at: DynamicValue` (absolute ms) or `gap: number` (relative to previous layer end).
231
+ - **Each layer supports its own callbacks.** Pass `onStarted`, `onUpdate`, `onProgress`, and `onEnded` inside the layer's `animation` config to react to per-layer events independently.
232
+ - Timeline handle has the same shape as `Animation`: `play()`, `pause()`, `resume()`, `stop()`, `skipToEnd()`, `setProgress()`.
233
+ - `values` and `velocities` are arrays — one entry per layer in definition order.
234
+
235
+ ---
236
+
237
+ ## Continuous: `createSpring`
238
+
239
+ Mass-spring-damper physics. Bouncy or elastic movement toward a live target.
240
+
241
+ ```ts
242
+ const spring = createSpring({
243
+ to: () => mouseX, // Re-evaluated every frame
244
+ stiffness: 180, // default — higher = snappier
245
+ damping: 12, // default — higher = less oscillation
246
+ mass: 1, // default — higher = heavier feel
247
+ precision: 0.01, // default — settles within this tolerance
248
+ onUpdate: (value, velocity) => {
249
+ sprite.x = value;
250
+ },
251
+ onEnded: () => {},
252
+ ticker: customTicker,
253
+ });
254
+
255
+ // Later:
256
+ spring.stop();
257
+ spring.setValue(50); // Immediately set position
258
+ ```
259
+
260
+ Uses Verlet integration for stable, energy-conserving simulation.
261
+
262
+ Starts active automatically. Call `stop()` to halt.
263
+
264
+ ---
265
+
266
+ ## Continuous: `createSmoothDamp`
267
+
268
+ Natural deceleration toward a target (Unity-style `SmoothDamp`).
269
+
270
+ ```ts
271
+ import { createSmoothDamp } from "anim-engine";
272
+
273
+ const damp = createSmoothDamp({
274
+ to: () => targetY,
275
+ smoothTimeMs: 300, // Approximate time to reach target
276
+ maxSpeed: undefined, // Optional velocity cap
277
+ precision: 0.01,
278
+ onUpdate: (value, vel) => {
279
+ /* assign */
280
+ },
281
+ onEnded: () => {},
282
+ ticker: customTicker,
283
+ });
284
+ ```
285
+
286
+ ---
287
+
288
+ ## Continuous: `createLerp`
289
+
290
+ Exponential approach. Always moves toward target, never overshoots.
291
+
292
+ ```ts
293
+ const lerp = createLerp({
294
+ to: () => targetX,
295
+ smoothTimeMs: 300,
296
+ precision: 0.01,
297
+ onUpdate: (value, vel) => {
298
+ /* assign */
299
+ },
300
+ onEnded: () => {},
301
+ ticker: customTicker,
302
+ });
303
+ ```
304
+
305
+ ---
306
+
307
+ ## Pure function: `createSmoothClamp`
308
+
309
+ Asymptotic saturation — caps a value with smooth deceleration. No control handle, no ticker.
310
+
311
+ ```ts
312
+ import { createSmoothClamp } from "anim-engine";
313
+
314
+ const clamp = createSmoothClamp(45); // max velocity
315
+
316
+ clamp(1000); // ≈ 44.96 (asymptotic to 45)
317
+ clamp(5); // ≈ 4.99
318
+ ```
319
+
320
+ Useful for limiting velocity, torque, force, or any value that should approach a maximum smoothly.
321
+
322
+ ---
323
+
324
+ ## Color
325
+
326
+ Perceptually uniform Oklab color interpolation.
327
+
328
+ ```ts
329
+ import { lerpRgba, hexToRgba } from "anim-engine";
330
+
331
+ const from = hexToRgba("#ff6b6b"); // → [1, 0.42, 0.42, 1] (normalized RGBA)
332
+ const to = hexToRgba("#4ecdc4");
333
+
334
+ createAnimation({
335
+ from: 0,
336
+ to: 1,
337
+ durationMs: 2000,
338
+ onUpdate: (t) => {
339
+ const [r, g, b, a] = lerpRgba(from, to, t);
340
+ element.style.color = `rgba(${r * 255}, ${g * 255}, ${b * 255}, ${a})`;
341
+ },
342
+ });
343
+ ```
344
+
345
+ Accepted hex formats: `#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA`.
346
+
347
+ ---
348
+
349
+ ## Dynamic Values
350
+
351
+ `DynamicValue` (`number | () => number`) lets values be static or computed lazily.
352
+
353
+ Resolution timing depends on the primitive:
354
+
355
+ | Primitive | `from` / `to` / `gap` / `smoothTimeMs` resolved |
356
+ | -------------------------------------------------- | ----------------------------------------------- |
357
+ | `createAnimation` (tween) | Once per `play()` call, cached for duration |
358
+ | `createAnimation` (keyframes) | Once per `play()` call |
359
+ | `createTimeline` | Once per `play()` call |
360
+ | `createSpring` / `createSmoothDamp` / `createLerp` | **Every frame** (target is a live value) |
361
+
362
+ ```ts
363
+ // Static value — resolved once
364
+ createAnimation({ from: 0, to: 100, durationMs: 1000 });
365
+
366
+ // Dynamic — function called at resolution time
367
+ createAnimation({
368
+ from: () => getCurrentX(),
369
+ durationMs: () => 500 / speedMultiplier,
370
+ });
371
+
372
+ // Continuous — function called every frame
373
+ createSpring({ to: () => mouseX });
374
+ ```
375
+
376
+ ---
377
+
378
+ ## Easing
379
+
380
+ 31 named presets plus custom cubic bezier.
381
+
382
+ ```ts
383
+ type EaseName =
384
+ | "linear"
385
+ | "inQuad"
386
+ | "outQuad"
387
+ | "inOutQuad"
388
+ | "inCubic"
389
+ | "outCubic"
390
+ | "inOutCubic"
391
+ | "inQuart"
392
+ | "outQuart"
393
+ | "inOutQuart"
394
+ | "inQuint"
395
+ | "outQuint"
396
+ | "inOutQuint"
397
+ | "inSine"
398
+ | "outSine"
399
+ | "inOutSine"
400
+ | "inExpo"
401
+ | "outExpo"
402
+ | "inOutExpo"
403
+ | "inCirc"
404
+ | "outCirc"
405
+ | "inOutCirc"
406
+ | "inBack"
407
+ | "outBack"
408
+ | "inOutBack"
409
+ | "inElastic"
410
+ | "outElastic"
411
+ | "inOutElastic"
412
+ | "inBounce"
413
+ | "outBounce"
414
+ | "inOutBounce";
415
+ ```
416
+
417
+ Custom bezier:
418
+
419
+ ```ts
420
+ import { cubicBezier } from "anim-engine";
421
+
422
+ const ease = cubicBezier(0.25, 0.1, 0.25, 1);
423
+
424
+ createAnimation({ from: 0, to: 100, durationMs: 1000, ease });
425
+ ```
426
+
427
+ Custom function:
428
+
429
+ ```ts
430
+ createAnimation({
431
+ from: 0,
432
+ to: 100,
433
+ durationMs: 1000,
434
+ ease: (t) => t * t, // custom quadratic
435
+ });
436
+ ```
437
+
438
+ ---
439
+
440
+ ## Common Recipes
441
+
442
+ ### Animating CSS transforms
443
+
444
+ ```ts
445
+ const anim = createAnimation({
446
+ from: 0,
447
+ to: 100,
448
+ durationMs: 2000,
449
+ ease: "outElastic",
450
+ onUpdate: (v) => {
451
+ element.style.transform = `translateX(${v}px) rotate(${v * 0.1}deg)`;
452
+ },
453
+ });
454
+ ```
455
+
456
+ ### Animating canvas drawing
457
+
458
+ ```ts
459
+ const ctx = canvas.getContext("2d");
460
+ let progress = 0;
461
+
462
+ createAnimation({
463
+ keyframes: [{ value: 0 }, { value: 1, gap: 1000, ease: "inOutSine" }],
464
+ onUpdate: (v) => {
465
+ progress = v;
466
+ },
467
+ }).play();
468
+
469
+ function draw() {
470
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
471
+ ctx.beginPath();
472
+ ctx.arc(100 + progress * 200, 100, 30, 0, Math.PI * 2);
473
+ ctx.fill();
474
+ requestAnimationFrame(draw);
475
+ }
476
+ draw();
477
+ ```
478
+
479
+ ### Spring-loaded UI element
480
+
481
+ ```ts
482
+ import { createSpring, getTicker } from "anim-engine";
483
+ getTicker().start();
484
+
485
+ createSpring({
486
+ to: () => mouseX,
487
+ stiffness: 160,
488
+ damping: 14,
489
+ onUpdate: (x) => (cursor.style.left = `${x}px`),
490
+ });
491
+ ```
492
+
493
+ ### Fade in with smooth damp
494
+
495
+ ```ts
496
+ createSmoothDamp({
497
+ to: () => (visible ? 1 : 0),
498
+ smoothTimeMs: 400,
499
+ onUpdate: (v) => (element.style.opacity = String(v)),
500
+ });
501
+ ```
502
+
503
+ ### Polling values in a game loop
504
+
505
+ Read-only `value` and `velocity` properties let you poll instead of using callbacks:
506
+
507
+ ```ts
508
+ const spring = createSpring({ to: () => enemy.targetX });
509
+
510
+ function gameLoop(dt: number) {
511
+ ticker.update(dt);
512
+ enemy.x = spring.value; // Read current position
513
+ enemy.vx = spring.velocity; // Read current velocity
514
+ }
515
+ ```
@@ -1,2 +1,14 @@
1
1
  import { AnimationOptions, Animation } from '../domain';
2
+ /**
3
+ * Creates an animation instance that can be played, paused, stopped,
4
+ * and queried for its current state.
5
+ *
6
+ * Accepts either a single tween configuration (from → to over a duration)
7
+ * or a keyframe animation with multiple keyframes, each with optional
8
+ * easing and gaps.
9
+ *
10
+ * @param options - The animation configuration, either {@link SingleTweenOptions}
11
+ * or {@link KeyframeAnimationOptions}.
12
+ * @returns An {@link Animation} instance for controlling the animation.
13
+ */
2
14
  export declare const createAnimation: (options: AnimationOptions) => Animation;
@@ -6,16 +6,27 @@ import { createKeyframeRunner, createTweenRunner } from "./runner.js";
6
6
  var isKeyframeMode = (options) => {
7
7
  return "keyframes" in options && Array.isArray(options.keyframes);
8
8
  };
9
+ /**
10
+ * Creates an animation instance that can be played, paused, stopped,
11
+ * and queried for its current state.
12
+ *
13
+ * Accepts either a single tween configuration (from → to over a duration)
14
+ * or a keyframe animation with multiple keyframes, each with optional
15
+ * easing and gaps.
16
+ *
17
+ * @param options - The animation configuration, either {@link SingleTweenOptions}
18
+ * or {@link KeyframeAnimationOptions}.
19
+ * @returns An {@link Animation} instance for controlling the animation.
20
+ */
9
21
  var createAnimation = (options) => {
10
22
  if (isKeyframeMode(options)) return createKeyframeAnimation(options);
11
23
  return createSingleTween(options);
12
24
  };
13
- var createSingleTween = ({ onStarted, onUpdate, onProgress, onEnded, from: rawFrom, to: rawTo, durationMs: rawDurationMs, ease: easeName = "inOutSine" }) => {
25
+ var createSingleTween = ({ from: rawFrom, to: rawTo, durationMs: rawDurationMs, ease: easeName = "inOutSine", onStarted, onUpdate, onProgress, onEnded, ticker = getTicker() }) => {
14
26
  let cachedDurationMs = resolveValue(rawDurationMs);
15
27
  let status = "stopped";
16
28
  const hasDynamicProperty = typeof rawFrom === "function" || typeof rawTo === "function" || typeof rawDurationMs === "function";
17
29
  let resolvePromise;
18
- const ticker = getTicker();
19
30
  const handleEnded = () => {
20
31
  status = "stopped";
21
32
  ticker.remove(runner);
@@ -41,7 +52,6 @@ var createSingleTween = ({ onStarted, onUpdate, onProgress, onEnded, from: rawFr
41
52
  };
42
53
  runner = buildRunner();
43
54
  const play = () => {
44
- if (status === "dead") throw new Error("Cannot play a dead animation");
45
55
  if (hasDynamicProperty) runner = buildRunner();
46
56
  else runner.reset();
47
57
  const promise = new Promise((resolve) => {
@@ -76,11 +86,6 @@ var createSingleTween = ({ onStarted, onUpdate, onProgress, onEnded, from: rawFr
76
86
  resolvePromise?.();
77
87
  resolvePromise = void 0;
78
88
  };
79
- const kill = () => {
80
- status = "dead";
81
- ticker.remove(runner);
82
- resolvePromise = void 0;
83
- };
84
89
  const setProgress = (value) => {
85
90
  if (status === "playing") pause();
86
91
  runner.evaluate(Math.max(0, Math.min(1, value)));
@@ -91,9 +96,8 @@ var createSingleTween = ({ onStarted, onUpdate, onProgress, onEnded, from: rawFr
91
96
  resume,
92
97
  stop,
93
98
  skipToEnd,
94
- kill,
95
- get currentValue() {
96
- return runner.currentValue;
99
+ get value() {
100
+ return runner.value;
97
101
  },
98
102
  get velocity() {
99
103
  return runner.velocity;
@@ -110,7 +114,7 @@ var createSingleTween = ({ onStarted, onUpdate, onProgress, onEnded, from: rawFr
110
114
  }
111
115
  };
112
116
  };
113
- var createKeyframeAnimation = ({ keyframes: rawKeyframes, onStarted, onUpdate, onProgress, onEnded }) => {
117
+ var createKeyframeAnimation = ({ keyframes: rawKeyframes, onStarted, onUpdate, onProgress, onEnded, ticker = getTicker() }) => {
114
118
  const resolveKeyframeGaps = () => {
115
119
  let total = 0;
116
120
  for (let i = 1; i < rawKeyframes.length; i++) total += resolveValue(rawKeyframes[i].gap ?? 0);
@@ -120,7 +124,6 @@ var createKeyframeAnimation = ({ keyframes: rawKeyframes, onStarted, onUpdate, o
120
124
  let status = "stopped";
121
125
  let resolvePromise;
122
126
  const hasDynamicProperty = rawKeyframes.some((kf) => typeof kf.value === "function" || typeof kf.gap === "function");
123
- const ticker = getTicker();
124
127
  const handleEnded = () => {
125
128
  status = "stopped";
126
129
  ticker.remove(runner);
@@ -144,7 +147,6 @@ var createKeyframeAnimation = ({ keyframes: rawKeyframes, onStarted, onUpdate, o
144
147
  };
145
148
  runner = buildRunner();
146
149
  const play = () => {
147
- if (status === "dead") throw new Error("Cannot play a dead animation");
148
150
  if (hasDynamicProperty) {
149
151
  runner = buildRunner();
150
152
  cachedDurationMs = resolveKeyframeGaps();
@@ -181,11 +183,6 @@ var createKeyframeAnimation = ({ keyframes: rawKeyframes, onStarted, onUpdate, o
181
183
  resolvePromise?.();
182
184
  resolvePromise = void 0;
183
185
  };
184
- const kill = () => {
185
- status = "dead";
186
- ticker.remove(runner);
187
- resolvePromise = void 0;
188
- };
189
186
  const setProgress = (value) => {
190
187
  if (status === "playing") pause();
191
188
  runner.evaluate(Math.max(0, Math.min(1, value)));
@@ -196,9 +193,8 @@ var createKeyframeAnimation = ({ keyframes: rawKeyframes, onStarted, onUpdate, o
196
193
  resume,
197
194
  stop,
198
195
  skipToEnd,
199
- kill,
200
- get currentValue() {
201
- return runner.currentValue;
196
+ get value() {
197
+ return runner.value;
202
198
  },
203
199
  get velocity() {
204
200
  return runner.velocity;
@@ -28,8 +28,7 @@ export type Animation = {
28
28
  resume: () => void;
29
29
  stop: () => void;
30
30
  skipToEnd: () => void;
31
- kill: () => void;
32
- currentValue: number;
31
+ value: number;
33
32
  velocity: number;
34
33
  progress: number;
35
34
  setProgress: (value: number) => void;
@@ -3,7 +3,7 @@ export type Runner = {
3
3
  (deltaMs: number): boolean;
4
4
  evaluate: (progress: number) => number;
5
5
  reset: () => void;
6
- currentValue: number;
6
+ value: number;
7
7
  velocity: number;
8
8
  progress: number;
9
9
  onStarted: (() => void) | undefined;