@lab206/animate 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/LICENSE +114 -0
  2. package/README.md +6 -0
  3. package/dist/animate.d.ts +28 -0
  4. package/dist/animate.d.ts.map +1 -0
  5. package/dist/animate.js +184 -0
  6. package/dist/animate.js.map +1 -0
  7. package/dist/driver.d.ts +24 -0
  8. package/dist/driver.d.ts.map +1 -0
  9. package/dist/driver.js +87 -0
  10. package/dist/driver.js.map +1 -0
  11. package/dist/easings.d.ts +6 -0
  12. package/dist/easings.d.ts.map +1 -0
  13. package/dist/easings.js +36 -0
  14. package/dist/easings.js.map +1 -0
  15. package/dist/gsap-bridge.d.ts +65 -0
  16. package/dist/gsap-bridge.d.ts.map +1 -0
  17. package/dist/gsap-bridge.js +98 -0
  18. package/dist/gsap-bridge.js.map +1 -0
  19. package/dist/gsap.d.ts +8 -0
  20. package/dist/gsap.d.ts.map +1 -0
  21. package/dist/gsap.js +21 -0
  22. package/dist/gsap.js.map +1 -0
  23. package/dist/index.d.ts +22 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +20 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/playback.d.ts +10 -0
  28. package/dist/playback.d.ts.map +1 -0
  29. package/dist/playback.js +58 -0
  30. package/dist/playback.js.map +1 -0
  31. package/dist/reduced-motion.d.ts +11 -0
  32. package/dist/reduced-motion.d.ts.map +1 -0
  33. package/dist/reduced-motion.js +25 -0
  34. package/dist/reduced-motion.js.map +1 -0
  35. package/dist/registry.d.ts +8 -0
  36. package/dist/registry.d.ts.map +1 -0
  37. package/dist/registry.js +19 -0
  38. package/dist/registry.js.map +1 -0
  39. package/dist/types.d.ts +69 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/types.js +2 -0
  42. package/dist/types.js.map +1 -0
  43. package/package.json +73 -0
  44. package/src/animate.ts +256 -0
  45. package/src/driver.ts +100 -0
  46. package/src/easings.ts +41 -0
  47. package/src/gsap-bridge.ts +183 -0
  48. package/src/gsap.ts +30 -0
  49. package/src/index.ts +45 -0
  50. package/src/playback.ts +67 -0
  51. package/src/reduced-motion.ts +23 -0
  52. package/src/registry.ts +31 -0
  53. package/src/types.ts +88 -0
