anim-engine 0.3.1 → 0.4.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.
Files changed (50) hide show
  1. package/README.md +8 -8
  2. package/dist/animation/create-animation.d.ts +1 -23
  3. package/dist/animation/create-animation.js +35 -41
  4. package/dist/animation/create-single-tween.d.ts +39 -0
  5. package/dist/animation/index.d.ts +1 -0
  6. package/dist/animation/runner.d.ts +5 -8
  7. package/dist/animation/runner.js +8 -62
  8. package/dist/animation/update.d.ts +1 -1
  9. package/dist/domain/animation.d.ts +40 -0
  10. package/dist/domain/color.d.ts +2 -0
  11. package/dist/domain/easing.d.ts +18 -0
  12. package/dist/{easing → domain}/easing.js +4 -37
  13. package/dist/domain/index.d.ts +10 -0
  14. package/dist/domain/interpolation.d.ts +35 -0
  15. package/dist/domain/resolve-value.d.ts +2 -0
  16. package/dist/domain/resolve-value.js +4 -0
  17. package/dist/domain/ticker.d.ts +8 -0
  18. package/dist/domain/timeline.d.ts +21 -0
  19. package/dist/index.d.ts +10 -17
  20. package/dist/index.js +7 -6
  21. package/dist/lerp/create-lerp.d.ts +1 -8
  22. package/dist/lerp/create-lerp.js +15 -15
  23. package/dist/lerp/index.d.ts +1 -0
  24. package/dist/lerp-rgba/hex-to-rgba.d.ts +13 -0
  25. package/dist/lerp-rgba/hex-to-rgba.js +48 -0
  26. package/dist/lerp-rgba/index.d.ts +2 -0
  27. package/dist/lerp-rgba/lerp-rgba.d.ts +13 -0
  28. package/dist/{color/lerp-oklab.js → lerp-rgba/lerp-rgba.js} +3 -48
  29. package/dist/smooth-clamp/{smooth-clamp.js → create-smooth-clamp.js} +1 -1
  30. package/dist/smooth-clamp/index.d.ts +1 -0
  31. package/dist/smooth-damp/create-smooth-damp.d.ts +1 -9
  32. package/dist/smooth-damp/create-smooth-damp.js +1 -1
  33. package/dist/smooth-damp/index.d.ts +1 -0
  34. package/dist/spring/create-spring.d.ts +1 -10
  35. package/dist/spring/create-spring.js +1 -1
  36. package/dist/spring/index.d.ts +1 -0
  37. package/dist/ticker/get-ticker.d.ts +16 -4
  38. package/dist/ticker/get-ticker.js +72 -2
  39. package/dist/ticker/index.d.ts +1 -0
  40. package/dist/timeline/create-timeline.d.ts +1 -21
  41. package/dist/timeline/create-timeline.js +8 -11
  42. package/dist/timeline/index.d.ts +1 -0
  43. package/package.json +3 -2
  44. package/dist/color/lerp-oklab.d.ts +0 -26
  45. package/dist/easing/easing.d.ts +0 -16
  46. package/dist/shared/internal.d.ts +0 -4
  47. package/dist/shared/types.d.ts +0 -28
  48. package/dist/ticker/ticker.d.ts +0 -22
  49. package/dist/ticker/ticker.js +0 -74
  50. /package/dist/smooth-clamp/{smooth-clamp.d.ts → create-smooth-clamp.d.ts} +0 -0
package/README.md CHANGED
@@ -81,7 +81,7 @@ By restricting itself to numeric values, the engine eliminates string parsing, c
81
81
  | 🧩 **Composable models** | Animations, keyframes, and timelines compose cleanly — put tweens inside timelines, nest keyframes anywhere. |
82
82
  | 🔄 **Continuous primitives** | Spring, smooth damp, and lerp chase live targets with zero setup — pass `() => value` for dynamic targets. |
83
83
  | 🚫 **No GC pressure** | Zero object allocations in hot update paths. State is mutated in place. |
84
- | 🎨 **Perceptual color** | Oklab interpolation via `lerpOklab` — no muddy browns. Compose it into any `onUpdate`. |
84
+ | 🎨 **Perceptual color** | Oklab interpolation via `lerpRgba` — no muddy browns. Compose it into any `onUpdate`. |
85
85
  | 📐 **TypeScript-first** | Full type exports, exhaustive discriminated unions, no `any`. |
86
86
  | 🔧 **Ticker control** | Bring your own game loop or use the built-in rAF ticker. Explicit — never auto-starts. |
87
87
 
