@praxisjs/motion 0.2.3 → 1.0.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 (39) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/dist/__tests__/decorators.test.d.ts +2 -0
  3. package/dist/__tests__/decorators.test.d.ts.map +1 -0
  4. package/dist/__tests__/decorators.test.js +139 -0
  5. package/dist/__tests__/decorators.test.js.map +1 -0
  6. package/dist/__tests__/easings.test.js +64 -55
  7. package/dist/__tests__/easings.test.js.map +1 -1
  8. package/dist/__tests__/spring.test.js +59 -34
  9. package/dist/__tests__/spring.test.js.map +1 -1
  10. package/dist/__tests__/transition.test.js +71 -44
  11. package/dist/__tests__/transition.test.js.map +1 -1
  12. package/dist/__tests__/tween.test.js +74 -94
  13. package/dist/__tests__/tween.test.js.map +1 -1
  14. package/dist/decorators.d.ts +3 -1
  15. package/dist/decorators.d.ts.map +1 -1
  16. package/dist/decorators.js +39 -16
  17. package/dist/decorators.js.map +1 -1
  18. package/dist/index.d.ts +2 -10
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -6
  21. package/dist/index.js.map +1 -1
  22. package/package.json +3 -2
  23. package/src/__tests__/decorators.test.ts +152 -0
  24. package/src/__tests__/easings.test.ts +66 -59
  25. package/src/__tests__/spring.test.ts +66 -34
  26. package/src/__tests__/transition.test.ts +74 -46
  27. package/src/__tests__/tween.test.ts +77 -95
  28. package/src/decorators.ts +42 -15
  29. package/src/index.ts +2 -15
  30. package/dist/__tests__/use-motion.test.d.ts +0 -2
  31. package/dist/__tests__/use-motion.test.d.ts.map +0 -1
  32. package/dist/__tests__/use-motion.test.js +0 -139
  33. package/dist/__tests__/use-motion.test.js.map +0 -1
  34. package/dist/use-motion.d.ts +0 -20
  35. package/dist/use-motion.d.ts.map +0 -1
  36. package/dist/use-motion.js +0 -58
  37. package/dist/use-motion.js.map +0 -1
  38. package/src/__tests__/use-motion.test.ts +0 -172
  39. package/src/use-motion.ts +0 -89
