framer-motion 7.8.0 → 7.9.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/README.md +6 -5
- package/dist/cjs/index.js +2030 -1920
- package/dist/es/animation/animate.mjs +2 -2
- package/dist/es/animation/create-instant-animation.mjs +12 -0
- package/dist/es/animation/{animation-controls.mjs → hooks/animation-controls.mjs} +2 -2
- package/dist/es/animation/{use-animated-state.mjs → hooks/use-animated-state.mjs} +6 -6
- package/dist/es/animation/{use-animation.mjs → hooks/use-animation.mjs} +1 -1
- package/dist/es/animation/index.mjs +124 -0
- package/dist/es/animation/legacy-popmotion/decay.mjs +11 -4
- package/dist/es/animation/legacy-popmotion/index.mjs +22 -11
- package/dist/es/animation/legacy-popmotion/inertia.mjs +14 -8
- package/dist/es/animation/legacy-popmotion/keyframes.mjs +21 -13
- package/dist/es/animation/legacy-popmotion/spring.mjs +13 -11
- package/dist/es/animation/utils/default-transitions.mjs +9 -14
- package/dist/es/animation/utils/keyframes.mjs +41 -0
- package/dist/es/animation/utils/transitions.mjs +1 -166
- package/dist/es/animation/waapi/create-accelerated-animation.mjs +82 -0
- package/dist/es/animation/waapi/index.mjs +4 -6
- package/dist/es/gestures/drag/VisualElementDragControls.mjs +2 -2
- package/dist/es/index.mjs +3 -3
- package/dist/es/render/VisualElement.mjs +1 -1
- package/dist/es/render/utils/animation.mjs +2 -2
- package/dist/es/render/utils/motion-values.mjs +2 -2
- package/dist/es/render/utils/setters.mjs +1 -1
- package/dist/es/value/index.mjs +11 -5
- package/dist/es/value/use-spring.mjs +1 -2
- package/dist/framer-motion.dev.js +2051 -1941
- package/dist/framer-motion.js +1 -1
- package/dist/index.d.ts +409 -348
- package/dist/projection.dev.js +1672 -1535
- package/dist/size-rollup-dom-animation-assets.js +1 -1
- package/dist/size-rollup-dom-animation.js +1 -1
- package/dist/size-rollup-dom-max-assets.js +1 -1
- package/dist/size-rollup-dom-max.js +1 -1
- package/dist/size-rollup-motion.js +1 -1
- package/dist/size-webpack-dom-animation.js +1 -1
- package/dist/size-webpack-dom-max.js +1 -1
- package/dist/three-entry.d.ts +287 -281
- package/package.json +8 -8
package/dist/cjs/index.js
CHANGED
|
@@ -1869,2073 +1869,2214 @@ function shallowCompare(next, prev) {
|
|
|
1869
1869
|
}
|
|
1870
1870
|
|
|
1871
1871
|
/**
|
|
1872
|
-
*
|
|
1873
|
-
*
|
|
1874
|
-
* @param seconds - Time in seconds.
|
|
1875
|
-
* @return milliseconds - Converted time in milliseconds.
|
|
1876
|
-
*/
|
|
1877
|
-
const secondsToMilliseconds = (seconds) => seconds * 1000;
|
|
1878
|
-
|
|
1879
|
-
const noop = (any) => any;
|
|
1880
|
-
|
|
1881
|
-
/*
|
|
1882
|
-
Bezier function generator
|
|
1883
|
-
This has been modified from Gaëtan Renaudeau's BezierEasing
|
|
1884
|
-
https://github.com/gre/bezier-easing/blob/master/src/index.js
|
|
1885
|
-
https://github.com/gre/bezier-easing/blob/master/LICENSE
|
|
1886
|
-
|
|
1887
|
-
I've removed the newtonRaphsonIterate algo because in benchmarking it
|
|
1888
|
-
wasn't noticiably faster than binarySubdivision, indeed removing it
|
|
1889
|
-
usually improved times, depending on the curve.
|
|
1890
|
-
I also removed the lookup table, as for the added bundle size and loop we're
|
|
1891
|
-
only cutting ~4 or so subdivision iterations. I bumped the max iterations up
|
|
1892
|
-
to 12 to compensate and this still tended to be faster for no perceivable
|
|
1893
|
-
loss in accuracy.
|
|
1894
|
-
Usage
|
|
1895
|
-
const easeOut = cubicBezier(.17,.67,.83,.67);
|
|
1896
|
-
const x = easeOut(0.5); // returns 0.627...
|
|
1897
|
-
*/
|
|
1898
|
-
// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
|
|
1899
|
-
const calcBezier = (t, a1, a2) => (((1.0 - 3.0 * a2 + 3.0 * a1) * t + (3.0 * a2 - 6.0 * a1)) * t + 3.0 * a1) *
|
|
1900
|
-
t;
|
|
1901
|
-
const subdivisionPrecision = 0.0000001;
|
|
1902
|
-
const subdivisionMaxIterations = 12;
|
|
1903
|
-
function binarySubdivide(x, lowerBound, upperBound, mX1, mX2) {
|
|
1904
|
-
let currentX;
|
|
1905
|
-
let currentT;
|
|
1906
|
-
let i = 0;
|
|
1907
|
-
do {
|
|
1908
|
-
currentT = lowerBound + (upperBound - lowerBound) / 2.0;
|
|
1909
|
-
currentX = calcBezier(currentT, mX1, mX2) - x;
|
|
1910
|
-
if (currentX > 0.0) {
|
|
1911
|
-
upperBound = currentT;
|
|
1912
|
-
}
|
|
1913
|
-
else {
|
|
1914
|
-
lowerBound = currentT;
|
|
1915
|
-
}
|
|
1916
|
-
} while (Math.abs(currentX) > subdivisionPrecision &&
|
|
1917
|
-
++i < subdivisionMaxIterations);
|
|
1918
|
-
return currentT;
|
|
1919
|
-
}
|
|
1920
|
-
function cubicBezier(mX1, mY1, mX2, mY2) {
|
|
1921
|
-
// If this is a linear gradient, return linear easing
|
|
1922
|
-
if (mX1 === mY1 && mX2 === mY2)
|
|
1923
|
-
return noop;
|
|
1924
|
-
const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2);
|
|
1925
|
-
// If animation is at start/end, return t without easing
|
|
1926
|
-
return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
// Accepts an easing function and returns a new one that outputs mirrored values for
|
|
1930
|
-
// the second half of the animation. Turns easeIn into easeInOut.
|
|
1931
|
-
const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
|
|
1932
|
-
|
|
1933
|
-
// Accepts an easing function and returns a new one that outputs reversed values.
|
|
1934
|
-
// Turns easeIn into easeOut.
|
|
1935
|
-
const reverseEasing = (easing) => (p) => 1 - easing(1 - p);
|
|
1936
|
-
|
|
1937
|
-
const easeIn = (p) => p * p;
|
|
1938
|
-
const easeOut = reverseEasing(easeIn);
|
|
1939
|
-
const easeInOut = mirrorEasing(easeIn);
|
|
1940
|
-
|
|
1941
|
-
const circIn = (p) => 1 - Math.sin(Math.acos(p));
|
|
1942
|
-
const circOut = reverseEasing(circIn);
|
|
1943
|
-
const circInOut = mirrorEasing(circOut);
|
|
1944
|
-
|
|
1945
|
-
const createBackIn = (power = 1.525) => (p) => p * p * ((power + 1) * p - power);
|
|
1946
|
-
const backIn = createBackIn();
|
|
1947
|
-
const backOut = reverseEasing(backIn);
|
|
1948
|
-
const backInOut = mirrorEasing(backIn);
|
|
1949
|
-
|
|
1950
|
-
const createAnticipate = (power) => {
|
|
1951
|
-
const backEasing = createBackIn(power);
|
|
1952
|
-
return (p) => (p *= 2) < 1
|
|
1953
|
-
? 0.5 * backEasing(p)
|
|
1954
|
-
: 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
|
|
1955
|
-
};
|
|
1956
|
-
const anticipate = createAnticipate();
|
|
1957
|
-
|
|
1958
|
-
const easingLookup = {
|
|
1959
|
-
linear: noop,
|
|
1960
|
-
easeIn,
|
|
1961
|
-
easeInOut,
|
|
1962
|
-
easeOut,
|
|
1963
|
-
circIn,
|
|
1964
|
-
circInOut,
|
|
1965
|
-
circOut,
|
|
1966
|
-
backIn,
|
|
1967
|
-
backInOut,
|
|
1968
|
-
backOut,
|
|
1969
|
-
anticipate,
|
|
1970
|
-
};
|
|
1971
|
-
const easingDefinitionToFunction = (definition) => {
|
|
1972
|
-
if (Array.isArray(definition)) {
|
|
1973
|
-
// If cubic bezier definition, create bezier curve
|
|
1974
|
-
heyListen.invariant(definition.length === 4, `Cubic bezier arrays must contain four numerical values.`);
|
|
1975
|
-
const [x1, y1, x2, y2] = definition;
|
|
1976
|
-
return cubicBezier(x1, y1, x2, y2);
|
|
1977
|
-
}
|
|
1978
|
-
else if (typeof definition === "string") {
|
|
1979
|
-
// Else lookup from table
|
|
1980
|
-
heyListen.invariant(easingLookup[definition] !== undefined, `Invalid easing type '${definition}'`);
|
|
1981
|
-
return easingLookup[definition];
|
|
1982
|
-
}
|
|
1983
|
-
return definition;
|
|
1984
|
-
};
|
|
1985
|
-
const isEasingArray = (ease) => {
|
|
1986
|
-
return Array.isArray(ease) && typeof ease[0] !== "number";
|
|
1987
|
-
};
|
|
1988
|
-
|
|
1989
|
-
/**
|
|
1990
|
-
* Returns true if the provided string is a color, ie rgba(0,0,0,0) or #000,
|
|
1991
|
-
* but false if a number or multiple colors
|
|
1872
|
+
* Check if value is a numerical string, ie a string that is purely a number eg "100" or "-100.1"
|
|
1992
1873
|
*/
|
|
1993
|
-
const
|
|
1994
|
-
return Boolean((isString(v) && singleColorRegex.test(v) && v.startsWith(type)) ||
|
|
1995
|
-
(testProp && Object.prototype.hasOwnProperty.call(v, testProp)));
|
|
1996
|
-
};
|
|
1997
|
-
const splitColor = (aName, bName, cName) => (v) => {
|
|
1998
|
-
if (!isString(v))
|
|
1999
|
-
return v;
|
|
2000
|
-
const [a, b, c, alpha] = v.match(floatRegex);
|
|
2001
|
-
return {
|
|
2002
|
-
[aName]: parseFloat(a),
|
|
2003
|
-
[bName]: parseFloat(b),
|
|
2004
|
-
[cName]: parseFloat(c),
|
|
2005
|
-
alpha: alpha !== undefined ? parseFloat(alpha) : 1,
|
|
2006
|
-
};
|
|
2007
|
-
};
|
|
2008
|
-
|
|
2009
|
-
const clampRgbUnit = (v) => clamp(0, 255, v);
|
|
2010
|
-
const rgbUnit = {
|
|
2011
|
-
...number,
|
|
2012
|
-
transform: (v) => Math.round(clampRgbUnit(v)),
|
|
2013
|
-
};
|
|
2014
|
-
const rgba = {
|
|
2015
|
-
test: isColorString("rgb", "red"),
|
|
2016
|
-
parse: splitColor("red", "green", "blue"),
|
|
2017
|
-
transform: ({ red, green, blue, alpha: alpha$1 = 1 }) => "rgba(" +
|
|
2018
|
-
rgbUnit.transform(red) +
|
|
2019
|
-
", " +
|
|
2020
|
-
rgbUnit.transform(green) +
|
|
2021
|
-
", " +
|
|
2022
|
-
rgbUnit.transform(blue) +
|
|
2023
|
-
", " +
|
|
2024
|
-
sanitize(alpha.transform(alpha$1)) +
|
|
2025
|
-
")",
|
|
2026
|
-
};
|
|
2027
|
-
|
|
2028
|
-
function parseHex(v) {
|
|
2029
|
-
let r = "";
|
|
2030
|
-
let g = "";
|
|
2031
|
-
let b = "";
|
|
2032
|
-
let a = "";
|
|
2033
|
-
// If we have 6 characters, ie #FF0000
|
|
2034
|
-
if (v.length > 5) {
|
|
2035
|
-
r = v.substring(1, 3);
|
|
2036
|
-
g = v.substring(3, 5);
|
|
2037
|
-
b = v.substring(5, 7);
|
|
2038
|
-
a = v.substring(7, 9);
|
|
2039
|
-
// Or we have 3 characters, ie #F00
|
|
2040
|
-
}
|
|
2041
|
-
else {
|
|
2042
|
-
r = v.substring(1, 2);
|
|
2043
|
-
g = v.substring(2, 3);
|
|
2044
|
-
b = v.substring(3, 4);
|
|
2045
|
-
a = v.substring(4, 5);
|
|
2046
|
-
r += r;
|
|
2047
|
-
g += g;
|
|
2048
|
-
b += b;
|
|
2049
|
-
a += a;
|
|
2050
|
-
}
|
|
2051
|
-
return {
|
|
2052
|
-
red: parseInt(r, 16),
|
|
2053
|
-
green: parseInt(g, 16),
|
|
2054
|
-
blue: parseInt(b, 16),
|
|
2055
|
-
alpha: a ? parseInt(a, 16) / 255 : 1,
|
|
2056
|
-
};
|
|
2057
|
-
}
|
|
2058
|
-
const hex = {
|
|
2059
|
-
test: isColorString("#"),
|
|
2060
|
-
parse: parseHex,
|
|
2061
|
-
transform: rgba.transform,
|
|
2062
|
-
};
|
|
2063
|
-
|
|
2064
|
-
const hsla = {
|
|
2065
|
-
test: isColorString("hsl", "hue"),
|
|
2066
|
-
parse: splitColor("hue", "saturation", "lightness"),
|
|
2067
|
-
transform: ({ hue, saturation, lightness, alpha: alpha$1 = 1 }) => {
|
|
2068
|
-
return ("hsla(" +
|
|
2069
|
-
Math.round(hue) +
|
|
2070
|
-
", " +
|
|
2071
|
-
percent.transform(sanitize(saturation)) +
|
|
2072
|
-
", " +
|
|
2073
|
-
percent.transform(sanitize(lightness)) +
|
|
2074
|
-
", " +
|
|
2075
|
-
sanitize(alpha.transform(alpha$1)) +
|
|
2076
|
-
")");
|
|
2077
|
-
},
|
|
2078
|
-
};
|
|
2079
|
-
|
|
2080
|
-
const color = {
|
|
2081
|
-
test: (v) => rgba.test(v) || hex.test(v) || hsla.test(v),
|
|
2082
|
-
parse: (v) => {
|
|
2083
|
-
if (rgba.test(v)) {
|
|
2084
|
-
return rgba.parse(v);
|
|
2085
|
-
}
|
|
2086
|
-
else if (hsla.test(v)) {
|
|
2087
|
-
return hsla.parse(v);
|
|
2088
|
-
}
|
|
2089
|
-
else {
|
|
2090
|
-
return hex.parse(v);
|
|
2091
|
-
}
|
|
2092
|
-
},
|
|
2093
|
-
transform: (v) => {
|
|
2094
|
-
return isString(v)
|
|
2095
|
-
? v
|
|
2096
|
-
: v.hasOwnProperty("red")
|
|
2097
|
-
? rgba.transform(v)
|
|
2098
|
-
: hsla.transform(v);
|
|
2099
|
-
},
|
|
2100
|
-
};
|
|
2101
|
-
|
|
2102
|
-
const colorToken = "${c}";
|
|
2103
|
-
const numberToken = "${n}";
|
|
2104
|
-
function test(v) {
|
|
2105
|
-
var _a, _b;
|
|
2106
|
-
return (isNaN(v) &&
|
|
2107
|
-
isString(v) &&
|
|
2108
|
-
(((_a = v.match(floatRegex)) === null || _a === void 0 ? void 0 : _a.length) || 0) +
|
|
2109
|
-
(((_b = v.match(colorRegex)) === null || _b === void 0 ? void 0 : _b.length) || 0) >
|
|
2110
|
-
0);
|
|
2111
|
-
}
|
|
2112
|
-
function analyseComplexValue(v) {
|
|
2113
|
-
if (typeof v === "number")
|
|
2114
|
-
v = `${v}`;
|
|
2115
|
-
const values = [];
|
|
2116
|
-
let numColors = 0;
|
|
2117
|
-
let numNumbers = 0;
|
|
2118
|
-
const colors = v.match(colorRegex);
|
|
2119
|
-
if (colors) {
|
|
2120
|
-
numColors = colors.length;
|
|
2121
|
-
// Strip colors from input so they're not picked up by number regex.
|
|
2122
|
-
// There's a better way to combine these regex searches, but its beyond my regex skills
|
|
2123
|
-
v = v.replace(colorRegex, colorToken);
|
|
2124
|
-
values.push(...colors.map(color.parse));
|
|
2125
|
-
}
|
|
2126
|
-
const numbers = v.match(floatRegex);
|
|
2127
|
-
if (numbers) {
|
|
2128
|
-
numNumbers = numbers.length;
|
|
2129
|
-
v = v.replace(floatRegex, numberToken);
|
|
2130
|
-
values.push(...numbers.map(number.parse));
|
|
2131
|
-
}
|
|
2132
|
-
return { values, numColors, numNumbers, tokenised: v };
|
|
2133
|
-
}
|
|
2134
|
-
function parse(v) {
|
|
2135
|
-
return analyseComplexValue(v).values;
|
|
2136
|
-
}
|
|
2137
|
-
function createTransformer(source) {
|
|
2138
|
-
const { values, numColors, tokenised } = analyseComplexValue(source);
|
|
2139
|
-
const numValues = values.length;
|
|
2140
|
-
return (v) => {
|
|
2141
|
-
let output = tokenised;
|
|
2142
|
-
for (let i = 0; i < numValues; i++) {
|
|
2143
|
-
output = output.replace(i < numColors ? colorToken : numberToken, i < numColors
|
|
2144
|
-
? color.transform(v[i])
|
|
2145
|
-
: sanitize(v[i]));
|
|
2146
|
-
}
|
|
2147
|
-
return output;
|
|
2148
|
-
};
|
|
2149
|
-
}
|
|
2150
|
-
const convertNumbersToZero = (v) => typeof v === "number" ? 0 : v;
|
|
2151
|
-
function getAnimatableNone$1(v) {
|
|
2152
|
-
const parsed = parse(v);
|
|
2153
|
-
const transformer = createTransformer(v);
|
|
2154
|
-
return transformer(parsed.map(convertNumbersToZero));
|
|
2155
|
-
}
|
|
2156
|
-
const complex = { test, parse, createTransformer, getAnimatableNone: getAnimatableNone$1 };
|
|
1874
|
+
const isNumericalString = (v) => /^\-?\d*\.?\d+$/.test(v);
|
|
2157
1875
|
|
|
2158
1876
|
/**
|
|
2159
|
-
* Check if
|
|
2160
|
-
*
|
|
2161
|
-
* ✅: 100, "100px", "#fff"
|
|
2162
|
-
* ❌: "block", "url(2.jpg)"
|
|
2163
|
-
* @param value
|
|
2164
|
-
*
|
|
2165
|
-
* @internal
|
|
1877
|
+
* Check if the value is a zero value string like "0px" or "0%"
|
|
2166
1878
|
*/
|
|
2167
|
-
const
|
|
2168
|
-
// If the list of keys tat might be non-animatable grows, replace with Set
|
|
2169
|
-
if (key === "zIndex")
|
|
2170
|
-
return false;
|
|
2171
|
-
// If it's a number or a keyframes array, we can animate it. We might at some point
|
|
2172
|
-
// need to do a deep isAnimatable check of keyframes, or let Popmotion handle this,
|
|
2173
|
-
// but for now lets leave it like this for performance reasons
|
|
2174
|
-
if (typeof value === "number" || Array.isArray(value))
|
|
2175
|
-
return true;
|
|
2176
|
-
if (typeof value === "string" && // It's animatable if we have a string
|
|
2177
|
-
complex.test(value) && // And it contains numbers and/or colors
|
|
2178
|
-
!value.startsWith("url(") // Unless it starts with "url("
|
|
2179
|
-
) {
|
|
2180
|
-
return true;
|
|
2181
|
-
}
|
|
2182
|
-
return false;
|
|
2183
|
-
};
|
|
2184
|
-
|
|
2185
|
-
const underDampedSpring = () => ({
|
|
2186
|
-
type: "spring",
|
|
2187
|
-
stiffness: 500,
|
|
2188
|
-
damping: 25,
|
|
2189
|
-
restSpeed: 10,
|
|
2190
|
-
});
|
|
2191
|
-
const criticallyDampedSpring = (to) => ({
|
|
2192
|
-
type: "spring",
|
|
2193
|
-
stiffness: 550,
|
|
2194
|
-
damping: to === 0 ? 2 * Math.sqrt(550) : 30,
|
|
2195
|
-
restSpeed: 10,
|
|
2196
|
-
});
|
|
2197
|
-
const linearTween = () => ({
|
|
2198
|
-
type: "keyframes",
|
|
2199
|
-
ease: "linear",
|
|
2200
|
-
duration: 0.3,
|
|
2201
|
-
});
|
|
2202
|
-
const keyframes$1 = (values) => ({
|
|
2203
|
-
type: "keyframes",
|
|
2204
|
-
duration: 0.8,
|
|
2205
|
-
values,
|
|
2206
|
-
});
|
|
2207
|
-
const defaultTransitions = {
|
|
2208
|
-
x: underDampedSpring,
|
|
2209
|
-
y: underDampedSpring,
|
|
2210
|
-
z: underDampedSpring,
|
|
2211
|
-
rotate: underDampedSpring,
|
|
2212
|
-
rotateX: underDampedSpring,
|
|
2213
|
-
rotateY: underDampedSpring,
|
|
2214
|
-
rotateZ: underDampedSpring,
|
|
2215
|
-
scaleX: criticallyDampedSpring,
|
|
2216
|
-
scaleY: criticallyDampedSpring,
|
|
2217
|
-
scale: criticallyDampedSpring,
|
|
2218
|
-
opacity: linearTween,
|
|
2219
|
-
backgroundColor: linearTween,
|
|
2220
|
-
color: linearTween,
|
|
2221
|
-
default: criticallyDampedSpring,
|
|
2222
|
-
};
|
|
2223
|
-
const getDefaultTransition = (valueKey, to) => {
|
|
2224
|
-
let transitionFactory;
|
|
2225
|
-
if (isKeyframesTarget(to)) {
|
|
2226
|
-
transitionFactory = keyframes$1;
|
|
2227
|
-
}
|
|
2228
|
-
else {
|
|
2229
|
-
transitionFactory =
|
|
2230
|
-
defaultTransitions[valueKey] || defaultTransitions.default;
|
|
2231
|
-
}
|
|
2232
|
-
return { to, ...transitionFactory(to) };
|
|
2233
|
-
};
|
|
1879
|
+
const isZeroValueString = (v) => /^0[^.\s]+$/.test(v);
|
|
2234
1880
|
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
const maxDefaults = new Set(["brightness", "contrast", "saturate", "opacity"]);
|
|
2239
|
-
function applyDefaultFilter(v) {
|
|
2240
|
-
const [name, value] = v.slice(0, -1).split("(");
|
|
2241
|
-
if (name === "drop-shadow")
|
|
2242
|
-
return v;
|
|
2243
|
-
const [number] = value.match(floatRegex) || [];
|
|
2244
|
-
if (!number)
|
|
2245
|
-
return v;
|
|
2246
|
-
const unit = value.replace(number, "");
|
|
2247
|
-
let defaultValue = maxDefaults.has(name) ? 1 : 0;
|
|
2248
|
-
if (number !== value)
|
|
2249
|
-
defaultValue *= 100;
|
|
2250
|
-
return name + "(" + defaultValue + unit + ")";
|
|
2251
|
-
}
|
|
2252
|
-
const functionRegex = /([a-z-]*)\(.*?\)/g;
|
|
2253
|
-
const filter = {
|
|
2254
|
-
...complex,
|
|
2255
|
-
getAnimatableNone: (v) => {
|
|
2256
|
-
const functions = v.match(functionRegex);
|
|
2257
|
-
return functions ? functions.map(applyDefaultFilter).join(" ") : v;
|
|
2258
|
-
},
|
|
1881
|
+
const frameData = {
|
|
1882
|
+
delta: 0,
|
|
1883
|
+
timestamp: 0,
|
|
2259
1884
|
};
|
|
2260
1885
|
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
*/
|
|
2264
|
-
const defaultValueTypes = {
|
|
2265
|
-
...numberValueTypes,
|
|
2266
|
-
// Color props
|
|
2267
|
-
color,
|
|
2268
|
-
backgroundColor: color,
|
|
2269
|
-
outlineColor: color,
|
|
2270
|
-
fill: color,
|
|
2271
|
-
stroke: color,
|
|
2272
|
-
// Border props
|
|
2273
|
-
borderColor: color,
|
|
2274
|
-
borderTopColor: color,
|
|
2275
|
-
borderRightColor: color,
|
|
2276
|
-
borderBottomColor: color,
|
|
2277
|
-
borderLeftColor: color,
|
|
2278
|
-
filter,
|
|
2279
|
-
WebkitFilter: filter,
|
|
2280
|
-
};
|
|
2281
|
-
/**
|
|
2282
|
-
* Gets the default ValueType for the provided value key
|
|
1886
|
+
/*
|
|
1887
|
+
Detect and load appropriate clock setting for the execution environment
|
|
2283
1888
|
*/
|
|
2284
|
-
const
|
|
1889
|
+
const defaultTimestep = (1 / 60) * 1000;
|
|
1890
|
+
const getCurrentTime = typeof performance !== "undefined"
|
|
1891
|
+
? () => performance.now()
|
|
1892
|
+
: () => Date.now();
|
|
1893
|
+
const onNextFrame = typeof window !== "undefined"
|
|
1894
|
+
? (callback) => window.requestAnimationFrame(callback)
|
|
1895
|
+
: (callback) => setTimeout(() => callback(getCurrentTime()), defaultTimestep);
|
|
2285
1896
|
|
|
2286
|
-
function
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
1897
|
+
function createRenderStep(runNextFrame) {
|
|
1898
|
+
/**
|
|
1899
|
+
* We create and reuse two arrays, one to queue jobs for the current frame
|
|
1900
|
+
* and one for the next. We reuse to avoid triggering GC after x frames.
|
|
1901
|
+
*/
|
|
1902
|
+
let toRun = [];
|
|
1903
|
+
let toRunNextFrame = [];
|
|
1904
|
+
/**
|
|
1905
|
+
*
|
|
1906
|
+
*/
|
|
1907
|
+
let numToRun = 0;
|
|
1908
|
+
/**
|
|
1909
|
+
* Track whether we're currently processing jobs in this step. This way
|
|
1910
|
+
* we can decide whether to schedule new jobs for this frame or next.
|
|
1911
|
+
*/
|
|
1912
|
+
let isProcessing = false;
|
|
1913
|
+
let flushNextFrame = false;
|
|
1914
|
+
/**
|
|
1915
|
+
* A set of processes which were marked keepAlive when scheduled.
|
|
1916
|
+
*/
|
|
1917
|
+
const toKeepAlive = new WeakSet();
|
|
1918
|
+
const step = {
|
|
1919
|
+
/**
|
|
1920
|
+
* Schedule a process to run on the next frame.
|
|
1921
|
+
*/
|
|
1922
|
+
schedule: (callback, keepAlive = false, immediate = false) => {
|
|
1923
|
+
const addToCurrentFrame = immediate && isProcessing;
|
|
1924
|
+
const buffer = addToCurrentFrame ? toRun : toRunNextFrame;
|
|
1925
|
+
if (keepAlive)
|
|
1926
|
+
toKeepAlive.add(callback);
|
|
1927
|
+
// If the buffer doesn't already contain this callback, add it
|
|
1928
|
+
if (buffer.indexOf(callback) === -1) {
|
|
1929
|
+
buffer.push(callback);
|
|
1930
|
+
// If we're adding it to the currently running buffer, update its measured size
|
|
1931
|
+
if (addToCurrentFrame && isProcessing)
|
|
1932
|
+
numToRun = toRun.length;
|
|
1933
|
+
}
|
|
1934
|
+
return callback;
|
|
1935
|
+
},
|
|
1936
|
+
/**
|
|
1937
|
+
* Cancel the provided callback from running on the next frame.
|
|
1938
|
+
*/
|
|
1939
|
+
cancel: (callback) => {
|
|
1940
|
+
const index = toRunNextFrame.indexOf(callback);
|
|
1941
|
+
if (index !== -1)
|
|
1942
|
+
toRunNextFrame.splice(index, 1);
|
|
1943
|
+
toKeepAlive.delete(callback);
|
|
1944
|
+
},
|
|
1945
|
+
/**
|
|
1946
|
+
* Execute all schedule callbacks.
|
|
1947
|
+
*/
|
|
1948
|
+
process: (frameData) => {
|
|
1949
|
+
/**
|
|
1950
|
+
* If we're already processing we've probably been triggered by a flushSync
|
|
1951
|
+
* inside an existing process. Instead of executing, mark flushNextFrame
|
|
1952
|
+
* as true and ensure we flush the following frame at the end of this one.
|
|
1953
|
+
*/
|
|
1954
|
+
if (isProcessing) {
|
|
1955
|
+
flushNextFrame = true;
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
isProcessing = true;
|
|
1959
|
+
[toRun, toRunNextFrame] = [toRunNextFrame, toRun];
|
|
1960
|
+
// Clear the next frame list
|
|
1961
|
+
toRunNextFrame.length = 0;
|
|
1962
|
+
// Execute this frame
|
|
1963
|
+
numToRun = toRun.length;
|
|
1964
|
+
if (numToRun) {
|
|
1965
|
+
for (let i = 0; i < numToRun; i++) {
|
|
1966
|
+
const callback = toRun[i];
|
|
1967
|
+
callback(frameData);
|
|
1968
|
+
if (toKeepAlive.has(callback)) {
|
|
1969
|
+
step.schedule(callback);
|
|
1970
|
+
runNextFrame();
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
isProcessing = false;
|
|
1975
|
+
if (flushNextFrame) {
|
|
1976
|
+
flushNextFrame = false;
|
|
1977
|
+
step.process(frameData);
|
|
1978
|
+
}
|
|
1979
|
+
},
|
|
1980
|
+
};
|
|
1981
|
+
return step;
|
|
2293
1982
|
}
|
|
2294
1983
|
|
|
2295
|
-
const
|
|
2296
|
-
|
|
1984
|
+
const maxElapsed = 40;
|
|
1985
|
+
let useDefaultElapsed = true;
|
|
1986
|
+
let runNextFrame = false;
|
|
1987
|
+
let isProcessing = false;
|
|
1988
|
+
const stepsOrder = [
|
|
1989
|
+
"read",
|
|
1990
|
+
"update",
|
|
1991
|
+
"preRender",
|
|
1992
|
+
"render",
|
|
1993
|
+
"postRender",
|
|
1994
|
+
];
|
|
1995
|
+
const steps = stepsOrder.reduce((acc, key) => {
|
|
1996
|
+
acc[key] = createRenderStep(() => (runNextFrame = true));
|
|
1997
|
+
return acc;
|
|
1998
|
+
}, {});
|
|
1999
|
+
const sync = stepsOrder.reduce((acc, key) => {
|
|
2000
|
+
const step = steps[key];
|
|
2001
|
+
acc[key] = (process, keepAlive = false, immediate = false) => {
|
|
2002
|
+
if (!runNextFrame)
|
|
2003
|
+
startLoop();
|
|
2004
|
+
return step.schedule(process, keepAlive, immediate);
|
|
2005
|
+
};
|
|
2006
|
+
return acc;
|
|
2007
|
+
}, {});
|
|
2008
|
+
const cancelSync = stepsOrder.reduce((acc, key) => {
|
|
2009
|
+
acc[key] = steps[key].cancel;
|
|
2010
|
+
return acc;
|
|
2011
|
+
}, {});
|
|
2012
|
+
const flushSync = stepsOrder.reduce((acc, key) => {
|
|
2013
|
+
acc[key] = () => steps[key].process(frameData);
|
|
2014
|
+
return acc;
|
|
2015
|
+
}, {});
|
|
2016
|
+
const processStep = (stepId) => steps[stepId].process(frameData);
|
|
2017
|
+
const processFrame = (timestamp) => {
|
|
2018
|
+
runNextFrame = false;
|
|
2019
|
+
frameData.delta = useDefaultElapsed
|
|
2020
|
+
? defaultTimestep
|
|
2021
|
+
: Math.max(Math.min(timestamp - frameData.timestamp, maxElapsed), 1);
|
|
2022
|
+
frameData.timestamp = timestamp;
|
|
2023
|
+
isProcessing = true;
|
|
2024
|
+
stepsOrder.forEach(processStep);
|
|
2025
|
+
isProcessing = false;
|
|
2026
|
+
if (runNextFrame) {
|
|
2027
|
+
useDefaultElapsed = false;
|
|
2028
|
+
onNextFrame(processFrame);
|
|
2029
|
+
}
|
|
2030
|
+
};
|
|
2031
|
+
const startLoop = () => {
|
|
2032
|
+
runNextFrame = true;
|
|
2033
|
+
useDefaultElapsed = true;
|
|
2034
|
+
if (!isProcessing)
|
|
2035
|
+
onNextFrame(processFrame);
|
|
2297
2036
|
};
|
|
2298
2037
|
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
Given a lower limit and an upper limit, we return the value within
|
|
2303
|
-
that range as expressed by progress (usually a number from 0 to 1)
|
|
2304
|
-
|
|
2305
|
-
So progress = 0.5 would change
|
|
2306
|
-
|
|
2307
|
-
from -------- to
|
|
2308
|
-
|
|
2309
|
-
to
|
|
2310
|
-
|
|
2311
|
-
from ---- to
|
|
2312
|
-
|
|
2313
|
-
E.g. from = 10, to = 20, progress = 0.5 => 15
|
|
2314
|
-
|
|
2315
|
-
@param [number]: Lower limit of range
|
|
2316
|
-
@param [number]: Upper limit of range
|
|
2317
|
-
@param [number]: The progress between lower and upper limits expressed 0-1
|
|
2318
|
-
@return [number]: Value as calculated from progress within range (not limited within range)
|
|
2319
|
-
*/
|
|
2320
|
-
const mix = (from, to, progress) => -progress * from + progress * to + from;
|
|
2321
|
-
|
|
2322
|
-
// Adapted from https://gist.github.com/mjackson/5311256
|
|
2323
|
-
function hueToRgb(p, q, t) {
|
|
2324
|
-
if (t < 0)
|
|
2325
|
-
t += 1;
|
|
2326
|
-
if (t > 1)
|
|
2327
|
-
t -= 1;
|
|
2328
|
-
if (t < 1 / 6)
|
|
2329
|
-
return p + (q - p) * 6 * t;
|
|
2330
|
-
if (t < 1 / 2)
|
|
2331
|
-
return q;
|
|
2332
|
-
if (t < 2 / 3)
|
|
2333
|
-
return p + (q - p) * (2 / 3 - t) * 6;
|
|
2334
|
-
return p;
|
|
2038
|
+
function addUniqueItem(arr, item) {
|
|
2039
|
+
if (arr.indexOf(item) === -1)
|
|
2040
|
+
arr.push(item);
|
|
2335
2041
|
}
|
|
2336
|
-
function
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
let red = 0;
|
|
2341
|
-
let green = 0;
|
|
2342
|
-
let blue = 0;
|
|
2343
|
-
if (!saturation) {
|
|
2344
|
-
red = green = blue = lightness;
|
|
2345
|
-
}
|
|
2346
|
-
else {
|
|
2347
|
-
const q = lightness < 0.5
|
|
2348
|
-
? lightness * (1 + saturation)
|
|
2349
|
-
: lightness + saturation - lightness * saturation;
|
|
2350
|
-
const p = 2 * lightness - q;
|
|
2351
|
-
red = hueToRgb(p, q, hue + 1 / 3);
|
|
2352
|
-
green = hueToRgb(p, q, hue);
|
|
2353
|
-
blue = hueToRgb(p, q, hue - 1 / 3);
|
|
2354
|
-
}
|
|
2355
|
-
return {
|
|
2356
|
-
red: Math.round(red * 255),
|
|
2357
|
-
green: Math.round(green * 255),
|
|
2358
|
-
blue: Math.round(blue * 255),
|
|
2359
|
-
alpha,
|
|
2360
|
-
};
|
|
2042
|
+
function removeItem(arr, item) {
|
|
2043
|
+
const index = arr.indexOf(item);
|
|
2044
|
+
if (index > -1)
|
|
2045
|
+
arr.splice(index, 1);
|
|
2361
2046
|
}
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
const
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
};
|
|
2370
|
-
const colorTypes = [hex, rgba, hsla];
|
|
2371
|
-
const getColorType = (v) => colorTypes.find((type) => type.test(v));
|
|
2372
|
-
function asRGBA(color) {
|
|
2373
|
-
const type = getColorType(color);
|
|
2374
|
-
heyListen.invariant(Boolean(type), `'${color}' is not an animatable color. Use the equivalent color code instead.`);
|
|
2375
|
-
let model = type.parse(color);
|
|
2376
|
-
if (type === hsla) {
|
|
2377
|
-
// TODO Remove this cast - needed since Framer Motion's stricter typing
|
|
2378
|
-
model = hslaToRgba(model);
|
|
2047
|
+
// Adapted from array-move
|
|
2048
|
+
function moveItem([...arr], fromIndex, toIndex) {
|
|
2049
|
+
const startIndex = fromIndex < 0 ? arr.length + fromIndex : fromIndex;
|
|
2050
|
+
if (startIndex >= 0 && startIndex < arr.length) {
|
|
2051
|
+
const endIndex = toIndex < 0 ? arr.length + toIndex : toIndex;
|
|
2052
|
+
const [item] = arr.splice(fromIndex, 1);
|
|
2053
|
+
arr.splice(endIndex, 0, item);
|
|
2379
2054
|
}
|
|
2380
|
-
return
|
|
2055
|
+
return arr;
|
|
2381
2056
|
}
|
|
2382
|
-
const mixColor = (from, to) => {
|
|
2383
|
-
const fromRGBA = asRGBA(from);
|
|
2384
|
-
const toRGBA = asRGBA(to);
|
|
2385
|
-
const blended = { ...fromRGBA };
|
|
2386
|
-
return (v) => {
|
|
2387
|
-
blended.red = mixLinearColor(fromRGBA.red, toRGBA.red, v);
|
|
2388
|
-
blended.green = mixLinearColor(fromRGBA.green, toRGBA.green, v);
|
|
2389
|
-
blended.blue = mixLinearColor(fromRGBA.blue, toRGBA.blue, v);
|
|
2390
|
-
blended.alpha = mix(fromRGBA.alpha, toRGBA.alpha, v);
|
|
2391
|
-
return rgba.transform(blended);
|
|
2392
|
-
};
|
|
2393
|
-
};
|
|
2394
2057
|
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
}
|
|
2399
|
-
else if (color.test(origin)) {
|
|
2400
|
-
return mixColor(origin, target);
|
|
2401
|
-
}
|
|
2402
|
-
else {
|
|
2403
|
-
return mixComplex(origin, target);
|
|
2058
|
+
class SubscriptionManager {
|
|
2059
|
+
constructor() {
|
|
2060
|
+
this.subscriptions = [];
|
|
2404
2061
|
}
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
const numValues = output.length;
|
|
2409
|
-
const blendValue = from.map((fromThis, i) => getMixer$1(fromThis, to[i]));
|
|
2410
|
-
return (v) => {
|
|
2411
|
-
for (let i = 0; i < numValues; i++) {
|
|
2412
|
-
output[i] = blendValue[i](v);
|
|
2413
|
-
}
|
|
2414
|
-
return output;
|
|
2415
|
-
};
|
|
2416
|
-
};
|
|
2417
|
-
const mixObject = (origin, target) => {
|
|
2418
|
-
const output = { ...origin, ...target };
|
|
2419
|
-
const blendValue = {};
|
|
2420
|
-
for (const key in output) {
|
|
2421
|
-
if (origin[key] !== undefined && target[key] !== undefined) {
|
|
2422
|
-
blendValue[key] = getMixer$1(origin[key], target[key]);
|
|
2423
|
-
}
|
|
2062
|
+
add(handler) {
|
|
2063
|
+
addUniqueItem(this.subscriptions, handler);
|
|
2064
|
+
return () => removeItem(this.subscriptions, handler);
|
|
2424
2065
|
}
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2066
|
+
notify(a, b, c) {
|
|
2067
|
+
const numSubscriptions = this.subscriptions.length;
|
|
2068
|
+
if (!numSubscriptions)
|
|
2069
|
+
return;
|
|
2070
|
+
if (numSubscriptions === 1) {
|
|
2071
|
+
/**
|
|
2072
|
+
* If there's only a single handler we can just call it without invoking a loop.
|
|
2073
|
+
*/
|
|
2074
|
+
this.subscriptions[0](a, b, c);
|
|
2075
|
+
}
|
|
2076
|
+
else {
|
|
2077
|
+
for (let i = 0; i < numSubscriptions; i++) {
|
|
2078
|
+
/**
|
|
2079
|
+
* Check whether the handler exists before firing as it's possible
|
|
2080
|
+
* the subscriptions were modified during this loop running.
|
|
2081
|
+
*/
|
|
2082
|
+
const handler = this.subscriptions[i];
|
|
2083
|
+
handler && handler(a, b, c);
|
|
2084
|
+
}
|
|
2428
2085
|
}
|
|
2429
|
-
return output;
|
|
2430
|
-
};
|
|
2431
|
-
};
|
|
2432
|
-
const mixComplex = (origin, target) => {
|
|
2433
|
-
const template = complex.createTransformer(target);
|
|
2434
|
-
const originStats = analyseComplexValue(origin);
|
|
2435
|
-
const targetStats = analyseComplexValue(target);
|
|
2436
|
-
const canInterpolate = originStats.numColors === targetStats.numColors &&
|
|
2437
|
-
originStats.numNumbers >= targetStats.numNumbers;
|
|
2438
|
-
if (canInterpolate) {
|
|
2439
|
-
return pipe(mixArray(originStats.values, targetStats.values), template);
|
|
2440
2086
|
}
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
return (p) => `${p > 0 ? target : origin}`;
|
|
2087
|
+
getSize() {
|
|
2088
|
+
return this.subscriptions.length;
|
|
2444
2089
|
}
|
|
2445
|
-
|
|
2090
|
+
clear() {
|
|
2091
|
+
this.subscriptions.length = 0;
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2446
2094
|
|
|
2447
2095
|
/*
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
Given a lower limit and an upper limit, we return the progress
|
|
2451
|
-
(expressed as a number 0-1) represented by the given value, and
|
|
2452
|
-
limit that progress to within 0-1.
|
|
2096
|
+
Convert velocity into velocity per second
|
|
2453
2097
|
|
|
2454
|
-
@param [number]:
|
|
2455
|
-
@param [number]:
|
|
2456
|
-
@param [number]: Value to find progress within given range
|
|
2457
|
-
@return [number]: Progress of value within range as expressed 0-1
|
|
2098
|
+
@param [number]: Unit per frame
|
|
2099
|
+
@param [number]: Frame duration in ms
|
|
2458
2100
|
*/
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
};
|
|
2101
|
+
function velocityPerSecond(velocity, frameDuration) {
|
|
2102
|
+
return frameDuration ? velocity * (1000 / frameDuration) : 0;
|
|
2103
|
+
}
|
|
2463
2104
|
|
|
2464
|
-
const
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2105
|
+
const isFloat = (value) => {
|
|
2106
|
+
return !isNaN(parseFloat(value));
|
|
2107
|
+
};
|
|
2108
|
+
/**
|
|
2109
|
+
* `MotionValue` is used to track the state and velocity of motion values.
|
|
2110
|
+
*
|
|
2111
|
+
* @public
|
|
2112
|
+
*/
|
|
2113
|
+
class MotionValue {
|
|
2114
|
+
/**
|
|
2115
|
+
* @param init - The initiating value
|
|
2116
|
+
* @param config - Optional configuration options
|
|
2117
|
+
*
|
|
2118
|
+
* - `transformer`: A function to transform incoming values with.
|
|
2119
|
+
*
|
|
2120
|
+
* @internal
|
|
2121
|
+
*/
|
|
2122
|
+
constructor(init, options = {}) {
|
|
2123
|
+
/**
|
|
2124
|
+
* This will be replaced by the build step with the latest version number.
|
|
2125
|
+
* When MotionValues are provided to motion components, warn if versions are mixed.
|
|
2126
|
+
*/
|
|
2127
|
+
this.version = "7.9.0";
|
|
2128
|
+
/**
|
|
2129
|
+
* Duration, in milliseconds, since last updating frame.
|
|
2130
|
+
*
|
|
2131
|
+
* @internal
|
|
2132
|
+
*/
|
|
2133
|
+
this.timeDelta = 0;
|
|
2134
|
+
/**
|
|
2135
|
+
* Timestamp of the last time this `MotionValue` was updated.
|
|
2136
|
+
*
|
|
2137
|
+
* @internal
|
|
2138
|
+
*/
|
|
2139
|
+
this.lastUpdated = 0;
|
|
2140
|
+
/**
|
|
2141
|
+
* Functions to notify when the `MotionValue` updates.
|
|
2142
|
+
*
|
|
2143
|
+
* @internal
|
|
2144
|
+
*/
|
|
2145
|
+
this.updateSubscribers = new SubscriptionManager();
|
|
2146
|
+
/**
|
|
2147
|
+
* Functions to notify when the velocity updates.
|
|
2148
|
+
*
|
|
2149
|
+
* @internal
|
|
2150
|
+
*/
|
|
2151
|
+
this.velocityUpdateSubscribers = new SubscriptionManager();
|
|
2152
|
+
/**
|
|
2153
|
+
* Functions to notify when the `MotionValue` updates and `render` is set to `true`.
|
|
2154
|
+
*
|
|
2155
|
+
* @internal
|
|
2156
|
+
*/
|
|
2157
|
+
this.renderSubscribers = new SubscriptionManager();
|
|
2158
|
+
/**
|
|
2159
|
+
* Tracks whether this value can output a velocity. Currently this is only true
|
|
2160
|
+
* if the value is numerical, but we might be able to widen the scope here and support
|
|
2161
|
+
* other value types.
|
|
2162
|
+
*
|
|
2163
|
+
* @internal
|
|
2164
|
+
*/
|
|
2165
|
+
this.canTrackVelocity = false;
|
|
2166
|
+
this.updateAndNotify = (v, render = true) => {
|
|
2167
|
+
this.prev = this.current;
|
|
2168
|
+
this.current = v;
|
|
2169
|
+
// Update timestamp
|
|
2170
|
+
const { delta, timestamp } = frameData;
|
|
2171
|
+
if (this.lastUpdated !== timestamp) {
|
|
2172
|
+
this.timeDelta = delta;
|
|
2173
|
+
this.lastUpdated = timestamp;
|
|
2174
|
+
sync.postRender(this.scheduleVelocityCheck);
|
|
2175
|
+
}
|
|
2176
|
+
// Update update subscribers
|
|
2177
|
+
if (this.prev !== this.current) {
|
|
2178
|
+
this.updateSubscribers.notify(this.current);
|
|
2179
|
+
}
|
|
2180
|
+
// Update velocity subscribers
|
|
2181
|
+
if (this.velocityUpdateSubscribers.getSize()) {
|
|
2182
|
+
this.velocityUpdateSubscribers.notify(this.getVelocity());
|
|
2183
|
+
}
|
|
2184
|
+
// Update render subscribers
|
|
2185
|
+
if (render) {
|
|
2186
|
+
this.renderSubscribers.notify(this.current);
|
|
2187
|
+
}
|
|
2188
|
+
};
|
|
2189
|
+
/**
|
|
2190
|
+
* Schedule a velocity check for the next frame.
|
|
2191
|
+
*
|
|
2192
|
+
* This is an instanced and bound function to prevent generating a new
|
|
2193
|
+
* function once per frame.
|
|
2194
|
+
*
|
|
2195
|
+
* @internal
|
|
2196
|
+
*/
|
|
2197
|
+
this.scheduleVelocityCheck = () => sync.postRender(this.velocityCheck);
|
|
2198
|
+
/**
|
|
2199
|
+
* Updates `prev` with `current` if the value hasn't been updated this frame.
|
|
2200
|
+
* This ensures velocity calculations return `0`.
|
|
2201
|
+
*
|
|
2202
|
+
* This is an instanced and bound function to prevent generating a new
|
|
2203
|
+
* function once per frame.
|
|
2204
|
+
*
|
|
2205
|
+
* @internal
|
|
2206
|
+
*/
|
|
2207
|
+
this.velocityCheck = ({ timestamp }) => {
|
|
2208
|
+
if (timestamp !== this.lastUpdated) {
|
|
2209
|
+
this.prev = this.current;
|
|
2210
|
+
this.velocityUpdateSubscribers.notify(this.getVelocity());
|
|
2211
|
+
}
|
|
2212
|
+
};
|
|
2213
|
+
this.hasAnimated = false;
|
|
2214
|
+
this.prev = this.current = init;
|
|
2215
|
+
this.canTrackVelocity = isFloat(this.current);
|
|
2216
|
+
this.owner = options.owner;
|
|
2468
2217
|
}
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2218
|
+
/**
|
|
2219
|
+
* Adds a function that will be notified when the `MotionValue` is updated.
|
|
2220
|
+
*
|
|
2221
|
+
* It returns a function that, when called, will cancel the subscription.
|
|
2222
|
+
*
|
|
2223
|
+
* When calling `onChange` inside a React component, it should be wrapped with the
|
|
2224
|
+
* `useEffect` hook. As it returns an unsubscribe function, this should be returned
|
|
2225
|
+
* from the `useEffect` function to ensure you don't add duplicate subscribers..
|
|
2226
|
+
*
|
|
2227
|
+
* ```jsx
|
|
2228
|
+
* export const MyComponent = () => {
|
|
2229
|
+
* const x = useMotionValue(0)
|
|
2230
|
+
* const y = useMotionValue(0)
|
|
2231
|
+
* const opacity = useMotionValue(1)
|
|
2232
|
+
*
|
|
2233
|
+
* useEffect(() => {
|
|
2234
|
+
* function updateOpacity() {
|
|
2235
|
+
* const maxXY = Math.max(x.get(), y.get())
|
|
2236
|
+
* const newOpacity = transform(maxXY, [0, 100], [1, 0])
|
|
2237
|
+
* opacity.set(newOpacity)
|
|
2238
|
+
* }
|
|
2239
|
+
*
|
|
2240
|
+
* const unsubscribeX = x.onChange(updateOpacity)
|
|
2241
|
+
* const unsubscribeY = y.onChange(updateOpacity)
|
|
2242
|
+
*
|
|
2243
|
+
* return () => {
|
|
2244
|
+
* unsubscribeX()
|
|
2245
|
+
* unsubscribeY()
|
|
2246
|
+
* }
|
|
2247
|
+
* }, [])
|
|
2248
|
+
*
|
|
2249
|
+
* return <motion.div style={{ x }} />
|
|
2250
|
+
* }
|
|
2251
|
+
* ```
|
|
2252
|
+
*
|
|
2253
|
+
* @privateRemarks
|
|
2254
|
+
*
|
|
2255
|
+
* We could look into a `useOnChange` hook if the above lifecycle management proves confusing.
|
|
2256
|
+
*
|
|
2257
|
+
* ```jsx
|
|
2258
|
+
* useOnChange(x, () => {})
|
|
2259
|
+
* ```
|
|
2260
|
+
*
|
|
2261
|
+
* @param subscriber - A function that receives the latest value.
|
|
2262
|
+
* @returns A function that, when called, will cancel this subscription.
|
|
2263
|
+
*
|
|
2264
|
+
* @public
|
|
2265
|
+
*/
|
|
2266
|
+
onChange(subscription) {
|
|
2267
|
+
return this.updateSubscribers.add(subscription);
|
|
2476
2268
|
}
|
|
2477
|
-
|
|
2478
|
-
|
|
2269
|
+
clearListeners() {
|
|
2270
|
+
this.updateSubscribers.clear();
|
|
2479
2271
|
}
|
|
2480
|
-
|
|
2481
|
-
|
|
2272
|
+
/**
|
|
2273
|
+
* Adds a function that will be notified when the `MotionValue` requests a render.
|
|
2274
|
+
*
|
|
2275
|
+
* @param subscriber - A function that's provided the latest value.
|
|
2276
|
+
* @returns A function that, when called, will cancel this subscription.
|
|
2277
|
+
*
|
|
2278
|
+
* @internal
|
|
2279
|
+
*/
|
|
2280
|
+
onRenderRequest(subscription) {
|
|
2281
|
+
// Render immediately
|
|
2282
|
+
subscription(this.get());
|
|
2283
|
+
return this.renderSubscribers.add(subscription);
|
|
2482
2284
|
}
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2285
|
+
/**
|
|
2286
|
+
* Attaches a passive effect to the `MotionValue`.
|
|
2287
|
+
*
|
|
2288
|
+
* @internal
|
|
2289
|
+
*/
|
|
2290
|
+
attach(passiveEffect) {
|
|
2291
|
+
this.passiveEffect = passiveEffect;
|
|
2292
|
+
}
|
|
2293
|
+
/**
|
|
2294
|
+
* Sets the state of the `MotionValue`.
|
|
2295
|
+
*
|
|
2296
|
+
* @remarks
|
|
2297
|
+
*
|
|
2298
|
+
* ```jsx
|
|
2299
|
+
* const x = useMotionValue(0)
|
|
2300
|
+
* x.set(10)
|
|
2301
|
+
* ```
|
|
2302
|
+
*
|
|
2303
|
+
* @param latest - Latest value to set.
|
|
2304
|
+
* @param render - Whether to notify render subscribers. Defaults to `true`
|
|
2305
|
+
*
|
|
2306
|
+
* @public
|
|
2307
|
+
*/
|
|
2308
|
+
set(v, render = true) {
|
|
2309
|
+
if (!render || !this.passiveEffect) {
|
|
2310
|
+
this.updateAndNotify(v, render);
|
|
2311
|
+
}
|
|
2312
|
+
else {
|
|
2313
|
+
this.passiveEffect(v, this.updateAndNotify);
|
|
2494
2314
|
}
|
|
2495
|
-
mixers.push(mixer);
|
|
2496
2315
|
}
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
*
|
|
2502
|
-
* Accepts:
|
|
2503
|
-
* - Numbers
|
|
2504
|
-
* - Colors (hex, hsl, hsla, rgb, rgba)
|
|
2505
|
-
* - Complex (combinations of one or more numbers or strings)
|
|
2506
|
-
*
|
|
2507
|
-
* ```jsx
|
|
2508
|
-
* const mixColor = interpolate([0, 1], ['#fff', '#000'])
|
|
2509
|
-
*
|
|
2510
|
-
* mixColor(0.5) // 'rgba(128, 128, 128, 1)'
|
|
2511
|
-
* ```
|
|
2512
|
-
*
|
|
2513
|
-
* TODO Revist this approach once we've moved to data models for values,
|
|
2514
|
-
* probably not needed to pregenerate mixer functions.
|
|
2515
|
-
*
|
|
2516
|
-
* @public
|
|
2517
|
-
*/
|
|
2518
|
-
function interpolate(input, output, { clamp: isClamp = true, ease, mixer } = {}) {
|
|
2519
|
-
const inputLength = input.length;
|
|
2520
|
-
heyListen.invariant(inputLength === output.length, "Both input and output ranges must be the same length");
|
|
2521
|
-
heyListen.invariant(!ease || !Array.isArray(ease) || ease.length === inputLength - 1, "Array of easing functions must be of length `input.length - 1`, as it applies to the transitions **between** the defined values.");
|
|
2522
|
-
// If input runs highest -> lowest, reverse both arrays
|
|
2523
|
-
if (input[0] > input[inputLength - 1]) {
|
|
2524
|
-
input = [...input].reverse();
|
|
2525
|
-
output = [...output].reverse();
|
|
2316
|
+
setWithVelocity(prev, current, delta) {
|
|
2317
|
+
this.set(current);
|
|
2318
|
+
this.prev = prev;
|
|
2319
|
+
this.timeDelta = delta;
|
|
2526
2320
|
}
|
|
2527
|
-
const mixers = createMixers(output, ease, mixer);
|
|
2528
|
-
const numMixers = mixers.length;
|
|
2529
|
-
const interpolator = (v) => {
|
|
2530
|
-
let i = 0;
|
|
2531
|
-
if (numMixers > 1) {
|
|
2532
|
-
for (; i < input.length - 2; i++) {
|
|
2533
|
-
if (v < input[i + 1])
|
|
2534
|
-
break;
|
|
2535
|
-
}
|
|
2536
|
-
}
|
|
2537
|
-
const progressInRange = progress(input[i], input[i + 1], v);
|
|
2538
|
-
return mixers[i](progressInRange);
|
|
2539
|
-
};
|
|
2540
|
-
return isClamp
|
|
2541
|
-
? (v) => interpolator(clamp(input[0], input[inputLength - 1], v))
|
|
2542
|
-
: interpolator;
|
|
2543
|
-
}
|
|
2544
|
-
|
|
2545
|
-
function defaultEasing(values, easing) {
|
|
2546
|
-
return values.map(() => easing || easeInOut).splice(0, values.length - 1);
|
|
2547
|
-
}
|
|
2548
|
-
function defaultOffset(values) {
|
|
2549
|
-
const numValues = values.length;
|
|
2550
|
-
return values.map((_value, i) => i !== 0 ? i / (numValues - 1) : 0);
|
|
2551
|
-
}
|
|
2552
|
-
function convertOffsetToTimes(offset, duration) {
|
|
2553
|
-
return offset.map((o) => o * duration);
|
|
2554
|
-
}
|
|
2555
|
-
function keyframes({ from = 0, to = 1, ease, offset, duration = 300, }) {
|
|
2556
2321
|
/**
|
|
2557
|
-
*
|
|
2558
|
-
*
|
|
2322
|
+
* Returns the latest state of `MotionValue`
|
|
2323
|
+
*
|
|
2324
|
+
* @returns - The latest state of `MotionValue`
|
|
2325
|
+
*
|
|
2326
|
+
* @public
|
|
2559
2327
|
*/
|
|
2560
|
-
|
|
2328
|
+
get() {
|
|
2329
|
+
return this.current;
|
|
2330
|
+
}
|
|
2561
2331
|
/**
|
|
2562
|
-
*
|
|
2332
|
+
* @public
|
|
2563
2333
|
*/
|
|
2564
|
-
|
|
2334
|
+
getPrevious() {
|
|
2335
|
+
return this.prev;
|
|
2336
|
+
}
|
|
2565
2337
|
/**
|
|
2566
|
-
*
|
|
2338
|
+
* Returns the latest velocity of `MotionValue`
|
|
2339
|
+
*
|
|
2340
|
+
* @returns - The latest velocity of `MotionValue`. Returns `0` if the state is non-numerical.
|
|
2341
|
+
*
|
|
2342
|
+
* @public
|
|
2567
2343
|
*/
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
return interpolate(times, values, {
|
|
2576
|
-
ease: Array.isArray(ease) ? ease : defaultEasing(values, ease),
|
|
2577
|
-
});
|
|
2344
|
+
getVelocity() {
|
|
2345
|
+
// This could be isFloat(this.prev) && isFloat(this.current), but that would be wasteful
|
|
2346
|
+
return this.canTrackVelocity
|
|
2347
|
+
? // These casts could be avoided if parseFloat would be typed better
|
|
2348
|
+
velocityPerSecond(parseFloat(this.current) -
|
|
2349
|
+
parseFloat(this.prev), this.timeDelta)
|
|
2350
|
+
: 0;
|
|
2578
2351
|
}
|
|
2579
|
-
let interpolator = createInterpolator();
|
|
2580
|
-
return {
|
|
2581
|
-
next: (t) => {
|
|
2582
|
-
state.value = interpolator(t);
|
|
2583
|
-
state.done = t >= duration;
|
|
2584
|
-
return state;
|
|
2585
|
-
},
|
|
2586
|
-
flipTarget: () => {
|
|
2587
|
-
values.reverse();
|
|
2588
|
-
interpolator = createInterpolator();
|
|
2589
|
-
},
|
|
2590
|
-
};
|
|
2591
|
-
}
|
|
2592
|
-
|
|
2593
|
-
const safeMin = 0.001;
|
|
2594
|
-
const minDuration = 0.01;
|
|
2595
|
-
const maxDuration = 10.0;
|
|
2596
|
-
const minDamping = 0.05;
|
|
2597
|
-
const maxDamping = 1;
|
|
2598
|
-
function findSpring({ duration = 800, bounce = 0.25, velocity = 0, mass = 1, }) {
|
|
2599
|
-
let envelope;
|
|
2600
|
-
let derivative;
|
|
2601
|
-
heyListen.warning(duration <= maxDuration * 1000, "Spring duration must be 10 seconds or less");
|
|
2602
|
-
let dampingRatio = 1 - bounce;
|
|
2603
2352
|
/**
|
|
2604
|
-
*
|
|
2353
|
+
* Registers a new animation to control this `MotionValue`. Only one
|
|
2354
|
+
* animation can drive a `MotionValue` at one time.
|
|
2355
|
+
*
|
|
2356
|
+
* ```jsx
|
|
2357
|
+
* value.start()
|
|
2358
|
+
* ```
|
|
2359
|
+
*
|
|
2360
|
+
* @param animation - A function that starts the provided animation
|
|
2361
|
+
*
|
|
2362
|
+
* @internal
|
|
2605
2363
|
*/
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
envelope = (undampedFreq) => {
|
|
2613
|
-
const exponentialDecay = undampedFreq * dampingRatio;
|
|
2614
|
-
const delta = exponentialDecay * duration;
|
|
2615
|
-
const a = exponentialDecay - velocity;
|
|
2616
|
-
const b = calcAngularFreq(undampedFreq, dampingRatio);
|
|
2617
|
-
const c = Math.exp(-delta);
|
|
2618
|
-
return safeMin - (a / b) * c;
|
|
2619
|
-
};
|
|
2620
|
-
derivative = (undampedFreq) => {
|
|
2621
|
-
const exponentialDecay = undampedFreq * dampingRatio;
|
|
2622
|
-
const delta = exponentialDecay * duration;
|
|
2623
|
-
const d = delta * velocity + velocity;
|
|
2624
|
-
const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
|
|
2625
|
-
const f = Math.exp(-delta);
|
|
2626
|
-
const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
|
|
2627
|
-
const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
|
|
2628
|
-
return (factor * ((d - e) * f)) / g;
|
|
2629
|
-
};
|
|
2364
|
+
start(animation) {
|
|
2365
|
+
this.stop();
|
|
2366
|
+
return new Promise((resolve) => {
|
|
2367
|
+
this.hasAnimated = true;
|
|
2368
|
+
this.stopAnimation = animation(resolve);
|
|
2369
|
+
}).then(() => this.clearAnimation());
|
|
2630
2370
|
}
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
derivative = (undampedFreq) => {
|
|
2641
|
-
const a = Math.exp(-undampedFreq * duration);
|
|
2642
|
-
const b = (velocity - undampedFreq) * (duration * duration);
|
|
2643
|
-
return a * b;
|
|
2644
|
-
};
|
|
2371
|
+
/**
|
|
2372
|
+
* Stop the currently active animation.
|
|
2373
|
+
*
|
|
2374
|
+
* @public
|
|
2375
|
+
*/
|
|
2376
|
+
stop() {
|
|
2377
|
+
if (this.stopAnimation)
|
|
2378
|
+
this.stopAnimation();
|
|
2379
|
+
this.clearAnimation();
|
|
2645
2380
|
}
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2381
|
+
/**
|
|
2382
|
+
* Returns `true` if this value is currently animating.
|
|
2383
|
+
*
|
|
2384
|
+
* @public
|
|
2385
|
+
*/
|
|
2386
|
+
isAnimating() {
|
|
2387
|
+
return !!this.stopAnimation;
|
|
2388
|
+
}
|
|
2389
|
+
clearAnimation() {
|
|
2390
|
+
this.stopAnimation = null;
|
|
2391
|
+
}
|
|
2392
|
+
/**
|
|
2393
|
+
* Destroy and clean up subscribers to this `MotionValue`.
|
|
2394
|
+
*
|
|
2395
|
+
* The `MotionValue` hooks like `useMotionValue` and `useTransform` automatically
|
|
2396
|
+
* handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually
|
|
2397
|
+
* created a `MotionValue` via the `motionValue` function.
|
|
2398
|
+
*
|
|
2399
|
+
* @public
|
|
2400
|
+
*/
|
|
2401
|
+
destroy() {
|
|
2402
|
+
this.updateSubscribers.clear();
|
|
2403
|
+
this.renderSubscribers.clear();
|
|
2404
|
+
this.stop();
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
function motionValue(init, options) {
|
|
2408
|
+
return new MotionValue(init, options);
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
/**
|
|
2412
|
+
* Returns true if the provided string is a color, ie rgba(0,0,0,0) or #000,
|
|
2413
|
+
* but false if a number or multiple colors
|
|
2414
|
+
*/
|
|
2415
|
+
const isColorString = (type, testProp) => (v) => {
|
|
2416
|
+
return Boolean((isString(v) && singleColorRegex.test(v) && v.startsWith(type)) ||
|
|
2417
|
+
(testProp && Object.prototype.hasOwnProperty.call(v, testProp)));
|
|
2418
|
+
};
|
|
2419
|
+
const splitColor = (aName, bName, cName) => (v) => {
|
|
2420
|
+
if (!isString(v))
|
|
2421
|
+
return v;
|
|
2422
|
+
const [a, b, c, alpha] = v.match(floatRegex);
|
|
2423
|
+
return {
|
|
2424
|
+
[aName]: parseFloat(a),
|
|
2425
|
+
[bName]: parseFloat(b),
|
|
2426
|
+
[cName]: parseFloat(c),
|
|
2427
|
+
alpha: alpha !== undefined ? parseFloat(alpha) : 1,
|
|
2428
|
+
};
|
|
2429
|
+
};
|
|
2430
|
+
|
|
2431
|
+
const clampRgbUnit = (v) => clamp(0, 255, v);
|
|
2432
|
+
const rgbUnit = {
|
|
2433
|
+
...number,
|
|
2434
|
+
transform: (v) => Math.round(clampRgbUnit(v)),
|
|
2435
|
+
};
|
|
2436
|
+
const rgba = {
|
|
2437
|
+
test: isColorString("rgb", "red"),
|
|
2438
|
+
parse: splitColor("red", "green", "blue"),
|
|
2439
|
+
transform: ({ red, green, blue, alpha: alpha$1 = 1 }) => "rgba(" +
|
|
2440
|
+
rgbUnit.transform(red) +
|
|
2441
|
+
", " +
|
|
2442
|
+
rgbUnit.transform(green) +
|
|
2443
|
+
", " +
|
|
2444
|
+
rgbUnit.transform(blue) +
|
|
2445
|
+
", " +
|
|
2446
|
+
sanitize(alpha.transform(alpha$1)) +
|
|
2447
|
+
")",
|
|
2448
|
+
};
|
|
2449
|
+
|
|
2450
|
+
function parseHex(v) {
|
|
2451
|
+
let r = "";
|
|
2452
|
+
let g = "";
|
|
2453
|
+
let b = "";
|
|
2454
|
+
let a = "";
|
|
2455
|
+
// If we have 6 characters, ie #FF0000
|
|
2456
|
+
if (v.length > 5) {
|
|
2457
|
+
r = v.substring(1, 3);
|
|
2458
|
+
g = v.substring(3, 5);
|
|
2459
|
+
b = v.substring(5, 7);
|
|
2460
|
+
a = v.substring(7, 9);
|
|
2461
|
+
// Or we have 3 characters, ie #F00
|
|
2655
2462
|
}
|
|
2656
2463
|
else {
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
const rootIterations = 12;
|
|
2666
|
-
function approximateRoot(envelope, derivative, initialGuess) {
|
|
2667
|
-
let result = initialGuess;
|
|
2668
|
-
for (let i = 1; i < rootIterations; i++) {
|
|
2669
|
-
result = result - envelope(result) / derivative(result);
|
|
2464
|
+
r = v.substring(1, 2);
|
|
2465
|
+
g = v.substring(2, 3);
|
|
2466
|
+
b = v.substring(3, 4);
|
|
2467
|
+
a = v.substring(4, 5);
|
|
2468
|
+
r += r;
|
|
2469
|
+
g += g;
|
|
2470
|
+
b += b;
|
|
2471
|
+
a += a;
|
|
2670
2472
|
}
|
|
2671
|
-
return
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2473
|
+
return {
|
|
2474
|
+
red: parseInt(r, 16),
|
|
2475
|
+
green: parseInt(g, 16),
|
|
2476
|
+
blue: parseInt(b, 16),
|
|
2477
|
+
alpha: a ? parseInt(a, 16) / 255 : 1,
|
|
2478
|
+
};
|
|
2675
2479
|
}
|
|
2480
|
+
const hex = {
|
|
2481
|
+
test: isColorString("#"),
|
|
2482
|
+
parse: parseHex,
|
|
2483
|
+
transform: rgba.transform,
|
|
2484
|
+
};
|
|
2676
2485
|
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2486
|
+
const hsla = {
|
|
2487
|
+
test: isColorString("hsl", "hue"),
|
|
2488
|
+
parse: splitColor("hue", "saturation", "lightness"),
|
|
2489
|
+
transform: ({ hue, saturation, lightness, alpha: alpha$1 = 1 }) => {
|
|
2490
|
+
return ("hsla(" +
|
|
2491
|
+
Math.round(hue) +
|
|
2492
|
+
", " +
|
|
2493
|
+
percent.transform(sanitize(saturation)) +
|
|
2494
|
+
", " +
|
|
2495
|
+
percent.transform(sanitize(lightness)) +
|
|
2496
|
+
", " +
|
|
2497
|
+
sanitize(alpha.transform(alpha$1)) +
|
|
2498
|
+
")");
|
|
2499
|
+
},
|
|
2500
|
+
};
|
|
2686
2501
|
|
|
2687
|
-
const
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
function getSpringOptions(options) {
|
|
2693
|
-
let springOptions = {
|
|
2694
|
-
velocity: 0.0,
|
|
2695
|
-
stiffness: 100,
|
|
2696
|
-
damping: 10,
|
|
2697
|
-
mass: 1.0,
|
|
2698
|
-
isResolvedFromDuration: false,
|
|
2699
|
-
...options,
|
|
2700
|
-
};
|
|
2701
|
-
// stiffness/damping/mass overrides duration/bounce
|
|
2702
|
-
if (!isSpringType(options, physicsKeys) &&
|
|
2703
|
-
isSpringType(options, durationKeys)) {
|
|
2704
|
-
const derived = findSpring(options);
|
|
2705
|
-
springOptions = {
|
|
2706
|
-
...springOptions,
|
|
2707
|
-
...derived,
|
|
2708
|
-
velocity: 0.0,
|
|
2709
|
-
mass: 1.0,
|
|
2710
|
-
};
|
|
2711
|
-
springOptions.isResolvedFromDuration = true;
|
|
2712
|
-
}
|
|
2713
|
-
return springOptions;
|
|
2714
|
-
}
|
|
2715
|
-
const velocitySampleDuration = 5;
|
|
2716
|
-
/**
|
|
2717
|
-
* This is based on the spring implementation of Wobble https://github.com/skevy/wobble
|
|
2718
|
-
*/
|
|
2719
|
-
function spring({ from = 0.0, to = 1.0, restSpeed = 2, restDelta = 0.01, ...options }) {
|
|
2720
|
-
/**
|
|
2721
|
-
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
2722
|
-
* to reduce GC during animation.
|
|
2723
|
-
*/
|
|
2724
|
-
const state = { done: false, value: from };
|
|
2725
|
-
let { stiffness, damping, mass, velocity, duration, isResolvedFromDuration, } = getSpringOptions(options);
|
|
2726
|
-
let resolveSpring = zero;
|
|
2727
|
-
let initialVelocity = velocity ? -(velocity / 1000) : 0.0;
|
|
2728
|
-
const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
|
|
2729
|
-
function createSpring() {
|
|
2730
|
-
const initialDelta = to - from;
|
|
2731
|
-
const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
|
|
2732
|
-
/**
|
|
2733
|
-
* If we're working within what looks like a 0-1 range, change the default restDelta
|
|
2734
|
-
* to 0.01
|
|
2735
|
-
*/
|
|
2736
|
-
if (restDelta === undefined) {
|
|
2737
|
-
restDelta = Math.min(Math.abs(to - from) / 100, 0.4);
|
|
2738
|
-
}
|
|
2739
|
-
if (dampingRatio < 1) {
|
|
2740
|
-
const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
|
|
2741
|
-
// Underdamped spring
|
|
2742
|
-
resolveSpring = (t) => {
|
|
2743
|
-
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
2744
|
-
return (to -
|
|
2745
|
-
envelope *
|
|
2746
|
-
(((initialVelocity +
|
|
2747
|
-
dampingRatio * undampedAngularFreq * initialDelta) /
|
|
2748
|
-
angularFreq) *
|
|
2749
|
-
Math.sin(angularFreq * t) +
|
|
2750
|
-
initialDelta * Math.cos(angularFreq * t)));
|
|
2751
|
-
};
|
|
2502
|
+
const color = {
|
|
2503
|
+
test: (v) => rgba.test(v) || hex.test(v) || hsla.test(v),
|
|
2504
|
+
parse: (v) => {
|
|
2505
|
+
if (rgba.test(v)) {
|
|
2506
|
+
return rgba.parse(v);
|
|
2752
2507
|
}
|
|
2753
|
-
else if (
|
|
2754
|
-
|
|
2755
|
-
resolveSpring = (t) => to -
|
|
2756
|
-
Math.exp(-undampedAngularFreq * t) *
|
|
2757
|
-
(initialDelta +
|
|
2758
|
-
(initialVelocity + undampedAngularFreq * initialDelta) *
|
|
2759
|
-
t);
|
|
2508
|
+
else if (hsla.test(v)) {
|
|
2509
|
+
return hsla.parse(v);
|
|
2760
2510
|
}
|
|
2761
2511
|
else {
|
|
2762
|
-
|
|
2763
|
-
const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
|
|
2764
|
-
resolveSpring = (t) => {
|
|
2765
|
-
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
2766
|
-
// When performing sinh or cosh values can hit Infinity so we cap them here
|
|
2767
|
-
const freqForT = Math.min(dampedAngularFreq * t, 300);
|
|
2768
|
-
return (to -
|
|
2769
|
-
(envelope *
|
|
2770
|
-
((initialVelocity +
|
|
2771
|
-
dampingRatio * undampedAngularFreq * initialDelta) *
|
|
2772
|
-
Math.sinh(freqForT) +
|
|
2773
|
-
dampedAngularFreq *
|
|
2774
|
-
initialDelta *
|
|
2775
|
-
Math.cosh(freqForT))) /
|
|
2776
|
-
dampedAngularFreq);
|
|
2777
|
-
};
|
|
2512
|
+
return hex.parse(v);
|
|
2778
2513
|
}
|
|
2514
|
+
},
|
|
2515
|
+
transform: (v) => {
|
|
2516
|
+
return isString(v)
|
|
2517
|
+
? v
|
|
2518
|
+
: v.hasOwnProperty("red")
|
|
2519
|
+
? rgba.transform(v)
|
|
2520
|
+
: hsla.transform(v);
|
|
2521
|
+
},
|
|
2522
|
+
};
|
|
2523
|
+
|
|
2524
|
+
const colorToken = "${c}";
|
|
2525
|
+
const numberToken = "${n}";
|
|
2526
|
+
function test(v) {
|
|
2527
|
+
var _a, _b;
|
|
2528
|
+
return (isNaN(v) &&
|
|
2529
|
+
isString(v) &&
|
|
2530
|
+
(((_a = v.match(floatRegex)) === null || _a === void 0 ? void 0 : _a.length) || 0) +
|
|
2531
|
+
(((_b = v.match(colorRegex)) === null || _b === void 0 ? void 0 : _b.length) || 0) >
|
|
2532
|
+
0);
|
|
2533
|
+
}
|
|
2534
|
+
function analyseComplexValue(v) {
|
|
2535
|
+
if (typeof v === "number")
|
|
2536
|
+
v = `${v}`;
|
|
2537
|
+
const values = [];
|
|
2538
|
+
let numColors = 0;
|
|
2539
|
+
let numNumbers = 0;
|
|
2540
|
+
const colors = v.match(colorRegex);
|
|
2541
|
+
if (colors) {
|
|
2542
|
+
numColors = colors.length;
|
|
2543
|
+
// Strip colors from input so they're not picked up by number regex.
|
|
2544
|
+
// There's a better way to combine these regex searches, but its beyond my regex skills
|
|
2545
|
+
v = v.replace(colorRegex, colorToken);
|
|
2546
|
+
values.push(...colors.map(color.parse));
|
|
2779
2547
|
}
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
/**
|
|
2788
|
-
* We only need to calculate velocity for under-damped springs
|
|
2789
|
-
* as over- and critically-damped springs can't overshoot, so
|
|
2790
|
-
* checking only for displacement is enough.
|
|
2791
|
-
*/
|
|
2792
|
-
if (dampingRatio < 1) {
|
|
2793
|
-
const prevT = Math.max(0, t - velocitySampleDuration);
|
|
2794
|
-
currentVelocity = velocityPerSecond(current - resolveSpring(prevT), t - prevT);
|
|
2795
|
-
}
|
|
2796
|
-
else {
|
|
2797
|
-
currentVelocity = 0;
|
|
2798
|
-
}
|
|
2799
|
-
}
|
|
2800
|
-
const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed;
|
|
2801
|
-
const isBelowDisplacementThreshold = Math.abs(to - current) <= restDelta;
|
|
2802
|
-
state.done =
|
|
2803
|
-
isBelowVelocityThreshold && isBelowDisplacementThreshold;
|
|
2804
|
-
}
|
|
2805
|
-
else {
|
|
2806
|
-
state.done = t >= duration;
|
|
2807
|
-
}
|
|
2808
|
-
state.value = state.done ? to : current;
|
|
2809
|
-
return state;
|
|
2810
|
-
},
|
|
2811
|
-
flipTarget: () => {
|
|
2812
|
-
initialVelocity = -initialVelocity;
|
|
2813
|
-
[from, to] = [to, from];
|
|
2814
|
-
createSpring();
|
|
2815
|
-
},
|
|
2816
|
-
};
|
|
2548
|
+
const numbers = v.match(floatRegex);
|
|
2549
|
+
if (numbers) {
|
|
2550
|
+
numNumbers = numbers.length;
|
|
2551
|
+
v = v.replace(floatRegex, numberToken);
|
|
2552
|
+
values.push(...numbers.map(number.parse));
|
|
2553
|
+
}
|
|
2554
|
+
return { values, numColors, numNumbers, tokenised: v };
|
|
2817
2555
|
}
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
function
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
* the animation will start from the wrong position.
|
|
2833
|
-
*/
|
|
2834
|
-
if (target !== ideal)
|
|
2835
|
-
amplitude = target - from;
|
|
2836
|
-
return {
|
|
2837
|
-
next: (t) => {
|
|
2838
|
-
const delta = -amplitude * Math.exp(-t / timeConstant);
|
|
2839
|
-
state.done = !(delta > restDelta || delta < -restDelta);
|
|
2840
|
-
state.value = state.done ? target : target + delta;
|
|
2841
|
-
return state;
|
|
2842
|
-
},
|
|
2843
|
-
flipTarget: () => { },
|
|
2556
|
+
function parse(v) {
|
|
2557
|
+
return analyseComplexValue(v).values;
|
|
2558
|
+
}
|
|
2559
|
+
function createTransformer(source) {
|
|
2560
|
+
const { values, numColors, tokenised } = analyseComplexValue(source);
|
|
2561
|
+
const numValues = values.length;
|
|
2562
|
+
return (v) => {
|
|
2563
|
+
let output = tokenised;
|
|
2564
|
+
for (let i = 0; i < numValues; i++) {
|
|
2565
|
+
output = output.replace(i < numColors ? colorToken : numberToken, i < numColors
|
|
2566
|
+
? color.transform(v[i])
|
|
2567
|
+
: sanitize(v[i]));
|
|
2568
|
+
}
|
|
2569
|
+
return output;
|
|
2844
2570
|
};
|
|
2845
2571
|
}
|
|
2572
|
+
const convertNumbersToZero = (v) => typeof v === "number" ? 0 : v;
|
|
2573
|
+
function getAnimatableNone$1(v) {
|
|
2574
|
+
const parsed = parse(v);
|
|
2575
|
+
const transformer = createTransformer(v);
|
|
2576
|
+
return transformer(parsed.map(convertNumbersToZero));
|
|
2577
|
+
}
|
|
2578
|
+
const complex = { test, parse, createTransformer, getAnimatableNone: getAnimatableNone$1 };
|
|
2846
2579
|
|
|
2847
|
-
|
|
2848
|
-
|
|
2580
|
+
/**
|
|
2581
|
+
* Properties that should default to 1 or 100%
|
|
2849
2582
|
*/
|
|
2850
|
-
const
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
let toRun = [];
|
|
2864
|
-
let toRunNextFrame = [];
|
|
2865
|
-
/**
|
|
2866
|
-
*
|
|
2867
|
-
*/
|
|
2868
|
-
let numToRun = 0;
|
|
2869
|
-
/**
|
|
2870
|
-
* Track whether we're currently processing jobs in this step. This way
|
|
2871
|
-
* we can decide whether to schedule new jobs for this frame or next.
|
|
2872
|
-
*/
|
|
2873
|
-
let isProcessing = false;
|
|
2874
|
-
let flushNextFrame = false;
|
|
2875
|
-
/**
|
|
2876
|
-
* A set of processes which were marked keepAlive when scheduled.
|
|
2877
|
-
*/
|
|
2878
|
-
const toKeepAlive = new WeakSet();
|
|
2879
|
-
const step = {
|
|
2880
|
-
/**
|
|
2881
|
-
* Schedule a process to run on the next frame.
|
|
2882
|
-
*/
|
|
2883
|
-
schedule: (callback, keepAlive = false, immediate = false) => {
|
|
2884
|
-
const addToCurrentFrame = immediate && isProcessing;
|
|
2885
|
-
const buffer = addToCurrentFrame ? toRun : toRunNextFrame;
|
|
2886
|
-
if (keepAlive)
|
|
2887
|
-
toKeepAlive.add(callback);
|
|
2888
|
-
// If the buffer doesn't already contain this callback, add it
|
|
2889
|
-
if (buffer.indexOf(callback) === -1) {
|
|
2890
|
-
buffer.push(callback);
|
|
2891
|
-
// If we're adding it to the currently running buffer, update its measured size
|
|
2892
|
-
if (addToCurrentFrame && isProcessing)
|
|
2893
|
-
numToRun = toRun.length;
|
|
2894
|
-
}
|
|
2895
|
-
return callback;
|
|
2896
|
-
},
|
|
2897
|
-
/**
|
|
2898
|
-
* Cancel the provided callback from running on the next frame.
|
|
2899
|
-
*/
|
|
2900
|
-
cancel: (callback) => {
|
|
2901
|
-
const index = toRunNextFrame.indexOf(callback);
|
|
2902
|
-
if (index !== -1)
|
|
2903
|
-
toRunNextFrame.splice(index, 1);
|
|
2904
|
-
toKeepAlive.delete(callback);
|
|
2905
|
-
},
|
|
2906
|
-
/**
|
|
2907
|
-
* Execute all schedule callbacks.
|
|
2908
|
-
*/
|
|
2909
|
-
process: (frameData) => {
|
|
2910
|
-
/**
|
|
2911
|
-
* If we're already processing we've probably been triggered by a flushSync
|
|
2912
|
-
* inside an existing process. Instead of executing, mark flushNextFrame
|
|
2913
|
-
* as true and ensure we flush the following frame at the end of this one.
|
|
2914
|
-
*/
|
|
2915
|
-
if (isProcessing) {
|
|
2916
|
-
flushNextFrame = true;
|
|
2917
|
-
return;
|
|
2918
|
-
}
|
|
2919
|
-
isProcessing = true;
|
|
2920
|
-
[toRun, toRunNextFrame] = [toRunNextFrame, toRun];
|
|
2921
|
-
// Clear the next frame list
|
|
2922
|
-
toRunNextFrame.length = 0;
|
|
2923
|
-
// Execute this frame
|
|
2924
|
-
numToRun = toRun.length;
|
|
2925
|
-
if (numToRun) {
|
|
2926
|
-
for (let i = 0; i < numToRun; i++) {
|
|
2927
|
-
const callback = toRun[i];
|
|
2928
|
-
callback(frameData);
|
|
2929
|
-
if (toKeepAlive.has(callback)) {
|
|
2930
|
-
step.schedule(callback);
|
|
2931
|
-
runNextFrame();
|
|
2932
|
-
}
|
|
2933
|
-
}
|
|
2934
|
-
}
|
|
2935
|
-
isProcessing = false;
|
|
2936
|
-
if (flushNextFrame) {
|
|
2937
|
-
flushNextFrame = false;
|
|
2938
|
-
step.process(frameData);
|
|
2939
|
-
}
|
|
2940
|
-
},
|
|
2941
|
-
};
|
|
2942
|
-
return step;
|
|
2583
|
+
const maxDefaults = new Set(["brightness", "contrast", "saturate", "opacity"]);
|
|
2584
|
+
function applyDefaultFilter(v) {
|
|
2585
|
+
const [name, value] = v.slice(0, -1).split("(");
|
|
2586
|
+
if (name === "drop-shadow")
|
|
2587
|
+
return v;
|
|
2588
|
+
const [number] = value.match(floatRegex) || [];
|
|
2589
|
+
if (!number)
|
|
2590
|
+
return v;
|
|
2591
|
+
const unit = value.replace(number, "");
|
|
2592
|
+
let defaultValue = maxDefaults.has(name) ? 1 : 0;
|
|
2593
|
+
if (number !== value)
|
|
2594
|
+
defaultValue *= 100;
|
|
2595
|
+
return name + "(" + defaultValue + unit + ")";
|
|
2943
2596
|
}
|
|
2944
|
-
|
|
2945
|
-
const
|
|
2946
|
-
|
|
2947
|
-
|
|
2597
|
+
const functionRegex = /([a-z-]*)\(.*?\)/g;
|
|
2598
|
+
const filter = {
|
|
2599
|
+
...complex,
|
|
2600
|
+
getAnimatableNone: (v) => {
|
|
2601
|
+
const functions = v.match(functionRegex);
|
|
2602
|
+
return functions ? functions.map(applyDefaultFilter).join(" ") : v;
|
|
2603
|
+
},
|
|
2948
2604
|
};
|
|
2949
2605
|
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
startLoop();
|
|
2970
|
-
return step.schedule(process, keepAlive, immediate);
|
|
2971
|
-
};
|
|
2972
|
-
return acc;
|
|
2973
|
-
}, {});
|
|
2974
|
-
const cancelSync = stepsOrder.reduce((acc, key) => {
|
|
2975
|
-
acc[key] = steps[key].cancel;
|
|
2976
|
-
return acc;
|
|
2977
|
-
}, {});
|
|
2978
|
-
const flushSync = stepsOrder.reduce((acc, key) => {
|
|
2979
|
-
acc[key] = () => steps[key].process(frameData);
|
|
2980
|
-
return acc;
|
|
2981
|
-
}, {});
|
|
2982
|
-
const processStep = (stepId) => steps[stepId].process(frameData);
|
|
2983
|
-
const processFrame = (timestamp) => {
|
|
2984
|
-
runNextFrame = false;
|
|
2985
|
-
frameData.delta = useDefaultElapsed
|
|
2986
|
-
? defaultTimestep
|
|
2987
|
-
: Math.max(Math.min(timestamp - frameData.timestamp, maxElapsed), 1);
|
|
2988
|
-
frameData.timestamp = timestamp;
|
|
2989
|
-
isProcessing = true;
|
|
2990
|
-
stepsOrder.forEach(processStep);
|
|
2991
|
-
isProcessing = false;
|
|
2992
|
-
if (runNextFrame) {
|
|
2993
|
-
useDefaultElapsed = false;
|
|
2994
|
-
onNextFrame(processFrame);
|
|
2995
|
-
}
|
|
2606
|
+
/**
|
|
2607
|
+
* A map of default value types for common values
|
|
2608
|
+
*/
|
|
2609
|
+
const defaultValueTypes = {
|
|
2610
|
+
...numberValueTypes,
|
|
2611
|
+
// Color props
|
|
2612
|
+
color,
|
|
2613
|
+
backgroundColor: color,
|
|
2614
|
+
outlineColor: color,
|
|
2615
|
+
fill: color,
|
|
2616
|
+
stroke: color,
|
|
2617
|
+
// Border props
|
|
2618
|
+
borderColor: color,
|
|
2619
|
+
borderTopColor: color,
|
|
2620
|
+
borderRightColor: color,
|
|
2621
|
+
borderBottomColor: color,
|
|
2622
|
+
borderLeftColor: color,
|
|
2623
|
+
filter,
|
|
2624
|
+
WebkitFilter: filter,
|
|
2996
2625
|
};
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
2626
|
+
/**
|
|
2627
|
+
* Gets the default ValueType for the provided value key
|
|
2628
|
+
*/
|
|
2629
|
+
const getDefaultValueType = (key) => defaultValueTypes[key];
|
|
2630
|
+
|
|
2631
|
+
function getAnimatableNone(key, value) {
|
|
2632
|
+
var _a;
|
|
2633
|
+
let defaultValueType = getDefaultValueType(key);
|
|
2634
|
+
if (defaultValueType !== filter)
|
|
2635
|
+
defaultValueType = complex;
|
|
2636
|
+
// If value is not recognised as animatable, ie "none", create an animatable version origin based on the target
|
|
2637
|
+
return (_a = defaultValueType.getAnimatableNone) === null || _a === void 0 ? void 0 : _a.call(defaultValueType, value);
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
/**
|
|
2641
|
+
* Tests a provided value against a ValueType
|
|
2642
|
+
*/
|
|
2643
|
+
const testValueType = (v) => (type) => type.test(v);
|
|
2644
|
+
|
|
2645
|
+
/**
|
|
2646
|
+
* ValueType for "auto"
|
|
2647
|
+
*/
|
|
2648
|
+
const auto = {
|
|
2649
|
+
test: (v) => v === "auto",
|
|
2650
|
+
parse: (v) => v,
|
|
3002
2651
|
};
|
|
3003
2652
|
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
2653
|
+
/**
|
|
2654
|
+
* A list of value types commonly used for dimensions
|
|
2655
|
+
*/
|
|
2656
|
+
const dimensionValueTypes = [number, px, percent, degrees, vw, vh, auto];
|
|
2657
|
+
/**
|
|
2658
|
+
* Tests a dimensional value against the list of dimension ValueTypes
|
|
2659
|
+
*/
|
|
2660
|
+
const findDimensionValueType = (v) => dimensionValueTypes.find(testValueType(v));
|
|
2661
|
+
|
|
2662
|
+
/**
|
|
2663
|
+
* A list of all ValueTypes
|
|
2664
|
+
*/
|
|
2665
|
+
const valueTypes = [...dimensionValueTypes, color, complex];
|
|
2666
|
+
/**
|
|
2667
|
+
* Tests a value against the list of ValueTypes
|
|
2668
|
+
*/
|
|
2669
|
+
const findValueType = (v) => valueTypes.find(testValueType(v));
|
|
2670
|
+
|
|
2671
|
+
/**
|
|
2672
|
+
* Creates an object containing the latest state of every MotionValue on a VisualElement
|
|
2673
|
+
*/
|
|
2674
|
+
function getCurrent(visualElement) {
|
|
2675
|
+
const current = {};
|
|
2676
|
+
visualElement.values.forEach((value, key) => (current[key] = value.get()));
|
|
2677
|
+
return current;
|
|
3007
2678
|
}
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
2679
|
+
/**
|
|
2680
|
+
* Creates an object containing the latest velocity of every MotionValue on a VisualElement
|
|
2681
|
+
*/
|
|
2682
|
+
function getVelocity$1(visualElement) {
|
|
2683
|
+
const velocity = {};
|
|
2684
|
+
visualElement.values.forEach((value, key) => (velocity[key] = value.getVelocity()));
|
|
2685
|
+
return velocity;
|
|
3012
2686
|
}
|
|
3013
|
-
function
|
|
3014
|
-
|
|
2687
|
+
function resolveVariant(visualElement, definition, custom) {
|
|
2688
|
+
const props = visualElement.getProps();
|
|
2689
|
+
return resolveVariantFromProps(props, definition, custom !== undefined ? custom : props.custom, getCurrent(visualElement), getVelocity$1(visualElement));
|
|
3015
2690
|
}
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
let
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
2691
|
+
|
|
2692
|
+
/**
|
|
2693
|
+
* Set VisualElement's MotionValue, creating a new MotionValue for it if
|
|
2694
|
+
* it doesn't exist.
|
|
2695
|
+
*/
|
|
2696
|
+
function setMotionValue(visualElement, key, value) {
|
|
2697
|
+
if (visualElement.hasValue(key)) {
|
|
2698
|
+
visualElement.getValue(key).set(value);
|
|
2699
|
+
}
|
|
2700
|
+
else {
|
|
2701
|
+
visualElement.addValue(key, motionValue(value));
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
function setTarget(visualElement, definition) {
|
|
2705
|
+
const resolved = resolveVariant(visualElement, definition);
|
|
2706
|
+
let { transitionEnd = {}, transition = {}, ...target } = resolved ? visualElement.makeTargetAnimatable(resolved, false) : {};
|
|
2707
|
+
target = { ...target, ...transitionEnd };
|
|
2708
|
+
for (const key in target) {
|
|
2709
|
+
const value = resolveFinalValueInKeyframes(target[key]);
|
|
2710
|
+
setMotionValue(visualElement, key, value);
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
function setVariants(visualElement, variantLabels) {
|
|
2714
|
+
const reversedLabels = [...variantLabels].reverse();
|
|
2715
|
+
reversedLabels.forEach((key) => {
|
|
2716
|
+
var _a;
|
|
2717
|
+
const variant = visualElement.getVariant(key);
|
|
2718
|
+
variant && setTarget(visualElement, variant);
|
|
2719
|
+
(_a = visualElement.variantChildren) === null || _a === void 0 ? void 0 : _a.forEach((child) => {
|
|
2720
|
+
setVariants(child, variantLabels);
|
|
3038
2721
|
});
|
|
3039
|
-
|
|
3040
|
-
|
|
2722
|
+
});
|
|
2723
|
+
}
|
|
2724
|
+
function setValues(visualElement, definition) {
|
|
2725
|
+
if (Array.isArray(definition)) {
|
|
2726
|
+
return setVariants(visualElement, definition);
|
|
3041
2727
|
}
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
2728
|
+
else if (typeof definition === "string") {
|
|
2729
|
+
return setVariants(visualElement, [definition]);
|
|
2730
|
+
}
|
|
2731
|
+
else {
|
|
2732
|
+
setTarget(visualElement, definition);
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
function checkTargetForNewValues(visualElement, target, origin) {
|
|
2736
|
+
var _a, _b;
|
|
2737
|
+
const newValueKeys = Object.keys(target).filter((key) => !visualElement.hasValue(key));
|
|
2738
|
+
const numNewValues = newValueKeys.length;
|
|
2739
|
+
if (!numNewValues)
|
|
2740
|
+
return;
|
|
2741
|
+
for (let i = 0; i < numNewValues; i++) {
|
|
2742
|
+
const key = newValueKeys[i];
|
|
2743
|
+
const targetValue = target[key];
|
|
2744
|
+
let value = null;
|
|
2745
|
+
/**
|
|
2746
|
+
* If the target is a series of keyframes, we can use the first value
|
|
2747
|
+
* in the array. If this first value is null, we'll still need to read from the DOM.
|
|
2748
|
+
*/
|
|
2749
|
+
if (Array.isArray(targetValue)) {
|
|
2750
|
+
value = targetValue[0];
|
|
3048
2751
|
}
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
2752
|
+
/**
|
|
2753
|
+
* If the target isn't keyframes, or the first keyframe was null, we need to
|
|
2754
|
+
* first check if an origin value was explicitly defined in the transition as "from",
|
|
2755
|
+
* if not read the value from the DOM. As an absolute fallback, take the defined target value.
|
|
2756
|
+
*/
|
|
2757
|
+
if (value === null) {
|
|
2758
|
+
value = (_b = (_a = origin[key]) !== null && _a !== void 0 ? _a : visualElement.readValue(key)) !== null && _b !== void 0 ? _b : target[key];
|
|
3053
2759
|
}
|
|
3054
|
-
|
|
3055
|
-
|
|
2760
|
+
/**
|
|
2761
|
+
* If value is still undefined or null, ignore it. Preferably this would throw,
|
|
2762
|
+
* but this was causing issues in Framer.
|
|
2763
|
+
*/
|
|
2764
|
+
if (value === undefined || value === null)
|
|
2765
|
+
continue;
|
|
2766
|
+
if (typeof value === "string" &&
|
|
2767
|
+
(isNumericalString(value) || isZeroValueString(value))) {
|
|
2768
|
+
// If this is a number read as a string, ie "0" or "200", convert it to a number
|
|
2769
|
+
value = parseFloat(value);
|
|
2770
|
+
}
|
|
2771
|
+
else if (!findValueType(value) && complex.test(targetValue)) {
|
|
2772
|
+
value = getAnimatableNone(key, targetValue);
|
|
2773
|
+
}
|
|
2774
|
+
visualElement.addValue(key, motionValue(value, { owner: visualElement }));
|
|
2775
|
+
if (origin[key] === undefined) {
|
|
2776
|
+
origin[key] = value;
|
|
2777
|
+
}
|
|
2778
|
+
if (value !== null)
|
|
2779
|
+
visualElement.setBaseTarget(key, value);
|
|
3056
2780
|
}
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
2781
|
+
}
|
|
2782
|
+
function getOriginFromTransition(key, transition) {
|
|
2783
|
+
if (!transition)
|
|
2784
|
+
return;
|
|
2785
|
+
const valueTransition = transition[key] || transition["default"] || transition;
|
|
2786
|
+
return valueTransition.from;
|
|
2787
|
+
}
|
|
2788
|
+
function getOrigin(target, transition, visualElement) {
|
|
2789
|
+
var _a;
|
|
2790
|
+
const origin = {};
|
|
2791
|
+
for (const key in target) {
|
|
2792
|
+
const transitionOrigin = getOriginFromTransition(key, transition);
|
|
2793
|
+
origin[key] =
|
|
2794
|
+
transitionOrigin !== undefined
|
|
2795
|
+
? transitionOrigin
|
|
2796
|
+
: (_a = visualElement.getValue(key)) === null || _a === void 0 ? void 0 : _a.get();
|
|
3060
2797
|
}
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
2798
|
+
return origin;
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
function isWillChangeMotionValue(value) {
|
|
2802
|
+
return Boolean(isMotionValue(value) && value.add);
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2805
|
+
const appearStoreId = (id, value) => `${id}: ${value}`;
|
|
2806
|
+
|
|
2807
|
+
function handoffOptimizedAppearAnimation(id, name) {
|
|
2808
|
+
const { MotionAppearAnimations } = window;
|
|
2809
|
+
const animationId = appearStoreId(id, transformProps.has(name) ? "transform" : name);
|
|
2810
|
+
const animation = MotionAppearAnimations && MotionAppearAnimations.get(animationId);
|
|
2811
|
+
if (animation) {
|
|
2812
|
+
/**
|
|
2813
|
+
* We allow the animation to persist until the next frame:
|
|
2814
|
+
* 1. So it continues to play until Framer Motion is ready to render
|
|
2815
|
+
* (avoiding a potential flash of the element's original state)
|
|
2816
|
+
* 2. As all independent transforms share a single transform animation, stopping
|
|
2817
|
+
* it synchronously would prevent subsequent transforms from handing off.
|
|
2818
|
+
*/
|
|
2819
|
+
sync.render(() => {
|
|
2820
|
+
/**
|
|
2821
|
+
* Animation.cancel() throws so it needs to be wrapped in a try/catch
|
|
2822
|
+
*/
|
|
2823
|
+
try {
|
|
2824
|
+
animation.cancel();
|
|
2825
|
+
MotionAppearAnimations.delete(animationId);
|
|
3083
2826
|
}
|
|
3084
|
-
|
|
2827
|
+
catch (e) { }
|
|
2828
|
+
});
|
|
2829
|
+
return animation.currentTime || 0;
|
|
2830
|
+
}
|
|
2831
|
+
else {
|
|
2832
|
+
return 0;
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
const optimizedAppearDataId = "framerAppearId";
|
|
2837
|
+
const optimizedAppearDataAttribute = "data-" + camelToDash(optimizedAppearDataId);
|
|
2838
|
+
|
|
2839
|
+
/**
|
|
2840
|
+
* Converts seconds to milliseconds
|
|
2841
|
+
*
|
|
2842
|
+
* @param seconds - Time in seconds.
|
|
2843
|
+
* @return milliseconds - Converted time in milliseconds.
|
|
2844
|
+
*/
|
|
2845
|
+
const secondsToMilliseconds = (seconds) => seconds * 1000;
|
|
2846
|
+
|
|
2847
|
+
const instantAnimationState = {
|
|
2848
|
+
current: false,
|
|
2849
|
+
};
|
|
2850
|
+
|
|
2851
|
+
// Accepts an easing function and returns a new one that outputs mirrored values for
|
|
2852
|
+
// the second half of the animation. Turns easeIn into easeInOut.
|
|
2853
|
+
const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
|
|
2854
|
+
|
|
2855
|
+
// Accepts an easing function and returns a new one that outputs reversed values.
|
|
2856
|
+
// Turns easeIn into easeOut.
|
|
2857
|
+
const reverseEasing = (easing) => (p) => 1 - easing(1 - p);
|
|
2858
|
+
|
|
2859
|
+
const easeIn = (p) => p * p;
|
|
2860
|
+
const easeOut = reverseEasing(easeIn);
|
|
2861
|
+
const easeInOut = mirrorEasing(easeIn);
|
|
2862
|
+
|
|
2863
|
+
/*
|
|
2864
|
+
Value in range from progress
|
|
2865
|
+
|
|
2866
|
+
Given a lower limit and an upper limit, we return the value within
|
|
2867
|
+
that range as expressed by progress (usually a number from 0 to 1)
|
|
2868
|
+
|
|
2869
|
+
So progress = 0.5 would change
|
|
2870
|
+
|
|
2871
|
+
from -------- to
|
|
2872
|
+
|
|
2873
|
+
to
|
|
2874
|
+
|
|
2875
|
+
from ---- to
|
|
2876
|
+
|
|
2877
|
+
E.g. from = 10, to = 20, progress = 0.5 => 15
|
|
2878
|
+
|
|
2879
|
+
@param [number]: Lower limit of range
|
|
2880
|
+
@param [number]: Upper limit of range
|
|
2881
|
+
@param [number]: The progress between lower and upper limits expressed 0-1
|
|
2882
|
+
@return [number]: Value as calculated from progress within range (not limited within range)
|
|
2883
|
+
*/
|
|
2884
|
+
const mix = (from, to, progress) => -progress * from + progress * to + from;
|
|
2885
|
+
|
|
2886
|
+
// Adapted from https://gist.github.com/mjackson/5311256
|
|
2887
|
+
function hueToRgb(p, q, t) {
|
|
2888
|
+
if (t < 0)
|
|
2889
|
+
t += 1;
|
|
2890
|
+
if (t > 1)
|
|
2891
|
+
t -= 1;
|
|
2892
|
+
if (t < 1 / 6)
|
|
2893
|
+
return p + (q - p) * 6 * t;
|
|
2894
|
+
if (t < 1 / 2)
|
|
2895
|
+
return q;
|
|
2896
|
+
if (t < 2 / 3)
|
|
2897
|
+
return p + (q - p) * (2 / 3 - t) * 6;
|
|
2898
|
+
return p;
|
|
2899
|
+
}
|
|
2900
|
+
function hslaToRgba({ hue, saturation, lightness, alpha }) {
|
|
2901
|
+
hue /= 360;
|
|
2902
|
+
saturation /= 100;
|
|
2903
|
+
lightness /= 100;
|
|
2904
|
+
let red = 0;
|
|
2905
|
+
let green = 0;
|
|
2906
|
+
let blue = 0;
|
|
2907
|
+
if (!saturation) {
|
|
2908
|
+
red = green = blue = lightness;
|
|
3085
2909
|
}
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
2910
|
+
else {
|
|
2911
|
+
const q = lightness < 0.5
|
|
2912
|
+
? lightness * (1 + saturation)
|
|
2913
|
+
: lightness + saturation - lightness * saturation;
|
|
2914
|
+
const p = 2 * lightness - q;
|
|
2915
|
+
red = hueToRgb(p, q, hue + 1 / 3);
|
|
2916
|
+
green = hueToRgb(p, q, hue);
|
|
2917
|
+
blue = hueToRgb(p, q, hue - 1 / 3);
|
|
3090
2918
|
}
|
|
3091
|
-
autoplay && play();
|
|
3092
2919
|
return {
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
2920
|
+
red: Math.round(red * 255),
|
|
2921
|
+
green: Math.round(green * 255),
|
|
2922
|
+
blue: Math.round(blue * 255),
|
|
2923
|
+
alpha,
|
|
3097
2924
|
};
|
|
3098
2925
|
}
|
|
3099
2926
|
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
}
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
driver,
|
|
3117
|
-
onUpdate: (v) => {
|
|
3118
|
-
var _a;
|
|
3119
|
-
onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate(v);
|
|
3120
|
-
(_a = options.onUpdate) === null || _a === void 0 ? void 0 : _a.call(options, v);
|
|
3121
|
-
},
|
|
3122
|
-
onComplete,
|
|
3123
|
-
onStop,
|
|
3124
|
-
});
|
|
2927
|
+
// Linear color space blending
|
|
2928
|
+
// Explained https://www.youtube.com/watch?v=LKnqECcg6Gw
|
|
2929
|
+
// Demonstrated http://codepen.io/osublake/pen/xGVVaN
|
|
2930
|
+
const mixLinearColor = (from, to, v) => {
|
|
2931
|
+
const fromExpo = from * from;
|
|
2932
|
+
return Math.sqrt(Math.max(0, v * (to * to - fromExpo) + fromExpo));
|
|
2933
|
+
};
|
|
2934
|
+
const colorTypes = [hex, rgba, hsla];
|
|
2935
|
+
const getColorType = (v) => colorTypes.find((type) => type.test(v));
|
|
2936
|
+
function asRGBA(color) {
|
|
2937
|
+
const type = getColorType(color);
|
|
2938
|
+
heyListen.invariant(Boolean(type), `'${color}' is not an animatable color. Use the equivalent color code instead.`);
|
|
2939
|
+
let model = type.parse(color);
|
|
2940
|
+
if (type === hsla) {
|
|
2941
|
+
// TODO Remove this cast - needed since Framer Motion's stricter typing
|
|
2942
|
+
model = hslaToRgba(model);
|
|
3125
2943
|
}
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
2944
|
+
return model;
|
|
2945
|
+
}
|
|
2946
|
+
const mixColor = (from, to) => {
|
|
2947
|
+
const fromRGBA = asRGBA(from);
|
|
2948
|
+
const toRGBA = asRGBA(to);
|
|
2949
|
+
const blended = { ...fromRGBA };
|
|
2950
|
+
return (v) => {
|
|
2951
|
+
blended.red = mixLinearColor(fromRGBA.red, toRGBA.red, v);
|
|
2952
|
+
blended.green = mixLinearColor(fromRGBA.green, toRGBA.green, v);
|
|
2953
|
+
blended.blue = mixLinearColor(fromRGBA.blue, toRGBA.blue, v);
|
|
2954
|
+
blended.alpha = mix(fromRGBA.alpha, toRGBA.alpha, v);
|
|
2955
|
+
return rgba.transform(blended);
|
|
2956
|
+
};
|
|
2957
|
+
};
|
|
2958
|
+
|
|
2959
|
+
function getMixer$1(origin, target) {
|
|
2960
|
+
if (typeof origin === "number") {
|
|
2961
|
+
return (v) => mix(origin, target, v);
|
|
3134
2962
|
}
|
|
3135
|
-
if (
|
|
3136
|
-
|
|
3137
|
-
startSpring({ from, velocity, to: boundaryNearest(from) });
|
|
2963
|
+
else if (color.test(origin)) {
|
|
2964
|
+
return mixColor(origin, target);
|
|
3138
2965
|
}
|
|
3139
2966
|
else {
|
|
3140
|
-
|
|
3141
|
-
* Or if the value is out of bounds, simulate the inertia movement
|
|
3142
|
-
* with the decay animation.
|
|
3143
|
-
*
|
|
3144
|
-
* Pre-calculate the target so we can detect if it's out-of-bounds.
|
|
3145
|
-
* If it is, we want to check per frame when to switch to a spring
|
|
3146
|
-
* animation
|
|
3147
|
-
*/
|
|
3148
|
-
let target = power * velocity + from;
|
|
3149
|
-
if (typeof modifyTarget !== "undefined")
|
|
3150
|
-
target = modifyTarget(target);
|
|
3151
|
-
const boundary = boundaryNearest(target);
|
|
3152
|
-
const heading = boundary === min ? -1 : 1;
|
|
3153
|
-
let prev;
|
|
3154
|
-
let current;
|
|
3155
|
-
const checkBoundary = (v) => {
|
|
3156
|
-
prev = current;
|
|
3157
|
-
current = v;
|
|
3158
|
-
velocity = velocityPerSecond(v - prev, frameData.delta);
|
|
3159
|
-
if ((heading === 1 && v > boundary) ||
|
|
3160
|
-
(heading === -1 && v < boundary)) {
|
|
3161
|
-
startSpring({ from: v, to: boundary, velocity });
|
|
3162
|
-
}
|
|
3163
|
-
};
|
|
3164
|
-
startAnimation({
|
|
3165
|
-
type: "decay",
|
|
3166
|
-
from,
|
|
3167
|
-
velocity,
|
|
3168
|
-
timeConstant,
|
|
3169
|
-
power,
|
|
3170
|
-
restDelta,
|
|
3171
|
-
modifyTarget,
|
|
3172
|
-
onUpdate: isOutOfBounds(target) ? checkBoundary : undefined,
|
|
3173
|
-
});
|
|
2967
|
+
return mixComplex(origin, target);
|
|
3174
2968
|
}
|
|
3175
|
-
return {
|
|
3176
|
-
stop: () => currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.stop(),
|
|
3177
|
-
};
|
|
3178
2969
|
}
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
const elapsed = timestamp - start;
|
|
3187
|
-
if (elapsed >= timeout) {
|
|
3188
|
-
cancelSync.read(checkElapsed);
|
|
3189
|
-
callback(elapsed - timeout);
|
|
2970
|
+
const mixArray = (from, to) => {
|
|
2971
|
+
const output = [...from];
|
|
2972
|
+
const numValues = output.length;
|
|
2973
|
+
const blendValue = from.map((fromThis, i) => getMixer$1(fromThis, to[i]));
|
|
2974
|
+
return (v) => {
|
|
2975
|
+
for (let i = 0; i < numValues; i++) {
|
|
2976
|
+
output[i] = blendValue[i](v);
|
|
3190
2977
|
}
|
|
2978
|
+
return output;
|
|
3191
2979
|
};
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
}
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
*/
|
|
3201
|
-
function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, ...transition }) {
|
|
3202
|
-
return !!Object.keys(transition).length;
|
|
3203
|
-
}
|
|
3204
|
-
/**
|
|
3205
|
-
* Convert Framer Motion's Transition type into Popmotion-compatible options.
|
|
3206
|
-
*/
|
|
3207
|
-
function convertTransitionToAnimationOptions({ ease, times, ...transition }) {
|
|
3208
|
-
const options = { ...transition };
|
|
3209
|
-
if (times)
|
|
3210
|
-
options["offset"] = times;
|
|
3211
|
-
/**
|
|
3212
|
-
* Convert any existing durations from seconds to milliseconds
|
|
3213
|
-
*/
|
|
3214
|
-
if (transition.duration)
|
|
3215
|
-
options["duration"] = secondsToMilliseconds(transition.duration);
|
|
3216
|
-
if (transition.repeatDelay)
|
|
3217
|
-
options.repeatDelay = secondsToMilliseconds(transition.repeatDelay);
|
|
3218
|
-
/**
|
|
3219
|
-
* Map easing names to Popmotion's easing functions
|
|
3220
|
-
*/
|
|
3221
|
-
if (ease) {
|
|
3222
|
-
options["ease"] = isEasingArray(ease)
|
|
3223
|
-
? ease.map(easingDefinitionToFunction)
|
|
3224
|
-
: easingDefinitionToFunction(ease);
|
|
3225
|
-
}
|
|
3226
|
-
/**
|
|
3227
|
-
* Support legacy transition API
|
|
3228
|
-
*/
|
|
3229
|
-
if (transition.type === "tween")
|
|
3230
|
-
options.type = "keyframes";
|
|
3231
|
-
/**
|
|
3232
|
-
* TODO: Popmotion 9 has the ability to automatically detect whether to use
|
|
3233
|
-
* a keyframes or spring animation, but does so by detecting velocity and other spring options.
|
|
3234
|
-
* It'd be good to introduce a similar thing here.
|
|
3235
|
-
*/
|
|
3236
|
-
if (transition.type !== "spring")
|
|
3237
|
-
options.type = "keyframes";
|
|
3238
|
-
return options;
|
|
3239
|
-
}
|
|
3240
|
-
/**
|
|
3241
|
-
* Get the delay for a value by checking Transition with decreasing specificity.
|
|
3242
|
-
*/
|
|
3243
|
-
function getDelayFromTransition(transition, key) {
|
|
3244
|
-
const valueTransition = getValueTransition(transition, key) || {};
|
|
3245
|
-
return valueTransition.delay !== undefined
|
|
3246
|
-
? valueTransition.delay
|
|
3247
|
-
: transition.delay !== undefined
|
|
3248
|
-
? transition.delay
|
|
3249
|
-
: 0;
|
|
3250
|
-
}
|
|
3251
|
-
function hydrateKeyframes(options) {
|
|
3252
|
-
if (Array.isArray(options.to) && options.to[0] === null) {
|
|
3253
|
-
options.to = [...options.to];
|
|
3254
|
-
options.to[0] = options.from;
|
|
2980
|
+
};
|
|
2981
|
+
const mixObject = (origin, target) => {
|
|
2982
|
+
const output = { ...origin, ...target };
|
|
2983
|
+
const blendValue = {};
|
|
2984
|
+
for (const key in output) {
|
|
2985
|
+
if (origin[key] !== undefined && target[key] !== undefined) {
|
|
2986
|
+
blendValue[key] = getMixer$1(origin[key], target[key]);
|
|
2987
|
+
}
|
|
3255
2988
|
}
|
|
3256
|
-
return
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
2989
|
+
return (v) => {
|
|
2990
|
+
for (const key in blendValue) {
|
|
2991
|
+
output[key] = blendValue[key](v);
|
|
2992
|
+
}
|
|
2993
|
+
return output;
|
|
2994
|
+
};
|
|
2995
|
+
};
|
|
2996
|
+
const mixComplex = (origin, target) => {
|
|
2997
|
+
const template = complex.createTransformer(target);
|
|
2998
|
+
const originStats = analyseComplexValue(origin);
|
|
2999
|
+
const targetStats = analyseComplexValue(target);
|
|
3000
|
+
const canInterpolate = originStats.numColors === targetStats.numColors &&
|
|
3001
|
+
originStats.numNumbers >= targetStats.numNumbers;
|
|
3002
|
+
if (canInterpolate) {
|
|
3003
|
+
return pipe(mixArray(originStats.values, targetStats.values), template);
|
|
3261
3004
|
}
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
*/
|
|
3266
|
-
if (!isTransitionDefined(transition)) {
|
|
3267
|
-
transition = {
|
|
3268
|
-
...transition,
|
|
3269
|
-
...getDefaultTransition(key, options.to),
|
|
3270
|
-
};
|
|
3005
|
+
else {
|
|
3006
|
+
heyListen.warning(true, `Complex values '${origin}' and '${target}' too different to mix. Ensure all colors are of the same type, and that each contains the same quantity of number and color values. Falling back to instant transition.`);
|
|
3007
|
+
return (p) => `${p > 0 ? target : origin}`;
|
|
3271
3008
|
}
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3009
|
+
};
|
|
3010
|
+
|
|
3011
|
+
/*
|
|
3012
|
+
Progress within given range
|
|
3013
|
+
|
|
3014
|
+
Given a lower limit and an upper limit, we return the progress
|
|
3015
|
+
(expressed as a number 0-1) represented by the given value, and
|
|
3016
|
+
limit that progress to within 0-1.
|
|
3017
|
+
|
|
3018
|
+
@param [number]: Lower limit
|
|
3019
|
+
@param [number]: Upper limit
|
|
3020
|
+
@param [number]: Value to find progress within given range
|
|
3021
|
+
@return [number]: Progress of value within range as expressed 0-1
|
|
3022
|
+
*/
|
|
3023
|
+
const progress = (from, to, value) => {
|
|
3024
|
+
const toFromDifference = to - from;
|
|
3025
|
+
return toFromDifference === 0 ? 1 : (value - from) / toFromDifference;
|
|
3026
|
+
};
|
|
3027
|
+
|
|
3028
|
+
const mixNumber = (from, to) => (p) => mix(from, to, p);
|
|
3029
|
+
function detectMixerFactory(v) {
|
|
3030
|
+
if (typeof v === "number") {
|
|
3031
|
+
return mixNumber;
|
|
3293
3032
|
}
|
|
3294
|
-
else if (
|
|
3295
|
-
|
|
3033
|
+
else if (typeof v === "string") {
|
|
3034
|
+
if (color.test(v)) {
|
|
3035
|
+
return mixColor;
|
|
3036
|
+
}
|
|
3037
|
+
else {
|
|
3038
|
+
return mixComplex;
|
|
3039
|
+
}
|
|
3296
3040
|
}
|
|
3297
|
-
else if (
|
|
3298
|
-
|
|
3299
|
-
typeof origin === "string") {
|
|
3300
|
-
target = getZeroUnit(origin);
|
|
3041
|
+
else if (Array.isArray(v)) {
|
|
3042
|
+
return mixArray;
|
|
3301
3043
|
}
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
function start() {
|
|
3305
|
-
const options = {
|
|
3306
|
-
from: origin,
|
|
3307
|
-
to: target,
|
|
3308
|
-
velocity: value.getVelocity(),
|
|
3309
|
-
onComplete,
|
|
3310
|
-
onUpdate: (v) => value.set(v),
|
|
3311
|
-
};
|
|
3312
|
-
const animation = valueTransition.type === "inertia" ||
|
|
3313
|
-
valueTransition.type === "decay"
|
|
3314
|
-
? inertia({ ...options, ...valueTransition })
|
|
3315
|
-
: animate$1({
|
|
3316
|
-
...getPopmotionAnimationOptions(valueTransition, options, key),
|
|
3317
|
-
onUpdate: (v) => {
|
|
3318
|
-
options.onUpdate(v);
|
|
3319
|
-
valueTransition.onUpdate &&
|
|
3320
|
-
valueTransition.onUpdate(v);
|
|
3321
|
-
},
|
|
3322
|
-
onComplete: () => {
|
|
3323
|
-
options.onComplete();
|
|
3324
|
-
valueTransition.onComplete &&
|
|
3325
|
-
valueTransition.onComplete();
|
|
3326
|
-
},
|
|
3327
|
-
});
|
|
3328
|
-
return () => animation.stop();
|
|
3329
|
-
}
|
|
3330
|
-
function set() {
|
|
3331
|
-
const finalTarget = resolveFinalValueInKeyframes(target);
|
|
3332
|
-
value.set(finalTarget);
|
|
3333
|
-
onComplete();
|
|
3334
|
-
valueTransition.onUpdate && valueTransition.onUpdate(finalTarget);
|
|
3335
|
-
valueTransition.onComplete && valueTransition.onComplete();
|
|
3336
|
-
return () => { };
|
|
3044
|
+
else if (typeof v === "object") {
|
|
3045
|
+
return mixObject;
|
|
3337
3046
|
}
|
|
3338
|
-
|
|
3339
|
-
!isTargetAnimatable ||
|
|
3340
|
-
valueTransition.type === false;
|
|
3341
|
-
return useInstantAnimation
|
|
3342
|
-
? valueTransition.elapsed
|
|
3343
|
-
? () => delay(set, -valueTransition.elapsed)
|
|
3344
|
-
: set()
|
|
3345
|
-
: start();
|
|
3346
|
-
}
|
|
3347
|
-
function isZero(value) {
|
|
3348
|
-
return (value === 0 ||
|
|
3349
|
-
(typeof value === "string" &&
|
|
3350
|
-
parseFloat(value) === 0 &&
|
|
3351
|
-
value.indexOf(" ") === -1));
|
|
3352
|
-
}
|
|
3353
|
-
function getZeroUnit(potentialUnitType) {
|
|
3354
|
-
return typeof potentialUnitType === "number"
|
|
3355
|
-
? 0
|
|
3356
|
-
: getAnimatableNone("", potentialUnitType);
|
|
3357
|
-
}
|
|
3358
|
-
function getValueTransition(transition, key) {
|
|
3359
|
-
return transition[key] || transition["default"] || transition;
|
|
3047
|
+
return mixNumber;
|
|
3360
3048
|
}
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3049
|
+
function createMixers(output, ease, customMixer) {
|
|
3050
|
+
const mixers = [];
|
|
3051
|
+
const mixerFactory = customMixer || detectMixerFactory(output[0]);
|
|
3052
|
+
const numMixers = output.length - 1;
|
|
3053
|
+
for (let i = 0; i < numMixers; i++) {
|
|
3054
|
+
let mixer = mixerFactory(output[i], output[i + 1]);
|
|
3055
|
+
if (ease) {
|
|
3056
|
+
const easingFunction = Array.isArray(ease) ? ease[i] : ease;
|
|
3057
|
+
mixer = pipe(easingFunction, mixer);
|
|
3058
|
+
}
|
|
3059
|
+
mixers.push(mixer);
|
|
3368
3060
|
}
|
|
3369
|
-
return
|
|
3370
|
-
return getAnimation(key, value, target, { ...transition, delay: getDelayFromTransition(transition, key) }, onComplete);
|
|
3371
|
-
});
|
|
3061
|
+
return mixers;
|
|
3372
3062
|
}
|
|
3373
|
-
|
|
3374
|
-
/**
|
|
3375
|
-
* Check if value is a numerical string, ie a string that is purely a number eg "100" or "-100.1"
|
|
3376
|
-
*/
|
|
3377
|
-
const isNumericalString = (v) => /^\-?\d*\.?\d+$/.test(v);
|
|
3378
|
-
|
|
3379
3063
|
/**
|
|
3380
|
-
*
|
|
3064
|
+
* Create a function that maps from a numerical input array to a generic output array.
|
|
3065
|
+
*
|
|
3066
|
+
* Accepts:
|
|
3067
|
+
* - Numbers
|
|
3068
|
+
* - Colors (hex, hsl, hsla, rgb, rgba)
|
|
3069
|
+
* - Complex (combinations of one or more numbers or strings)
|
|
3070
|
+
*
|
|
3071
|
+
* ```jsx
|
|
3072
|
+
* const mixColor = interpolate([0, 1], ['#fff', '#000'])
|
|
3073
|
+
*
|
|
3074
|
+
* mixColor(0.5) // 'rgba(128, 128, 128, 1)'
|
|
3075
|
+
* ```
|
|
3076
|
+
*
|
|
3077
|
+
* TODO Revist this approach once we've moved to data models for values,
|
|
3078
|
+
* probably not needed to pregenerate mixer functions.
|
|
3079
|
+
*
|
|
3080
|
+
* @public
|
|
3381
3081
|
*/
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
if (index > -1)
|
|
3391
|
-
arr.splice(index, 1);
|
|
3392
|
-
}
|
|
3393
|
-
// Adapted from array-move
|
|
3394
|
-
function moveItem([...arr], fromIndex, toIndex) {
|
|
3395
|
-
const startIndex = fromIndex < 0 ? arr.length + fromIndex : fromIndex;
|
|
3396
|
-
if (startIndex >= 0 && startIndex < arr.length) {
|
|
3397
|
-
const endIndex = toIndex < 0 ? arr.length + toIndex : toIndex;
|
|
3398
|
-
const [item] = arr.splice(fromIndex, 1);
|
|
3399
|
-
arr.splice(endIndex, 0, item);
|
|
3082
|
+
function interpolate(input, output, { clamp: isClamp = true, ease, mixer } = {}) {
|
|
3083
|
+
const inputLength = input.length;
|
|
3084
|
+
heyListen.invariant(inputLength === output.length, "Both input and output ranges must be the same length");
|
|
3085
|
+
heyListen.invariant(!ease || !Array.isArray(ease) || ease.length === inputLength - 1, "Array of easing functions must be of length `input.length - 1`, as it applies to the transitions **between** the defined values.");
|
|
3086
|
+
// If input runs highest -> lowest, reverse both arrays
|
|
3087
|
+
if (input[0] > input[inputLength - 1]) {
|
|
3088
|
+
input = [...input].reverse();
|
|
3089
|
+
output = [...output].reverse();
|
|
3400
3090
|
}
|
|
3401
|
-
|
|
3091
|
+
const mixers = createMixers(output, ease, mixer);
|
|
3092
|
+
const numMixers = mixers.length;
|
|
3093
|
+
const interpolator = (v) => {
|
|
3094
|
+
let i = 0;
|
|
3095
|
+
if (numMixers > 1) {
|
|
3096
|
+
for (; i < input.length - 2; i++) {
|
|
3097
|
+
if (v < input[i + 1])
|
|
3098
|
+
break;
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
const progressInRange = progress(input[i], input[i + 1], v);
|
|
3102
|
+
return mixers[i](progressInRange);
|
|
3103
|
+
};
|
|
3104
|
+
return isClamp
|
|
3105
|
+
? (v) => interpolator(clamp(input[0], input[inputLength - 1], v))
|
|
3106
|
+
: interpolator;
|
|
3402
3107
|
}
|
|
3403
3108
|
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3109
|
+
const noop = (any) => any;
|
|
3110
|
+
|
|
3111
|
+
/*
|
|
3112
|
+
Bezier function generator
|
|
3113
|
+
This has been modified from Gaëtan Renaudeau's BezierEasing
|
|
3114
|
+
https://github.com/gre/bezier-easing/blob/master/src/index.js
|
|
3115
|
+
https://github.com/gre/bezier-easing/blob/master/LICENSE
|
|
3116
|
+
|
|
3117
|
+
I've removed the newtonRaphsonIterate algo because in benchmarking it
|
|
3118
|
+
wasn't noticiably faster than binarySubdivision, indeed removing it
|
|
3119
|
+
usually improved times, depending on the curve.
|
|
3120
|
+
I also removed the lookup table, as for the added bundle size and loop we're
|
|
3121
|
+
only cutting ~4 or so subdivision iterations. I bumped the max iterations up
|
|
3122
|
+
to 12 to compensate and this still tended to be faster for no perceivable
|
|
3123
|
+
loss in accuracy.
|
|
3124
|
+
Usage
|
|
3125
|
+
const easeOut = cubicBezier(.17,.67,.83,.67);
|
|
3126
|
+
const x = easeOut(0.5); // returns 0.627...
|
|
3127
|
+
*/
|
|
3128
|
+
// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
|
|
3129
|
+
const calcBezier = (t, a1, a2) => (((1.0 - 3.0 * a2 + 3.0 * a1) * t + (3.0 * a2 - 6.0 * a1)) * t + 3.0 * a1) *
|
|
3130
|
+
t;
|
|
3131
|
+
const subdivisionPrecision = 0.0000001;
|
|
3132
|
+
const subdivisionMaxIterations = 12;
|
|
3133
|
+
function binarySubdivide(x, lowerBound, upperBound, mX1, mX2) {
|
|
3134
|
+
let currentX;
|
|
3135
|
+
let currentT;
|
|
3136
|
+
let i = 0;
|
|
3137
|
+
do {
|
|
3138
|
+
currentT = lowerBound + (upperBound - lowerBound) / 2.0;
|
|
3139
|
+
currentX = calcBezier(currentT, mX1, mX2) - x;
|
|
3140
|
+
if (currentX > 0.0) {
|
|
3141
|
+
upperBound = currentT;
|
|
3421
3142
|
}
|
|
3422
3143
|
else {
|
|
3423
|
-
|
|
3424
|
-
/**
|
|
3425
|
-
* Check whether the handler exists before firing as it's possible
|
|
3426
|
-
* the subscriptions were modified during this loop running.
|
|
3427
|
-
*/
|
|
3428
|
-
const handler = this.subscriptions[i];
|
|
3429
|
-
handler && handler(a, b, c);
|
|
3430
|
-
}
|
|
3144
|
+
lowerBound = currentT;
|
|
3431
3145
|
}
|
|
3146
|
+
} while (Math.abs(currentX) > subdivisionPrecision &&
|
|
3147
|
+
++i < subdivisionMaxIterations);
|
|
3148
|
+
return currentT;
|
|
3149
|
+
}
|
|
3150
|
+
function cubicBezier(mX1, mY1, mX2, mY2) {
|
|
3151
|
+
// If this is a linear gradient, return linear easing
|
|
3152
|
+
if (mX1 === mY1 && mX2 === mY2)
|
|
3153
|
+
return noop;
|
|
3154
|
+
const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2);
|
|
3155
|
+
// If animation is at start/end, return t without easing
|
|
3156
|
+
return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
const circIn = (p) => 1 - Math.sin(Math.acos(p));
|
|
3160
|
+
const circOut = reverseEasing(circIn);
|
|
3161
|
+
const circInOut = mirrorEasing(circOut);
|
|
3162
|
+
|
|
3163
|
+
const createBackIn = (power = 1.525) => (p) => p * p * ((power + 1) * p - power);
|
|
3164
|
+
const backIn = createBackIn();
|
|
3165
|
+
const backOut = reverseEasing(backIn);
|
|
3166
|
+
const backInOut = mirrorEasing(backIn);
|
|
3167
|
+
|
|
3168
|
+
const createAnticipate = (power) => {
|
|
3169
|
+
const backEasing = createBackIn(power);
|
|
3170
|
+
return (p) => (p *= 2) < 1
|
|
3171
|
+
? 0.5 * backEasing(p)
|
|
3172
|
+
: 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
|
|
3173
|
+
};
|
|
3174
|
+
const anticipate = createAnticipate();
|
|
3175
|
+
|
|
3176
|
+
const easingLookup = {
|
|
3177
|
+
linear: noop,
|
|
3178
|
+
easeIn,
|
|
3179
|
+
easeInOut,
|
|
3180
|
+
easeOut,
|
|
3181
|
+
circIn,
|
|
3182
|
+
circInOut,
|
|
3183
|
+
circOut,
|
|
3184
|
+
backIn,
|
|
3185
|
+
backInOut,
|
|
3186
|
+
backOut,
|
|
3187
|
+
anticipate,
|
|
3188
|
+
};
|
|
3189
|
+
const easingDefinitionToFunction = (definition) => {
|
|
3190
|
+
if (Array.isArray(definition)) {
|
|
3191
|
+
// If cubic bezier definition, create bezier curve
|
|
3192
|
+
heyListen.invariant(definition.length === 4, `Cubic bezier arrays must contain four numerical values.`);
|
|
3193
|
+
const [x1, y1, x2, y2] = definition;
|
|
3194
|
+
return cubicBezier(x1, y1, x2, y2);
|
|
3432
3195
|
}
|
|
3433
|
-
|
|
3434
|
-
|
|
3196
|
+
else if (typeof definition === "string") {
|
|
3197
|
+
// Else lookup from table
|
|
3198
|
+
heyListen.invariant(easingLookup[definition] !== undefined, `Invalid easing type '${definition}'`);
|
|
3199
|
+
return easingLookup[definition];
|
|
3435
3200
|
}
|
|
3436
|
-
|
|
3437
|
-
|
|
3201
|
+
return definition;
|
|
3202
|
+
};
|
|
3203
|
+
const isEasingArray = (ease) => {
|
|
3204
|
+
return Array.isArray(ease) && typeof ease[0] !== "number";
|
|
3205
|
+
};
|
|
3206
|
+
|
|
3207
|
+
function defaultEasing(values, easing) {
|
|
3208
|
+
return values.map(() => easing || easeInOut).splice(0, values.length - 1);
|
|
3209
|
+
}
|
|
3210
|
+
function defaultOffset(values) {
|
|
3211
|
+
const numValues = values.length;
|
|
3212
|
+
return values.map((_value, i) => i !== 0 ? i / (numValues - 1) : 0);
|
|
3213
|
+
}
|
|
3214
|
+
function convertOffsetToTimes(offset, duration) {
|
|
3215
|
+
return offset.map((o) => o * duration);
|
|
3216
|
+
}
|
|
3217
|
+
function keyframes({ keyframes: keyframeValues, ease = easeInOut, times, duration = 300, }) {
|
|
3218
|
+
keyframeValues = [...keyframeValues];
|
|
3219
|
+
const origin = keyframes[0];
|
|
3220
|
+
/**
|
|
3221
|
+
* Easing functions can be externally defined as strings. Here we convert them
|
|
3222
|
+
* into actual functions.
|
|
3223
|
+
*/
|
|
3224
|
+
const easingFunctions = isEasingArray(ease)
|
|
3225
|
+
? ease.map(easingDefinitionToFunction)
|
|
3226
|
+
: easingDefinitionToFunction(ease);
|
|
3227
|
+
/**
|
|
3228
|
+
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
3229
|
+
* to reduce GC during animation.
|
|
3230
|
+
*/
|
|
3231
|
+
const state = { done: false, value: origin };
|
|
3232
|
+
/**
|
|
3233
|
+
* Create a times array based on the provided 0-1 offsets
|
|
3234
|
+
*/
|
|
3235
|
+
const absoluteTimes = convertOffsetToTimes(
|
|
3236
|
+
// Only use the provided offsets if they're the correct length
|
|
3237
|
+
// TODO Maybe we should warn here if there's a length mismatch
|
|
3238
|
+
times && times.length === keyframes.length
|
|
3239
|
+
? times
|
|
3240
|
+
: defaultOffset(keyframeValues), duration);
|
|
3241
|
+
function createInterpolator() {
|
|
3242
|
+
return interpolate(absoluteTimes, keyframeValues, {
|
|
3243
|
+
ease: Array.isArray(easingFunctions)
|
|
3244
|
+
? easingFunctions
|
|
3245
|
+
: defaultEasing(keyframeValues, easingFunctions),
|
|
3246
|
+
});
|
|
3438
3247
|
}
|
|
3248
|
+
let interpolator = createInterpolator();
|
|
3249
|
+
return {
|
|
3250
|
+
next: (t) => {
|
|
3251
|
+
state.value = interpolator(t);
|
|
3252
|
+
state.done = t >= duration;
|
|
3253
|
+
return state;
|
|
3254
|
+
},
|
|
3255
|
+
flipTarget: () => {
|
|
3256
|
+
keyframeValues.reverse();
|
|
3257
|
+
interpolator = createInterpolator();
|
|
3258
|
+
},
|
|
3259
|
+
};
|
|
3439
3260
|
}
|
|
3440
3261
|
|
|
3441
|
-
const
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3262
|
+
const safeMin = 0.001;
|
|
3263
|
+
const minDuration = 0.01;
|
|
3264
|
+
const maxDuration = 10.0;
|
|
3265
|
+
const minDamping = 0.05;
|
|
3266
|
+
const maxDamping = 1;
|
|
3267
|
+
function findSpring({ duration = 800, bounce = 0.25, velocity = 0, mass = 1, }) {
|
|
3268
|
+
let envelope;
|
|
3269
|
+
let derivative;
|
|
3270
|
+
heyListen.warning(duration <= maxDuration * 1000, "Spring duration must be 10 seconds or less");
|
|
3271
|
+
let dampingRatio = 1 - bounce;
|
|
3450
3272
|
/**
|
|
3451
|
-
*
|
|
3452
|
-
* @param config - Optional configuration options
|
|
3453
|
-
*
|
|
3454
|
-
* - `transformer`: A function to transform incoming values with.
|
|
3455
|
-
*
|
|
3456
|
-
* @internal
|
|
3273
|
+
* Restrict dampingRatio and duration to within acceptable ranges.
|
|
3457
3274
|
*/
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
* When MotionValues are provided to motion components, warn if versions are mixed.
|
|
3462
|
-
*/
|
|
3463
|
-
this.version = "7.8.0";
|
|
3464
|
-
/**
|
|
3465
|
-
* Duration, in milliseconds, since last updating frame.
|
|
3466
|
-
*
|
|
3467
|
-
* @internal
|
|
3468
|
-
*/
|
|
3469
|
-
this.timeDelta = 0;
|
|
3470
|
-
/**
|
|
3471
|
-
* Timestamp of the last time this `MotionValue` was updated.
|
|
3472
|
-
*
|
|
3473
|
-
* @internal
|
|
3474
|
-
*/
|
|
3475
|
-
this.lastUpdated = 0;
|
|
3476
|
-
/**
|
|
3477
|
-
* Functions to notify when the `MotionValue` updates.
|
|
3478
|
-
*
|
|
3479
|
-
* @internal
|
|
3480
|
-
*/
|
|
3481
|
-
this.updateSubscribers = new SubscriptionManager();
|
|
3482
|
-
/**
|
|
3483
|
-
* Functions to notify when the velocity updates.
|
|
3484
|
-
*
|
|
3485
|
-
* @internal
|
|
3486
|
-
*/
|
|
3487
|
-
this.velocityUpdateSubscribers = new SubscriptionManager();
|
|
3488
|
-
/**
|
|
3489
|
-
* Functions to notify when the `MotionValue` updates and `render` is set to `true`.
|
|
3490
|
-
*
|
|
3491
|
-
* @internal
|
|
3492
|
-
*/
|
|
3493
|
-
this.renderSubscribers = new SubscriptionManager();
|
|
3275
|
+
dampingRatio = clamp(minDamping, maxDamping, dampingRatio);
|
|
3276
|
+
duration = clamp(minDuration, maxDuration, duration / 1000);
|
|
3277
|
+
if (dampingRatio < 1) {
|
|
3494
3278
|
/**
|
|
3495
|
-
*
|
|
3496
|
-
* if the value is numerical, but we might be able to widen the scope here and support
|
|
3497
|
-
* other value types.
|
|
3498
|
-
*
|
|
3499
|
-
* @internal
|
|
3279
|
+
* Underdamped spring
|
|
3500
3280
|
*/
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
const
|
|
3507
|
-
|
|
3508
|
-
this.timeDelta = delta;
|
|
3509
|
-
this.lastUpdated = timestamp;
|
|
3510
|
-
sync.postRender(this.scheduleVelocityCheck);
|
|
3511
|
-
}
|
|
3512
|
-
// Update update subscribers
|
|
3513
|
-
if (this.prev !== this.current) {
|
|
3514
|
-
this.updateSubscribers.notify(this.current);
|
|
3515
|
-
}
|
|
3516
|
-
// Update velocity subscribers
|
|
3517
|
-
if (this.velocityUpdateSubscribers.getSize()) {
|
|
3518
|
-
this.velocityUpdateSubscribers.notify(this.getVelocity());
|
|
3519
|
-
}
|
|
3520
|
-
// Update render subscribers
|
|
3521
|
-
if (render) {
|
|
3522
|
-
this.renderSubscribers.notify(this.current);
|
|
3523
|
-
}
|
|
3281
|
+
envelope = (undampedFreq) => {
|
|
3282
|
+
const exponentialDecay = undampedFreq * dampingRatio;
|
|
3283
|
+
const delta = exponentialDecay * duration;
|
|
3284
|
+
const a = exponentialDecay - velocity;
|
|
3285
|
+
const b = calcAngularFreq(undampedFreq, dampingRatio);
|
|
3286
|
+
const c = Math.exp(-delta);
|
|
3287
|
+
return safeMin - (a / b) * c;
|
|
3524
3288
|
};
|
|
3289
|
+
derivative = (undampedFreq) => {
|
|
3290
|
+
const exponentialDecay = undampedFreq * dampingRatio;
|
|
3291
|
+
const delta = exponentialDecay * duration;
|
|
3292
|
+
const d = delta * velocity + velocity;
|
|
3293
|
+
const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
|
|
3294
|
+
const f = Math.exp(-delta);
|
|
3295
|
+
const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
|
|
3296
|
+
const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
|
|
3297
|
+
return (factor * ((d - e) * f)) / g;
|
|
3298
|
+
};
|
|
3299
|
+
}
|
|
3300
|
+
else {
|
|
3525
3301
|
/**
|
|
3526
|
-
*
|
|
3527
|
-
*
|
|
3528
|
-
* This is an instanced and bound function to prevent generating a new
|
|
3529
|
-
* function once per frame.
|
|
3530
|
-
*
|
|
3531
|
-
* @internal
|
|
3532
|
-
*/
|
|
3533
|
-
this.scheduleVelocityCheck = () => sync.postRender(this.velocityCheck);
|
|
3534
|
-
/**
|
|
3535
|
-
* Updates `prev` with `current` if the value hasn't been updated this frame.
|
|
3536
|
-
* This ensures velocity calculations return `0`.
|
|
3537
|
-
*
|
|
3538
|
-
* This is an instanced and bound function to prevent generating a new
|
|
3539
|
-
* function once per frame.
|
|
3540
|
-
*
|
|
3541
|
-
* @internal
|
|
3302
|
+
* Critically-damped spring
|
|
3542
3303
|
*/
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3304
|
+
envelope = (undampedFreq) => {
|
|
3305
|
+
const a = Math.exp(-undampedFreq * duration);
|
|
3306
|
+
const b = (undampedFreq - velocity) * duration + 1;
|
|
3307
|
+
return -safeMin + a * b;
|
|
3308
|
+
};
|
|
3309
|
+
derivative = (undampedFreq) => {
|
|
3310
|
+
const a = Math.exp(-undampedFreq * duration);
|
|
3311
|
+
const b = (velocity - undampedFreq) * (duration * duration);
|
|
3312
|
+
return a * b;
|
|
3548
3313
|
};
|
|
3549
|
-
this.hasAnimated = false;
|
|
3550
|
-
this.prev = this.current = init;
|
|
3551
|
-
this.canTrackVelocity = isFloat(this.current);
|
|
3552
3314
|
}
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
* ```jsx
|
|
3563
|
-
* export const MyComponent = () => {
|
|
3564
|
-
* const x = useMotionValue(0)
|
|
3565
|
-
* const y = useMotionValue(0)
|
|
3566
|
-
* const opacity = useMotionValue(1)
|
|
3567
|
-
*
|
|
3568
|
-
* useEffect(() => {
|
|
3569
|
-
* function updateOpacity() {
|
|
3570
|
-
* const maxXY = Math.max(x.get(), y.get())
|
|
3571
|
-
* const newOpacity = transform(maxXY, [0, 100], [1, 0])
|
|
3572
|
-
* opacity.set(newOpacity)
|
|
3573
|
-
* }
|
|
3574
|
-
*
|
|
3575
|
-
* const unsubscribeX = x.onChange(updateOpacity)
|
|
3576
|
-
* const unsubscribeY = y.onChange(updateOpacity)
|
|
3577
|
-
*
|
|
3578
|
-
* return () => {
|
|
3579
|
-
* unsubscribeX()
|
|
3580
|
-
* unsubscribeY()
|
|
3581
|
-
* }
|
|
3582
|
-
* }, [])
|
|
3583
|
-
*
|
|
3584
|
-
* return <motion.div style={{ x }} />
|
|
3585
|
-
* }
|
|
3586
|
-
* ```
|
|
3587
|
-
*
|
|
3588
|
-
* @privateRemarks
|
|
3589
|
-
*
|
|
3590
|
-
* We could look into a `useOnChange` hook if the above lifecycle management proves confusing.
|
|
3591
|
-
*
|
|
3592
|
-
* ```jsx
|
|
3593
|
-
* useOnChange(x, () => {})
|
|
3594
|
-
* ```
|
|
3595
|
-
*
|
|
3596
|
-
* @param subscriber - A function that receives the latest value.
|
|
3597
|
-
* @returns A function that, when called, will cancel this subscription.
|
|
3598
|
-
*
|
|
3599
|
-
* @public
|
|
3600
|
-
*/
|
|
3601
|
-
onChange(subscription) {
|
|
3602
|
-
return this.updateSubscribers.add(subscription);
|
|
3315
|
+
const initialGuess = 5 / duration;
|
|
3316
|
+
const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
|
|
3317
|
+
duration = duration * 1000;
|
|
3318
|
+
if (isNaN(undampedFreq)) {
|
|
3319
|
+
return {
|
|
3320
|
+
stiffness: 100,
|
|
3321
|
+
damping: 10,
|
|
3322
|
+
duration,
|
|
3323
|
+
};
|
|
3603
3324
|
}
|
|
3604
|
-
|
|
3605
|
-
|
|
3325
|
+
else {
|
|
3326
|
+
const stiffness = Math.pow(undampedFreq, 2) * mass;
|
|
3327
|
+
return {
|
|
3328
|
+
stiffness,
|
|
3329
|
+
damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
|
|
3330
|
+
duration,
|
|
3331
|
+
};
|
|
3606
3332
|
}
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
* @internal
|
|
3614
|
-
*/
|
|
3615
|
-
onRenderRequest(subscription) {
|
|
3616
|
-
// Render immediately
|
|
3617
|
-
subscription(this.get());
|
|
3618
|
-
return this.renderSubscribers.add(subscription);
|
|
3333
|
+
}
|
|
3334
|
+
const rootIterations = 12;
|
|
3335
|
+
function approximateRoot(envelope, derivative, initialGuess) {
|
|
3336
|
+
let result = initialGuess;
|
|
3337
|
+
for (let i = 1; i < rootIterations; i++) {
|
|
3338
|
+
result = result - envelope(result) / derivative(result);
|
|
3619
3339
|
}
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3340
|
+
return result;
|
|
3341
|
+
}
|
|
3342
|
+
function calcAngularFreq(undampedFreq, dampingRatio) {
|
|
3343
|
+
return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
const durationKeys = ["duration", "bounce"];
|
|
3347
|
+
const physicsKeys = ["stiffness", "damping", "mass"];
|
|
3348
|
+
function isSpringType(options, keys) {
|
|
3349
|
+
return keys.some((key) => options[key] !== undefined);
|
|
3350
|
+
}
|
|
3351
|
+
function getSpringOptions(options) {
|
|
3352
|
+
let springOptions = {
|
|
3353
|
+
velocity: 0.0,
|
|
3354
|
+
stiffness: 100,
|
|
3355
|
+
damping: 10,
|
|
3356
|
+
mass: 1.0,
|
|
3357
|
+
isResolvedFromDuration: false,
|
|
3358
|
+
...options,
|
|
3359
|
+
};
|
|
3360
|
+
// stiffness/damping/mass overrides duration/bounce
|
|
3361
|
+
if (!isSpringType(options, physicsKeys) &&
|
|
3362
|
+
isSpringType(options, durationKeys)) {
|
|
3363
|
+
const derived = findSpring(options);
|
|
3364
|
+
springOptions = {
|
|
3365
|
+
...springOptions,
|
|
3366
|
+
...derived,
|
|
3367
|
+
velocity: 0.0,
|
|
3368
|
+
mass: 1.0,
|
|
3369
|
+
};
|
|
3370
|
+
springOptions.isResolvedFromDuration = true;
|
|
3627
3371
|
}
|
|
3372
|
+
return springOptions;
|
|
3373
|
+
}
|
|
3374
|
+
const velocitySampleDuration = 5;
|
|
3375
|
+
/**
|
|
3376
|
+
* This is based on the spring implementation of Wobble https://github.com/skevy/wobble
|
|
3377
|
+
*/
|
|
3378
|
+
function spring({ keyframes, restSpeed = 2, restDelta = 0.01, ...options }) {
|
|
3379
|
+
let origin = keyframes[0];
|
|
3380
|
+
let target = keyframes[keyframes.length - 1];
|
|
3628
3381
|
/**
|
|
3629
|
-
*
|
|
3630
|
-
*
|
|
3631
|
-
* @remarks
|
|
3632
|
-
*
|
|
3633
|
-
* ```jsx
|
|
3634
|
-
* const x = useMotionValue(0)
|
|
3635
|
-
* x.set(10)
|
|
3636
|
-
* ```
|
|
3637
|
-
*
|
|
3638
|
-
* @param latest - Latest value to set.
|
|
3639
|
-
* @param render - Whether to notify render subscribers. Defaults to `true`
|
|
3640
|
-
*
|
|
3641
|
-
* @public
|
|
3382
|
+
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
3383
|
+
* to reduce GC during animation.
|
|
3642
3384
|
*/
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3385
|
+
const state = { done: false, value: origin };
|
|
3386
|
+
const { stiffness, damping, mass, velocity, duration, isResolvedFromDuration, } = getSpringOptions(options);
|
|
3387
|
+
let resolveSpring = zero;
|
|
3388
|
+
let initialVelocity = velocity ? -(velocity / 1000) : 0.0;
|
|
3389
|
+
const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
|
|
3390
|
+
function createSpring() {
|
|
3391
|
+
const initialDelta = target - origin;
|
|
3392
|
+
const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
|
|
3393
|
+
/**
|
|
3394
|
+
* If we're working within what looks like a 0-1 range, change the default restDelta
|
|
3395
|
+
* to 0.01
|
|
3396
|
+
*/
|
|
3397
|
+
if (restDelta === undefined) {
|
|
3398
|
+
restDelta = Math.min(Math.abs(target - origin) / 100, 0.4);
|
|
3399
|
+
}
|
|
3400
|
+
if (dampingRatio < 1) {
|
|
3401
|
+
const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
|
|
3402
|
+
// Underdamped spring
|
|
3403
|
+
resolveSpring = (t) => {
|
|
3404
|
+
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
3405
|
+
return (target -
|
|
3406
|
+
envelope *
|
|
3407
|
+
(((initialVelocity +
|
|
3408
|
+
dampingRatio * undampedAngularFreq * initialDelta) /
|
|
3409
|
+
angularFreq) *
|
|
3410
|
+
Math.sin(angularFreq * t) +
|
|
3411
|
+
initialDelta * Math.cos(angularFreq * t)));
|
|
3412
|
+
};
|
|
3413
|
+
}
|
|
3414
|
+
else if (dampingRatio === 1) {
|
|
3415
|
+
// Critically damped spring
|
|
3416
|
+
resolveSpring = (t) => target -
|
|
3417
|
+
Math.exp(-undampedAngularFreq * t) *
|
|
3418
|
+
(initialDelta +
|
|
3419
|
+
(initialVelocity + undampedAngularFreq * initialDelta) *
|
|
3420
|
+
t);
|
|
3646
3421
|
}
|
|
3647
3422
|
else {
|
|
3648
|
-
|
|
3423
|
+
// Overdamped spring
|
|
3424
|
+
const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
|
|
3425
|
+
resolveSpring = (t) => {
|
|
3426
|
+
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
3427
|
+
// When performing sinh or cosh values can hit Infinity so we cap them here
|
|
3428
|
+
const freqForT = Math.min(dampedAngularFreq * t, 300);
|
|
3429
|
+
return (target -
|
|
3430
|
+
(envelope *
|
|
3431
|
+
((initialVelocity +
|
|
3432
|
+
dampingRatio * undampedAngularFreq * initialDelta) *
|
|
3433
|
+
Math.sinh(freqForT) +
|
|
3434
|
+
dampedAngularFreq *
|
|
3435
|
+
initialDelta *
|
|
3436
|
+
Math.cosh(freqForT))) /
|
|
3437
|
+
dampedAngularFreq);
|
|
3438
|
+
};
|
|
3649
3439
|
}
|
|
3650
3440
|
}
|
|
3441
|
+
createSpring();
|
|
3442
|
+
return {
|
|
3443
|
+
next: (t) => {
|
|
3444
|
+
const current = resolveSpring(t);
|
|
3445
|
+
if (!isResolvedFromDuration) {
|
|
3446
|
+
let currentVelocity = initialVelocity;
|
|
3447
|
+
if (t !== 0) {
|
|
3448
|
+
/**
|
|
3449
|
+
* We only need to calculate velocity for under-damped springs
|
|
3450
|
+
* as over- and critically-damped springs can't overshoot, so
|
|
3451
|
+
* checking only for displacement is enough.
|
|
3452
|
+
*/
|
|
3453
|
+
if (dampingRatio < 1) {
|
|
3454
|
+
const prevT = Math.max(0, t - velocitySampleDuration);
|
|
3455
|
+
currentVelocity = velocityPerSecond(current - resolveSpring(prevT), t - prevT);
|
|
3456
|
+
}
|
|
3457
|
+
else {
|
|
3458
|
+
currentVelocity = 0;
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed;
|
|
3462
|
+
const isBelowDisplacementThreshold = Math.abs(target - current) <= restDelta;
|
|
3463
|
+
state.done =
|
|
3464
|
+
isBelowVelocityThreshold && isBelowDisplacementThreshold;
|
|
3465
|
+
}
|
|
3466
|
+
else {
|
|
3467
|
+
state.done = t >= duration;
|
|
3468
|
+
}
|
|
3469
|
+
state.value = state.done ? target : current;
|
|
3470
|
+
return state;
|
|
3471
|
+
},
|
|
3472
|
+
flipTarget: () => {
|
|
3473
|
+
initialVelocity = -initialVelocity;
|
|
3474
|
+
[origin, target] = [target, origin];
|
|
3475
|
+
createSpring();
|
|
3476
|
+
},
|
|
3477
|
+
};
|
|
3478
|
+
}
|
|
3479
|
+
spring.needsInterpolation = (a, b) => typeof a === "string" || typeof b === "string";
|
|
3480
|
+
const zero = (_t) => 0;
|
|
3481
|
+
|
|
3482
|
+
function decay({
|
|
3483
|
+
/**
|
|
3484
|
+
* The decay animation dynamically calculates an end of the animation
|
|
3485
|
+
* based on the initial keyframe, so we only need to define a single keyframe
|
|
3486
|
+
* as default.
|
|
3487
|
+
*/
|
|
3488
|
+
keyframes = [0], velocity = 0, power = 0.8, timeConstant = 350, restDelta = 0.5, modifyTarget, }) {
|
|
3489
|
+
const origin = keyframes[0];
|
|
3651
3490
|
/**
|
|
3652
|
-
*
|
|
3653
|
-
*
|
|
3654
|
-
* @returns - The latest state of `MotionValue`
|
|
3655
|
-
*
|
|
3656
|
-
* @public
|
|
3491
|
+
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
3492
|
+
* to reduce GC during animation.
|
|
3657
3493
|
*/
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3494
|
+
const state = { done: false, value: origin };
|
|
3495
|
+
let amplitude = power * velocity;
|
|
3496
|
+
const ideal = origin + amplitude;
|
|
3497
|
+
const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
|
|
3661
3498
|
/**
|
|
3662
|
-
*
|
|
3499
|
+
* If the target has changed we need to re-calculate the amplitude, otherwise
|
|
3500
|
+
* the animation will start from the wrong position.
|
|
3663
3501
|
*/
|
|
3664
|
-
|
|
3665
|
-
|
|
3502
|
+
if (target !== ideal)
|
|
3503
|
+
amplitude = target - origin;
|
|
3504
|
+
return {
|
|
3505
|
+
next: (t) => {
|
|
3506
|
+
const delta = -amplitude * Math.exp(-t / timeConstant);
|
|
3507
|
+
state.done = !(delta > restDelta || delta < -restDelta);
|
|
3508
|
+
state.value = state.done ? target : target + delta;
|
|
3509
|
+
return state;
|
|
3510
|
+
},
|
|
3511
|
+
flipTarget: () => { },
|
|
3512
|
+
};
|
|
3513
|
+
}
|
|
3514
|
+
|
|
3515
|
+
const types = {
|
|
3516
|
+
decay,
|
|
3517
|
+
keyframes: keyframes,
|
|
3518
|
+
tween: keyframes,
|
|
3519
|
+
spring,
|
|
3520
|
+
};
|
|
3521
|
+
function loopElapsed(elapsed, duration, delay = 0) {
|
|
3522
|
+
return elapsed - duration - delay;
|
|
3523
|
+
}
|
|
3524
|
+
function reverseElapsed(elapsed, duration = 0, delay = 0, isForwardPlayback = true) {
|
|
3525
|
+
return isForwardPlayback
|
|
3526
|
+
? loopElapsed(duration + -elapsed, duration, delay)
|
|
3527
|
+
: duration - (elapsed - duration) + delay;
|
|
3528
|
+
}
|
|
3529
|
+
function hasRepeatDelayElapsed(elapsed, duration, delay, isForwardPlayback) {
|
|
3530
|
+
return isForwardPlayback ? elapsed >= duration + delay : elapsed <= -delay;
|
|
3531
|
+
}
|
|
3532
|
+
const framesync = (update) => {
|
|
3533
|
+
const passTimestamp = ({ delta }) => update(delta);
|
|
3534
|
+
return {
|
|
3535
|
+
start: () => sync.update(passTimestamp, true),
|
|
3536
|
+
stop: () => cancelSync.update(passTimestamp),
|
|
3537
|
+
};
|
|
3538
|
+
};
|
|
3539
|
+
function animate$1({ duration, driver = framesync, elapsed = 0, repeat: repeatMax = 0, repeatType = "loop", repeatDelay = 0, keyframes, autoplay = true, onPlay, onStop, onComplete, onRepeat, onUpdate, type = "keyframes", ...options }) {
|
|
3540
|
+
var _a, _b;
|
|
3541
|
+
let driverControls;
|
|
3542
|
+
let repeatCount = 0;
|
|
3543
|
+
let computedDuration = duration;
|
|
3544
|
+
let latest;
|
|
3545
|
+
let isComplete = false;
|
|
3546
|
+
let isForwardPlayback = true;
|
|
3547
|
+
let interpolateFromNumber;
|
|
3548
|
+
const animator = types[keyframes.length > 2 ? "keyframes" : type];
|
|
3549
|
+
const origin = keyframes[0];
|
|
3550
|
+
const target = keyframes[keyframes.length - 1];
|
|
3551
|
+
if ((_b = (_a = animator).needsInterpolation) === null || _b === void 0 ? void 0 : _b.call(_a, origin, target)) {
|
|
3552
|
+
interpolateFromNumber = interpolate([0, 100], [origin, target], {
|
|
3553
|
+
clamp: false,
|
|
3554
|
+
});
|
|
3555
|
+
keyframes = [0, 100];
|
|
3666
3556
|
}
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3557
|
+
const animation = animator({
|
|
3558
|
+
...options,
|
|
3559
|
+
duration,
|
|
3560
|
+
keyframes,
|
|
3561
|
+
});
|
|
3562
|
+
function repeat() {
|
|
3563
|
+
repeatCount++;
|
|
3564
|
+
if (repeatType === "reverse") {
|
|
3565
|
+
isForwardPlayback = repeatCount % 2 === 0;
|
|
3566
|
+
elapsed = reverseElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback);
|
|
3567
|
+
}
|
|
3568
|
+
else {
|
|
3569
|
+
elapsed = loopElapsed(elapsed, computedDuration, repeatDelay);
|
|
3570
|
+
if (repeatType === "mirror")
|
|
3571
|
+
animation.flipTarget();
|
|
3572
|
+
}
|
|
3573
|
+
isComplete = false;
|
|
3574
|
+
onRepeat && onRepeat();
|
|
3575
|
+
}
|
|
3576
|
+
function complete() {
|
|
3577
|
+
driverControls.stop();
|
|
3578
|
+
onComplete && onComplete();
|
|
3579
|
+
}
|
|
3580
|
+
function update(delta) {
|
|
3581
|
+
if (!isForwardPlayback)
|
|
3582
|
+
delta = -delta;
|
|
3583
|
+
elapsed += delta;
|
|
3584
|
+
if (!isComplete) {
|
|
3585
|
+
const state = animation.next(Math.max(0, elapsed));
|
|
3586
|
+
latest = state.value;
|
|
3587
|
+
if (interpolateFromNumber)
|
|
3588
|
+
latest = interpolateFromNumber(latest);
|
|
3589
|
+
isComplete = isForwardPlayback ? state.done : elapsed <= 0;
|
|
3590
|
+
}
|
|
3591
|
+
onUpdate && onUpdate(latest);
|
|
3592
|
+
if (isComplete) {
|
|
3593
|
+
if (repeatCount === 0) {
|
|
3594
|
+
computedDuration =
|
|
3595
|
+
computedDuration !== undefined ? computedDuration : elapsed;
|
|
3596
|
+
}
|
|
3597
|
+
if (repeatCount < repeatMax) {
|
|
3598
|
+
hasRepeatDelayElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback) && repeat();
|
|
3599
|
+
}
|
|
3600
|
+
else {
|
|
3601
|
+
complete();
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3681
3604
|
}
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
* ```jsx
|
|
3687
|
-
* value.start()
|
|
3688
|
-
* ```
|
|
3689
|
-
*
|
|
3690
|
-
* @param animation - A function that starts the provided animation
|
|
3691
|
-
*
|
|
3692
|
-
* @internal
|
|
3693
|
-
*/
|
|
3694
|
-
start(animation) {
|
|
3695
|
-
this.stop();
|
|
3696
|
-
return new Promise((resolve) => {
|
|
3697
|
-
this.hasAnimated = true;
|
|
3698
|
-
this.stopAnimation = animation(resolve);
|
|
3699
|
-
}).then(() => this.clearAnimation());
|
|
3605
|
+
function play() {
|
|
3606
|
+
onPlay && onPlay();
|
|
3607
|
+
driverControls = driver(update);
|
|
3608
|
+
driverControls.start();
|
|
3700
3609
|
}
|
|
3610
|
+
autoplay && play();
|
|
3611
|
+
return {
|
|
3612
|
+
stop: () => {
|
|
3613
|
+
onStop && onStop();
|
|
3614
|
+
driverControls.stop();
|
|
3615
|
+
},
|
|
3616
|
+
sample: (t) => {
|
|
3617
|
+
return animation.next(Math.max(0, t)).value;
|
|
3618
|
+
},
|
|
3619
|
+
};
|
|
3620
|
+
}
|
|
3621
|
+
|
|
3622
|
+
const cubicBezierAsString = ([a, b, c, d]) => `cubic-bezier(${a}, ${b}, ${c}, ${d})`;
|
|
3623
|
+
|
|
3624
|
+
function animateStyle(element, valueName, keyframes, { delay = 0, duration, repeat = 0, repeatType = "loop", ease, times, } = {}) {
|
|
3625
|
+
return element.animate({ [valueName]: keyframes, offset: times }, {
|
|
3626
|
+
delay,
|
|
3627
|
+
duration,
|
|
3628
|
+
easing: Array.isArray(ease) ? cubicBezierAsString(ease) : ease,
|
|
3629
|
+
fill: "both",
|
|
3630
|
+
iterations: repeat + 1,
|
|
3631
|
+
direction: repeatType === "reverse" ? "alternate" : "normal",
|
|
3632
|
+
});
|
|
3633
|
+
}
|
|
3634
|
+
|
|
3635
|
+
/**
|
|
3636
|
+
* 10ms is chosen here as it strikes a balance between smooth
|
|
3637
|
+
* results (more than one keyframe per frame at 60fps) and
|
|
3638
|
+
* keyframe quantity.
|
|
3639
|
+
*/
|
|
3640
|
+
const sampleDelta = 10; //ms
|
|
3641
|
+
function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ...options }) {
|
|
3642
|
+
let { keyframes, duration = 0.3, elapsed = 0, ease } = options;
|
|
3701
3643
|
/**
|
|
3702
|
-
*
|
|
3644
|
+
* If this is a spring animation, pre-generate keyframes and
|
|
3645
|
+
* record duration.
|
|
3703
3646
|
*
|
|
3704
|
-
*
|
|
3647
|
+
* TODO: When introducing support for values beyond opacity it
|
|
3648
|
+
* might be better to use `animate.sample()`
|
|
3705
3649
|
*/
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3650
|
+
if (options.type === "spring") {
|
|
3651
|
+
const springAnimation = spring(options);
|
|
3652
|
+
let state = { done: false, value: keyframes[0] };
|
|
3653
|
+
const springKeyframes = [];
|
|
3654
|
+
let t = 0;
|
|
3655
|
+
while (!state.done) {
|
|
3656
|
+
state = springAnimation.next(t);
|
|
3657
|
+
springKeyframes.push(state.value);
|
|
3658
|
+
t += sampleDelta;
|
|
3659
|
+
}
|
|
3660
|
+
keyframes = springKeyframes;
|
|
3661
|
+
duration = t - sampleDelta;
|
|
3662
|
+
ease = "linear";
|
|
3663
|
+
}
|
|
3664
|
+
const animation = animateStyle(value.owner.current, valueName, keyframes, {
|
|
3665
|
+
...options,
|
|
3666
|
+
delay: -elapsed,
|
|
3667
|
+
duration,
|
|
3668
|
+
/**
|
|
3669
|
+
* This function is currently not called if ease is provided
|
|
3670
|
+
* as a function so the cast is safe.
|
|
3671
|
+
*
|
|
3672
|
+
* However it would be possible for a future refinement to port
|
|
3673
|
+
* in easing pregeneration from Motion One for browsers that
|
|
3674
|
+
* support the upcoming `linear()` easing function.
|
|
3675
|
+
*/
|
|
3676
|
+
ease: ease,
|
|
3677
|
+
});
|
|
3711
3678
|
/**
|
|
3712
|
-
*
|
|
3679
|
+
* Prefer the `onfinish` prop as it's more widely supported than
|
|
3680
|
+
* the `finished` promise.
|
|
3713
3681
|
*
|
|
3714
|
-
*
|
|
3682
|
+
* Here, we synchronously set the provided MotionValue to the end
|
|
3683
|
+
* keyframe. If we didn't, when the WAAPI animation is finished it would
|
|
3684
|
+
* be removed from the element which would then revert to its old styles.
|
|
3715
3685
|
*/
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
this.stopAnimation = null;
|
|
3721
|
-
}
|
|
3686
|
+
animation.onfinish = () => {
|
|
3687
|
+
value.set(keyframes[keyframes.length - 1]);
|
|
3688
|
+
onComplete && onComplete();
|
|
3689
|
+
};
|
|
3722
3690
|
/**
|
|
3723
|
-
*
|
|
3724
|
-
*
|
|
3725
|
-
* The `MotionValue` hooks like `useMotionValue` and `useTransform` automatically
|
|
3726
|
-
* handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually
|
|
3727
|
-
* created a `MotionValue` via the `motionValue` function.
|
|
3728
|
-
*
|
|
3729
|
-
* @public
|
|
3691
|
+
* Animation interrupt callback.
|
|
3730
3692
|
*/
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3693
|
+
return () => {
|
|
3694
|
+
/**
|
|
3695
|
+
* WAAPI doesn't natively have any interruption capabilities.
|
|
3696
|
+
*
|
|
3697
|
+
* Rather than read commited styles back out of the DOM, we can
|
|
3698
|
+
* create a renderless JS animation and sample it twice to calculate
|
|
3699
|
+
* its current value, "previous" value, and therefore allow
|
|
3700
|
+
* Motion to calculate velocity for any subsequent animation.
|
|
3701
|
+
*/
|
|
3702
|
+
const { currentTime } = animation;
|
|
3703
|
+
if (currentTime) {
|
|
3704
|
+
const sampleAnimation = animate$1(options);
|
|
3705
|
+
value.setWithVelocity(sampleAnimation.sample(currentTime - sampleDelta), sampleAnimation.sample(currentTime), sampleDelta);
|
|
3706
|
+
}
|
|
3707
|
+
sync.update(() => animation.cancel());
|
|
3708
|
+
};
|
|
3739
3709
|
}
|
|
3740
3710
|
|
|
3741
3711
|
/**
|
|
3742
|
-
*
|
|
3712
|
+
* Timeout defined in ms
|
|
3743
3713
|
*/
|
|
3744
|
-
|
|
3714
|
+
function delay(callback, timeout) {
|
|
3715
|
+
const start = performance.now();
|
|
3716
|
+
const checkElapsed = ({ timestamp }) => {
|
|
3717
|
+
const elapsed = timestamp - start;
|
|
3718
|
+
if (elapsed >= timeout) {
|
|
3719
|
+
cancelSync.read(checkElapsed);
|
|
3720
|
+
callback(elapsed - timeout);
|
|
3721
|
+
}
|
|
3722
|
+
};
|
|
3723
|
+
sync.read(checkElapsed, true);
|
|
3724
|
+
return () => cancelSync.read(checkElapsed);
|
|
3725
|
+
}
|
|
3745
3726
|
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3727
|
+
function createInstantAnimation({ keyframes, elapsed, onUpdate, onComplete, }) {
|
|
3728
|
+
const setValue = () => {
|
|
3729
|
+
onUpdate && onUpdate(keyframes[keyframes.length - 1]);
|
|
3730
|
+
onComplete && onComplete();
|
|
3731
|
+
return () => { };
|
|
3732
|
+
};
|
|
3733
|
+
return elapsed ? delay(setValue, -elapsed) : setValue();
|
|
3734
|
+
}
|
|
3753
3735
|
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3736
|
+
function inertia({ keyframes, velocity = 0, min, max, power = 0.8, timeConstant = 750, bounceStiffness = 500, bounceDamping = 10, restDelta = 1, modifyTarget, driver, onUpdate, onComplete, onStop, }) {
|
|
3737
|
+
const origin = keyframes[0];
|
|
3738
|
+
let currentAnimation;
|
|
3739
|
+
function isOutOfBounds(v) {
|
|
3740
|
+
return (min !== undefined && v < min) || (max !== undefined && v > max);
|
|
3741
|
+
}
|
|
3742
|
+
function findNearestBoundary(v) {
|
|
3743
|
+
if (min === undefined)
|
|
3744
|
+
return max;
|
|
3745
|
+
if (max === undefined)
|
|
3746
|
+
return min;
|
|
3747
|
+
return Math.abs(min - v) < Math.abs(max - v) ? min : max;
|
|
3748
|
+
}
|
|
3749
|
+
function startAnimation(options) {
|
|
3750
|
+
currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.stop();
|
|
3751
|
+
currentAnimation = animate$1({
|
|
3752
|
+
keyframes: [0, 1],
|
|
3753
|
+
velocity: 0,
|
|
3754
|
+
...options,
|
|
3755
|
+
driver,
|
|
3756
|
+
onUpdate: (v) => {
|
|
3757
|
+
var _a;
|
|
3758
|
+
onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate(v);
|
|
3759
|
+
(_a = options.onUpdate) === null || _a === void 0 ? void 0 : _a.call(options, v);
|
|
3760
|
+
},
|
|
3761
|
+
onComplete,
|
|
3762
|
+
onStop,
|
|
3763
|
+
});
|
|
3764
|
+
}
|
|
3765
|
+
function startSpring(options) {
|
|
3766
|
+
startAnimation({
|
|
3767
|
+
type: "spring",
|
|
3768
|
+
stiffness: bounceStiffness,
|
|
3769
|
+
damping: bounceDamping,
|
|
3770
|
+
restDelta,
|
|
3771
|
+
...options,
|
|
3772
|
+
});
|
|
3773
|
+
}
|
|
3774
|
+
if (isOutOfBounds(origin)) {
|
|
3775
|
+
// Start the animation with spring if outside the defined boundaries
|
|
3776
|
+
startSpring({
|
|
3777
|
+
velocity,
|
|
3778
|
+
keyframes: [origin, findNearestBoundary(origin)],
|
|
3779
|
+
});
|
|
3780
|
+
}
|
|
3781
|
+
else {
|
|
3782
|
+
/**
|
|
3783
|
+
* Or if the value is out of bounds, simulate the inertia movement
|
|
3784
|
+
* with the decay animation.
|
|
3785
|
+
*
|
|
3786
|
+
* Pre-calculate the target so we can detect if it's out-of-bounds.
|
|
3787
|
+
* If it is, we want to check per frame when to switch to a spring
|
|
3788
|
+
* animation
|
|
3789
|
+
*/
|
|
3790
|
+
let target = power * velocity + origin;
|
|
3791
|
+
if (typeof modifyTarget !== "undefined")
|
|
3792
|
+
target = modifyTarget(target);
|
|
3793
|
+
const boundary = findNearestBoundary(target);
|
|
3794
|
+
const heading = boundary === min ? -1 : 1;
|
|
3795
|
+
let prev;
|
|
3796
|
+
let current;
|
|
3797
|
+
const checkBoundary = (v) => {
|
|
3798
|
+
prev = current;
|
|
3799
|
+
current = v;
|
|
3800
|
+
velocity = velocityPerSecond(v - prev, frameData.delta);
|
|
3801
|
+
if ((heading === 1 && v > boundary) ||
|
|
3802
|
+
(heading === -1 && v < boundary)) {
|
|
3803
|
+
startSpring({ keyframes: [v, boundary], velocity });
|
|
3804
|
+
}
|
|
3805
|
+
};
|
|
3806
|
+
startAnimation({
|
|
3807
|
+
type: "decay",
|
|
3808
|
+
keyframes: [origin, 0],
|
|
3809
|
+
velocity,
|
|
3810
|
+
timeConstant,
|
|
3811
|
+
power,
|
|
3812
|
+
restDelta,
|
|
3813
|
+
modifyTarget,
|
|
3814
|
+
onUpdate: isOutOfBounds(target) ? checkBoundary : undefined,
|
|
3815
|
+
});
|
|
3816
|
+
}
|
|
3817
|
+
return {
|
|
3818
|
+
stop: () => currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.stop(),
|
|
3819
|
+
};
|
|
3820
|
+
}
|
|
3821
|
+
|
|
3822
|
+
const underDampedSpring = () => ({
|
|
3823
|
+
type: "spring",
|
|
3824
|
+
stiffness: 500,
|
|
3825
|
+
damping: 25,
|
|
3826
|
+
restSpeed: 10,
|
|
3827
|
+
});
|
|
3828
|
+
const criticallyDampedSpring = (target) => ({
|
|
3829
|
+
type: "spring",
|
|
3830
|
+
stiffness: 550,
|
|
3831
|
+
damping: target === 0 ? 2 * Math.sqrt(550) : 30,
|
|
3832
|
+
restSpeed: 10,
|
|
3833
|
+
});
|
|
3834
|
+
const linearTween = () => ({
|
|
3835
|
+
type: "keyframes",
|
|
3836
|
+
ease: "linear",
|
|
3837
|
+
duration: 0.3,
|
|
3838
|
+
});
|
|
3839
|
+
const keyframesTransition = {
|
|
3840
|
+
type: "keyframes",
|
|
3841
|
+
duration: 0.8,
|
|
3842
|
+
};
|
|
3843
|
+
const defaultTransitions = {
|
|
3844
|
+
x: underDampedSpring,
|
|
3845
|
+
y: underDampedSpring,
|
|
3846
|
+
z: underDampedSpring,
|
|
3847
|
+
rotate: underDampedSpring,
|
|
3848
|
+
rotateX: underDampedSpring,
|
|
3849
|
+
rotateY: underDampedSpring,
|
|
3850
|
+
rotateZ: underDampedSpring,
|
|
3851
|
+
scaleX: criticallyDampedSpring,
|
|
3852
|
+
scaleY: criticallyDampedSpring,
|
|
3853
|
+
scale: criticallyDampedSpring,
|
|
3854
|
+
opacity: linearTween,
|
|
3855
|
+
backgroundColor: linearTween,
|
|
3856
|
+
color: linearTween,
|
|
3857
|
+
default: criticallyDampedSpring,
|
|
3858
|
+
};
|
|
3859
|
+
const getDefaultTransition = (valueKey, { keyframes }) => {
|
|
3860
|
+
if (keyframes.length > 2) {
|
|
3861
|
+
return keyframesTransition;
|
|
3862
|
+
}
|
|
3863
|
+
else {
|
|
3864
|
+
const factory = defaultTransitions[valueKey] || defaultTransitions.default;
|
|
3865
|
+
return factory(keyframes[1]);
|
|
3866
|
+
}
|
|
3867
|
+
};
|
|
3762
3868
|
|
|
3763
3869
|
/**
|
|
3764
|
-
*
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
*
|
|
3870
|
+
* Check if a value is animatable. Examples:
|
|
3871
|
+
*
|
|
3872
|
+
* ✅: 100, "100px", "#fff"
|
|
3873
|
+
* ❌: "block", "url(2.jpg)"
|
|
3874
|
+
* @param value
|
|
3875
|
+
*
|
|
3876
|
+
* @internal
|
|
3769
3877
|
*/
|
|
3770
|
-
const
|
|
3878
|
+
const isAnimatable = (key, value) => {
|
|
3879
|
+
// If the list of keys tat might be non-animatable grows, replace with Set
|
|
3880
|
+
if (key === "zIndex")
|
|
3881
|
+
return false;
|
|
3882
|
+
// If it's a number or a keyframes array, we can animate it. We might at some point
|
|
3883
|
+
// need to do a deep isAnimatable check of keyframes, or let Popmotion handle this,
|
|
3884
|
+
// but for now lets leave it like this for performance reasons
|
|
3885
|
+
if (typeof value === "number" || Array.isArray(value))
|
|
3886
|
+
return true;
|
|
3887
|
+
if (typeof value === "string" && // It's animatable if we have a string
|
|
3888
|
+
complex.test(value) && // And it contains numbers and/or colors
|
|
3889
|
+
!value.startsWith("url(") // Unless it starts with "url("
|
|
3890
|
+
) {
|
|
3891
|
+
return true;
|
|
3892
|
+
}
|
|
3893
|
+
return false;
|
|
3894
|
+
};
|
|
3771
3895
|
|
|
3772
3896
|
/**
|
|
3773
|
-
*
|
|
3897
|
+
* Decide whether a transition is defined on a given Transition.
|
|
3898
|
+
* This filters out orchestration options and returns true
|
|
3899
|
+
* if any options are left.
|
|
3774
3900
|
*/
|
|
3775
|
-
function
|
|
3776
|
-
|
|
3777
|
-
visualElement.values.forEach((value, key) => (current[key] = value.get()));
|
|
3778
|
-
return current;
|
|
3901
|
+
function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, ...transition }) {
|
|
3902
|
+
return !!Object.keys(transition).length;
|
|
3779
3903
|
}
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
visualElement.values.forEach((value, key) => (velocity[key] = value.getVelocity()));
|
|
3786
|
-
return velocity;
|
|
3904
|
+
function isZero(value) {
|
|
3905
|
+
return (value === 0 ||
|
|
3906
|
+
(typeof value === "string" &&
|
|
3907
|
+
parseFloat(value) === 0 &&
|
|
3908
|
+
value.indexOf(" ") === -1));
|
|
3787
3909
|
}
|
|
3788
|
-
function
|
|
3789
|
-
|
|
3790
|
-
|
|
3910
|
+
function getZeroUnit(potentialUnitType) {
|
|
3911
|
+
return typeof potentialUnitType === "number"
|
|
3912
|
+
? 0
|
|
3913
|
+
: getAnimatableNone("", potentialUnitType);
|
|
3914
|
+
}
|
|
3915
|
+
function getValueTransition(transition, key) {
|
|
3916
|
+
return transition[key] || transition["default"] || transition;
|
|
3791
3917
|
}
|
|
3792
3918
|
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
visualElement.addValue(key, motionValue(value));
|
|
3919
|
+
function getKeyframes(value, valueName, target, transition) {
|
|
3920
|
+
const isTargetAnimatable = isAnimatable(valueName, target);
|
|
3921
|
+
let origin = transition.from !== undefined ? transition.from : value.get();
|
|
3922
|
+
if (origin === "none" && isTargetAnimatable && typeof target === "string") {
|
|
3923
|
+
/**
|
|
3924
|
+
* If we're trying to animate from "none", try and get an animatable version
|
|
3925
|
+
* of the target. This could be improved to work both ways.
|
|
3926
|
+
*/
|
|
3927
|
+
origin = getAnimatableNone(valueName, target);
|
|
3803
3928
|
}
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
const resolved = resolveVariant(visualElement, definition);
|
|
3807
|
-
let { transitionEnd = {}, transition = {}, ...target } = resolved ? visualElement.makeTargetAnimatable(resolved, false) : {};
|
|
3808
|
-
target = { ...target, ...transitionEnd };
|
|
3809
|
-
for (const key in target) {
|
|
3810
|
-
const value = resolveFinalValueInKeyframes(target[key]);
|
|
3811
|
-
setMotionValue(visualElement, key, value);
|
|
3929
|
+
else if (isZero(origin) && typeof target === "string") {
|
|
3930
|
+
origin = getZeroUnit(target);
|
|
3812
3931
|
}
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
var _a;
|
|
3818
|
-
const variant = visualElement.getVariant(key);
|
|
3819
|
-
variant && setTarget(visualElement, variant);
|
|
3820
|
-
(_a = visualElement.variantChildren) === null || _a === void 0 ? void 0 : _a.forEach((child) => {
|
|
3821
|
-
setVariants(child, variantLabels);
|
|
3822
|
-
});
|
|
3823
|
-
});
|
|
3824
|
-
}
|
|
3825
|
-
function setValues(visualElement, definition) {
|
|
3826
|
-
if (Array.isArray(definition)) {
|
|
3827
|
-
return setVariants(visualElement, definition);
|
|
3932
|
+
else if (!Array.isArray(target) &&
|
|
3933
|
+
isZero(target) &&
|
|
3934
|
+
typeof origin === "string") {
|
|
3935
|
+
target = getZeroUnit(origin);
|
|
3828
3936
|
}
|
|
3829
|
-
|
|
3830
|
-
|
|
3937
|
+
/**
|
|
3938
|
+
* If the target has been defined as a series of keyframes
|
|
3939
|
+
*/
|
|
3940
|
+
if (Array.isArray(target)) {
|
|
3941
|
+
/**
|
|
3942
|
+
* Ensure an initial wildcard keyframe is hydrated by the origin.
|
|
3943
|
+
* TODO: Support extra wildcard keyframes i.e [1, null, 0]
|
|
3944
|
+
*/
|
|
3945
|
+
if (target[0] === null) {
|
|
3946
|
+
target[0] = origin;
|
|
3947
|
+
}
|
|
3948
|
+
return target;
|
|
3831
3949
|
}
|
|
3832
3950
|
else {
|
|
3833
|
-
|
|
3951
|
+
return [origin, target];
|
|
3834
3952
|
}
|
|
3835
3953
|
}
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3954
|
+
|
|
3955
|
+
const featureTests = {
|
|
3956
|
+
waapi: () => Object.hasOwnProperty.call(Element.prototype, "animate"),
|
|
3957
|
+
};
|
|
3958
|
+
const results = {};
|
|
3959
|
+
const supports = {};
|
|
3960
|
+
/**
|
|
3961
|
+
* Generate features tests that cache their results.
|
|
3962
|
+
*/
|
|
3963
|
+
for (const key in featureTests) {
|
|
3964
|
+
supports[key] = () => {
|
|
3965
|
+
if (results[key] === undefined)
|
|
3966
|
+
results[key] = featureTests[key]();
|
|
3967
|
+
return results[key];
|
|
3968
|
+
};
|
|
3969
|
+
}
|
|
3970
|
+
|
|
3971
|
+
/**
|
|
3972
|
+
* A list of values that can be hardware-accelerated.
|
|
3973
|
+
*/
|
|
3974
|
+
const acceleratedValues = new Set(["opacity"]);
|
|
3975
|
+
const createMotionValueAnimation = (valueName, value, target, transition = {}) => {
|
|
3976
|
+
return (onComplete) => {
|
|
3977
|
+
const valueTransition = getValueTransition(transition, valueName) || {};
|
|
3846
3978
|
/**
|
|
3847
|
-
*
|
|
3848
|
-
*
|
|
3979
|
+
* Most transition values are currently completely overwritten by value-specific
|
|
3980
|
+
* transitions. In the future it'd be nicer to blend these transitions. But for now
|
|
3981
|
+
* delay actually does inherit from the root transition if not value-specific.
|
|
3849
3982
|
*/
|
|
3850
|
-
|
|
3851
|
-
value = targetValue[0];
|
|
3852
|
-
}
|
|
3983
|
+
const delay = valueTransition.delay || transition.delay || 0;
|
|
3853
3984
|
/**
|
|
3854
|
-
*
|
|
3855
|
-
*
|
|
3856
|
-
* if not read the value from the DOM. As an absolute fallback, take the defined target value.
|
|
3985
|
+
* Elapsed isn't a public transition option but can be passed through from
|
|
3986
|
+
* optimized appear effects in milliseconds.
|
|
3857
3987
|
*/
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3988
|
+
let { elapsed = 0 } = transition;
|
|
3989
|
+
elapsed = elapsed - secondsToMilliseconds(delay);
|
|
3990
|
+
const keyframes = getKeyframes(value, valueName, target, valueTransition);
|
|
3861
3991
|
/**
|
|
3862
|
-
*
|
|
3863
|
-
*
|
|
3992
|
+
* Check if we're able to animate between the start and end keyframes,
|
|
3993
|
+
* and throw a warning if we're attempting to animate between one that's
|
|
3994
|
+
* animatable and another that isn't.
|
|
3864
3995
|
*/
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3996
|
+
const originKeyframe = keyframes[0];
|
|
3997
|
+
const targetKeyframe = keyframes[keyframes.length - 1];
|
|
3998
|
+
const isOriginAnimatable = isAnimatable(valueName, originKeyframe);
|
|
3999
|
+
const isTargetAnimatable = isAnimatable(valueName, targetKeyframe);
|
|
4000
|
+
heyListen.warning(isOriginAnimatable === isTargetAnimatable, `You are trying to animate ${valueName} from "${originKeyframe}" to "${targetKeyframe}". ${originKeyframe} is not an animatable value - to enable this animation set ${originKeyframe} to a value animatable to ${targetKeyframe} via the \`style\` property.`);
|
|
4001
|
+
let options = {
|
|
4002
|
+
keyframes,
|
|
4003
|
+
velocity: value.getVelocity(),
|
|
4004
|
+
...valueTransition,
|
|
4005
|
+
elapsed,
|
|
4006
|
+
onUpdate: (v) => {
|
|
4007
|
+
value.set(v);
|
|
4008
|
+
valueTransition.onUpdate && valueTransition.onUpdate(v);
|
|
4009
|
+
},
|
|
4010
|
+
onComplete: () => {
|
|
4011
|
+
onComplete();
|
|
4012
|
+
valueTransition.onComplete && valueTransition.onComplete();
|
|
4013
|
+
},
|
|
4014
|
+
};
|
|
4015
|
+
if (!isOriginAnimatable ||
|
|
4016
|
+
!isTargetAnimatable ||
|
|
4017
|
+
instantAnimationState.current ||
|
|
4018
|
+
valueTransition.type === false) {
|
|
4019
|
+
/**
|
|
4020
|
+
* If we can't animate this value, or the global instant animation flag is set,
|
|
4021
|
+
* or this is simply defined as an instant transition, return an instant transition.
|
|
4022
|
+
*/
|
|
4023
|
+
return createInstantAnimation(options);
|
|
3871
4024
|
}
|
|
3872
|
-
else if (
|
|
3873
|
-
|
|
4025
|
+
else if (valueTransition.type === "inertia") {
|
|
4026
|
+
/**
|
|
4027
|
+
* If this is an inertia animation, we currently don't support pre-generating
|
|
4028
|
+
* keyframes for this as such it must always run on the main thread.
|
|
4029
|
+
*/
|
|
4030
|
+
const animation = inertia(options);
|
|
4031
|
+
return () => animation.stop();
|
|
3874
4032
|
}
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
4033
|
+
/**
|
|
4034
|
+
* If there's no transition defined for this value, we can generate
|
|
4035
|
+
* unqiue transition settings for this value.
|
|
4036
|
+
*/
|
|
4037
|
+
if (!isTransitionDefined(valueTransition)) {
|
|
4038
|
+
options = {
|
|
4039
|
+
...options,
|
|
4040
|
+
...getDefaultTransition(valueName, options),
|
|
4041
|
+
};
|
|
3878
4042
|
}
|
|
3879
|
-
if (value !== null)
|
|
3880
|
-
visualElement.setBaseTarget(key, value);
|
|
3881
|
-
}
|
|
3882
|
-
}
|
|
3883
|
-
function getOriginFromTransition(key, transition) {
|
|
3884
|
-
if (!transition)
|
|
3885
|
-
return;
|
|
3886
|
-
const valueTransition = transition[key] || transition["default"] || transition;
|
|
3887
|
-
return valueTransition.from;
|
|
3888
|
-
}
|
|
3889
|
-
function getOrigin(target, transition, visualElement) {
|
|
3890
|
-
var _a;
|
|
3891
|
-
const origin = {};
|
|
3892
|
-
for (const key in target) {
|
|
3893
|
-
const transitionOrigin = getOriginFromTransition(key, transition);
|
|
3894
|
-
origin[key] =
|
|
3895
|
-
transitionOrigin !== undefined
|
|
3896
|
-
? transitionOrigin
|
|
3897
|
-
: (_a = visualElement.getValue(key)) === null || _a === void 0 ? void 0 : _a.get();
|
|
3898
|
-
}
|
|
3899
|
-
return origin;
|
|
3900
|
-
}
|
|
3901
|
-
|
|
3902
|
-
function isWillChangeMotionValue(value) {
|
|
3903
|
-
return Boolean(isMotionValue(value) && value.add);
|
|
3904
|
-
}
|
|
3905
|
-
|
|
3906
|
-
const appearStoreId = (id, value) => `${id}: ${value}`;
|
|
3907
|
-
|
|
3908
|
-
function handoffOptimizedAppearAnimation(id, name) {
|
|
3909
|
-
const { MotionAppearAnimations } = window;
|
|
3910
|
-
const animationId = appearStoreId(id, transformProps.has(name) ? "transform" : name);
|
|
3911
|
-
const animation = MotionAppearAnimations && MotionAppearAnimations.get(animationId);
|
|
3912
|
-
if (animation) {
|
|
3913
4043
|
/**
|
|
3914
|
-
*
|
|
3915
|
-
*
|
|
3916
|
-
*
|
|
3917
|
-
* 2. As all independent transforms share a single transform animation, stopping
|
|
3918
|
-
* it synchronously would prevent subsequent transforms from handing off.
|
|
4044
|
+
* Both WAAPI and our internal animation functions use durations
|
|
4045
|
+
* as defined by milliseconds, while our external API defines them
|
|
4046
|
+
* as seconds.
|
|
3919
4047
|
*/
|
|
3920
|
-
|
|
4048
|
+
if (options.duration) {
|
|
4049
|
+
options.duration = secondsToMilliseconds(options.duration);
|
|
4050
|
+
}
|
|
4051
|
+
if (options.repeatDelay) {
|
|
4052
|
+
options.repeatDelay = secondsToMilliseconds(options.repeatDelay);
|
|
4053
|
+
}
|
|
4054
|
+
const visualElement = value.owner;
|
|
4055
|
+
const element = visualElement && visualElement.current;
|
|
4056
|
+
const canAccelerateAnimation = supports.waapi() &&
|
|
4057
|
+
acceleratedValues.has(valueName) &&
|
|
4058
|
+
!options.repeatDelay &&
|
|
4059
|
+
options.repeatType !== "mirror" &&
|
|
4060
|
+
options.damping !== 0 &&
|
|
4061
|
+
typeof options.ease !== "function" &&
|
|
4062
|
+
visualElement &&
|
|
4063
|
+
element instanceof HTMLElement &&
|
|
4064
|
+
!visualElement.getProps().onUpdate;
|
|
4065
|
+
if (canAccelerateAnimation) {
|
|
3921
4066
|
/**
|
|
3922
|
-
*
|
|
4067
|
+
* If this animation is capable of being run via WAAPI, then do so.
|
|
3923
4068
|
*/
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
}
|
|
3936
|
-
|
|
3937
|
-
const optimizedAppearDataId = "framerAppearId";
|
|
3938
|
-
const optimizedAppearDataAttribute = "data-" + camelToDash(optimizedAppearDataId);
|
|
4069
|
+
return createAcceleratedAnimation(value, valueName, options);
|
|
4070
|
+
}
|
|
4071
|
+
else {
|
|
4072
|
+
/**
|
|
4073
|
+
* Otherwise, fall back to the main thread.
|
|
4074
|
+
*/
|
|
4075
|
+
const animation = animate$1(options);
|
|
4076
|
+
return () => animation.stop();
|
|
4077
|
+
}
|
|
4078
|
+
};
|
|
4079
|
+
};
|
|
3939
4080
|
|
|
3940
4081
|
function animateVisualElement(visualElement, definition, options = {}) {
|
|
3941
4082
|
visualElement.notify("AnimationStart", definition);
|
|
@@ -4035,7 +4176,7 @@ function animateTarget(visualElement, definition, { delay = 0, transitionOverrid
|
|
|
4035
4176
|
valueTransition.elapsed = handoffOptimizedAppearAnimation(appearId, key);
|
|
4036
4177
|
}
|
|
4037
4178
|
}
|
|
4038
|
-
let animation =
|
|
4179
|
+
let animation = value.start(createMotionValueAnimation(key, value, valueTarget, valueTransition));
|
|
4039
4180
|
if (isWillChangeMotionValue(willChange)) {
|
|
4040
4181
|
willChange.add(key);
|
|
4041
4182
|
animation = animation.then(() => willChange.remove(key));
|
|
@@ -5191,7 +5332,7 @@ class VisualElementDragControls {
|
|
|
5191
5332
|
}
|
|
5192
5333
|
startAxisValueAnimation(axis, transition) {
|
|
5193
5334
|
const axisValue = this.getAxisMotionValue(axis);
|
|
5194
|
-
return
|
|
5335
|
+
return axisValue.start(createMotionValueAnimation(axis, axisValue, 0, transition));
|
|
5195
5336
|
}
|
|
5196
5337
|
stopAnimation() {
|
|
5197
5338
|
eachAxis((axis) => this.getAxisMotionValue(axis).stop());
|
|
@@ -5786,7 +5927,7 @@ function updateMotionValuesFromProps(element, next, prev) {
|
|
|
5786
5927
|
* and warn against mismatches.
|
|
5787
5928
|
*/
|
|
5788
5929
|
if (process.env.NODE_ENV === "development") {
|
|
5789
|
-
warnOnce(nextValue.version === "7.
|
|
5930
|
+
warnOnce(nextValue.version === "7.9.0", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.9.0 may not work as expected.`);
|
|
5790
5931
|
}
|
|
5791
5932
|
}
|
|
5792
5933
|
else if (isMotionValue(prevValue)) {
|
|
@@ -5794,7 +5935,7 @@ function updateMotionValuesFromProps(element, next, prev) {
|
|
|
5794
5935
|
* If we're swapping from a motion value to a static value,
|
|
5795
5936
|
* create a new motion value from that
|
|
5796
5937
|
*/
|
|
5797
|
-
element.addValue(key, motionValue(nextValue));
|
|
5938
|
+
element.addValue(key, motionValue(nextValue, { owner: element }));
|
|
5798
5939
|
if (isWillChangeMotionValue(willChange)) {
|
|
5799
5940
|
willChange.remove(key);
|
|
5800
5941
|
}
|
|
@@ -6209,7 +6350,7 @@ class VisualElement {
|
|
|
6209
6350
|
}
|
|
6210
6351
|
let value = this.values.get(key);
|
|
6211
6352
|
if (value === undefined && defaultValue !== undefined) {
|
|
6212
|
-
value = motionValue(defaultValue);
|
|
6353
|
+
value = motionValue(defaultValue, { owner: this });
|
|
6213
6354
|
this.addValue(key, value);
|
|
6214
6355
|
}
|
|
6215
6356
|
return value;
|
|
@@ -6633,7 +6774,7 @@ const layoutFeatures = {
|
|
|
6633
6774
|
*/
|
|
6634
6775
|
function animate(from, to, transition = {}) {
|
|
6635
6776
|
const value = isMotionValue(from) ? from : motionValue(from);
|
|
6636
|
-
|
|
6777
|
+
value.start(createMotionValueAnimation("", value, to, transition));
|
|
6637
6778
|
return {
|
|
6638
6779
|
stop: () => value.stop(),
|
|
6639
6780
|
isAnimating: () => value.isAnimating(),
|
|
@@ -9092,8 +9233,7 @@ function useSpring(source, config = {}) {
|
|
|
9092
9233
|
activeSpringAnimation.current.stop();
|
|
9093
9234
|
}
|
|
9094
9235
|
activeSpringAnimation.current = animate$1({
|
|
9095
|
-
|
|
9096
|
-
to: v,
|
|
9236
|
+
keyframes: [value.get(), v],
|
|
9097
9237
|
velocity: value.getVelocity(),
|
|
9098
9238
|
type: "spring",
|
|
9099
9239
|
...config,
|
|
@@ -9606,36 +9746,6 @@ function useResetProjection() {
|
|
|
9606
9746
|
return reset;
|
|
9607
9747
|
}
|
|
9608
9748
|
|
|
9609
|
-
const featureTests = {
|
|
9610
|
-
waapi: () => Object.hasOwnProperty.call(Element.prototype, "animate"),
|
|
9611
|
-
};
|
|
9612
|
-
const results = {};
|
|
9613
|
-
const supports = {};
|
|
9614
|
-
/**
|
|
9615
|
-
* Generate features tests that cache their results.
|
|
9616
|
-
*/
|
|
9617
|
-
for (const key in featureTests) {
|
|
9618
|
-
supports[key] = () => {
|
|
9619
|
-
if (results[key] === undefined)
|
|
9620
|
-
results[key] = featureTests[key]();
|
|
9621
|
-
return results[key];
|
|
9622
|
-
};
|
|
9623
|
-
}
|
|
9624
|
-
|
|
9625
|
-
const cubicBezierAsString = ([a, b, c, d]) => `cubic-bezier(${a}, ${b}, ${c}, ${d})`;
|
|
9626
|
-
|
|
9627
|
-
function animateStyle(element, valueName, keyframes, { delay, duration, ease }) {
|
|
9628
|
-
if (!supports.waapi())
|
|
9629
|
-
return undefined;
|
|
9630
|
-
const animation = element.animate({ [valueName]: keyframes }, {
|
|
9631
|
-
delay,
|
|
9632
|
-
duration,
|
|
9633
|
-
easing: Array.isArray(ease) ? cubicBezierAsString(ease) : ease,
|
|
9634
|
-
fill: "both",
|
|
9635
|
-
});
|
|
9636
|
-
return animation;
|
|
9637
|
-
}
|
|
9638
|
-
|
|
9639
9749
|
function startOptimizedAppearAnimation(element, name, keyframes, options) {
|
|
9640
9750
|
window.MotionAppearAnimations || (window.MotionAppearAnimations = new Map());
|
|
9641
9751
|
const id = element.dataset[optimizedAppearDataId];
|