anim-engine 0.1.2 → 0.2.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.
@@ -1,8 +1,9 @@
1
1
  import { easingFunctions } from "../easing/easing.js";
2
2
  import { getTicker } from "../ticker/get-ticker.js";
3
- import { updateTween } from "./update.js";
3
+ import { createKeyframeRunner, createTweenRunner } from "./runner.js";
4
4
  //#region src/animation/create-animation.ts
5
5
  var resolveEasing = (ease) => typeof ease === "function" ? ease : easingFunctions[ease];
6
+ var resolveValue = (v) => typeof v === "function" ? v() : v;
6
7
  var isKeyframeMode = (options) => {
7
8
  return "keyframes" in options && Array.isArray(options.keyframes);
8
9
  };
@@ -12,101 +13,79 @@ var createAnimation = (options) => {
12
13
  };
13
14
  var createSingleTween = (options) => {
14
15
  const easeName = options.ease ?? "inOutSine";
15
- const delayMs = options.delayMs ?? 0;
16
- const { onStarted, onUpdate, onEnded, durationMs } = options;
17
- let rawFrom = options.from;
18
- let rawTo = options.to;
19
- const state = {
20
- progress: 0,
21
- currentValue: 0,
22
- velocity: 0
23
- };
16
+ const { onStarted, onUpdate, onEnded } = options;
17
+ const rawFrom = options.from;
18
+ const rawTo = options.to;
19
+ const rawDurationMs = options.durationMs;
20
+ let cachedDurationMs = resolveValue(rawDurationMs);
24
21
  let status = "stopped";
25
- let stopped = false;
26
22
  let resolvePromise;
27
- let delayRemainingMs = 0;
28
- let pendingStart = false;
29
23
  const ticker = getTicker();
30
- const update = (deltaMs) => {
31
- if (stopped) return;
32
- if (pendingStart) {
33
- delayRemainingMs -= deltaMs;
34
- if (delayRemainingMs > 0) return;
35
- pendingStart = false;
36
- onStarted?.();
37
- deltaMs = -delayRemainingMs;
38
- }
39
- const from = typeof rawFrom === "function" ? rawFrom() : rawFrom;
40
- const to = typeof rawTo === "function" ? rawTo() : rawTo;
41
- const completed = updateTween(state, deltaMs, durationMs, currentEase, from, to);
42
- onUpdate?.(state.currentValue, state.velocity);
43
- if (completed) handleCompletion();
24
+ let runner;
25
+ const finish = () => {
26
+ status = "stopped";
27
+ ticker.remove(runner);
28
+ resolvePromise?.(controls);
29
+ resolvePromise = void 0;
44
30
  };
45
- let currentEase = resolveEasing(easeName);
31
+ const buildRunner = () => {
32
+ const from = resolveValue(rawFrom);
33
+ const to = resolveValue(rawTo);
34
+ cachedDurationMs = resolveValue(rawDurationMs);
35
+ return createTweenRunner({
36
+ from,
37
+ to,
38
+ durationMs: cachedDurationMs,
39
+ easeFn: resolveEasing(easeName),
40
+ onStarted,
41
+ onUpdate,
42
+ onEnded,
43
+ onComplete: finish
44
+ });
45
+ };
46
+ const hasDynamic = typeof rawFrom === "function" || typeof rawTo === "function" || typeof rawDurationMs === "function";
47
+ runner = buildRunner();
46
48
  const play = () => {
47
49
  if (status === "dead") throw new Error("Cannot play a dead animation");
48
- stopped = false;
49
- state.progress = 0;
50
- const initFrom = typeof rawFrom === "function" ? rawFrom() : rawFrom;
51
- state.currentValue = initFrom;
52
- state.velocity = 0;
53
- if (delayMs > 0) {
54
- delayRemainingMs = delayMs;
55
- pendingStart = true;
56
- } else {
57
- pendingStart = false;
58
- delayRemainingMs = 0;
59
- }
50
+ if (hasDynamic) runner = buildRunner();
51
+ else runner.reset();
60
52
  const promise = new Promise((resolve) => {
61
53
  resolvePromise = resolve;
62
54
  });
63
55
  status = "playing";
64
- ticker.add(update);
65
- if (delayRemainingMs <= 0) onStarted?.();
56
+ ticker.add(runner);
57
+ onStarted?.();
66
58
  return promise;
67
59
  };
68
60
  const pause = () => {
69
61
  if (status !== "playing") return;
70
62
  status = "paused";
71
- ticker.remove(update);
63
+ ticker.remove(runner);
72
64
  };
73
65
  const resume = () => {
74
66
  if (status !== "paused") return;
75
67
  status = "playing";
76
- ticker.add(update);
68
+ ticker.add(runner);
77
69
  };
78
70
  const stop = () => {
79
- stopped = true;
80
71
  status = "stopped";
81
- ticker.remove(update);
72
+ ticker.remove(runner);
82
73
  resolvePromise?.(controls);
83
74
  resolvePromise = void 0;
84
75
  };
85
76
  const skipToEnd = () => {
86
- const skipTo = typeof rawTo === "function" ? rawTo() : rawTo;
87
- state.currentValue = skipTo;
88
- state.velocity = 0;
89
- state.progress = 1;
90
- onUpdate?.(state.currentValue, state.velocity);
77
+ runner.evaluate(1);
91
78
  if (status === "playing" || status === "paused") onEnded?.();
92
79
  status = "stopped";
93
- ticker.remove(update);
80
+ ticker.remove(runner);
94
81
  resolvePromise?.(controls);
95
82
  resolvePromise = void 0;
96
83
  };
97
84
  const kill = () => {
98
85
  status = "dead";
99
- ticker.remove(update);
100
- stopped = true;
86
+ ticker.remove(runner);
101
87
  resolvePromise = void 0;
102
88
  };
103
- function handleCompletion() {
104
- onEnded?.();
105
- status = "stopped";
106
- ticker.remove(update);
107
- resolvePromise?.(controls);
108
- resolvePromise = void 0;
109
- }
110
89
  const controls = {
111
90
  play,
112
91
  pause,
@@ -114,170 +93,103 @@ var createSingleTween = (options) => {
114
93
  stop,
115
94
  skipToEnd,
116
95
  kill,
117
- setCurrentValue: (value) => {
118
- state.currentValue = value;
119
- state.velocity = 0;
120
- },
121
96
  get currentValue() {
122
- return state.currentValue;
97
+ return runner.currentValue;
123
98
  },
124
99
  get velocity() {
125
- return state.velocity;
100
+ return runner.velocity;
126
101
  },
127
102
  get progress() {
128
- return state.progress;
103
+ return runner.progress;
129
104
  },
130
105
  setProgress(value) {
131
106
  if (status === "playing") pause();
132
- const clamped = Math.max(0, Math.min(1, value));
133
- state.progress = clamped;
134
- const from = typeof rawFrom === "function" ? rawFrom() : rawFrom;
135
- const to = typeof rawTo === "function" ? rawTo() : rawTo;
136
- state.currentValue = from + (to - from) * currentEase(clamped);
137
- state.velocity = 0;
138
- onUpdate?.(state.currentValue, state.velocity);
107
+ runner.evaluate(Math.max(0, Math.min(1, value)));
139
108
  },
140
109
  get status() {
141
110
  return status;
142
111
  },
143
112
  get durationMs() {
144
- return durationMs;
113
+ return cachedDurationMs;
145
114
  }
146
115
  };
147
116
  return controls;
148
117
  };
149
118
  var createKeyframeAnimation = (options) => {
150
- const keyframes = options.keyframes;
151
- const onUpdate = options.onUpdate;
152
- const onProgress = options.onProgress;
153
- const onEnded = options.onEnded;
154
- const sorted = [...keyframes].sort((a, b) => a.at - b.at);
155
- const totalDurationMs = sorted[sorted.length - 1].at;
156
- const invTotalDuration = 1 / totalDurationMs;
157
- const resolveKeyframeValue = (kf) => {
158
- return typeof kf.value === "function" ? kf.value() : kf.value;
159
- };
160
- const segments = [];
161
- for (let i = 0; i < sorted.length - 1; i++) {
162
- const current = sorted[i];
163
- const next = sorted[i + 1];
164
- const from = resolveKeyframeValue(current);
165
- const to = resolveKeyframeValue(next);
166
- segments.push({
167
- from,
168
- to,
169
- range: to - from,
170
- durationMs: next.at - current.at,
171
- easeFn: resolveEasing(next.ease ?? "inOutSine")
172
- });
173
- }
174
- const prefixSum = [0];
175
- for (let i = 0; i < segments.length; i++) prefixSum.push(prefixSum[i] + segments[i].durationMs);
176
- if (segments.length === 0) return createSingleTween({
177
- from: resolveKeyframeValue(sorted[0]),
178
- to: resolveKeyframeValue(sorted[0]),
179
- durationMs: totalDurationMs,
180
- ease: "linear",
181
- onUpdate,
182
- onEnded
183
- });
184
- const state = {
185
- progress: 0,
186
- currentValue: 0,
187
- velocity: 0
119
+ const { keyframes: rawKeyframes, onStarted, onUpdate, onProgress, onEnded } = options;
120
+ const resolveKeyframeGaps = () => {
121
+ let total = 0;
122
+ for (let i = 1; i < rawKeyframes.length; i++) total += resolveValue(rawKeyframes[i].gap ?? 0);
123
+ return total;
188
124
  };
125
+ let cachedDurationMs = resolveKeyframeGaps();
189
126
  let status = "stopped";
190
- let stopped = false;
191
127
  let resolvePromise;
192
- let currentSegmentIndex = 0;
193
- let previousValue = resolveKeyframeValue(sorted[0]);
194
128
  const ticker = getTicker();
195
- const update = (deltaMs) => {
196
- if (stopped) return;
197
- const segment = segments[currentSegmentIndex];
198
- segmentElapsed += deltaMs;
199
- segmentProgress += deltaMs / segment.durationMs;
200
- if (segmentProgress >= 1) segmentProgress = 1;
201
- const eased = segment.easeFn(segmentProgress);
202
- previousValue = state.currentValue;
203
- state.currentValue = segment.from + segment.range * eased;
204
- if (segmentProgress >= 1) {
205
- state.currentValue = segment.to;
206
- state.velocity = 0;
207
- } else state.velocity = (state.currentValue - previousValue) / (deltaMs / 1e3);
208
- const elapsedTotal = prefixSum[currentSegmentIndex] + segmentElapsed;
209
- state.progress = Math.min(elapsedTotal * invTotalDuration, 1);
210
- onProgress?.(state.progress);
211
- onUpdate?.(state.currentValue, state.velocity);
212
- if (segmentProgress >= 1) if (currentSegmentIndex < segments.length - 1) {
213
- currentSegmentIndex++;
214
- segmentElapsed = 0;
215
- segmentProgress = 0;
216
- state.currentValue = segments[currentSegmentIndex].from;
217
- previousValue = state.currentValue;
218
- state.velocity = 0;
219
- state.progress = Math.min(prefixSum[currentSegmentIndex] * invTotalDuration, 1);
220
- onProgress?.(state.progress);
221
- } else {
222
- status = "stopped";
223
- ticker.remove(update);
224
- onEnded?.();
225
- resolvePromise?.(controls);
226
- resolvePromise = void 0;
227
- }
129
+ let runner;
130
+ const finish = () => {
131
+ status = "stopped";
132
+ ticker.remove(runner);
133
+ resolvePromise?.(controls);
134
+ resolvePromise = void 0;
228
135
  };
229
- let segmentProgress = 0;
230
- let segmentElapsed = 0;
136
+ const buildRunner = () => {
137
+ return createKeyframeRunner({
138
+ keyframes: rawKeyframes.map((kf, i) => ({
139
+ value: resolveValue(kf.value),
140
+ gap: i === 0 ? 0 : resolveValue(kf.gap ?? 0),
141
+ easeFn: resolveEasing(kf.ease ?? "inOutSine")
142
+ })),
143
+ onStarted,
144
+ onUpdate,
145
+ onProgress,
146
+ onEnded,
147
+ onComplete: finish
148
+ });
149
+ };
150
+ const hasDynamic = rawKeyframes.some((kf) => typeof kf.value === "function" || typeof kf.gap === "function");
151
+ runner = buildRunner();
231
152
  const play = () => {
232
153
  if (status === "dead") throw new Error("Cannot play a dead animation");
233
- stopped = false;
234
- currentSegmentIndex = 0;
235
- segmentElapsed = 0;
236
- segmentProgress = 0;
237
- state.progress = 0;
238
- state.currentValue = segments[0].from;
239
- previousValue = segments[0].from;
240
- state.velocity = 0;
154
+ if (hasDynamic) {
155
+ runner = buildRunner();
156
+ cachedDurationMs = resolveKeyframeGaps();
157
+ } else runner.reset();
241
158
  const promise = new Promise((resolve) => {
242
159
  resolvePromise = resolve;
243
160
  });
244
161
  status = "playing";
245
- ticker.add(update);
162
+ ticker.add(runner);
163
+ onStarted?.();
246
164
  return promise;
247
165
  };
248
166
  const pause = () => {
249
167
  if (status !== "playing") return;
250
168
  status = "paused";
251
- ticker.remove(update);
169
+ ticker.remove(runner);
252
170
  };
253
171
  const resume = () => {
254
172
  if (status !== "paused") return;
255
173
  status = "playing";
256
- ticker.add(update);
174
+ ticker.add(runner);
257
175
  };
258
176
  const stop = () => {
259
- stopped = true;
260
177
  status = "stopped";
261
- ticker.remove(update);
178
+ ticker.remove(runner);
262
179
  resolvePromise?.(controls);
263
180
  resolvePromise = void 0;
264
181
  };
265
182
  const skipToEnd = () => {
266
- const last = segments[segments.length - 1];
267
- state.currentValue = last.to;
268
- state.velocity = 0;
269
- state.progress = 1;
270
- onUpdate?.(state.currentValue, state.velocity);
183
+ runner.evaluate(1);
271
184
  if (status === "playing" || status === "paused") onEnded?.();
272
185
  status = "stopped";
273
- ticker.remove(update);
186
+ ticker.remove(runner);
274
187
  resolvePromise?.(controls);
275
188
  resolvePromise = void 0;
276
189
  };
277
190
  const kill = () => {
278
191
  status = "dead";
279
- ticker.remove(update);
280
- stopped = true;
192
+ ticker.remove(runner);
281
193
  resolvePromise = void 0;
282
194
  };
283
195
  const controls = {
@@ -287,47 +199,24 @@ var createKeyframeAnimation = (options) => {
287
199
  stop,
288
200
  skipToEnd,
289
201
  kill,
290
- setCurrentValue: (value) => {
291
- state.currentValue = value;
292
- state.velocity = 0;
293
- },
294
202
  get currentValue() {
295
- return state.currentValue;
203
+ return runner.currentValue;
296
204
  },
297
205
  get velocity() {
298
- return state.velocity;
206
+ return runner.velocity;
299
207
  },
300
208
  get progress() {
301
- return state.progress;
209
+ return runner.progress;
302
210
  },
303
211
  setProgress(value) {
304
212
  if (status === "playing") pause();
305
- const clamped = Math.max(0, Math.min(1, value));
306
- state.progress = clamped;
307
- const elapsed = clamped * totalDurationMs;
308
- let segIdx = 0;
309
- for (let i = 0; i < segments.length; i++) {
310
- if (elapsed <= prefixSum[i + 1]) {
311
- segIdx = i;
312
- break;
313
- }
314
- segIdx = i;
315
- }
316
- const segment = segments[segIdx];
317
- const segStart = prefixSum[segIdx];
318
- const segDuration = segment.durationMs;
319
- const segProgress = segDuration > 0 ? (elapsed - segStart) / segDuration : 1;
320
- const eased = segment.easeFn(Math.max(0, Math.min(1, segProgress)));
321
- state.currentValue = segment.from + segment.range * eased;
322
- state.velocity = 0;
323
- onUpdate?.(state.currentValue, state.velocity);
324
- onProgress?.(clamped);
213
+ runner.evaluate(Math.max(0, Math.min(1, value)));
325
214
  },
326
215
  get status() {
327
216
  return status;
328
217
  },
329
218
  get durationMs() {
330
- return totalDurationMs;
219
+ return cachedDurationMs;
331
220
  }
332
221
  };
333
222
  return controls;
@@ -0,0 +1,37 @@
1
+ import { EaseFunction } from '../shared/types';
2
+ export type Runner = {
3
+ (deltaMs: number): boolean;
4
+ evaluate: (progress: number) => number;
5
+ reset: () => void;
6
+ currentValue: number;
7
+ velocity: number;
8
+ progress: number;
9
+ onStarted: (() => void) | undefined;
10
+ onUpdate: ((value: number, velocity: number) => void) | undefined;
11
+ onProgress: ((progress: number) => void) | undefined;
12
+ onEnded: (() => void) | undefined;
13
+ };
14
+ export type TweenRunnerConfig = {
15
+ from: number;
16
+ to: number;
17
+ durationMs: number;
18
+ easeFn: EaseFunction;
19
+ onStarted?: () => void;
20
+ onUpdate?: (value: number, velocity: number) => void;
21
+ onEnded?: () => void;
22
+ onComplete?: () => void;
23
+ };
24
+ export declare const createTweenRunner: (config: TweenRunnerConfig) => Runner;
25
+ export type KeyframeRunnerConfig = {
26
+ keyframes: {
27
+ value: number;
28
+ gap: number;
29
+ easeFn: EaseFunction;
30
+ }[];
31
+ onStarted?: () => void;
32
+ onUpdate?: (value: number, velocity: number) => void;
33
+ onProgress?: (progress: number) => void;
34
+ onEnded?: () => void;
35
+ onComplete?: () => void;
36
+ };
37
+ export declare const createKeyframeRunner: (config: KeyframeRunnerConfig) => Runner;
@@ -0,0 +1,220 @@
1
+ import { updateTween } from "./update.js";
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;
10
+ const state = {
11
+ progress: 0,
12
+ currentValue: from,
13
+ velocity: 0
14
+ };
15
+ let runner;
16
+ const step = (deltaMs) => {
17
+ const completed = updateTween(state, deltaMs, durationMs, easeFn, from, to);
18
+ onUpdate(state.currentValue, state.velocity);
19
+ if (completed) {
20
+ onEnded?.();
21
+ onComplete?.();
22
+ }
23
+ return completed;
24
+ };
25
+ const evaluate = (progress) => {
26
+ const clamped = Math.max(0, Math.min(1, progress));
27
+ state.progress = clamped;
28
+ if (clamped >= 1) {
29
+ state.currentValue = to;
30
+ state.velocity = 0;
31
+ } else {
32
+ const range = to - from;
33
+ state.currentValue = from + range * easeFn(clamped);
34
+ state.velocity = 0;
35
+ }
36
+ onUpdate(state.currentValue, state.velocity);
37
+ return state.currentValue;
38
+ };
39
+ const reset = () => {
40
+ state.progress = 0;
41
+ state.currentValue = from;
42
+ state.velocity = 0;
43
+ };
44
+ runner = step;
45
+ runner.evaluate = evaluate;
46
+ runner.reset = reset;
47
+ Object.defineProperty(runner, "currentValue", {
48
+ get: () => state.currentValue,
49
+ configurable: true
50
+ });
51
+ Object.defineProperty(runner, "velocity", {
52
+ get: () => state.velocity,
53
+ configurable: true
54
+ });
55
+ Object.defineProperty(runner, "progress", {
56
+ get: () => state.progress,
57
+ configurable: true
58
+ });
59
+ runner.onStarted = onStarted;
60
+ runner.onUpdate = config.onUpdate;
61
+ runner.onProgress = void 0;
62
+ runner.onEnded = onEnded;
63
+ return runner;
64
+ };
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;
72
+ const segments = [];
73
+ for (let i = 0; i < keyframes.length - 1; i++) {
74
+ const cur = keyframes[i];
75
+ const nxt = keyframes[i + 1];
76
+ segments.push({
77
+ from: cur.value,
78
+ to: nxt.value,
79
+ range: nxt.value - cur.value,
80
+ durationMs: nxt.gap,
81
+ easeFn: nxt.easeFn
82
+ });
83
+ }
84
+ const prefixSum = [0];
85
+ for (let i = 0; i < segments.length; i++) prefixSum.push(prefixSum[i] + segments[i].durationMs);
86
+ const totalDurationMs = prefixSum[prefixSum.length - 1];
87
+ const invTotalDuration = totalDurationMs > 0 ? 1 / totalDurationMs : 1;
88
+ let currentValue = keyframes[0].value;
89
+ let velocity = 0;
90
+ let globalProgress = 0;
91
+ let currentSegmentIndex = 0;
92
+ let segmentElapsed = 0;
93
+ let segmentProgress = 0;
94
+ 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) => {
133
+ const segment = segments[currentSegmentIndex];
134
+ segmentElapsed += deltaMs;
135
+ segmentProgress += deltaMs / segment.durationMs;
136
+ if (segmentProgress >= 1) segmentProgress = 1;
137
+ const previousValue = currentValue;
138
+ const eased = segment.easeFn(segmentProgress);
139
+ currentValue = segment.from + segment.range * eased;
140
+ if (segmentProgress >= 1) {
141
+ currentValue = segment.to;
142
+ velocity = 0;
143
+ } else velocity = (currentValue - previousValue) / (deltaMs / 1e3);
144
+ const elapsedTotal = prefixSum[currentSegmentIndex] + segmentElapsed;
145
+ globalProgress = Math.min(elapsedTotal * invTotalDuration, 1);
146
+ onProgress(globalProgress);
147
+ onUpdate(currentValue, velocity);
148
+ if (segmentProgress >= 1) if (currentSegmentIndex < segments.length - 1) {
149
+ currentSegmentIndex++;
150
+ segmentElapsed = 0;
151
+ segmentProgress = 0;
152
+ currentValue = segments[currentSegmentIndex].from;
153
+ velocity = 0;
154
+ globalProgress = Math.min(prefixSum[currentSegmentIndex] * invTotalDuration, 1);
155
+ onProgress(globalProgress);
156
+ } else {
157
+ onEnded?.();
158
+ onComplete?.();
159
+ return true;
160
+ }
161
+ return false;
162
+ };
163
+ const evaluate = (progress) => {
164
+ const clamped = Math.max(0, Math.min(1, progress));
165
+ const elapsed = clamped * totalDurationMs;
166
+ let segIdx = 0;
167
+ for (let i = 0; i < segments.length; i++) {
168
+ if (elapsed <= prefixSum[i + 1]) {
169
+ segIdx = i;
170
+ break;
171
+ }
172
+ segIdx = i;
173
+ }
174
+ const segment = segments[segIdx];
175
+ const segStart = prefixSum[segIdx];
176
+ const segDuration = segment.durationMs;
177
+ const segProgress = segDuration > 0 ? (elapsed - segStart) / segDuration : 1;
178
+ const clampedSegProgress = Math.max(0, Math.min(1, segProgress));
179
+ const eased = segment.easeFn(clampedSegProgress);
180
+ currentValue = segment.from + segment.range * eased;
181
+ velocity = 0;
182
+ globalProgress = clamped;
183
+ currentSegmentIndex = segIdx;
184
+ segmentElapsed = elapsed - segStart;
185
+ segmentProgress = clampedSegProgress;
186
+ onUpdate(currentValue, velocity);
187
+ onProgress(clamped);
188
+ return currentValue;
189
+ };
190
+ const reset = () => {
191
+ currentValue = keyframes[0].value;
192
+ velocity = 0;
193
+ globalProgress = 0;
194
+ currentSegmentIndex = 0;
195
+ segmentElapsed = 0;
196
+ segmentProgress = 0;
197
+ };
198
+ runner = step;
199
+ runner.evaluate = evaluate;
200
+ runner.reset = reset;
201
+ Object.defineProperty(runner, "currentValue", {
202
+ get: () => currentValue,
203
+ configurable: true
204
+ });
205
+ Object.defineProperty(runner, "velocity", {
206
+ get: () => velocity,
207
+ configurable: true
208
+ });
209
+ Object.defineProperty(runner, "progress", {
210
+ get: () => globalProgress,
211
+ configurable: true
212
+ });
213
+ runner.onStarted = onStarted;
214
+ runner.onUpdate = config.onUpdate;
215
+ runner.onProgress = config.onProgress;
216
+ runner.onEnded = onEnded;
217
+ return runner;
218
+ };
219
+ //#endregion
220
+ export { createKeyframeRunner, createTweenRunner };
@@ -10,7 +10,6 @@ export type Animation = {
10
10
  stop: () => void;
11
11
  skipToEnd: () => void;
12
12
  kill: () => void;
13
- setCurrentValue: (value: number) => void;
14
13
  currentValue: number;
15
14
  velocity: number;
16
15
  progress: number;
@@ -1,10 +1,11 @@
1
- import { Animation, AnimationStatus } from '../shared/types';
1
+ import { KeyframedAnimationOptions } from '../animation/create-animation';
2
+ import { AnimationStatus, DynamicValue } from '../shared/types';
2
3
  export type TimelineLayer = {
3
- at: number;
4
- animation: Animation | Animation[];
4
+ keyframe: KeyframedAnimationOptions;
5
+ at: DynamicValue;
5
6
  } | {
7
+ keyframe: KeyframedAnimationOptions;
6
8
  gap: number;
7
- animation: Animation | Animation[];
8
9
  };
9
10
  export type Timeline = {
10
11
  play: () => Promise<Timeline>;