framer-motion 7.7.3 → 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 (43) hide show
  1. package/dist/cjs/index.js +1966 -1860
  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 +23 -15
  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 +33 -39
  14. package/dist/es/animation/optimized-appear/data-id.mjs +6 -0
  15. package/dist/es/animation/optimized-appear/handoff.mjs +34 -0
  16. package/dist/es/animation/optimized-appear/start.mjs +15 -0
  17. package/dist/es/animation/optimized-appear/store-id.mjs +3 -0
  18. package/dist/es/animation/utils/default-transitions.mjs +9 -14
  19. package/dist/es/animation/utils/keyframes.mjs +41 -0
  20. package/dist/es/animation/utils/transitions.mjs +1 -171
  21. package/dist/es/animation/waapi/easing.mjs +3 -0
  22. package/dist/es/animation/waapi/index.mjs +16 -0
  23. package/dist/es/animation/waapi/supports.mjs +17 -0
  24. package/dist/es/gestures/drag/VisualElementDragControls.mjs +2 -2
  25. package/dist/es/index.mjs +6 -3
  26. package/dist/es/render/utils/animation.mjs +15 -3
  27. package/dist/es/render/utils/motion-values.mjs +1 -1
  28. package/dist/es/utils/delay.mjs +3 -0
  29. package/dist/es/value/index.mjs +2 -2
  30. package/dist/es/value/use-spring.mjs +1 -2
  31. package/dist/framer-motion.dev.js +1971 -1865
  32. package/dist/framer-motion.js +1 -1
  33. package/dist/index.d.ts +424 -341
  34. package/dist/projection.dev.js +1655 -1623
  35. package/dist/size-rollup-dom-animation-assets.js +1 -1
  36. package/dist/size-rollup-dom-animation.js +1 -1
  37. package/dist/size-rollup-dom-max-assets.js +1 -1
  38. package/dist/size-rollup-dom-max.js +1 -1
  39. package/dist/size-rollup-motion.js +1 -1
  40. package/dist/size-webpack-dom-animation.js +1 -1
  41. package/dist/size-webpack-dom-max.js +1 -1
  42. package/dist/three-entry.d.ts +289 -282
  43. package/package.json +11 -9
package/dist/cjs/index.js CHANGED
@@ -1869,2050 +1869,2120 @@ 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
- };
1879
+ const isZeroValueString = (v) => /^0[^.\s]+$/.test(v);
2063
1880
 
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
- },
1881
+ const frameData = {
1882
+ delta: 0,
1883
+ timestamp: 0,
2078
1884
  };
2079
1885
 
2080
- const color = {
2081
- test: (v) => rgba.test(v) || hex.test(v) || hsla.test(v),
2082
- parse: (v) => {
2083
- if (rgba.test(v)) {
2084
- return rgba.parse(v);
2085
- }
2086
- else if (hsla.test(v)) {
2087
- return hsla.parse(v);
2088
- }
2089
- else {
2090
- return hex.parse(v);
2091
- }
2092
- },
2093
- transform: (v) => {
2094
- return isString(v)
2095
- ? v
2096
- : v.hasOwnProperty("red")
2097
- ? rgba.transform(v)
2098
- : hsla.transform(v);
2099
- },
2100
- };
1886
+ /*
1887
+ Detect and load appropriate clock setting for the execution environment
1888
+ */
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);
2101
1896
 
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));
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;
1982
+ }
1983
+
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);
2131
2029
  }
2132
- return { values, numColors, numNumbers, tokenised: v };
2030
+ };
2031
+ const startLoop = () => {
2032
+ runNextFrame = true;
2033
+ useDefaultElapsed = true;
2034
+ if (!isProcessing)
2035
+ onNextFrame(processFrame);
2036
+ };
2037
+
2038
+ function addUniqueItem(arr, item) {
2039
+ if (arr.indexOf(item) === -1)
2040
+ arr.push(item);
2133
2041
  }
2134
- function parse(v) {
2135
- return analyseComplexValue(v).values;
2042
+ function removeItem(arr, item) {
2043
+ const index = arr.indexOf(item);
2044
+ if (index > -1)
2045
+ arr.splice(index, 1);
2136
2046
  }
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]));
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);
2054
+ }
2055
+ return arr;
2056
+ }
2057
+
2058
+ class SubscriptionManager {
2059
+ constructor() {
2060
+ this.subscriptions = [];
2061
+ }
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);
2146
2075
  }
2147
- return output;
2148
- };
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
+ }
2085
+ }
2086
+ }
2087
+ getSize() {
2088
+ return this.subscriptions.length;
2089
+ }
2090
+ clear() {
2091
+ this.subscriptions.length = 0;
2092
+ }
2149
2093
  }
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));
2094
+
2095
+ /*
2096
+ Convert velocity into velocity per second
2097
+
2098
+ @param [number]: Unit per frame
2099
+ @param [number]: Frame duration in ms
2100
+ */
2101
+ function velocityPerSecond(velocity, frameDuration) {
2102
+ return frameDuration ? velocity * (1000 / frameDuration) : 0;
2155
2103
  }
2156
- const complex = { test, parse, createTransformer, getAnimatableNone: getAnimatableNone$1 };
2157
2104
 
2105
+ const isFloat = (value) => {
2106
+ return !isNaN(parseFloat(value));
2107
+ };
2158
2108
  /**
2159
- * Check if a value is animatable. Examples:
2160
- *
2161
- * ✅: 100, "100px", "#fff"
2162
- * ❌: "block", "url(2.jpg)"
2163
- * @param value
2109
+ * `MotionValue` is used to track the state and velocity of motion values.
2164
2110
  *
2165
- * @internal
2111
+ * @public
2166
2112
  */
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;
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);
2181
2216
  }
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;
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);
2227
2267
  }
2228
- else {
2229
- transitionFactory =
2230
- defaultTransitions[valueKey] || defaultTransitions.default;
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;
2291
+ }
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);
2310
+ }
2311
+ else {
2312
+ this.passiveEffect(v, this.updateAndNotify);
2313
+ }
2314
+ }
2315
+ /**
2316
+ * Returns the latest state of `MotionValue`
2317
+ *
2318
+ * @returns - The latest state of `MotionValue`
2319
+ *
2320
+ * @public
2321
+ */
2322
+ get() {
2323
+ return this.current;
2231
2324
  }
2232
- return { to, ...transitionFactory(to) };
2233
- };
2234
-
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 + ")";
2251
- }
2252
- const functionRegex = /([a-z-]*)\(.*?\)/g;
2253
- const filter = {
2254
- ...complex,
2255
- getAnimatableNone: (v) => {
2256
- const functions = v.match(functionRegex);
2257
- return functions ? functions.map(applyDefaultFilter).join(" ") : v;
2258
- },
2259
- };
2260
-
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,
2280
- };
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,
2297
- };
2298
-
2299
- /*
2300
- Detect and load appropriate clock setting for the execution environment
2301
- */
2302
- const defaultTimestep = (1 / 60) * 1000;
2303
- const getCurrentTime = typeof performance !== "undefined"
2304
- ? () => performance.now()
2305
- : () => Date.now();
2306
- const onNextFrame = typeof window !== "undefined"
2307
- ? (callback) => window.requestAnimationFrame(callback)
2308
- : (callback) => setTimeout(() => callback(getCurrentTime()), defaultTimestep);
2309
-
2310
- function createRenderStep(runNextFrame) {
2311
2325
  /**
2312
- * We create and reuse two arrays, one to queue jobs for the current frame
2313
- * and one for the next. We reuse to avoid triggering GC after x frames.
2326
+ * @public
2314
2327
  */
2315
- let toRun = [];
2316
- let toRunNextFrame = [];
2328
+ getPrevious() {
2329
+ return this.prev;
2330
+ }
2317
2331
  /**
2332
+ * Returns the latest velocity of `MotionValue`
2333
+ *
2334
+ * @returns - The latest velocity of `MotionValue`. Returns `0` if the state is non-numerical.
2318
2335
  *
2336
+ * @public
2319
2337
  */
2320
- let numToRun = 0;
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;
2345
+ }
2321
2346
  /**
2322
- * Track whether we're currently processing jobs in this step. This way
2323
- * we can decide whether to schedule new jobs for this frame or next.
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
2324
2357
  */
2325
- let isProcessing = false;
2326
- let flushNextFrame = false;
2358
+ start(animation) {
2359
+ this.stop();
2360
+ return new Promise((resolve) => {
2361
+ this.hasAnimated = true;
2362
+ this.stopAnimation = animation(resolve);
2363
+ }).then(() => this.clearAnimation());
2364
+ }
2327
2365
  /**
2328
- * A set of processes which were marked keepAlive when scheduled.
2366
+ * Stop the currently active animation.
2367
+ *
2368
+ * @public
2329
2369
  */
2330
- const toKeepAlive = new WeakSet();
2331
- const step = {
2332
- /**
2333
- * Schedule a process to run on the next frame.
2334
- */
2335
- schedule: (callback, keepAlive = false, immediate = false) => {
2336
- const addToCurrentFrame = immediate && isProcessing;
2337
- const buffer = addToCurrentFrame ? toRun : toRunNextFrame;
2338
- if (keepAlive)
2339
- toKeepAlive.add(callback);
2340
- // If the buffer doesn't already contain this callback, add it
2341
- if (buffer.indexOf(callback) === -1) {
2342
- buffer.push(callback);
2343
- // If we're adding it to the currently running buffer, update its measured size
2344
- if (addToCurrentFrame && isProcessing)
2345
- numToRun = toRun.length;
2346
- }
2347
- return callback;
2348
- },
2349
- /**
2350
- * Cancel the provided callback from running on the next frame.
2351
- */
2352
- cancel: (callback) => {
2353
- const index = toRunNextFrame.indexOf(callback);
2354
- if (index !== -1)
2355
- toRunNextFrame.splice(index, 1);
2356
- toKeepAlive.delete(callback);
2357
- },
2358
- /**
2359
- * Execute all schedule callbacks.
2360
- */
2361
- process: (frameData) => {
2362
- /**
2363
- * If we're already processing we've probably been triggered by a flushSync
2364
- * inside an existing process. Instead of executing, mark flushNextFrame
2365
- * as true and ensure we flush the following frame at the end of this one.
2366
- */
2367
- if (isProcessing) {
2368
- flushNextFrame = true;
2369
- return;
2370
- }
2371
- isProcessing = true;
2372
- [toRun, toRunNextFrame] = [toRunNextFrame, toRun];
2373
- // Clear the next frame list
2374
- toRunNextFrame.length = 0;
2375
- // Execute this frame
2376
- numToRun = toRun.length;
2377
- if (numToRun) {
2378
- for (let i = 0; i < numToRun; i++) {
2379
- const callback = toRun[i];
2380
- callback(frameData);
2381
- if (toKeepAlive.has(callback)) {
2382
- step.schedule(callback);
2383
- runNextFrame();
2384
- }
2385
- }
2386
- }
2387
- isProcessing = false;
2388
- if (flushNextFrame) {
2389
- flushNextFrame = false;
2390
- step.process(frameData);
2391
- }
2392
- },
2393
- };
2394
- return step;
2370
+ stop() {
2371
+ if (this.stopAnimation)
2372
+ this.stopAnimation();
2373
+ this.clearAnimation();
2374
+ }
2375
+ /**
2376
+ * Returns `true` if this value is currently animating.
2377
+ *
2378
+ * @public
2379
+ */
2380
+ isAnimating() {
2381
+ return !!this.stopAnimation;
2382
+ }
2383
+ clearAnimation() {
2384
+ this.stopAnimation = null;
2385
+ }
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();
2399
+ }
2400
+ }
2401
+ function motionValue(init) {
2402
+ return new MotionValue(init);
2395
2403
  }
