framer-motion 7.8.0 → 7.8.1

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