@praxisjs/motion 0.2.3 → 1.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 (55) hide show
  1. package/CHANGELOG.md +65 -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 +179 -0
  5. package/dist/__tests__/decorators.test.js.map +1 -0
  6. package/dist/__tests__/easings.test.js +82 -55
  7. package/dist/__tests__/easings.test.js.map +1 -1
  8. package/dist/__tests__/spring.test.js +80 -34
  9. package/dist/__tests__/spring.test.js.map +1 -1
  10. package/dist/__tests__/transition.test.js +111 -43
  11. package/dist/__tests__/transition.test.js.map +1 -1
  12. package/dist/__tests__/tween.test.js +107 -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/easings.d.ts.map +1 -1
  19. package/dist/easings.js +7 -1
  20. package/dist/easings.js.map +1 -1
  21. package/dist/index.d.ts +2 -10
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1 -6
  24. package/dist/index.js.map +1 -1
  25. package/dist/spring.d.ts.map +1 -1
  26. package/dist/spring.js +3 -0
  27. package/dist/spring.js.map +1 -1
  28. package/dist/transition.d.ts.map +1 -1
  29. package/dist/transition.js +34 -18
  30. package/dist/transition.js.map +1 -1
  31. package/dist/tween.d.ts.map +1 -1
  32. package/dist/tween.js +2 -1
  33. package/dist/tween.js.map +1 -1
  34. package/package.json +3 -2
  35. package/src/__tests__/decorators.test.ts +196 -0
  36. package/src/__tests__/easings.test.ts +86 -57
  37. package/src/__tests__/spring.test.ts +90 -34
  38. package/src/__tests__/transition.test.ts +122 -45
  39. package/src/__tests__/tween.test.ts +116 -97
  40. package/src/decorators.ts +42 -15
  41. package/src/easings.ts +6 -1
  42. package/src/index.ts +2 -15
  43. package/src/spring.ts +4 -0
  44. package/src/transition.ts +28 -18
  45. package/src/tween.ts +2 -1
  46. package/dist/__tests__/use-motion.test.d.ts +0 -2
  47. package/dist/__tests__/use-motion.test.d.ts.map +0 -1
  48. package/dist/__tests__/use-motion.test.js +0 -139
  49. package/dist/__tests__/use-motion.test.js.map +0 -1
  50. package/dist/use-motion.d.ts +0 -20
  51. package/dist/use-motion.d.ts.map +0 -1
  52. package/dist/use-motion.js +0 -58
  53. package/dist/use-motion.js.map +0 -1
  54. package/src/__tests__/use-motion.test.ts +0 -172
  55. package/src/use-motion.ts +0 -89
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAGnD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGlC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGhD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"spring.d.ts","sourceRoot":"","sources":["../src/spring.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB;;;;EA2ClE"}
1
+ {"version":3,"file":"spring.d.ts","sourceRoot":"","sources":["../src/spring.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB;;;;EA+ClE"}
package/dist/spring.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { signal, computed, effect } from "@praxisjs/core/internal";
2
2
  export function spring(initial, options = {}) {
3
3
  const { stiffness = 0.15, damping = 0.8, mass = 1, precision = 0.001, } = options;
4
+ if (stiffness <= 0) {
5
+ throw new Error("stiffness must be greater than 0");
6
+ }
4
7
  const _value = signal(initial);
5
8
  const _target = signal(initial);
6
9
  let velocity = 0;
@@ -1 +1 @@
1
- {"version":3,"file":"spring.js","sourceRoot":"","sources":["../src/spring.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AASnE,MAAM,UAAU,MAAM,CAAC,OAAe,EAAE,UAAyB,EAAE;IACjE,MAAM,EACJ,SAAS,GAAG,IAAI,EAChB,OAAO,GAAG,GAAG,EACb,IAAI,GAAG,CAAC,EACR,SAAS,GAAG,KAAK,GAClB,GAAG,OAAO,CAAC;IAEZ,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/B,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAChC,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,GAAuB,CAAC;IAE5B,SAAS,IAAI;QACX,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,OAAO,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,CAAC,MAAM,GAAG,OAAO,CAAC,GAAG,SAAS,CAAC;QAC7C,QAAQ,GAAG,CAAC,QAAQ,GAAG,KAAK,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC;QAE/C,MAAM,IAAI,GAAG,OAAO,GAAG,QAAQ,CAAC;QAChC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAEjB,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,SAAS,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,SAAS,EAAE,CAAC;YAC1E,GAAG,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;QACpC,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACnB,QAAQ,GAAG,CAAC,CAAC;QACf,CAAC;IACH,CAAC;IAED,MAAM,CAAC,GAAG,EAAE;QACV,KAAK,OAAO,EAAE,CAAC;QACf,IAAI,GAAG;YAAE,oBAAoB,CAAC,GAAG,CAAC,CAAC;QACnC,GAAG,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,KAAK,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC;QAC/B,MAAM,EAAE,OAAO;QACf,IAAI;YACF,IAAI,GAAG;gBAAE,oBAAoB,CAAC,GAAG,CAAC,CAAC;QACrC,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"spring.js","sourceRoot":"","sources":["../src/spring.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AASnE,MAAM,UAAU,MAAM,CAAC,OAAe,EAAE,UAAyB,EAAE;IACjE,MAAM,EACJ,SAAS,GAAG,IAAI,EAChB,OAAO,GAAG,GAAG,EACb,IAAI,GAAG,CAAC,EACR,SAAS,GAAG,KAAK,GAClB,GAAG,OAAO,CAAC;IAEZ,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACtD,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/B,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAChC,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,GAAuB,CAAC;IAE5B,SAAS,IAAI;QACX,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,OAAO,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,CAAC,MAAM,GAAG,OAAO,CAAC,GAAG,SAAS,CAAC;QAC7C,QAAQ,GAAG,CAAC,QAAQ,GAAG,KAAK,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC;QAE/C,MAAM,IAAI,GAAG,OAAO,GAAG,QAAQ,CAAC;QAChC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAEjB,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,SAAS,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,SAAS,EAAE,CAAC;YAC1E,GAAG,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;QACpC,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACnB,QAAQ,GAAG,CAAC,CAAC;QACf,CAAC;IACH,CAAC;IAED,MAAM,CAAC,GAAG,EAAE;QACV,KAAK,OAAO,EAAE,CAAC;QACf,IAAI,GAAG;YAAE,oBAAoB,CAAC,GAAG,CAAC,CAAC;QACnC,GAAG,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,KAAK,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC;QAC/B,MAAM,EAAE,OAAO;QACf,IAAI;YACF,IAAI,GAAG;gBAAE,oBAAoB,CAAC,GAAG,CAAC,CAAC;QACrC,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"transition.d.ts","sourceRoot":"","sources":["../src/transition.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,IAAI,CAAC;IACpC,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,IAAI,CAAC;CACrC;AAED,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,iBAAsB;cAIlD,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;cAc3B,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;EAexC"}
1
+ {"version":3,"file":"transition.d.ts","sourceRoot":"","sources":["../src/transition.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,IAAI,CAAC;IACpC,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,IAAI,CAAC;CACrC;AAED,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,iBAAsB;cAIlD,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;cAmB3B,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;EAoBxC"}
@@ -2,28 +2,44 @@ export function createTransition(options = {}) {
2
2
  const { name = "transition", duration = 300, onEnter, onLeave } = options;
3
3
  return {
4
4
  enter(el) {
5
- onEnter?.(el);
6
- el.classList.add(`${name}-enter-from`);
7
- requestAnimationFrame(() => {
8
- el.classList.remove(`${name}-enter-from`);
9
- el.classList.add(`${name}-enter-to`);
5
+ return new Promise((res, rej) => {
6
+ try {
7
+ onEnter?.(el);
8
+ }
9
+ catch (err) {
10
+ rej(err instanceof Error ? err : new Error(String(err)));
11
+ return;
12
+ }
13
+ el.classList.add(`${name}-enter-from`);
14
+ requestAnimationFrame(() => {
15
+ el.classList.remove(`${name}-enter-from`);
16
+ el.classList.add(`${name}-enter-to`);
17
+ });
18
+ setTimeout(() => {
19
+ el.classList.remove(`${name}-enter-to`);
20
+ res();
21
+ }, duration);
10
22
  });
11
- return new Promise((res) => setTimeout(() => {
12
- el.classList.remove(`${name}-enter-to`);
13
- res();
14
- }, duration));
15
23
  },
16
24
  leave(el) {
17
- onLeave?.(el);
18
- el.classList.add(`${name}-leave-from`);
19
- requestAnimationFrame(() => {
20
- el.classList.remove(`${name}-leave-from`);
21
- el.classList.add(`${name}-leave-to`);
25
+ return new Promise((res, rej) => {
26
+ try {
27
+ onLeave?.(el);
28
+ }
29
+ catch (err) {
30
+ rej(err instanceof Error ? err : new Error(String(err)));
31
+ return;
32
+ }
33
+ el.classList.add(`${name}-leave-from`);
34
+ requestAnimationFrame(() => {
35
+ el.classList.remove(`${name}-leave-from`);
36
+ el.classList.add(`${name}-leave-to`);
37
+ });
38
+ setTimeout(() => {
39
+ el.classList.remove(`${name}-leave-to`);
40
+ res();
41
+ }, duration);
22
42
  });
23
- return new Promise((res) => setTimeout(() => {
24
- el.classList.remove(`${name}-leave-to`);
25
- res();
26
- }, duration));
27
43
  },
28
44
  };
29
45
  }
@@ -1 +1 @@
1
- {"version":3,"file":"transition.js","sourceRoot":"","sources":["../src/transition.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,gBAAgB,CAAC,UAA6B,EAAE;IAC9D,MAAM,EAAE,IAAI,GAAG,YAAY,EAAE,QAAQ,GAAG,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAE1E,OAAO;QACL,KAAK,CAAC,EAAe;YACnB,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;YACd,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,aAAa,CAAC,CAAC;YACvC,qBAAqB,CAAC,GAAG,EAAE;gBACzB,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,aAAa,CAAC,CAAC;gBAC1C,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,WAAW,CAAC,CAAC;YACvC,CAAC,CAAC,CAAC;YACH,OAAO,IAAI,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CACzB,UAAU,CAAC,GAAG,EAAE;gBACd,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,WAAW,CAAC,CAAC;gBACxC,GAAG,EAAE,CAAC;YACR,CAAC,EAAE,QAAQ,CAAC,CACb,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,EAAe;YACnB,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;YACd,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,aAAa,CAAC,CAAC;YACvC,qBAAqB,CAAC,GAAG,EAAE;gBACzB,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,aAAa,CAAC,CAAC;gBAC1C,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,WAAW,CAAC,CAAC;YACvC,CAAC,CAAC,CAAC;YACH,OAAO,IAAI,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CACzB,UAAU,CAAC,GAAG,EAAE;gBACd,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,WAAW,CAAC,CAAC;gBACxC,GAAG,EAAE,CAAC;YACR,CAAC,EAAE,QAAQ,CAAC,CACb,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"transition.js","sourceRoot":"","sources":["../src/transition.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,gBAAgB,CAAC,UAA6B,EAAE;IAC9D,MAAM,EAAE,IAAI,GAAG,YAAY,EAAE,QAAQ,GAAG,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAE1E,OAAO;QACL,KAAK,CAAC,EAAe;YACnB,OAAO,IAAI,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;gBAC9B,IAAI,CAAC;oBACH,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;gBAChB,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,GAAG,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;oBACzD,OAAO;gBACT,CAAC;gBACD,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,aAAa,CAAC,CAAC;gBACvC,qBAAqB,CAAC,GAAG,EAAE;oBACzB,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,aAAa,CAAC,CAAC;oBAC1C,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,WAAW,CAAC,CAAC;gBACvC,CAAC,CAAC,CAAC;gBACH,UAAU,CAAC,GAAG,EAAE;oBACd,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,WAAW,CAAC,CAAC;oBACxC,GAAG,EAAE,CAAC;gBACR,CAAC,EAAE,QAAQ,CAAC,CAAC;YACf,CAAC,CAAC,CAAC;QACL,CAAC;QACD,KAAK,CAAC,EAAe;YACnB,OAAO,IAAI,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;gBAC9B,IAAI,CAAC;oBACH,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;gBAChB,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,GAAG,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;oBACzD,OAAO;gBACT,CAAC;gBACD,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,aAAa,CAAC,CAAC;gBACvC,qBAAqB,CAAC,GAAG,EAAE;oBACzB,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,aAAa,CAAC,CAAC;oBAC1C,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,WAAW,CAAC,CAAC;gBACvC,CAAC,CAAC,CAAC;gBACH,UAAU,CAAC,GAAG,EAAE;oBACd,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,WAAW,CAAC,CAAC;oBACxC,GAAG,EAAE,CAAC;gBACR,CAAC,EAAE,QAAQ,CAAC,CAAC;YACf,CAAC,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"tween.d.ts","sourceRoot":"","sources":["../src/tween.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAEvD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACvB,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3B,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,IAAI,IAAI,CAAC;IACb,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,KAAK,CACnB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,OAAO,GAAE,YAAiB,GACzB,KAAK,CAyDP"}
1
+ {"version":3,"file":"tween.d.ts","sourceRoot":"","sources":["../src/tween.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAEvD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACvB,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3B,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,IAAI,IAAI,CAAC;IACb,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,KAAK,CACnB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,OAAO,GAAE,YAAiB,GACzB,KAAK,CA0DP"}
package/dist/tween.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { signal, computed, effect } from "@praxisjs/core/internal";
2
2
  import { resolveEasing } from "./easings";
3
3
  export function tween(from, to, options = {}) {
4
- const { duration = 300, easing = "easeOut", delay = 0 } = options;
4
+ const { duration: rawDuration = 300, easing = "easeOut", delay = 0 } = options;
5
+ const duration = Math.max(1, rawDuration);
5
6
  const easeFn = resolveEasing(easing);
6
7
  const _value = signal(from);
7
8
  const _target = signal(to);
package/dist/tween.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tween.js","sourceRoot":"","sources":["../src/tween.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAGnE,OAAO,EAAE,aAAa,EAAe,MAAM,WAAW,CAAC;AAiBvD,MAAM,UAAU,KAAK,CACnB,IAAY,EACZ,EAAU,EACV,UAAwB,EAAE;IAE1B,MAAM,EAAE,QAAQ,GAAG,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,OAAO,CAAC;IAClE,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAErC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC5B,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3B,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAE/B,IAAI,GAAuB,CAAC;IAC5B,IAAI,SAA6B,CAAC;IAClC,IAAI,UAAU,GAAG,IAAI,CAAC;IAEtB,SAAS,OAAO,CAAC,SAAiB;QAChC,SAAS,KAAK,SAAS,GAAG,KAAK,CAAC;QAChC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC,CAAC;QACnD,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC;QAE1C,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACjB,MAAM,CAAC,GAAG,CAAC,UAAU,GAAG,CAAC,OAAO,EAAE,GAAG,UAAU,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAE9D,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACV,GAAG,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;QACvC,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACtB,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAED,SAAS,KAAK;QACZ,IAAI,GAAG;YAAE,oBAAoB,CAAC,GAAG,CAAC,CAAC;QACnC,UAAU,GAAG,MAAM,EAAE,CAAC;QACtB,SAAS,GAAG,SAAS,CAAC;QACtB,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnB,GAAG,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC;IAED,MAAM,CAAC,GAAG,EAAE;QACV,KAAK,OAAO,EAAE,CAAC;QACf,KAAK,EAAE,CAAC;IACV,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,KAAK,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC;QAC/B,MAAM,EAAE,OAAO;QACf,OAAO,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;QACnC,QAAQ,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC;QACrC,IAAI;YACF,IAAI,GAAG;gBAAE,oBAAoB,CAAC,GAAG,CAAC,CAAC;YACnC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;QACD,KAAK;YACH,IAAI,GAAG;gBAAE,oBAAoB,CAAC,GAAG,CAAC,CAAC;YACnC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACjB,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"tween.js","sourceRoot":"","sources":["../src/tween.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAGnE,OAAO,EAAE,aAAa,EAAe,MAAM,WAAW,CAAC;AAiBvD,MAAM,UAAU,KAAK,CACnB,IAAY,EACZ,EAAU,EACV,UAAwB,EAAE;IAE1B,MAAM,EAAE,QAAQ,EAAE,WAAW,GAAG,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,OAAO,CAAC;IAC/E,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAErC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC5B,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3B,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAE/B,IAAI,GAAuB,CAAC;IAC5B,IAAI,SAA6B,CAAC;IAClC,IAAI,UAAU,GAAG,IAAI,CAAC;IAEtB,SAAS,OAAO,CAAC,SAAiB;QAChC,SAAS,KAAK,SAAS,GAAG,KAAK,CAAC;QAChC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC,CAAC;QACnD,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC;QAE1C,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACjB,MAAM,CAAC,GAAG,CAAC,UAAU,GAAG,CAAC,OAAO,EAAE,GAAG,UAAU,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAE9D,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACV,GAAG,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;QACvC,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACtB,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAED,SAAS,KAAK;QACZ,IAAI,GAAG;YAAE,oBAAoB,CAAC,GAAG,CAAC,CAAC;QACnC,UAAU,GAAG,MAAM,EAAE,CAAC;QACtB,SAAS,GAAG,SAAS,CAAC;QACtB,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnB,GAAG,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC;IAED,MAAM,CAAC,GAAG,EAAE;QACV,KAAK,OAAO,EAAE,CAAC;QACf,KAAK,EAAE,CAAC;IACV,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,KAAK,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC;QAC/B,MAAM,EAAE,OAAO;QACf,OAAO,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;QACnC,QAAQ,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC;QACrC,IAAI;YACF,IAAI,GAAG;gBAAE,oBAAoB,CAAC,GAAG,CAAC,CAAC;YACnC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;QACD,KAAK;YACH,IAAI,GAAG;gBAAE,oBAAoB,CAAC,GAAG,CAAC,CAAC;YACnC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACjB,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxisjs/motion",
3
- "version": "0.2.3",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -14,7 +14,8 @@
14
14
  "typescript": "^5.9.3"
15
15
  },
16
16
  "dependencies": {
17
- "@praxisjs/core": "0.4.2",
17
+ "@praxisjs/core": "1.1.0",
18
+ "@praxisjs/decorators": "0.6.0",
18
19
  "@praxisjs/shared": "0.2.0"
19
20
  },
20
21
  "scripts": {
@@ -0,0 +1,196 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect, vi } from "vitest";
3
+
4
+ import { Tween, Spring } from "../decorators";
5
+
6
+ function makeCtx(name: string) {
7
+ const initializers: Array<(this: unknown) => void> = [];
8
+ return {
9
+ ctx: {
10
+ name,
11
+ kind: "field" as const,
12
+ addInitializer(fn: (this: unknown) => void) { initializers.push(fn); },
13
+ } as ClassFieldDecoratorContext,
14
+ run(instance: unknown) {
15
+ initializers.forEach((fn) => { fn.call(instance); });
16
+ },
17
+ };
18
+ }
19
+
20
+ // ── @Tween ────────────────────────────────────────────────────────────────────
21
+
22
+ describe("@Tween", () => {
23
+ it("returns 0 before any value is set", () => {
24
+ vi.useFakeTimers();
25
+ const { ctx, run } = makeCtx("opacity");
26
+ Tween()(undefined, ctx);
27
+ const instance: Record<string, unknown> = {};
28
+ run(instance);
29
+ expect(instance.opacity).toBe(0);
30
+ vi.clearAllTimers();
31
+ vi.useRealTimers();
32
+ });
33
+
34
+ it("creates a numeric getter/setter on first assignment", () => {
35
+ vi.useFakeTimers();
36
+ const { ctx, run } = makeCtx("x");
37
+ Tween()(undefined, ctx);
38
+ const instance: Record<string, unknown> = {};
39
+ run(instance);
40
+ instance.x = 10;
41
+ expect(typeof instance.x).toBe("number");
42
+ vi.clearAllTimers();
43
+ vi.useRealTimers();
44
+ });
45
+
46
+ it("updating the property updates the tween target", () => {
47
+ vi.useFakeTimers();
48
+ const { ctx, run } = makeCtx("scale");
49
+ Tween()(undefined, ctx);
50
+ const instance: Record<string, unknown> = {};
51
+ run(instance);
52
+ instance.scale = 1;
53
+ instance.scale = 2;
54
+ expect(typeof instance.scale).toBe("number");
55
+ vi.clearAllTimers();
56
+ vi.useRealTimers();
57
+ });
58
+
59
+ it("accepts TweenOptions", () => {
60
+ vi.useFakeTimers();
61
+ const { ctx, run } = makeCtx("y");
62
+ Tween({ duration: 500, easing: "linear", delay: 50 })(undefined, ctx);
63
+ const instance: Record<string, unknown> = {};
64
+ run(instance);
65
+ instance.y = 100;
66
+ expect(typeof instance.y).toBe("number");
67
+ vi.clearAllTimers();
68
+ vi.useRealTimers();
69
+ });
70
+
71
+ it("each instance gets its own tween", () => {
72
+ vi.useFakeTimers();
73
+ const { ctx, run } = makeCtx("val");
74
+ Tween()(undefined, ctx);
75
+ const a: Record<string, unknown> = {};
76
+ const b: Record<string, unknown> = {};
77
+ run(a);
78
+ run(b);
79
+ a.val = 10;
80
+ b.val = 50;
81
+ expect(a.val).not.toBe(b.val);
82
+ vi.clearAllTimers();
83
+ vi.useRealTimers();
84
+ });
85
+ });
86
+
87
+ // ── @Spring ───────────────────────────────────────────────────────────────────
88
+
89
+ describe("@Spring", () => {
90
+ it("returns 0 before any value is set", () => {
91
+ vi.useFakeTimers();
92
+ const { ctx, run } = makeCtx("x");
93
+ Spring()(undefined, ctx);
94
+ const instance: Record<string, unknown> = {};
95
+ run(instance);
96
+ expect(instance.x).toBe(0);
97
+ vi.clearAllTimers();
98
+ vi.useRealTimers();
99
+ });
100
+
101
+ it("creates a numeric getter/setter on first assignment", () => {
102
+ vi.useFakeTimers();
103
+ const { ctx, run } = makeCtx("y");
104
+ Spring()(undefined, ctx);
105
+ const instance: Record<string, unknown> = {};
106
+ run(instance);
107
+ instance.y = 42;
108
+ expect(typeof instance.y).toBe("number");
109
+ vi.clearAllTimers();
110
+ vi.useRealTimers();
111
+ });
112
+
113
+ it("updating the property sets the spring target", () => {
114
+ vi.useFakeTimers();
115
+ const { ctx, run } = makeCtx("scale");
116
+ Spring()(undefined, ctx);
117
+ const instance: Record<string, unknown> = {};
118
+ run(instance);
119
+ instance.scale = 1;
120
+ instance.scale = 2;
121
+ expect(typeof instance.scale).toBe("number");
122
+ vi.clearAllTimers();
123
+ vi.useRealTimers();
124
+ });
125
+
126
+ it("accepts SpringOptions", () => {
127
+ vi.useFakeTimers();
128
+ const { ctx, run } = makeCtx("opacity");
129
+ Spring({ stiffness: 0.3, damping: 0.9, mass: 2 })(undefined, ctx);
130
+ const instance: Record<string, unknown> = {};
131
+ run(instance);
132
+ instance.opacity = 1;
133
+ expect(typeof instance.opacity).toBe("number");
134
+ vi.clearAllTimers();
135
+ vi.useRealTimers();
136
+ });
137
+
138
+ it("each instance gets its own spring", () => {
139
+ vi.useFakeTimers();
140
+ const { ctx, run } = makeCtx("val");
141
+ Spring()(undefined, ctx);
142
+ const a: Record<string, unknown> = {};
143
+ const b: Record<string, unknown> = {};
144
+ run(a);
145
+ run(b);
146
+ a.val = 10;
147
+ b.val = 50;
148
+ expect(a.val).not.toBe(b.val);
149
+ vi.clearAllTimers();
150
+ vi.useRealTimers();
151
+ });
152
+ });
153
+
154
+ // ── Edge case tests ───────────────────────────────────────────────────────────
155
+
156
+ describe("@Tween edge cases", () => {
157
+ it("setting NaN on @Tween field does not crash", () => {
158
+ vi.useFakeTimers();
159
+ const { ctx, run } = makeCtx("opacity");
160
+ Tween()(undefined, ctx);
161
+ const instance: Record<string, unknown> = {};
162
+ run(instance);
163
+ expect(() => { instance.opacity = NaN; }).not.toThrow();
164
+ vi.clearAllTimers();
165
+ vi.useRealTimers();
166
+ });
167
+
168
+ it("two @Tween fields on same class — independent animations", () => {
169
+ vi.useFakeTimers();
170
+ const { ctx: ctxX, run: runX } = makeCtx("x");
171
+ const { ctx: ctxY, run: runY } = makeCtx("y");
172
+ Tween()(undefined, ctxX);
173
+ Tween()(undefined, ctxY);
174
+ const instance: Record<string, unknown> = {};
175
+ runX(instance);
176
+ runY(instance);
177
+ instance.x = 10;
178
+ instance.y = 50;
179
+ expect(instance.x).not.toBe(instance.y);
180
+ vi.clearAllTimers();
181
+ vi.useRealTimers();
182
+ });
183
+ });
184
+
185
+ describe("@Spring edge cases", () => {
186
+ it("setting Infinity on @Spring field does not crash", () => {
187
+ vi.useFakeTimers();
188
+ const { ctx, run } = makeCtx("scale");
189
+ Spring()(undefined, ctx);
190
+ const instance: Record<string, unknown> = {};
191
+ run(instance);
192
+ expect(() => { instance.scale = Infinity; }).not.toThrow();
193
+ vi.clearAllTimers();
194
+ vi.useRealTimers();
195
+ });
196
+ });
@@ -2,82 +2,111 @@ import { describe, it, expect } from "vitest";
2
2
 
3
3
  import { easings, resolveEasing } from "../easings";
4
4
 
5
- const EPSILON = 1e-6;
5
+ describe("easings.linear", () => {
6
+ it("returns 0 at t=0", () => expect(easings.linear(0)).toBe(0));
7
+ it("returns 0.5 at t=0.5", () => expect(easings.linear(0.5)).toBe(0.5));
8
+ it("returns 1 at t=1", () => expect(easings.linear(1)).toBe(1));
9
+ });
6
10
 
7
- function nearly(a: number, b: number) {
8
- return Math.abs(a - b) < EPSILON;
9
- }
11
+ describe("easings.easeIn", () => {
12
+ it("returns 0 at t=0", () => expect(easings.easeIn(0)).toBe(0));
13
+ it("returns 0.25 at t=0.5", () => expect(easings.easeIn(0.5)).toBe(0.25));
14
+ it("returns 1 at t=1", () => expect(easings.easeIn(1)).toBe(1));
15
+ });
10
16
 
11
- describe("easings", () => {
12
- describe("boundary conditions (t=0 0, t=1 → 1)", () => {
13
- for (const [name, fn] of Object.entries(easings)) {
14
- it(`${name}(0) === 0`, () => {
15
- expect(nearly(fn(0), 0)).toBe(true);
16
- });
17
- it(`${name}(1) === 1`, () => {
18
- expect(nearly(fn(1), 1)).toBe(true);
19
- });
20
- }
17
+ describe("easings.easeOut", () => {
18
+ it("returns 0 at t=0", () => expect(easings.easeOut(0)).toBe(0));
19
+ it("returns 0.75 at t=0.5", () => expect(easings.easeOut(0.5)).toBe(0.75));
20
+ it("returns 1 at t=1", () => expect(easings.easeOut(1)).toBe(1));
21
+ });
22
+
23
+ describe("easings.easeInOut", () => {
24
+ it("returns 0 at t=0", () => expect(easings.easeInOut(0)).toBe(0));
25
+ it("returns 0.5 at t=0.5", () => expect(easings.easeInOut(0.5)).toBe(0.5));
26
+ it("returns 1 at t=1", () => expect(easings.easeInOut(1)).toBe(1));
27
+ it("uses easeIn branch for t<0.5", () => {
28
+ expect(easings.easeInOut(0.25)).toBeCloseTo(0.125);
29
+ });
30
+ it("uses easeOut branch for t>=0.5", () => {
31
+ expect(easings.easeInOut(0.75)).toBeCloseTo(0.875);
21
32
  });
33
+ });
34
+
35
+ describe("easings.easeInCubic", () => {
36
+ it("returns 0 at t=0", () => expect(easings.easeInCubic(0)).toBe(0));
37
+ it("returns 0.125 at t=0.5", () => expect(easings.easeInCubic(0.5)).toBe(0.125));
38
+ it("returns 1 at t=1", () => expect(easings.easeInCubic(1)).toBe(1));
39
+ });
22
40
 
23
- describe("linear", () => {
24
- it("returns t unchanged", () => {
25
- expect(easings.linear(0.5)).toBe(0.5);
26
- expect(easings.linear(0.25)).toBe(0.25);
27
- });
41
+ describe("easings.bounce", () => {
42
+ it("returns 0 at t=0", () => expect(easings.bounce(0)).toBe(0));
43
+ it("returns 1 at t=1", () => expect(easings.bounce(1)).toBeCloseTo(1));
44
+ it("handles first branch (t < 1/2.75)", () => {
45
+ const t = 0.2;
46
+ expect(easings.bounce(t)).toBeGreaterThan(0);
47
+ });
48
+ it("handles second branch (t < 2/2.75)", () => {
49
+ const t = 0.5;
50
+ expect(easings.bounce(t)).toBeGreaterThan(0.7);
51
+ });
52
+ it("handles third branch (t < 2.5/2.75)", () => {
53
+ const t = 0.85;
54
+ expect(easings.bounce(t)).toBeGreaterThan(0.9);
28
55
  });
56
+ it("handles fourth branch (t >= 2.5/2.75)", () => {
57
+ const t = 0.97;
58
+ expect(easings.bounce(t)).toBeGreaterThan(0.98);
59
+ });
60
+ });
29
61
 
30
- describe("easeIn", () => {
31
- it("is slower at start than end (t² curve)", () => {
32
- expect(easings.easeIn(0.25)).toBeLessThan(0.25);
33
- });
62
+ describe("easings.elastic", () => {
63
+ it("returns 0 at t=0", () => expect(easings.elastic(0)).toBe(0));
64
+ it("returns 1 at t=1", () => expect(easings.elastic(1)).toBe(1));
65
+ it("returns a negative value mid-range (overshoot)", () => {
66
+ expect(easings.elastic(0.5)).toBeLessThan(0);
34
67
  });
68
+ });
35
69
 
36
- describe("easeOut", () => {
37
- it("is faster at start than end", () => {
38
- expect(easings.easeOut(0.75)).toBeGreaterThan(0.75);
39
- });
70
+ describe("resolveEasing", () => {
71
+ it("resolves a named easing", () => {
72
+ const fn = resolveEasing("linear");
73
+ expect(fn(0.5)).toBe(0.5);
40
74
  });
41
75
 
42
- describe("easeInOut", () => {
43
- it("is symmetric around 0.5", () => {
44
- const v = easings.easeInOut(0.5);
45
- expect(nearly(v, 0.5)).toBe(true);
46
- });
76
+ it("returns a custom function as-is", () => {
77
+ const custom = (t: number) => t * 2;
78
+ const fn = resolveEasing(custom);
79
+ expect(fn).toBe(custom);
80
+ expect(fn(0.5)).toBe(1);
47
81
  });
48
82
 
49
- describe("easeInCubic", () => {
50
- it("is ", () => {
51
- expect(nearly(easings.easeInCubic(0.5), 0.125)).toBe(true);
52
- });
83
+ it("resolves all named easings", () => {
84
+ const names = ["linear", "easeIn", "easeOut", "easeInOut", "easeInCubic", "bounce", "elastic"] as const;
85
+ for (const name of names) {
86
+ const fn = resolveEasing(name);
87
+ expect(typeof fn).toBe("function");
88
+ }
53
89
  });
54
90
 
55
- describe("bounce", () => {
56
- it("stays within [0, 1] for t in [0, 1]", () => {
57
- for (let t = 0; t <= 1; t += 0.1) {
58
- const v = easings.bounce(t);
59
- expect(v).toBeGreaterThanOrEqual(0);
60
- expect(v).toBeLessThanOrEqual(1 + EPSILON);
61
- }
62
- });
91
+ it('resolveEasing("notARealEasing") throws a descriptive error', () => {
92
+ expect(() => resolveEasing("notARealEasing" as never)).toThrow(/notARealEasing/);
63
93
  });
64
94
 
65
- describe("elastic", () => {
66
- it("returns 0 at t=0 and 1 at t=1", () => {
67
- expect(easings.elastic(0)).toBe(0);
68
- expect(easings.elastic(1)).toBe(1);
69
- });
95
+ it("all easings with t < 0 — do not crash", () => {
96
+ const names = ["linear", "easeIn", "easeOut", "easeInOut", "easeInCubic", "bounce", "elastic"] as const;
97
+ for (const name of names) {
98
+ expect(() => easings[name](-0.1)).not.toThrow();
99
+ }
70
100
  });
71
- });
72
101
 
73
- describe("resolveEasing", () => {
74
- it("resolves a string key to the easing function", () => {
75
- const fn = resolveEasing("linear");
76
- expect(fn).toBe(easings.linear);
102
+ it("all easings with t > 1 — do not crash", () => {
103
+ const names = ["linear", "easeIn", "easeOut", "easeInOut", "easeInCubic", "bounce", "elastic"] as const;
104
+ for (const name of names) {
105
+ expect(() => easings[name](1.1)).not.toThrow();
106
+ }
77
107
  });
78
108
 
79
- it("passes through a custom function", () => {
80
- const custom = (t: number) => t * t * t;
81
- expect(resolveEasing(custom)).toBe(custom);
109
+ it("easeInOut(0.5) returns exactly 0.5 (smooth boundary)", () => {
110
+ expect(easings.easeInOut(0.5)).toBe(0.5);
82
111
  });
83
112
  });