anim-engine 0.3.1 → 0.3.2

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/README.md CHANGED
@@ -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";
2
- import { getTicker } from "../ticker/get-ticker.js";
1
+ import { resolveEasing } from "../domain/easing.js";
2
+ import { getTicker } from "../domain/ticker.js";
3
+ import { resolveValue } from "../domain/resolve-value.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;
@@ -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,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;
@@ -51,7 +52,7 @@ var EASE_NAMES = [
51
52
  "outBounce",
52
53
  "inOutBounce"
53
54
  ];
54
- var easingFunctions = {
55
+ var EASING_FUNCTIONS = {
55
56
  linear: (x) => x,
56
57
  inQuad: (x) => x * x,
57
58
  outQuad: (x) => 1 - (1 - x) * (1 - x),
@@ -151,4 +152,4 @@ var cubicBezier = (p1x, p1y, p2x, p2y) => {
151
152
  };
152
153
  };
153
154
  //#endregion
154
- export { EASE_NAMES, cubicBezier, easingFunctions };
155
+ export { EASE_NAMES, EASING_FUNCTIONS, cubicBezier, resolveEasing };
@@ -0,0 +1,10 @@
1
+ export type { AnimationStatus } from './animation';
2
+ export type { InterpolationStatus, Interpolation } from './interpolation';
3
+ export type { EaseName, EaseFunction } from './easing';
4
+ export { EASE_NAMES, cubicBezier, EASING_FUNCTIONS, resolveEasing } from './easing';
5
+ export { getTicker } from './ticker';
6
+ export type { Ticker, TickHandler } from './ticker';
7
+ export { resolveValue } from './resolve-value';
8
+ export type { DynamicValue } from './resolve-value';
9
+ export type { Animation, AnimationOptions, SingleTweenOptions, KeyframeAnimationOptions, Keyframe, } from './animation';
10
+ export type { TimelineLayer, Timeline } from './timeline';
@@ -0,0 +1,10 @@
1
+ export type InterpolationStatus = "active" | "inactive" | "dead";
2
+ export type Interpolation = {
3
+ start: () => void;
4
+ stop: () => void;
5
+ kill: () => void;
6
+ setCurrentValue: (value: number) => void;
7
+ currentValue: number;
8
+ velocity: number;
9
+ status: InterpolationStatus;
10
+ };
@@ -0,0 +1,2 @@
1
+ export type DynamicValue = number | (() => number);
2
+ export declare const resolveValue: (value: DynamicValue) => number;
@@ -0,0 +1,4 @@
1
+ //#region src/domain/resolve-value.ts
2
+ var resolveValue = (value) => typeof value === "function" ? value() : value;
3
+ //#endregion
4
+ export { resolveValue };
@@ -1,11 +1,13 @@
1
1
  export type TickHandler = (deltaMs: number) => void;
2
- export type TickerControls = {
2
+ export type Ticker = {
3
3
  start: () => void;
4
4
  stop: () => void;
5
5
  update: (deltaMs: number) => void;
6
6
  add: (handler: TickHandler) => void;
7
7
  remove: (handler: TickHandler) => void;
8
8
  };
9
+ /** Returns the default ticker singleton. Created lazily on first access. */
10
+ export declare const getTicker: () => Ticker;
9
11
  /**
10
12
  * Create a ticker that drives active animations.
11
13
  *
@@ -19,4 +21,4 @@ export type TickerControls = {
19
21
  * Uses a flat array with undefined-tombstone removal for safe concurrent
20
22
  * modification during iteration. Compacted after each frame.
21
23
  */
22
- export declare const createTicker: () => TickerControls;
24
+ export declare const createTicker: () => Ticker;
@@ -1,4 +1,10 @@
1
- //#region src/ticker/ticker.ts
1
+ //#region src/domain/ticker.ts
2
+ var singleton;
3
+ /** Returns the default ticker singleton. Created lazily on first access. */
4
+ var getTicker = () => {
5
+ if (!singleton) singleton = createTicker();
6
+ return singleton;
7
+ };
2
8
  /**
3
9
  * Create a ticker that drives active animations.
4
10
  *
@@ -71,4 +77,4 @@ var createTicker = () => {
71
77
  };
72
78
  };
73
79
  //#endregion
74
- export { createTicker };
80
+ export { createTicker, getTicker };
@@ -0,0 +1,21 @@
1
+ import { DynamicValue } from '../domain';
2
+ import { KeyframeAnimationOptions, AnimationStatus } from './animation';
3
+ export type TimelineLayer = {
4
+ animation: KeyframeAnimationOptions;
5
+ at: DynamicValue;
6
+ } | {
7
+ animation: KeyframeAnimationOptions;
8
+ gap: number;
9
+ };
10
+ export type Timeline = {
11
+ play: () => Promise<void>;
12
+ pause: () => void;
13
+ resume: () => void;
14
+ stop: () => void;
15
+ skipToEnd: () => void;
16
+ kill: () => void;
17
+ setProgress: (value: number) => void;
18
+ progress: number;
19
+ status: AnimationStatus;
20
+ durationMs: number;
21
+ };
package/dist/index.d.ts CHANGED
@@ -5,13 +5,9 @@ export { createSmoothDamp } from './smooth-damp/create-smooth-damp';
5
5
  export { createLerp } from './lerp/create-lerp';
6
6
  export { createSmoothClamp } from './smooth-clamp/smooth-clamp';
7
7
  export { lerpOklab, hexToRgba } from './color/lerp-oklab';
8
- export { getTicker } from './ticker/get-ticker';
9
- export { EASE_NAMES, cubicBezier } from './easing/easing';
10
- export type { Keyframe, AnimationOptions, KeyframeAnimationOptions, } from './animation/create-animation';
11
- export type { Animation, Interpolation, EaseName, AnimationStatus, InterpolationStatus, DynamicValue, } from './shared/types';
8
+ export { getTicker, EASE_NAMES, cubicBezier } from './domain';
9
+ export type { EaseName, Interpolation, AnimationStatus, InterpolationStatus, DynamicValue, Animation, Keyframe, AnimationOptions, KeyframeAnimationOptions, TimelineLayer, Timeline, Ticker, } from './domain';
12
10
  export type { SpringOptions } from './spring/create-spring';
13
11
  export type { SmoothDampOptions } from './smooth-damp/create-smooth-damp';
14
12
  export type { LerpOptions } from './lerp/create-lerp';
15
- export type { TimelineLayer, Timeline } from './timeline/create-timeline';
16
13
  export type { RgbaTuple } from './color/lerp-oklab';
17
- export type { TickerControls } from './ticker/ticker';
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { EASE_NAMES, cubicBezier } from "./easing/easing.js";
2
- import { getTicker } from "./ticker/get-ticker.js";
1
+ import { EASE_NAMES, cubicBezier } from "./domain/easing.js";
2
+ import { getTicker } from "./domain/ticker.js";
3
3
  import { createAnimation } from "./animation/create-animation.js";
4
4
  import { createTimeline } from "./timeline/create-timeline.js";
5
5
  import { createSpring } from "./spring/create-spring.js";
@@ -1,4 +1,4 @@
1
- import { Interpolation, DynamicValue } from '../shared/types';
1
+ import { Interpolation, DynamicValue } from '../domain';
2
2
  export type LerpOptions = {
3
3
  to: () => number;
4
4
  smoothTimeMs: DynamicValue;
@@ -1,4 +1,5 @@
1
- import { getTicker } from "../ticker/get-ticker.js";
1
+ import { getTicker } from "../domain/ticker.js";
2
+ import { resolveValue } from "../domain/resolve-value.js";
2
3
  import { lerpStep } from "./step.js";
3
4
  //#region src/lerp/create-lerp.ts
4
5
  var createLerp = (options) => {
@@ -10,9 +11,22 @@ var createLerp = (options) => {
10
11
  let currentVelocity = 0;
11
12
  let active = true;
12
13
  const ticker = getTicker();
13
- const resolveValue = (v) => typeof v === "function" ? v() : v;
14
14
  state.current = options.to();
15
15
  previousValue = state.current;
16
+ const update = (deltaMs) => {
17
+ if (!active) return;
18
+ const target = options.to();
19
+ const smoothTimeMs = resolveValue(options.smoothTimeMs);
20
+ lerpStep(state, target, smoothTimeMs, deltaMs);
21
+ currentVelocity = (state.current - previousValue) / (deltaMs / 1e3);
22
+ previousValue = state.current;
23
+ onUpdate?.(state.current, currentVelocity);
24
+ if (onEnded && Math.abs(state.current - target) < precision && Math.abs(currentVelocity) < precision) {
25
+ state.current = target;
26
+ currentVelocity = 0;
27
+ onEnded();
28
+ }
29
+ };
16
30
  ticker.add(update);
17
31
  const start = () => {
18
32
  if (active) return;
@@ -27,20 +41,6 @@ var createLerp = (options) => {
27
41
  active = false;
28
42
  ticker.remove(update);
29
43
  };
30
- function update(deltaMs) {
31
- if (!active) return;
32
- const target = options.to();
33
- const smoothTimeMs = resolveValue(options.smoothTimeMs);
34
- lerpStep(state, target, smoothTimeMs, deltaMs);
35
- currentVelocity = (state.current - previousValue) / (deltaMs / 1e3);
36
- previousValue = state.current;
37
- onUpdate?.(state.current, currentVelocity);
38
- if (onEnded && Math.abs(state.current - target) < precision && Math.abs(currentVelocity) < precision) {
39
- state.current = target;
40
- currentVelocity = 0;
41
- onEnded();
42
- }
43
- }
44
44
  return {
45
45
  start,
46
46
  stop,
@@ -1,4 +1,4 @@
1
- import { Interpolation, DynamicValue } from '../shared/types';
1
+ import { Interpolation, DynamicValue } from '../domain';
2
2
  export type SmoothDampOptions = {
3
3
  to: () => number;
4
4
  smoothTimeMs: DynamicValue;
@@ -1,4 +1,5 @@
1
- import { getTicker } from "../ticker/get-ticker.js";
1
+ import { getTicker } from "../domain/ticker.js";
2
+ import { resolveValue } from "../domain/resolve-value.js";
2
3
  import { smoothDampStep } from "./step.js";
3
4
  //#region src/smooth-damp/create-smooth-damp.ts
4
5
  var createSmoothDamp = (options) => {
@@ -11,7 +12,6 @@ var createSmoothDamp = (options) => {
11
12
  };
12
13
  let active = true;
13
14
  const ticker = getTicker();
14
- const resolveValue = (v) => typeof v === "function" ? v() : v;
15
15
  state.current = options.to();
16
16
  ticker.add(update);
17
17
  const start = () => {
@@ -1,4 +1,4 @@
1
- import { Interpolation, DynamicValue } from '../shared/types';
1
+ import { Interpolation, DynamicValue } from '../domain';
2
2
  export type SpringOptions = {
3
3
  to: () => number;
4
4
  stiffness?: DynamicValue;
@@ -1,11 +1,11 @@
1
- import { getTicker } from "../ticker/get-ticker.js";
1
+ import { getTicker } from "../domain/ticker.js";
2
+ import { resolveValue } from "../domain/resolve-value.js";
2
3
  import { verletStep } from "./verlet.js";
3
4
  //#region src/spring/create-spring.ts
4
5
  var createSpring = (options) => {
5
6
  const precision = options.precision ?? .01;
6
7
  const onUpdate = options.onUpdate;
7
8
  const onEnded = options.onEnded;
8
- const resolveValue = (v) => typeof v === "function" ? v() : v;
9
9
  const rawTo = options.to;
10
10
  const rawStiffness = options.stiffness ?? 180;
11
11
  const rawDamping = options.damping ?? 12;
@@ -1,24 +1,4 @@
1
- import { KeyframeAnimationOptions } from '../animation/create-animation';
2
- import { AnimationStatus, DynamicValue } from '../shared/types';
3
- export type TimelineLayer = {
4
- animation: KeyframeAnimationOptions;
5
- at: DynamicValue;
6
- } | {
7
- animation: KeyframeAnimationOptions;
8
- gap: number;
9
- };
10
- export type Timeline = {
11
- play: () => Promise<Timeline>;
12
- pause: () => void;
13
- resume: () => void;
14
- stop: () => void;
15
- skipToEnd: () => void;
16
- kill: () => void;
17
- setProgress: (value: number) => void;
18
- progress: number;
19
- status: AnimationStatus;
20
- durationMs: number;
21
- };
1
+ import { TimelineLayer, Timeline } from '../domain';
22
2
  export declare const createTimeline: (layers: TimelineLayer[], options?: {
23
3
  onStarted?: () => void;
24
4
  onProgress?: (progress: number) => void;
@@ -1,10 +1,9 @@
1
- import { easingFunctions } from "../easing/easing.js";
2
- import { getTicker } from "../ticker/get-ticker.js";
1
+ import { resolveEasing } from "../domain/easing.js";
2
+ import { getTicker } from "../domain/ticker.js";
3
+ import { resolveValue } from "../domain/resolve-value.js";
3
4
  import { createKeyframeRunner } from "../animation/runner.js";
4
5
  //#region src/timeline/create-timeline.ts
5
6
  var noop = () => {};
6
- var resolveValue = (v) => typeof v === "function" ? v() : v;
7
- var resolveEasing = (ease) => typeof ease === "function" ? ease : easingFunctions[ease];
8
7
  var buildFromConfigs = (rawLayers) => {
9
8
  const activeLayers = [];
10
9
  let previousEndAt = 0;
@@ -55,7 +54,7 @@ var createTimeline = (layers, options) => {
55
54
  timelineStatus = "stopped";
56
55
  ticker.remove(update);
57
56
  onEnded?.();
58
- resolvePromise?.(timeline);
57
+ resolvePromise?.();
59
58
  resolvePromise = void 0;
60
59
  };
61
60
  const update = (deltaMs) => {
@@ -105,7 +104,7 @@ var createTimeline = (layers, options) => {
105
104
  if (timelineStatus !== "playing" && timelineStatus !== "paused") return;
106
105
  timelineStatus = "stopped";
107
106
  ticker.remove(update);
108
- resolvePromise?.(timeline);
107
+ resolvePromise?.();
109
108
  resolvePromise = void 0;
110
109
  };
111
110
  const skipToEnd = () => {
@@ -116,7 +115,7 @@ var createTimeline = (layers, options) => {
116
115
  timelineStatus = "stopped";
117
116
  ticker.remove(update);
118
117
  onEnded?.();
119
- resolvePromise?.(timeline);
118
+ resolvePromise?.();
120
119
  resolvePromise = void 0;
121
120
  };
122
121
  const kill = () => {
@@ -140,7 +139,7 @@ var createTimeline = (layers, options) => {
140
139
  }
141
140
  remainingLayers = activeLayers.filter((l) => !l.ended).length;
142
141
  };
143
- const timeline = {
142
+ return {
144
143
  play,
145
144
  pause,
146
145
  resume,
@@ -158,7 +157,6 @@ var createTimeline = (layers, options) => {
158
157
  return totalDurationMs;
159
158
  }
160
159
  };
161
- return timeline;
162
160
  };
163
161
  //#endregion
164
162
  export { createTimeline };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anim-engine",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "private": false,
5
5
  "description": "JavaScript library for animating numbers",
6
6
  "license": "MIT",
@@ -22,7 +22,7 @@
22
22
  "build": "npm run lint && npm run test && tsc && vite build",
23
23
  "preview": "vite preview",
24
24
  "check:types": "tsc --noEmit",
25
- "lint": "oxlint -c .oxlintrc.json src/",
25
+ "lint": "oxlint",
26
26
  "test": "vitest run",
27
27
  "test:watch": "vitest",
28
28
  "storybook": "storybook dev -p 6006",
@@ -42,6 +42,7 @@
42
42
  "gsap": "3.15.0",
43
43
  "oxfmt": "0.57.0",
44
44
  "oxlint": "1.72.0",
45
+ "oxlint-tsgolint": "^0.24.0",
45
46
  "storybook": "10.4.6",
46
47
  "typescript": "6.0.3",
47
48
  "vite": "8.1.3",
@@ -1,16 +0,0 @@
1
- import { EaseName, EaseFunction } from '../shared/types';
2
- /** All 31 named easing identifiers, ordered by type. */
3
- export declare const EASE_NAMES: EaseName[];
4
- export declare const easingFunctions: Record<EaseName, EaseFunction>;
5
- /**
6
- * Cubic bezier easing. Builds a pre-computed lookup table at construction time
7
- * for O(log n) binary search at runtime — no Newton iteration, no allocation
8
- * per frame.
9
- *
10
- * @param p1x - First control point x coordinate.
11
- * @param p1y - First control point y coordinate.
12
- * @param p2x - Second control point x coordinate.
13
- * @param p2y - Second control point y coordinate.
14
- * @returns An EaseFunction suitable for use with animate() or createTimeline().
15
- */
16
- export declare const cubicBezier: (p1x: number, p1y: number, p2x: number, p2y: number) => EaseFunction;
@@ -1,4 +0,0 @@
1
- /** An object that can be driven by the ticker. */
2
- export type Updateable = {
3
- update(deltaMs: number): void;
4
- };
@@ -1,28 +0,0 @@
1
- export type EaseName = "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";
2
- export type EaseFunction = (t: number) => number;
3
- export type AnimationStatus = "playing" | "paused" | "stopped" | "dead";
4
- export type InterpolationStatus = "active" | "inactive" | "dead";
5
- export type DynamicValue = number | (() => number);
6
- export type Animation = {
7
- play: () => Promise<Animation>;
8
- pause: () => void;
9
- resume: () => void;
10
- stop: () => void;
11
- skipToEnd: () => void;
12
- kill: () => void;
13
- currentValue: number;
14
- velocity: number;
15
- progress: number;
16
- setProgress: (value: number) => void;
17
- status: AnimationStatus;
18
- durationMs: number;
19
- };
20
- export type Interpolation = {
21
- start: () => void;
22
- stop: () => void;
23
- kill: () => void;
24
- setCurrentValue: (value: number) => void;
25
- currentValue: number;
26
- velocity: number;
27
- status: InterpolationStatus;
28
- };
@@ -1,5 +0,0 @@
1
- import { TickerControls } from './ticker';
2
- /** Returns the default ticker singleton. Created lazily on first access. */
3
- export declare const getTicker: () => TickerControls;
4
- /** Creates a new independent ticker instance (for testing or multi-loop setups). */
5
- export { createTicker } from './ticker';
@@ -1,10 +0,0 @@
1
- import { createTicker } from "./ticker.js";
2
- //#region src/ticker/get-ticker.ts
3
- var singleton;
4
- /** Returns the default ticker singleton. Created lazily on first access. */
5
- var getTicker = () => {
6
- if (!singleton) singleton = createTicker();
7
- return singleton;
8
- };
9
- //#endregion
10
- export { getTicker };