@@ -0,0 +1,67 @@
1
+ import type { AnimationPlayback, PlayState } from "./types.js";
2
+
3
+ export interface PlaybackController extends AnimationPlayback {
4
+ /** @internal */
5
+ _resolveFinished(): void;
6
+ }
7
+
8
+ export function createPlayback(handlers: {
9
+ onCancel: () => void;
10
+ onComplete: () => void;
11
+ }): PlaybackController {
12
+ let playState: PlayState = "running";
13
+ let resolveFinished!: () => void;
14
+ let rejectFinished!: (reason?: unknown) => void;
15
+ let settled = false;
16
+
17
+ const finished = new Promise<void>((resolve, reject) => {
18
+ resolveFinished = () => {
19
+ if (settled) return;
20
+ settled = true;
21
+ resolve();
22
+ };
23
+ rejectFinished = (reason?: unknown) => {
24
+ if (settled) return;
25
+ settled = true;
26
+ reject(reason ?? abortError());
27
+ };
28
+ });
29
+
30
+ // Prevent unhandled rejection when nobody awaits cancel.
31
+ finished.catch(() => {});
32
+
33
+ const playback: PlaybackController = {
34
+ get playState() {
35
+ return playState;
36
+ },
37
+ finished,
38
+ cancel() {
39
+ if (playState !== "running") return;
40
+ playState = "cancelled";
41
+ handlers.onCancel();
42
+ rejectFinished();
43
+ },
44
+ complete() {
45
+ if (playState !== "running") return;
46
+ playState = "finished";
47
+ handlers.onComplete();
48
+ resolveFinished();
49
+ },
50
+ _resolveFinished() {
51
+ if (playState === "cancelled") return;
52
+ playState = "finished";
53
+ resolveFinished();
54
+ },
55
+ };
56
+
57
+ return playback;
58
+ }
59
+
60
+ function abortError(): Error {
61
+ if (typeof DOMException !== "undefined") {
62
+ return new DOMException("Animation cancelled", "AbortError");
63
+ }
64
+ const err = new Error("Animation cancelled");
65
+ err.name = "AbortError";
66
+ return err;
67
+ }
@@ -0,0 +1,23 @@
1
+ let override: boolean | null = null;
2
+
3
+ /**
4
+ * Detect `prefers-reduced-motion: reduce`.
5
+ * Safe on server (returns false unless overridden).
6
+ */
7
+ export function prefersReducedMotion(): boolean {
8
+ if (override !== null) return override;
9
+ if (typeof matchMedia !== "function") return false;
10
+ try {
11
+ return matchMedia("(prefers-reduced-motion: reduce)").matches;
12
+ } catch {
13
+ return false;
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Force reduced-motion on/off (tests, user settings).
19
+ * Pass `null` to clear the override.
20
+ */
21
+ export function setReducedMotionOverride(value: boolean | null): void {
22
+ override = value;
23
+ }
@@ -0,0 +1,31 @@
1
+ import type { Animatable } from "./types.js";
2
+ import type { AnimationPlayback } from "./types.js";
3
+
4
+ /** One active animation per signal — new animate() interrupts the previous. */
5
+ const active = new WeakMap<Animatable, AnimationPlayback>();
6
+
7
+ export function getActive(target: Animatable): AnimationPlayback | undefined {
8
+ return active.get(target);
9
+ }
10
+
11
+ export function setActive(
12
+ target: Animatable,
13
+ playback: AnimationPlayback,
14
+ ): void {
15
+ active.set(target, playback);
16
+ }
17
+
18
+ export function clearActive(
19
+ target: Animatable,
20
+ playback: AnimationPlayback,
21
+ ): void {
22
+ if (active.get(target) === playback) {
23
+ active.delete(target);
24
+ }
25
+ }
26
+
27
+ /** Cancel the in-flight animation on a signal, if any. */
28
+ export function cancel(target: Animatable): void {
29
+ const current = active.get(target);
30
+ current?.cancel();
31
+ }
package/src/types.ts ADDED
@@ -0,0 +1,88 @@
1
+ import type { Signal } from "@lab206/core";
2
+
3
+ /** Easing function: progress 0..1 → eased 0..1. */
4
+ export type EasingFn = (t: number) => number;
5
+
6
+ /** Named easings shipped with the library. */
7
+ export type EaseName =
8
+ | "linear"
9
+ | "easeIn"
10
+ | "easeOut"
11
+ | "easeInOut"
12
+ | "easeInCubic"
13
+ | "easeOutCubic"
14
+ | "easeInOutCubic"
15
+ | "easeInBack"
16
+ | "easeOutBack"
17
+ | "easeInOutBack";
18
+
19
+ export type Ease = EaseName | EasingFn;
20
+
21
+ /** Playback state of a single animation. */
22
+ export type PlayState = "running" | "finished" | "cancelled";
23
+
24
+ /**
25
+ * Handle returned by `animate()`.
26
+ * - `finished` resolves on natural completion (not on cancel)
27
+ * - `cancel()` stops mid-flight and leaves the current value
28
+ * - `complete()` jumps to the end value and finishes
29
+ */
30
+ export interface AnimationPlayback {
31
+ readonly playState: PlayState;
32
+ readonly finished: Promise<void>;
33
+ cancel(): void;
34
+ complete(): void;
35
+ }
36
+
37
+ /** Duration-based tween options. */
38
+ export interface TweenOptions {
39
+ type?: "tween";
40
+ /** Duration in milliseconds. Default: 300. */
41
+ duration?: number;
42
+ /** Delay before starting, in ms. Default: 0. */
43
+ delay?: number;
44
+ /** Easing curve. Default: "easeOut". */
45
+ ease?: Ease;
46
+ /** Override the starting value (defaults to `target.peek()`). */
47
+ from?: number;
48
+ /**
49
+ * Honor `prefers-reduced-motion` (jump to end).
50
+ * Default: true.
51
+ */
52
+ respectReducedMotion?: boolean;
53
+ onUpdate?: (value: number) => void;
54
+ onComplete?: () => void;
55
+ onCancel?: () => void;
56
+ }
57
+
58
+ /** Spring physics options. */
59
+ export interface SpringOptions {
60
+ type: "spring";
61
+ stiffness?: number;
62
+ damping?: number;
63
+ mass?: number;
64
+ /** Initial velocity (units per second). */
65
+ velocity?: number;
66
+ /** Settle threshold for position. */
67
+ restDelta?: number;
68
+ /** Settle threshold for velocity. */
69
+ restSpeed?: number;
70
+ delay?: number;
71
+ from?: number;
72
+ respectReducedMotion?: boolean;
73
+ onUpdate?: (value: number) => void;
74
+ onComplete?: () => void;
75
+ onCancel?: () => void;
76
+ }
77
+
78
+ export type AnimateOptions = TweenOptions | SpringOptions;
79
+
80
+ /** Anything we can animate — a writable number signal. */
81
+ export type Animatable = Signal<number>;
82
+
83
+ /** @internal Driver for time + frames (swappable in tests). */
84
+ export interface FrameDriver {
85
+ now(): number;
86
+ raf(cb: (time: number) => void): number;
87
+ caf(id: number): void;
88
+ }