2396
2404
 
2397
- const frameData = {
2398
- delta: 0,
2399
- timestamp: 0,
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)));
2400
2412
  };
2401
-
2402
- const maxElapsed = 40;
2403
- let useDefaultElapsed = true;
2404
- let runNextFrame = false;
2405
- let isProcessing = false;
2406
- const stepsOrder = [
2407
- "read",
2408
- "update",
2409
- "preRender",
2410
- "render",
2411
- "postRender",
2412
- ];
2413
- const steps = stepsOrder.reduce((acc, key) => {
2414
- acc[key] = createRenderStep(() => (runNextFrame = true));
2415
- return acc;
2416
- }, {});
2417
- const sync = stepsOrder.reduce((acc, key) => {
2418
- const step = steps[key];
2419
- acc[key] = (process, keepAlive = false, immediate = false) => {
2420
- if (!runNextFrame)
2421
- startLoop();
2422
- return step.schedule(process, keepAlive, immediate);
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,
2423
2422
  };
2424
- return acc;
2425
- }, {});
2426
- const cancelSync = stepsOrder.reduce((acc, key) => {
2427
- acc[key] = steps[key].cancel;
2428
- return acc;
2429
- }, {});
2430
- const flushSync = stepsOrder.reduce((acc, key) => {
2431
- acc[key] = () => steps[key].process(frameData);
2432
- return acc;
2433
- }, {});
2434
- const processStep = (stepId) => steps[stepId].process(frameData);
2435
- const processFrame = (timestamp) => {
2436
- runNextFrame = false;
2437
- frameData.delta = useDefaultElapsed
2438
- ? defaultTimestep
2439
- : Math.max(Math.min(timestamp - frameData.timestamp, maxElapsed), 1);
2440
- frameData.timestamp = timestamp;
2441
- isProcessing = true;
2442
- stepsOrder.forEach(processStep);
2443
- isProcessing = false;
2444
- if (runNextFrame) {
2445
- useDefaultElapsed = false;
2446
- onNextFrame(processFrame);
2447
- }
2448
- };
2449
- const startLoop = () => {
2450
- runNextFrame = true;
2451
- useDefaultElapsed = true;
2452
- if (!isProcessing)
2453
- onNextFrame(processFrame);
2454
2423
  };
2455
2424
 
2456
- function delay(callback, timeout) {
2457
- const start = performance.now();
2458
- const checkElapsed = ({ timestamp }) => {
2459
- const elapsed = timestamp - start;
2460
- if (elapsed >= timeout) {
2461
- cancelSync.read(checkElapsed);
2462
- callback(elapsed - timeout);
2463
- }
2464
- };
2465
- sync.read(checkElapsed, true);
2466
- return () => cancelSync.read(checkElapsed);
2467
- }
2468
-
2469
- /*
2470
- Value in range from progress
2471
-
2472
- Given a lower limit and an upper limit, we return the value within
2473
- that range as expressed by progress (usually a number from 0 to 1)
2474
-
2475
- So progress = 0.5 would change
2476
-
2477
- from -------- to
2478
-
2479
- to
2480
-
2481
- from ---- to
2482
-
2483
- E.g. from = 10, to = 20, progress = 0.5 => 15
2484
-
2485
- @param [number]: Lower limit of range
2486
- @param [number]: Upper limit of range
2487
- @param [number]: The progress between lower and upper limits expressed 0-1
2488
- @return [number]: Value as calculated from progress within range (not limited within range)
2489
- */
2490
- const mix = (from, to, progress) => -progress * from + progress * to + from;
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
+ };
2491
2443
 
2492
- // Adapted from https://gist.github.com/mjackson/5311256
2493
- function hueToRgb(p, q, t) {
2494
- if (t < 0)
2495
- t += 1;
2496
- if (t > 1)
2497
- t -= 1;
2498
- if (t < 1 / 6)
2499
- return p + (q - p) * 6 * t;
2500
- if (t < 1 / 2)
2501
- return q;
2502
- if (t < 2 / 3)
2503
- return p + (q - p) * (2 / 3 - t) * 6;
2504
- return p;
2505
- }
2506
- function hslaToRgba({ hue, saturation, lightness, alpha }) {
2507
- hue /= 360;
2508
- saturation /= 100;
2509
- lightness /= 100;
2510
- let red = 0;
2511
- let green = 0;
2512
- let blue = 0;
2513
- if (!saturation) {
2514
- red = green = blue = lightness;
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
2515
2456
  }
2516
2457
  else {
2517
- const q = lightness < 0.5
2518
- ? lightness * (1 + saturation)
2519
- : lightness + saturation - lightness * saturation;
2520
- const p = 2 * lightness - q;
2521
- red = hueToRgb(p, q, hue + 1 / 3);
2522
- green = hueToRgb(p, q, hue);
2523
- blue = hueToRgb(p, q, hue - 1 / 3);
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;
2524
2466
  }
2525
2467
  return {
2526
- red: Math.round(red * 255),
2527
- green: Math.round(green * 255),
2528
- blue: Math.round(blue * 255),
2529
- alpha,
2468
+ red: parseInt(r, 16),
2469
+ green: parseInt(g, 16),
2470
+ blue: parseInt(b, 16),
2471
+ alpha: a ? parseInt(a, 16) / 255 : 1,
2530
2472
  };
2531
2473
  }
2474
+ const hex = {
2475
+ test: isColorString("#"),
2476
+ parse: parseHex,
2477
+ transform: rgba.transform,
2478
+ };
2532
2479
 
2533
- // Linear color space blending
2534
- // Explained https://www.youtube.com/watch?v=LKnqECcg6Gw
2535
- // Demonstrated http://codepen.io/osublake/pen/xGVVaN
2536
- const mixLinearColor = (from, to, v) => {
2537
- const fromExpo = from * from;
2538
- return Math.sqrt(Math.max(0, v * (to * to - fromExpo) + fromExpo));
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
+ },
2539
2494
  };
2540
- const colorTypes = [hex, rgba, hsla];
2541
- const getColorType = (v) => colorTypes.find((type) => type.test(v));
2542
- function asRGBA(color) {
2543
- const type = getColorType(color);
2544
- heyListen.invariant(Boolean(type), `'${color}' is not an animatable color. Use the equivalent color code instead.`);
2545
- let model = type.parse(color);
2546
- if (type === hsla) {
2547
- // TODO Remove this cast - needed since Framer Motion's stricter typing
2548
- model = hslaToRgba(model);
2549
- }
2550
- return model;
2551
- }
2552
- const mixColor = (from, to) => {
2553
- const fromRGBA = asRGBA(from);
2554
- const toRGBA = asRGBA(to);
2555
- const blended = { ...fromRGBA };
2556
- return (v) => {
2557
- blended.red = mixLinearColor(fromRGBA.red, toRGBA.red, v);
2558
- blended.green = mixLinearColor(fromRGBA.green, toRGBA.green, v);
2559
- blended.blue = mixLinearColor(fromRGBA.blue, toRGBA.blue, v);
2560
- blended.alpha = mix(fromRGBA.alpha, toRGBA.alpha, v);
2561
- return rgba.transform(blended);
2562
- };
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);
2501
+ }
2502
+ else if (hsla.test(v)) {
2503
+ return hsla.parse(v);
2504
+ }
2505
+ else {
2506
+ return hex.parse(v);
2507
+ }
2508
+ },
2509
+ transform: (v) => {
2510
+ return isString(v)
2511
+ ? v
2512
+ : v.hasOwnProperty("red")
2513
+ ? rgba.transform(v)
2514
+ : hsla.transform(v);
2515
+ },
2563
2516
  };
2564
2517
 
2565
- function getMixer$1(origin, target) {
2566
- if (typeof origin === "number") {
2567
- return (v) => mix(origin, target, v);
2568
- }
2569
- else if (color.test(origin)) {
2570
- return mixColor(origin, target);
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));
2571
2541
  }
2572
- else {
2573
- return mixComplex(origin, target);
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));
2574
2547
  }
2548
+ return { values, numColors, numNumbers, tokenised: v };
2575
2549
  }