@@ -1,172 +0,0 @@
1
- // @vitest-environment jsdom
2
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
3
-
4
- import { useMotion } from "../use-motion";
5
-
6
- function makeRef(el: HTMLElement | null = null) {
7
- return { current: el };
8
- }
9
-
10
- describe("useMotion", () => {
11
- beforeEach(() => {
12
- vi.useFakeTimers();
13
- });
14
-
15
- afterEach(() => {
16
- vi.clearAllTimers();
17
- vi.useRealTimers();
18
- });
19
-
20
- it("returns animate, enter, and exit functions", () => {
21
- const ref = makeRef(document.createElement("div"));
22
- const m = useMotion(ref);
23
- expect(typeof m.animate).toBe("function");
24
- expect(typeof m.enter).toBe("function");
25
- expect(typeof m.exit).toBe("function");
26
- });
27
-
28
- it("returns a cancel function when ref.current is null", () => {
29
- const ref = makeRef(null);
30
- const m = useMotion(ref);
31
- const cancel = m.animate({ opacity: [0, 1] });
32
- expect(typeof cancel).toBe("function");
33
- // calling cancel on null ref should not throw
34
- expect(() => { cancel(); }).not.toThrow();
35
- });
36
-
37
- it("sets opacity style during animation", async () => {
38
- const el = document.createElement("div");
39
- const ref = makeRef(el);
40
- const m = useMotion(ref);
41
-
42
- const rafCallbacks: FrameRequestCallback[] = [];
43
- vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
44
- rafCallbacks.push(cb);
45
- return 1;
46
- });
47
-
48
- m.animate({ opacity: [0, 1], duration: 100 });
49
-
50
- // Simulate first frame at t=50 (half way through)
51
- rafCallbacks[0]?.(50);
52
- expect(el.style.opacity).not.toBe("");
53
- });
54
-
55
- it("sets transform style for x, y, scale, rotate", () => {
56
- const el = document.createElement("div");
57
- const ref = makeRef(el);
58
- const m = useMotion(ref);
59
-
60
- const rafCallbacks: FrameRequestCallback[] = [];
61
- vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
62
- rafCallbacks.push(cb);
63
- return 1;
64
- });
65
-
66
- m.animate({ x: [0, 100], y: [0, 50], scale: [1, 2], rotate: [0, 90], duration: 200 });
67
-
68
- rafCallbacks[0]?.(100);
69
- expect(el.style.transform).toContain("translateX");
70
- expect(el.style.transform).toContain("translateY");
71
- expect(el.style.transform).toContain("scale");
72
- expect(el.style.transform).toContain("rotate");
73
- });
74
-
75
- it("calls onComplete when animation finishes", () => {
76
- const el = document.createElement("div");
77
- const ref = makeRef(el);
78
- const m = useMotion(ref);
79
- const onComplete = vi.fn();
80
-
81
- const rafCallbacks: FrameRequestCallback[] = [];
82
- vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
83
- rafCallbacks.push(cb);
84
- return rafCallbacks.length;
85
- });
86
-
87
- m.animate({ opacity: [0, 1], duration: 100, onComplete });
88
-
89
- // First frame sets startTime = 0
90
- rafCallbacks[0](0);
91
- // Second frame at ts=100: elapsed=100, t=1, onComplete fires
92
- rafCallbacks[1](100);
93
- expect(onComplete).toHaveBeenCalledTimes(1);
94
- });
95
-
96
- it("cancel() stops the animation", () => {
97
- const el = document.createElement("div");
98
- const ref = makeRef(el);
99
- const m = useMotion(ref);
100
-
101
- const cancelRaf = vi.fn();
102
- const rafCallbacks: FrameRequestCallback[] = [];
103
- vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
104
- rafCallbacks.push(cb);
105
- return 42;
106
- });
107
- vi.spyOn(window, "cancelAnimationFrame").mockImplementation(cancelRaf);
108
-
109
- const cancel = m.animate({ opacity: [0, 1], duration: 300 });
110
- cancel();
111
-
112
- // After cancel, further frames should be no-ops
113
- rafCallbacks[0]?.(100);
114
- expect(cancelRaf).toHaveBeenCalledWith(42);
115
- });
116
-
117
- it("exit() reverses opacity and x/y values", () => {
118
- const el = document.createElement("div");
119
- const ref = makeRef(el);
120
- const m = useMotion(ref);
121
-
122
- const rafCallbacks: FrameRequestCallback[] = [];
123
- vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
124
- rafCallbacks.push(cb);
125
- return 1;
126
- });
127
-
128
- // exit reverses opacity [0,1] → animates [1,0] and x [0,100] → [100,0]
129
- m.exit({ opacity: [0, 1], x: [0, 100], duration: 100 });
130
-
131
- rafCallbacks[0]?.(0);
132
- // At t=0 (start), opacity should be 1 (the reversed start)
133
- expect(el.style.opacity).toBe("1");
134
- });
135
-
136
- it("enter() is an alias for animate()", () => {
137
- const el = document.createElement("div");
138
- const ref = makeRef(el);
139
- const m = useMotion(ref);
140
- const onComplete = vi.fn();
141
-
142
- const rafCallbacks: FrameRequestCallback[] = [];
143
- vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
144
- rafCallbacks.push(cb);
145
- return rafCallbacks.length;
146
- });
147
-
148
- m.enter({ opacity: [0, 1], duration: 50, onComplete });
149
-
150
- rafCallbacks[0](0); // sets startTime=0
151
- rafCallbacks[1](50); // elapsed=50, t=1
152
- expect(onComplete).toHaveBeenCalledTimes(1);
153
- });
154
-
155
- it("respects delay option — no progress before delay elapses", () => {
156
- const el = document.createElement("div");
157
- const ref = makeRef(el);
158
- const m = useMotion(ref);
159
-
160
- const rafCallbacks: FrameRequestCallback[] = [];
161
- vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
162
- rafCallbacks.push(cb);
163
- return 1;
164
- });
165
-
166
- m.animate({ opacity: [0, 1], delay: 200, duration: 100 });
167
-
168
- // At ts=50, delay not yet passed; opacity should be at start value (0)
169
- rafCallbacks[0]?.(50);
170
- expect(el.style.opacity).toBe("0");
171
- });
172
- });
package/src/use-motion.ts DELETED
@@ -1,89 +0,0 @@
1
- import { resolveEasing, type Easing } from "./easings";
2
-
3
- export interface MotionKeyframes {
4
- opacity?: [number, number];
5
- x?: [number, number];
6
- y?: [number, number];
7
- scale?: [number, number];
8
- rotate?: [number, number];
9
- duration?: number;
10
- easing?: Easing;
11
- delay?: number;
12
- onComplete?: () => void;
13
- }
14
-
15
- export function useMotion(ref: { current: HTMLElement | null }) {
16
- function animate(keyframes: MotionKeyframes): () => void {
17
- const el: HTMLElement | null = ref.current;
18
- if (!el) return () => undefined;
19
- const safeEl = el;
20
-
21
- const {
22
- opacity,
23
- x,
24
- y,
25
- scale,
26
- rotate,
27
- duration = 300,
28
- easing = "easeOut",
29
- delay = 0,
30
- onComplete,
31
- } = keyframes;
32
- const easeFn = resolveEasing(easing);
33
- let raf: number | undefined;
34
- let startTime: number | undefined;
35
- let cancelled = false;
36
-
37
- function frame(ts: number) {
38
- if (cancelled) return;
39
- startTime ??= ts + delay;
40
- const elapsed = Math.max(0, ts - startTime);
41
- const t = Math.min(elapsed / duration, 1);
42
- const e = easeFn(t);
43
-
44
- const transforms: string[] = [];
45
- if (x)
46
- transforms.push(`translateX(${String(x[0] + (x[1] - x[0]) * e)}px)`);
47
- if (y)
48
- transforms.push(`translateY(${String(y[0] + (y[1] - y[0]) * e)}px)`);
49
- if (scale)
50
- transforms.push(
51
- `scale(${String(scale[0] + (scale[1] - scale[0]) * e)})`,
52
- );
53
- if (rotate)
54
- transforms.push(
55
- `rotate(${String(rotate[0] + (rotate[1] - rotate[0]) * e)}deg)`,
56
- );
57
-
58
- if (transforms.length) safeEl.style.transform = transforms.join(" ");
59
- if (opacity)
60
- safeEl.style.opacity = String(
61
- opacity[0] + (opacity[1] - opacity[0]) * e,
62
- );
63
-
64
- if (t < 1) {
65
- raf = requestAnimationFrame(frame);
66
- } else {
67
- onComplete?.();
68
- }
69
- }
70
-
71
- raf = requestAnimationFrame(frame);
72
- return () => {
73
- cancelled = true;
74
- if (raf) cancelAnimationFrame(raf);
75
- };
76
- }
77
-
78
- return {
79
- animate,
80
- enter: (kf: MotionKeyframes) => animate(kf),
81
- exit: (kf: MotionKeyframes) =>
82
- animate({
83
- ...kf,
84
- opacity: kf.opacity ? [kf.opacity[1], kf.opacity[0]] : undefined,
85
- x: kf.x ? [kf.x[1], kf.x[0]] : undefined,
86
- y: kf.y ? [kf.y[1], kf.y[0]] : undefined,
87
- }),
88
- };
89
- }