@vysmo/easings 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.
- package/LICENSE +21 -0
- package/README.md +368 -0
- package/dist/builders/anticipate.d.ts +20 -0
- package/dist/builders/anticipate.d.ts.map +1 -0
- package/dist/builders/anticipate.js +52 -0
- package/dist/builders/anticipate.js.map +1 -0
- package/dist/builders/bezier.d.ts +7 -0
- package/dist/builders/bezier.d.ts.map +1 -0
- package/dist/builders/bezier.js +69 -0
- package/dist/builders/bezier.js.map +1 -0
- package/dist/builders/breathe.d.ts +11 -0
- package/dist/builders/breathe.d.ts.map +1 -0
- package/dist/builders/breathe.js +14 -0
- package/dist/builders/breathe.js.map +1 -0
- package/dist/builders/custom.d.ts +13 -0
- package/dist/builders/custom.d.ts.map +1 -0
- package/dist/builders/custom.js +40 -0
- package/dist/builders/custom.js.map +1 -0
- package/dist/builders/expoScale.d.ts +13 -0
- package/dist/builders/expoScale.d.ts.map +1 -0
- package/dist/builders/expoScale.js +27 -0
- package/dist/builders/expoScale.js.map +1 -0
- package/dist/builders/gravity.d.ts +12 -0
- package/dist/builders/gravity.d.ts.map +1 -0
- package/dist/builders/gravity.js +11 -0
- package/dist/builders/gravity.js.map +1 -0
- package/dist/builders/index.d.ts +12 -0
- package/dist/builders/index.d.ts.map +1 -0
- package/dist/builders/index.js +12 -0
- package/dist/builders/index.js.map +1 -0
- package/dist/builders/rough.d.ts +17 -0
- package/dist/builders/rough.d.ts.map +1 -0
- package/dist/builders/rough.js +62 -0
- package/dist/builders/rough.js.map +1 -0
- package/dist/builders/slow.d.ts +16 -0
- package/dist/builders/slow.d.ts.map +1 -0
- package/dist/builders/slow.js +59 -0
- package/dist/builders/slow.js.map +1 -0
- package/dist/builders/spring-presets.d.ts +48 -0
- package/dist/builders/spring-presets.d.ts.map +1 -0
- package/dist/builders/spring-presets.js +19 -0
- package/dist/builders/spring-presets.js.map +1 -0
- package/dist/builders/spring.d.ts +12 -0
- package/dist/builders/spring.d.ts.map +1 -0
- package/dist/builders/spring.js +46 -0
- package/dist/builders/spring.js.map +1 -0
- package/dist/builders/wiggle.d.ts +9 -0
- package/dist/builders/wiggle.d.ts.map +1 -0
- package/dist/builders/wiggle.js +23 -0
- package/dist/builders/wiggle.js.map +1 -0
- package/dist/css.d.ts +40 -0
- package/dist/css.d.ts.map +1 -0
- package/dist/css.js +71 -0
- package/dist/css.js.map +1 -0
- package/dist/define.d.ts +12 -0
- package/dist/define.d.ts.map +1 -0
- package/dist/define.js +31 -0
- package/dist/define.js.map +1 -0
- package/dist/easings/back.d.ts +8 -0
- package/dist/easings/back.d.ts.map +1 -0
- package/dist/easings/back.js +20 -0
- package/dist/easings/back.js.map +1 -0
- package/dist/easings/bounce.d.ts +4 -0
- package/dist/easings/bounce.d.ts.map +1 -0
- package/dist/easings/bounce.js +21 -0
- package/dist/easings/bounce.js.map +1 -0
- package/dist/easings/circ.d.ts +4 -0
- package/dist/easings/circ.d.ts.map +1 -0
- package/dist/easings/circ.js +7 -0
- package/dist/easings/circ.js.map +1 -0
- package/dist/easings/elastic.d.ts +9 -0
- package/dist/easings/elastic.d.ts.map +1 -0
- package/dist/easings/elastic.js +33 -0
- package/dist/easings/elastic.js.map +1 -0
- package/dist/easings/expo.d.ts +4 -0
- package/dist/easings/expo.d.ts.map +1 -0
- package/dist/easings/expo.js +11 -0
- package/dist/easings/expo.js.map +1 -0
- package/dist/easings/index.d.ts +11 -0
- package/dist/easings/index.d.ts.map +1 -0
- package/dist/easings/index.js +11 -0
- package/dist/easings/index.js.map +1 -0
- package/dist/easings/linear.d.ts +3 -0
- package/dist/easings/linear.d.ts.map +1 -0
- package/dist/easings/linear.js +4 -0
- package/dist/easings/linear.js.map +1 -0
- package/dist/easings/power.d.ts +25 -0
- package/dist/easings/power.d.ts.map +1 -0
- package/dist/easings/power.js +29 -0
- package/dist/easings/power.js.map +1 -0
- package/dist/easings/sine.d.ts +4 -0
- package/dist/easings/sine.d.ts.map +1 -0
- package/dist/easings/sine.js +6 -0
- package/dist/easings/sine.js.map +1 -0
- package/dist/easings/smooth.d.ts +4 -0
- package/dist/easings/smooth.d.ts.map +1 -0
- package/dist/easings/smooth.js +21 -0
- package/dist/easings/smooth.js.map +1 -0
- package/dist/easings/steps.d.ts +8 -0
- package/dist/easings/steps.d.ts.map +1 -0
- package/dist/easings/steps.js +16 -0
- package/dist/easings/steps.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/modifiers/blend.d.ts +13 -0
- package/dist/modifiers/blend.d.ts.map +1 -0
- package/dist/modifiers/blend.js +21 -0
- package/dist/modifiers/blend.js.map +1 -0
- package/dist/modifiers/chain.d.ts +18 -0
- package/dist/modifiers/chain.d.ts.map +1 -0
- package/dist/modifiers/chain.js +40 -0
- package/dist/modifiers/chain.js.map +1 -0
- package/dist/modifiers/index.d.ts +7 -0
- package/dist/modifiers/index.d.ts.map +1 -0
- package/dist/modifiers/index.js +7 -0
- package/dist/modifiers/index.js.map +1 -0
- package/dist/modifiers/mirror.d.ts +8 -0
- package/dist/modifiers/mirror.d.ts.map +1 -0
- package/dist/modifiers/mirror.js +14 -0
- package/dist/modifiers/mirror.js.map +1 -0
- package/dist/modifiers/reverse.d.ts +4 -0
- package/dist/modifiers/reverse.d.ts.map +1 -0
- package/dist/modifiers/reverse.js +6 -0
- package/dist/modifiers/reverse.js.map +1 -0
- package/dist/modifiers/slice.d.ts +11 -0
- package/dist/modifiers/slice.d.ts.map +1 -0
- package/dist/modifiers/slice.js +28 -0
- package/dist/modifiers/slice.js.map +1 -0
- package/dist/modifiers/yoyo.d.ts +8 -0
- package/dist/modifiers/yoyo.d.ts.map +1 -0
- package/dist/modifiers/yoyo.js +10 -0
- package/dist/modifiers/yoyo.js.map +1 -0
- package/dist/parse.d.ts +14 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +108 -0
- package/dist/parse.js.map +1 -0
- package/dist/reduced-motion.d.ts +17 -0
- package/dist/reduced-motion.d.ts.map +1 -0
- package/dist/reduced-motion.js +24 -0
- package/dist/reduced-motion.js.map +1 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +63 -0
- package/src/__tests__/anticipate-variants.test.ts +58 -0
- package/src/__tests__/builders.test.ts +349 -0
- package/src/__tests__/contract.test.ts +47 -0
- package/src/__tests__/css.test.ts +93 -0
- package/src/__tests__/easings.bench.ts +66 -0
- package/src/__tests__/endpoint-correctness.test.ts +170 -0
- package/src/__tests__/modifiers.test.ts +134 -0
- package/src/__tests__/parametric.test.ts +148 -0
- package/src/__tests__/parse.test.ts +71 -0
- package/src/__tests__/property.test.ts +110 -0
- package/src/__tests__/reduced-motion.test.ts +66 -0
- package/src/__tests__/slice.test.ts +38 -0
- package/src/__tests__/spring-presets.test.ts +48 -0
- package/src/__tests__/ssr.test.ts +62 -0
- package/src/__tests__/types-check.ts +104 -0
- package/src/builders/anticipate.ts +66 -0
- package/src/builders/bezier.ts +71 -0
- package/src/builders/breathe.ts +31 -0
- package/src/builders/custom.ts +43 -0
- package/src/builders/expoScale.ts +30 -0
- package/src/builders/gravity.ts +27 -0
- package/src/builders/index.ts +24 -0
- package/src/builders/rough.ts +83 -0
- package/src/builders/slow.ts +72 -0
- package/src/builders/spring-presets.ts +20 -0
- package/src/builders/spring.ts +61 -0
- package/src/builders/wiggle.ts +40 -0
- package/src/css.ts +79 -0
- package/src/define.ts +49 -0
- package/src/easings/back.ts +24 -0
- package/src/easings/bounce.ts +23 -0
- package/src/easings/circ.ts +9 -0
- package/src/easings/elastic.ts +52 -0
- package/src/easings/expo.ts +9 -0
- package/src/easings/index.ts +35 -0
- package/src/easings/linear.ts +4 -0
- package/src/easings/power.ts +38 -0
- package/src/easings/sine.ts +7 -0
- package/src/easings/smooth.ts +25 -0
- package/src/easings/steps.ts +25 -0
- package/src/index.ts +9 -0
- package/src/modifiers/blend.ts +24 -0
- package/src/modifiers/chain.ts +63 -0
- package/src/modifiers/index.ts +6 -0
- package/src/modifiers/mirror.ts +14 -0
- package/src/modifiers/reverse.ts +7 -0
- package/src/modifiers/slice.ts +29 -0
- package/src/modifiers/yoyo.ts +15 -0
- package/src/parse.ts +167 -0
- package/src/reduced-motion.ts +26 -0
- package/src/types.ts +8 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { defineEasing } from "../define.js";
|
|
2
|
+
import type { EasingFn } from "../types.js";
|
|
3
|
+
|
|
4
|
+
export type ChainSegment = {
|
|
5
|
+
/** Ease for this segment. Input t is [0, 1] within the segment. */
|
|
6
|
+
ease: EasingFn;
|
|
7
|
+
/** Fraction of total duration this segment occupies. Must be > 0. */
|
|
8
|
+
duration: number;
|
|
9
|
+
/** Starting output value for the segment. Defaults to previous segment's end or 0. */
|
|
10
|
+
from?: number;
|
|
11
|
+
/** Ending output value. Defaults to the next segment's `from` or 1. */
|
|
12
|
+
to?: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Chain multiple eases sequentially across [0, 1]. Each segment has its
|
|
17
|
+
* own duration and output range. Useful for multi-phase animations
|
|
18
|
+
* (e.g., "power2.in for 30%, then spring for 70%").
|
|
19
|
+
*/
|
|
20
|
+
export function chain(segments: ReadonlyArray<ChainSegment>): EasingFn {
|
|
21
|
+
if (segments.length === 0) {
|
|
22
|
+
throw new RangeError("chain: at least one segment required");
|
|
23
|
+
}
|
|
24
|
+
const totalDuration = segments.reduce((sum, s) => sum + s.duration, 0);
|
|
25
|
+
if (totalDuration <= 0) {
|
|
26
|
+
throw new RangeError("chain: total duration must be > 0");
|
|
27
|
+
}
|
|
28
|
+
type ResolvedSegment = {
|
|
29
|
+
ease: EasingFn;
|
|
30
|
+
start: number;
|
|
31
|
+
end: number;
|
|
32
|
+
from: number;
|
|
33
|
+
to: number;
|
|
34
|
+
};
|
|
35
|
+
const resolved: ResolvedSegment[] = [];
|
|
36
|
+
let cursor = 0;
|
|
37
|
+
for (let i = 0; i < segments.length; i++) {
|
|
38
|
+
const seg = segments[i]!;
|
|
39
|
+
const start = cursor;
|
|
40
|
+
const end = cursor + seg.duration / totalDuration;
|
|
41
|
+
const prevEnd = i > 0 ? resolved[i - 1]!.to : 0;
|
|
42
|
+
const nextFrom = segments[i + 1]?.from;
|
|
43
|
+
const from = seg.from ?? prevEnd;
|
|
44
|
+
const to = seg.to ?? nextFrom ?? (i === segments.length - 1 ? 1 : from);
|
|
45
|
+
resolved.push({ ease: seg.ease, start, end, from, to });
|
|
46
|
+
cursor = end;
|
|
47
|
+
}
|
|
48
|
+
const name = `chain(${segments.map((s) => s.ease.easingName).join(", ")})`;
|
|
49
|
+
return defineEasing(
|
|
50
|
+
name,
|
|
51
|
+
(t) => {
|
|
52
|
+
for (const seg of resolved) {
|
|
53
|
+
if (t <= seg.end) {
|
|
54
|
+
const span = seg.end - seg.start;
|
|
55
|
+
const localT = span === 0 ? 0 : (t - seg.start) / span;
|
|
56
|
+
return seg.from + (seg.to - seg.from) * seg.ease(localT);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return resolved[resolved.length - 1]!.to;
|
|
60
|
+
},
|
|
61
|
+
{ exactEndpoints: false },
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineEasing } from "../define.js";
|
|
2
|
+
import type { EasingFn } from "../types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Mirror an ease across the midpoint. The first half plays the ease forward,
|
|
6
|
+
* scaled into [0, 0.5]; the second half plays it reversed, scaled into
|
|
7
|
+
* [0.5, 1]. Turns any ease into its inOut variant.
|
|
8
|
+
*/
|
|
9
|
+
export function mirror(ease: EasingFn): EasingFn {
|
|
10
|
+
return defineEasing(`mirror(${ease.easingName})`, (t) => {
|
|
11
|
+
if (t < 0.5) return ease(t * 2) / 2;
|
|
12
|
+
return 1 - ease((1 - t) * 2) / 2;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { defineEasing } from "../define.js";
|
|
2
|
+
import type { EasingFn } from "../types.js";
|
|
3
|
+
|
|
4
|
+
/** Play the ease backward: f(t) → 1 - f(1 - t). Useful to turn any "in" into an "out". */
|
|
5
|
+
export function reverse(ease: EasingFn): EasingFn {
|
|
6
|
+
return defineEasing(`reverse(${ease.easingName})`, (t) => 1 - ease(1 - t));
|
|
7
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { defineEasing } from "../define.js";
|
|
2
|
+
import type { EasingFn } from "../types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extract a sub-section of an easing curve, stretched to fill [0, 1].
|
|
6
|
+
* Use when you want to play only part of a curve (e.g., just the overshoot
|
|
7
|
+
* portion of `back.out`, or the settle portion of `elastic.out`).
|
|
8
|
+
*
|
|
9
|
+
* The sliced portion is normalised: y(0) = ease(start), y(1) = ease(end),
|
|
10
|
+
* then remapped so the output hits 0 and 1 at the new endpoints.
|
|
11
|
+
*/
|
|
12
|
+
export function slice(ease: EasingFn, start: number, end: number): EasingFn {
|
|
13
|
+
if (!(start >= 0 && start < end && end <= 1)) {
|
|
14
|
+
throw new RangeError(
|
|
15
|
+
`slice: require 0 <= start < end <= 1; got start=${start}, end=${end}`,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
if (start === 0 && end === 1) return ease;
|
|
19
|
+
const span = end - start;
|
|
20
|
+
const baseStart = ease(start);
|
|
21
|
+
const baseEnd = ease(end);
|
|
22
|
+
const ySpan = baseEnd - baseStart;
|
|
23
|
+
return defineEasing(`slice(${ease.easingName}, ${start}, ${end})`, (t) => {
|
|
24
|
+
const u = start + span * t;
|
|
25
|
+
const v = ease(u);
|
|
26
|
+
if (ySpan === 0) return 0;
|
|
27
|
+
return (v - baseStart) / ySpan;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineEasing } from "../define.js";
|
|
2
|
+
import type { EasingFn } from "../types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Round-trip an ease: 0 → 1 → 0 over t in [0, 1]. First half runs the ease
|
|
6
|
+
* forward, second half runs it reversed. Unlike `mirror` which produces an
|
|
7
|
+
* inOut, `yoyo` returns to the start.
|
|
8
|
+
*/
|
|
9
|
+
export function yoyo(ease: EasingFn): EasingFn {
|
|
10
|
+
return defineEasing(
|
|
11
|
+
`yoyo(${ease.easingName})`,
|
|
12
|
+
(t) => (t < 0.5 ? ease(t * 2) : ease((1 - t) * 2)),
|
|
13
|
+
{ exactEndpoints: false },
|
|
14
|
+
);
|
|
15
|
+
}
|
package/src/parse.ts
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import {
|
|
2
|
+
backIn,
|
|
3
|
+
backInOut,
|
|
4
|
+
backOut,
|
|
5
|
+
bounceIn,
|
|
6
|
+
bounceInOut,
|
|
7
|
+
bounceOut,
|
|
8
|
+
circIn,
|
|
9
|
+
circInOut,
|
|
10
|
+
circOut,
|
|
11
|
+
cubicIn,
|
|
12
|
+
cubicInOut,
|
|
13
|
+
cubicOut,
|
|
14
|
+
elasticIn,
|
|
15
|
+
elasticInOut,
|
|
16
|
+
elasticOut,
|
|
17
|
+
expoIn,
|
|
18
|
+
expoInOut,
|
|
19
|
+
expoOut,
|
|
20
|
+
linear,
|
|
21
|
+
none,
|
|
22
|
+
power1In,
|
|
23
|
+
power1InOut,
|
|
24
|
+
power1Out,
|
|
25
|
+
power2In,
|
|
26
|
+
power2InOut,
|
|
27
|
+
power2Out,
|
|
28
|
+
power3In,
|
|
29
|
+
power3InOut,
|
|
30
|
+
power3Out,
|
|
31
|
+
power4In,
|
|
32
|
+
power4InOut,
|
|
33
|
+
power4Out,
|
|
34
|
+
quadIn,
|
|
35
|
+
quadInOut,
|
|
36
|
+
quadOut,
|
|
37
|
+
quartIn,
|
|
38
|
+
quartInOut,
|
|
39
|
+
quartOut,
|
|
40
|
+
quintIn,
|
|
41
|
+
quintInOut,
|
|
42
|
+
quintOut,
|
|
43
|
+
sineIn,
|
|
44
|
+
sineInOut,
|
|
45
|
+
sineOut,
|
|
46
|
+
steps,
|
|
47
|
+
} from "./easings/index.js";
|
|
48
|
+
import {
|
|
49
|
+
anticipate,
|
|
50
|
+
anticipateIn,
|
|
51
|
+
anticipateInOut,
|
|
52
|
+
anticipateOut,
|
|
53
|
+
bezier,
|
|
54
|
+
spring,
|
|
55
|
+
wiggle,
|
|
56
|
+
} from "./builders/index.js";
|
|
57
|
+
import type { EasingFn, ParametricEasing } from "./types.js";
|
|
58
|
+
|
|
59
|
+
type Buildable = EasingFn | ParametricEasing<Record<string, number | string>>;
|
|
60
|
+
|
|
61
|
+
const REGISTRY: Record<string, Buildable> = {
|
|
62
|
+
linear,
|
|
63
|
+
none,
|
|
64
|
+
"power1.in": power1In,
|
|
65
|
+
"power1.out": power1Out,
|
|
66
|
+
"power1.inOut": power1InOut,
|
|
67
|
+
"power2.in": power2In,
|
|
68
|
+
"power2.out": power2Out,
|
|
69
|
+
"power2.inOut": power2InOut,
|
|
70
|
+
"power3.in": power3In,
|
|
71
|
+
"power3.out": power3Out,
|
|
72
|
+
"power3.inOut": power3InOut,
|
|
73
|
+
"power4.in": power4In,
|
|
74
|
+
"power4.out": power4Out,
|
|
75
|
+
"power4.inOut": power4InOut,
|
|
76
|
+
"quad.in": quadIn,
|
|
77
|
+
"quad.out": quadOut,
|
|
78
|
+
"quad.inOut": quadInOut,
|
|
79
|
+
"cubic.in": cubicIn,
|
|
80
|
+
"cubic.out": cubicOut,
|
|
81
|
+
"cubic.inOut": cubicInOut,
|
|
82
|
+
"quart.in": quartIn,
|
|
83
|
+
"quart.out": quartOut,
|
|
84
|
+
"quart.inOut": quartInOut,
|
|
85
|
+
"quint.in": quintIn,
|
|
86
|
+
"quint.out": quintOut,
|
|
87
|
+
"quint.inOut": quintInOut,
|
|
88
|
+
"sine.in": sineIn,
|
|
89
|
+
"sine.out": sineOut,
|
|
90
|
+
"sine.inOut": sineInOut,
|
|
91
|
+
"circ.in": circIn,
|
|
92
|
+
"circ.out": circOut,
|
|
93
|
+
"circ.inOut": circInOut,
|
|
94
|
+
"expo.in": expoIn,
|
|
95
|
+
"expo.out": expoOut,
|
|
96
|
+
"expo.inOut": expoInOut,
|
|
97
|
+
"back.in": backIn as Buildable,
|
|
98
|
+
"back.out": backOut as Buildable,
|
|
99
|
+
"back.inOut": backInOut as Buildable,
|
|
100
|
+
"elastic.in": elasticIn as Buildable,
|
|
101
|
+
"elastic.out": elasticOut as Buildable,
|
|
102
|
+
"elastic.inOut": elasticInOut as Buildable,
|
|
103
|
+
"bounce.in": bounceIn,
|
|
104
|
+
"bounce.out": bounceOut,
|
|
105
|
+
"bounce.inOut": bounceInOut,
|
|
106
|
+
steps: steps as Buildable,
|
|
107
|
+
spring: spring as Buildable,
|
|
108
|
+
wiggle: wiggle as Buildable,
|
|
109
|
+
anticipate: anticipate as Buildable,
|
|
110
|
+
"anticipate.in": anticipateIn as Buildable,
|
|
111
|
+
"anticipate.out": anticipateOut as Buildable,
|
|
112
|
+
"anticipate.inOut": anticipateInOut as Buildable,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const PARSE_RE = /^([a-zA-Z][\w]*(?:\.[a-zA-Z][\w]*)?)(?:\((.*)\))?$/;
|
|
116
|
+
const CUBIC_BEZIER_RE = /^cubic-bezier\(\s*([^,]+),\s*([^,]+),\s*([^,]+),\s*([^)]+)\s*\)$/;
|
|
117
|
+
|
|
118
|
+
function isParametric(x: unknown): x is ParametricEasing<Record<string, number | string>> {
|
|
119
|
+
return (
|
|
120
|
+
typeof x === "function" &&
|
|
121
|
+
"defaults" in (x as object) &&
|
|
122
|
+
"with" in (x as object)
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function coerceArg(raw: string): number | string {
|
|
127
|
+
const trimmed = raw.trim().replace(/^["']|["']$/g, "");
|
|
128
|
+
const n = Number(trimmed);
|
|
129
|
+
return Number.isFinite(n) ? n : trimmed;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Parse a GSAP-style string reference into an EasingFn. Examples:
|
|
134
|
+
*
|
|
135
|
+
* - `"linear"`, `"power2.out"`, `"sine.inOut"` — core + variant
|
|
136
|
+
* - `"back.out(2)"` — parametric with positional arg
|
|
137
|
+
* - `"elastic.out(1.2, 0.4)"` — multiple positional args (amplitude, period)
|
|
138
|
+
* - `"steps(5, start)"` — mix of numeric and string args
|
|
139
|
+
* - `"cubic-bezier(0.42, 0, 0.58, 1)"` — CSS-style, exact bezier
|
|
140
|
+
*
|
|
141
|
+
* Throws a RangeError for unknown names or malformed input.
|
|
142
|
+
*/
|
|
143
|
+
export function parseEasing(spec: string): EasingFn {
|
|
144
|
+
const input = spec.trim();
|
|
145
|
+
const cb = CUBIC_BEZIER_RE.exec(input);
|
|
146
|
+
if (cb) {
|
|
147
|
+
const [, a, b, c, d] = cb;
|
|
148
|
+
return bezier(Number(a), Number(b), Number(c), Number(d));
|
|
149
|
+
}
|
|
150
|
+
const match = PARSE_RE.exec(input);
|
|
151
|
+
if (!match) throw new RangeError(`parseEasing: cannot parse '${spec}'`);
|
|
152
|
+
const [, name, argsStr] = match;
|
|
153
|
+
const entry = REGISTRY[name!];
|
|
154
|
+
if (!entry) throw new RangeError(`parseEasing: unknown easing '${name}'`);
|
|
155
|
+
const args = argsStr !== undefined && argsStr.length > 0 ? argsStr.split(",").map(coerceArg) : [];
|
|
156
|
+
if (args.length === 0) return entry as EasingFn;
|
|
157
|
+
if (!isParametric(entry)) {
|
|
158
|
+
throw new RangeError(`parseEasing: '${name}' is not parametric but received arguments`);
|
|
159
|
+
}
|
|
160
|
+
const keys = Object.keys(entry.defaults);
|
|
161
|
+
const params: Record<string, number | string> = {};
|
|
162
|
+
args.forEach((value, i) => {
|
|
163
|
+
const key = keys[i];
|
|
164
|
+
if (key !== undefined) params[key] = value;
|
|
165
|
+
});
|
|
166
|
+
return entry.with(params);
|
|
167
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { linear } from "./easings/linear.js";
|
|
2
|
+
import type { EasingFn } from "./types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Check if the user has requested reduced motion via OS-level setting.
|
|
6
|
+
* Returns false when no DOM is available (SSR / Node).
|
|
7
|
+
*/
|
|
8
|
+
export function prefersReducedMotion(): boolean {
|
|
9
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Wrap an easing to respect the user's `prefers-reduced-motion` setting.
|
|
17
|
+
* When reduced motion is requested, returns `fallback` (default: linear);
|
|
18
|
+
* otherwise returns the original ease.
|
|
19
|
+
*
|
|
20
|
+
* Evaluated at call time — not reactive to preference changes during the
|
|
21
|
+
* lifetime of a single animation. For long-running apps, re-evaluate when
|
|
22
|
+
* starting new animations.
|
|
23
|
+
*/
|
|
24
|
+
export function respectReducedMotion(ease: EasingFn, fallback: EasingFn = linear): EasingFn {
|
|
25
|
+
return prefersReducedMotion() ? fallback : ease;
|
|
26
|
+
}
|