2576
- const mixArray = (from, to) => {
2577
- const output = [...from];
2578
- const numValues = output.length;
2579
- const blendValue = from.map((fromThis, i) => getMixer$1(fromThis, to[i]));
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;
2580
2556
  return (v) => {
2557
+ let output = tokenised;
2581
2558
  for (let i = 0; i < numValues; i++) {
2582
- output[i] = blendValue[i](v);
2583
- }
2584
- return output;
2585
- };
2586
- };
2587
- const mixObject = (origin, target) => {
2588
- const output = { ...origin, ...target };
2589
- const blendValue = {};
2590
- for (const key in output) {
2591
- if (origin[key] !== undefined && target[key] !== undefined) {
2592
- blendValue[key] = getMixer$1(origin[key], target[key]);
2593
- }
2594
- }
2595
- return (v) => {
2596
- for (const key in blendValue) {
2597
- output[key] = blendValue[key](v);
2559
+ output = output.replace(i < numColors ? colorToken : numberToken, i < numColors
2560
+ ? color.transform(v[i])
2561
+ : sanitize(v[i]));
2598
2562
  }
2599
2563
  return output;
2600
2564
  };
2601
- };
2602
- const mixComplex = (origin, target) => {
2603
- const template = complex.createTransformer(target);
2604
- const originStats = analyseComplexValue(origin);
2605
- const targetStats = analyseComplexValue(target);
2606
- const canInterpolate = originStats.numColors === targetStats.numColors &&
2607
- originStats.numNumbers >= targetStats.numNumbers;
2608
- if (canInterpolate) {
2609
- return pipe(mixArray(originStats.values, targetStats.values), template);
2610
- }
2611
- else {
2612
- 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.`);
2613
- return (p) => `${p > 0 ? target : origin}`;
2614
- }
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 };
2573
+
2574
+ /**
2575
+ * Properties that should default to 1 or 100%
2576
+ */
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 + ")";
2590
+ }
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
+ },
2615
2598
  };
2616
2599
 
2617
- /*
2618
- Progress within given range
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,
2619
+ };
2620
+ /**
2621
+ * Gets the default ValueType for the provided value key
2622
+ */
2623
+ const getDefaultValueType = (key) => defaultValueTypes[key];
2619
2624
 
2620
- Given a lower limit and an upper limit, we return the progress
2621
- (expressed as a number 0-1) represented by the given value, and
2622
- limit that progress to within 0-1.
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
+ }
2623
2633
 
2624
- @param [number]: Lower limit
2625
- @param [number]: Upper limit
2626
- @param [number]: Value to find progress within given range
2627
- @return [number]: Progress of value within range as expressed 0-1
2628
- */
2629
- const progress = (from, to, value) => {
2630
- const toFromDifference = to - from;
2631
- return toFromDifference === 0 ? 1 : (value - from) / toFromDifference;
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,
2632
2645
  };
2633
2646
 
2634
- const mixNumber = (from, to) => (p) => mix(from, to, p);
2635
- function detectMixerFactory(v) {
2636
- if (typeof v === "number") {
2637
- return mixNumber;
2638
- }
2639
- else if (typeof v === "string") {
2640
- if (color.test(v)) {
2641
- return mixColor;
2642
- }
2643
- else {
2644
- return mixComplex;
2645
- }
2646
- }
2647
- else if (Array.isArray(v)) {
2648
- return mixArray;
2649
- }
2650
- else if (typeof v === "object") {
2651
- return mixObject;
2652
- }
2653
- return mixNumber;
2654
- }
2655
- function createMixers(output, ease, customMixer) {
2656
- const mixers = [];
2657
- const mixerFactory = customMixer || detectMixerFactory(output[0]);
2658
- const numMixers = output.length - 1;
2659
- for (let i = 0; i < numMixers; i++) {
2660
- let mixer = mixerFactory(output[i], output[i + 1]);
2661
- if (ease) {
2662
- const easingFunction = Array.isArray(ease) ? ease[i] : ease;
2663
- mixer = pipe(easingFunction, mixer);
2664
- }
2665
- mixers.push(mixer);
2666
- }
2667
- return mixers;
2668
- }
2669
2647
  /**
2670
- * Create a function that maps from a numerical input array to a generic output array.
2671
- *
2672
- * Accepts:
2673
- * - Numbers
2674
- * - Colors (hex, hsl, hsla, rgb, rgba)
2675
- * - Complex (combinations of one or more numbers or strings)
2676
- *
2677
- * ```jsx
2678
- * const mixColor = interpolate([0, 1], ['#fff', '#000'])
2679
- *
2680
- * mixColor(0.5) // 'rgba(128, 128, 128, 1)'
2681
- * ```
2682
- *
2683
- * TODO Revist this approach once we've moved to data models for values,
2684
- * probably not needed to pregenerate mixer functions.
2685
- *
2686
- * @public
2648
+ * A list of value types commonly used for dimensions
2687
2649
  */
2688
- function interpolate(input, output, { clamp: isClamp = true, ease, mixer } = {}) {
2689
- const inputLength = input.length;
2690
- heyListen.invariant(inputLength === output.length, "Both input and output ranges must be the same length");
2691
- 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.");
2692
- // If input runs highest -> lowest, reverse both arrays
2693
- if (input[0] > input[inputLength - 1]) {
2694
- input = [...input].reverse();
2695
- output = [...output].reverse();
2696
- }
2697
- const mixers = createMixers(output, ease, mixer);
2698
- const numMixers = mixers.length;
2699
- const interpolator = (v) => {
2700
- let i = 0;
2701
- if (numMixers > 1) {
2702
- for (; i < input.length - 2; i++) {
2703
- if (v < input[i + 1])
2704
- break;
2705
- }
2706
- }
2707
- const progressInRange = progress(input[i], input[i + 1], v);
2708
- return mixers[i](progressInRange);
2709
- };
2710
- return isClamp
2711
- ? (v) => interpolator(clamp(input[0], input[inputLength - 1], v))
2712
- : interpolator;
2713
- }
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));
2714
2655
 
2715
- function defaultEasing(values, easing) {
2716
- return values.map(() => easing || easeInOut).splice(0, values.length - 1);
2717
- }
2718
- function defaultOffset(values) {
2719
- const numValues = values.length;
2720
- return values.map((_value, i) => i !== 0 ? i / (numValues - 1) : 0);
2721
- }
2722
- function convertOffsetToTimes(offset, duration) {
2723
- return offset.map((o) => o * duration);
2724
- }
2725
- function keyframes({ from = 0, to = 1, ease, offset, duration = 300, }) {
2726
- /**
2727
- * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
2728
- * to reduce GC during animation.
2729
- */
2730
- const state = { done: false, value: from };
2731
- /**
2732
- * Convert values to an array if they've been given as from/to options
2733
- */
2734
- const values = Array.isArray(to) ? to : [from, to];
2735
- /**
2736
- * Create a times array based on the provided 0-1 offsets
2737
- */
2738
- const times = convertOffsetToTimes(
2739
- // Only use the provided offsets if they're the correct length
2740
- // TODO Maybe we should warn here if there's a length mismatch
2741
- offset && offset.length === values.length
2742
- ? offset
2743
- : defaultOffset(values), duration);
2744
- function createInterpolator() {
2745
- return interpolate(times, values, {
2746
- ease: Array.isArray(ease) ? ease : defaultEasing(values, ease),
2747
- });
2748
- }
2749
- let interpolator = createInterpolator();
2750
- return {
2751
- next: (t) => {
2752
- state.value = interpolator(t);
2753
- state.done = t >= duration;
2754
- return state;
2755
- },
2756
- flipTarget: () => {
2757
- values.reverse();
2758
- interpolator = createInterpolator();
2759
- },
2760
- };
2761
- }
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));
2762
2664
 
2763
- const safeMin = 0.001;
2764
- const minDuration = 0.01;
2765
- const maxDuration = 10.0;
2766
- const minDamping = 0.05;
2767
- const maxDamping = 1;
2768
- function findSpring({ duration = 800, bounce = 0.25, velocity = 0, mass = 1, }) {
2769
- let envelope;
2770
- let derivative;
2771
- heyListen.warning(duration <= maxDuration * 1000, "Spring duration must be 10 seconds or less");
2772
- let dampingRatio = 1 - bounce;
2773
- /**
2774
- * Restrict dampingRatio and duration to within acceptable ranges.
2775
- */
2776
- dampingRatio = clamp(minDamping, maxDamping, dampingRatio);
2777
- duration = clamp(minDuration, maxDuration, duration / 1000);
2778
- if (dampingRatio < 1) {
2779
- /**
2780
- * Underdamped spring
2781
- */
2782
- envelope = (undampedFreq) => {
2783
- const exponentialDecay = undampedFreq * dampingRatio;
2784
- const delta = exponentialDecay * duration;
2785
- const a = exponentialDecay - velocity;
2786
- const b = calcAngularFreq(undampedFreq, dampingRatio);
2787
- const c = Math.exp(-delta);
2788
- return safeMin - (a / b) * c;
2789
- };
2790
- derivative = (undampedFreq) => {
2791
- const exponentialDecay = undampedFreq * dampingRatio;
2792
- const delta = exponentialDecay * duration;
2793
- const d = delta * velocity + velocity;
2794
- const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
2795
- const f = Math.exp(-delta);
2796
- const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
2797
- const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
2798
- return (factor * ((d - e) * f)) / g;
2799
- };
2800
- }
2801
- else {
2802
- /**
2803
- * Critically-damped spring
2804
- */
2805
- envelope = (undampedFreq) => {
2806
- const a = Math.exp(-undampedFreq * duration);
2807
- const b = (undampedFreq - velocity) * duration + 1;
2808
- return -safeMin + a * b;
2809
- };
2810
- derivative = (undampedFreq) => {
2811
- const a = Math.exp(-undampedFreq * duration);
2812
- const b = (velocity - undampedFreq) * (duration * duration);
2813
- return a * b;
2814
- };
2815
- }
2816
- const initialGuess = 5 / duration;
2817
- const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
2818
- duration = duration * 1000;
2819
- if (isNaN(undampedFreq)) {
2820
- return {
2821
- stiffness: 100,
2822
- damping: 10,
2823
- duration,
2824
- };
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;
2672
+ }
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;
2680
+ }
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));
2684
+ }
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);
2825
2693
  }
2826
2694
  else {
2827
- const stiffness = Math.pow(undampedFreq, 2) * mass;
2828
- return {
2829
- stiffness,
2830
- damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
2831
- duration,
2832
- };
2695
+ visualElement.addValue(key, motionValue(value));
2833
2696
  }
2834
2697
  }
2835
- const rootIterations = 12;
2836
- function approximateRoot(envelope, derivative, initialGuess) {
2837
- let result = initialGuess;
2838
- for (let i = 1; i < rootIterations; i++) {
2839
- result = result - envelope(result) / derivative(result);
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);
2840
2705
  }
2841
- return result;
2842
2706
  }
2843
- function calcAngularFreq(undampedFreq, dampingRatio) {
2844
- return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
2845
- }
2846
-
2847
- const durationKeys = ["duration", "bounce"];
2848
- const physicsKeys = ["stiffness", "damping", "mass"];
2849
- function isSpringType(options, keys) {
2850
- return keys.some((key) => options[key] !== undefined);
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
+ });
2851
2717
  }
2852
- function getSpringOptions(options) {
2853
- let springOptions = {
2854
- velocity: 0.0,
2855
- stiffness: 100,
2856
- damping: 10,
2857
- mass: 1.0,
2858
- isResolvedFromDuration: false,
2859
- ...options,
2860
- };
2861
- // stiffness/damping/mass overrides duration/bounce
2862
- if (!isSpringType(options, physicsKeys) &&
2863
- isSpringType(options, durationKeys)) {
2864
- const derived = findSpring(options);
2865
- springOptions = {
2866
- ...springOptions,
2867
- ...derived,
2868
- velocity: 0.0,
2869
- mass: 1.0,
2870
- };
2871
- springOptions.isResolvedFromDuration = true;
2718
+ function setValues(visualElement, definition) {
2719
+ if (Array.isArray(definition)) {
2720
+ return setVariants(visualElement, definition);
2721
+ }
2722
+ else if (typeof definition === "string") {
2723
+ return setVariants(visualElement, [definition]);
2724
+ }
2725
+ else {
2726
+ setTarget(visualElement, definition);
2872
2727
  }
2873
- return springOptions;
2874
2728
  }
2875
- /**
2876
- * This is based on the spring implementation of Wobble https://github.com/skevy/wobble
2877
- */
2878
- function spring({ from = 0.0, to = 1.0, restSpeed = 2, restDelta = 0.01, ...options }) {
2879
- /**
2880
- * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
2881
- * to reduce GC during animation.
2882
- */
2883
- const state = { done: false, value: from };
2884
- let { stiffness, damping, mass, velocity, duration, isResolvedFromDuration, } = getSpringOptions(options);
2885
- let resolveSpring = zero;
2886
- let resolveVelocity = zero;
2887
- function createSpring() {
2888
- const initialVelocity = velocity ? -(velocity / 1000) : 0.0;
2889
- const initialDelta = to - from;
2890
- const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
2891
- const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
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;
2892
2739
  /**
2893
- * If we're working within what looks like a 0-1 range, change the default restDelta
2894
- * to 0.01
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.
2895
2742
  */
2896
- if (restDelta === undefined) {
2897
- restDelta = Math.min(Math.abs(to - from) / 100, 0.4);
2743
+ if (Array.isArray(targetValue)) {
2744
+ value = targetValue[0];
2898
2745
  }
2899
- if (dampingRatio < 1) {
2900
- const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
2901
- // Underdamped spring
2902
- resolveSpring = (t) => {
2903
- const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
2904
- return (to -
2905
- envelope *
2906
- (((initialVelocity +
2907
- dampingRatio * undampedAngularFreq * initialDelta) /
2908
- angularFreq) *
2909
- Math.sin(angularFreq * t) +
2910
- initialDelta * Math.cos(angularFreq * t)));
2911
- };
2912
- resolveVelocity = (t) => {
2913
- // TODO Resolve these calculations with the above
2914
- const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
2915
- return (dampingRatio *
2916
- undampedAngularFreq *
2917
- envelope *
2918
- ((Math.sin(angularFreq * t) *
2919
- (initialVelocity +
2920
- dampingRatio *
2921
- undampedAngularFreq *
2922
- initialDelta)) /
2923
- angularFreq +
2924
- initialDelta * Math.cos(angularFreq * t)) -
2925
- envelope *
2926
- (Math.cos(angularFreq * t) *
2927
- (initialVelocity +
2928
- dampingRatio *
2929
- undampedAngularFreq *
2930
- initialDelta) -
2931
- angularFreq *
2932
- initialDelta *
2933
- Math.sin(angularFreq * t)));
2934
- };
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];
2935
2753
  }
2936
- else if (dampingRatio === 1) {
2937
- // Critically damped spring
2938
- resolveSpring = (t) => to -
2939
- Math.exp(-undampedAngularFreq * t) *
2940
- (initialDelta +
2941
- (initialVelocity + undampedAngularFreq * initialDelta) *
2942
- t);
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);
2943
2764
  }
2944
- else {
2945
- // Overdamped spring
2946
- const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
2947
- resolveSpring = (t) => {
2948
- const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
2949
- // When performing sinh or cosh values can hit Infinity so we cap them here
2950
- const freqForT = Math.min(dampedAngularFreq * t, 300);
2951
- return (to -
2952
- (envelope *
2953
- ((initialVelocity +
2954
- dampingRatio * undampedAngularFreq * initialDelta) *
2955
- Math.sinh(freqForT) +
2956
- dampedAngularFreq *
2957
- initialDelta *
2958
- Math.cosh(freqForT))) /
2959
- dampedAngularFreq);
2960
- };
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;
2961
2771
  }
2772
+ if (value !== null)
2773
+ visualElement.setBaseTarget(key, value);
2962
2774
  }
2963
- createSpring();
2964
- return {
2965
- next: (t) => {
2966
- const current = resolveSpring(t);
2967
- if (!isResolvedFromDuration) {
2968
- const currentVelocity = resolveVelocity(t) * 1000;
2969
- const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed;
2970
- const isBelowDisplacementThreshold = Math.abs(to - current) <= restDelta;
2971
- state.done =
2972
- isBelowVelocityThreshold && isBelowDisplacementThreshold;
2973
- }
2974
- else {
2975
- state.done = t >= duration;
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();
2791
+ }
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) {
2806
+ /**
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.
2812
+ */
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);
2976
2820
  }
2977
- state.value = state.done ? to : current;
2978
- return state;
2979
- },
2980
- flipTarget: () => {
2981
- velocity = -velocity;
2982
- [from, to] = [to, from];
2983
- createSpring();
2984
- },
2821
+ catch (e) { }
2822
+ });
2823
+ return animation.currentTime || 0;
2824
+ }
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 () => { };
2850
+ }
2851
+
2852
+ /**
2853
+ * Timeout defined in ms
2854
+ */
2855
+ function delay(callback, timeout) {
2856
+ const start = performance.now();
2857
+ const checkElapsed = ({ timestamp }) => {
2858
+ const elapsed = timestamp - start;
2859
+ if (elapsed >= timeout) {
2860
+ cancelSync.read(checkElapsed);
2861
+ callback(elapsed - timeout);
2862
+ }
2985
2863
  };
2864
+ sync.read(checkElapsed, true);
2865
+ return () => cancelSync.read(checkElapsed);
2986
2866
  }
2987
- spring.needsInterpolation = (a, b) => typeof a === "string" || typeof b === "string";
2988
- const zero = (_t) => 0;
2989
2867
 
2990
- function decay({ velocity = 0, from = 0, power = 0.8, timeConstant = 350, restDelta = 0.5, modifyTarget, }) {
2991
- /**
2992
- * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
2993
- * to reduce GC during animation.
2994
- */
2995
- const state = { done: false, value: from };
2996
- let amplitude = power * velocity;
2997
- const ideal = from + amplitude;
2998
- const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
2999
- /**
3000
- * If the target has changed we need to re-calculate the amplitude, otherwise
3001
- * the animation will start from the wrong position.
3002
- */
3003
- if (target !== ideal)
3004
- amplitude = target - from;
3005
- return {
3006
- next: (t) => {
3007
- const delta = -amplitude * Math.exp(-t / timeConstant);
3008
- state.done = !(delta > restDelta || delta < -restDelta);
3009
- state.value = state.done ? target : target + delta;
3010
- return state;
3011
- },
3012
- flipTarget: () => { },
2868
+ function createInstantAnimation({ keyframes, elapsed, onUpdate, onComplete, }) {
2869
+ const setValue = () => {
2870
+ onUpdate && onUpdate(keyframes[keyframes.length - 1]);
2871
+ onComplete && onComplete();
2872
+ return () => { };
3013
2873
  };
2874
+ return elapsed ? delay(setValue, -elapsed) : setValue();
3014
2875
  }
3015
2876
 
3016
- const types = { decay, keyframes, spring };
3017
- function loopElapsed(elapsed, duration, delay = 0) {
3018
- return elapsed - duration - delay;
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;
3019
2925
  }
3020
- function reverseElapsed(elapsed, duration = 0, delay = 0, isForwardPlayback = true) {
3021
- return isForwardPlayback
3022
- ? loopElapsed(duration + -elapsed, duration, delay)
3023
- : duration - (elapsed - duration) + delay;
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
+ };
3024
2951
  }
3025
- function hasRepeatDelayElapsed(elapsed, duration, delay, isForwardPlayback) {
3026
- return isForwardPlayback ? elapsed >= duration + delay : elapsed <= -delay;
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;
3027
2971
  }
3028
- const framesync = (update) => {
3029
- const passTimestamp = ({ delta }) => update(delta);
3030
- return {
3031
- start: () => sync.update(passTimestamp, true),
3032
- stop: () => cancelSync.update(passTimestamp),
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);
3033
2982
  };
3034
2983
  };
3035
- 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 }) {
3036
- var _a, _b;
3037
- let { to } = options;
3038
- let driverControls;
3039
- let repeatCount = 0;
3040
- let computedDuration = options
3041
- .duration;
3042
- let latest;
3043
- let isComplete = false;
3044
- let isForwardPlayback = true;
3045
- let interpolateFromNumber;
3046
- const animator = types[Array.isArray(to) ? "keyframes" : type];
3047
- if ((_b = (_a = animator).needsInterpolation) === null || _b === void 0 ? void 0 : _b.call(_a, from, to)) {
3048
- interpolateFromNumber = interpolate([0, 100], [from, to], {
3049
- clamp: false,
3050
- });
3051
- from = 0;
3052
- to = 100;
2984
+
2985
+ function getMixer$1(origin, target) {
2986
+ if (typeof origin === "number") {
2987
+ return (v) => mix(origin, target, v);
3053
2988
  }
3054
- const animation = animator({ ...options, from, to });
3055
- function repeat() {
3056
- repeatCount++;
3057
- if (repeatType === "reverse") {
3058
- isForwardPlayback = repeatCount % 2 === 0;
3059
- elapsed = reverseElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback);
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;
3058
+ }
3059
+ else if (typeof v === "string") {
3060
+ if (color.test(v)) {
3061
+ return mixColor;
3060
3062
  }
3061
3063
  else {
3062
- elapsed = loopElapsed(elapsed, computedDuration, repeatDelay);
3063
- if (repeatType === "mirror")
3064
- animation.flipTarget();
3064
+ return mixComplex;
3065
+ }
3066
+ }
3067
+ else if (Array.isArray(v)) {
3068
+ return mixArray;
3069
+ }
3070
+ else if (typeof v === "object") {
3071
+ return mixObject;
3072
+ }
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);
3065
3084
  }
3066
- isComplete = false;
3067
- onRepeat && onRepeat();
3085
+ mixers.push(mixer);
3068
3086
  }
3069
- function complete() {
3070
- driverControls.stop();
3071
- onComplete && onComplete();
3087
+ return mixers;
3088
+ }
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
+ * ```
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
3107
+ */
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();
3072
3116
  }
3073
- function update(delta) {
3074
- if (!isForwardPlayback)
3075
- delta = -delta;
3076
- elapsed += delta;
3077
- if (!isComplete) {
3078
- const state = animation.next(Math.max(0, elapsed));
3079
- latest = state.value;
3080
- if (interpolateFromNumber)
3081
- latest = interpolateFromNumber(latest);
3082
- isComplete = isForwardPlayback ? state.done : elapsed <= 0;
3083
- }
3084
- onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate(latest);
3085
- if (isComplete) {
3086
- if (repeatCount === 0) {
3087
- computedDuration =
3088
- computedDuration !== undefined ? computedDuration : elapsed;
3089
- }
3090
- if (repeatCount < repeatMax) {
3091
- hasRepeatDelayElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback) && repeat();
3092
- }
3093
- else {
3094
- complete();
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;
3095
3125
  }
3096
3126
  }
3097
- }
3098
- function play() {
3099
- onPlay === null || onPlay === void 0 ? void 0 : onPlay();
3100
- driverControls = driver(update);
3101
- driverControls.start();
3102
- }
3103
- autoplay && play();
3104
- return {
3105
- stop: () => {
3106
- onStop === null || onStop === void 0 ? void 0 : onStop();
3107
- driverControls.stop();
3108
- },
3127
+ const progressInRange = progress(input[i], input[i + 1], v);
3128
+ return mixers[i](progressInRange);
3109
3129
  };
