@vune-ui/animation 0.1.20

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 (68) hide show
  1. package/ARCHITECTURE.md +470 -0
  2. package/CHANGELOG.md +88 -0
  3. package/LICENSE +21 -0
  4. package/PERFORMANCE.md +151 -0
  5. package/README.md +630 -0
  6. package/dist/index.d.ts +474 -0
  7. package/dist/src/canvas/index.d.ts +15 -0
  8. package/dist/src/canvas/index.js +67 -0
  9. package/dist/src/constraints/index.d.ts +33 -0
  10. package/dist/src/constraints/index.js +346 -0
  11. package/dist/src/core/bezier.js +51 -0
  12. package/dist/src/core/composition.js +17 -0
  13. package/dist/src/core/controls.js +22 -0
  14. package/dist/src/core/default-engine.js +20 -0
  15. package/dist/src/core/easing.js +58 -0
  16. package/dist/src/core/engine.js +1031 -0
  17. package/dist/src/core/frame-budget.js +30 -0
  18. package/dist/src/core/index.d.ts +43 -0
  19. package/dist/src/core/index.js +17 -0
  20. package/dist/src/core/js-spring-batch.js +57 -0
  21. package/dist/src/core/kinetics.js +140 -0
  22. package/dist/src/core/math.js +20 -0
  23. package/dist/src/core/motion-value.js +53 -0
  24. package/dist/src/core/planner.js +72 -0
  25. package/dist/src/core/specs.js +70 -0
  26. package/dist/src/dom/index.d.ts +41 -0
  27. package/dist/src/dom/index.js +364 -0
  28. package/dist/src/gesture/index.d.ts +66 -0
  29. package/dist/src/gesture/index.js +376 -0
  30. package/dist/src/index.js +53 -0
  31. package/dist/src/interpolate/color.js +223 -0
  32. package/dist/src/interpolate/css.d.ts +13 -0
  33. package/dist/src/interpolate/css.js +34 -0
  34. package/dist/src/interpolate/index.d.ts +13 -0
  35. package/dist/src/interpolate/index.js +55 -0
  36. package/dist/src/interpolate/transform.js +247 -0
  37. package/dist/src/layout/index.d.ts +56 -0
  38. package/dist/src/layout/index.js +485 -0
  39. package/dist/src/material/index.d.ts +9 -0
  40. package/dist/src/material/index.js +70 -0
  41. package/dist/src/path/index.d.ts +37 -0
  42. package/dist/src/path/index.js +527 -0
  43. package/dist/src/render/frame-batcher.js +52 -0
  44. package/dist/src/scroll/index.d.ts +55 -0
  45. package/dist/src/scroll/index.js +233 -0
  46. package/dist/src/timeline/index.d.ts +147 -0
  47. package/dist/src/timeline/index.js +849 -0
  48. package/dist/src/transition/index.d.ts +88 -0
  49. package/dist/src/transition/index.js +369 -0
  50. package/dist/src/wasm/index.d.ts +29 -0
  51. package/dist/src/wasm/index.js +8 -0
  52. package/dist/src/wasm/loader.js +55 -0
  53. package/dist/src/wasm/shared-wasm-spring-batch.js +52 -0
  54. package/dist/src/wasm/wasm-spring-batch.js +52 -0
  55. package/dist/src/webgl/index.d.ts +22 -0
  56. package/dist/src/webgl/index.js +94 -0
  57. package/dist/src/webgpu/index.d.ts +35 -0
  58. package/dist/src/webgpu/index.js +73 -0
  59. package/dist/src/webgpu/spring-batch.js +218 -0
  60. package/dist/src/worker/index.d.ts +17 -0
  61. package/dist/src/worker/index.js +1 -0
  62. package/dist/src/worker/shared-spring-worker.js +218 -0
  63. package/dist/src/worker/shared-worker.js +75 -0
  64. package/dist/wasm/kernel-scalar.wasm +0 -0
  65. package/dist/wasm/kernel-shared-scalar.wasm +0 -0
  66. package/dist/wasm/kernel-shared-simd.wasm +0 -0
  67. package/dist/wasm/kernel-simd.wasm +0 -0
  68. package/package.json +113 -0
