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