3130
+ return isClamp
3131
+ ? (v) => interpolator(clamp(input[0], input[inputLength - 1], v))
3132
+ : interpolator;
3110
3133
  }
3111
3134
 
3112
- /*
3113
- Convert velocity into velocity per second
3135
+ const noop = (any) => any;
3114
3136
 
3115
- @param [number]: Unit per frame
3116
- @param [number]: Frame duration in ms
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...
3117
3153
  */
3118
- function velocityPerSecond(velocity, frameDuration) {
3119
- return frameDuration ? velocity * (1000 / frameDuration) : 0;
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;
3175
+ }
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);
3120
3183
  }
3121
3184
 
3122
- function inertia({ from = 0, velocity = 0, min, max, power = 0.8, timeConstant = 750, bounceStiffness = 500, bounceDamping = 10, restDelta = 1, modifyTarget, driver, onUpdate, onComplete, onStop, }) {
3123
- let currentAnimation;
3124
- function isOutOfBounds(v) {
3125
- return (min !== undefined && v < min) || (max !== undefined && v > max);
3126
- }
3127
- function boundaryNearest(v) {
3128
- if (min === undefined)
3129
- return max;
3130
- if (max === undefined)
3131
- return min;
3132
- return Math.abs(min - v) < Math.abs(max - v) ? min : max;
3133
- }
3134
- function startAnimation(options) {
3135
- currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.stop();
3136
- currentAnimation = animate$1({
3137
- ...options,
3138
- driver,
3139
- onUpdate: (v) => {
3140
- var _a;
3141
- onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate(v);
3142
- (_a = options.onUpdate) === null || _a === void 0 ? void 0 : _a.call(options, v);
3143
- },
3144
- onComplete,
3145
- onStop,
3146
- });
3147
- }
3148
- function startSpring(options) {
3149
- startAnimation({
3150
- type: "spring",
3151
- stiffness: bounceStiffness,
3152
- damping: bounceDamping,
3153
- restDelta,
3154
- ...options,
3155
- });
3156
- }
3157
- if (isOutOfBounds(from)) {
3158
- // Start the animation with spring if outside the defined boundaries
3159
- startSpring({ from, velocity, to: boundaryNearest(from) });
3185
+ const circIn = (p) => 1 - Math.sin(Math.acos(p));
3186
+ const circOut = reverseEasing(circIn);
3187
+ const circInOut = mirrorEasing(circOut);
3188
+
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);
3193
+
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);
3160
3221
  }