@@ -0,0 +1,474 @@
1
+ import { FrameBudgetGovernor as ExecutionFrameBudgetGovernor, type FrameBudgetSnapshot } from '@vune-ui/execution';
2
+
3
+ export type AnimationStatus = 'finished' | 'interrupted' | 'cancelled';
4
+ export type AnimationResult = { status: AnimationStatus; value: number; reducedMotion?: boolean };
5
+ export type SpringSpec = { kind: 'spring'; omega: number; dampingRatio: number; initialVelocity?: number; blendDuration?: number; source: string };
6
+ export type TimingSpec = { kind: 'timing'; duration: number; curve: BezierCurve };
7
+ export type ProfileSpec = { kind: 'profile'; name: string; options: Record<string, number> };
8
+ export type MotionSpec = SpringSpec | TimingSpec | ProfileSpec;
9
+ export type SpringMotionExecutionPlan = {
10
+ kind: 'motion-plan';
11
+ route: 'spring';
12
+ spec: SpringSpec;
13
+ omega: number;
14
+ dampingRatio: number;
15
+ initialVelocity?: number;
16
+ blendDurationMs: number;
17
+ };
18
+ export type TimingMotionExecutionPlan = {
19
+ kind: 'motion-plan';
20
+ route: 'timing';
21
+ spec: TimingSpec;
22
+ durationMs: number;
23
+ easing: CompiledEasing;
24
+ };
25
+ export type ProfileMotionExecutionPlan = {
26
+ kind: 'motion-plan';
27
+ route: 'profile';
28
+ spec: ProfileSpec;
29
+ };
30
+ export type MotionExecutionPlan = SpringMotionExecutionPlan | TimingMotionExecutionPlan | ProfileMotionExecutionPlan;
31
+ export type ResolvedMotionExecutionPlan = SpringMotionExecutionPlan | TimingMotionExecutionPlan;
32
+ export type DecayOptions = {
33
+ velocity?: number;
34
+ timeConstant?: number;
35
+ power?: number;
36
+ restSpeed?: number;
37
+ modifyTarget?: (target: number) => number;
38
+ };
39
+ export type DecaySpec = {
40
+ kind: 'decay';
41
+ velocity?: number;
42
+ timeConstant: number;
43
+ power: number;
44
+ restSpeed: number;
45
+ modifyTarget?: (target: number) => number;
46
+ };
47
+ export type InertiaOptions = DecayOptions & {
48
+ min?: number;
49
+ max?: number;
50
+ restDelta?: number;
51
+ bounce?: SpringSpec;
52
+ bounceResponse?: number;
53
+ bounceDampingRatio?: number;
54
+ };
55
+ export type InertiaSpec = {
56
+ kind: 'inertia';
57
+ velocity?: number;
58
+ timeConstant: number;
59
+ power: number;
60
+ restSpeed: number;
61
+ restDelta: number;
62
+ min: number;
63
+ max: number;
64
+ bounceOmega: number;
65
+ bounceDampingRatio: number;
66
+ modifyTarget?: (target: number) => number;
67
+ };
68
+ export type VelocityAnimationSpec = DecaySpec | InertiaSpec;
69
+ export type BezierCurve = { kind: 'bezier'; x1: number; y1: number; x2: number; y2: number };
70
+ export type CompiledEasing = { kind: 'linear' } | { kind: 'function'; easing: (progress: number) => number } | { kind: 'lut'; values: Float64Array };
71
+ export function compileEasing(easing?: BezierCurve | ((progress: number) => number)): CompiledEasing;
72
+ export function evaluateCompiledEasing(compiled: CompiledEasing, progress: number): number;
73
+ export function derivativeCompiledEasing(compiled: CompiledEasing, progress: number): number;
74
+ export type WorkerMode = boolean | 'auto';
75
+ export type GpuMode = boolean | 'auto';
76
+
77
+ export class MotionValue {
78
+ constructor(initial?: number);
79
+ get(): number;
80
+ getVelocity(): number;
81
+ getVersion(): number;
82
+ set(value: number, velocity?: number): void;
83
+ subscribe(listener: (value: number, info: { previous: number; velocity: number; version: number }) => void, options?: { emitCurrent?: boolean }): () => void;
84
+ subscribeValue(listener: (value: number) => void, options?: { emitCurrent?: boolean }): () => void;
85
+ }
86
+ export function motionValue(initial?: number): MotionValue;
87
+
88
+ export class AnimationControls {
89
+ cancel(): void;
90
+ finish(): void;
91
+ readonly finished: Promise<AnimationResult>;
92
+ }
93
+
94
+ export type MotionEngineOptions = {
95
+ autoStart?: boolean;
96
+ wasm?: 'auto' | boolean;
97
+ wasmThreshold?: number;
98
+ maxWasmMotions?: number;
99
+ worker?: WorkerMode;
100
+ workerThreshold?: number;
101
+ gpu?: GpuMode;
102
+ gpuThreshold?: number;
103
+ gpuDevice?: unknown;
104
+ autoWorkerScheduler?: boolean;
105
+ adaptiveBackends?: boolean;
106
+ frameBudgetMs?: number | false;
107
+ respectReducedMotion?: boolean;
108
+ };
109
+
110
+ export class MotionEngine {
111
+ constructor(options?: MotionEngineOptions);
112
+ animate(value: MotionValue, to: number, spec?: MotionSpec | MotionExecutionPlan): AnimationControls;
113
+ animateVelocity(value: MotionValue, spec?: VelocityAnimationSpec | InertiaOptions): AnimationControls;
114
+ addDriver(driver: { step(dtMs: number): boolean | void; owns?(value: MotionValue): boolean; interruptValue?(value: MotionValue, status?: 'cancelled' | 'interrupted'): boolean | void; onEngineDispose?(): void }): () => void;
115
+ removeDriver(driver: object): boolean;
116
+ stop(value: MotionValue, status?: 'cancelled' | 'interrupted'): void;
117
+ step(dtMs: number): void;
118
+ stepAsync(dtMs: number): Promise<void>;
119
+ prepareWasm(): Promise<unknown>;
120
+ prepareWorker(): Promise<unknown>;
121
+ prepareGpu(): Promise<unknown>;
122
+ maybePromoteToWasm(): void;
123
+ maybePromoteToWorker(): boolean;
124
+ maybePromoteToGpu(): boolean;
125
+ dispose(): void;
126
+ readonly stats: {
127
+ frames: number;
128
+ promotedToWasm: boolean;
129
+ promotedToWorker: boolean;
130
+ promotedToGpu: boolean;
131
+ backend: string;
132
+ lastDtMs: number;
133
+ syncFrames: number;
134
+ asyncFrames: number;
135
+ workerFrames: number;
136
+ workerFailures: number;
137
+ gpuFrames: number;
138
+ gpuFailures: number;
139
+ lastStepWallMs: number;
140
+ lastMainThreadMs: number;
141
+ emaMainThreadMs: number;
142
+ budgetPressure: number;
143
+ budgetLevel: 'idle' | 'comfortable' | 'pressured' | 'critical';
144
+ effectiveWasmThreshold: number;
145
+ effectiveWorkerThreshold: number;
146
+ activeSprings: number;
147
+ activeKinetics: number;
148
+ activeDrivers: number;
149
+ pendingMutations: number;
150
+ };
151
+ getBackendPlan(): {
152
+ current: string;
153
+ activeSprings: number;
154
+ activeKinetics: number;
155
+ wasm: { mode: 'auto' | boolean; ready: boolean; threshold: number };
156
+ worker: { mode: WorkerMode; ready: boolean; unavailable: boolean; threshold: number; inFlight: boolean };
157
+ gpu: { mode: GpuMode; ready: boolean; unavailable: boolean; threshold: number; inFlight: boolean };
158
+ budget: FrameBudgetSnapshot | null;
159
+ };
160
+ }
161
+
162
+
163
+ export type { FrameBudgetLevel, FrameBudgetSnapshot } from '@vune-ui/execution';
164
+
165
+ export class FrameBudgetGovernor extends ExecutionFrameBudgetGovernor {
166
+ constructor(options?: { budgetMs?: number; alpha?: number; minWasmThreshold?: number; minWorkerThreshold?: number });
167
+ wasmThreshold(baseThreshold: number, activeCount: number): number;
168
+ workerThreshold(baseThreshold: number, activeCount: number): number;
169
+ }
170
+
171
+ export const defaultEngine: MotionEngine;
172
+ export function animate(value: MotionValue, to: number, spec?: MotionSpec | MotionExecutionPlan): AnimationControls;
173
+ export function animateVelocity(value: MotionValue, spec?: VelocityAnimationSpec | InertiaOptions): AnimationControls;
174
+ export function animateDecay(value: MotionValue, options?: DecayOptions): AnimationControls;
175
+ export function animateInertia(value: MotionValue, options?: InertiaOptions): AnimationControls;
176
+ export function spring(options?: { response?: number; dampingRatio?: number; initialVelocity?: number; blendDuration?: number }): SpringSpec;
177
+ export namespace spring { function physics(options?: { mass?: number; stiffness?: number; damping?: number; initialVelocity?: number; blendDuration?: number }): SpringSpec; }
178
+ export function timing(options?: { duration?: number; curve?: BezierCurve }): TimingSpec;
179
+ export function cubicBezier(x1: number, y1: number, x2: number, y2: number): BezierCurve;
180
+ export const curves: Record<'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | 'smooth', BezierCurve>;
181
+ export function smooth(options?: { responseBias?: number }): ProfileSpec;
182
+ export function snappy(options?: { responseBias?: number }): ProfileSpec;
183
+ export function bouncy(options?: { responseBias?: number }): ProfileSpec;
184
+ export function gentle(options?: { responseBias?: number }): ProfileSpec;
185
+ export function interactive(options?: { responseBias?: number }): ProfileSpec;
186
+ export function resolveMotionSpec(spec: MotionSpec | undefined, from: number, to: number): SpringSpec | TimingSpec;
187
+ export function isMotionExecutionPlan(value: unknown): value is MotionExecutionPlan;
188
+ export function compileMotionPlan(spec?: MotionSpec | MotionExecutionPlan): MotionExecutionPlan;
189
+ export function resolveMotionPlan(plan: MotionSpec | MotionExecutionPlan | undefined, from: number, to: number): ResolvedMotionExecutionPlan;
190
+ export function decay(options?: DecayOptions): DecaySpec;
191
+ export function inertia(options?: InertiaOptions): InertiaSpec;
192
+ export function projectDecayTarget(value: number, velocity: number, spec: DecaySpec | InertiaSpec): number;
193
+ export function stepDecay(position: number, velocity: number, dtSeconds: number, timeConstant: number, out?: { position: number; velocity: number }): { position: number; velocity: number };
194
+ export function stepDampedSpring(position: number, velocity: number, target: number, omega: number, dampingRatio: number, dtSeconds: number, out?: { position: number; velocity: number }): { position: number; velocity: number };
195
+ export function delay(milliseconds: number): Promise<void>;
196
+ export function parallel(...factories: Array<(() => AnimationControls | Promise<unknown>) | Promise<unknown>>): Promise<unknown[]>;
197
+ export function sequence(...factories: Array<(() => AnimationControls | Promise<unknown>) | Promise<unknown>>): Promise<unknown[]>;
198
+ export { WebGPUSpringBatch } from './src/webgpu/index.js';
199
+
200
+ export type Color = { r: number; g: number; b: number; a?: number };
201
+ export type ColorInput = string | Color | [number, number, number] | [number, number, number, number];
202
+ export type ColorSpace = 'srgb' | 'linear-srgb' | 'oklab' | 'oklch';
203
+ export function parseColor(input: ColorInput): Required<Color>;
204
+ export function formatColor(color: Color): string;
205
+ export function mixColor(from: ColorInput, to: ColorInput, progress: number, options?: { space?: ColorSpace }): Required<Color>;
206
+ export function interpolateColor(from: ColorInput, to: ColorInput, options?: { space?: ColorSpace }): (progress: number) => string;
207
+
208
+ export type TransformValue = {
209
+ x?: number; y?: number; z?: number;
210
+ scale?: number; scaleX?: number; scaleY?: number; scaleZ?: number;
211
+ rotate?: number; rotateX?: number; rotateY?: number; rotateZ?: number;
212
+ skewX?: number; skewY?: number;
213
+ perspective?: number;
214
+ };
215
+ export type ParsedTransform = Required<Omit<TransformValue, 'scale' | 'rotate'>>;
216
+ export function parseTransform(input: string | TransformValue): ParsedTransform;
217
+ export function formatTransform(value: string | TransformValue): string;
218
+ export function mixTransform(from: string | TransformValue, to: string | TransformValue, progress: number, options?: { shortestRotation?: boolean }): ParsedTransform;
219
+ export function interpolateTransform(from: string | TransformValue, to: string | TransformValue, options?: { shortestRotation?: boolean }): (progress: number) => string;
220
+ export function interpolateNumber(from: number, to: number): (progress: number) => number;
221
+
222
+ export type PathMorphOptions = { align?: boolean; allowReverse?: boolean; precision?: number; alignmentCandidates?: number };
223
+
224
+ export type MaterialInput = 'clear' | 'ultraThin' | 'thin' | 'regular' | 'thick' | 'glass' | {
225
+ blur?: number;
226
+ saturation?: number;
227
+ brightness?: number;
228
+ contrast?: number;
229
+ tint?: ColorInput;
230
+ tintStrength?: number;
231
+ };
232
+ export type ResolvedMaterial = {
233
+ blur: number;
234
+ saturation: number;
235
+ brightness: number;
236
+ contrast: number;
237
+ tint: Required<Color>;
238
+ tintStrength: number;
239
+ };
240
+
241
+ export type InterpolatorOptions = {
242
+ type?: 'color' | 'transform' | 'path' | 'material';
243
+ color?: { space?: ColorSpace };
244
+ transform?: { shortestRotation?: boolean };
245
+ path?: PathMorphOptions;
246
+ material?: { colorSpace?: ColorSpace };
247
+ interpolate?: (from: unknown, to: unknown, progress: number) => unknown;
248
+ };
249
+ export function createInterpolator<T = unknown>(from: T, to: T, options?: InterpolatorOptions): (progress: number) => T | string | number;
250
+ export function animateInterpolated<T>(
251
+ from: T,
252
+ to: T,
253
+ spec: MotionSpec | undefined,
254
+ onUpdate: (value: unknown, progress: number) => void,
255
+ options?: InterpolatorOptions & { engine?: MotionEngine },
256
+ ): AnimationControls;
257
+
258
+
259
+ export type ParsedPathSegment = {
260
+ p0: { x: number; y: number };
261
+ p1: { x: number; y: number };
262
+ p2: { x: number; y: number };
263
+ p3: { x: number; y: number };
264
+ };
265
+ export function parsePath(path: string): { subpaths: Array<{ segments: ParsedPathSegment[]; closed: boolean }> };
266
+ export function normalizePathPair(fromPath: string, toPath: string, options?: PathMorphOptions): {
267
+ from: { coords: Float64Array; subpaths: Array<{ offset: number; count: number; closed: boolean }> };
268
+ to: { coords: Float64Array; subpaths: Array<{ offset: number; count: number; closed: boolean }> };
269
+ };
270
+ export class PathMorpher {
271
+ constructor(fromPath: string, toPath: string, options?: PathMorphOptions);
272
+ readonly coordinateCount: number;
273
+ readonly segmentCount: number;
274
+ readonly from: Float64Array;
275
+ readonly to: Float64Array;
276
+ readonly buffer: Float64Array;
277
+ sampleInto(progress: number, output?: Float64Array): Float64Array;
278
+ format(buffer?: Float64Array): string;
279
+ sample(progress: number): string;
280
+ }
281
+ export function createPathMorpher(fromPath: string, toPath: string, options?: PathMorphOptions): PathMorpher;
282
+ export function interpolatePath(fromPath: string, toPath: string, options?: PathMorphOptions): (progress: number) => string;
283
+
284
+ export const materials: Readonly<Record<'clear' | 'ultraThin' | 'thin' | 'regular' | 'thick' | 'glass', Readonly<MaterialInput>>>;
285
+ export function resolveMaterial(input?: MaterialInput): ResolvedMaterial;
286
+ export function mixMaterial(from: MaterialInput, to: MaterialInput, progress: number, options?: { colorSpace?: ColorSpace }): ResolvedMaterial;
287
+ export function interpolateMaterial(from: MaterialInput, to: MaterialInput, options?: { colorSpace?: ColorSpace }): (progress: number) => ResolvedMaterial;
288
+ export function materialToCss(input: MaterialInput): { backdropFilter: string; backgroundColor: string };
289
+
290
+
291
+ export type Point = { x: number; y: number };
292
+ export type DragBounds = { minX?: number; maxX?: number; minY?: number; maxY?: number };
293
+ export type DragAxis = 'x' | 'y' | 'both';
294
+ export type DragState = {
295
+ active: boolean;
296
+ axis: DragAxis;
297
+ lockedAxis: 'x' | 'y' | null;
298
+ point: Point;
299
+ value: { x: number | null; y: number | null };
300
+ velocity: Point;
301
+ };
302
+ export type GroupAnimationControls = { cancel(): void; finish(): void; finished: Promise<Array<AnimationResult>> };
303
+ export class VelocityTracker {
304
+ constructor(options?: { windowMs?: number; maxSamples?: number; maxVelocity?: number });
305
+ readonly velocity: number;
306
+ reset(value: number, time?: number): this;
307
+ add(value: number, time?: number): this;
308
+ }
309
+ export function rubberBandDistance(distance: number, dimension?: number, constant?: number): number;
310
+ export function constrainWithRubberBand(value: number, min?: number, max?: number, options?: { enabled?: boolean; constant?: number; dimension?: number }): number;
311
+ export type DragControllerOptions = {
312
+ x?: MotionValue | null; y?: MotionValue | null; axis?: DragAxis; engine?: MotionEngine;
313
+ bounds?: DragBounds | (() => DragBounds) | null; momentum?: boolean; inertia?: InertiaOptions;
314
+ rubberBand?: boolean | number; rubberBandConstant?: number; rubberBandDimension?: number | { x?: number; y?: number };
315
+ directionLock?: boolean; directionLockThreshold?: number;
316
+ snapX?: number[] | ((target: number) => number) | null; snapY?: number[] | ((target: number) => number) | null;
317
+ settle?: { response?: number; dampingRatio?: number };
318
+ velocity?: { windowMs?: number; maxSamples?: number; maxVelocity?: number };
319
+ onStart?: (state: DragState) => void; onMove?: (state: DragState) => void;
320
+ onEnd?: (state: DragState & { controls: GroupAnimationControls }) => void;
321
+ };
322
+ export class DragController {
323
+ constructor(options?: DragControllerOptions);
324
+ readonly active: boolean; readonly lockedAxis: 'x' | 'y' | null;
325
+ start(point: Point, time?: number): DragState; move(point: Point, time?: number): DragState;
326
+ end(time?: number): DragState & { controls: GroupAnimationControls };
327
+ cancel(options?: { settle?: boolean }): DragState; getState(): DragState;
328
+ }
329
+ export function createDragController(options?: DragControllerOptions): DragController;
330
+
331
+
332
+ export type TimelineEasing = BezierCurve | ((progress: number) => number);
333
+ export type TimelineKeyframe<T> = {
334
+ at?: number;
335
+ time?: number;
336
+ offset?: number;
337
+ value: T;
338
+ easing?: TimelineEasing;
339
+ };
340
+ export type TimelineTrackOptions = InterpolatorOptions & {
341
+ duration?: number;
342
+ easing?: TimelineEasing;
343
+ };
344
+ export type TimelineDirection = 'normal' | 'reverse' | 'alternate' | 'alternate-reverse';
345
+ export type TimelineFill = 'none' | 'forwards' | 'backwards' | 'both';
346
+ export type TimelineStatus = 'finished' | 'cancelled' | 'interrupted';
347
+ export type TimelineResult = {
348
+ status: TimelineStatus;
349
+ currentTime: number;
350
+ elapsedTime: number;
351
+ progress: number;
352
+ iteration: number;
353
+ };
354
+ export type TimelinePlayerOptions = {
355
+ engine?: MotionEngine;
356
+ autoplay?: boolean;
357
+ playbackRate?: number;
358
+ iterations?: number;
359
+ direction?: TimelineDirection;
360
+ onUpdate?: (player: TimelinePlayer) => void;
361
+ onRepeat?: (iteration: number, player: TimelinePlayer, crossedIterations: number) => void;
362
+ onComplete?: (player: TimelinePlayer) => void;
363
+ };
364
+
365
+ export class Timeline {
366
+ constructor(options?: { duration?: number; easing?: TimelineEasing });
367
+ readonly duration: number;
368
+ track(target: MotionValue, frames: Array<number | TimelineKeyframe<number>>, options?: { duration?: number; easing?: TimelineEasing }): this;
369
+ track<T>(target: ((value: T, velocity?: number) => void) | { set(value: T, velocity?: number): void }, frames: Array<T | TimelineKeyframe<T>>, options?: TimelineTrackOptions): this;
370
+ keyframes(target: MotionValue, frames: Array<number | TimelineKeyframe<number>>, options?: { duration?: number; easing?: TimelineEasing }): this;
371
+ keyframes<T>(target: ((value: T, velocity?: number) => void) | { set(value: T, velocity?: number): void }, frames: Array<T | TimelineKeyframe<T>>, options?: TimelineTrackOptions): this;
372
+ fromTo(target: MotionValue, from: number, to: number, options?: { at?: number; duration?: number; easing?: TimelineEasing }): this;
373
+ fromTo<T>(target: ((value: T, velocity?: number) => void) | { set(value: T, velocity?: number): void }, from: T, to: T, options?: TimelineTrackOptions & { at?: number }): this;
374
+ to(target: MotionValue, to: number, options?: { at?: number; duration?: number; easing?: TimelineEasing; from?: number }): this;
375
+ to<T>(target: ((value: T, velocity?: number) => void) | { get?(): T; set(value: T, velocity?: number): void }, to: T, options?: TimelineTrackOptions & { at?: number; from?: T }): this;
376
+ add(child: Timeline, options?: { at?: number; speed?: number; fill?: TimelineFill }): this;
377
+ sample(time: number, options?: { velocityScale?: number }): number;
378
+ zeroVelocities(): void;
379
+ hasMotionValue(value: MotionValue): boolean;
380
+ stopConflicts(engine: MotionEngine): void;
381
+ player(options?: TimelinePlayerOptions): TimelinePlayer;
382
+ }
383
+
384
+ export class TimelinePlayer {
385
+ constructor(timeline: Timeline, options?: TimelinePlayerOptions);
386
+ readonly timeline: Timeline;
387
+ readonly engine: MotionEngine;
388
+ readonly duration: number;
389
+ readonly totalDuration: number;
390
+ readonly finished: Promise<TimelineResult>;
391
+ readonly running: boolean;
392
+ state: 'idle' | 'running' | 'paused' | TimelineStatus;
393
+ playbackRate: number;
394
+ readonly iterations: number;
395
+ readonly direction: TimelineDirection;
396
+ elapsedTime: number;
397
+ currentTime: number;
398
+ progress: number;
399
+ iteration: number;
400
+ play(): this;
401
+ pause(): this;
402
+ cancel(): this;
403
+ finish(): this;
404
+ reverse(): this;
405
+ setPlaybackRate(rate: number): this;
406
+ seek(timeSeconds: number, options?: { iteration?: number }): this;
407
+ seekProgress(progress: number, options?: { iteration?: number }): this;
408
+ scrub(progress: number, options?: { iteration?: number }): this;
409
+ seekElapsed(elapsedSeconds: number): this;
410
+ step(dtMs: number): boolean;
411
+ owns(value: MotionValue): boolean;
412
+ interruptValue(value: MotionValue, status?: 'cancelled' | 'interrupted'): boolean;
413
+ }
414
+
415
+ export type PhaseTarget<T = unknown> =
416
+ | MotionValue
417
+ | ((value: T) => void)
418
+ | { set(value: T): void }
419
+ | ({ target: MotionValue | ((value: T) => void) | { set(value: T): void } } & TimelineTrackOptions);
420
+ export type PhaseDefinition = {
421
+ name?: string;
422
+ duration?: number;
423
+ hold?: number;
424
+ easing?: TimelineEasing;
425
+ values?: Record<string, unknown>;
426
+ };
427
+
428
+ export class PhaseTimeline {
429
+ constructor(
430
+ targets: Record<string, PhaseTarget>,
431
+ phases: PhaseDefinition[],
432
+ options?: { defaultDuration?: number; easing?: TimelineEasing },
433
+ );
434
+ readonly names: string[];
435
+ readonly arrivals: Float64Array;
436
+ readonly timeline: Timeline;
437
+ readonly duration: number;
438
+ phaseAt(timeSeconds: number): string;
439
+ player(options?: TimelinePlayerOptions): TimelinePlayer;
440
+ sample(time: number, options?: { velocityScale?: number }): number;
441
+ }
442
+
443
+ export function timeline(options?: { duration?: number; easing?: TimelineEasing }): Timeline;
444
+ export function createPhaseTimeline(
445
+ targets: Record<string, PhaseTarget>,
446
+ phases: PhaseDefinition[],
447
+ options?: { defaultDuration?: number; easing?: TimelineEasing },
448
+ ): PhaseTimeline;
449
+ export function stagger(
450
+ interval: number,
451
+ options?: { start?: number; from?: 'first' | 'last' | 'center' | number; easing?: TimelineEasing },
452
+ ): (index: number, total: number) => number;
453
+
454
+
455
+ export { TimelineScrubber, createTimelineScrubber } from './src/timeline/index.js';
456
+ export {
457
+ StateTransitionGraph,
458
+ TransitionController,
459
+ PresenceController,
460
+ createStateTransitionGraph,
461
+ createTransition,
462
+ createPresence,
463
+ } from './src/transition/index.js';
464
+ export type {
465
+ StateBinding,
466
+ TransitionBinding,
467
+ TransitionGroupControls,
468
+ TransitionGroupResult,
469
+ TransitionRoutes,
470
+ TransitionTarget,
471
+ } from './src/transition/index.js';
472
+
473
+ export * from './src/scroll/index.js';
474
+ export * from './src/constraints/index.js';
@@ -0,0 +1,15 @@
1
+ import type { MotionValue } from '../../index.js';
2
+ export class CanvasMotionRenderer {
3
+ constructor(context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | object, values: MotionValue[], draw: (context: any, values: Float64Array, time: number, renderer: CanvasMotionRenderer) => void, options?: {
4
+ autoClear?: boolean;
5
+ requestFrame?: (callback: FrameRequestCallback) => any;
6
+ cancelFrame?: (id: any) => void;
7
+ renderInitial?: boolean;
8
+ });
9
+ readonly snapshot: Float64Array;
10
+ readonly frames: number;
11
+ invalidate(): this;
12
+ renderNow(time?: number): this;
13
+ dispose(): void;
14
+ }
15
+ export function createCanvasRenderer(context: any, values: MotionValue[], draw: ConstructorParameters<typeof CanvasMotionRenderer>[2], options?: ConstructorParameters<typeof CanvasMotionRenderer>[3]): CanvasMotionRenderer;
@@ -0,0 +1,67 @@
1
+ import { FrameBatcher } from '../render/frame-batcher.js';
2
+
3
+ function isMotionValue(value) {
4
+ return value && typeof value.get === 'function' && (typeof value.subscribeValue === 'function' || typeof value.subscribe === 'function');
5
+ }
6
+
7
+ /**
8
+ * Coalesces any number of MotionValue changes into one Canvas draw per frame.
9
+ * The Float64Array passed to draw() is retained and reused for the lifetime of
10
+ * the renderer.
11
+ */
12
+ export class CanvasMotionRenderer {
13
+ constructor(context, values, draw, {
14
+ autoClear = false,
15
+ requestFrame,
16
+ cancelFrame,
17
+ renderInitial = true,
18
+ } = {}) {
19
+ if (!context || typeof draw !== 'function') throw new TypeError('CanvasMotionRenderer requires a context and draw callback.');
20
+ if (!Array.isArray(values)) throw new TypeError('CanvasMotionRenderer values must be an array.');
21
+ this.context = context;
22
+ this.values = values;
23
+ this.draw = draw;
24
+ this.autoClear = autoClear;
25
+ this.snapshot = new Float64Array(values.length);
26
+ this.unsubscribers = [];
27
+ this.frames = 0;
28
+ this.disposed = false;
29
+ this.batcher = new FrameBatcher((time) => this.#render(time), { requestFrame, cancelFrame });
30
+
31
+ values.forEach((value, index) => {
32
+ if (!isMotionValue(value)) throw new TypeError(`Canvas motion value at index ${index} is not MotionValue-like.`);
33
+ this.snapshot[index] = Number(value.get()) || 0;
34
+ const subscribe = value.subscribeValue ?? value.subscribe;
35
+ this.unsubscribers.push(subscribe.call(value, (next) => {
36
+ this.snapshot[index] = Number(next) || 0;
37
+ this.invalidate();
38
+ }, { emitCurrent: false }));
39
+ });
40
+ if (renderInitial) this.invalidate();
41
+ }
42
+
43
+ #render(time) {
44
+ if (this.disposed) return;
45
+ if (this.autoClear) {
46
+ const canvas = this.context.canvas;
47
+ if (canvas && typeof this.context.clearRect === 'function') this.context.clearRect(0, 0, canvas.width, canvas.height);
48
+ }
49
+ this.frames += 1;
50
+ this.draw(this.context, this.snapshot, time, this);
51
+ }
52
+
53
+ invalidate() { this.batcher.invalidate(); return this; }
54
+ renderNow(time) { this.batcher.flushNow(time); return this; }
55
+
56
+ dispose() {
57
+ if (this.disposed) return;
58
+ this.disposed = true;
59
+ this.batcher.dispose();
60
+ for (const unsubscribe of this.unsubscribers) unsubscribe?.();
61
+ this.unsubscribers.length = 0;
62
+ }
63
+ }
64
+
65
+ export function createCanvasRenderer(context, values, draw, options) {
66
+ return new CanvasMotionRenderer(context, values, draw, options);
67
+ }
@@ -0,0 +1,33 @@
1
+ import type { MotionEngine, MotionValue } from '../../index.js';
2
+
3
+ export class ConstraintNode {
4
+ readonly graph: ConstraintGraph;
5
+ readonly index: number;
6
+ readonly name: string;
7
+ get(): number;
8
+ getVelocity(): number;
9
+ set(value: number, velocity?: number): this;
10
+ }
11
+
12
+ export class ConstraintGraph {
13
+ constructor(options?: { engine?: MotionEngine | null });
14
+ readonly nodes: ConstraintNode[];
15
+ readonly dirty: boolean;
16
+ node(value?: number | MotionValue, options?: { name?: string }): ConstraintNode;
17
+ constant(value: number, options?: { name?: string }): ConstraintNode;
18
+ affine(target: ConstraintNode, source: ConstraintNode | number, options?: { scale?: number; offset?: number }): this;
19
+ follow(target: ConstraintNode, source: ConstraintNode | number, options?: { scale?: number; offset?: number }): this;
20
+ clamp(target: ConstraintNode, source: ConstraintNode | number, options?: { min?: number; max?: number }): this;
21
+ sum(target: ConstraintNode, a: ConstraintNode | number, b: ConstraintNode | number, options?: { scaleA?: number; scaleB?: number; offset?: number }): this;
22
+ mix(target: ConstraintNode, a: ConstraintNode | number, b: ConstraintNode | number, progress: ConstraintNode | number): this;
23
+ map(target: ConstraintNode, inputs: Array<ConstraintNode | number>, compute: (values: Float64Array, velocities: Float64Array, graph: ConstraintGraph) => number | { value: number; velocity?: number }): this;
24
+ compile(): this;
25
+ set(node: ConstraintNode, value: number, velocity?: number): this;
26
+ invalidate(): void;
27
+ evaluate(): boolean;
28
+ step(dtMs?: number): boolean;
29
+ attach(engine: MotionEngine): this;
30
+ detach(): this;
31
+ dispose(): void;
32
+ }
33
+ export function createConstraintGraph(options?: ConstructorParameters<typeof ConstraintGraph>[0]): ConstraintGraph;