@@ -112,7 +112,7 @@ Interpolations have `start()`/`stop()`/`kill()` controls, auto-start on creation
112
112
  | Primitive | Returns | Description |
113
113
  | ----------------------------------------- | -------------------- | --------------------------------------------- |
114
114
  | [`createSmoothClamp`](#createsmoothclamp) | `(n: number) => n` | Asymptotic clamp — saturates toward threshold |
115
- | [`lerpOklab` / `hexToRgba`](#color) | `RgbaTuple` / parser | Perceptually uniform color interpolation |
115
+ | [`lerpRgba` / `hexToRgba`](#color) | `RgbaTuple` / parser | Perceptually uniform color interpolation |
116
116
 
117
117
  ## Usage
118
118
 
@@ -381,7 +381,7 @@ Uses `threshold * (normalized / (1 + |normalized|))` for asymptotic saturation.
381
381
  Perceptually uniform Oklab interpolation. Straight RGB lerp produces muddy transitions — red→green goes through brown, blue→yellow goes through grey. Oklab matches human perception: equal steps look like equal changes.
382
382
 
383
383
  ```ts
384
- import { lerpOklab, hexToRgba } from "anim-engine";
384
+ import { lerpRgba, hexToRgba } from "anim-engine";
385
385
 
386
386
  hexToRgba("#ff6b6b"); // → [1, 0.42, 0.42, 1]
387
387
  hexToRgba("#f80"); // → [1, 0.533, 0, 1] (shorthand)
@@ -396,7 +396,7 @@ createAnimation({
396
396
  durationMs: 2000,
397
397
  ease: "outCubic",
398
398
  onUpdate: (t) => {
399
- const [r, g, b, a] = lerpOklab(fromColor, toColor, t);
399
+ const [r, g, b, a] = lerpRgba(fromColor, toColor, t);
400
400
  sprite.setColor(r, g, b, a);
401
401
  },
402
402
  });
@@ -405,7 +405,7 @@ createAnimation({
405
405
  **With continuous primitives** — drive the blend factor via spring, damp, or lerp:
406
406
 
407
407
  ```ts
408
- import { createSpring, lerpOklab, hexToRgba } from "anim-engine";
408
+ import { createSpring, lerpRgba, hexToRgba } from "anim-engine";
409
409
 
410
410
  const fromColor = hexToRgba("#ff6b6b");
411
411
  const toColor = hexToRgba("#4ecdc4");
@@ -416,7 +416,7 @@ const spring = createSpring({
416
416
  stiffness: 180,
417
417
  damping: 12,
418
418
  onUpdate: (t) => {
419
- const [r, g, b] = lerpOklab(fromColor, toColor, t);
419
+ const [r, g, b] = lerpRgba(fromColor, toColor, t);
420
420
  sprite.setColor(r, g, b, 1);
421
421
  },
422
422
  });
@@ -658,7 +658,7 @@ requestAnimationFrame(gameLoop);
658
658
  | `createSmoothClamp(threshold)` | Asymptotic clamp factory |
659
659
  | `getTicker()` | Singleton ticker |
660
660
  | `cubicBezier(p1x, p1y, p2x, p2y)` | Custom cubic bezier easing |
661
- | `lerpOklab(from, to, t)` | Oklab color interpolation |
661
+ | `lerpRgba(from, to, t)` | Oklab color interpolation |
662
662
  | `hexToRgba(hex)` | Parse hex color to normalized RGBA |
663
663
 
664
664
  ### Type exports
@@ -681,7 +681,7 @@ requestAnimationFrame(gameLoop);
681
681
  | `SmoothDampOptions` | `to`, `smoothTimeMs`, `maxSpeed?`, `precision?`, `onUpdate`, `onEnded` |
682
682
  | `LerpOptions` | `to`, `smoothTimeMs`, `precision?`, `onUpdate`, `onEnded` |
683
683
  | `RgbaTuple` | `readonly [number, number, number, number]` |
684
- | `TickerControls` | `start`, `stop`, `update`, `add`, `remove` |
684
+ | `Ticker` | `start`, `stop`, `update`, `add`, `remove` |
685
685
 
686
686
  ## License
687
687
 
@@ -1,24 +1,2 @@
1
- import { Animation, DynamicValue, EaseFunction, EaseName } from '../shared/types';
2
- export type SingleTweenOptions = {
3
- from: DynamicValue;
4
- to: DynamicValue;
5
- durationMs: DynamicValue;
6
- ease?: EaseName | EaseFunction;
7
- onStarted?: () => void;
8
- onUpdate?: (value: number, velocity: number) => void;
9
- onEnded?: () => void;
10
- };
11
- export type Keyframe = {
12
- value: DynamicValue;
13
- ease?: EaseName | EaseFunction;
14
- gap?: DynamicValue;
15
- };
16
- export type KeyframeAnimationOptions = {
17
- keyframes: Keyframe[];
18
- onStarted?: () => void;
19
- onUpdate?: (value: number, velocity: number) => void;
20
- onProgress?: (progress: number) => void;
21
- onEnded?: () => void;
22
- };
23
- export type AnimationOptions = SingleTweenOptions | KeyframeAnimationOptions;
1
+ import { AnimationOptions, Animation } from '../domain';
24
2
  export declare const createAnimation: (options: AnimationOptions) => Animation;
@@ -1,9 +1,8 @@
1
- import { easingFunctions } from "../easing/easing.js";
1
+ import { resolveEasing } from "../domain/easing.js";
2
+ import { resolveValue } from "../domain/resolve-value.js";
2
3
  import { getTicker } from "../ticker/get-ticker.js";
3
4
  import { createKeyframeRunner, createTweenRunner } from "./runner.js";
4
5
  //#region src/animation/create-animation.ts
5
- var resolveEasing = (ease) => typeof ease === "function" ? ease : easingFunctions[ease];
6
- var resolveValue = (v) => typeof v === "function" ? v() : v;
7
6
  var isKeyframeMode = (options) => {
8
7
  return "keyframes" in options && Array.isArray(options.keyframes);
9
8
  };
@@ -11,23 +10,20 @@ var createAnimation = (options) => {
11
10
  if (isKeyframeMode(options)) return createKeyframeAnimation(options);
12
11
  return createSingleTween(options);
13
12
  };
14
- var createSingleTween = (options) => {
15
- const easeName = options.ease ?? "inOutSine";
16
- const { onStarted, onUpdate, onEnded } = options;
17
- const rawFrom = options.from;
18
- const rawTo = options.to;
19
- const rawDurationMs = options.durationMs;
13
+ var createSingleTween = ({ onStarted, onUpdate, onProgress, onEnded, from: rawFrom, to: rawTo, durationMs: rawDurationMs, ease: easeName = "inOutSine" }) => {
20
14
  let cachedDurationMs = resolveValue(rawDurationMs);
21
15
  let status = "stopped";
16
+ const hasDynamicProperty = typeof rawFrom === "function" || typeof rawTo === "function" || typeof rawDurationMs === "function";
22
17
  let resolvePromise;
23
18
  const ticker = getTicker();
24
- let runner;
25
- const finish = () => {
19
+ const handleEnded = () => {
26
20
  status = "stopped";
27
21
  ticker.remove(runner);
28
- resolvePromise?.(controls);
22
+ resolvePromise?.();
23
+ onEnded?.();
29
24
  resolvePromise = void 0;
30
25
  };
26
+ let runner;
31
27
  const buildRunner = () => {
32
28
  const from = resolveValue(rawFrom);
33
29
  const to = resolveValue(rawTo);
@@ -39,15 +35,14 @@ var createSingleTween = (options) => {
39
35
  easeFn: resolveEasing(easeName),
40
36
  onStarted,
41
37
  onUpdate,
42
- onEnded,
43
- onComplete: finish
38
+ onProgress,
39
+ onEnded: handleEnded
44
40
  });
45
41
  };
46
- const hasDynamic = typeof rawFrom === "function" || typeof rawTo === "function" || typeof rawDurationMs === "function";
47
42
  runner = buildRunner();
48
43
  const play = () => {
49
44
  if (status === "dead") throw new Error("Cannot play a dead animation");
50
- if (hasDynamic) runner = buildRunner();
45
+ if (hasDynamicProperty) runner = buildRunner();
51
46
  else runner.reset();
52
47
  const promise = new Promise((resolve) => {
53
48
  resolvePromise = resolve;
@@ -70,7 +65,7 @@ var createSingleTween = (options) => {
70
65
  const stop = () => {
71
66
  status = "stopped";
72
67
  ticker.remove(runner);
73
- resolvePromise?.(controls);
68
+ resolvePromise?.();
74
69
  resolvePromise = void 0;
75
70
  };
76
71
  const skipToEnd = () => {
@@ -78,7 +73,7 @@ var createSingleTween = (options) => {
78
73
  if (status === "playing" || status === "paused") onEnded?.();
79
74
  status = "stopped";
80
75
  ticker.remove(runner);
81
- resolvePromise?.(controls);
76
+ resolvePromise?.();
82
77
  resolvePromise = void 0;
83
78
  };
84
79
  const kill = () => {
@@ -86,7 +81,11 @@ var createSingleTween = (options) => {
86
81
  ticker.remove(runner);
87
82
  resolvePromise = void 0;
88
83
  };
89
- const controls = {
84
+ const setProgress = (value) => {
85
+ if (status === "playing") pause();
86
+ runner.evaluate(Math.max(0, Math.min(1, value)));
87
+ };
88
+ return {
90
89
  play,
91
90
  pause,
92
91
  resume,
@@ -102,10 +101,7 @@ var createSingleTween = (options) => {
102
101
  get progress() {
103
102
  return runner.progress;
104
103
  },
105
- setProgress(value) {
106
- if (status === "playing") pause();
107
- runner.evaluate(Math.max(0, Math.min(1, value)));
108
- },
104
+ setProgress,
109
105
  get status() {
110
106
  return status;
111
107
  },
@@ -113,10 +109,8 @@ var createSingleTween = (options) => {
113
109
  return cachedDurationMs;
114
110
  }
115
111
  };
116
- return controls;
117
112
  };
118
- var createKeyframeAnimation = (options) => {
119
- const { keyframes: rawKeyframes, onStarted, onUpdate, onProgress, onEnded } = options;
113
+ var createKeyframeAnimation = ({ keyframes: rawKeyframes, onStarted, onUpdate, onProgress, onEnded }) => {
120
114
  const resolveKeyframeGaps = () => {
121
115
  let total = 0;
122
116
  for (let i = 1; i < rawKeyframes.length; i++) total += resolveValue(rawKeyframes[i].gap ?? 0);
@@ -125,14 +119,16 @@ var createKeyframeAnimation = (options) => {
125
119
  let cachedDurationMs = resolveKeyframeGaps();
126
120
  let status = "stopped";
127
121
  let resolvePromise;
122
+ const hasDynamicProperty = rawKeyframes.some((kf) => typeof kf.value === "function" || typeof kf.gap === "function");
128
123
  const ticker = getTicker();
129
- let runner;
130
- const finish = () => {
124
+ const handleEnded = () => {
131
125
  status = "stopped";
132
126
  ticker.remove(runner);
133
- resolvePromise?.(controls);
127
+ resolvePromise?.();
128
+ onEnded?.();
134
129
  resolvePromise = void 0;
135
130
  };
131
+ let runner;
136
132
  const buildRunner = () => {
137
133
  return createKeyframeRunner({
138
134
  keyframes: rawKeyframes.map((kf, i) => ({
@@ -143,15 +139,13 @@ var createKeyframeAnimation = (options) => {
143
139
  onStarted,
144
140
  onUpdate,
145
141
  onProgress,
146
- onEnded,
147
- onComplete: finish
142
+ onEnded: handleEnded
148
143
  });
149
144
  };
150
- const hasDynamic = rawKeyframes.some((kf) => typeof kf.value === "function" || typeof kf.gap === "function");
151
145
  runner = buildRunner();
152
146
  const play = () => {
153
147
  if (status === "dead") throw new Error("Cannot play a dead animation");
154
- if (hasDynamic) {
148
+ if (hasDynamicProperty) {
155
149
  runner = buildRunner();
156
150
  cachedDurationMs = resolveKeyframeGaps();
157
151
  } else runner.reset();
@@ -176,7 +170,7 @@ var createKeyframeAnimation = (options) => {
176
170
  const stop = () => {
177
171
  status = "stopped";
178
172
  ticker.remove(runner);
179
- resolvePromise?.(controls);
173
+ resolvePromise?.();
180
174
  resolvePromise = void 0;
181
175
  };
182
176
  const skipToEnd = () => {
@@ -184,7 +178,7 @@ var createKeyframeAnimation = (options) => {
184
178
  if (status === "playing" || status === "paused") onEnded?.();
185
179
  status = "stopped";
186
180
  ticker.remove(runner);
187
- resolvePromise?.(controls);
181
+ resolvePromise?.();
188
182
  resolvePromise = void 0;
189
183
  };
190
184
  const kill = () => {
@@ -192,7 +186,11 @@ var createKeyframeAnimation = (options) => {
192
186
  ticker.remove(runner);
193
187
  resolvePromise = void 0;
194
188
  };
195
- const controls = {
189
+ const setProgress = (value) => {
190
+ if (status === "playing") pause();
191
+ runner.evaluate(Math.max(0, Math.min(1, value)));
192
+ };
193
+ return {
196
194
  play,
197
195
  pause,
198
196
  resume,
@@ -208,10 +206,7 @@ var createKeyframeAnimation = (options) => {
208
206
  get progress() {
209
207
  return runner.progress;
210
208
  },
211
- setProgress(value) {
212
- if (status === "playing") pause();
213
- runner.evaluate(Math.max(0, Math.min(1, value)));
214
- },
209
+ setProgress,
215
210
  get status() {
216
211
  return status;
217
212
  },
@@ -219,7 +214,6 @@ var createKeyframeAnimation = (options) => {
219
214
  return cachedDurationMs;
220
215
  }
221
216
  };
222
- return controls;
223
217
  };
224
218
  //#endregion
225
219
  export { createAnimation };
@@ -0,0 +1,39 @@
1
+ import { EaseName, AnimationStatus, DynamicValue, EaseFunction } from '../domain';
2
+ export type SingleTweenOptions = {
3
+ from: DynamicValue;
4
+ to: DynamicValue;
5
+ durationMs: DynamicValue;
6
+ ease?: EaseName | EaseFunction;
7
+ onStarted?: () => void;
8
+ onUpdate?: (value: number, velocity: number) => void;
9
+ onProgress?: (progress: number) => void;
10
+ onEnded?: () => void;
11
+ };
12
+ export type Keyframe = {
13
+ value: DynamicValue;
14
+ ease?: EaseName | EaseFunction;
15
+ gap?: DynamicValue;
16
+ };
17
+ export type KeyframeAnimationOptions = {
18
+ keyframes: Keyframe[];
19
+ onStarted?: () => void;
20
+ onUpdate?: (value: number, velocity: number) => void;
21
+ onProgress?: (progress: number) => void;
22
+ onEnded?: () => void;
23
+ };
24
+ export type AnimationOptions = SingleTweenOptions | KeyframeAnimationOptions;
25
+ export type Animation = {
26
+ play: () => Promise<void>;
27
+ pause: () => void;
28
+ resume: () => void;
29
+ stop: () => void;
30
+ skipToEnd: () => void;
31
+ kill: () => void;
32
+ currentValue: number;
33
+ velocity: number;
34
+ progress: number;
35
+ setProgress: (value: number) => void;
36
+ status: AnimationStatus;
37
+ durationMs: number;
38
+ };
39
+ export declare const createSingleTween: ({ onStarted, onUpdate, onProgress, onEnded, from: rawFrom, to: rawTo, durationMs: rawDurationMs, ease: easeName, }: SingleTweenOptions) => Animation;
@@ -0,0 +1 @@
1
+ export { createAnimation } from './create-animation';
@@ -1,4 +1,4 @@
1
- import { EaseFunction } from '../shared/types';
1
+ import { EaseFunction } from '../domain';
2
2
  export type Runner = {
3
3
  (deltaMs: number): boolean;
4
4
  evaluate: (progress: number) => number;
@@ -7,8 +7,6 @@ export type Runner = {
7
7
  velocity: number;
8
8
  progress: number;
9
9
  onStarted: (() => void) | undefined;
10
- onUpdate: ((value: number, velocity: number) => void) | undefined;
11
- onProgress: ((progress: number) => void) | undefined;
12
10
  onEnded: (() => void) | undefined;
13
11
  };
14
12
  export type TweenRunnerConfig = {
@@ -18,10 +16,10 @@ export type TweenRunnerConfig = {
18
16
  easeFn: EaseFunction;
19
17
  onStarted?: () => void;
20
18
  onUpdate?: (value: number, velocity: number) => void;
21
- onEnded?: () => void;
22
- onComplete?: () => void;
19
+ onProgress?: (progress: number) => void;
20
+ onEnded: () => void;
23
21
  };
24
- export declare const createTweenRunner: (config: TweenRunnerConfig) => Runner;
22
+ export declare const createTweenRunner: ({ from, to, durationMs, easeFn, onStarted, onUpdate, onProgress, onEnded, }: TweenRunnerConfig) => Runner;
25
23
  export type KeyframeRunnerConfig = {
26
24
  keyframes: {
27
25
  value: number;
@@ -32,6 +30,5 @@ export type KeyframeRunnerConfig = {
32
30
  onUpdate?: (value: number, velocity: number) => void;
33
31
  onProgress?: (progress: number) => void;
34
32
  onEnded?: () => void;
35
- onComplete?: () => void;
36
33
  };
37
- export declare const createKeyframeRunner: (config: KeyframeRunnerConfig) => Runner;
34
+ export declare const createKeyframeRunner: ({ keyframes, onStarted, onUpdate, onProgress, onEnded, }: KeyframeRunnerConfig) => Runner;
@@ -1,12 +1,7 @@
1
1
  import { updateTween } from "./update.js";
2
2
  //#region src/animation/runner.ts
3
- var noop = () => {};
4
- var createTweenRunner = (config) => {
5
- const { from, to, durationMs, easeFn } = config;
6
- const onStarted = config.onStarted;
7
- const onUpdate = config.onUpdate ?? noop;
8
- const onEnded = config.onEnded;
9
- const onComplete = config.onComplete;
3
+ var noOp = () => {};
4
+ var createTweenRunner = ({ from, to, durationMs, easeFn, onStarted, onUpdate = noOp, onProgress = noOp, onEnded }) => {
10
5
  const state = {
11
6
  progress: 0,
12
7
  currentValue: from,
@@ -16,10 +11,8 @@ var createTweenRunner = (config) => {
16
11
  const step = (deltaMs) => {
17
12
  const completed = updateTween(state, deltaMs, durationMs, easeFn, from, to);
18
13
  onUpdate(state.currentValue, state.velocity);
19
- if (completed) {
20
- onEnded?.();
21
- onComplete?.();
22
- }
14
+ onProgress(state.progress);
15
+ if (completed) onEnded();
23
16
  return completed;
24
17
  };
25
18
  const evaluate = (progress) => {
@@ -57,18 +50,11 @@ var createTweenRunner = (config) => {
57
50
  configurable: true
58
51
  });
59
52
  runner.onStarted = onStarted;
60
- runner.onUpdate = config.onUpdate;
61
- runner.onProgress = void 0;
62
53
  runner.onEnded = onEnded;
63
54
  return runner;
64
55
  };
65
- var createKeyframeRunner = (config) => {
66
- const { keyframes } = config;
67
- const onStarted = config.onStarted;
68
- const onUpdate = config.onUpdate ?? noop;
69
- const onProgress = config.onProgress ?? noop;
70
- const onEnded = config.onEnded;
71
- const onComplete = config.onComplete;
56
+ var createKeyframeRunner = ({ keyframes, onStarted, onUpdate = noOp, onProgress = noOp, onEnded }) => {
57
+ if (keyframes.length < 2) throw new Error("Keyframe animation must have at least 2 keyframes");
72
58
  const segments = [];
73
59
  for (let i = 0; i < keyframes.length - 1; i++) {
74
60
  const cur = keyframes[i];
@@ -92,44 +78,7 @@ var createKeyframeRunner = (config) => {
92
78
  let segmentElapsed = 0;
93
79
  let segmentProgress = 0;
94
80
  let runner;
95
- if (segments.length === 0) {
96
- const complete = () => {
97
- onEnded?.();
98
- onComplete?.();
99
- };
100
- const step = (_deltaMs) => {
101
- onUpdate(currentValue, 0);
102
- onProgress(1);
103
- complete();
104
- return true;
105
- };
106
- const evaluate = (prog) => {
107
- onUpdate(currentValue, 0);
108
- onProgress(Math.max(0, Math.min(1, prog)));
109
- return currentValue;
110
- };
111
- runner = step;
112
- runner.evaluate = evaluate;
113
- runner.reset = () => {};
114
- Object.defineProperty(runner, "currentValue", {
115
- get: () => currentValue,
116
- configurable: true
117
- });
118
- Object.defineProperty(runner, "velocity", {
119
- get: () => 0,
120
- configurable: true
121
- });
122
- Object.defineProperty(runner, "progress", {
123
- get: () => 1,
124
- configurable: true
125
- });
126
- runner.onStarted = onStarted;
127
- runner.onUpdate = config.onUpdate;
128
- runner.onProgress = config.onProgress;
129
- runner.onEnded = onEnded;
130
- return runner;
131
- }
132
- const step = (deltaMs) => {
81
+ const update = (deltaMs) => {
133
82
  const segment = segments[currentSegmentIndex];
134
83
  segmentElapsed += deltaMs;
135
84
  segmentProgress += deltaMs / segment.durationMs;
@@ -155,7 +104,6 @@ var createKeyframeRunner = (config) => {
155
104
  onProgress(globalProgress);
156
105
  } else {
157
106
  onEnded?.();
158
- onComplete?.();
159
107
  return true;
160
108
  }
161
109
  return false;
@@ -195,7 +143,7 @@ var createKeyframeRunner = (config) => {
195
143
  segmentElapsed = 0;
196
144
  segmentProgress = 0;
197
145
  };
198
- runner = step;
146
+ runner = update;
199
147
  runner.evaluate = evaluate;
200
148
  runner.reset = reset;
201
149
  Object.defineProperty(runner, "currentValue", {
@@ -211,8 +159,6 @@ var createKeyframeRunner = (config) => {
211
159
  configurable: true
212
160
  });
213
161
  runner.onStarted = onStarted;
214
- runner.onUpdate = config.onUpdate;
215
- runner.onProgress = config.onProgress;
216
162
  runner.onEnded = onEnded;
217
163
  return runner;
218
164
  };
@@ -1,4 +1,4 @@
1
- import { EaseFunction } from '../shared/types';
1
+ import { EaseFunction } from '../domain';
2
2
  export type TweenState = {
3
3
  progress: number;
4
4
  currentValue: number;
@@ -0,0 +1,40 @@
1
+ import { EaseFunction, EaseName } from './easing';
2
+ import { DynamicValue } from './resolve-value';
3
+ export type AnimationStatus = "playing" | "paused" | "stopped" | "dead";
4
+ export type SingleTweenOptions = {
5
+ from: DynamicValue;
6
+ to: DynamicValue;
7
+ durationMs: DynamicValue;
8
+ ease?: EaseName | EaseFunction;
9
+ onStarted?: () => void;
10
+ onUpdate?: (value: number, velocity: number) => void;
11
+ onProgress?: (progress: number) => void;
12
+ onEnded?: () => void;
13
+ };
14
+ export type Keyframe = {
15
+ value: DynamicValue;
16
+ ease?: EaseName | EaseFunction;
17
+ gap?: DynamicValue;
18
+ };
19
+ export type KeyframeAnimationOptions = {
20
+ keyframes: Keyframe[];
21
+ onStarted?: () => void;
22
+ onUpdate?: (value: number, velocity: number) => void;
23
+ onProgress?: (progress: number) => void;
24
+ onEnded?: () => void;
25
+ };
26
+ export type AnimationOptions = SingleTweenOptions | KeyframeAnimationOptions;
27
+ export type Animation = {
28
+ play: () => Promise<void>;
29
+ pause: () => void;
30
+ resume: () => void;
31
+ stop: () => void;
32
+ skipToEnd: () => void;
33
+ kill: () => void;
34
+ currentValue: number;
35
+ velocity: number;
36
+ progress: number;
37
+ setProgress: (value: number) => void;
38
+ status: AnimationStatus;
39
+ durationMs: number;
40
+ };
@@ -0,0 +1,2 @@
1
+ export type RgbaTuple = readonly [number, number, number, number];
2
+ export type LerpRgba = (from: RgbaTuple, to: RgbaTuple, progress: number) => RgbaTuple;
@@ -0,0 +1,18 @@
1
+ export type EaseFunction = (t: number) => number;
2
+ export declare const resolveEasing: (ease: EaseName | EaseFunction) => EaseFunction;
3
+ /** All 31 named easing identifiers, ordered by type. */
4
+ export declare const EASE_NAMES: readonly ["linear", "inQuad", "outQuad", "inOutQuad", "inCubic", "outCubic", "inOutCubic", "inQuart", "outQuart", "inOutQuart", "inQuint", "outQuint", "inOutQuint", "inSine", "outSine", "inOutSine", "inExpo", "outExpo", "inOutExpo", "inCirc", "outCirc", "inOutCirc", "inBack", "outBack", "inOutBack", "inElastic", "outElastic", "inOutElastic", "inBounce", "outBounce", "inOutBounce"];
5
+ export type EaseName = (typeof EASE_NAMES)[number];
6
+ export declare const EASING_FUNCTIONS: Record<EaseName, EaseFunction>;
7
+ /**
8
+ * Cubic bezier easing. Builds a pre-computed lookup table at construction time
9
+ * for O(log n) binary search at runtime — no Newton iteration, no allocation
10
+ * per frame.
11
+ *
12
+ * @param p1x - First control point x coordinate.
13
+ * @param p1y - First control point y coordinate.
14
+ * @param p2x - Second control point x coordinate.
15
+ * @param p2y - Second control point y coordinate.
16
+ * @returns An EaseFunction suitable for use with animate() or createTimeline().
17
+ */
18
+ export declare const cubicBezier: (p1x: number, p1y: number, p2x: number, p2y: number) => EaseFunction;
@@ -1,4 +1,5 @@
1
- //#region src/easing/easing.ts
1
+ //#region src/domain/easing.ts
2
+ var resolveEasing = (ease) => typeof ease === "function" ? ease : EASING_FUNCTIONS[ease];
2
3
  var pow = Math.pow;
3
4
  var sqrt = Math.sqrt;
4
5
  var sin = Math.sin;
@@ -17,41 +18,7 @@ var bounceOut = (x) => {
17
18
  else if (x < 2.5 / d1) return n1 * (x -= 2.25 / d1) * x + .9375;
18
19
  else return n1 * (x -= 2.625 / d1) * x + .984375;
19
20
  };
20
- /** All 31 named easing identifiers, ordered by type. */
21
- var EASE_NAMES = [
22
- "linear",
23
- "inQuad",
24
- "outQuad",
25
- "inOutQuad",
26
- "inCubic",
27
- "outCubic",
28
- "inOutCubic",
29
- "inQuart",
30
- "outQuart",
31
- "inOutQuart",
32
- "inQuint",
33
- "outQuint",
34
- "inOutQuint",
35
- "inSine",
36
- "outSine",
37
- "inOutSine",
38
- "inExpo",
39
- "outExpo",
40
- "inOutExpo",
41
- "inCirc",
42
- "outCirc",
43
- "inOutCirc",
44
- "inBack",
45
- "outBack",
46
- "inOutBack",
47
- "inElastic",
48
- "outElastic",
49
- "inOutElastic",
50
- "inBounce",
51
- "outBounce",
52
- "inOutBounce"
53
- ];
54
- var easingFunctions = {
21
+ var EASING_FUNCTIONS = {
55
22
  linear: (x) => x,
56
23
  inQuad: (x) => x * x,
57
24
  outQuad: (x) => 1 - (1 - x) * (1 - x),
@@ -151,4 +118,4 @@ var cubicBezier = (p1x, p1y, p2x, p2y) => {
151
118
  };
152
119
  };
153
120
  //#endregion
154
- export { EASE_NAMES, cubicBezier, easingFunctions };
121
+ export { EASING_FUNCTIONS, cubicBezier, resolveEasing };
@@ -0,0 +1,10 @@
1
+ export type { AnimationStatus } from './animation';
2
+ export type { InterpolationStatus, Interpolation, LerpOptions, SmoothDampOptions, SpringOptions, } from './interpolation';
3
+ export type { EaseName, EaseFunction } from './easing';
4
+ export { EASE_NAMES, cubicBezier, EASING_FUNCTIONS, resolveEasing } from './easing';
5
+ export type { Ticker, TickHandler } from './ticker';
6
+ export { resolveValue } from './resolve-value';
7
+ export type { DynamicValue } from './resolve-value';
8
+ export type { Animation, AnimationOptions, SingleTweenOptions, KeyframeAnimationOptions, Keyframe, } from './animation';
9
+ export type { TimelineLayer, Timeline } from './timeline';
10
+ export type { RgbaTuple, LerpRgba } from './color';
@@ -0,0 +1,35 @@
1
+ import { DynamicValue } from './resolve-value';
2
+ export type InterpolationStatus = "active" | "inactive" | "dead";
3
+ export type Interpolation = {
4
+ start: () => void;
5
+ stop: () => void;
6
+ kill: () => void;
7
+ setCurrentValue: (value: number) => void;
8
+ currentValue: number;
9
+ velocity: number;
10
+ status: InterpolationStatus;
11
+ };
12
+ export type LerpOptions = {
13
+ to: () => number;
14
+ smoothTimeMs: DynamicValue;
15
+ precision?: number;
16
+ onUpdate?: (value: number, velocity: number) => void;
17
+ onEnded?: () => void;
18
+ };
19
+ export type SmoothDampOptions = {
20
+ to: () => number;
21
+ smoothTimeMs: DynamicValue;
22
+ maxSpeed?: DynamicValue;
23
+ precision?: number;
24
+ onUpdate?: (value: number, velocity: number) => void;
25
+ onEnded?: () => void;
26
+ };
27
+ export type SpringOptions = {
28
+ to: () => number;
29
+ stiffness?: DynamicValue;
30
+ damping?: DynamicValue;
31
+ mass?: DynamicValue;
32
+ precision?: number;
33
+ onUpdate?: (value: number, velocity: number) => void;
34
+ onEnded?: () => void;
35
+ };
@@ -0,0 +1,2 @@
1
+ export type DynamicValue = number | (() => number);
2
+ export declare const resolveValue: (value: DynamicValue) => number;