3161
- else {
3162
- /**
3163
- * Or if the value is out of bounds, simulate the inertia movement
3164
- * with the decay animation.
3165
- *
3166
- * Pre-calculate the target so we can detect if it's out-of-bounds.
3167
- * If it is, we want to check per frame when to switch to a spring
3168
- * animation
3169
- */
3170
- let target = power * velocity + from;
3171
- if (typeof modifyTarget !== "undefined")
3172
- target = modifyTarget(target);
3173
- const boundary = boundaryNearest(target);
3174
- const heading = boundary === min ? -1 : 1;
3175
- let prev;
3176
- let current;
3177
- const checkBoundary = (v) => {
3178
- prev = current;
3179
- current = v;
3180
- velocity = velocityPerSecond(v - prev, frameData.delta);
3181
- if ((heading === 1 && v > boundary) ||
3182
- (heading === -1 && v < boundary)) {
3183
- startSpring({ from: v, to: boundary, velocity });
3184
- }
3185
- };
3186
- startAnimation({
3187
- type: "decay",
3188
- from,
3189
- velocity,
3190
- timeConstant,
3191
- power,
3192
- restDelta,
3193
- modifyTarget,
3194
- onUpdate: isOutOfBounds(target) ? checkBoundary : undefined,
3195
- });
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];
3196
3226
  }
3197
- return {
3198
- stop: () => currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.stop(),
3199
- };
3200
- }
3227
+ return definition;
3228
+ };
3229
+ const isEasingArray = (ease) => {
3230
+ return Array.isArray(ease) && typeof ease[0] !== "number";
3231
+ };
3201
3232
 
3202
- /**
3203
- * Decide whether a transition is defined on a given Transition.
3204
- * This filters out orchestration options and returns true
3205
- * if any options are left.
3206
- */
3207
- function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, ...transition }) {
3208
- return !!Object.keys(transition).length;
3233
+ function defaultEasing(values, easing) {
3234
+ return values.map(() => easing || easeInOut).splice(0, values.length - 1);
3235
+ }
3236
+ function defaultOffset(values) {
3237
+ const numValues = values.length;
3238
+ return values.map((_value, i) => i !== 0 ? i / (numValues - 1) : 0);
3209
3239
  }
3210
- /**
3211
- * Convert Framer Motion's Transition type into Popmotion-compatible options.
3212
- */
3213
- function convertTransitionToAnimationOptions({ ease, times, ...transition }) {
3214
- const options = { ...transition };
3215
- if (times)
3216
- options["offset"] = times;
3217
- /**
3218
- * Convert any existing durations from seconds to milliseconds
3219
- */
3220
- if (transition.duration)
3221
- options["duration"] = secondsToMilliseconds(transition.duration);
3222
- if (transition.repeatDelay)
3223
- options.repeatDelay = secondsToMilliseconds(transition.repeatDelay);
3240
+ function convertOffsetToTimes(offset, duration) {
3241
+ return offset.map((o) => o * duration);
3242
+ }
3243
+ function keyframes({ keyframes: keyframeValues, ease = easeInOut, times, duration = 300, }) {
3244
+ keyframeValues = [...keyframeValues];
3245
+ const origin = keyframes[0];
3224
3246
  /**
3225
- * Map easing names to Popmotion's easing functions
3247
+ * Easing functions can be externally defined as strings. Here we convert them
3248
+ * into actual functions.
3226
3249
  */
3227
- if (ease) {
3228
- options["ease"] = isEasingArray(ease)
3229
- ? ease.map(easingDefinitionToFunction)
3230
- : easingDefinitionToFunction(ease);
3231
- }
3250
+ const easingFunctions = isEasingArray(ease)
3251
+ ? ease.map(easingDefinitionToFunction)
3252
+ : easingDefinitionToFunction(ease);
3232
3253
  /**
3233
- * Support legacy transition API
3254
+ * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
3255
+ * to reduce GC during animation.
3234
3256
  */
3235
- if (transition.type === "tween")
3236
- options.type = "keyframes";
3257
+ const state = { done: false, value: origin };
3237
3258
  /**
3238
- * TODO: Popmotion 9 has the ability to automatically detect whether to use
3239
- * a keyframes or spring animation, but does so by detecting velocity and other spring options.
3240
- * It'd be good to introduce a similar thing here.
3259
+ * Create a times array based on the provided 0-1 offsets
3241
3260
  */
3242
- if (transition.type !== "spring")
3243
- options.type = "keyframes";
3244
- return options;
3245
- }
3246
- /**
3247
- * Get the delay for a value by checking Transition with decreasing specificity.
3248
- */
3249
- function getDelayFromTransition(transition, key) {
3250
- const valueTransition = getValueTransition(transition, key) || {};
3251
- return valueTransition.delay !== undefined
3252
- ? valueTransition.delay
3253
- : transition.delay !== undefined
3254
- ? transition.delay
3255
- : 0;
3256
- }
3257
- function hydrateKeyframes(options) {
3258
- if (Array.isArray(options.to) && options.to[0] === null) {
3259
- options.to = [...options.to];
3260
- options.to[0] = options.from;
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
+ });
3261
3273
  }
3262
- return options;
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
+ };
3263
3286
  }
