framer-motion
Version:
A simple and powerful React animation library
1,417 lines (1,373 loc) • 341 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var tslib = require('tslib');
var React = require('react');
var heyListen = require('hey-listen');
var sync = require('framesync');
var popmotion = require('popmotion');
var styleValueTypes = require('style-value-types');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n["default"] = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespace(React);
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
var sync__default = /*#__PURE__*/_interopDefaultLegacy(sync);
/**
* Browser-safe usage of process
*/
var mockProcess = { env: { NODE_ENV: "production" } };
var safeProcess = typeof process === "undefined" ? mockProcess : process;
// eslint-disable-next-line import/no-default-export
var process$1 = safeProcess;
var createDefinition = function (propNames) { return ({
isEnabled: function (props) { return propNames.some(function (name) { return !!props[name]; }); },
}); };
var featureDefinitions = {
measureLayout: createDefinition(["layout", "layoutId", "drag"]),
animation: createDefinition([
"animate",
"exit",
"variants",
"whileHover",
"whileTap",
"whileFocus",
"whileDrag",
"whileInView",
]),
exit: createDefinition(["exit"]),
drag: createDefinition(["drag", "dragControls"]),
focus: createDefinition(["whileFocus"]),
hover: createDefinition(["whileHover", "onHoverStart", "onHoverEnd"]),
tap: createDefinition(["whileTap", "onTap", "onTapStart", "onTapCancel"]),
pan: createDefinition([
"onPan",
"onPanStart",
"onPanSessionStart",
"onPanEnd",
]),
inView: createDefinition([
"whileInView",
"onViewportEnter",
"onViewportLeave",
]),
};
function loadFeatures(features) {
for (var key in features) {
if (features[key] === null)
continue;
if (key === "projectionNodeConstructor") {
featureDefinitions.projectionNodeConstructor = features[key];
}
else {
featureDefinitions[key].Component = features[key];
}
}
}
var LazyContext = React.createContext({ strict: false });
var featureNames = Object.keys(featureDefinitions);
var numFeatures = featureNames.length;
/**
* Load features via renderless components based on the provided MotionProps.
*/
function useFeatures(props, visualElement, preloadedFeatures) {
var features = [];
var lazyContext = React.useContext(LazyContext);
if (!visualElement)
return null;
/**
* If we're in development mode, check to make sure we're not rendering a motion component
* as a child of LazyMotion, as this will break the file-size benefits of using it.
*/
if (process$1.env.NODE_ENV !== "production" &&
preloadedFeatures &&
lazyContext.strict) {
heyListen.invariant(false, "You have rendered a `motion` component within a `LazyMotion` component. This will break tree shaking. Import and render a `m` component instead.");
}
for (var i = 0; i < numFeatures; i++) {
var name_1 = featureNames[i];
var _a = featureDefinitions[name_1], isEnabled = _a.isEnabled, Component = _a.Component;
/**
* It might be possible in the future to use this moment to
* dynamically request functionality. In initial tests this
* was producing a lot of duplication amongst bundles.
*/
if (isEnabled(props) && Component) {
features.push(React__namespace.createElement(Component, tslib.__assign({ key: name_1 }, props, { visualElement: visualElement })));
}
}
return features;
}
/**
* @public
*/
var MotionConfigContext = React.createContext({
transformPagePoint: function (p) { return p; },
isStatic: false,
reducedMotion: "never",
});
var MotionContext = React.createContext({});
function useVisualElementContext() {
return React.useContext(MotionContext).visualElement;
}
/**
* @public
*/
var PresenceContext = React.createContext(null);
var isBrowser = typeof document !== "undefined";
var useIsomorphicLayoutEffect = isBrowser ? React.useLayoutEffect : React.useEffect;
// Does this device prefer reduced motion? Returns `null` server-side.
var prefersReducedMotion = { current: null };
var hasDetected = false;
function initPrefersReducedMotion() {
hasDetected = true;
if (!isBrowser)
return;
if (window.matchMedia) {
var motionMediaQuery_1 = window.matchMedia("(prefers-reduced-motion)");
var setReducedMotionPreferences = function () {
return (prefersReducedMotion.current = motionMediaQuery_1.matches);
};
motionMediaQuery_1.addListener(setReducedMotionPreferences);
setReducedMotionPreferences();
}
else {
prefersReducedMotion.current = false;
}
}
/**
* A hook that returns `true` if we should be using reduced motion based on the current device's Reduced Motion setting.
*
* This can be used to implement changes to your UI based on Reduced Motion. For instance, replacing motion-sickness inducing
* `x`/`y` animations with `opacity`, disabling the autoplay of background videos, or turning off parallax motion.
*
* It will actively respond to changes and re-render your components with the latest setting.
*
* ```jsx
* export function Sidebar({ isOpen }) {
* const shouldReduceMotion = useReducedMotion()
* const closedX = shouldReduceMotion ? 0 : "-100%"
*
* return (
* <motion.div animate={{
* opacity: isOpen ? 1 : 0,
* x: isOpen ? 0 : closedX
* }} />
* )
* }
* ```
*
* @return boolean
*
* @public
*/
function useReducedMotion() {
/**
* Lazy initialisation of prefersReducedMotion
*/
!hasDetected && initPrefersReducedMotion();
var _a = tslib.__read(React.useState(prefersReducedMotion.current), 1), shouldReduceMotion = _a[0];
/**
* TODO See if people miss automatically updating shouldReduceMotion setting
*/
return shouldReduceMotion;
}
function useReducedMotionConfig() {
var reducedMotionPreference = useReducedMotion();
var reducedMotion = React.useContext(MotionConfigContext).reducedMotion;
if (reducedMotion === "never") {
return false;
}
else if (reducedMotion === "always") {
return true;
}
else {
return reducedMotionPreference;
}
}
function useVisualElement(Component, visualState, props, createVisualElement) {
var lazyContext = React.useContext(LazyContext);
var parent = useVisualElementContext();
var presenceContext = React.useContext(PresenceContext);
var shouldReduceMotion = useReducedMotionConfig();
var visualElementRef = React.useRef(undefined);
/**
* If we haven't preloaded a renderer, check to see if we have one lazy-loaded
*/
if (!createVisualElement)
createVisualElement = lazyContext.renderer;
if (!visualElementRef.current && createVisualElement) {
visualElementRef.current = createVisualElement(Component, {
visualState: visualState,
parent: parent,
props: props,
presenceId: presenceContext === null || presenceContext === void 0 ? void 0 : presenceContext.id,
blockInitialAnimation: (presenceContext === null || presenceContext === void 0 ? void 0 : presenceContext.initial) === false,
shouldReduceMotion: shouldReduceMotion,
});
}
var visualElement = visualElementRef.current;
useIsomorphicLayoutEffect(function () {
visualElement === null || visualElement === void 0 ? void 0 : visualElement.syncRender();
});
React.useEffect(function () {
var _a;
(_a = visualElement === null || visualElement === void 0 ? void 0 : visualElement.animationState) === null || _a === void 0 ? void 0 : _a.animateChanges();
});
useIsomorphicLayoutEffect(function () { return function () { return visualElement === null || visualElement === void 0 ? void 0 : visualElement.notifyUnmount(); }; }, []);
return visualElement;
}
function isRefObject(ref) {
return (typeof ref === "object" &&
Object.prototype.hasOwnProperty.call(ref, "current"));
}
/**
* Creates a ref function that, when called, hydrates the provided
* external ref and VisualElement.
*/
function useMotionRef(visualState, visualElement, externalRef) {
return React.useCallback(function (instance) {
var _a;
instance && ((_a = visualState.mount) === null || _a === void 0 ? void 0 : _a.call(visualState, instance));
if (visualElement) {
instance
? visualElement.mount(instance)
: visualElement.unmount();
}
if (externalRef) {
if (typeof externalRef === "function") {
externalRef(instance);
}
else if (isRefObject(externalRef)) {
externalRef.current = instance;
}
}
},
/**
* Only pass a new ref callback to React if we've received a visual element
* factory. Otherwise we'll be mounting/remounting every time externalRef
* or other dependencies change.
*/
[visualElement]);
}
/**
* Decides if the supplied variable is an array of variant labels
*/
function isVariantLabels(v) {
return Array.isArray(v);
}
/**
* Decides if the supplied variable is variant label
*/
function isVariantLabel(v) {
return typeof v === "string" || isVariantLabels(v);
}
/**
* Creates an object containing the latest state of every MotionValue on a VisualElement
*/
function getCurrent(visualElement) {
var current = {};
visualElement.forEachValue(function (value, key) { return (current[key] = value.get()); });
return current;
}
/**
* Creates an object containing the latest velocity of every MotionValue on a VisualElement
*/
function getVelocity$1(visualElement) {
var velocity = {};
visualElement.forEachValue(function (value, key) { return (velocity[key] = value.getVelocity()); });
return velocity;
}
function resolveVariantFromProps(props, definition, custom, currentValues, currentVelocity) {
var _a;
if (currentValues === void 0) { currentValues = {}; }
if (currentVelocity === void 0) { currentVelocity = {}; }
/**
* If the variant definition is a function, resolve.
*/
if (typeof definition === "function") {
definition = definition(custom !== null && custom !== void 0 ? custom : props.custom, currentValues, currentVelocity);
}
/**
* If the variant definition is a variant label, or
* the function returned a variant label, resolve.
*/
if (typeof definition === "string") {
definition = (_a = props.variants) === null || _a === void 0 ? void 0 : _a[definition];
}
/**
* At this point we've resolved both functions and variant labels,
* but the resolved variant label might itself have been a function.
* If so, resolve. This can only have returned a valid target object.
*/
if (typeof definition === "function") {
definition = definition(custom !== null && custom !== void 0 ? custom : props.custom, currentValues, currentVelocity);
}
return definition;
}
function resolveVariant(visualElement, definition, custom) {
var props = visualElement.getProps();
return resolveVariantFromProps(props, definition, custom !== null && custom !== void 0 ? custom : props.custom, getCurrent(visualElement), getVelocity$1(visualElement));
}
function checkIfControllingVariants(props) {
var _a;
return (typeof ((_a = props.animate) === null || _a === void 0 ? void 0 : _a.start) === "function" ||
isVariantLabel(props.initial) ||
isVariantLabel(props.animate) ||
isVariantLabel(props.whileHover) ||
isVariantLabel(props.whileDrag) ||
isVariantLabel(props.whileTap) ||
isVariantLabel(props.whileFocus) ||
isVariantLabel(props.exit));
}
function checkIfVariantNode(props) {
return Boolean(checkIfControllingVariants(props) || props.variants);
}
function getCurrentTreeVariants(props, context) {
if (checkIfControllingVariants(props)) {
var initial = props.initial, animate = props.animate;
return {
initial: initial === false || isVariantLabel(initial)
? initial
: undefined,
animate: isVariantLabel(animate) ? animate : undefined,
};
}
return props.inherit !== false ? context : {};
}
function useCreateMotionContext(props) {
var _a = getCurrentTreeVariants(props, React.useContext(MotionContext)), initial = _a.initial, animate = _a.animate;
return React.useMemo(function () { return ({ initial: initial, animate: animate }); }, [variantLabelsAsDependency(initial), variantLabelsAsDependency(animate)]);
}
function variantLabelsAsDependency(prop) {
return Array.isArray(prop) ? prop.join(" ") : prop;
}
/**
* Creates a constant value over the lifecycle of a component.
*
* Even if `useMemo` is provided an empty array as its final argument, it doesn't offer
* a guarantee that it won't re-run for performance reasons later on. By using `useConstant`
* you can ensure that initialisers don't execute twice or more.
*/
function useConstant(init) {
var ref = React.useRef(null);
if (ref.current === null) {
ref.current = init();
}
return ref.current;
}
function addUniqueItem(arr, item) {
arr.indexOf(item) === -1 && arr.push(item);
}
function removeItem(arr, item) {
var index = arr.indexOf(item);
index > -1 && arr.splice(index, 1);
}
// Adapted from array-move
function moveItem(_a, fromIndex, toIndex) {
var _b = tslib.__read(_a), arr = _b.slice(0);
var startIndex = fromIndex < 0 ? arr.length + fromIndex : fromIndex;
if (startIndex >= 0 && startIndex < arr.length) {
var endIndex = toIndex < 0 ? arr.length + toIndex : toIndex;
var _c = tslib.__read(arr.splice(fromIndex, 1), 1), item = _c[0];
arr.splice(endIndex, 0, item);
}
return arr;
}
var SubscriptionManager = /** @class */ (function () {
function SubscriptionManager() {
this.subscriptions = [];
}
SubscriptionManager.prototype.add = function (handler) {
var _this = this;
addUniqueItem(this.subscriptions, handler);
return function () { return removeItem(_this.subscriptions, handler); };
};
SubscriptionManager.prototype.notify = function (a, b, c) {
var numSubscriptions = this.subscriptions.length;
if (!numSubscriptions)
return;
if (numSubscriptions === 1) {
/**
* If there's only a single handler we can just call it without invoking a loop.
*/
this.subscriptions[0](a, b, c);
}
else {
for (var i = 0; i < numSubscriptions; i++) {
/**
* Check whether the handler exists before firing as it's possible
* the subscriptions were modified during this loop running.
*/
var handler = this.subscriptions[i];
handler && handler(a, b, c);
}
}
};
SubscriptionManager.prototype.getSize = function () {
return this.subscriptions.length;
};
SubscriptionManager.prototype.clear = function () {
this.subscriptions.length = 0;
};
return SubscriptionManager;
}());
var isFloat = function (value) {
return !isNaN(parseFloat(value));
};
/**
* `MotionValue` is used to track the state and velocity of motion values.
*
* @public
*/
var MotionValue = /** @class */ (function () {
/**
* @param init - The initiating value
* @param config - Optional configuration options
*
* - `transformer`: A function to transform incoming values with.
*
* @internal
*/
function MotionValue(init) {
var _this = this;
/**
* Duration, in milliseconds, since last updating frame.
*
* @internal
*/
this.timeDelta = 0;
/**
* Timestamp of the last time this `MotionValue` was updated.
*
* @internal
*/
this.lastUpdated = 0;
/**
* Functions to notify when the `MotionValue` updates.
*
* @internal
*/
this.updateSubscribers = new SubscriptionManager();
/**
* Functions to notify when the velocity updates.
*
* @internal
*/
this.velocityUpdateSubscribers = new SubscriptionManager();
/**
* Functions to notify when the `MotionValue` updates and `render` is set to `true`.
*
* @internal
*/
this.renderSubscribers = new SubscriptionManager();
/**
* Tracks whether this value can output a velocity. Currently this is only true
* if the value is numerical, but we might be able to widen the scope here and support
* other value types.
*
* @internal
*/
this.canTrackVelocity = false;
this.updateAndNotify = function (v, render) {
if (render === void 0) { render = true; }
_this.prev = _this.current;
_this.current = v;
// Update timestamp
var _a = sync.getFrameData(), delta = _a.delta, timestamp = _a.timestamp;
if (_this.lastUpdated !== timestamp) {
_this.timeDelta = delta;
_this.lastUpdated = timestamp;
sync__default["default"].postRender(_this.scheduleVelocityCheck);
}
// Update update subscribers
if (_this.prev !== _this.current) {
_this.updateSubscribers.notify(_this.current);
}
// Update velocity subscribers
if (_this.velocityUpdateSubscribers.getSize()) {
_this.velocityUpdateSubscribers.notify(_this.getVelocity());
}
// Update render subscribers
if (render) {
_this.renderSubscribers.notify(_this.current);
}
};
/**
* Schedule a velocity check for the next frame.
*
* This is an instanced and bound function to prevent generating a new
* function once per frame.
*
* @internal
*/
this.scheduleVelocityCheck = function () { return sync__default["default"].postRender(_this.velocityCheck); };
/**
* Updates `prev` with `current` if the value hasn't been updated this frame.
* This ensures velocity calculations return `0`.
*
* This is an instanced and bound function to prevent generating a new
* function once per frame.
*
* @internal
*/
this.velocityCheck = function (_a) {
var timestamp = _a.timestamp;
if (timestamp !== _this.lastUpdated) {
_this.prev = _this.current;
_this.velocityUpdateSubscribers.notify(_this.getVelocity());
}
};
this.hasAnimated = false;
this.prev = this.current = init;
this.canTrackVelocity = isFloat(this.current);
}
/**
* Adds a function that will be notified when the `MotionValue` is updated.
*
* It returns a function that, when called, will cancel the subscription.
*
* When calling `onChange` inside a React component, it should be wrapped with the
* `useEffect` hook. As it returns an unsubscribe function, this should be returned
* from the `useEffect` function to ensure you don't add duplicate subscribers..
*
* ```jsx
* export const MyComponent = () => {
* const x = useMotionValue(0)
* const y = useMotionValue(0)
* const opacity = useMotionValue(1)
*
* useEffect(() => {
* function updateOpacity() {
* const maxXY = Math.max(x.get(), y.get())
* const newOpacity = transform(maxXY, [0, 100], [1, 0])
* opacity.set(newOpacity)
* }
*
* const unsubscribeX = x.onChange(updateOpacity)
* const unsubscribeY = y.onChange(updateOpacity)
*
* return () => {
* unsubscribeX()
* unsubscribeY()
* }
* }, [])
*
* return <motion.div style={{ x }} />
* }
* ```
*
* @internalremarks
*
* We could look into a `useOnChange` hook if the above lifecycle management proves confusing.
*
* ```jsx
* useOnChange(x, () => {})
* ```
*
* @param subscriber - A function that receives the latest value.
* @returns A function that, when called, will cancel this subscription.
*
* @public
*/
MotionValue.prototype.onChange = function (subscription) {
return this.updateSubscribers.add(subscription);
};
MotionValue.prototype.clearListeners = function () {
this.updateSubscribers.clear();
};
/**
* Adds a function that will be notified when the `MotionValue` requests a render.
*
* @param subscriber - A function that's provided the latest value.
* @returns A function that, when called, will cancel this subscription.
*
* @internal
*/
MotionValue.prototype.onRenderRequest = function (subscription) {
// Render immediately
subscription(this.get());
return this.renderSubscribers.add(subscription);
};
/**
* Attaches a passive effect to the `MotionValue`.
*
* @internal
*/
MotionValue.prototype.attach = function (passiveEffect) {
this.passiveEffect = passiveEffect;
};
/**
* Sets the state of the `MotionValue`.
*
* @remarks
*
* ```jsx
* const x = useMotionValue(0)
* x.set(10)
* ```
*
* @param latest - Latest value to set.
* @param render - Whether to notify render subscribers. Defaults to `true`
*
* @public
*/
MotionValue.prototype.set = function (v, render) {
if (render === void 0) { render = true; }
if (!render || !this.passiveEffect) {
this.updateAndNotify(v, render);
}
else {
this.passiveEffect(v, this.updateAndNotify);
}
};
/**
* Returns the latest state of `MotionValue`
*
* @returns - The latest state of `MotionValue`
*
* @public
*/
MotionValue.prototype.get = function () {
return this.current;
};
/**
* @public
*/
MotionValue.prototype.getPrevious = function () {
return this.prev;
};
/**
* Returns the latest velocity of `MotionValue`
*
* @returns - The latest velocity of `MotionValue`. Returns `0` if the state is non-numerical.
*
* @public
*/
MotionValue.prototype.getVelocity = function () {
// This could be isFloat(this.prev) && isFloat(this.current), but that would be wasteful
return this.canTrackVelocity
? // These casts could be avoided if parseFloat would be typed better
popmotion.velocityPerSecond(parseFloat(this.current) -
parseFloat(this.prev), this.timeDelta)
: 0;
};
/**
* Registers a new animation to control this `MotionValue`. Only one
* animation can drive a `MotionValue` at one time.
*
* ```jsx
* value.start()
* ```
*
* @param animation - A function that starts the provided animation
*
* @internal
*/
MotionValue.prototype.start = function (animation) {
var _this = this;
this.stop();
return new Promise(function (resolve) {
_this.hasAnimated = true;
_this.stopAnimation = animation(resolve);
}).then(function () { return _this.clearAnimation(); });
};
/**
* Stop the currently active animation.
*
* @public
*/
MotionValue.prototype.stop = function () {
if (this.stopAnimation)
this.stopAnimation();
this.clearAnimation();
};
/**
* Returns `true` if this value is currently animating.
*
* @public
*/
MotionValue.prototype.isAnimating = function () {
return !!this.stopAnimation;
};
MotionValue.prototype.clearAnimation = function () {
this.stopAnimation = null;
};
/**
* Destroy and clean up subscribers to this `MotionValue`.
*
* The `MotionValue` hooks like `useMotionValue` and `useTransform` automatically
* handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually
* created a `MotionValue` via the `motionValue` function.
*
* @public
*/
MotionValue.prototype.destroy = function () {
this.updateSubscribers.clear();
this.renderSubscribers.clear();
this.stop();
};
return MotionValue;
}());
/**
* @internal
*/
function motionValue(init) {
return new MotionValue(init);
}
var isMotionValue = function (value) {
return Boolean(value !== null && typeof value === "object" && value.getVelocity);
};
/**
* Converts seconds to milliseconds
*
* @param seconds - Time in seconds.
* @return milliseconds - Converted time in milliseconds.
*/
var secondsToMilliseconds = function (seconds) { return seconds * 1000; };
var easingLookup = {
linear: popmotion.linear,
easeIn: popmotion.easeIn,
easeInOut: popmotion.easeInOut,
easeOut: popmotion.easeOut,
circIn: popmotion.circIn,
circInOut: popmotion.circInOut,
circOut: popmotion.circOut,
backIn: popmotion.backIn,
backInOut: popmotion.backInOut,
backOut: popmotion.backOut,
anticipate: popmotion.anticipate,
bounceIn: popmotion.bounceIn,
bounceInOut: popmotion.bounceInOut,
bounceOut: popmotion.bounceOut,
};
var easingDefinitionToFunction = function (definition) {
if (Array.isArray(definition)) {
// If cubic bezier definition, create bezier curve
heyListen.invariant(definition.length === 4, "Cubic bezier arrays must contain four numerical values.");
var _a = tslib.__read(definition, 4), x1 = _a[0], y1 = _a[1], x2 = _a[2], y2 = _a[3];
return popmotion.cubicBezier(x1, y1, x2, y2);
}
else if (typeof definition === "string") {
// Else lookup from table
heyListen.invariant(easingLookup[definition] !== undefined, "Invalid easing type '".concat(definition, "'"));
return easingLookup[definition];
}
return definition;
};
var isEasingArray = function (ease) {
return Array.isArray(ease) && typeof ease[0] !== "number";
};
/**
* Check if a value is animatable. Examples:
*
* ✅: 100, "100px", "#fff"
* ❌: "block", "url(2.jpg)"
* @param value
*
* @internal
*/
var isAnimatable = function (key, value) {
// If the list of keys tat might be non-animatable grows, replace with Set
if (key === "zIndex")
return false;
// If it's a number or a keyframes array, we can animate it. We might at some point
// need to do a deep isAnimatable check of keyframes, or let Popmotion handle this,
// but for now lets leave it like this for performance reasons
if (typeof value === "number" || Array.isArray(value))
return true;
if (typeof value === "string" && // It's animatable if we have a string
styleValueTypes.complex.test(value) && // And it contains numbers and/or colors
!value.startsWith("url(") // Unless it starts with "url("
) {
return true;
}
return false;
};
var isKeyframesTarget = function (v) {
return Array.isArray(v);
};
var underDampedSpring = function () { return ({
type: "spring",
stiffness: 500,
damping: 25,
restSpeed: 10,
}); };
var criticallyDampedSpring = function (to) { return ({
type: "spring",
stiffness: 550,
damping: to === 0 ? 2 * Math.sqrt(550) : 30,
restSpeed: 10,
}); };
var linearTween = function () { return ({
type: "keyframes",
ease: "linear",
duration: 0.3,
}); };
var keyframes = function (values) { return ({
type: "keyframes",
duration: 0.8,
values: values,
}); };
var defaultTransitions = {
x: underDampedSpring,
y: underDampedSpring,
z: underDampedSpring,
rotate: underDampedSpring,
rotateX: underDampedSpring,
rotateY: underDampedSpring,
rotateZ: underDampedSpring,
scaleX: criticallyDampedSpring,
scaleY: criticallyDampedSpring,
scale: criticallyDampedSpring,
opacity: linearTween,
backgroundColor: linearTween,
color: linearTween,
default: criticallyDampedSpring,
};
var getDefaultTransition = function (valueKey, to) {
var transitionFactory;
if (isKeyframesTarget(to)) {
transitionFactory = keyframes;
}
else {
transitionFactory =
defaultTransitions[valueKey] || defaultTransitions.default;
}
return tslib.__assign({ to: to }, transitionFactory(to));
};
var int = tslib.__assign(tslib.__assign({}, styleValueTypes.number), { transform: Math.round });
var numberValueTypes = {
// Border props
borderWidth: styleValueTypes.px,
borderTopWidth: styleValueTypes.px,
borderRightWidth: styleValueTypes.px,
borderBottomWidth: styleValueTypes.px,
borderLeftWidth: styleValueTypes.px,
borderRadius: styleValueTypes.px,
radius: styleValueTypes.px,
borderTopLeftRadius: styleValueTypes.px,
borderTopRightRadius: styleValueTypes.px,
borderBottomRightRadius: styleValueTypes.px,
borderBottomLeftRadius: styleValueTypes.px,
// Positioning props
width: styleValueTypes.px,
maxWidth: styleValueTypes.px,
height: styleValueTypes.px,
maxHeight: styleValueTypes.px,
size: styleValueTypes.px,
top: styleValueTypes.px,
right: styleValueTypes.px,
bottom: styleValueTypes.px,
left: styleValueTypes.px,
// Spacing props
padding: styleValueTypes.px,
paddingTop: styleValueTypes.px,
paddingRight: styleValueTypes.px,
paddingBottom: styleValueTypes.px,
paddingLeft: styleValueTypes.px,
margin: styleValueTypes.px,
marginTop: styleValueTypes.px,
marginRight: styleValueTypes.px,
marginBottom: styleValueTypes.px,
marginLeft: styleValueTypes.px,
// Transform props
rotate: styleValueTypes.degrees,
rotateX: styleValueTypes.degrees,
rotateY: styleValueTypes.degrees,
rotateZ: styleValueTypes.degrees,
scale: styleValueTypes.scale,
scaleX: styleValueTypes.scale,
scaleY: styleValueTypes.scale,
scaleZ: styleValueTypes.scale,
skew: styleValueTypes.degrees,
skewX: styleValueTypes.degrees,
skewY: styleValueTypes.degrees,
distance: styleValueTypes.px,
translateX: styleValueTypes.px,
translateY: styleValueTypes.px,
translateZ: styleValueTypes.px,
x: styleValueTypes.px,
y: styleValueTypes.px,
z: styleValueTypes.px,
perspective: styleValueTypes.px,
transformPerspective: styleValueTypes.px,
opacity: styleValueTypes.alpha,
originX: styleValueTypes.progressPercentage,
originY: styleValueTypes.progressPercentage,
originZ: styleValueTypes.px,
// Misc
zIndex: int,
// SVG
fillOpacity: styleValueTypes.alpha,
strokeOpacity: styleValueTypes.alpha,
numOctaves: int,
};
/**
* A map of default value types for common values
*/
var defaultValueTypes = tslib.__assign(tslib.__assign({}, numberValueTypes), {
// Color props
color: styleValueTypes.color, backgroundColor: styleValueTypes.color, outlineColor: styleValueTypes.color, fill: styleValueTypes.color, stroke: styleValueTypes.color,
// Border props
borderColor: styleValueTypes.color, borderTopColor: styleValueTypes.color, borderRightColor: styleValueTypes.color, borderBottomColor: styleValueTypes.color, borderLeftColor: styleValueTypes.color, filter: styleValueTypes.filter, WebkitFilter: styleValueTypes.filter });
/**
* Gets the default ValueType for the provided value key
*/
var getDefaultValueType = function (key) { return defaultValueTypes[key]; };
function getAnimatableNone(key, value) {
var _a;
var defaultValueType = getDefaultValueType(key);
if (defaultValueType !== styleValueTypes.filter)
defaultValueType = styleValueTypes.complex;
// If value is not recognised as animatable, ie "none", create an animatable version origin based on the target
return (_a = defaultValueType.getAnimatableNone) === null || _a === void 0 ? void 0 : _a.call(defaultValueType, value);
}
var instantAnimationState = {
current: false,
};
var isCustomValue = function (v) {
return Boolean(v && typeof v === "object" && v.mix && v.toValue);
};
var resolveFinalValueInKeyframes = function (v) {
// TODO maybe throw if v.length - 1 is placeholder token?
return isKeyframesTarget(v) ? v[v.length - 1] || 0 : v;
};
/**
* Decide whether a transition is defined on a given Transition.
* This filters out orchestration options and returns true
* if any options are left.
*/
function isTransitionDefined(_a) {
_a.when; _a.delay; _a.delayChildren; _a.staggerChildren; _a.staggerDirection; _a.repeat; _a.repeatType; _a.repeatDelay; _a.from; var transition = tslib.__rest(_a, ["when", "delay", "delayChildren", "staggerChildren", "staggerDirection", "repeat", "repeatType", "repeatDelay", "from"]);
return !!Object.keys(transition).length;
}
var legacyRepeatWarning = false;
/**
* Convert Framer Motion's Transition type into Popmotion-compatible options.
*/
function convertTransitionToAnimationOptions(_a) {
var ease = _a.ease, times = _a.times, yoyo = _a.yoyo, flip = _a.flip, loop = _a.loop, transition = tslib.__rest(_a, ["ease", "times", "yoyo", "flip", "loop"]);
var options = tslib.__assign({}, transition);
if (times)
options["offset"] = times;
/**
* Convert any existing durations from seconds to milliseconds
*/
if (transition.duration)
options["duration"] = secondsToMilliseconds(transition.duration);
if (transition.repeatDelay)
options.repeatDelay = secondsToMilliseconds(transition.repeatDelay);
/**
* Map easing names to Popmotion's easing functions
*/
if (ease) {
options["ease"] = isEasingArray(ease)
? ease.map(easingDefinitionToFunction)
: easingDefinitionToFunction(ease);
}
/**
* Support legacy transition API
*/
if (transition.type === "tween")
options.type = "keyframes";
/**
* TODO: These options are officially removed from the API.
*/
if (yoyo || loop || flip) {
heyListen.warning(!legacyRepeatWarning, "yoyo, loop and flip have been removed from the API. Replace with repeat and repeatType options.");
legacyRepeatWarning = true;
if (yoyo) {
options.repeatType = "reverse";
}
else if (loop) {
options.repeatType = "loop";
}
else if (flip) {
options.repeatType = "mirror";
}
options.repeat = loop || yoyo || flip || transition.repeat;
}
/**
* TODO: Popmotion 9 has the ability to automatically detect whether to use
* a keyframes or spring animation, but does so by detecting velocity and other spring options.
* It'd be good to introduce a similar thing here.
*/
if (transition.type !== "spring")
options.type = "keyframes";
return options;
}
/**
* Get the delay for a value by checking Transition with decreasing specificity.
*/
function getDelayFromTransition(transition, key) {
var _a, _b;
var valueTransition = getValueTransition(transition, key) || {};
return (_b = (_a = valueTransition.delay) !== null && _a !== void 0 ? _a : transition.delay) !== null && _b !== void 0 ? _b : 0;
}
function hydrateKeyframes(options) {
if (Array.isArray(options.to) && options.to[0] === null) {
options.to = tslib.__spreadArray([], tslib.__read(options.to), false);
options.to[0] = options.from;
}
return options;
}
function getPopmotionAnimationOptions(transition, options, key) {
var _a;
if (Array.isArray(options.to)) {
(_a = transition.duration) !== null && _a !== void 0 ? _a : (transition.duration = 0.8);
}
hydrateKeyframes(options);
/**
* Get a default transition if none is determined to be defined.
*/
if (!isTransitionDefined(transition)) {
transition = tslib.__assign(tslib.__assign({}, transition), getDefaultTransition(key, options.to));
}
return tslib.__assign(tslib.__assign({}, options), convertTransitionToAnimationOptions(transition));
}
/**
*
*/
function getAnimation(key, value, target, transition, onComplete) {
var _a;
var valueTransition = getValueTransition(transition, key);
var origin = (_a = valueTransition.from) !== null && _a !== void 0 ? _a : value.get();
var isTargetAnimatable = isAnimatable(key, target);
if (origin === "none" && isTargetAnimatable && typeof target === "string") {
/**
* If we're trying to animate from "none", try and get an animatable version
* of the target. This could be improved to work both ways.
*/
origin = getAnimatableNone(key, target);
}
else if (isZero(origin) && typeof target === "string") {
origin = getZeroUnit(target);
}
else if (!Array.isArray(target) &&
isZero(target) &&
typeof origin === "string") {
target = getZeroUnit(origin);
}
var isOriginAnimatable = isAnimatable(key, origin);
heyListen.warning(isOriginAnimatable === isTargetAnimatable, "You are trying to animate ".concat(key, " from \"").concat(origin, "\" to \"").concat(target, "\". ").concat(origin, " is not an animatable value - to enable this animation set ").concat(origin, " to a value animatable to ").concat(target, " via the `style` property."));
function start() {
var options = {
from: origin,
to: target,
velocity: value.getVelocity(),
onComplete: onComplete,
onUpdate: function (v) { return value.set(v); },
};
return valueTransition.type === "inertia" ||
valueTransition.type === "decay"
? popmotion.inertia(tslib.__assign(tslib.__assign({}, options), valueTransition))
: popmotion.animate(tslib.__assign(tslib.__assign({}, getPopmotionAnimationOptions(valueTransition, options, key)), { onUpdate: function (v) {
var _a;
options.onUpdate(v);
(_a = valueTransition.onUpdate) === null || _a === void 0 ? void 0 : _a.call(valueTransition, v);
}, onComplete: function () {
var _a;
options.onComplete();
(_a = valueTransition.onComplete) === null || _a === void 0 ? void 0 : _a.call(valueTransition);
} }));
}
function set() {
var _a, _b;
var finalTarget = resolveFinalValueInKeyframes(target);
value.set(finalTarget);
onComplete();
(_a = valueTransition === null || valueTransition === void 0 ? void 0 : valueTransition.onUpdate) === null || _a === void 0 ? void 0 : _a.call(valueTransition, finalTarget);
(_b = valueTransition === null || valueTransition === void 0 ? void 0 : valueTransition.onComplete) === null || _b === void 0 ? void 0 : _b.call(valueTransition);
return { stop: function () { } };
}
return !isOriginAnimatable ||
!isTargetAnimatable ||
valueTransition.type === false
? set
: start;
}
function isZero(value) {
return (value === 0 ||
(typeof value === "string" &&
parseFloat(value) === 0 &&
value.indexOf(" ") === -1));
}
function getZeroUnit(potentialUnitType) {
return typeof potentialUnitType === "number"
? 0
: getAnimatableNone("", potentialUnitType);
}
function getValueTransition(transition, key) {
return transition[key] || transition["default"] || transition;
}
/**
* Start animation on a MotionValue. This function is an interface between
* Framer Motion and Popmotion
*
* @internal
*/
function startAnimation(key, value, target, transition) {
if (transition === void 0) { transition = {}; }
if (instantAnimationState.current) {
transition = { type: false };
}
return value.start(function (onComplete) {
var delayTimer;
var controls;
var animation = getAnimation(key, value, target, transition, onComplete);
var delay = getDelayFromTransition(transition, key);
var start = function () { return (controls = animation()); };
if (delay) {
delayTimer = window.setTimeout(start, secondsToMilliseconds(delay));
}
else {
start();
}
return function () {
clearTimeout(delayTimer);
controls === null || controls === void 0 ? void 0 : controls.stop();
};
});
}
/**
* Animate a single value or a `MotionValue`.
*
* The first argument is either a `MotionValue` to animate, or an initial animation value.
*
* The second is either a value to animate to, or an array of keyframes to animate through.
*
* The third argument can be either tween or spring options, and optional lifecycle methods: `onUpdate`, `onPlay`, `onComplete`, `onRepeat` and `onStop`.
*
* Returns `AnimationPlaybackControls`, currently just a `stop` method.
*
* ```javascript
* const x = useMotionValue(0)
*
* useEffect(() => {
* const controls = animate(x, 100, {
* type: "spring",
* stiffness: 2000,
* onComplete: v => {}
* })
*
* return controls.stop
* })
* ```
*
* @public
*/
function animate(from, to, transition) {
if (transition === void 0) { transition = {}; }
var value = isMotionValue(from) ? from : motionValue(from);
startAnimation("", value, to, transition);
return {
stop: function () { return value.stop(); },
isAnimating: function () { return value.isAnimating(); },
};
}
var borders = ["TopLeft", "TopRight", "BottomLeft", "BottomRight"];
var numBorders = borders.length;
var asNumber = function (value) {
return typeof value === "string" ? parseFloat(value) : value;
};
var isPx = function (value) {
return typeof value === "number" || styleValueTypes.px.test(value);
};
function mixValues(target, follow, lead, progress, shouldCrossfadeOpacity, isOnlyMember) {
var _a, _b, _c, _d;
if (shouldCrossfadeOpacity) {
target.opacity = popmotion.mix(0,
// (follow?.opacity as number) ?? 0,
// TODO Reinstate this if only child
(_a = lead.opacity) !== null && _a !== void 0 ? _a : 1, easeCrossfadeIn(progress));
target.opacityExit = popmotion.mix((_b = follow.opacity) !== null && _b !== void 0 ? _b : 1, 0, easeCrossfadeOut(progress));
}
else if (isOnlyMember) {
target.opacity = popmotion.mix((_c = follow.opacity) !== null && _c !== void 0 ? _c : 1, (_d = lead.opacity) !== null && _d !== void 0 ? _d : 1, progress);
}
/**
* Mix border radius
*/
for (var i = 0; i < numBorders; i++) {
var borderLabel = "border".concat(borders[i], "Radius");
var followRadius = getRadius(follow, borderLabel);
var leadRadius = getRadius(lead, borderLabel);
if (followRadius === undefined && leadRadius === undefined)
continue;
followRadius || (followRadius = 0);
leadRadius || (leadRadius = 0);
var canMix = followRadius === 0 ||
leadRadius === 0 ||
isPx(followRadius) === isPx(leadRadius);
if (canMix) {
target[borderLabel] = Math.max(popmotion.mix(asNumber(followRadius), asNumber(leadRadius), progress), 0);
if (styleValueTypes.percent.test(leadRadius) || styleValueTypes.percent.test(followRadius)) {
target[borderLabel] += "%";
}
}
else {
target[borderLabel] = leadRadius;
}
}
/**
* Mix rotation
*/
if (follow.rotate || lead.rotate) {
target.rotate = popmotion.mix(follow.rotate || 0, lead.rotate || 0, progress);
}
}
function getRadius(values, radiusName) {
var _a;
return (_a = values[radiusName]) !== null && _a !== void 0 ? _a : values.borderRadius;
}
// /**
// * We only want to mix the background color if there's a follow element
// * that we're not crossfading opacity between. For instance with switch
// * AnimateSharedLayout animations, this helps the illusion of a continuous
// * element being animated but also cuts down on the number of paints triggered
// * for elements where opacity is doing that work for us.
// */
// if (
// !hasFollowElement &&
// latestLeadValues.backgroundColor &&
// latestFollowValues.backgroundColor
// ) {
// /**
// * This isn't ideal performance-wise as mixColor is creating a new function every frame.
// * We could probably create a mixer that runs at the start of the animation but
// * the idea behind the crossfader is that it runs dynamically between two potentially
// * changing targets (ie opacity or borderRadius may be animating independently via variants)
// */
// leadState.backgroundColor = followState.backgroundColor = mixColor(
// latestFollowValues.backgroundColor as string,
// latestLeadValues.backgroundColor as string
// )(p)
// }
var easeCrossfadeIn = compress(0, 0.5, popmotion.circOut);
var easeCrossfadeOut = compress(0.5, 0.95, popmotion.linear);
function compress(min, max, easing) {
return function (p) {
// Could replace ifs with clamp
if (p < min)
return 0;
if (p > max)
return 1;
return easing(popmotion.progress(min, max, p));
};
}
/**
* Reset an axis to the provided origin box.
*
* This is a mutative operation.
*/
function copyAxisInto(axis, originAxis) {
axis.min = originAxis.min;
axis.max = originAxis.max;
}
/**
* Reset a box to the provided origin box.
*
* This is a mutative operation.
*/
function copyBoxInto(box, originBox) {
copyAxisInto(box.x, originBox.x);
copyAxisInto(box.y, originBox.y);
}
function isIdentityScale(scale) {
return scale === undefined || scale === 1;
}
function hasScale(_a) {
var scale = _a.scale, scaleX = _a.scaleX, scaleY = _a.scaleY;
return (!isIdentityScale(scale) ||
!isIdentityScale(scaleX) ||
!isIdentityScale(scaleY));
}
function hasTransform(values) {
return (hasScale(values) ||
hasTranslate(values.x) ||
hasTranslate(values.y) ||
values.z ||
values.rotate ||
values.rotateX ||
values.rotateY);
}
function hasTranslate(value) {
return value && value !== "0%";
}
/**
* Scales a point based on a factor and an originPoint
*/
function scalePoint(point, scale, originPoint) {
var distanceFromOrigin = point - originPoint;
var scaled = scale * distanceFromOrigin;
return originPoint + scaled;
}
/**
* Applies a translate/scale delta to a point
*/
function applyPointDelta(point, translate, scale, originPoint, boxScale) {
if (boxScale !== undefined) {
point = scalePoint(point, boxScale, originPoint);
}
return scalePoint(point, scale, originPoint) + translate;
}
/**
* Applies a translate/scale delta to an axis
*/
function applyAxisDelta(axis, translate, scale, originPoint, boxScale) {
if (translate === void 0) { translate = 0; }
if (scale === void 0) { scale = 1; }
axis.min = applyPointDelta(axis.min, translate, scale, originPoint, boxScale);
axis.max = applyPointDelta(axis.max, translate, scale, originPoint, boxScale);
}
/**
* Applies a translate/scale delta to a box
*/
function applyBoxDelta(box, _a) {
var x = _a.x, y = _a.y;
applyAxisDelta(box.x, x.translate, x.scale, x.originPoint);
applyAxisDelta(box.y, y.translate, y.scale, y.originPoint);
}
/**
* Apply a tree of deltas to a box. We do this to calculate the effect of all the transforms
* in a tree upon our box before then calculating how to project it into our desired viewport-relative box
*
* This is the final nested loop within updateLayoutDelta for future refactoring
*/
function applyTreeDeltas(box, treeScale, treePath, isSharedTransition) {
var _a, _b;
if (isSharedTransition === void 0) { isSharedTransition = false; }
var treeLength = treePath.length;
if (!treeLength)
return;
// Reset the treeScale
treeScale.x = treeScale.y = 1;
var node;
var delta;
for (var i = 0; i < treeLength; i++) {
node = treePath[i];
delta = node.projectionDelta;
if (((_b = (_a = node.instance) === null || _a === void 0 ? void 0 : _a.style) === null || _b === void 0 ? void 0 : _b.display) === "contents")
continue;
if (isSharedTransition &&
node.options.layoutScroll &&
node.scroll &&
node !== node.root) {
transformBox(box, { x: -node.scroll.x, y: -node.scroll.y });
}
if (delta) {