@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.
- package/CHANGELOG.md +49 -0
- package/dist/__tests__/decorators.test.d.ts +2 -0
- package/dist/__tests__/decorators.test.d.ts.map +1 -0
- package/dist/__tests__/decorators.test.js +139 -0
- package/dist/__tests__/decorators.test.js.map +1 -0
- package/dist/__tests__/easings.test.js +64 -55
- package/dist/__tests__/easings.test.js.map +1 -1
- package/dist/__tests__/spring.test.js +59 -34
- package/dist/__tests__/spring.test.js.map +1 -1
- package/dist/__tests__/transition.test.js +71 -44
- package/dist/__tests__/transition.test.js.map +1 -1
- package/dist/__tests__/tween.test.js +74 -94
- package/dist/__tests__/tween.test.js.map +1 -1
- package/dist/decorators.d.ts +3 -1
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +39 -16
- package/dist/decorators.js.map +1 -1
- package/dist/index.d.ts +2 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -6
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/__tests__/decorators.test.ts +152 -0
- package/src/__tests__/easings.test.ts +66 -59
- package/src/__tests__/spring.test.ts +66 -34
- package/src/__tests__/transition.test.ts +74 -46
- package/src/__tests__/tween.test.ts +77 -95
- package/src/decorators.ts +42 -15
- package/src/index.ts +2 -15
- package/dist/__tests__/use-motion.test.d.ts +0 -2
- package/dist/__tests__/use-motion.test.d.ts.map +0 -1
- package/dist/__tests__/use-motion.test.js +0 -139
- package/dist/__tests__/use-motion.test.js.map +0 -1
- package/dist/use-motion.d.ts +0 -20
- package/dist/use-motion.d.ts.map +0 -1
- package/dist/use-motion.js +0 -58
- package/dist/use-motion.js.map +0 -1
- package/src/__tests__/use-motion.test.ts +0 -172
- 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
|
-
}
|