3264
- function getPopmotionAnimationOptions(transition, options, key) {
3265
- if (Array.isArray(options.to) && transition.duration === undefined) {
3266
- transition.duration = 0.8;
3267
- }
3268
- hydrateKeyframes(options);
3287
+
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;
3269
3298
  /**
3270
- * Get a default transition if none is determined to be defined.
3299
+ * Restrict dampingRatio and duration to within acceptable ranges.
3271
3300
  */
3272
- if (!isTransitionDefined(transition)) {
3273
- transition = {
3274
- ...transition,
3275
- ...getDefaultTransition(key, options.to),
3301
+ dampingRatio = clamp(minDamping, maxDamping, dampingRatio);
3302
+ duration = clamp(minDuration, maxDuration, duration / 1000);
3303
+ if (dampingRatio < 1) {
3304
+ /**
3305
+ * Underdamped spring
3306
+ */
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;
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;
3276
3324
  };
3277
3325
  }
3278
- return {
3279
- ...options,
3280
- ...convertTransitionToAnimationOptions(transition),
3281
- };
3282
- }
3283
- /**
3284
- *
3285
- */
3286
- function getAnimation(key, value, target, transition, onComplete) {
3287
- const valueTransition = getValueTransition(transition, key) || {};
3288
- let origin = valueTransition.from !== undefined ? valueTransition.from : value.get();
3289
- const isTargetAnimatable = isAnimatable(key, target);
3290
- if (origin === "none" && isTargetAnimatable && typeof target === "string") {
3326
+ else {
3291
3327
  /**
3292
- * If we're trying to animate from "none", try and get an animatable version
3293
- * of the target. This could be improved to work both ways.
3328
+ * Critically-damped spring
3294
3329
  */
3295
- origin = getAnimatableNone(key, target);
3296
- }
3297
- else if (isZero(origin) && typeof target === "string") {
3298
- origin = getZeroUnit(target);
3299
- }
3300
- else if (!Array.isArray(target) &&
3301
- isZero(target) &&
3302
- typeof origin === "string") {
3303
- target = getZeroUnit(origin);
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
+ };
3304
3340
  }
3305
- const isOriginAnimatable = isAnimatable(key, origin);
3306
- 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.`);
3307
- function start() {
3308
- const options = {
3309
- from: origin,
3310
- to: target,
3311
- velocity: value.getVelocity(),
3312
- onComplete,
3313
- onUpdate: (v) => value.set(v),
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,
3314
3349
  };
3315
- return valueTransition.type === "inertia" ||
3316
- valueTransition.type === "decay"
3317
- ? inertia({ ...options, ...valueTransition })
3318
- : animate$1({
3319
- ...getPopmotionAnimationOptions(valueTransition, options, key),
3320
- onUpdate: (v) => {
3321
- options.onUpdate(v);
3322
- valueTransition.onUpdate && valueTransition.onUpdate(v);
3323
- },
3324
- onComplete: () => {
3325
- options.onComplete();
3326
- valueTransition.onComplete && valueTransition.onComplete();
3327
- },
3328
- });
3329
3350
  }
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 { stop: () => { } };
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
+ };
3337
3358
  }
3338
- return !isOriginAnimatable ||
3339
- !isTargetAnimatable ||
3340
- valueTransition.type === false
3341
- ? set
3342
- : start;
3343
3359
  }
3344
- function isZero(value) {
3345
- return (value === 0 ||
3346
- (typeof value === "string" &&
3347
- parseFloat(value) === 0 &&
3348
- value.indexOf(" ") === -1));
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;
3349
3367
  }
3350
- function getZeroUnit(potentialUnitType) {
3351
- return typeof potentialUnitType === "number"
3352
- ? 0
3353
- : getAnimatableNone("", potentialUnitType);
3368
+ function calcAngularFreq(undampedFreq, dampingRatio) {
3369
+ return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
3354
3370
  }
3355
- function getValueTransition(transition, key) {
3356
- return transition[key] || transition["default"] || transition;
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);
3357
3376
  }
3358
- /**
3359
- * Start animation on a MotionValue. This function is an interface between
3360
- * Framer Motion and Popmotion
3361
- */
3362
- function startAnimation(key, value, target, transition = {}) {
3363
- if (instantAnimationState.current) {
3364
- transition = { type: false };
3365
- }
3366
- return value.start((onComplete) => {
3367
- let controls;
3368
- const animation = getAnimation(key, value, target, transition, onComplete);
3369
- const delayBy = getDelayFromTransition(transition, key);
3370
- const start = () => (controls = animation());
3371
- let cancelDelay;
3372
- if (delayBy) {
3373
- cancelDelay = delay(start, secondsToMilliseconds(delayBy));
3374
- }
3375
- else {
3376
- start();
3377
- }
3378
- return () => {
3379
- cancelDelay && cancelDelay();
3380
- controls && controls.stop();
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,
3381
3395
  };
3382
- });
3396
+ springOptions.isResolvedFromDuration = true;
3397
+ }
3398
+ return springOptions;
3383
3399
  }
3384
-
3385
- /**
3386
- * Check if value is a numerical string, ie a string that is purely a number eg "100" or "-100.1"
3387
- */
3388
- const isNumericalString = (v) => /^\-?\d*\.?\d+$/.test(v);
3389
-
3400
+ const velocitySampleDuration = 5;
3390
3401
  /**
3391
- * Check if the value is a zero value string like "0px" or "0%"
3402
+ * This is based on the spring implementation of Wobble https://github.com/skevy/wobble
3392
3403
  */
3393
- const isZeroValueString = (v) => /^0[^.\s]+$/.test(v);
3394
-
3395
- function addUniqueItem(arr, item) {
3396
- if (arr.indexOf(item) === -1)
3397
- arr.push(item);
3398
- }
3399
- function removeItem(arr, item) {
3400
- const index = arr.indexOf(item);
3401
- if (index > -1)
3402
- arr.splice(index, 1);
3403
- }
3404
- // Adapted from array-move
3405
- function moveItem([...arr], fromIndex, toIndex) {
3406
- const startIndex = fromIndex < 0 ? arr.length + fromIndex : fromIndex;
3407
- if (startIndex >= 0 && startIndex < arr.length) {
3408
- const endIndex = toIndex < 0 ? arr.length + toIndex : toIndex;
3409
- const [item] = arr.splice(fromIndex, 1);
3410
- arr.splice(endIndex, 0, item);
3411
- }
3412
- return arr;
3413
- }
3414
-
3415
- class SubscriptionManager {
3416
- constructor() {
3417
- this.subscriptions = [];
3418
- }
3419
- add(handler) {
3420
- addUniqueItem(this.subscriptions, handler);
3421
- return () => removeItem(this.subscriptions, handler);
3422
- }
3423
- notify(a, b, c) {
3424
- const numSubscriptions = this.subscriptions.length;
3425
- if (!numSubscriptions)
3426
- return;
3427
- if (numSubscriptions === 1) {
3428
- /**
3429
- * If there's only a single handler we can just call it without invoking a loop.
3430
- */
3431
- this.subscriptions[0](a, b, c);
3404
+ function spring({ keyframes, restSpeed = 2, restDelta = 0.01, ...options }) {
3405
+ let origin = keyframes[0];
3406
+ let target = keyframes[keyframes.length - 1];
3407
+ /**
3408
+ * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
3409
+ * to reduce GC during animation.
3410
+ */
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);
3432
3425
  }
3433
- else {
3434
- for (let i = 0; i < numSubscriptions; i++) {
3435
- /**
3436
- * Check whether the handler exists before firing as it's possible
3437
- * the subscriptions were modified during this loop running.
3438
- */
3439
- const handler = this.subscriptions[i];
3440
- handler && handler(a, b, c);
3441
- }
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
+ };
3442
3465
  }
3443
3466
  }
3444
- getSize() {
3445
- return this.subscriptions.length;
3446
- }
3447
- clear() {
3448
- this.subscriptions.length = 0;
3449
- }
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
+ };
3450
3504
  }
3505
+ spring.needsInterpolation = (a, b) => typeof a === "string" || typeof b === "string";
3506
+ const zero = (_t) => 0;
3451
3507
 
3452
- const isFloat = (value) => {
3453
- return !isNaN(parseFloat(value));
3454
- };
3508
+ function decay({
3455
3509
  /**
3456
- * `MotionValue` is used to track the state and velocity of motion values.
3457
- *
3458
- * @public
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.
3459
3513
  */
3460
- class MotionValue {
3514
+ keyframes = [0], velocity = 0, power = 0.8, timeConstant = 350, restDelta = 0.5, modifyTarget, }) {
3515
+ const origin = keyframes[0];
3461
3516
  /**
3462
- * @param init - The initiating value
3463
- * @param config - Optional configuration options
3464
- *
3465
- * - `transformer`: A function to transform incoming values with.
3466
- *
3467
- * @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.
3468
3519
  */
3469
- constructor(init) {
3470
- /**
3471
- * This will be replaced by the build step with the latest version number.
3472
- * When MotionValues are provided to motion components, warn if versions are mixed.
3473
- */
3474
- this.version = "7.7.3";
3475
- /**
3476
- * Duration, in milliseconds, since last updating frame.
3477
- *
3478
- * @internal
3479
- */
3480
- this.timeDelta = 0;
3481
- /**
3482
- * Timestamp of the last time this `MotionValue` was updated.
3483
- *
3484
- * @internal
3485
- */
3486
- this.lastUpdated = 0;
3487
- /**
3488
- * Functions to notify when the `MotionValue` updates.
3489
- *
3490
- * @internal
3491
- */
3492
- this.updateSubscribers = new SubscriptionManager();
3493
- /**
3494
- * Functions to notify when the velocity updates.
3495
- *
3496
- * @internal
3497
- */
3498
- this.velocityUpdateSubscribers = new SubscriptionManager();
3499
- /**
3500
- * Functions to notify when the `MotionValue` updates and `render` is set to `true`.
3501
- *
3502
- * @internal
3503
- */
3504
- this.renderSubscribers = new SubscriptionManager();
3505
- /**
3506
- * Tracks whether this value can output a velocity. Currently this is only true
3507
- * if the value is numerical, but we might be able to widen the scope here and support
3508
- * other value types.
3509
- *
3510
- * @internal
3511
- */
3512
- this.canTrackVelocity = false;
3513
- this.updateAndNotify = (v, render = true) => {
3514
- this.prev = this.current;
3515
- this.current = v;
3516
- // Update timestamp
3517
- const { delta, timestamp } = frameData;
3518
- if (this.lastUpdated !== timestamp) {
3519
- this.timeDelta = delta;
3520
- this.lastUpdated = timestamp;
3521
- sync.postRender(this.scheduleVelocityCheck);
3522
- }
3523
- // Update update subscribers
3524
- if (this.prev !== this.current) {
3525
- this.updateSubscribers.notify(this.current);
3526
- }
3527
- // Update velocity subscribers
3528
- if (this.velocityUpdateSubscribers.getSize()) {
3529
- this.velocityUpdateSubscribers.notify(this.getVelocity());
3530
- }
3531
- // Update render subscribers
3532
- if (render) {
3533
- this.renderSubscribers.notify(this.current);
3534
- }
3535
- };
3536
- /**
3537
- * Schedule a velocity check for the next frame.
3538
- *
3539
- * This is an instanced and bound function to prevent generating a new
3540
- * function once per frame.
3541
- *
3542
- * @internal
3543
- */
3544
- this.scheduleVelocityCheck = () => sync.postRender(this.velocityCheck);
3545
- /**
3546
- * Updates `prev` with `current` if the value hasn't been updated this frame.
3547
- * This ensures velocity calculations return `0`.
3548
- *
3549
- * This is an instanced and bound function to prevent generating a new
3550
- * function once per frame.
3551
- *
3552
- * @internal
3553
- */
3554
- this.velocityCheck = ({ timestamp }) => {
3555
- if (timestamp !== this.lastUpdated) {
3556
- this.prev = this.current;
3557
- this.velocityUpdateSubscribers.notify(this.getVelocity());
3558
- }
3559
- };
3560
- this.hasAnimated = false;
3561
- this.prev = this.current = init;
3562
- this.canTrackVelocity = isFloat(this.current);
3563
- }
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);
3564
3524
  /**
3565
- * Adds a function that will be notified when the `MotionValue` is updated.
3566
- *
3567
- * It returns a function that, when called, will cancel the subscription.
3568
- *
3569
- * When calling `onChange` inside a React component, it should be wrapped with the
3570
- * `useEffect` hook. As it returns an unsubscribe function, this should be returned
3571
- * from the `useEffect` function to ensure you don't add duplicate subscribers..
3572
- *
3573
- * ```jsx
3574
- * export const MyComponent = () => {
3575
- * const x = useMotionValue(0)
3576
- * const y = useMotionValue(0)
3577
- * const opacity = useMotionValue(1)
3578
- *
3579
- * useEffect(() => {
3580
- * function updateOpacity() {
3581
- * const maxXY = Math.max(x.get(), y.get())
3582
- * const newOpacity = transform(maxXY, [0, 100], [1, 0])
3583
- * opacity.set(newOpacity)
3584
- * }
3585
- *
3586
- * const unsubscribeX = x.onChange(updateOpacity)
3587
- * const unsubscribeY = y.onChange(updateOpacity)
3588
- *
3589
- * return () => {
3590
- * unsubscribeX()
3591
- * unsubscribeY()
3592
- * }
3593
- * }, [])
3594
- *
3595
- * return <motion.div style={{ x }} />
3596
- * }
3597
- * ```
3598
- *
3599
- * @privateRemarks
3600
- *
3601
- * We could look into a `useOnChange` hook if the above lifecycle management proves confusing.
3602
- *
3603
- * ```jsx
3604
- * useOnChange(x, () => {})
3605
- * ```
3606
- *
3607
- * @param subscriber - A function that receives the latest value.
3608
- * @returns A function that, when called, will cancel this subscription.
3609
- *
3610
- * @public
3525
+ * If the target has changed we need to re-calculate the amplitude, otherwise
3526
+ * the animation will start from the wrong position.
3611
3527
  */
3612
- onChange(subscription) {
3613
- return this.updateSubscribers.add(subscription);
3614
- }
3615
- clearListeners() {
3616
- this.updateSubscribers.clear();
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];
3617
3582
  }
3618
- /**
3619
- * Adds a function that will be notified when the `MotionValue` requests a render.
3620
- *
3621
- * @param subscriber - A function that's provided the latest value.
3622
- * @returns A function that, when called, will cancel this subscription.
3623
- *
3624
- * @internal
3625
- */
3626
- onRenderRequest(subscription) {
3627
- // Render immediately
3628
- subscription(this.get());
3629
- return this.renderSubscribers.add(subscription);
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);
3593
+ }
3594
+ else {
3595
+ elapsed = loopElapsed(elapsed, computedDuration, repeatDelay);
3596
+ if (repeatType === "mirror")
3597
+ animation.flipTarget();
3598
+ }
3599
+ isComplete = false;
3600
+ onRepeat && onRepeat();
3630
3601
  }
3631
- /**
3632
- * Attaches a passive effect to the `MotionValue`.
3633
- *
3634
- * @internal
3635
- */
3636
- attach(passiveEffect) {
3637
- this.passiveEffect = passiveEffect;
3602
+ function complete() {
3603
+ driverControls.stop();
3604
+ onComplete && onComplete();
3638
3605
  }
3639
- /**
3640
- * Sets the state of the `MotionValue`.
3641
- *
3642
- * @remarks
3643
- *
3644
- * ```jsx
3645
- * const x = useMotionValue(0)
3646
- * x.set(10)
3647
- * ```
3648
- *
3649
- * @param latest - Latest value to set.
3650
- * @param render - Whether to notify render subscribers. Defaults to `true`
3651
- *
3652
- * @public
3653
- */
3654
- set(v, render = true) {
3655
- if (!render || !this.passiveEffect) {
3656
- this.updateAndNotify(v, render);
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;
3657
3616
  }
3658
- else {
3659
- this.passiveEffect(v, this.updateAndNotify);
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
+ }
3660
3629
  }
3661
3630
  }
3662
- /**
3663
- * Returns the latest state of `MotionValue`
3664
- *
3665
- * @returns - The latest state of `MotionValue`
3666
- *
3667
- * @public
3668
- */
3669
- get() {
3670
- return this.current;
3631
+ function play() {
3632
+ onPlay && onPlay();
3633
+ driverControls = driver(update);
3634
+ driverControls.start();
3671
3635
  }
3672
- /**
3673
- * @public
3674
- */
3675
- getPrevious() {
3676
- return this.prev;
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);
3677
3650
  }
3678
- /**
3679
- * Returns the latest velocity of `MotionValue`
3680
- *
3681
- * @returns - The latest velocity of `MotionValue`. Returns `0` if the state is non-numerical.
3682
- *
3683
- * @public
3684
- */
3685
- getVelocity() {
3686
- // This could be isFloat(this.prev) && isFloat(this.current), but that would be wasteful
3687
- return this.canTrackVelocity
3688
- ? // These casts could be avoided if parseFloat would be typed better
3689
- velocityPerSecond(parseFloat(this.current) -
3690
- parseFloat(this.prev), this.timeDelta)
3691
- : 0;
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;
3692
3657
  }
3693
- /**
3694
- * Registers a new animation to control this `MotionValue`. Only one
3695
- * animation can drive a `MotionValue` at one time.
3696
- *
3697
- * ```jsx
3698
- * value.start()
3699
- * ```
3700
- *
3701
- * @param animation - A function that starts the provided animation
3702
- *
3703
- * @internal
3704
- */
3705
- start(animation) {
3706
- this.stop();
3707
- return new Promise((resolve) => {
3708
- this.hasAnimated = true;
3709
- this.stopAnimation = animation(resolve);
3710
- }).then(() => this.clearAnimation());
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
+ });
3711
3673
  }
3712
- /**
3713
- * Stop the currently active animation.
3714
- *
3715
- * @public
3716
- */
3717
- stop() {
3718
- if (this.stopAnimation)
3719
- this.stopAnimation();
3720
- this.clearAnimation();
3674
+ function startSpring(options) {
3675
+ startAnimation({
3676
+ type: "spring",
3677
+ stiffness: bounceStiffness,
3678
+ damping: bounceDamping,
3679
+ restDelta,
3680
+ ...options,
3681
+ });
3721
3682
  }
3722
- /**
3723
- * Returns `true` if this value is currently animating.
3724
- *
3725
- * @public
3726
- */
3727
- isAnimating() {
3728
- return !!this.stopAnimation;
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
+ });
3729
3689
  }
3730
- clearAnimation() {
3731
- this.stopAnimation = null;
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
+ });
3732
3725
  }
3733
- /**
3734
- * Destroy and clean up subscribers to this `MotionValue`.
3735
- *
3736
- * The `MotionValue` hooks like `useMotionValue` and `useTransform` automatically
3737
- * handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually
3738
- * created a `MotionValue` via the `motionValue` function.
3739
- *
3740
- * @public
3741
- */
3742
- destroy() {
3743
- this.updateSubscribers.clear();
3744
- this.renderSubscribers.clear();
3745
- this.stop();
3726
+ return {
3727
+ stop: () => currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.stop(),
3728
+ };
3729
+ }
3730
+
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]);
3746
3775
  }
3747
- }
3748
- function motionValue(init) {
3749
- return new MotionValue(init);
3750
- }
3751
-
3752
- /**
3753
- * Tests a provided value against a ValueType
3754
- */
3755
- const testValueType = (v) => (type) => type.test(v);
3756
-
3757
- /**
3758
- * ValueType for "auto"
3759
- */
3760
- const auto = {
3761
- test: (v) => v === "auto",
3762
- parse: (v) => v,
3763
3776
  };
3764
3777
 
3765
3778
  /**
3766
- * A list of value types commonly used for dimensions
3767
- */
3768
- const dimensionValueTypes = [number, px, percent, degrees, vw, vh, auto];
3769
- /**
3770
- * Tests a dimensional value against the list of dimension ValueTypes
3771
- */
3772
- const findDimensionValueType = (v) => dimensionValueTypes.find(testValueType(v));
3773
-
3774
- /**
3775
- * A list of all ValueTypes
3776
- */
3777
- const valueTypes = [...dimensionValueTypes, color, complex];
3778
- /**
3779
- * 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
3780
3786
  */
3781
- 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
+ };
3782
3804
 
3783
3805
  /**
3784
- * 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.
3785
3809
  */
3786
- function getCurrent(visualElement) {
3787
- const current = {};
3788
- visualElement.values.forEach((value, key) => (current[key] = value.get()));
3789
- return current;
3810
+ function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, ...transition }) {
3811
+ return !!Object.keys(transition).length;
3790
3812
  }
3791
- /**
3792
- * Creates an object containing the latest velocity of every MotionValue on a VisualElement
3793
- */
3794
- function getVelocity$1(visualElement) {
3795
- const velocity = {};
3796
- visualElement.values.forEach((value, key) => (velocity[key] = value.getVelocity()));
3797
- return velocity;
3813
+ function isZero(value) {
3814
+ return (value === 0 ||
3815
+ (typeof value === "string" &&
3816
+ parseFloat(value) === 0 &&
3817
+ value.indexOf(" ") === -1));
3798
3818
  }
3799
- function resolveVariant(visualElement, definition, custom) {
3800
- const props = visualElement.getProps();
3801
- 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;
3802
3826
  }
3803
3827
 
3804
- /**
3805
- * Set VisualElement's MotionValue, creating a new MotionValue for it if
3806
- * it doesn't exist.
3807
- */
3808
- function setMotionValue(visualElement, key, value) {
3809
- if (visualElement.hasValue(key)) {
3810
- visualElement.getValue(key).set(value);
3811
- }
3812
- else {
3813
- 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);
3814
3837
  }
3815
- }
3816
- function setTarget(visualElement, definition) {
3817
- const resolved = resolveVariant(visualElement, definition);
3818
- let { transitionEnd = {}, transition = {}, ...target } = resolved ? visualElement.makeTargetAnimatable(resolved, false) : {};
3819
- target = { ...target, ...transitionEnd };
3820
- for (const key in target) {
3821
- const value = resolveFinalValueInKeyframes(target[key]);
3822
- setMotionValue(visualElement, key, value);
3838
+ else if (isZero(origin) && typeof target === "string") {
3839
+ origin = getZeroUnit(target);
3823
3840
  }
3824
- }
3825
- function setVariants(visualElement, variantLabels) {
3826
- const reversedLabels = [...variantLabels].reverse();
3827
- reversedLabels.forEach((key) => {
3828
- var _a;
3829
- const variant = visualElement.getVariant(key);
3830
- variant && setTarget(visualElement, variant);
3831
- (_a = visualElement.variantChildren) === null || _a === void 0 ? void 0 : _a.forEach((child) => {
3832
- setVariants(child, variantLabels);
3833
- });
3834
- });
3835
- }
3836
- function setValues(visualElement, definition) {
3837
- if (Array.isArray(definition)) {
3838
- return setVariants(visualElement, definition);
3841
+ else if (!Array.isArray(target) &&
3842
+ isZero(target) &&
3843
+ typeof origin === "string") {
3844
+ target = getZeroUnit(origin);
3839
3845
  }
3840
- else if (typeof definition === "string") {
3841
- 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;
3842
3858
  }
3843
3859
  else {
3844
- setTarget(visualElement, definition);
3860
+ return [origin, target];
3845
3861
  }
3846
3862
  }
3847
- function checkTargetForNewValues(visualElement, target, origin) {
3848
- var _a, _b;
3849
- const newValueKeys = Object.keys(target).filter((key) => !visualElement.hasValue(key));
3850
- const numNewValues = newValueKeys.length;
3851
- if (!numNewValues)
3852
- return;
3853
- for (let i = 0; i < numNewValues; i++) {
3854
- const key = newValueKeys[i];
3855
- const targetValue = target[key];
3856
- 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) || {};
3857
3887
  /**
3858
- * If the target is a series of keyframes, we can use the first value
3859
- * 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.
3860
3891
  */
3861
- if (Array.isArray(targetValue)) {
3862
- value = targetValue[0];
3863
- }
3892
+ const delay = valueTransition.delay || transition.delay || 0;
3864
3893
  /**
3865
- * If the target isn't keyframes, or the first keyframe was null, we need to
3866
- * first check if an origin value was explicitly defined in the transition as "from",
3867
- * 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.
3868
3896
  */
3869
- if (value === null) {
3870
- value = (_b = (_a = origin[key]) !== null && _a !== void 0 ? _a : visualElement.readValue(key)) !== null && _b !== void 0 ? _b : target[key];
3897
+ let { elapsed = 0 } = transition;
3898
+ elapsed = elapsed - secondsToMilliseconds(delay);
3899
+ const keyframes = getKeyframes(value, valueName, target, valueTransition);
3900
+ /**
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.
3904
+ */
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);
3933
+ }
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();
3871
3941
  }
3872
3942
  /**
3873
- * If value is still undefined or null, ignore it. Preferably this would throw,
3874
- * but this was causing issues in Framer.
3943
+ * If there's no transition defined for this value, we can generate
3944
+ * unqiue transition settings for this value.
3875
3945
  */
3876
- if (value === undefined || value === null)
3877
- continue;
3878
- if (typeof value === "string" &&
3879
- (isNumericalString(value) || isZeroValueString(value))) {
3880
- // If this is a number read as a string, ie "0" or "200", convert it to a number
3881
- value = parseFloat(value);
3946
+ if (!isTransitionDefined(valueTransition)) {
3947
+ options = {
3948
+ ...options,
3949
+ ...getDefaultTransition(valueName, options),
3950
+ };
3882
3951
  }
3883
- else if (!findValueType(value) && complex.test(targetValue)) {
3884
- value = getAnimatableNone(key, targetValue);
3952
+ /**
3953
+ * Both WAAPI and our internal animation functions use durations
3954
+ * as defined by milliseconds, while our external API defines them
3955
+ * as seconds.
3956
+ */
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) {
3969
+ /**
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.
3974
+ */
3975
+ return createAcceleratedAnimation();
3885
3976
  }
3886
- visualElement.addValue(key, motionValue(value));
3887
- if (origin[key] === undefined) {
3888
- origin[key] = value;
3977
+ else {
3978
+ /**
3979
+ * Otherwise, fall back to the main thread.
3980
+ */
3981
+ const animation = animate$1(options);
3982
+ return () => animation.stop();
3889
3983
  }
3890
- if (value !== null)
3891
- visualElement.setBaseTarget(key, value);
3892
- }
3893
- }
3894
- function getOriginFromTransition(key, transition) {
3895
- if (!transition)
3896
- return;
3897
- const valueTransition = transition[key] || transition["default"] || transition;
3898
- return valueTransition.from;
3899
- }
3900
- function getOrigin(target, transition, visualElement) {
3901
- var _a;
3902
- const origin = {};
3903
- for (const key in target) {
3904
- const transitionOrigin = getOriginFromTransition(key, transition);
3905
- origin[key] =
3906
- transitionOrigin !== undefined
3907
- ? transitionOrigin
3908
- : (_a = visualElement.getValue(key)) === null || _a === void 0 ? void 0 : _a.get();
3909
- }
3910
- return origin;
3911
- }
3912
-
3913
- function isWillChangeMotionValue(value) {
3914
- return Boolean(isMotionValue(value) && value.add);
3915
- }
3984
+ };
3985
+ };
3916
3986
 
3917
3987
  function animateVisualElement(visualElement, definition, options = {}) {
3918
3988
  visualElement.notify("AnimationStart", definition);
@@ -3991,7 +4061,7 @@ function animateTarget(visualElement, definition, { delay = 0, transitionOverrid
3991
4061
  shouldBlockAnimation(animationTypeState, key))) {
3992
4062
  continue;
3993
4063
  }
3994
- let valueTransition = { delay, ...transition };
4064
+ let valueTransition = { delay, elapsed: 0, ...transition };
3995
4065
  /**
3996
4066
  * Make animation instant if this is a transform prop and we should reduce motion.
3997
4067
  */
@@ -4002,7 +4072,17 @@ function animateTarget(visualElement, definition, { delay = 0, transitionOverrid
4002
4072
  delay: 0,
4003
4073
  };
4004
4074
  }
4005
- let animation = startAnimation(key, value, valueTarget, valueTransition);
4075
+ /**
4076
+ * If this is the first time a value is being animated, check
4077
+ * to see if we're handling off from an existing animation.
4078
+ */
4079
+ if (!value.hasAnimated) {
4080
+ const appearId = visualElement.getProps()[optimizedAppearDataAttribute];
4081
+ if (appearId) {
4082
+ valueTransition.elapsed = handoffOptimizedAppearAnimation(appearId, key);
4083
+ }
4084
+ }
4085
+ let animation = value.start(createMotionValueAnimation(key, value, valueTarget, valueTransition));
4006
4086
  if (isWillChangeMotionValue(willChange)) {
4007
4087
  willChange.add(key);
4008
4088
  animation = animation.then(() => willChange.remove(key));
@@ -5158,7 +5238,7 @@ class VisualElementDragControls {
5158
5238
  }
5159
5239
  startAxisValueAnimation(axis, transition) {
5160
5240
  const axisValue = this.getAxisMotionValue(axis);
5161
- return startAnimation(axis, axisValue, 0, transition);
5241
+ return axisValue.start(createMotionValueAnimation(axis, axisValue, 0, transition));
5162
5242
  }
5163
5243
  stopAnimation() {
5164
5244
  eachAxis((axis) => this.getAxisMotionValue(axis).stop());
@@ -5753,7 +5833,7 @@ function updateMotionValuesFromProps(element, next, prev) {
5753
5833
  * and warn against mismatches.
5754
5834
  */
5755
5835
  if (process.env.NODE_ENV === "development") {
5756
- warnOnce(nextValue.version === "7.7.3", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.7.3 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.`);
5757
5837
  }
5758
5838
  }
5759
5839
  else if (isMotionValue(prevValue)) {
@@ -6600,7 +6680,7 @@ const layoutFeatures = {
6600
6680
  */
6601
6681
  function animate(from, to, transition = {}) {
6602
6682
  const value = isMotionValue(from) ? from : motionValue(from);
6603
- startAnimation("", value, to, transition);
6683
+ value.start(createMotionValueAnimation("", value, to, transition));
6604
6684
  return {
6605
6685
  stop: () => value.stop(),
6606
6686
  isAnimating: () => value.isAnimating(),
@@ -9059,8 +9139,7 @@ function useSpring(source, config = {}) {
9059
9139
  activeSpringAnimation.current.stop();
9060
9140
  }
9061
9141
  activeSpringAnimation.current = animate$1({
9062
- from: value.get(),
9063
- to: v,
9142
+ keyframes: [value.get(), v],
9064
9143
  velocity: value.getVelocity(),
9065
9144
  type: "spring",
9066
9145
  ...config,
@@ -9573,6 +9652,30 @@ function useResetProjection() {
9573
9652
  return reset;
9574
9653
  }
9575
9654
 
9655
+ const cubicBezierAsString = ([a, b, c, d]) => `cubic-bezier(${a}, ${b}, ${c}, ${d})`;
9656
+
9657
+ function animateStyle(element, valueName, keyframes, { delay, duration, ease }) {
9658
+ if (!supports.waapi())
9659
+ return undefined;
9660
+ const animation = element.animate({ [valueName]: keyframes }, {
9661
+ delay,
9662
+ duration,
9663
+ easing: Array.isArray(ease) ? cubicBezierAsString(ease) : ease,
9664
+ fill: "both",
9665
+ });
9666
+ return animation;
9667
+ }
9668
+
9669
+ function startOptimizedAppearAnimation(element, name, keyframes, options) {
9670
+ window.MotionAppearAnimations || (window.MotionAppearAnimations = new Map());
9671
+ const id = element.dataset[optimizedAppearDataId];
9672
+ const animation = animateStyle(element, name, keyframes, options);
9673
+ if (id && animation) {
9674
+ window.MotionAppearAnimations.set(appearStoreId(id, name), animation);
9675
+ }
9676
+ return animation;
9677
+ }
9678
+
9576
9679
  const createObject = () => ({});
9577
9680
  class StateVisualElement extends VisualElement {
9578
9681
  build() { }
@@ -9722,8 +9825,11 @@ exports.makeUseVisualState = makeUseVisualState;
9722
9825
  exports.mix = mix;
9723
9826
  exports.motion = motion;
9724
9827
  exports.motionValue = motionValue;
9828
+ exports.optimizedAppearDataAttribute = optimizedAppearDataAttribute;
9725
9829
  exports.pipe = pipe;
9726
9830
  exports.resolveMotionValue = resolveMotionValue;
9831
+ exports.spring = spring;
9832
+ exports.startOptimizedAppearAnimation = startOptimizedAppearAnimation;
9727
9833
  exports.transform = transform;
9728
9834
  exports.unwrapMotionComponent = unwrapMotionComponent;
9729
9835
  exports.useAnimation = useAnimation;