'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var tslib = require('tslib');
var React = require('react');
var heyListen = require('hey-listen');
var styleValueTypes = require('style-value-types');
var popmotion = require('popmotion');
var sync = require('framesync');
var dom = require('@motionone/dom');
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 defaultEnvironment = "production";
var env = typeof process === "undefined" || process.env === undefined
? defaultEnvironment
: process.env.NODE_ENV || defaultEnvironment;
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 (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;
}
/**
* This should only ever be modified on the client otherwise it'll
* persist through server requests. If we need instanced states we
* could lazy-init via root.
*/
var globalProjectionState = {
/**
* Global flag as to whether the tree has animated since the last time
* we resized the window
*/
hasAnimatedSinceResize: true,
/**
* We set this to true once, on the first update. Any nodes added to the tree beyond that
* update will be given a `data-projection-id` attribute.
*/
hasEverUpdated: false,
};
var id$1 = 1;
function useProjectionId() {
return useConstant(function () {
if (globalProjectionState.hasEverUpdated) {
return id$1++;
}
});
}
var LayoutGroupContext = React.createContext({});
/**
* Internal, exported only for usage in Framer
*/
var SwitchLayoutGroupContext = React.createContext({});
function useProjection(projectionId, _a, visualElement, ProjectionNodeConstructor) {
var _b;
var layoutId = _a.layoutId, layout = _a.layout, drag = _a.drag, dragConstraints = _a.dragConstraints, layoutScroll = _a.layoutScroll;
var initialPromotionConfig = React.useContext(SwitchLayoutGroupContext);
if (!ProjectionNodeConstructor ||
!visualElement ||
(visualElement === null || visualElement === void 0 ? void 0 : visualElement.projection)) {
return;
}
visualElement.projection = new ProjectionNodeConstructor(projectionId, visualElement.getLatestValues(), (_b = visualElement.parent) === null || _b === void 0 ? void 0 : _b.projection);
visualElement.projection.setOptions({
layoutId: layoutId,
layout: layout,
alwaysMeasureLayout: Boolean(drag) || (dragConstraints && isRefObject(dragConstraints)),
visualElement: visualElement,
scheduleRender: function () { return visualElement.scheduleRender(); },
/**
* TODO: Update options in an effect. This could be tricky as it'll be too late
* to update by the time layout animations run.
* We also need to fix this safeToRemove by linking it up to the one returned by usePresence,
* ensuring it gets called if there's no potential layout animations.
*
*/
animationType: typeof layout === "string" ? layout : "both",
initialPromotionConfig: initialPromotionConfig,
layoutScroll: layoutScroll,
});
}
var VisualElementHandler = /** @class */ (function (_super) {
tslib.__extends(VisualElementHandler, _super);
function VisualElementHandler() {
return _super !== null && _super.apply(this, arguments) || this;
}
/**
* Update visual element props as soon as we know this update is going to be commited.
*/
VisualElementHandler.prototype.getSnapshotBeforeUpdate = function () {
this.updateProps();
return null;
};
VisualElementHandler.prototype.componentDidUpdate = function () { };
VisualElementHandler.prototype.updateProps = function () {
var _a = this.props, visualElement = _a.visualElement, props = _a.props;
if (visualElement)
visualElement.setProps(props);
};
VisualElementHandler.prototype.render = function () {
return this.props.children;
};
return VisualElementHandler;
}(React__default["default"].Component));
/**
* Create a `motion` component.
*
* This function accepts a Component argument, which can be either a string (ie "div"
* for `motion.div`), or an actual React component.
*
* Alongside this is a config option which provides a way of rendering the provided
* component "offline", or outside the React render cycle.
*/
function createMotionComponent(_a) {
var preloadedFeatures = _a.preloadedFeatures, createVisualElement = _a.createVisualElement, projectionNodeConstructor = _a.projectionNodeConstructor, useRender = _a.useRender, useVisualState = _a.useVisualState, Component = _a.Component;
preloadedFeatures && loadFeatures(preloadedFeatures);
function MotionComponent(props, externalRef) {
var layoutId = useLayoutId(props);
props = tslib.__assign(tslib.__assign({}, props), { layoutId: layoutId });
/**
* If we're rendering in a static environment, we only visually update the component
* as a result of a React-rerender rather than interactions or animations. This
* means we don't need to load additional memory structures like VisualElement,
* or any gesture/animation features.
*/
var config = React.useContext(MotionConfigContext);
var features = null;
var context = useCreateMotionContext(props);
/**
* Create a unique projection ID for this component. If a new component is added
* during a layout animation we'll use this to query the DOM and hydrate its ref early, allowing
* us to measure it as soon as any layout effect flushes pending layout animations.
*
* Performance note: It'd be better not to have to search the DOM for these elements.
* For newly-entering components it could be enough to only correct treeScale, in which
* case we could mount in a scale-correction mode. This wouldn't be enough for
* shared element transitions however. Perhaps for those we could revert to a root node
* that gets forceRendered and layout animations are triggered on its layout effect.
*/
var projectionId = config.isStatic ? undefined : useProjectionId();
/**
*
*/
var visualState = useVisualState(props, config.isStatic);
if (!config.isStatic && isBrowser) {
/**
* Create a VisualElement for this component. A VisualElement provides a common
* interface to renderer-specific APIs (ie DOM/Three.js etc) as well as
* providing a way of rendering to these APIs outside of the React render loop
* for more performant animations and interactions
*/
context.visualElement = useVisualElement(Component, visualState, tslib.__assign(tslib.__assign({}, config), props), createVisualElement);
useProjection(projectionId, props, context.visualElement, projectionNodeConstructor ||
featureDefinitions.projectionNodeConstructor);
/**
* Load Motion gesture and animation features. These are rendered as renderless
* components so each feature can optionally make use of React lifecycle methods.
*/
features = useFeatures(props, context.visualElement, preloadedFeatures);
}
/**
* The mount order and hierarchy is specific to ensure our element ref
* is hydrated by the time features fire their effects.
*/
return (React__namespace.createElement(VisualElementHandler, { visualElement: context.visualElement, props: tslib.__assign(tslib.__assign({}, config), props) },
features,
React__namespace.createElement(MotionContext.Provider, { value: context }, useRender(Component, props, projectionId, useMotionRef(visualState, context.visualElement, externalRef), visualState, config.isStatic, context.visualElement))));
}
return React.forwardRef(MotionComponent);
}
function useLayoutId(_a) {
var _b;
var layoutId = _a.layoutId;
var layoutGroupId = (_b = React.useContext(LayoutGroupContext)) === null || _b === void 0 ? void 0 : _b.id;
return layoutGroupId && layoutId !== undefined
? layoutGroupId + "-" + layoutId
: layoutId;
}
/**
* Convert any React component into a `motion` component. The provided component
* **must** use `React.forwardRef` to the underlying DOM component you want to animate.
*
* ```jsx
* const Component = React.forwardRef((props, ref) => {
* return <div ref={ref} />
* })
*
* const MotionComponent = motion(Component)
* ```
*
* @public
*/
function createMotionProxy(createConfig) {
function custom(Component, customMotionComponentConfig) {
if (customMotionComponentConfig === void 0) { customMotionComponentConfig = {}; }
return createMotionComponent(createConfig(Component, customMotionComponentConfig));
}
if (typeof Proxy === "undefined") {
return custom;
}
/**
* A cache of generated `motion` components, e.g `motion.div`, `motion.input` etc.
* Rather than generating them anew every render.
*/
var componentCache = new Map();
return new Proxy(custom, {
/**
* Called when `motion` is referenced with a prop: `motion.div`, `motion.input` etc.
* The prop name is passed through as `key` and we can use that to generate a `motion`
* DOM component with that name.
*/
get: function (_target, key) {
/**
* If this element doesn't exist in the component cache, create it and cache.
*/
if (!componentCache.has(key)) {
componentCache.set(key, custom(key));
}
return componentCache.get(key);
},
});
}
/**
* We keep these listed seperately as we use the lowercase tag names as part
* of the runtime bundle to detect SVG components
*/
var lowercaseSVGElements = [
"animate",
"circle",
"defs",
"desc",
"ellipse",
"g",
"image",
"line",
"filter",
"marker",
"mask",
"metadata",
"path",
"pattern",
"polygon",
"polyline",
"rect",
"stop",
"svg",
"switch",
"symbol",
"text",
"tspan",
"use",
"view",
];
function isSVGComponent(Component) {
if (
/**
* If it's not a string, it's a custom React component. Currently we only support
* HTML custom React components.
*/
typeof Component !== "string" ||
/**
* If it contains a dash, the element is a custom HTML webcomponent.
*/
Component.includes("-")) {
return false;
}
else if (
/**
* If it's in our list of lowercase SVG tags, it's an SVG component
*/
lowercaseSVGElements.indexOf(Component) > -1 ||
/**
* If it contains a capital letter, it's an SVG component
*/
/[A-Z]/.test(Component)) {
return true;
}
return false;
}
var scaleCorrectors = {};
function addScaleCorrector(correctors) {
Object.assign(scaleCorrectors, correctors);
}
/**
* A list of all transformable axes. We'll use this list to generated a version
* of each axes for each transform.
*/
var transformAxes = ["", "X", "Y", "Z"];
/**
* An ordered array of each transformable value. By default, transform values
* will be sorted to this order.
*/
var order = ["translate", "scale", "rotate", "skew"];
/**
* Generate a list of every possible transform key.
*/
var transformProps = ["transformPerspective", "x", "y", "z"];
order.forEach(function (operationKey) {
return transformAxes.forEach(function (axesKey) {
return transformProps.push(operationKey + axesKey);
});
});
/**
* A function to use with Array.sort to sort transform keys by their default order.
*/
function sortTransformProps(a, b) {
return transformProps.indexOf(a) - transformProps.indexOf(b);
}
/**
* A quick lookup for transform props.
*/
var transformPropSet = new Set(transformProps);
function isTransformProp(key) {
return transformPropSet.has(key);
}
/**
* A quick lookup for transform origin props
*/
var transformOriginProps = new Set(["originX", "originY", "originZ"]);
function isTransformOriginProp(key) {
return transformOriginProps.has(key);
}
function isForcedMotionValue(key, _a) {
var layout = _a.layout, layoutId = _a.layoutId;
return (isTransformProp(key) ||
isTransformOriginProp(key) ||
((layout || layoutId !== undefined) &&
(!!scaleCorrectors[key] || key === "opacity")));
}
var isMotionValue = function (value) {
return Boolean(value !== null && typeof value === "object" && value.getVelocity);
};
var translateAlias = {
x: "translateX",
y: "translateY",
z: "translateZ",
transformPerspective: "perspective",
};
/**
* Build a CSS transform style from individual x/y/scale etc properties.
*
* This outputs with a default order of transforms/scales/rotations, this can be customised by
* providing a transformTemplate function.
*/
function buildTransform(_a, _b, transformIsDefault, transformTemplate) {
var transform = _a.transform, transformKeys = _a.transformKeys;
var _c = _b.enableHardwareAcceleration, enableHardwareAcceleration = _c === void 0 ? true : _c, _d = _b.allowTransformNone, allowTransformNone = _d === void 0 ? true : _d;
// The transform string we're going to build into.
var transformString = "";
// Transform keys into their default order - this will determine the output order.
transformKeys.sort(sortTransformProps);
// Track whether the defined transform has a defined z so we don't add a
// second to enable hardware acceleration
var transformHasZ = false;
// Loop over each transform and build them into transformString
var numTransformKeys = transformKeys.length;
for (var i = 0; i < numTransformKeys; i++) {
var key = transformKeys[i];
transformString += "".concat(translateAlias[key] || key, "(").concat(transform[key], ") ");
if (key === "z")
transformHasZ = true;
}
if (!transformHasZ && enableHardwareAcceleration) {
transformString += "translateZ(0)";
}
else {
transformString = transformString.trim();
}
// If we have a custom `transform` template, pass our transform values and
// generated transformString to that before returning
if (transformTemplate) {
transformString = transformTemplate(transform, transformIsDefault ? "" : transformString);
}
else if (allowTransformNone && transformIsDefault) {
transformString = "none";
}
return transformString;
}
/**
* Build a transformOrigin style. Uses the same defaults as the browser for
* undefined origins.
*/
function buildTransformOrigin(_a) {
var _b = _a.originX, originX = _b === void 0 ? "50%" : _b, _c = _a.originY, originY = _c === void 0 ? "50%" : _c, _d = _a.originZ, originZ = _d === void 0 ? 0 : _d;
return "".concat(originX, " ").concat(originY, " ").concat(originZ);
}
/**
* Returns true if the provided key is a CSS variable
*/
function isCSSVariable$1(key) {
return key.startsWith("--");
}
/**
* Provided a value and a ValueType, returns the value as that value type.
*/
var getValueAsType = function (value, type) {
return type && typeof value === "number"
? type.transform(value)
: value;
};
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,
};
function buildHTMLStyles(state, latestValues, options, transformTemplate) {
var _a;
var style = state.style, vars = state.vars, transform = state.transform, transformKeys = state.transformKeys, transformOrigin = state.transformOrigin;
// Empty the transformKeys array. As we're throwing out refs to its items
// this might not be as cheap as suspected. Maybe using the array as a buffer
// with a manual incrementation would be better.
transformKeys.length = 0;
// Track whether we encounter any transform or transformOrigin values.
var hasTransform = false;
var hasTransformOrigin = false;
// Does the calculated transform essentially equal "none"?
var transformIsNone = true;
/**
* Loop over all our latest animated values and decide whether to handle them
* as a style or CSS variable.
*
* Transforms and transform origins are kept seperately for further processing.
*/
for (var key in latestValues) {
var value = latestValues[key];
/**
* If this is a CSS variable we don't do any further processing.
*/
if (isCSSVariable$1(key)) {
vars[key] = value;
continue;
}
// Convert the value to its default value type, ie 0 -> "0px"
var valueType = numberValueTypes[key];
var valueAsType = getValueAsType(value, valueType);
if (isTransformProp(key)) {
// If this is a transform, flag to enable further transform processing
hasTransform = true;
transform[key] = valueAsType;
transformKeys.push(key);
// If we already know we have a non-default transform, early return
if (!transformIsNone)
continue;
// Otherwise check to see if this is a default transform
if (value !== ((_a = valueType.default) !== null && _a !== void 0 ? _a : 0))
transformIsNone = false;
}
else if (isTransformOriginProp(key)) {
transformOrigin[key] = valueAsType;
// If this is a transform origin, flag and enable further transform-origin processing
hasTransformOrigin = true;
}
else {
style[key] = valueAsType;
}
}
if (hasTransform) {
style.transform = buildTransform(state, options, transformIsNone, transformTemplate);
}
else if (transformTemplate) {
style.transform = transformTemplate({}, "");
}
else if (!latestValues.transform && style.transform) {
style.transform = "none";
}
if (hasTransformOrigin) {
style.transformOrigin = buildTransformOrigin(transformOrigin);
}
}
var createHtmlRenderState = function () { return ({
style: {},
transform: {},
transformKeys: [],
transformOrigin: {},
vars: {},
}); };
function copyRawValuesOnly(target, source, props) {
for (var key in source) {
if (!isMotionValue(source[key]) && !isForcedMotionValue(key, props)) {
target[key] = source[key];
}
}
}
function useInitialMotionValues(_a, visualState, isStatic) {
var transformTemplate = _a.transformTemplate;
return React.useMemo(function () {
var state = createHtmlRenderState();
buildHTMLStyles(state, visualState, { enableHardwareAcceleration: !isStatic }, transformTemplate);
var vars = state.vars, style = state.style;
return tslib.__assign(tslib.__assign({}, vars), style);
}, [visualState]);
}
function useStyle(props, visualState, isStatic) {
var styleProp = props.style || {};
var style = {};
/**
* Copy non-Motion Values straight into style
*/
copyRawValuesOnly(style, styleProp, props);
Object.assign(style, useInitialMotionValues(props, visualState, isStatic));
if (props.transformValues) {
style = props.transformValues(style);
}
return style;
}
function useHTMLProps(props, visualState, isStatic) {
// The `any` isn't ideal but it is the type of createElement props argument
var htmlProps = {};
var style = useStyle(props, visualState, isStatic);
if (Boolean(props.drag) && props.dragListener !== false) {
// Disable the ghost element when a user drags
htmlProps.draggable = false;
// Disable text selection
style.userSelect =
style.WebkitUserSelect =
style.WebkitTouchCallout =
"none";
// Disable scrolling on the draggable direction
style.touchAction =
props.drag === true
? "none"
: "pan-".concat(props.drag === "x" ? "y" : "x");
}
htmlProps.style = style;
return htmlProps;
}
/**
* A list of all valid MotionProps.
*
* @privateRemarks
* This doesn't throw if a `MotionProp` name is missing - it should.
*/
var validMotionProps = new Set([
"initial",
"animate",
"exit",
"style",
"variants",
"transition",
"transformTemplate",
"transformValues",
"custom",
"inherit",
"layout",
"layoutId",
"layoutDependency",
"onLayoutAnimationStart",
"onLayoutAnimationComplete",
"onLayoutMeasure",
"onBeforeLayoutMeasure",
"onAnimationStart",
"onAnimationComplete",
"onUpdate",
"onDragStart",
"onDrag",
"onDragEnd",
"onMeasureDragConstraints",
"onDirectionLock",
"onDragTransitionEnd",
"drag",
"dragControls",
"dragListener",
"dragConstraints",
"dragDirectionLock",
"dragSnapToOrigin",
"_dragX",
"_dragY",
"dragElastic",
"dragMomentum",
"dragPropagation",
"dragTransition",
"whileDrag",
"onPan",
"onPanStart",
"onPanEnd",
"onPanSessionStart",
"onTap",
"onTapStart",
"onTapCancel",
"onHoverStart",
"onHoverEnd",
"whileFocus",
"whileTap",
"whileHover",
"whileInView",
"onViewportEnter",
"onViewportLeave",
"viewport",
"layoutScroll",
]);
/**
* Check whether a prop name is a valid `MotionProp` key.
*
* @param key - Name of the property to check
* @returns `true` is key is a valid `MotionProp`.
*
* @public
*/
function isValidMotionProp(key) {
return validMotionProps.has(key);
}
var shouldForward = function (key) { return !isValidMotionProp(key); };
function loadExternalIsValidProp(isValidProp) {
if (!isValidProp)
return;
// Explicitly filter our events
shouldForward = function (key) {
return key.startsWith("on") ? !isValidMotionProp(key) : isValidProp(key);
};
}
/**
* Emotion and Styled Components both allow users to pass through arbitrary props to their components
* to dynamically generate CSS. They both use the `@emotion/is-prop-valid` package to determine which
* of these should be passed to the underlying DOM node.
*
* However, when styling a Motion component `styled(motion.div)`, both packages pass through *all* props
* as it's seen as an arbitrary component rather than a DOM node. Motion only allows arbitrary props
* passed through the `custom` prop so it doesn't *need* the payload or computational overhead of
* `@emotion/is-prop-valid`, however to fix this problem we need to use it.
*
* By making it an optionalDependency we can offer this functionality only in the situations where it's
* actually required.
*/
try {
/**
* We attempt to import this package but require won't be defined in esm environments, in that case
* isPropValid will have to be provided via `MotionContext`. In a 6.0.0 this should probably be removed
* in favour of explicit injection.
*/
loadExternalIsValidProp(require("@emotion/is-prop-valid").default);
}
catch (_a) {
// We don't need to actually do anything here - the fallback is the existing `isPropValid`.
}
function filterProps(props, isDom, forwardMotionProps) {
var filteredProps = {};
for (var key in props) {
if (shouldForward(key) ||
(forwardMotionProps === true && isValidMotionProp(key)) ||
(!isDom && !isValidMotionProp(key)) ||
// If trying to use native HTML drag events, forward drag listeners
(props["draggable"] && key.startsWith("onDrag"))) {
filteredProps[key] = props[key];
}
}
return filteredProps;
}
function calcOrigin$1(origin, offset, size) {
return typeof origin === "string"
? origin
: styleValueTypes.px.transform(offset + size * origin);
}
/**
* The SVG transform origin defaults are different to CSS and is less intuitive,
* so we use the measured dimensions of the SVG to reconcile these.
*/
function calcSVGTransformOrigin(dimensions, originX, originY) {
var pxOriginX = calcOrigin$1(originX, dimensions.x, dimensions.width);
var pxOriginY = calcOrigin$1(originY, dimensions.y, dimensions.height);
return "".concat(pxOriginX, " ").concat(pxOriginY);
}
var dashKeys = {
offset: "stroke-dashoffset",
array: "stroke-dasharray",
};
var camelKeys = {
offset: "strokeDashoffset",
array: "strokeDasharray",
};
/**
* Build SVG path properties. Uses the path's measured length to convert
* our custom pathLength, pathSpacing and pathOffset into stroke-dashoffset
* and stroke-dasharray attributes.
*
* This function is mutative to reduce per-frame GC.
*/
function buildSVGPath(attrs, length, spacing, offset, useDashCase) {
if (spacing === void 0) { spacing = 1; }
if (offset === void 0) { offset = 0; }
if (useDashCase === void 0) { useDashCase = true; }
// Normalise path length by setting SVG attribute pathLength to 1
attrs.pathLength = 1;
// We use dash case when setting attributes directly to the DOM node and camel case
// when defining props on a React component.
var keys = useDashCase ? dashKeys : camelKeys;
// Build the dash offset
attrs[keys.offset] = styleValueTypes.px.transform(-offset);
// Build the dash array
var pathLength = styleValueTypes.px.transform(length);
var pathSpacing = styleValueTypes.px.transform(spacing);
attrs[keys.array] = "".concat(pathLength, " ").concat(pathSpacing);
}
/**
* Build SVG visual attrbutes, like cx and style.transform
*/
function buildSVGAttrs(state, _a, options, transformTemplate) {
var attrX = _a.attrX, attrY = _a.attrY, originX = _a.originX, originY = _a.originY, pathLength = _a.pathLength, _b = _a.pathSpacing, pathSpacing = _b === void 0 ? 1 : _b, _c = _a.pathOffset, pathOffset = _c === void 0 ? 0 : _c,
// This is object creation, which we try to avoid per-frame.
latest = tslib.__rest(_a, ["attrX", "attrY", "originX", "originY", "pathLength", "pathSpacing", "pathOffset"]);
buildHTMLStyles(state, latest, options, transformTemplate);
state.attrs = state.style;
state.style = {};
var attrs = state.attrs, style = state.style, dimensions = state.dimensions;
/**
* However, we apply transforms as CSS transforms. So if we detect a transform we take it from attrs
* and copy it into style.
*/
if (attrs.transform) {
if (dimensions)
style.transform = attrs.transform;
delete attrs.transform;
}
// Parse transformOrigin
if (dimensions &&
(originX !== undefined || originY !== undefined || style.transform)) {
style.transformOrigin = calcSVGTransformOrigin(dimensions, originX !== undefined ? originX : 0.5, originY !== undefined ? originY : 0.5);
}
// Treat x/y not as shortcuts but as actual attributes
if (attrX !== undefined)
attrs.x = attrX;
if (attrY !== undefined)
attrs.y = attrY;
// Build SVG path if one has been defined
if (pathLength !== undefined) {
buildSVGPath(attrs, pathLength, pathSpacing, pathOffset, false);
}
}
var createSvgRenderState = function () { return (tslib.__assign(tslib.__assign({}, createHtmlRenderState()), { attrs: {} })); };
function useSVGProps(props, visualState) {
var visualProps = React.useMemo(function () {
var state = createSvgRenderState();
buildSVGAttrs(state, visualState, { enableHardwareAcceleration: false }, props.transformTemplate);
return tslib.__assign(tslib.__assign({}, state.attrs), { style: tslib.__assign({}, state.style) });
}, [visualState]);
if (props.style) {
var rawStyles = {};
copyRawValuesOnly(rawStyles, props.style, props);
visualProps.style = tslib.__assign(tslib.__assign({}, rawStyles), visualProps.style);
}
return visualProps;
}
function createUseRender(forwardMotionProps) {
if (forwardMotionProps === void 0) { forwardMotionProps = false; }
var useRender = function (Component, props, projectionId, ref, _a, isStatic) {
var latestValues = _a.latestValues;
var useVisualProps = isSVGComponent(Component)
? useSVGProps
: useHTMLProps;
var visualProps = useVisualProps(props, latestValues, isStatic);
var filteredProps = filterProps(props, typeof Component === "string", forwardMotionProps);
var elementProps = tslib.__assign(tslib.__assign(tslib.__assign({}, filteredProps), visualProps), { ref: ref });
if (projectionId) {
elementProps["data-projection-id"] = projectionId;
}
return React.createElement(Component, elementProps);
};
return useRender;
}
var CAMEL_CASE_PATTERN = /([a-z])([A-Z])/g;
var REPLACE_TEMPLATE = "$1-$2";
/**
* Convert camelCase to dash-case properties.
*/
var camelToDash = function (str) {
return str.replace(CAMEL_CASE_PATTERN, REPLACE_TEMPLATE).toLowerCase();
};
function renderHTML(element, _a, styleProp, projection) {
var style = _a.style, vars = _a.vars;
Object.assign(element.style, style, projection && projection.getProjectionStyles(styleProp));
// Loop over any CSS variables and assign those.
for (var key in vars) {
element.style.setProperty(key, vars[key]);
}
}
/**
* A set of attribute names that are always read/written as camel case.
*/
var camelCaseAttributes = new Set([
"baseFrequency",
"diffuseConstant",
"kernelMatrix",
"kernelUnitLength",
"keySplines",
"keyTimes",
"limitingConeAngle",
"markerHeight",
"markerWidth",
"numOctaves",
"targetX",
"targetY",
"surfaceScale",
"specularConstant",
"specularExponent",
"stdDeviation",
"tableValues",
"viewBox",
"gradientTransform",
"pathLength",
]);
function renderSVG(element, renderState, _styleProp, projection) {
renderHTML(element, renderState, undefined, projection);
for (var key in renderState.attrs) {
element.setAttribute(!camelCaseAttributes.has(key) ? camelToDash(key) : key, renderState.attrs[key]);
}
}
function scrapeMotionValuesFromProps$1(props) {
var style = props.style;
var newValues = {};
for (var key in style) {
if (isMotionValue(style[key]) || isForcedMotionValue(key, props)) {
newValues[key] = style[key];
}
}
return newValues;
}
function scrapeMotionValuesFromProps(props) {
var newValues = scrapeMotionValuesFromProps$1(props);
for (var key in props) {
if (isMotionValue(props[key])) {
var targetKey = key === "x" || key === "y" ? "attr" + key.toUpperCase() : key;
newValues[targetKey] = props[key];
}
}
return newValues;
}
function isAnimationControls(v) {
return typeof v === "object" && typeof v.start === "function";
}
var isKeyframesTarget = function (v) {
return Array.isArray(v);
};
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;
};
/**
* If the provided value is a MotionValue, this returns the actual value, otherwise just the value itself
*
* TODO: Remove and move to library
*/
function resolveMotionValue(value) {
var unwrappedValue = isMotionValue(value) ? value.get() : value;
return isCustomValue(unwrappedValue)
? unwrappedValue.toValue()
: unwrappedValue;
}
function makeState(_a, props, context, presenceContext) {
var scrapeMotionValuesFromProps = _a.scrapeMotionValuesFromProps, createRenderState = _a.createRenderState, onMount = _a.onMount;
var state = {
latestValues: makeLatestValues(props, context, presenceContext, scrapeMotionValuesFromProps),
renderState: createRenderState(),
};
if (onMount) {
state.mount = function (instance) { return onMount(props, instance, state); };
}
return state;
}
var makeUseVisualState = function (config) {
return function (props, isStatic) {
var context = React.useContext(MotionContext);
var presenceContext = React.useContext(PresenceContext);
return isStatic
? makeState(config, props, context, presenceContext)
: useConstant(function () {
return makeState(config, props, context, presenceContext);
});
};
};
function makeLatestValues(props, context, presenceContext, scrapeMotionValues) {
var values = {};
var blockInitialAnimation = (presenceContext === null || presenceContext === void 0 ? void 0 : presenceContext.initial) === false;
var motionValues = scrapeMotionValues(props);
for (var key in motionValues) {
values[key] = resolveMotionValue(motionValues[key]);
}
var initial = props.initial, animate = props.animate;
var isControllingVariants = checkIfControllingVariants(props);
var isVariantNode = checkIfVariantNode(props);
if (context &&
isVariantNode &&
!isControllingVariants &&
props.inherit !== false) {
initial !== null && initial !== void 0 ? initial : (initial = context.initial);
animate !== null && animate !== void 0 ? animate : (animate = context.animate);
}
var initialAnimationIsBlocked = blockInitialAnimation || initial === false;
var variantToSet = initialAnimationIsBlocked ? animate : initial;
if (variantToSet &&
typeof variantToSet !== "boolean" &&
!isAnimationControls(variantToSet)) {
var list = Array.isArray(variantToSet) ? variantToSet : [variantToSet];
list.forEach(function (definition) {
var resolved = resolveVariantFromProps(props, definition);
if (!resolved)
return;
var transitionEnd = resolved.transitionEnd; resolved.transition; var target = tslib.__rest(resolved, ["transitionEnd", "transition"]);
for (var key in target) {
var valueTarget = target[key];
if (Array.isArray(valueTarget)) {
/**
* Take final keyframe if the initial animation is blocked because
* we want to initialise at the end of that blocked animation.
*/
var index = initialAnimationIsBlocked
? valueTarget.length - 1
: 0;
valueTarget = valueTarget[index];
}
if (valueTarget !== null) {
values[key] = valueTarget;
}
}
for (var key in transitionEnd)
values[key] = transitionEnd[key];
});
}
return values;
}
var svgMotionConfig = {
useVisualState: makeUseVisualState({
scrapeMotionValuesFromProps: scrapeMotionValuesFromProps,
createRenderState: createSvgRenderState,
onMount: function (props, instance, _a) {
var renderState = _a.renderState, latestValues = _a.latestValues;
try {
renderState.dimensions =
typeof instance.getBBox ===
"function"
? instance.getBBox()
: instance.getBoundingClientRect();
}
catch (e) {
// Most likely trying to measure an unrendered element under Firefox
renderState.dimensions = {
x: 0,
y: 0,
width: 0,
height: 0,
};
}
buildSVGAttrs(renderState, latestValues, { enableHardwareAcceleration: false }, props.transformTemplate);
renderSVG(instance, renderState);
},
}),
};
var htmlMotionConfig = {
useVisualState: makeUseVisualState({
scrapeMotionValuesFromProps: scrapeMotionValuesFromProps$1,
createRenderState: createHtmlRenderState,
}),
};
function createDomMotionConfig(Component, _a, preloadedFeatures, createVisualElement, projectionNodeConstructor) {
var _b = _a.forwardMotionProps, forwardMotionProps = _b === void 0 ? false : _b;
var baseConfig = isSVGComponent(Component)
? svgMotionConfig
: htmlMotionConfig;
return tslib.__assign(tslib.__assign({}, baseConfig), { preloadedFeatures: preloadedFeatures, useRender: createUseRender(forwardMotionProps), createVisualElement: createVisualElement, projectionNodeConstructor: projectionNodeConstructor, Component: Component });
}
exports.AnimationType = void 0;
(function (AnimationType) {
AnimationType["Animate"] = "animate";
AnimationType["Hover"] = "whileHover";
AnimationType["Tap"] = "whileTap";
AnimationType["Drag"] = "whileDrag";
AnimationType["Focus"] = "whileFocus";
AnimationType["InView"] = "whileInView";
AnimationType["Exit"] = "exit";
})(exports.AnimationType || (exports.AnimationType = {}));
function addDomEvent(target, eventName, handler, options) {
if (options === void 0) { options = { passive: true }; }
target.addEventListener(eventName, handler, options);
return function () { return target.removeEventListener(eventName, handler); };
}
/**
* Attaches an event listener directly to the provided DOM element.
*
* Bypassing React's event system can be desirable, for instance when attaching non-passive
* event handlers.
*
* ```jsx
* const ref = useRef(null)
*
* useDomEvent(ref, 'wheel', onWheel, { passive: false })
*
* return <div ref={ref} />
* ```
*
* @param ref - React.RefObject that's been provided to the element you want to bind the listener to.
* @param eventName - Name of the event you want listen for.
* @param handler - Function to fire when receiving the event.
* @param options - Options to pass to `Event.addEventListener`.
*
* @public
*/
function useDomEvent(ref, eventName, handler, options) {
React.useEffect(function () {
var element = ref.current;
if (handler && element) {
return addDomEvent(element, eventName, handler, options);
}
}, [ref, eventName, handler, options]);
}
/**
*
* @param props
* @param ref
* @internal
*/
function useFocusGesture(_a) {
var whileFocus = _a.whileFocus, visualElement = _a.visualElement;
var onFocus = function () {
var _a;
(_a = visualElement.animationState) === null || _a === void 0 ? void 0 : _a.setActive(exports.AnimationType.Focus, true);
};
var onBlur = function () {
var _a;
(_a = visualElement.animationState) === null || _a === void 0 ? void 0 : _a.setActive(exports.AnimationType.Focus, false);
};
useDomEvent(visualElement, "focus", whileFocus ? onFocus : undefined);
useDomEvent(visualElement, "blur", whileFocus ? onBlur : undefined);
}
function isMouseEvent(event) {
// PointerEvent inherits from MouseEvent so we can't use a straight instanceof check.
if (typeof PointerEvent !== "undefined" && event instanceof PointerEvent) {
return !!(event.pointerType === "mouse");
}
return event instanceof MouseEvent;
}
function isTouchEvent(event) {
var hasTouches = !!event.touches;
return hasTouches;
}
/**
* Filters out events not attached to the primary pointer (currently left mouse button)
* @param eventHandler
*/
function filterPrimaryPointer(eventHandler) {
return function (event) {
var isMouseEvent = event instanceof MouseEvent;
var isPrimaryPointer = !isMouseEvent ||
(isMouseEvent && event.button === 0);
if (isPrimaryPointer) {
eventHandler(event);
}
};
}
var defaultPagePoint = { pageX: 0, pageY: 0 };
function pointFromTouch(e, pointType) {
if (pointType === void 0) { pointType = "page"; }
var primaryTouch = e.touches[0] || e.changedTouches[0];
var point = primaryTouch || defaultPagePoint;
return {
x: point[pointType + "X"],
y: point[pointType + "Y"],
};
}
function pointFromMouse(point, pointType) {
if (pointType === void 0) { pointType = "page"; }
return {
x: point[pointType + "X"],
y: point[pointType + "Y"],
};
}
function extractEventInfo(event, pointType) {
if (pointType === void 0) { pointType = "page"; }
return {
point: isTouchEvent(event)
? pointFromTouch(event, pointType)
: pointFromMouse(event, pointType),
};
}
var wrapHandler = function (handler, shouldFilterPrimaryPointer) {
if (shouldFilterPrimaryPointer === void 0) { shouldFilterPrimaryPointer = false; }
var listener = function (event) {
return handler(event, extractEventInfo(event));
};
return shouldFilterPrimaryPointer
? filterPrimaryPointer(listener)
: listener;
};
// We check for event support via functions in case they've been mocked by a testing suite.
var supportsPointerEvents = function () {
return isBrowser && window.onpointerdown === null;
};
var supportsTouchEvents = function () {
return isBrowser && window.ontouchstart === null;
};
var supportsMouseEvents = function () {
return isBrowser && window.onmousedown === null;
};
var mouseEventNames = {
pointerdown: "mousedown",
pointermove: "mousemove",
pointerup: "mouseup",
pointercancel: "mousecancel",
pointerover: "mouseover",
pointerout: "mouseout",
pointerenter: "mouseenter",
pointerleave: "mouseleave",
};
var touchEventNames = {
pointerdown: "touchstart",
pointermove: "touchmove",
pointerup: "touchend",
pointercancel: "touchcancel",
};
function getPointerEventName(name) {
if (supportsPointerEvents()) {
return name;
}
else if (supportsTouchEvents()) {
return touchEventNames[name];
}
else if (supportsMouseEvents()) {
return mouseEventNames[name];
}
return name;
}
function addPointerEvent(target, eventName, handler, options) {
return addDomEvent(target, getPointerEventName(eventName), wrapHandler(handler, eventName === "pointerdown"), options);
}
function usePointerEvent(ref, eventName, handler, options) {
return useDomEvent(ref, getPointerEventName(eventName), handler && wrapHandler(handler, eventName === "pointerdown"), options);
}
function createLock(name) {
var lock = null;
return function () {
var openLock = function () {
lock = null;
};
if (lock === null) {
lock = name;
return openLock;
}
return false;
};
}
var globalHorizontalLock = createLock("dragHorizontal");
var globalVerticalLock = createLock("dragVertical");
function getGlobalLock(drag) {
var lock = false;
if (drag === "y") {
lock = globalVerticalLock();
}
else if (drag === "x") {
lock = globalHorizontalLock();
}
else {
var openHorizontal_1 = globalHorizontalLock();
var openVertical_1 = globalVerticalLock();
if (openHorizontal_1 && openVertical_1) {
lock = function () {
openHorizontal_1();
openVertical_1();
};
}
else {
// Release the locks because we don't use them
if (openHorizontal_1)
openHorizontal_1();
if (openVertical_1)
openVertical_1();
}
}
return lock;
}
function isDragActive() {
// Check the gesture lock - if we get it, it means no drag gesture is active
// and we can safely fire the tap gesture.
var openGestureLock = getGlobalLock(true);
if (!openGestureLock)
return true;
openGestureLock();
return false;
}
function createHoverEvent(visualElement, isActive, callback) {
return function (event, info) {
var _a;
if (!isMouseEvent(event) || isDragActive())
return;
/**
* Ensure we trigger animations before firing event callback
*/
(_a = visualElement.animationState) === null || _a === void 0 ? void 0 : _a.setActive(exports.AnimationType.Hover, isActive);
callback === null || callback === void 0 ? void 0 : callback(event, info);
};
}
function useHoverGesture(_a) {
var onHoverStart = _a.onHoverStart, onHoverEnd = _a.onHoverEnd, whileHover = _a.whileHover, visualElement = _a.visualElement;
usePointerEvent(visualElement, "pointerenter", onHoverStart || whileHover
? createHoverEvent(visualElement, true, onHoverStart)
: undefined, { passive: !onHoverStart });
usePointerEvent(visualElement, "pointerleave", onHoverEnd || whileHover
? createHoverEvent(visualElement, false, onHoverEnd)
: undefined, { passive: !onHoverEnd });
}
/**
* Recursively traverse up the tree to check whether the provided child node
* is the parent or a descendant of it.
*
* @param parent - Element to find
* @param child - Element to test against parent
*/
var isNodeOrChild = function (parent, child) {
if (!child) {
return false;
}
else if (parent === child) {
return true;
}
else {
return isNodeOrChild(parent, child.parentElement);
}
};
function useUnmountEffect(callback) {
return React.useEffect(function () { return function () { return callback(); }; }, []);
}
/**
* @param handlers -
* @internal
*/
function useTapGesture(_a) {
var onTap = _a.onTap, onTapStart = _a.onTapStart, onTapCancel = _a.onTapCancel, whileTap = _a.whileTap, visualElement = _a.visualElement;
var hasPressListeners = onTap || onTapStart || onTapCancel || whileTap;
var isPressing = React.useRef(false);
var cancelPointerEndListeners = React.useRef(null);
/**
* Only set listener to passive if there are no external listeners.
*/
var eventOptions = {
passive: !(onTapStart || onTap || onTapCancel || onPointerDown),
};
function removePointerEndListener() {
var _a;
(_a = cancelPointerEndListeners.current) === null || _a === void 0 ? void 0 : _a.call(cancelPointerEndListeners);
cancelPointerEndListeners.current = null;
}
function checkPointerEnd() {
var _a;
removePointerEndListener();
isPressing.current = false;
(_a = visualElement.animationState) === null || _a === void 0 ? void 0 : _a.setActive(exports.AnimationType.Tap, false);
return !isDragActive();
}
function onPointerUp(event, info) {
if (!checkPointerEnd())
return;
/**
* We only count this as a tap gesture if the event.target is the same
* as, or a child of, this component's element
*/
!isNodeOrChild(visualElement.getInstance(), event.target)
? onTapCancel === null || onTapCancel === void 0 ? void 0 : onTapCancel(event, info)
: onTap === null || onTap === void 0 ? void 0 : onTap(event, info);
}
function onPointerCancel(event, info) {
if (!checkPointerEnd())
return;
onTapCancel === null || onTapCancel === void 0 ? void 0 : onTapCancel(event, info);
}
function onPointerDown(event, info) {
var _a;
removePointerEndListener();
if (isPressing.current)
return;
isPressing.current = true;
cancelPointerEndListeners.current = popmotion.pipe(addPointerEvent(window, "pointerup", onPointerUp, eventOptions), addPointerEvent(window, "pointercancel", onPointerCancel, eventOptions));
/**
* Ensure we trigger animations before firing event callback
*/
(_a = visualElement.animationState) === null || _a === void 0 ? void 0 : _a.setActive(exports.AnimationType.Tap, true);
onTapStart === null || onTapStart === void 0 ? void 0 : onTapStart(event, info);
}
usePointerEvent(visualElement, "pointerdown", hasPressListeners ? onPointerDown : undefined, eventOptions);
useUnmountEffect(removePointerEndListener);
}
var warned = new Set();
function warnOnce(condition, message, element) {
if (condition || warned.has(message))
return;
console.warn(message);
if (element)
console.warn(element);
warned.add(message);
}
/**
* Map an IntersectionHandler callback to an element. We only ever make one handler for one
* element, so even though these handlers might all be triggered by different
* observers, we can keep them in the same map.
*/
var observerCallbacks = new WeakMap();
/**
* Multiple observers can be created for multiple element/document roots. Each with
* different settings. So here we store dictionaries of observers to each root,
* using serialised settings (threshold/margin) as lookup keys.
*/
var observers = new WeakMap();
var fireObserverCallback = function (entry) {
var _a;
(_a = observerCallbacks.get(entry.target)) === null || _a === void 0 ? void 0 : _a(entry);
};
var fireAllObserverCallbacks = function (entries) {
entries.forEach(fireObserverCallback);
};
function initIntersectionObserver(_a) {
var root = _a.root, options = tslib.__rest(_a, ["root"]);
var lookupRoot = root || document;
/**
* If we don't have an observer lookup map for this root, create one.
*/
if (!observers.has(lookupRoot)) {
observers.set(lookupRoot, {});
}
var rootObservers = observers.get(lookupRoot);
var key = JSON.stringify(options);
/**
* If we don't have an observer for this combination of root and settings,
* create one.
*/
if (!rootObservers[key]) {
rootObservers[key] = new IntersectionObserver(fireAllObserverCallbacks, tslib.__assign({ root: root }, options));
}
return rootObservers[key];
}
function observeIntersection(element, options, callback) {
var rootInteresectionObserver = initIntersectionObserver(options);
observerCallbacks.set(element, callback);
rootInteresectionObserver.observe(element);
return function () {
observerCallbacks.delete(element);
rootInteresectionObserver.unobserve(element);
};
}
function useViewport(_a) {
var visualElement = _a.visualElement, whileInView = _a.whileInView, onViewportEnter = _a.onViewportEnter, onViewportLeave = _a.onViewportLeave, _b = _a.viewport, viewport = _b === void 0 ? {} : _b;
var state = React.useRef({
hasEnteredView: false,
isInView: false,
});
var shouldObserve = Boolean(whileInView || onViewportEnter || onViewportLeave);
if (viewport.once && state.current.hasEnteredView)
shouldObserve = false;
var useObserver = typeof IntersectionObserver === "undefined"
? useMissingIntersectionObserver
: useIntersectionObserver;
useObserver(shouldObserve, state.current, visualElement, viewport);
}
var thresholdNames = {
some: 0,
all: 1,
};
function useIntersectionObserver(shouldObserve, state, visualElement, _a) {
var root = _a.root, rootMargin = _a.margin, _b = _a.amount, amount = _b === void 0 ? "some" : _b, once = _a.once;
React.useEffect(function () {
if (!shouldObserve)
return;
var options = {
root: root === null || root === void 0 ? void 0 : root.current,
rootMargin: rootMargin,
threshold: typeof amount === "number" ? amount : thresholdNames[amount],
};
var intersectionCallback = function (entry) {
var _a;
var isIntersecting = entry.isIntersecting;
/**
* If there's been no change in the viewport state, early return.
*/
if (state.isInView === isIntersecting)
return;
state.isInView = isIntersecting;
/**
* Handle hasEnteredView. If this is only meant to run once, and
* element isn't visible, early return. Otherwise set hasEnteredView to true.
*/
if (once && !isIntersecting && state.hasEnteredView) {
return;
}
else if (isIntersecting) {
state.hasEnteredView = true;
}
(_a = visualElement.animationState) === null || _a === void 0 ? void 0 : _a.setActive(exports.AnimationType.InView, isIntersecting);
/**
* Use the latest committed props rather than the ones in scope
* when this observer is created
*/
var props = visualElement.getProps();
var callback = isIntersecting
? props.onViewportEnter
: props.onViewportLeave;
callback === null || callback === void 0 ? void 0 : callback(entry);
};
return observeIntersection(visualElement.getInstance(), options, intersectionCallback);
}, [shouldObserve, root, rootMargin, amount]);
}
/**
* If IntersectionObserver is missing, we activate inView and fire onViewportEnter
* on mount. This way, the page will be in the state the author expects users
* to see it in for everyone.
*/
function useMissingIntersectionObserver(shouldObserve, state, visualElement, _a) {
var _b = _a.fallback, fallback = _b === void 0 ? true : _b;
React.useEffect(function () {
if (!shouldObserve || !fallback)
return;
if (env !== "production") {
warnOnce(false, "IntersectionObserver not available on this device. whileInView animations will trigger on mount.");
}
/**
* Fire this in an rAF because, at this point, the animation state
* won't have flushed for the first time and there's certain logic in
* there that behaves differently on the initial animation.
*
* This hook should be quite rarely called so setting this in an rAF
* is preferred to changing the behaviour of the animation state.
*/
requestAnimationFrame(function () {
var _a;
state.hasEnteredView = true;
var onViewportEnter = visualElement.getProps().onViewportEnter;
onViewportEnter === null || onViewportEnter === void 0 ? void 0 : onViewportEnter(null);
(_a = visualElement.animationState) === null || _a === void 0 ? void 0 : _a.setActive(exports.AnimationType.InView, true);
});
}, [shouldObserve]);
}
var makeRenderlessComponent = function (hook) { return function (props) {
hook(props);
return null;
}; };
var gestureAnimations = {
inView: makeRenderlessComponent(useViewport),
tap: makeRenderlessComponent(useTapGesture),
focus: makeRenderlessComponent(useFocusGesture),
hover: makeRenderlessComponent(useHoverGesture),
};
var counter = 0;
var incrementId = function () { return counter++; };
var useId = function () { return useConstant(incrementId); };
/**
* Ideally we'd use the following code to support React 18 optionally.
* But this fairly fails in Webpack (otherwise treeshaking wouldn't work at all).
* Need to come up with a different way of figuring this out.
*/
// export const useId = (React as any).useId
// ? (React as any).useId
// : () => useConstant(incrementId)
/**
* When a component is the child of `AnimatePresence`, it can use `usePresence`
* to access information about whether it's still present in the React tree.
*
* ```jsx
* import { usePresence } from "framer-motion"
*
* export const Component = () => {
* const [isPresent, safeToRemove] = usePresence()
*
* useEffect(() => {
* !isPresent && setTimeout(safeToRemove, 1000)
* }, [isPresent])
*
* return <div />
* }
* ```
*
* If `isPresent` is `false`, it means that a component has been removed the tree, but
* `AnimatePresence` won't really remove it until `safeToRemove` has been called.
*
* @public
*/
function usePresence() {
var context = React.useContext(PresenceContext);
if (context === null)
return [true, null];
var isPresent = context.isPresent, onExitComplete = context.onExitComplete, register = context.register;
// It's safe to call the following hooks conditionally (after an early return) because the context will always
// either be null or non-null for the lifespan of the component.
// Replace with useId when released in React
var id = useId();
React.useEffect(function () { return register(id); }, []);
var safeToRemove = function () { return onExitComplete === null || onExitComplete === void 0 ? void 0 : onExitComplete(id); };
return !isPresent && onExitComplete ? [false, safeToRemove] : [true];
}
/**
* Similar to `usePresence`, except `useIsPresent` simply returns whether or not the component is present.
* There is no `safeToRemove` function.
*
* ```jsx
* import { useIsPresent } from "framer-motion"
*
* export const Component = () => {
* const isPresent = useIsPresent()
*
* useEffect(() => {
* !isPresent && console.log("I've been removed!")
* }, [isPresent])
*
* return <div />
* }
* ```
*
* @public
*/
function useIsPresent() {
return isPresent(React.useContext(PresenceContext));
}
function isPresent(context) {
return context === null ? true : context.isPresent;
}
function shallowCompare(next, prev) {
if (!Array.isArray(prev))
return false;
var prevLength = prev.length;
if (prevLength !== next.length)
return false;
for (var i = 0; i < prevLength; i++) {
if (prev[i] !== next[i])
return false;
}
return true;
}
/**
* 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 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));
};
/**
* 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,
};
/**
* 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
*/
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();
};
});
}
/**
* Check if value is a numerical string, ie a string that is purely a number eg "100" or "-100.1"
*/
var isNumericalString = function (v) { return /^\-?\d*\.?\d+$/.test(v); };
/**
* Check if the value is a zero value string like "0px" or "0%"
*/
var isZeroValueString = function (v) { return /^0[^.\s]+$/.test(v); };
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;
/**
* This will be replaced by the build step with the latest version number.
* When MotionValues are provided to motion components, warn if versions are mixed.
*/
this.version = "6.5.1";
/**
* 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 }} />
* }
* ```
*
* @privateRemarks
*
* 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;
}());
function motionValue(init) {
return new MotionValue(init);
}
/**
* Tests a provided value against a ValueType
*/
var testValueType = function (v) { return function (type) { return type.test(v); }; };
/**
* ValueType for "auto"
*/
var auto = {
test: function (v) { return v === "auto"; },
parse: function (v) { return v; },
};
/**
* A list of value types commonly used for dimensions
*/
var dimensionValueTypes = [styleValueTypes.number, styleValueTypes.px, styleValueTypes.percent, styleValueTypes.degrees, styleValueTypes.vw, styleValueTypes.vh, auto];
/**
* Tests a dimensional value against the list of dimension ValueTypes
*/
var findDimensionValueType = function (v) {
return dimensionValueTypes.find(testValueType(v));
};
/**
* A list of all ValueTypes
*/
var valueTypes = tslib.__spreadArray(tslib.__spreadArray([], tslib.__read(dimensionValueTypes), false), [styleValueTypes.color, styleValueTypes.complex], false);
/**
* Tests a value against the list of ValueTypes
*/
var findValueType = function (v) { return valueTypes.find(testValueType(v)); };
/**
* Set VisualElement's MotionValue, creating a new MotionValue for it if
* it doesn't exist.
*/
function setMotionValue(visualElement, key, value) {
if (visualElement.hasValue(key)) {
visualElement.getValue(key).set(value);
}
else {
visualElement.addValue(key, motionValue(value));
}
}
function setTarget(visualElement, definition) {
var resolved = resolveVariant(visualElement, definition);
var _a = resolved ? visualElement.makeTargetAnimatable(resolved, false) : {}, _b = _a.transitionEnd, transitionEnd = _b === void 0 ? {} : _b; _a.transition; var target = tslib.__rest(_a, ["transitionEnd", "transition"]);
target = tslib.__assign(tslib.__assign({}, target), transitionEnd);
for (var key in target) {
var value = resolveFinalValueInKeyframes(target[key]);
setMotionValue(visualElement, key, value);
}
}
function setVariants(visualElement, variantLabels) {
var reversedLabels = tslib.__spreadArray([], tslib.__read(variantLabels), false).reverse();
reversedLabels.forEach(function (key) {
var _a;
var variant = visualElement.getVariant(key);
variant && setTarget(visualElement, variant);
(_a = visualElement.variantChildren) === null || _a === void 0 ? void 0 : _a.forEach(function (child) {
setVariants(child, variantLabels);
});
});
}
function setValues(visualElement, definition) {
if (Array.isArray(definition)) {
return setVariants(visualElement, definition);
}
else if (typeof definition === "string") {
return setVariants(visualElement, [definition]);
}
else {
setTarget(visualElement, definition);
}
}
function checkTargetForNewValues(visualElement, target, origin) {
var _a, _b, _c;
var _d;
var newValueKeys = Object.keys(target).filter(function (key) { return !visualElement.hasValue(key); });
var numNewValues = newValueKeys.length;
if (!numNewValues)
return;
for (var i = 0; i < numNewValues; i++) {
var key = newValueKeys[i];
var targetValue = target[key];
var value = null;
/**
* If the target is a series of keyframes, we can use the first value
* in the array. If this first value is null, we'll still need to read from the DOM.
*/
if (Array.isArray(targetValue)) {
value = targetValue[0];
}
/**
* If the target isn't keyframes, or the first keyframe was null, we need to
* first check if an origin value was explicitly defined in the transition as "from",
* if not read the value from the DOM. As an absolute fallback, take the defined target value.
*/
if (value === null) {
value = (_b = (_a = origin[key]) !== null && _a !== void 0 ? _a : visualElement.readValue(key)) !== null && _b !== void 0 ? _b : target[key];
}
/**
* If value is still undefined or null, ignore it. Preferably this would throw,
* but this was causing issues in Framer.
*/
if (value === undefined || value === null)
continue;
if (typeof value === "string" &&
(isNumericalString(value) || isZeroValueString(value))) {
// If this is a number read as a string, ie "0" or "200", convert it to a number
value = parseFloat(value);
}
else if (!findValueType(value) && styleValueTypes.complex.test(targetValue)) {
value = getAnimatableNone(key, targetValue);
}
visualElement.addValue(key, motionValue(value));
(_c = (_d = origin)[key]) !== null && _c !== void 0 ? _c : (_d[key] = value);
visualElement.setBaseTarget(key, value);
}
}
function getOriginFromTransition(key, transition) {
if (!transition)
return;
var valueTransition = transition[key] || transition["default"] || transition;
return valueTransition.from;
}
function getOrigin(target, transition, visualElement) {
var _a, _b;
var origin = {};
for (var key in target) {
origin[key] =
(_a = getOriginFromTransition(key, transition)) !== null && _a !== void 0 ? _a : (_b = visualElement.getValue(key)) === null || _b === void 0 ? void 0 : _b.get();
}
return origin;
}
function animateVisualElement(visualElement, definition, options) {
if (options === void 0) { options = {}; }
visualElement.notifyAnimationStart(definition);
var animation;
if (Array.isArray(definition)) {
var animations = definition.map(function (variant) {
return animateVariant(visualElement, variant, options);
});
animation = Promise.all(animations);
}
else if (typeof definition === "string") {
animation = animateVariant(visualElement, definition, options);
}
else {
var resolvedDefinition = typeof definition === "function"
? resolveVariant(visualElement, definition, options.custom)
: definition;
animation = animateTarget(visualElement, resolvedDefinition, options);
}
return animation.then(function () {
return visualElement.notifyAnimationComplete(definition);
});
}
function animateVariant(visualElement, variant, options) {
var _a;
if (options === void 0) { options = {}; }
var resolved = resolveVariant(visualElement, variant, options.custom);
var _b = (resolved || {}).transition, transition = _b === void 0 ? visualElement.getDefaultTransition() || {} : _b;
if (options.transitionOverride) {
transition = options.transitionOverride;
}
/**
* If we have a variant, create a callback that runs it as an animation.
* Otherwise, we resolve a Promise immediately for a composable no-op.
*/
var getAnimation = resolved
? function () { return animateTarget(visualElement, resolved, options); }
: function () { return Promise.resolve(); };
/**
* If we have children, create a callback that runs all their animations.
* Otherwise, we resolve a Promise immediately for a composable no-op.
*/
var getChildAnimations = ((_a = visualElement.variantChildren) === null || _a === void 0 ? void 0 : _a.size)
? function (forwardDelay) {
if (forwardDelay === void 0) { forwardDelay = 0; }
var _a = transition.delayChildren, delayChildren = _a === void 0 ? 0 : _a, staggerChildren = transition.staggerChildren, staggerDirection = transition.staggerDirection;
return animateChildren(visualElement, variant, delayChildren + forwardDelay, staggerChildren, staggerDirection, options);
}
: function () { return Promise.resolve(); };
/**
* If the transition explicitly defines a "when" option, we need to resolve either
* this animation or all children animations before playing the other.
*/
var when = transition.when;
if (when) {
var _c = tslib.__read(when === "beforeChildren"
? [getAnimation, getChildAnimations]
: [getChildAnimations, getAnimation], 2), first = _c[0], last = _c[1];
return first().then(last);
}
else {
return Promise.all([getAnimation(), getChildAnimations(options.delay)]);
}
}
/**
* @internal
*/
function animateTarget(visualElement, definition, _a) {
var _b;
var _c = _a === void 0 ? {} : _a, _d = _c.delay, delay = _d === void 0 ? 0 : _d, transitionOverride = _c.transitionOverride, type = _c.type;
var _e = visualElement.makeTargetAnimatable(definition), _f = _e.transition, transition = _f === void 0 ? visualElement.getDefaultTransition() : _f, transitionEnd = _e.transitionEnd, target = tslib.__rest(_e, ["transition", "transitionEnd"]);
if (transitionOverride)
transition = transitionOverride;
var animations = [];
var animationTypeState = type && ((_b = visualElement.animationState) === null || _b === void 0 ? void 0 : _b.getState()[type]);
for (var key in target) {
var value = visualElement.getValue(key);
var valueTarget = target[key];
if (!value ||
valueTarget === undefined ||
(animationTypeState &&
shouldBlockAnimation(animationTypeState, key))) {
continue;
}
var valueTransition = tslib.__assign({ delay: delay }, transition);
/**
* Make animation instant if this is a transform prop and we should reduce motion.
*/
if (visualElement.shouldReduceMotion && isTransformProp(key)) {
valueTransition = tslib.__assign(tslib.__assign({}, valueTransition), { type: false, delay: 0 });
}
var animation = startAnimation(key, value, valueTarget, valueTransition);
animations.push(animation);
}
return Promise.all(animations).then(function () {
transitionEnd && setTarget(visualElement, transitionEnd);
});
}
function animateChildren(visualElement, variant, delayChildren, staggerChildren, staggerDirection, options) {
if (delayChildren === void 0) { delayChildren = 0; }
if (staggerChildren === void 0) { staggerChildren = 0; }
if (staggerDirection === void 0) { staggerDirection = 1; }
var animations = [];
var maxStaggerDuration = (visualElement.variantChildren.size - 1) * staggerChildren;
var generateStaggerDuration = staggerDirection === 1
? function (i) {
if (i === void 0) { i = 0; }
return i * staggerChildren;
}
: function (i) {
if (i === void 0) { i = 0; }
return maxStaggerDuration - i * staggerChildren;
};
Array.from(visualElement.variantChildren)
.sort(sortByTreeOrder)
.forEach(function (child, i) {
animations.push(animateVariant(child, variant, tslib.__assign(tslib.__assign({}, options), { delay: delayChildren + generateStaggerDuration(i) })).then(function () { return child.notifyAnimationComplete(variant); }));
});
return Promise.all(animations);
}
function stopAnimation(visualElement) {
visualElement.forEachValue(function (value) { return value.stop(); });
}
function sortByTreeOrder(a, b) {
return a.sortNodePosition(b);
}
/**
* Decide whether we should block this animation. Previously, we achieved this
* just by checking whether the key was listed in protectedKeys, but this
* posed problems if an animation was triggered by afterChildren and protectedKeys
* had been set to true in the meantime.
*/
function shouldBlockAnimation(_a, key) {
var protectedKeys = _a.protectedKeys, needsAnimating = _a.needsAnimating;
var shouldBlock = protectedKeys.hasOwnProperty(key) && needsAnimating[key] !== true;
needsAnimating[key] = false;
return shouldBlock;
}
var variantPriorityOrder = [
exports.AnimationType.Animate,
exports.AnimationType.InView,
exports.AnimationType.Focus,
exports.AnimationType.Hover,
exports.AnimationType.Tap,
exports.AnimationType.Drag,
exports.AnimationType.Exit,
];
var reversePriorityOrder = tslib.__spreadArray([], tslib.__read(variantPriorityOrder), false).reverse();
var numAnimationTypes = variantPriorityOrder.length;
function animateList(visualElement) {
return function (animations) {
return Promise.all(animations.map(function (_a) {
var animation = _a.animation, options = _a.options;
return animateVisualElement(visualElement, animation, options);
}));
};
}
function createAnimationState(visualElement) {
var animate = animateList(visualElement);
var state = createState();
var allAnimatedKeys = {};
var isInitialRender = true;
/**
* This function will be used to reduce the animation definitions for
* each active animation type into an object of resolved values for it.
*/
var buildResolvedTypeValues = function (acc, definition) {
var resolved = resolveVariant(visualElement, definition);
if (resolved) {
resolved.transition; var transitionEnd = resolved.transitionEnd, target = tslib.__rest(resolved, ["transition", "transitionEnd"]);
acc = tslib.__assign(tslib.__assign(tslib.__assign({}, acc), target), transitionEnd);
}
return acc;
};
function isAnimated(key) {
return allAnimatedKeys[key] !== undefined;
}
/**
* This just allows us to inject mocked animation functions
* @internal
*/
function setAnimateFunction(makeAnimator) {
animate = makeAnimator(visualElement);
}
/**
* When we receive new props, we need to:
* 1. Create a list of protected keys for each type. This is a directory of
* value keys that are currently being "handled" by types of a higher priority
* so that whenever an animation is played of a given type, these values are
* protected from being animated.
* 2. Determine if an animation type needs animating.
* 3. Determine if any values have been removed from a type and figure out
* what to animate those to.
*/
function animateChanges(options, changedActiveType) {
var _a;
var props = visualElement.getProps();
var context = visualElement.getVariantContext(true) || {};
/**
* A list of animations that we'll build into as we iterate through the animation
* types. This will get executed at the end of the function.
*/
var animations = [];
/**
* Keep track of which values have been removed. Then, as we hit lower priority
* animation types, we can check if they contain removed values and animate to that.
*/
var removedKeys = new Set();
/**
* A dictionary of all encountered keys. This is an object to let us build into and
* copy it without iteration. Each time we hit an animation type we set its protected
* keys - the keys its not allowed to animate - to the latest version of this object.
*/
var encounteredKeys = {};
/**
* If a variant has been removed at a given index, and this component is controlling
* variant animations, we want to ensure lower-priority variants are forced to animate.
*/
var removedVariantIndex = Infinity;
var _loop_1 = function (i) {
var type = reversePriorityOrder[i];
var typeState = state[type];
var prop = (_a = props[type]) !== null && _a !== void 0 ? _a : context[type];
var propIsVariant = isVariantLabel(prop);
/**
* If this type has *just* changed isActive status, set activeDelta
* to that status. Otherwise set to null.
*/
var activeDelta = type === changedActiveType ? typeState.isActive : null;
if (activeDelta === false)
removedVariantIndex = i;
/**
* If this prop is an inherited variant, rather than been set directly on the
* component itself, we want to make sure we allow the parent to trigger animations.
*
* TODO: Can probably change this to a !isControllingVariants check
*/
var isInherited = prop === context[type] && prop !== props[type] && propIsVariant;
/**
*
*/
if (isInherited &&
isInitialRender &&
visualElement.manuallyAnimateOnMount) {
isInherited = false;
}
/**
* Set all encountered keys so far as the protected keys for this type. This will
* be any key that has been animated or otherwise handled by active, higher-priortiy types.
*/
typeState.protectedKeys = tslib.__assign({}, encounteredKeys);
// Check if we can skip analysing this prop early
if (
// If it isn't active and hasn't *just* been set as inactive
(!typeState.isActive && activeDelta === null) ||
// If we didn't and don't have any defined prop for this animation type
(!prop && !typeState.prevProp) ||
// Or if the prop doesn't define an animation
isAnimationControls(prop) ||
typeof prop === "boolean") {
return "continue";
}
/**
* As we go look through the values defined on this type, if we detect
* a changed value or a value that was removed in a higher priority, we set
* this to true and add this prop to the animation list.
*/
var variantDidChange = checkVariantsDidChange(typeState.prevProp, prop);
var shouldAnimateType = variantDidChange ||
// If we're making this variant active, we want to always make it active
(type === changedActiveType &&
typeState.isActive &&
!isInherited &&
propIsVariant) ||
// If we removed a higher-priority variant (i is in reverse order)
(i > removedVariantIndex && propIsVariant);
/**
* As animations can be set as variant lists, variants or target objects, we
* coerce everything to an array if it isn't one already
*/
var definitionList = Array.isArray(prop) ? prop : [prop];
/**
* Build an object of all the resolved values. We'll use this in the subsequent
* animateChanges calls to determine whether a value has changed.
*/
var resolvedValues = definitionList.reduce(buildResolvedTypeValues, {});
if (activeDelta === false)
resolvedValues = {};
/**
* Now we need to loop through all the keys in the prev prop and this prop,
* and decide:
* 1. If the value has changed, and needs animating
* 2. If it has been removed, and needs adding to the removedKeys set
* 3. If it has been removed in a higher priority type and needs animating
* 4. If it hasn't been removed in a higher priority but hasn't changed, and
* needs adding to the type's protectedKeys list.
*/
var _b = typeState.prevResolvedValues, prevResolvedValues = _b === void 0 ? {} : _b;
var allKeys = tslib.__assign(tslib.__assign({}, prevResolvedValues), resolvedValues);
var markToAnimate = function (key) {
shouldAnimateType = true;
removedKeys.delete(key);
typeState.needsAnimating[key] = true;
};
for (var key in allKeys) {
var next = resolvedValues[key];
var prev = prevResolvedValues[key];
// If we've already handled this we can just skip ahead
if (encounteredKeys.hasOwnProperty(key))
continue;
/**
* If the value has changed, we probably want to animate it.
*/
if (next !== prev) {
/**
* If both values are keyframes, we need to shallow compare them to
* detect whether any value has changed. If it has, we animate it.
*/
if (isKeyframesTarget(next) && isKeyframesTarget(prev)) {
if (!shallowCompare(next, prev) || variantDidChange) {
markToAnimate(key);
}
else {
/**
* If it hasn't changed, we want to ensure it doesn't animate by
* adding it to the list of protected keys.
*/
typeState.protectedKeys[key] = true;
}
}
else if (next !== undefined) {
// If next is defined and doesn't equal prev, it needs animating
markToAnimate(key);
}
else {
// If it's undefined, it's been removed.
removedKeys.add(key);
}
}
else if (next !== undefined && removedKeys.has(key)) {
/**
* If next hasn't changed and it isn't undefined, we want to check if it's
* been removed by a higher priority
*/
markToAnimate(key);
}
else {
/**
* If it hasn't changed, we add it to the list of protected values
* to ensure it doesn't get animated.
*/
typeState.protectedKeys[key] = true;
}
}
/**
* Update the typeState so next time animateChanges is called we can compare the
* latest prop and resolvedValues to these.
*/
typeState.prevProp = prop;
typeState.prevResolvedValues = resolvedValues;
/**
*
*/
if (typeState.isActive) {
encounteredKeys = tslib.__assign(tslib.__assign({}, encounteredKeys), resolvedValues);
}
if (isInitialRender && visualElement.blockInitialAnimation) {
shouldAnimateType = false;
}
/**
* If this is an inherited prop we want to hard-block animations
* TODO: Test as this should probably still handle animations triggered
* by removed values?
*/
if (shouldAnimateType && !isInherited) {
animations.push.apply(animations, tslib.__spreadArray([], tslib.__read(definitionList.map(function (animation) { return ({
animation: animation,
options: tslib.__assign({ type: type }, options),
}); })), false));
}
};
/**
* Iterate through all animation types in reverse priority order. For each, we want to
* detect which values it's handling and whether or not they've changed (and therefore
* need to be animated). If any values have been removed, we want to detect those in
* lower priority props and flag for animation.
*/
for (var i = 0; i < numAnimationTypes; i++) {
_loop_1(i);
}
allAnimatedKeys = tslib.__assign({}, encounteredKeys);
/**
* If there are some removed value that haven't been dealt with,
* we need to create a new animation that falls back either to the value
* defined in the style prop, or the last read value.
*/
if (removedKeys.size) {
var fallbackAnimation_1 = {};
removedKeys.forEach(function (key) {
var fallbackTarget = visualElement.getBaseTarget(key);
if (fallbackTarget !== undefined) {
fallbackAnimation_1[key] = fallbackTarget;
}
});
animations.push({ animation: fallbackAnimation_1 });
}
var shouldAnimate = Boolean(animations.length);
if (isInitialRender &&
props.initial === false &&
!visualElement.manuallyAnimateOnMount) {
shouldAnimate = false;
}
isInitialRender = false;
return shouldAnimate ? animate(animations) : Promise.resolve();
}
/**
* Change whether a certain animation type is active.
*/
function setActive(type, isActive, options) {
var _a;
// If the active state hasn't changed, we can safely do nothing here
if (state[type].isActive === isActive)
return Promise.resolve();
// Propagate active change to children
(_a = visualElement.variantChildren) === null || _a === void 0 ? void 0 : _a.forEach(function (child) { var _a; return (_a = child.animationState) === null || _a === void 0 ? void 0 : _a.setActive(type, isActive); });
state[type].isActive = isActive;
var animations = animateChanges(options, type);
for (var key in state) {
state[key].protectedKeys = {};
}
return animations;
}
return {
isAnimated: isAnimated,
animateChanges: animateChanges,
setActive: setActive,
setAnimateFunction: setAnimateFunction,
getState: function () { return state; },
};
}
function checkVariantsDidChange(prev, next) {
if (typeof next === "string") {
return next !== prev;
}
else if (isVariantLabels(next)) {
return !shallowCompare(next, prev);
}
return false;
}
function createTypeState(isActive) {
if (isActive === void 0) { isActive = false; }
return {
isActive: isActive,
protectedKeys: {},
needsAnimating: {},
prevResolvedValues: {},
};
}
function createState() {
var _a;
return _a = {},
_a[exports.AnimationType.Animate] = createTypeState(true),
_a[exports.AnimationType.InView] = createTypeState(),
_a[exports.AnimationType.Hover] = createTypeState(),
_a[exports.AnimationType.Tap] = createTypeState(),
_a[exports.AnimationType.Drag] = createTypeState(),
_a[exports.AnimationType.Focus] = createTypeState(),
_a[exports.AnimationType.Exit] = createTypeState(),
_a;
}
var animations = {
animation: makeRenderlessComponent(function (_a) {
var visualElement = _a.visualElement, animate = _a.animate;
/**
* We dynamically generate the AnimationState manager as it contains a reference
* to the underlying animation library. We only want to load that if we load this,
* so people can optionally code split it out using the `m` component.
*/
visualElement.animationState || (visualElement.animationState = createAnimationState(visualElement));
/**
* Subscribe any provided AnimationControls to the component's VisualElement
*/
if (isAnimationControls(animate)) {
React.useEffect(function () { return animate.subscribe(visualElement); }, [animate]);
}
}),
exit: makeRenderlessComponent(function (props) {
var custom = props.custom, visualElement = props.visualElement;
var _a = tslib.__read(usePresence(), 2), isPresent = _a[0], safeToRemove = _a[1];
var presenceContext = React.useContext(PresenceContext);
React.useEffect(function () {
var _a, _b;
visualElement.isPresent = isPresent;
var animation = (_a = visualElement.animationState) === null || _a === void 0 ? void 0 : _a.setActive(exports.AnimationType.Exit, !isPresent, { custom: (_b = presenceContext === null || presenceContext === void 0 ? void 0 : presenceContext.custom) !== null && _b !== void 0 ? _b : custom });
!isPresent && (animation === null || animation === void 0 ? void 0 : animation.then(safeToRemove));
}, [isPresent]);
}),
};
/**
* @internal
*/
var PanSession = /** @class */ (function () {
function PanSession(event, handlers, _a) {
var _this = this;
var _b = _a === void 0 ? {} : _a, transformPagePoint = _b.transformPagePoint;
/**
* @internal
*/
this.startEvent = null;
/**
* @internal
*/
this.lastMoveEvent = null;
/**
* @internal
*/
this.lastMoveEventInfo = null;
/**
* @internal
*/
this.handlers = {};
this.updatePoint = function () {
if (!(_this.lastMoveEvent && _this.lastMoveEventInfo))
return;
var info = getPanInfo(_this.lastMoveEventInfo, _this.history);
var isPanStarted = _this.startEvent !== null;
// Only start panning if the offset is larger than 3 pixels. If we make it
// any larger than this we'll want to reset the pointer history
// on the first update to avoid visual snapping to the cursoe.
var isDistancePastThreshold = popmotion.distance(info.offset, { x: 0, y: 0 }) >= 3;
if (!isPanStarted && !isDistancePastThreshold)
return;
var point = info.point;
var timestamp = sync.getFrameData().timestamp;
_this.history.push(tslib.__assign(tslib.__assign({}, point), { timestamp: timestamp }));
var _a = _this.handlers, onStart = _a.onStart, onMove = _a.onMove;
if (!isPanStarted) {
onStart && onStart(_this.lastMoveEvent, info);
_this.startEvent = _this.lastMoveEvent;
}
onMove && onMove(_this.lastMoveEvent, info);
};
this.handlePointerMove = function (event, info) {
_this.lastMoveEvent = event;
_this.lastMoveEventInfo = transformPoint(info, _this.transformPagePoint);
// Because Safari doesn't trigger mouseup events when it's above a `<select>`
if (isMouseEvent(event) && event.buttons === 0) {
_this.handlePointerUp(event, info);
return;
}
// Throttle mouse move event to once per frame
sync__default["default"].update(_this.updatePoint, true);
};
this.handlePointerUp = function (event, info) {
_this.end();
var _a = _this.handlers, onEnd = _a.onEnd, onSessionEnd = _a.onSessionEnd;
var panInfo = getPanInfo(transformPoint(info, _this.transformPagePoint), _this.history);
if (_this.startEvent && onEnd) {
onEnd(event, panInfo);
}
onSessionEnd && onSessionEnd(event, panInfo);
};
// If we have more than one touch, don't start detecting this gesture
if (isTouchEvent(event) && event.touches.length > 1)
return;
this.handlers = handlers;
this.transformPagePoint = transformPagePoint;
var info = extractEventInfo(event);
var initialInfo = transformPoint(info, this.transformPagePoint);
var point = initialInfo.point;
var timestamp = sync.getFrameData().timestamp;
this.history = [tslib.__assign(tslib.__assign({}, point), { timestamp: timestamp })];
var onSessionStart = handlers.onSessionStart;
onSessionStart &&
onSessionStart(event, getPanInfo(initialInfo, this.history));
this.removeListeners = popmotion.pipe(addPointerEvent(window, "pointermove", this.handlePointerMove), addPointerEvent(window, "pointerup", this.handlePointerUp), addPointerEvent(window, "pointercancel", this.handlePointerUp));
}
PanSession.prototype.updateHandlers = function (handlers) {
this.handlers = handlers;
};
PanSession.prototype.end = function () {
this.removeListeners && this.removeListeners();
sync.cancelSync.update(this.updatePoint);
};
return PanSession;
}());
function transformPoint(info, transformPagePoint) {
return transformPagePoint ? { point: transformPagePoint(info.point) } : info;
}
function subtractPoint(a, b) {
return { x: a.x - b.x, y: a.y - b.y };
}
function getPanInfo(_a, history) {
var point = _a.point;
return {
point: point,
delta: subtractPoint(point, lastDevicePoint(history)),
offset: subtractPoint(point, startDevicePoint(history)),
velocity: getVelocity(history, 0.1),
};
}
function startDevicePoint(history) {
return history[0];
}
function lastDevicePoint(history) {
return history[history.length - 1];
}
function getVelocity(history, timeDelta) {
if (history.length < 2) {
return { x: 0, y: 0 };
}
var i = history.length - 1;
var timestampedPoint = null;
var lastPoint = lastDevicePoint(history);
while (i >= 0) {
timestampedPoint = history[i];
if (lastPoint.timestamp - timestampedPoint.timestamp >
secondsToMilliseconds(timeDelta)) {
break;
}
i--;
}
if (!timestampedPoint) {
return { x: 0, y: 0 };
}
var time = (lastPoint.timestamp - timestampedPoint.timestamp) / 1000;
if (time === 0) {
return { x: 0, y: 0 };
}
var currentVelocity = {
x: (lastPoint.x - timestampedPoint.x) / time,
y: (lastPoint.y - timestampedPoint.y) / time,
};
if (currentVelocity.x === Infinity) {
currentVelocity.x = 0;
}
if (currentVelocity.y === Infinity) {
currentVelocity.y = 0;
}
return currentVelocity;
}
function calcLength(axis) {
return axis.max - axis.min;
}
function isNear(value, target, maxDistance) {
if (target === void 0) { target = 0; }
if (maxDistance === void 0) { maxDistance = 0.01; }
return popmotion.distance(value, target) < maxDistance;
}
function calcAxisDelta(delta, source, target, origin) {
if (origin === void 0) { origin = 0.5; }
delta.origin = origin;
delta.originPoint = popmotion.mix(source.min, source.max, delta.origin);
delta.scale = calcLength(target) / calcLength(source);
if (isNear(delta.scale, 1, 0.0001) || isNaN(delta.scale))
delta.scale = 1;
delta.translate =
popmotion.mix(target.min, target.max, delta.origin) - delta.originPoint;
if (isNear(delta.translate) || isNaN(delta.translate))
delta.translate = 0;
}
function calcBoxDelta(delta, source, target, origin) {
calcAxisDelta(delta.x, source.x, target.x, origin === null || origin === void 0 ? void 0 : origin.originX);
calcAxisDelta(delta.y, source.y, target.y, origin === null || origin === void 0 ? void 0 : origin.originY);
}
function calcRelativeAxis(target, relative, parent) {
target.min = parent.min + relative.min;
target.max = target.min + calcLength(relative);
}
function calcRelativeBox(target, relative, parent) {
calcRelativeAxis(target.x, relative.x, parent.x);
calcRelativeAxis(target.y, relative.y, parent.y);
}
function calcRelativeAxisPosition(target, layout, parent) {
target.min = layout.min - parent.min;
target.max = target.min + calcLength(layout);
}
function calcRelativePosition(target, layout, parent) {
calcRelativeAxisPosition(target.x, layout.x, parent.x);
calcRelativeAxisPosition(target.y, layout.y, parent.y);
}
/**
* Apply constraints to a point. These constraints are both physical along an
* axis, and an elastic factor that determines how much to constrain the point
* by if it does lie outside the defined parameters.
*/
function applyConstraints(point, _a, elastic) {
var min = _a.min, max = _a.max;
if (min !== undefined && point < min) {
// If we have a min point defined, and this is outside of that, constrain
point = elastic ? popmotion.mix(min, point, elastic.min) : Math.max(point, min);
}
else if (max !== undefined && point > max) {
// If we have a max point defined, and this is outside of that, constrain
point = elastic ? popmotion.mix(max, point, elastic.max) : Math.min(point, max);
}
return point;
}
/**
* Calculate constraints in terms of the viewport when defined relatively to the
* measured axis. This is measured from the nearest edge, so a max constraint of 200
* on an axis with a max value of 300 would return a constraint of 500 - axis length
*/
function calcRelativeAxisConstraints(axis, min, max) {
return {
min: min !== undefined ? axis.min + min : undefined,
max: max !== undefined
? axis.max + max - (axis.max - axis.min)
: undefined,
};
}
/**
* Calculate constraints in terms of the viewport when
* defined relatively to the measured bounding box.
*/
function calcRelativeConstraints(layoutBox, _a) {
var top = _a.top, left = _a.left, bottom = _a.bottom, right = _a.right;
return {
x: calcRelativeAxisConstraints(layoutBox.x, left, right),
y: calcRelativeAxisConstraints(layoutBox.y, top, bottom),
};
}
/**
* Calculate viewport constraints when defined as another viewport-relative axis
*/
function calcViewportAxisConstraints(layoutAxis, constraintsAxis) {
var _a;
var min = constraintsAxis.min - layoutAxis.min;
var max = constraintsAxis.max - layoutAxis.max;
// If the constraints axis is actually smaller than the layout axis then we can
// flip the constraints
if (constraintsAxis.max - constraintsAxis.min <
layoutAxis.max - layoutAxis.min) {
_a = tslib.__read([max, min], 2), min = _a[0], max = _a[1];
}
return { min: min, max: max };
}
/**
* Calculate viewport constraints when defined as another viewport-relative box
*/
function calcViewportConstraints(layoutBox, constraintsBox) {
return {
x: calcViewportAxisConstraints(layoutBox.x, constraintsBox.x),
y: calcViewportAxisConstraints(layoutBox.y, constraintsBox.y),
};
}
/**
* Calculate a transform origin relative to the source axis, between 0-1, that results
* in an asthetically pleasing scale/transform needed to project from source to target.
*/
function calcOrigin(source, target) {
var origin = 0.5;
var sourceLength = calcLength(source);
var targetLength = calcLength(target);
if (targetLength > sourceLength) {
origin = popmotion.progress(target.min, target.max - sourceLength, source.min);
}
else if (sourceLength > targetLength) {
origin = popmotion.progress(source.min, source.max - targetLength, target.min);
}
return popmotion.clamp(0, 1, origin);
}
/**
* Rebase the calculated viewport constraints relative to the layout.min point.
*/
function rebaseAxisConstraints(layout, constraints) {
var relativeConstraints = {};
if (constraints.min !== undefined) {
relativeConstraints.min = constraints.min - layout.min;
}
if (constraints.max !== undefined) {
relativeConstraints.max = constraints.max - layout.min;
}
return relativeConstraints;
}
var defaultElastic = 0.35;
/**
* Accepts a dragElastic prop and returns resolved elastic values for each axis.
*/
function resolveDragElastic(dragElastic) {
if (dragElastic === void 0) { dragElastic = defaultElastic; }
if (dragElastic === false) {
dragElastic = 0;
}
else if (dragElastic === true) {
dragElastic = defaultElastic;
}
return {
x: resolveAxisElastic(dragElastic, "left", "right"),
y: resolveAxisElastic(dragElastic, "top", "bottom"),
};
}
function resolveAxisElastic(dragElastic, minLabel, maxLabel) {
return {
min: resolvePointElastic(dragElastic, minLabel),
max: resolvePointElastic(dragElastic, maxLabel),
};
}
function resolvePointElastic(dragElastic, label) {
var _a;
return typeof dragElastic === "number"
? dragElastic
: (_a = dragElastic[label]) !== null && _a !== void 0 ? _a : 0;
}
var createAxisDelta = function () { return ({
translate: 0,
scale: 1,
origin: 0,
originPoint: 0,
}); };
var createDelta = function () { return ({
x: createAxisDelta(),
y: createAxisDelta(),
}); };
var createAxis = function () { return ({ min: 0, max: 0 }); };
var createBox = function () { return ({
x: createAxis(),
y: createAxis(),
}); };
function eachAxis(callback) {
return [callback("x"), callback("y")];
}
/**
* Bounding boxes tend to be defined as top, left, right, bottom. For various operations
* it's easier to consider each axis individually. This function returns a bounding box
* as a map of single-axis min/max values.
*/
function convertBoundingBoxToBox(_a) {
var top = _a.top, left = _a.left, right = _a.right, bottom = _a.bottom;
return {
x: { min: left, max: right },
y: { min: top, max: bottom },
};
}
function convertBoxToBoundingBox(_a) {
var x = _a.x, y = _a.y;
return { top: y.min, right: x.max, bottom: y.max, left: x.min };
}
/**
* Applies a TransformPoint function to a bounding box. TransformPoint is usually a function
* provided by Framer to allow measured points to be corrected for device scaling. This is used
* when measuring DOM elements and DOM event points.
*/
function transformBoxPoints(point, transformPoint) {
if (!transformPoint)
return point;
var topLeft = transformPoint({ x: point.left, y: point.top });
var bottomRight = transformPoint({ x: point.right, y: point.bottom });
return {
top: topLeft.y,
left: topLeft.x,
bottom: bottomRight.y,
right: bottomRight.x,
};
}
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) {
// Incoporate each ancestor's scale into a culmulative treeScale for this component
treeScale.x *= delta.x.scale;
treeScale.y *= delta.y.scale;
// Apply each ancestor's calculated delta into this component's recorded layout box
applyBoxDelta(box, delta);
}
if (isSharedTransition && hasTransform(node.latestValues)) {
transformBox(box, node.latestValues);
}
}
}
function translateAxis(axis, distance) {
axis.min = axis.min + distance;
axis.max = axis.max + distance;
}
/**
* Apply a transform to an axis from the latest resolved motion values.
* This function basically acts as a bridge between a flat motion value map
* and applyAxisDelta
*/
function transformAxis(axis, transforms, _a) {
var _b = tslib.__read(_a, 3), key = _b[0], scaleKey = _b[1], originKey = _b[2];
var axisOrigin = transforms[originKey] !== undefined ? transforms[originKey] : 0.5;
var originPoint = popmotion.mix(axis.min, axis.max, axisOrigin);
// Apply the axis delta to the final axis
applyAxisDelta(axis, transforms[key], transforms[scaleKey], originPoint, transforms.scale);
}
/**
* The names of the motion values we want to apply as translation, scale and origin.
*/
var xKeys$1 = ["x", "scaleX", "originX"];
var yKeys$1 = ["y", "scaleY", "originY"];
/**
* Apply a transform to a box from the latest resolved motion values.
*/
function transformBox(box, transform) {
transformAxis(box.x, transform, xKeys$1);
transformAxis(box.y, transform, yKeys$1);
}
function measureViewportBox(instance, transformPoint) {
return convertBoundingBoxToBox(transformBoxPoints(instance.getBoundingClientRect(), transformPoint));
}
function measurePageBox(element, rootProjectionNode, transformPagePoint) {
var viewportBox = measureViewportBox(element, transformPagePoint);
var scroll = rootProjectionNode.scroll;
if (scroll) {
translateAxis(viewportBox.x, scroll.x);
translateAxis(viewportBox.y, scroll.y);
}
return viewportBox;
}
var elementDragControls = new WeakMap();
/**
*
*/
// let latestPointerEvent: AnyPointerEvent
var VisualElementDragControls = /** @class */ (function () {
function VisualElementDragControls(visualElement) {
// This is a reference to the global drag gesture lock, ensuring only one component
// can "capture" the drag of one or both axes.
// TODO: Look into moving this into pansession?
this.openGlobalLock = null;
this.isDragging = false;
this.currentDirection = null;
this.originPoint = { x: 0, y: 0 };
/**
* The permitted boundaries of travel, in pixels.
*/
this.constraints = false;
this.hasMutatedConstraints = false;
/**
* The per-axis resolved elastic values.
*/
this.elastic = createBox();
this.visualElement = visualElement;
}
VisualElementDragControls.prototype.start = function (originEvent, _a) {
var _this = this;
var _b = _a === void 0 ? {} : _a, _c = _b.snapToCursor, snapToCursor = _c === void 0 ? false : _c;
/**
* Don't start dragging if this component is exiting
*/
if (this.visualElement.isPresent === false)
return;
var onSessionStart = function (event) {
// Stop any animations on both axis values immediately. This allows the user to throw and catch
// the component.
_this.stopAnimation();
if (snapToCursor) {
_this.snapToCursor(extractEventInfo(event, "page").point);
}
};
var onStart = function (event, info) {
var _a;
// Attempt to grab the global drag gesture lock - maybe make this part of PanSession
var _b = _this.getProps(), drag = _b.drag, dragPropagation = _b.dragPropagation, onDragStart = _b.onDragStart;
if (drag && !dragPropagation) {
if (_this.openGlobalLock)
_this.openGlobalLock();
_this.openGlobalLock = getGlobalLock(drag);
// If we don 't have the lock, don't start dragging
if (!_this.openGlobalLock)
return;
}
_this.isDragging = true;
_this.currentDirection = null;
_this.resolveConstraints();
if (_this.visualElement.projection) {
_this.visualElement.projection.isAnimationBlocked = true;
_this.visualElement.projection.target = undefined;
}
/**
* Record gesture origin
*/
eachAxis(function (axis) {
var _a, _b;
var current = _this.getAxisMotionValue(axis).get() || 0;
/**
* If the MotionValue is a percentage value convert to px
*/
if (styleValueTypes.percent.test(current)) {
var measuredAxis = (_b = (_a = _this.visualElement.projection) === null || _a === void 0 ? void 0 : _a.layout) === null || _b === void 0 ? void 0 : _b.actual[axis];
if (measuredAxis) {
var length_1 = calcLength(measuredAxis);
current = length_1 * (parseFloat(current) / 100);
}
}
_this.originPoint[axis] = current;
});
// Fire onDragStart event
onDragStart === null || onDragStart === void 0 ? void 0 : onDragStart(event, info);
(_a = _this.visualElement.animationState) === null || _a === void 0 ? void 0 : _a.setActive(exports.AnimationType.Drag, true);
};
var onMove = function (event, info) {
// latestPointerEvent = event
var _a = _this.getProps(), dragPropagation = _a.dragPropagation, dragDirectionLock = _a.dragDirectionLock, onDirectionLock = _a.onDirectionLock, onDrag = _a.onDrag;
// If we didn't successfully receive the gesture lock, early return.
if (!dragPropagation && !_this.openGlobalLock)
return;
var offset = info.offset;
// Attempt to detect drag direction if directionLock is true
if (dragDirectionLock && _this.currentDirection === null) {
_this.currentDirection = getCurrentDirection(offset);
// If we've successfully set a direction, notify listener
if (_this.currentDirection !== null) {
onDirectionLock === null || onDirectionLock === void 0 ? void 0 : onDirectionLock(_this.currentDirection);
}
return;
}
// Update each point with the latest position
_this.updateAxis("x", info.point, offset);
_this.updateAxis("y", info.point, offset);
/**
* Ideally we would leave the renderer to fire naturally at the end of
* this frame but if the element is about to change layout as the result
* of a re-render we want to ensure the browser can read the latest
* bounding box to ensure the pointer and element don't fall out of sync.
*/
_this.visualElement.syncRender();
/**
* This must fire after the syncRender call as it might trigger a state
* change which itself might trigger a layout update.
*/
onDrag === null || onDrag === void 0 ? void 0 : onDrag(event, info);
};
var onSessionEnd = function (event, info) {
return _this.stop(event, info);
};
this.panSession = new PanSession(originEvent, {
onSessionStart: onSessionStart,
onStart: onStart,
onMove: onMove,
onSessionEnd: onSessionEnd,
}, { transformPagePoint: this.visualElement.getTransformPagePoint() });
};
VisualElementDragControls.prototype.stop = function (event, info) {
var isDragging = this.isDragging;
this.cancel();
if (!isDragging)
return;
var velocity = info.velocity;
this.startAnimation(velocity);
var onDragEnd = this.getProps().onDragEnd;
onDragEnd === null || onDragEnd === void 0 ? void 0 : onDragEnd(event, info);
};
VisualElementDragControls.prototype.cancel = function () {
var _a, _b;
this.isDragging = false;
if (this.visualElement.projection) {
this.visualElement.projection.isAnimationBlocked = false;
}
(_a = this.panSession) === null || _a === void 0 ? void 0 : _a.end();
this.panSession = undefined;
var dragPropagation = this.getProps().dragPropagation;
if (!dragPropagation && this.openGlobalLock) {
this.openGlobalLock();
this.openGlobalLock = null;
}
(_b = this.visualElement.animationState) === null || _b === void 0 ? void 0 : _b.setActive(exports.AnimationType.Drag, false);
};
VisualElementDragControls.prototype.updateAxis = function (axis, _point, offset) {
var drag = this.getProps().drag;
// If we're not dragging this axis, do an early return.
if (!offset || !shouldDrag(axis, drag, this.currentDirection))
return;
var axisValue = this.getAxisMotionValue(axis);
var next = this.originPoint[axis] + offset[axis];
// Apply constraints
if (this.constraints && this.constraints[axis]) {
next = applyConstraints(next, this.constraints[axis], this.elastic[axis]);
}
axisValue.set(next);
};
VisualElementDragControls.prototype.resolveConstraints = function () {
var _this = this;
var _a = this.getProps(), dragConstraints = _a.dragConstraints, dragElastic = _a.dragElastic;
var layout = (this.visualElement.projection || {}).layout;
var prevConstraints = this.constraints;
if (dragConstraints && isRefObject(dragConstraints)) {
if (!this.constraints) {
this.constraints = this.resolveRefConstraints();
}
}
else {
if (dragConstraints && layout) {
this.constraints = calcRelativeConstraints(layout.actual, dragConstraints);
}
else {
this.constraints = false;
}
}
this.elastic = resolveDragElastic(dragElastic);
/**
* If we're outputting to external MotionValues, we want to rebase the measured constraints
* from viewport-relative to component-relative.
*/
if (prevConstraints !== this.constraints &&
layout &&
this.constraints &&
!this.hasMutatedConstraints) {
eachAxis(function (axis) {
if (_this.getAxisMotionValue(axis)) {
_this.constraints[axis] = rebaseAxisConstraints(layout.actual[axis], _this.constraints[axis]);
}
});
}
};
VisualElementDragControls.prototype.resolveRefConstraints = function () {
var _a = this.getProps(), constraints = _a.dragConstraints, onMeasureDragConstraints = _a.onMeasureDragConstraints;
if (!constraints || !isRefObject(constraints))
return false;
var constraintsElement = constraints.current;
heyListen.invariant(constraintsElement !== null, "If `dragConstraints` is set as a React ref, that ref must be passed to another component's `ref` prop.");
var projection = this.visualElement.projection;
// TODO
if (!projection || !projection.layout)
return false;
var constraintsBox = measurePageBox(constraintsElement, projection.root, this.visualElement.getTransformPagePoint());
var measuredConstraints = calcViewportConstraints(projection.layout.actual, constraintsBox);
/**
* If there's an onMeasureDragConstraints listener we call it and
* if different constraints are returned, set constraints to that
*/
if (onMeasureDragConstraints) {
var userConstraints = onMeasureDragConstraints(convertBoxToBoundingBox(measuredConstraints));
this.hasMutatedConstraints = !!userConstraints;
if (userConstraints) {
measuredConstraints = convertBoundingBoxToBox(userConstraints);
}
}
return measuredConstraints;
};
VisualElementDragControls.prototype.startAnimation = function (velocity) {
var _this = this;
var _a = this.getProps(), drag = _a.drag, dragMomentum = _a.dragMomentum, dragElastic = _a.dragElastic, dragTransition = _a.dragTransition, dragSnapToOrigin = _a.dragSnapToOrigin, onDragTransitionEnd = _a.onDragTransitionEnd;
var constraints = this.constraints || {};
var momentumAnimations = eachAxis(function (axis) {
var _a;
if (!shouldDrag(axis, drag, _this.currentDirection)) {
return;
}
var transition = (_a = constraints === null || constraints === void 0 ? void 0 : constraints[axis]) !== null && _a !== void 0 ? _a : {};
if (dragSnapToOrigin)
transition = { min: 0, max: 0 };
/**
* Overdamp the boundary spring if `dragElastic` is disabled. There's still a frame
* of spring animations so we should look into adding a disable spring option to `inertia`.
* We could do something here where we affect the `bounceStiffness` and `bounceDamping`
* using the value of `dragElastic`.
*/
var bounceStiffness = dragElastic ? 200 : 1000000;
var bounceDamping = dragElastic ? 40 : 10000000;
var inertia = tslib.__assign(tslib.__assign({ type: "inertia", velocity: dragMomentum ? velocity[axis] : 0, bounceStiffness: bounceStiffness, bounceDamping: bounceDamping, timeConstant: 750, restDelta: 1, restSpeed: 10 }, dragTransition), transition);
// If we're not animating on an externally-provided `MotionValue` we can use the
// component's animation controls which will handle interactions with whileHover (etc),
// otherwise we just have to animate the `MotionValue` itself.
return _this.startAxisValueAnimation(axis, inertia);
});
// Run all animations and then resolve the new drag constraints.
return Promise.all(momentumAnimations).then(onDragTransitionEnd);
};
VisualElementDragControls.prototype.startAxisValueAnimation = function (axis, transition) {
var axisValue = this.getAxisMotionValue(axis);
return startAnimation(axis, axisValue, 0, transition);
};
VisualElementDragControls.prototype.stopAnimation = function () {
var _this = this;
eachAxis(function (axis) { return _this.getAxisMotionValue(axis).stop(); });
};
/**
* Drag works differently depending on which props are provided.
*
* - If _dragX and _dragY are provided, we output the gesture delta directly to those motion values.
* - Otherwise, we apply the delta to the x/y motion values.
*/
VisualElementDragControls.prototype.getAxisMotionValue = function (axis) {
var _a, _b;
var dragKey = "_drag" + axis.toUpperCase();
var externalMotionValue = this.visualElement.getProps()[dragKey];
return externalMotionValue
? externalMotionValue
: this.visualElement.getValue(axis, (_b = (_a = this.visualElement.getProps().initial) === null || _a === void 0 ? void 0 : _a[axis]) !== null && _b !== void 0 ? _b : 0);
};
VisualElementDragControls.prototype.snapToCursor = function (point) {
var _this = this;
eachAxis(function (axis) {
var drag = _this.getProps().drag;
// If we're not dragging this axis, do an early return.
if (!shouldDrag(axis, drag, _this.currentDirection))
return;
var projection = _this.visualElement.projection;
var axisValue = _this.getAxisMotionValue(axis);
if (projection && projection.layout) {
var _a = projection.layout.actual[axis], min = _a.min, max = _a.max;
axisValue.set(point[axis] - popmotion.mix(min, max, 0.5));
}
});
};
/**
* When the viewport resizes we want to check if the measured constraints
* have changed and, if so, reposition the element within those new constraints
* relative to where it was before the resize.
*/
VisualElementDragControls.prototype.scalePositionWithinConstraints = function () {
var _this = this;
var _a;
var _b = this.getProps(), drag = _b.drag, dragConstraints = _b.dragConstraints;
var projection = this.visualElement.projection;
if (!isRefObject(dragConstraints) || !projection || !this.constraints)
return;
/**
* Stop current animations as there can be visual glitching if we try to do
* this mid-animation
*/
this.stopAnimation();
/**
* Record the relative position of the dragged element relative to the
* constraints box and save as a progress value.
*/
var boxProgress = { x: 0, y: 0 };
eachAxis(function (axis) {
var axisValue = _this.getAxisMotionValue(axis);
if (axisValue) {
var latest = axisValue.get();
boxProgress[axis] = calcOrigin({ min: latest, max: latest }, _this.constraints[axis]);
}
});
/**
* Update the layout of this element and resolve the latest drag constraints
*/
var transformTemplate = this.visualElement.getProps().transformTemplate;
this.visualElement.getInstance().style.transform = transformTemplate
? transformTemplate({}, "")
: "none";
(_a = projection.root) === null || _a === void 0 ? void 0 : _a.updateScroll();
projection.updateLayout();
this.resolveConstraints();
/**
* For each axis, calculate the current progress of the layout axis
* within the new constraints.
*/
eachAxis(function (axis) {
if (!shouldDrag(axis, drag, null))
return;
/**
* Calculate a new transform based on the previous box progress
*/
var axisValue = _this.getAxisMotionValue(axis);
var _a = _this.constraints[axis], min = _a.min, max = _a.max;
axisValue.set(popmotion.mix(min, max, boxProgress[axis]));
});
};
VisualElementDragControls.prototype.addListeners = function () {
var _this = this;
var _a;
elementDragControls.set(this.visualElement, this);
var element = this.visualElement.getInstance();
/**
* Attach a pointerdown event listener on this DOM element to initiate drag tracking.
*/
var stopPointerListener = addPointerEvent(element, "pointerdown", function (event) {
var _a = _this.getProps(), drag = _a.drag, _b = _a.dragListener, dragListener = _b === void 0 ? true : _b;
drag && dragListener && _this.start(event);
});
var measureDragConstraints = function () {
var dragConstraints = _this.getProps().dragConstraints;
if (isRefObject(dragConstraints)) {
_this.constraints = _this.resolveRefConstraints();
}
};
var projection = this.visualElement.projection;
var stopMeasureLayoutListener = projection.addEventListener("measure", measureDragConstraints);
if (projection && !projection.layout) {
(_a = projection.root) === null || _a === void 0 ? void 0 : _a.updateScroll();
projection.updateLayout();
}
measureDragConstraints();
/**
* Attach a window resize listener to scale the draggable target within its defined
* constraints as the window resizes.
*/
var stopResizeListener = addDomEvent(window, "resize", function () {
return _this.scalePositionWithinConstraints();
});
/**
* If the element's layout changes, calculate the delta and apply that to
* the drag gesture's origin point.
*/
projection.addEventListener("didUpdate", (function (_a) {
var delta = _a.delta, hasLayoutChanged = _a.hasLayoutChanged;
if (_this.isDragging && hasLayoutChanged) {
eachAxis(function (axis) {
var motionValue = _this.getAxisMotionValue(axis);
if (!motionValue)
return;
_this.originPoint[axis] += delta[axis].translate;
motionValue.set(motionValue.get() + delta[axis].translate);
});
_this.visualElement.syncRender();
}
}));
return function () {
stopResizeListener();
stopPointerListener();
stopMeasureLayoutListener();
};
};
VisualElementDragControls.prototype.getProps = function () {
var props = this.visualElement.getProps();
var _a = props.drag, drag = _a === void 0 ? false : _a, _b = props.dragDirectionLock, dragDirectionLock = _b === void 0 ? false : _b, _c = props.dragPropagation, dragPropagation = _c === void 0 ? false : _c, _d = props.dragConstraints, dragConstraints = _d === void 0 ? false : _d, _e = props.dragElastic, dragElastic = _e === void 0 ? defaultElastic : _e, _f = props.dragMomentum, dragMomentum = _f === void 0 ? true : _f;
return tslib.__assign(tslib.__assign({}, props), { drag: drag, dragDirectionLock: dragDirectionLock, dragPropagation: dragPropagation, dragConstraints: dragConstraints, dragElastic: dragElastic, dragMomentum: dragMomentum });
};
return VisualElementDragControls;
}());
function shouldDrag(direction, drag, currentDirection) {
return ((drag === true || drag === direction) &&
(currentDirection === null || currentDirection === direction));
}
/**
* Based on an x/y offset determine the current drag direction. If both axis' offsets are lower
* than the provided threshold, return `null`.
*
* @param offset - The x/y offset from origin.
* @param lockThreshold - (Optional) - the minimum absolute offset before we can determine a drag direction.
*/
function getCurrentDirection(offset, lockThreshold) {
if (lockThreshold === void 0) { lockThreshold = 10; }
var direction = null;
if (Math.abs(offset.y) > lockThreshold) {
direction = "y";
}
else if (Math.abs(offset.x) > lockThreshold) {
direction = "x";
}
return direction;
}
/**
* A hook that allows an element to be dragged.
*
* @internal
*/
function useDrag(props) {
var groupDragControls = props.dragControls, visualElement = props.visualElement;
var dragControls = useConstant(function () { return new VisualElementDragControls(visualElement); });
// If we've been provided a DragControls for manual control over the drag gesture,
// subscribe this component to it on mount.
React.useEffect(function () { return groupDragControls && groupDragControls.subscribe(dragControls); }, [dragControls, groupDragControls]);
// Apply the event listeners to the element
React.useEffect(function () { return dragControls.addListeners(); }, [dragControls]);
}
/**
*
* @param handlers -
* @param ref -
*
* @privateRemarks
* Currently this sets new pan gesture functions every render. The memo route has been explored
* in the past but ultimately we're still creating new functions every render. An optimisation
* to explore is creating the pan gestures and loading them into a `ref`.
*
* @internal
*/
function usePanGesture(_a) {
var onPan = _a.onPan, onPanStart = _a.onPanStart, onPanEnd = _a.onPanEnd, onPanSessionStart = _a.onPanSessionStart, visualElement = _a.visualElement;
var hasPanEvents = onPan || onPanStart || onPanEnd || onPanSessionStart;
var panSession = React.useRef(null);
var transformPagePoint = React.useContext(MotionConfigContext).transformPagePoint;
var handlers = {
onSessionStart: onPanSessionStart,
onStart: onPanStart,
onMove: onPan,
onEnd: function (event, info) {
panSession.current = null;
onPanEnd && onPanEnd(event, info);
},
};
React.useEffect(function () {
if (panSession.current !== null) {
panSession.current.updateHandlers(handlers);
}
});
function onPointerDown(event) {
panSession.current = new PanSession(event, handlers, {
transformPagePoint: transformPagePoint,
});
}
usePointerEvent(visualElement, "pointerdown", hasPanEvents && onPointerDown);
useUnmountEffect(function () { return panSession.current && panSession.current.end(); });
}
var drag = {
pan: makeRenderlessComponent(usePanGesture),
drag: makeRenderlessComponent(useDrag),
};
var names = [
"LayoutMeasure",
"BeforeLayoutMeasure",
"LayoutUpdate",
"ViewportBoxUpdate",
"Update",
"Render",
"AnimationComplete",
"LayoutAnimationComplete",
"AnimationStart",
"LayoutAnimationStart",
"SetAxisTarget",
"Unmount",
];
function createLifecycles() {
var managers = names.map(function () { return new SubscriptionManager(); });
var propSubscriptions = {};
var lifecycles = {
clearAllListeners: function () { return managers.forEach(function (manager) { return manager.clear(); }); },
updatePropListeners: function (props) {
names.forEach(function (name) {
var _a;
var on = "on" + name;
var propListener = props[on];
// Unsubscribe existing subscription
(_a = propSubscriptions[name]) === null || _a === void 0 ? void 0 : _a.call(propSubscriptions);
// Add new subscription
if (propListener) {
propSubscriptions[name] = lifecycles[on](propListener);
}
});
},
};
managers.forEach(function (manager, i) {
lifecycles["on" + names[i]] = function (handler) { return manager.add(handler); };
lifecycles["notify" + names[i]] = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return manager.notify.apply(manager, tslib.__spreadArray([], tslib.__read(args), false));
};
});
return lifecycles;
}
function updateMotionValuesFromProps(element, next, prev) {
var _a;
for (var key in next) {
var nextValue = next[key];
var prevValue = prev[key];
if (isMotionValue(nextValue)) {
/**
* If this is a motion value found in props or style, we want to add it
* to our visual element's motion value map.
*/
element.addValue(key, nextValue);
/**
* Check the version of the incoming motion value with this version
* and warn against mismatches.
*/
if (process.env.NODE_ENV === "development") {
warnOnce(nextValue.version === "6.5.1", "Attempting to mix Framer Motion versions ".concat(nextValue.version, " with 6.5.1 may not work as expected."));
}
}
else if (isMotionValue(prevValue)) {
/**
* If we're swapping to a new motion value, create a new motion value
* from that
*/
element.addValue(key, motionValue(nextValue));
}
else if (prevValue !== nextValue) {
/**
* If this is a flat value that has changed, update the motion value
* or create one if it doesn't exist. We only want to do this if we're
* not handling the value with our animation state.
*/
if (element.hasValue(key)) {
var existingValue = element.getValue(key);
// TODO: Only update values that aren't being animated or even looked at
!existingValue.hasAnimated && existingValue.set(nextValue);
}
else {
element.addValue(key, motionValue((_a = element.getStaticValue(key)) !== null && _a !== void 0 ? _a : nextValue));
}
}
}
// Handle removed values
for (var key in prev) {
if (next[key] === undefined)
element.removeValue(key);
}
return next;
}
var visualElement = function (_a) {
var _b = _a.treeType, treeType = _b === void 0 ? "" : _b, build = _a.build, getBaseTarget = _a.getBaseTarget, makeTargetAnimatable = _a.makeTargetAnimatable, measureViewportBox = _a.measureViewportBox, renderInstance = _a.render, readValueFromInstance = _a.readValueFromInstance, removeValueFromRenderState = _a.removeValueFromRenderState, sortNodePosition = _a.sortNodePosition, scrapeMotionValuesFromProps = _a.scrapeMotionValuesFromProps;
return function (_a, options) {
var parent = _a.parent, props = _a.props, presenceId = _a.presenceId, blockInitialAnimation = _a.blockInitialAnimation, visualState = _a.visualState, shouldReduceMotion = _a.shouldReduceMotion;
if (options === void 0) { options = {}; }
var isMounted = false;
var latestValues = visualState.latestValues, renderState = visualState.renderState;
/**
* The instance of the render-specific node that will be hydrated by the
* exposed React ref. So for example, this visual element can host a
* HTMLElement, plain object, or Three.js object. The functions provided
* in VisualElementConfig allow us to interface with this instance.
*/
var instance;
/**
* Manages the subscriptions for a visual element's lifecycle, for instance
* onRender
*/
var lifecycles = createLifecycles();
/**
* A map of all motion values attached to this visual element. Motion
* values are source of truth for any given animated value. A motion
* value might be provided externally by the component via props.
*/
var values = new Map();
/**
* A map of every subscription that binds the provided or generated
* motion values onChange listeners to this visual element.
*/
var valueSubscriptions = new Map();
/**
* A reference to the previously-provided motion values as returned
* from scrapeMotionValuesFromProps. We use the keys in here to determine
* if any motion values need to be removed after props are updated.
*/
var prevMotionValues = {};
/**
* When values are removed from all animation props we need to search
* for a fallback value to animate to. These values are tracked in baseTarget.
*/
var baseTarget = tslib.__assign({}, latestValues);
// Internal methods ========================
/**
* On mount, this will be hydrated with a callback to disconnect
* this visual element from its parent on unmount.
*/
var removeFromVariantTree;
/**
* Render the element with the latest styles outside of the React
* render lifecycle
*/
function render() {
if (!instance || !isMounted)
return;
triggerBuild();
renderInstance(instance, renderState, props.style, element.projection);
}
function triggerBuild() {
build(element, renderState, latestValues, options, props);
}
function update() {
lifecycles.notifyUpdate(latestValues);
}
/**
*
*/
function bindToMotionValue(key, value) {
var removeOnChange = value.onChange(function (latestValue) {
latestValues[key] = latestValue;
props.onUpdate && sync__default["default"].update(update, false, true);
});
var removeOnRenderRequest = value.onRenderRequest(element.scheduleRender);
valueSubscriptions.set(key, function () {
removeOnChange();
removeOnRenderRequest();
});
}
/**
* Any motion values that are provided to the element when created
* aren't yet bound to the element, as this would technically be impure.
* However, we iterate through the motion values and set them to the
* initial values for this component.
*
* TODO: This is impure and we should look at changing this to run on mount.
* Doing so will break some tests but this isn't neccessarily a breaking change,
* more a reflection of the test.
*/
var initialMotionValues = scrapeMotionValuesFromProps(props);
for (var key in initialMotionValues) {
var value = initialMotionValues[key];
if (latestValues[key] !== undefined && isMotionValue(value)) {
value.set(latestValues[key], false);
}
}
/**
* Determine what role this visual element should take in the variant tree.
*/
var isControllingVariants = checkIfControllingVariants(props);
var isVariantNode = checkIfVariantNode(props);
var element = tslib.__assign(tslib.__assign({ treeType: treeType,
/**
* This is a mirror of the internal instance prop, which keeps
* VisualElement type-compatible with React's RefObject.
*/
current: null,
/**
* The depth of this visual element within the visual element tree.
*/
depth: parent ? parent.depth + 1 : 0, parent: parent, children: new Set(),
/**
*
*/
presenceId: presenceId, shouldReduceMotion: shouldReduceMotion,
/**
* If this component is part of the variant tree, it should track
* any children that are also part of the tree. This is essentially
* a shadow tree to simplify logic around how to stagger over children.
*/
variantChildren: isVariantNode ? new Set() : undefined,
/**
* Whether this instance is visible. This can be changed imperatively
* by the projection tree, is analogous to CSS's visibility in that
* hidden elements should take up layout, and needs enacting by the configured
* render function.
*/
isVisible: undefined,
/**
* Normally, if a component is controlled by a parent's variants, it can
* rely on that ancestor to trigger animations further down the tree.
* However, if a component is created after its parent is mounted, the parent
* won't trigger that mount animation so the child needs to.
*
* TODO: This might be better replaced with a method isParentMounted
*/
manuallyAnimateOnMount: Boolean(parent === null || parent === void 0 ? void 0 : parent.isMounted()),
/**
* This can be set by AnimatePresence to force components that mount
* at the same time as it to mount as if they have initial={false} set.
*/
blockInitialAnimation: blockInitialAnimation,
/**
* Determine whether this component has mounted yet. This is mostly used
* by variant children to determine whether they need to trigger their
* own animations on mount.
*/
isMounted: function () { return Boolean(instance); }, mount: function (newInstance) {
isMounted = true;
instance = element.current = newInstance;
if (element.projection) {
element.projection.mount(newInstance);
}
if (isVariantNode && parent && !isControllingVariants) {
removeFromVariantTree = parent === null || parent === void 0 ? void 0 : parent.addVariantChild(element);
}
values.forEach(function (value, key) { return bindToMotionValue(key, value); });
parent === null || parent === void 0 ? void 0 : parent.children.add(element);
element.setProps(props);
},
/**
*
*/
unmount: function () {
var _a;
(_a = element.projection) === null || _a === void 0 ? void 0 : _a.unmount();
sync.cancelSync.update(update);
sync.cancelSync.render(render);
valueSubscriptions.forEach(function (remove) { return remove(); });
removeFromVariantTree === null || removeFromVariantTree === void 0 ? void 0 : removeFromVariantTree();
parent === null || parent === void 0 ? void 0 : parent.children.delete(element);
lifecycles.clearAllListeners();
instance = undefined;
isMounted = false;
},
/**
* Add a child visual element to our set of children.
*/
addVariantChild: function (child) {
var _a;
var closestVariantNode = element.getClosestVariantNode();
if (closestVariantNode) {
(_a = closestVariantNode.variantChildren) === null || _a === void 0 ? void 0 : _a.add(child);
return function () {
return closestVariantNode.variantChildren.delete(child);
};
}
}, sortNodePosition: function (other) {
/**
* If these nodes aren't even of the same type we can't compare their depth.
*/
if (!sortNodePosition || treeType !== other.treeType)
return 0;
return sortNodePosition(element.getInstance(), other.getInstance());
},
/**
* Returns the closest variant node in the tree starting from
* this visual element.
*/
getClosestVariantNode: function () {
return isVariantNode ? element : parent === null || parent === void 0 ? void 0 : parent.getClosestVariantNode();
},
/**
* Expose the latest layoutId prop.
*/
getLayoutId: function () { return props.layoutId; },
/**
* Returns the current instance.
*/
getInstance: function () { return instance; },
/**
* Get/set the latest static values.
*/
getStaticValue: function (key) { return latestValues[key]; }, setStaticValue: function (key, value) { return (latestValues[key] = value); },
/**
* Returns the latest motion value state. Currently only used to take
* a snapshot of the visual element - perhaps this can return the whole
* visual state
*/
getLatestValues: function () { return latestValues; },
/**
* Set the visiblity of the visual element. If it's changed, schedule
* a render to reflect these changes.
*/
setVisibility: function (visibility) {
if (element.isVisible === visibility)
return;
element.isVisible = visibility;
element.scheduleRender();
},
/**
* Make a target animatable by Popmotion. For instance, if we're
* trying to animate width from 100px to 100vw we need to measure 100vw
* in pixels to determine what we really need to animate to. This is also
* pluggable to support Framer's custom value types like Color,
* and CSS variables.
*/
makeTargetAnimatable: function (target, canMutate) {
if (canMutate === void 0) { canMutate = true; }
return makeTargetAnimatable(element, target, props, canMutate);
},
/**
* Measure the current viewport box with or without transforms.
* Only measures axis-aligned boxes, rotate and skew must be manually
* removed with a re-render to work.
*/
measureViewportBox: function () {
return measureViewportBox(instance, props);
},
// Motion values ========================
/**
* Add a motion value and bind it to this visual element.
*/
addValue: function (key, value) {
// Remove existing value if it exists
if (element.hasValue(key))
element.removeValue(key);
values.set(key, value);
latestValues[key] = value.get();
bindToMotionValue(key, value);
},
/**
* Remove a motion value and unbind any active subscriptions.
*/
removeValue: function (key) {
var _a;
values.delete(key);
(_a = valueSubscriptions.get(key)) === null || _a === void 0 ? void 0 : _a();
valueSubscriptions.delete(key);
delete latestValues[key];
removeValueFromRenderState(key, renderState);
},
/**
* Check whether we have a motion value for this key
*/
hasValue: function (key) { return values.has(key); },
/**
* Get a motion value for this key. If called with a default
* value, we'll create one if none exists.
*/
getValue: function (key, defaultValue) {
var value = values.get(key);
if (value === undefined && defaultValue !== undefined) {
value = motionValue(defaultValue);
element.addValue(key, value);
}
return value;
},
/**
* Iterate over our motion values.
*/
forEachValue: function (callback) { return values.forEach(callback); },
/**
* If we're trying to animate to a previously unencountered value,
* we need to check for it in our state and as a last resort read it
* directly from the instance (which might have performance implications).
*/
readValue: function (key) {
var _a;
return (_a = latestValues[key]) !== null && _a !== void 0 ? _a : readValueFromInstance(instance, key, options);
},
/**
* Set the base target to later animate back to. This is currently
* only hydrated on creation and when we first read a value.
*/
setBaseTarget: function (key, value) {
baseTarget[key] = value;
},
/**
* Find the base target for a value thats been removed from all animation
* props.
*/
getBaseTarget: function (key) {
if (getBaseTarget) {
var target = getBaseTarget(props, key);
if (target !== undefined && !isMotionValue(target))
return target;
}
return baseTarget[key];
} }, lifecycles), {
/**
* Build the renderer state based on the latest visual state.
*/
build: function () {
triggerBuild();
return renderState;
},
/**
* Schedule a render on the next animation frame.
*/
scheduleRender: function () {
sync__default["default"].render(render, false, true);
},
/**
* Synchronously fire render. It's prefered that we batch renders but
* in many circumstances, like layout measurement, we need to run this
* synchronously. However in those instances other measures should be taken
* to batch reads/writes.
*/
syncRender: render,
/**
* Update the provided props. Ensure any newly-added motion values are
* added to our map, old ones removed, and listeners updated.
*/
setProps: function (newProps) {
if (newProps.transformTemplate || props.transformTemplate) {
element.scheduleRender();
}
props = newProps;
lifecycles.updatePropListeners(newProps);
prevMotionValues = updateMotionValuesFromProps(element, scrapeMotionValuesFromProps(props), prevMotionValues);
}, getProps: function () { return props; },
// Variants ==============================
/**
* Returns the variant definition with a given name.
*/
getVariant: function (name) { var _a; return (_a = props.variants) === null || _a === void 0 ? void 0 : _a[name]; },
/**
* Returns the defined default transition on this component.
*/
getDefaultTransition: function () { return props.transition; }, getTransformPagePoint: function () {
return props.transformPagePoint;
},
/**
* Used by child variant nodes to get the closest ancestor variant props.
*/
getVariantContext: function (startAtParent) {
if (startAtParent === void 0) { startAtParent = false; }
if (startAtParent)
return parent === null || parent === void 0 ? void 0 : parent.getVariantContext();
if (!isControllingVariants) {
var context_1 = (parent === null || parent === void 0 ? void 0 : parent.getVariantContext()) || {};
if (props.initial !== undefined) {
context_1.initial = props.initial;
}
return context_1;
}
var context = {};
for (var i = 0; i < numVariantProps; i++) {
var name_1 = variantProps[i];
var prop = props[name_1];
if (isVariantLabel(prop) || prop === false) {
context[name_1] = prop;
}
}
return context;
} });
return element;
};
};
var variantProps = tslib.__spreadArray(["initial"], tslib.__read(variantPriorityOrder), false);
var numVariantProps = variantProps.length;
function isCSSVariable(value) {
return typeof value === "string" && value.startsWith("var(--");
}
/**
* Parse Framer's special CSS variable format into a CSS token and a fallback.
*
* ```
* `var(--foo, #fff)` => [`--foo`, '#fff']
* ```
*
* @param current
*/
var cssVariableRegex = /var\((--[a-zA-Z0-9-_]+),? ?([a-zA-Z0-9 ()%#.,-]+)?\)/;
function parseCSSVariable(current) {
var match = cssVariableRegex.exec(current);
if (!match)
return [,];
var _a = tslib.__read(match, 3), token = _a[1], fallback = _a[2];
return [token, fallback];
}
var maxDepth = 4;
function getVariableValue(current, element, depth) {
if (depth === void 0) { depth = 1; }
heyListen.invariant(depth <= maxDepth, "Max CSS variable fallback depth detected in property \"".concat(current, "\". This may indicate a circular fallback dependency."));
var _a = tslib.__read(parseCSSVariable(current), 2), token = _a[0], fallback = _a[1];
// No CSS variable detected
if (!token)
return;
// Attempt to read this CSS variable off the element
var resolved = window.getComputedStyle(element).getPropertyValue(token);
if (resolved) {
return resolved.trim();
}
else if (isCSSVariable(fallback)) {
// The fallback might itself be a CSS variable, in which case we attempt to resolve it too.
return getVariableValue(fallback, element, depth + 1);
}
else {
return fallback;
}
}
/**
* Resolve CSS variables from
*
* @internal
*/
function resolveCSSVariables(visualElement, _a, transitionEnd) {
var _b;
var target = tslib.__rest(_a, []);
var element = visualElement.getInstance();
if (!(element instanceof Element))
return { target: target, transitionEnd: transitionEnd };
// If `transitionEnd` isn't `undefined`, clone it. We could clone `target` and `transitionEnd`
// only if they change but I think this reads clearer and this isn't a performance-critical path.
if (transitionEnd) {
transitionEnd = tslib.__assign({}, transitionEnd);
}
// Go through existing `MotionValue`s and ensure any existing CSS variables are resolved
visualElement.forEachValue(function (value) {
var current = value.get();
if (!isCSSVariable(current))
return;
var resolved = getVariableValue(current, element);
if (resolved)
value.set(resolved);
});
// Cycle through every target property and resolve CSS variables. Currently
// we only read single-var properties like `var(--foo)`, not `calc(var(--foo) + 20px)`
for (var key in target) {
var current = target[key];
if (!isCSSVariable(current))
continue;
var resolved = getVariableValue(current, element);
if (!resolved)
continue;
// Clone target if it hasn't already been
target[key] = resolved;
// If the user hasn't already set this key on `transitionEnd`, set it to the unresolved
// CSS variable. This will ensure that after the animation the component will reflect
// changes in the value of the CSS variable.
if (transitionEnd)
(_b = transitionEnd[key]) !== null && _b !== void 0 ? _b : (transitionEnd[key] = current);
}
return { target: target, transitionEnd: transitionEnd };
}
var positionalKeys = new Set([
"width",
"height",
"top",
"left",
"right",
"bottom",
"x",
"y",
]);
var isPositionalKey = function (key) { return positionalKeys.has(key); };
var hasPositionalKey = function (target) {
return Object.keys(target).some(isPositionalKey);
};
var setAndResetVelocity = function (value, to) {
// Looks odd but setting it twice doesn't render, it'll just
// set both prev and current to the latest value
value.set(to, false);
value.set(to);
};
var isNumOrPxType = function (v) {
return v === styleValueTypes.number || v === styleValueTypes.px;
};
var BoundingBoxDimension;
(function (BoundingBoxDimension) {
BoundingBoxDimension["width"] = "width";
BoundingBoxDimension["height"] = "height";
BoundingBoxDimension["left"] = "left";
BoundingBoxDimension["right"] = "right";
BoundingBoxDimension["top"] = "top";
BoundingBoxDimension["bottom"] = "bottom";
})(BoundingBoxDimension || (BoundingBoxDimension = {}));
var getPosFromMatrix = function (matrix, pos) {
return parseFloat(matrix.split(", ")[pos]);
};
var getTranslateFromMatrix = function (pos2, pos3) {
return function (_bbox, _a) {
var transform = _a.transform;
if (transform === "none" || !transform)
return 0;
var matrix3d = transform.match(/^matrix3d\((.+)\)$/);
if (matrix3d) {
return getPosFromMatrix(matrix3d[1], pos3);
}
else {
var matrix = transform.match(/^matrix\((.+)\)$/);
if (matrix) {
return getPosFromMatrix(matrix[1], pos2);
}
else {
return 0;
}
}
};
};
var transformKeys = new Set(["x", "y", "z"]);
var nonTranslationalTransformKeys = transformProps.filter(function (key) { return !transformKeys.has(key); });
function removeNonTranslationalTransform(visualElement) {
var removedTransforms = [];
nonTranslationalTransformKeys.forEach(function (key) {
var value = visualElement.getValue(key);
if (value !== undefined) {
removedTransforms.push([key, value.get()]);
value.set(key.startsWith("scale") ? 1 : 0);
}
});
// Apply changes to element before measurement
if (removedTransforms.length)
visualElement.syncRender();
return removedTransforms;
}
var positionalValues = {
// Dimensions
width: function (_a, _b) {
var x = _a.x;
var _c = _b.paddingLeft, paddingLeft = _c === void 0 ? "0" : _c, _d = _b.paddingRight, paddingRight = _d === void 0 ? "0" : _d;
return x.max - x.min - parseFloat(paddingLeft) - parseFloat(paddingRight);
},
height: function (_a, _b) {
var y = _a.y;
var _c = _b.paddingTop, paddingTop = _c === void 0 ? "0" : _c, _d = _b.paddingBottom, paddingBottom = _d === void 0 ? "0" : _d;
return y.max - y.min - parseFloat(paddingTop) - parseFloat(paddingBottom);
},
top: function (_bbox, _a) {
var top = _a.top;
return parseFloat(top);
},
left: function (_bbox, _a) {
var left = _a.left;
return parseFloat(left);
},
bottom: function (_a, _b) {
var y = _a.y;
var top = _b.top;
return parseFloat(top) + (y.max - y.min);
},
right: function (_a, _b) {
var x = _a.x;
var left = _b.left;
return parseFloat(left) + (x.max - x.min);
},
// Transform
x: getTranslateFromMatrix(4, 13),
y: getTranslateFromMatrix(5, 14),
};
var convertChangedValueTypes = function (target, visualElement, changedKeys) {
var originBbox = visualElement.measureViewportBox();
var element = visualElement.getInstance();
var elementComputedStyle = getComputedStyle(element);
var display = elementComputedStyle.display;
var origin = {};
// If the element is currently set to display: "none", make it visible before
// measuring the target bounding box
if (display === "none") {
visualElement.setStaticValue("display", target.display || "block");
}
/**
* Record origins before we render and update styles
*/
changedKeys.forEach(function (key) {
origin[key] = positionalValues[key](originBbox, elementComputedStyle);
});
// Apply the latest values (as set in checkAndConvertChangedValueTypes)
visualElement.syncRender();
var targetBbox = visualElement.measureViewportBox();
changedKeys.forEach(function (key) {
// Restore styles to their **calculated computed style**, not their actual
// originally set style. This allows us to animate between equivalent pixel units.
var value = visualElement.getValue(key);
setAndResetVelocity(value, origin[key]);
target[key] = positionalValues[key](targetBbox, elementComputedStyle);
});
return target;
};
var checkAndConvertChangedValueTypes = function (visualElement, target, origin, transitionEnd) {
if (origin === void 0) { origin = {}; }
if (transitionEnd === void 0) { transitionEnd = {}; }
target = tslib.__assign({}, target);
transitionEnd = tslib.__assign({}, transitionEnd);
var targetPositionalKeys = Object.keys(target).filter(isPositionalKey);
// We want to remove any transform values that could affect the element's bounding box before
// it's measured. We'll reapply these later.
var removedTransformValues = [];
var hasAttemptedToRemoveTransformValues = false;
var changedValueTypeKeys = [];
targetPositionalKeys.forEach(function (key) {
var value = visualElement.getValue(key);
if (!visualElement.hasValue(key))
return;
var from = origin[key];
var fromType = findDimensionValueType(from);
var to = target[key];
var toType;
// TODO: The current implementation of this basically throws an error
// if you try and do value conversion via keyframes. There's probably
// a way of doing this but the performance implications would need greater scrutiny,
// as it'd be doing multiple resize-remeasure operations.
if (isKeyframesTarget(to)) {
var numKeyframes = to.length;
var fromIndex = to[0] === null ? 1 : 0;
from = to[fromIndex];
fromType = findDimensionValueType(from);
for (var i = fromIndex; i < numKeyframes; i++) {
if (!toType) {
toType = findDimensionValueType(to[i]);
heyListen.invariant(toType === fromType ||
(isNumOrPxType(fromType) && isNumOrPxType(toType)), "Keyframes must be of the same dimension as the current value");
}
else {
heyListen.invariant(findDimensionValueType(to[i]) === toType, "All keyframes must be of the same type");
}
}
}
else {
toType = findDimensionValueType(to);
}
if (fromType !== toType) {
// If they're both just number or px, convert them both to numbers rather than
// relying on resize/remeasure to convert (which is wasteful in this situation)
if (isNumOrPxType(fromType) && isNumOrPxType(toType)) {
var current = value.get();
if (typeof current === "string") {
value.set(parseFloat(current));
}
if (typeof to === "string") {
target[key] = parseFloat(to);
}
else if (Array.isArray(to) && toType === styleValueTypes.px) {
target[key] = to.map(parseFloat);
}
}
else if ((fromType === null || fromType === void 0 ? void 0 : fromType.transform) &&
(toType === null || toType === void 0 ? void 0 : toType.transform) &&
(from === 0 || to === 0)) {
// If one or the other value is 0, it's safe to coerce it to the
// type of the other without measurement
if (from === 0) {
value.set(toType.transform(from));
}
else {
target[key] = fromType.transform(to);
}
}
else {
// If we're going to do value conversion via DOM measurements, we first
// need to remove non-positional transform values that could affect the bbox measurements.
if (!hasAttemptedToRemoveTransformValues) {
removedTransformValues =
removeNonTranslationalTransform(visualElement);
hasAttemptedToRemoveTransformValues = true;
}
changedValueTypeKeys.push(key);
transitionEnd[key] =
transitionEnd[key] !== undefined
? transitionEnd[key]
: target[key];
setAndResetVelocity(value, to);
}
}
});
if (changedValueTypeKeys.length) {
var scrollY_1 = changedValueTypeKeys.indexOf("height") >= 0
? window.pageYOffset
: null;
var convertedTarget = convertChangedValueTypes(target, visualElement, changedValueTypeKeys);
// If we removed transform values, reapply them before the next render
if (removedTransformValues.length) {
removedTransformValues.forEach(function (_a) {
var _b = tslib.__read(_a, 2), key = _b[0], value = _b[1];
visualElement.getValue(key).set(value);
});
}
// Reapply original values
visualElement.syncRender();
// Restore scroll position
if (scrollY_1 !== null)
window.scrollTo({ top: scrollY_1 });
return { target: convertedTarget, transitionEnd: transitionEnd };
}
else {
return { target: target, transitionEnd: transitionEnd };
}
};
/**
* Convert value types for x/y/width/height/top/left/bottom/right
*
* Allows animation between `'auto'` -> `'100%'` or `0` -> `'calc(50% - 10vw)'`
*
* @internal
*/
function unitConversion(visualElement, target, origin, transitionEnd) {
return hasPositionalKey(target)
? checkAndConvertChangedValueTypes(visualElement, target, origin, transitionEnd)
: { target: target, transitionEnd: transitionEnd };
}
/**
* Parse a DOM variant to make it animatable. This involves resolving CSS variables
* and ensuring animations like "20%" => "calc(50vw)" are performed in pixels.
*/
var parseDomVariant = function (visualElement, target, origin, transitionEnd) {
var resolved = resolveCSSVariables(visualElement, target, transitionEnd);
target = resolved.target;
transitionEnd = resolved.transitionEnd;
return unitConversion(visualElement, target, origin, transitionEnd);
};
function getComputedStyle$1(element) {
return window.getComputedStyle(element);
}
var htmlConfig = {
treeType: "dom",
readValueFromInstance: function (domElement, key) {
if (isTransformProp(key)) {
var defaultType = getDefaultValueType(key);
return defaultType ? defaultType.default || 0 : 0;
}
else {
var computedStyle = getComputedStyle$1(domElement);
return ((isCSSVariable$1(key)
? computedStyle.getPropertyValue(key)
: computedStyle[key]) || 0);
}
},
sortNodePosition: function (a, b) {
/**
* compareDocumentPosition returns a bitmask, by using the bitwise &
* we're returning true if 2 in that bitmask is set to true. 2 is set
* to true if b preceeds a.
*/
return a.compareDocumentPosition(b) & 2 ? 1 : -1;
},
getBaseTarget: function (props, key) {
var _a;
return (_a = props.style) === null || _a === void 0 ? void 0 : _a[key];
},
measureViewportBox: function (element, _a) {
var transformPagePoint = _a.transformPagePoint;
return measureViewportBox(element, transformPagePoint);
},
/**
* Reset the transform on the current Element. This is called as part
* of a batched process across the entire layout tree. To remove this write
* cycle it'd be interesting to see if it's possible to "undo" all the current
* layout transforms up the tree in the same way this.getBoundingBoxWithoutTransforms
* works
*/
resetTransform: function (element, domElement, props) {
var transformTemplate = props.transformTemplate;
domElement.style.transform = transformTemplate
? transformTemplate({}, "")
: "none";
// Ensure that whatever happens next, we restore our transform on the next frame
element.scheduleRender();
},
restoreTransform: function (instance, mutableState) {
instance.style.transform = mutableState.style.transform;
},
removeValueFromRenderState: function (key, _a) {
var vars = _a.vars, style = _a.style;
delete vars[key];
delete style[key];
},
/**
* Ensure that HTML and Framer-specific value types like `px`->`%` and `Color`
* can be animated by Motion.
*/
makeTargetAnimatable: function (element, _a, _b, isMounted) {
var transformValues = _b.transformValues;
if (isMounted === void 0) { isMounted = true; }
var transition = _a.transition, transitionEnd = _a.transitionEnd, target = tslib.__rest(_a, ["transition", "transitionEnd"]);
var origin = getOrigin(target, transition || {}, element);
/**
* If Framer has provided a function to convert `Color` etc value types, convert them
*/
if (transformValues) {
if (transitionEnd)
transitionEnd = transformValues(transitionEnd);
if (target)
target = transformValues(target);
if (origin)
origin = transformValues(origin);
}
if (isMounted) {
checkTargetForNewValues(element, target, origin);
var parsed = parseDomVariant(element, target, origin, transitionEnd);
transitionEnd = parsed.transitionEnd;
target = parsed.target;
}
return tslib.__assign({ transition: transition, transitionEnd: transitionEnd }, target);
},
scrapeMotionValuesFromProps: scrapeMotionValuesFromProps$1,
build: function (element, renderState, latestValues, options, props) {
if (element.isVisible !== undefined) {
renderState.style.visibility = element.isVisible
? "visible"
: "hidden";
}
buildHTMLStyles(renderState, latestValues, options, props.transformTemplate);
},
render: renderHTML,
};
var htmlVisualElement = visualElement(htmlConfig);
var svgVisualElement = visualElement(tslib.__assign(tslib.__assign({}, htmlConfig), { getBaseTarget: function (props, key) {
return props[key];
}, readValueFromInstance: function (domElement, key) {
var _a;
if (isTransformProp(key)) {
return ((_a = getDefaultValueType(key)) === null || _a === void 0 ? void 0 : _a.default) || 0;
}
key = !camelCaseAttributes.has(key) ? camelToDash(key) : key;
return domElement.getAttribute(key);
}, scrapeMotionValuesFromProps: scrapeMotionValuesFromProps, build: function (_element, renderState, latestValues, options, props) {
buildSVGAttrs(renderState, latestValues, options, props.transformTemplate);
}, render: renderSVG }));
var createDomVisualElement = function (Component, options) {
return isSVGComponent(Component)
? svgVisualElement(options, { enableHardwareAcceleration: false })
: htmlVisualElement(options, { enableHardwareAcceleration: true });
};
function pixelsToPercent(pixels, axis) {
if (axis.max === axis.min)
return 0;
return (pixels / (axis.max - axis.min)) * 100;
}
/**
* We always correct borderRadius as a percentage rather than pixels to reduce paints.
* For example, if you are projecting a box that is 100px wide with a 10px borderRadius
* into a box that is 200px wide with a 20px borderRadius, that is actually a 10%
* borderRadius in both states. If we animate between the two in pixels that will trigger
* a paint each time. If we animate between the two in percentage we'll avoid a paint.
*/
var correctBorderRadius = {
correct: function (latest, node) {
if (!node.target)
return latest;
/**
* If latest is a string, if it's a percentage we can return immediately as it's
* going to be stretched appropriately. Otherwise, if it's a pixel, convert it to a number.
*/
if (typeof latest === "string") {
if (styleValueTypes.px.test(latest)) {
latest = parseFloat(latest);
}
else {
return latest;
}
}
/**
* If latest is a number, it's a pixel value. We use the current viewportBox to calculate that
* pixel value as a percentage of each axis
*/
var x = pixelsToPercent(latest, node.target.x);
var y = pixelsToPercent(latest, node.target.y);
return "".concat(x, "% ").concat(y, "%");
},
};
var varToken = "_$css";
var correctBoxShadow = {
correct: function (latest, _a) {
var treeScale = _a.treeScale, projectionDelta = _a.projectionDelta;
var original = latest;
/**
* We need to first strip and store CSS variables from the string.
*/
var containsCSSVariables = latest.includes("var(");
var cssVariables = [];
if (containsCSSVariables) {
latest = latest.replace(cssVariableRegex, function (match) {
cssVariables.push(match);
return varToken;
});
}
var shadow = styleValueTypes.complex.parse(latest);
// TODO: Doesn't support multiple shadows
if (shadow.length > 5)
return original;
var template = styleValueTypes.complex.createTransformer(latest);
var offset = typeof shadow[0] !== "number" ? 1 : 0;
// Calculate the overall context scale
var xScale = projectionDelta.x.scale * treeScale.x;
var yScale = projectionDelta.y.scale * treeScale.y;
shadow[0 + offset] /= xScale;
shadow[1 + offset] /= yScale;
/**
* Ideally we'd correct x and y scales individually, but because blur and
* spread apply to both we have to take a scale average and apply that instead.
* We could potentially improve the outcome of this by incorporating the ratio between
* the two scales.
*/
var averageScale = popmotion.mix(xScale, yScale, 0.5);
// Blur
if (typeof shadow[2 + offset] === "number")
shadow[2 + offset] /= averageScale;
// Spread
if (typeof shadow[3 + offset] === "number")
shadow[3 + offset] /= averageScale;
var output = template(shadow);
if (containsCSSVariables) {
var i_1 = 0;
output = output.replace(varToken, function () {
var cssVariable = cssVariables[i_1];
i_1++;
return cssVariable;
});
}
return output;
},
};
var MeasureLayoutWithContext = /** @class */ (function (_super) {
tslib.__extends(MeasureLayoutWithContext, _super);
function MeasureLayoutWithContext() {
return _super !== null && _super.apply(this, arguments) || this;
}
/**
* This only mounts projection nodes for components that
* need measuring, we might want to do it for all components
* in order to incorporate transforms
*/
MeasureLayoutWithContext.prototype.componentDidMount = function () {
var _this = this;
var _a = this.props, visualElement = _a.visualElement, layoutGroup = _a.layoutGroup, switchLayoutGroup = _a.switchLayoutGroup, layoutId = _a.layoutId;
var projection = visualElement.projection;
addScaleCorrector(defaultScaleCorrectors);
if (projection) {
if (layoutGroup === null || layoutGroup === void 0 ? void 0 : layoutGroup.group)
layoutGroup.group.add(projection);
if ((switchLayoutGroup === null || switchLayoutGroup === void 0 ? void 0 : switchLayoutGroup.register) && layoutId) {
switchLayoutGroup.register(projection);
}
projection.root.didUpdate();
projection.addEventListener("animationComplete", function () {
_this.safeToRemove();
});
projection.setOptions(tslib.__assign(tslib.__assign({}, projection.options), { onExitComplete: function () { return _this.safeToRemove(); } }));
}
globalProjectionState.hasEverUpdated = true;
};
MeasureLayoutWithContext.prototype.getSnapshotBeforeUpdate = function (prevProps) {
var _this = this;
var _a = this.props, layoutDependency = _a.layoutDependency, visualElement = _a.visualElement, drag = _a.drag, isPresent = _a.isPresent;
var projection = visualElement.projection;
if (!projection)
return null;
/**
* TODO: We use this data in relegate to determine whether to
* promote a previous element. There's no guarantee its presence data
* will have updated by this point - if a bug like this arises it will
* have to be that we markForRelegation and then find a new lead some other way,
* perhaps in didUpdate
*/
projection.isPresent = isPresent;
if (drag ||
prevProps.layoutDependency !== layoutDependency ||
layoutDependency === undefined) {
projection.willUpdate();
}
else {
this.safeToRemove();
}
if (prevProps.isPresent !== isPresent) {
if (isPresent) {
projection.promote();
}
else if (!projection.relegate()) {
/**
* If there's another stack member taking over from this one,
* it's in charge of the exit animation and therefore should
* be in charge of the safe to remove. Otherwise we call it here.
*/
sync__default["default"].postRender(function () {
var _a;
if (!((_a = projection.getStack()) === null || _a === void 0 ? void 0 : _a.members.length)) {
_this.safeToRemove();
}
});
}
}
return null;
};
MeasureLayoutWithContext.prototype.componentDidUpdate = function () {
var projection = this.props.visualElement.projection;
if (projection) {
projection.root.didUpdate();
if (!projection.currentAnimation && projection.isLead()) {
this.safeToRemove();
}
}
};
MeasureLayoutWithContext.prototype.componentWillUnmount = function () {
var _a = this.props, visualElement = _a.visualElement, layoutGroup = _a.layoutGroup, promoteContext = _a.switchLayoutGroup;
var projection = visualElement.projection;
if (projection) {
projection.scheduleCheckAfterUnmount();
if (layoutGroup === null || layoutGroup === void 0 ? void 0 : layoutGroup.group)
layoutGroup.group.remove(projection);
if (promoteContext === null || promoteContext === void 0 ? void 0 : promoteContext.deregister)
promoteContext.deregister(projection);
}
};
MeasureLayoutWithContext.prototype.safeToRemove = function () {
var safeToRemove = this.props.safeToRemove;
safeToRemove === null || safeToRemove === void 0 ? void 0 : safeToRemove();
};
MeasureLayoutWithContext.prototype.render = function () {
return null;
};
return MeasureLayoutWithContext;
}(React__default["default"].Component));
function MeasureLayout(props) {
var _a = tslib.__read(usePresence(), 2), isPresent = _a[0], safeToRemove = _a[1];
var layoutGroup = React.useContext(LayoutGroupContext);
return (React__default["default"].createElement(MeasureLayoutWithContext, tslib.__assign({}, props, { layoutGroup: layoutGroup, switchLayoutGroup: React.useContext(SwitchLayoutGroupContext), isPresent: isPresent, safeToRemove: safeToRemove })));
}
var defaultScaleCorrectors = {
borderRadius: tslib.__assign(tslib.__assign({}, correctBorderRadius), { applyTo: [
"borderTopLeftRadius",
"borderTopRightRadius",
"borderBottomLeftRadius",
"borderBottomRightRadius",
] }),
borderTopLeftRadius: correctBorderRadius,
borderTopRightRadius: correctBorderRadius,
borderBottomLeftRadius: correctBorderRadius,
borderBottomRightRadius: correctBorderRadius,
boxShadow: correctBoxShadow,
};
var layoutFeatures = {
measureLayout: MeasureLayout,
};
/**
* 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);
}
/**
* Remove a delta from a point. This is essentially the steps of applyPointDelta in reverse
*/
function removePointDelta(point, translate, scale, originPoint, boxScale) {
point -= translate;
point = scalePoint(point, 1 / scale, originPoint);
if (boxScale !== undefined) {
point = scalePoint(point, 1 / boxScale, originPoint);
}
return point;
}
/**
* Remove a delta from an axis. This is essentially the steps of applyAxisDelta in reverse
*/
function removeAxisDelta(axis, translate, scale, origin, boxScale, originAxis, sourceAxis) {
if (translate === void 0) { translate = 0; }
if (scale === void 0) { scale = 1; }
if (origin === void 0) { origin = 0.5; }
if (originAxis === void 0) { originAxis = axis; }
if (sourceAxis === void 0) { sourceAxis = axis; }
if (styleValueTypes.percent.test(translate)) {
translate = parseFloat(translate);
var relativeProgress = popmotion.mix(sourceAxis.min, sourceAxis.max, translate / 100);
translate = relativeProgress - sourceAxis.min;
}
if (typeof translate !== "number")
return;
var originPoint = popmotion.mix(originAxis.min, originAxis.max, origin);
if (axis === originAxis)
originPoint -= translate;
axis.min = removePointDelta(axis.min, translate, scale, originPoint, boxScale);
axis.max = removePointDelta(axis.max, translate, scale, originPoint, boxScale);
}
/**
* Remove a transforms from an axis. This is essentially the steps of applyAxisTransforms in reverse
* and acts as a bridge between motion values and removeAxisDelta
*/
function removeAxisTransforms(axis, transforms, _a, origin, sourceAxis) {
var _b = tslib.__read(_a, 3), key = _b[0], scaleKey = _b[1], originKey = _b[2];
removeAxisDelta(axis, transforms[key], transforms[scaleKey], transforms[originKey], transforms.scale, origin, sourceAxis);
}
/**
* The names of the motion values we want to apply as translation, scale and origin.
*/
var xKeys = ["x", "scaleX", "originX"];
var yKeys = ["y", "scaleY", "originY"];
/**
* Remove a transforms from an box. This is essentially the steps of applyAxisBox in reverse
* and acts as a bridge between motion values and removeAxisDelta
*/
function removeBoxTransforms(box, transforms, originBox, sourceBox) {
removeAxisTransforms(box.x, transforms, xKeys, originBox === null || originBox === void 0 ? void 0 : originBox.x, sourceBox === null || sourceBox === void 0 ? void 0 : sourceBox.x);
removeAxisTransforms(box.y, transforms, yKeys, originBox === null || originBox === void 0 ? void 0 : originBox.y, sourceBox === null || sourceBox === void 0 ? void 0 : sourceBox.y);
}
function isAxisDeltaZero(delta) {
return delta.translate === 0 && delta.scale === 1;
}
function isDeltaZero(delta) {
return isAxisDeltaZero(delta.x) && isAxisDeltaZero(delta.y);
}
function boxEquals(a, b) {
return (a.x.min === b.x.min &&
a.x.max === b.x.max &&
a.y.min === b.y.min &&
a.y.max === b.y.max);
}
var NodeStack = /** @class */ (function () {
function NodeStack() {
this.members = [];
}
NodeStack.prototype.add = function (node) {
addUniqueItem(this.members, node);
node.scheduleRender();
};
NodeStack.prototype.remove = function (node) {
removeItem(this.members, node);
if (node === this.prevLead) {
this.prevLead = undefined;
}
if (node === this.lead) {
var prevLead = this.members[this.members.length - 1];
if (prevLead) {
this.promote(prevLead);
}
}
};
NodeStack.prototype.relegate = function (node) {
var indexOfNode = this.members.findIndex(function (member) { return node === member; });
if (indexOfNode === 0)
return false;
/**
* Find the next projection node that is present
*/
var prevLead;
for (var i = indexOfNode; i >= 0; i--) {
var member = this.members[i];
if (member.isPresent !== false) {
prevLead = member;
break;
}
}
if (prevLead) {
this.promote(prevLead);
return true;
}
else {
return false;
}
};
NodeStack.prototype.promote = function (node, preserveFollowOpacity) {
var _a;
var prevLead = this.lead;
if (node === prevLead)
return;
this.prevLead = prevLead;
this.lead = node;
node.show();
if (prevLead) {
prevLead.instance && prevLead.scheduleRender();
node.scheduleRender();
node.resumeFrom = prevLead;
if (preserveFollowOpacity) {
node.resumeFrom.preserveOpacity = true;
}
if (prevLead.snapshot) {
node.snapshot = prevLead.snapshot;
node.snapshot.latestValues =
prevLead.animationValues || prevLead.latestValues;
node.snapshot.isShared = true;
}
if ((_a = node.root) === null || _a === void 0 ? void 0 : _a.isUpdating) {
node.isLayoutDirty = true;
}
var crossfade = node.options.crossfade;
if (crossfade === false) {
prevLead.hide();
}
/**
* TODO:
* - Test border radius when previous node was deleted
* - boxShadow mixing
* - Shared between element A in scrolled container and element B (scroll stays the same or changes)
* - Shared between element A in transformed container and element B (transform stays the same or changes)
* - Shared between element A in scrolled page and element B (scroll stays the same or changes)
* ---
* - Crossfade opacity of root nodes
* - layoutId changes after animation
* - layoutId changes mid animation
*/
}
};
NodeStack.prototype.exitAnimationComplete = function () {
this.members.forEach(function (node) {
var _a, _b, _c, _d, _e;
(_b = (_a = node.options).onExitComplete) === null || _b === void 0 ? void 0 : _b.call(_a);
(_e = (_c = node.resumingFrom) === null || _c === void 0 ? void 0 : (_d = _c.options).onExitComplete) === null || _e === void 0 ? void 0 : _e.call(_d);
});
};
NodeStack.prototype.scheduleRender = function () {
this.members.forEach(function (node) {
node.instance && node.scheduleRender(false);
});
};
/**
* Clear any leads that have been removed this render to prevent them from being
* used in future animations and to prevent memory leaks
*/
NodeStack.prototype.removeLeadSnapshot = function () {
if (this.lead && this.lead.snapshot) {
this.lead.snapshot = undefined;
}
};
return NodeStack;
}());
var identityProjection = "translate3d(0px, 0px, 0) scale(1, 1) scale(1, 1)";
function buildProjectionTransform(delta, treeScale, latestTransform) {
/**
* The translations we use to calculate are always relative to the viewport coordinate space.
* But when we apply scales, we also scale the coordinate space of an element and its children.
* For instance if we have a treeScale (the culmination of all parent scales) of 0.5 and we need
* to move an element 100 pixels, we actually need to move it 200 in within that scaled space.
*/
var xTranslate = delta.x.translate / treeScale.x;
var yTranslate = delta.y.translate / treeScale.y;
var transform = "translate3d(".concat(xTranslate, "px, ").concat(yTranslate, "px, 0) ");
/**
* Apply scale correction for the tree transform.
* This will apply scale to the screen-orientated axes.
*/
transform += "scale(".concat(1 / treeScale.x, ", ").concat(1 / treeScale.y, ") ");
if (latestTransform) {
var rotate = latestTransform.rotate, rotateX = latestTransform.rotateX, rotateY = latestTransform.rotateY;
if (rotate)
transform += "rotate(".concat(rotate, "deg) ");
if (rotateX)
transform += "rotateX(".concat(rotateX, "deg) ");
if (rotateY)
transform += "rotateY(".concat(rotateY, "deg) ");
}
/**
* Apply scale to match the size of the element to the size we want it.
* This will apply scale to the element-orientated axes.
*/
var elementScaleX = delta.x.scale * treeScale.x;
var elementScaleY = delta.y.scale * treeScale.y;
transform += "scale(".concat(elementScaleX, ", ").concat(elementScaleY, ")");
return transform === identityProjection ? "none" : transform;
}
var compareByDepth = function (a, b) {
return a.depth - b.depth;
};
var FlatTree = /** @class */ (function () {
function FlatTree() {
this.children = [];
this.isDirty = false;
}
FlatTree.prototype.add = function (child) {
addUniqueItem(this.children, child);
this.isDirty = true;
};
FlatTree.prototype.remove = function (child) {
removeItem(this.children, child);
this.isDirty = true;
};
FlatTree.prototype.forEach = function (callback) {
this.isDirty && this.children.sort(compareByDepth);
this.isDirty = false;
this.children.forEach(callback);
};
return FlatTree;
}());
/**
* We use 1000 as the animation target as 0-1000 maps better to pixels than 0-1
* which has a noticeable difference in spring animations
*/
var animationTarget = 1000;
function createProjectionNode(_a) {
var attachResizeListener = _a.attachResizeListener, defaultParent = _a.defaultParent, measureScroll = _a.measureScroll, checkIsScrollRoot = _a.checkIsScrollRoot, resetTransform = _a.resetTransform;
return /** @class */ (function () {
function ProjectionNode(id, latestValues, parent) {
var _this = this;
if (latestValues === void 0) { latestValues = {}; }
if (parent === void 0) { parent = defaultParent === null || defaultParent === void 0 ? void 0 : defaultParent(); }
/**
* A Set containing all this component's children. This is used to iterate
* through the children.
*
* TODO: This could be faster to iterate as a flat array stored on the root node.
*/
this.children = new Set();
/**
* Options for the node. We use this to configure what kind of layout animations
* we should perform (if any).
*/
this.options = {};
/**
* We use this to detect when its safe to shut down part of a projection tree.
* We have to keep projecting children for scale correction and relative projection
* until all their parents stop performing layout animations.
*/
this.isTreeAnimating = false;
this.isAnimationBlocked = false;
/**
* Flag to true if we think this layout has been changed. We can't always know this,
* currently we set it to true every time a component renders, or if it has a layoutDependency
* if that has changed between renders. Additionally, components can be grouped by LayoutGroup
* and if one node is dirtied, they all are.
*/
this.isLayoutDirty = false;
/**
* Block layout updates for instant layout transitions throughout the tree.
*/
this.updateManuallyBlocked = false;
this.updateBlockedByResize = false;
/**
* Set to true between the start of the first `willUpdate` call and the end of the `didUpdate`
* call.
*/
this.isUpdating = false;
/**
* If this is an SVG element we currently disable projection transforms
*/
this.isSVG = false;
/**
* Flag to true (during promotion) if a node doing an instant layout transition needs to reset
* its projection styles.
*/
this.needsReset = false;
/**
* Flags whether this node should have its transform reset prior to measuring.
*/
this.shouldResetTransform = false;
/**
* An object representing the calculated contextual/accumulated/tree scale.
* This will be used to scale calculcated projection transforms, as these are
* calculated in screen-space but need to be scaled for elements to actually
* make it to their calculated destinations.
*
* TODO: Lazy-init
*/
this.treeScale = { x: 1, y: 1 };
/**
*
*/
this.eventHandlers = new Map();
// Note: Currently only running on root node
this.potentialNodes = new Map();
this.checkUpdateFailed = function () {
if (_this.isUpdating) {
_this.isUpdating = false;
_this.clearAllSnapshots();
}
};
this.updateProjection = function () {
_this.nodes.forEach(resolveTargetDelta);
_this.nodes.forEach(calcProjection);
};
this.hasProjected = false;
this.isVisible = true;
this.animationProgress = 0;
/**
* Shared layout
*/
// TODO Only running on root node
this.sharedNodes = new Map();
this.id = id;
this.latestValues = latestValues;
this.root = parent ? parent.root || parent : this;
this.path = parent ? tslib.__spreadArray(tslib.__spreadArray([], tslib.__read(parent.path), false), [parent], false) : [];
this.parent = parent;
this.depth = parent ? parent.depth + 1 : 0;
id && this.root.registerPotentialNode(id, this);
for (var i = 0; i < this.path.length; i++) {
this.path[i].shouldResetTransform = true;
}
if (this.root === this)
this.nodes = new FlatTree();
}
ProjectionNode.prototype.addEventListener = function (name, handler) {
if (!this.eventHandlers.has(name)) {
this.eventHandlers.set(name, new SubscriptionManager());
}
return this.eventHandlers.get(name).add(handler);
};
ProjectionNode.prototype.notifyListeners = function (name) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
var subscriptionManager = this.eventHandlers.get(name);
subscriptionManager === null || subscriptionManager === void 0 ? void 0 : subscriptionManager.notify.apply(subscriptionManager, tslib.__spreadArray([], tslib.__read(args), false));
};
ProjectionNode.prototype.hasListeners = function (name) {
return this.eventHandlers.has(name);
};
ProjectionNode.prototype.registerPotentialNode = function (id, node) {
this.potentialNodes.set(id, node);
};
/**
* Lifecycles
*/
ProjectionNode.prototype.mount = function (instance, isLayoutDirty) {
var _this = this;
var _a;
if (isLayoutDirty === void 0) { isLayoutDirty = false; }
if (this.instance)
return;
this.isSVG =
instance instanceof SVGElement && instance.tagName !== "svg";
this.instance = instance;
var _b = this.options, layoutId = _b.layoutId, layout = _b.layout, visualElement = _b.visualElement;
if (visualElement && !visualElement.getInstance()) {
visualElement.mount(instance);
}
this.root.nodes.add(this);
(_a = this.parent) === null || _a === void 0 ? void 0 : _a.children.add(this);
this.id && this.root.potentialNodes.delete(this.id);
if (isLayoutDirty && (layout || layoutId)) {
this.isLayoutDirty = true;
}
if (attachResizeListener) {
var unblockTimeout_1;
var resizeUnblockUpdate_1 = function () {
return (_this.root.updateBlockedByResize = false);
};
attachResizeListener(instance, function () {
_this.root.updateBlockedByResize = true;
clearTimeout(unblockTimeout_1);
unblockTimeout_1 = window.setTimeout(resizeUnblockUpdate_1, 250);
if (globalProjectionState.hasAnimatedSinceResize) {
globalProjectionState.hasAnimatedSinceResize = false;
_this.nodes.forEach(finishAnimation);
}
});
}
if (layoutId) {
this.root.registerSharedNode(layoutId, this);
}
// Only register the handler if it requires layout animation
if (this.options.animate !== false &&
visualElement &&
(layoutId || layout)) {
this.addEventListener("didUpdate", function (_a) {
var _b, _c, _d, _e, _f;
var delta = _a.delta, hasLayoutChanged = _a.hasLayoutChanged, hasRelativeTargetChanged = _a.hasRelativeTargetChanged, newLayout = _a.layout;
if (_this.isTreeAnimationBlocked()) {
_this.target = undefined;
_this.relativeTarget = undefined;
return;
}
// TODO: Check here if an animation exists
var layoutTransition = (_c = (_b = _this.options.transition) !== null && _b !== void 0 ? _b : visualElement.getDefaultTransition()) !== null && _c !== void 0 ? _c : defaultLayoutTransition;
var _g = visualElement.getProps(), onLayoutAnimationStart = _g.onLayoutAnimationStart, onLayoutAnimationComplete = _g.onLayoutAnimationComplete;
/**
* The target layout of the element might stay the same,
* but its position relative to its parent has changed.
*/
var targetChanged = !_this.targetLayout ||
!boxEquals(_this.targetLayout, newLayout) ||
hasRelativeTargetChanged;
/**
* If the layout hasn't seemed to have changed, it might be that the
* element is visually in the same place in the document but its position
* relative to its parent has indeed changed. So here we check for that.
*/
var hasOnlyRelativeTargetChanged = !hasLayoutChanged && hasRelativeTargetChanged;
if (((_d = _this.resumeFrom) === null || _d === void 0 ? void 0 : _d.instance) ||
hasOnlyRelativeTargetChanged ||
(hasLayoutChanged &&
(targetChanged || !_this.currentAnimation))) {
if (_this.resumeFrom) {
_this.resumingFrom = _this.resumeFrom;
_this.resumingFrom.resumingFrom = undefined;
}
_this.setAnimationOrigin(delta, hasOnlyRelativeTargetChanged);
var animationOptions = tslib.__assign(tslib.__assign({}, getValueTransition(layoutTransition, "layout")), { onPlay: onLayoutAnimationStart, onComplete: onLayoutAnimationComplete });
if (visualElement.shouldReduceMotion) {
animationOptions.delay = 0;
animationOptions.type = false;
}
_this.startAnimation(animationOptions);
}
else {
/**
* If the layout hasn't changed and we have an animation that hasn't started yet,
* finish it immediately. Otherwise it will be animating from a location
* that was probably never commited to screen and look like a jumpy box.
*/
if (!hasLayoutChanged &&
_this.animationProgress === 0) {
_this.finishAnimation();
}
_this.isLead() && ((_f = (_e = _this.options).onExitComplete) === null || _f === void 0 ? void 0 : _f.call(_e));
}
_this.targetLayout = newLayout;
});
}
};
ProjectionNode.prototype.unmount = function () {
var _a, _b;
this.options.layoutId && this.willUpdate();
this.root.nodes.remove(this);
(_a = this.getStack()) === null || _a === void 0 ? void 0 : _a.remove(this);
(_b = this.parent) === null || _b === void 0 ? void 0 : _b.children.delete(this);
this.instance = undefined;
sync.cancelSync.preRender(this.updateProjection);
};
// only on the root
ProjectionNode.prototype.blockUpdate = function () {
this.updateManuallyBlocked = true;
};
ProjectionNode.prototype.unblockUpdate = function () {
this.updateManuallyBlocked = false;
};
ProjectionNode.prototype.isUpdateBlocked = function () {
return this.updateManuallyBlocked || this.updateBlockedByResize;
};
ProjectionNode.prototype.isTreeAnimationBlocked = function () {
var _a;
return (this.isAnimationBlocked ||
((_a = this.parent) === null || _a === void 0 ? void 0 : _a.isTreeAnimationBlocked()) ||
false);
};
// Note: currently only running on root node
ProjectionNode.prototype.startUpdate = function () {
var _a;
if (this.isUpdateBlocked())
return;
this.isUpdating = true;
(_a = this.nodes) === null || _a === void 0 ? void 0 : _a.forEach(resetRotation);
};
ProjectionNode.prototype.willUpdate = function (shouldNotifyListeners) {
var _a, _b, _c;
if (shouldNotifyListeners === void 0) { shouldNotifyListeners = true; }
if (this.root.isUpdateBlocked()) {
(_b = (_a = this.options).onExitComplete) === null || _b === void 0 ? void 0 : _b.call(_a);
return;
}
!this.root.isUpdating && this.root.startUpdate();
if (this.isLayoutDirty)
return;
this.isLayoutDirty = true;
for (var i = 0; i < this.path.length; i++) {
var node = this.path[i];
node.shouldResetTransform = true;
/**
* TODO: Check we haven't updated the scroll
* since the last didUpdate
*/
node.updateScroll();
}
var _d = this.options, layoutId = _d.layoutId, layout = _d.layout;
if (layoutId === undefined && !layout)
return;
var transformTemplate = (_c = this.options.visualElement) === null || _c === void 0 ? void 0 : _c.getProps().transformTemplate;
this.prevTransformTemplateValue = transformTemplate === null || transformTemplate === void 0 ? void 0 : transformTemplate(this.latestValues, "");
this.updateSnapshot();
shouldNotifyListeners && this.notifyListeners("willUpdate");
};
// Note: Currently only running on root node
ProjectionNode.prototype.didUpdate = function () {
var updateWasBlocked = this.isUpdateBlocked();
// When doing an instant transition, we skip the layout update,
// but should still clean up the measurements so that the next
// snapshot could be taken correctly.
if (updateWasBlocked) {
this.unblockUpdate();
this.clearAllSnapshots();
this.nodes.forEach(clearMeasurements);
return;
}
if (!this.isUpdating)
return;
this.isUpdating = false;
/**
* Search for and mount newly-added projection elements.
*
* TODO: Every time a new component is rendered we could search up the tree for
* the closest mounted node and query from there rather than document.
*/
if (this.potentialNodes.size) {
this.potentialNodes.forEach(mountNodeEarly);
this.potentialNodes.clear();
}
/**
* Write
*/
this.nodes.forEach(resetTransformStyle);
/**
* Read ==================
*/
// Update layout measurements of updated children
this.nodes.forEach(updateLayout);
/**
* Write
*/
// Notify listeners that the layout is updated
this.nodes.forEach(notifyLayoutUpdate);
this.clearAllSnapshots();
// Flush any scheduled updates
sync.flushSync.update();
sync.flushSync.preRender();
sync.flushSync.render();
};
ProjectionNode.prototype.clearAllSnapshots = function () {
this.nodes.forEach(clearSnapshot);
this.sharedNodes.forEach(removeLeadSnapshots);
};
ProjectionNode.prototype.scheduleUpdateProjection = function () {
sync__default["default"].preRender(this.updateProjection, false, true);
};
ProjectionNode.prototype.scheduleCheckAfterUnmount = function () {
var _this = this;
/**
* If the unmounting node is in a layoutGroup and did trigger a willUpdate,
* we manually call didUpdate to give a chance to the siblings to animate.
* Otherwise, cleanup all snapshots to prevents future nodes from reusing them.
*/
sync__default["default"].postRender(function () {
if (_this.isLayoutDirty) {
_this.root.didUpdate();
}
else {
_this.root.checkUpdateFailed();
}
});
};
/**
* Update measurements
*/
ProjectionNode.prototype.updateSnapshot = function () {
if (this.snapshot || !this.instance)
return;
var measured = this.measure();
var layout = this.removeTransform(this.removeElementScroll(measured));
roundBox(layout);
this.snapshot = {
measured: measured,
layout: layout,
latestValues: {},
};
};
ProjectionNode.prototype.updateLayout = function () {
var _a;
if (!this.instance)
return;
// TODO: Incorporate into a forwarded scroll offset
this.updateScroll();
if (!(this.options.alwaysMeasureLayout && this.isLead()) &&
!this.isLayoutDirty) {
return;
}
/**
* When a node is mounted, it simply resumes from the prevLead's
* snapshot instead of taking a new one, but the ancestors scroll
* might have updated while the prevLead is unmounted. We need to
* update the scroll again to make sure the layout we measure is
* up to date.
*/
if (this.resumeFrom && !this.resumeFrom.instance) {
for (var i = 0; i < this.path.length; i++) {
var node = this.path[i];
node.updateScroll();
}
}
var measured = this.measure();
roundBox(measured);
var prevLayout = this.layout;
this.layout = {
measured: measured,
actual: this.removeElementScroll(measured),
};
this.layoutCorrected = createBox();
this.isLayoutDirty = false;
this.projectionDelta = undefined;
this.notifyListeners("measure", this.layout.actual);
(_a = this.options.visualElement) === null || _a === void 0 ? void 0 : _a.notifyLayoutMeasure(this.layout.actual, prevLayout === null || prevLayout === void 0 ? void 0 : prevLayout.actual);
};
ProjectionNode.prototype.updateScroll = function () {
if (this.options.layoutScroll && this.instance) {
this.isScrollRoot = checkIsScrollRoot(this.instance);
this.scroll = measureScroll(this.instance);
}
};
ProjectionNode.prototype.resetTransform = function () {
var _a;
if (!resetTransform)
return;
var isResetRequested = this.isLayoutDirty || this.shouldResetTransform;
var hasProjection = this.projectionDelta && !isDeltaZero(this.projectionDelta);
var transformTemplate = (_a = this.options.visualElement) === null || _a === void 0 ? void 0 : _a.getProps().transformTemplate;
var transformTemplateValue = transformTemplate === null || transformTemplate === void 0 ? void 0 : transformTemplate(this.latestValues, "");
var transformTemplateHasChanged = transformTemplateValue !== this.prevTransformTemplateValue;
if (isResetRequested &&
(hasProjection ||
hasTransform(this.latestValues) ||
transformTemplateHasChanged)) {
resetTransform(this.instance, transformTemplateValue);
this.shouldResetTransform = false;
this.scheduleRender();
}
};
ProjectionNode.prototype.measure = function () {
var visualElement = this.options.visualElement;
if (!visualElement)
return createBox();
var box = visualElement.measureViewportBox();
// Remove viewport scroll to give page-relative coordinates
var scroll = this.root.scroll;
if (scroll) {
translateAxis(box.x, scroll.x);
translateAxis(box.y, scroll.y);
}
return box;
};
ProjectionNode.prototype.removeElementScroll = function (box) {
var boxWithoutScroll = createBox();
copyBoxInto(boxWithoutScroll, box);
/**
* Performance TODO: Keep a cumulative scroll offset down the tree
* rather than loop back up the path.
*/
for (var i = 0; i < this.path.length; i++) {
var node = this.path[i];
var scroll_1 = node.scroll, options = node.options, isScrollRoot = node.isScrollRoot;
if (node !== this.root && scroll_1 && options.layoutScroll) {
/**
* If this is a new scroll root, we want to remove all previous scrolls
* from the viewport box.
*/
if (isScrollRoot) {
copyBoxInto(boxWithoutScroll, box);
var rootScroll = this.root.scroll;
/**
* Undo the application of page scroll that was originally added
* to the measured bounding box.
*/
if (rootScroll) {
translateAxis(boxWithoutScroll.x, -rootScroll.x);
translateAxis(boxWithoutScroll.y, -rootScroll.y);
}
}
translateAxis(boxWithoutScroll.x, scroll_1.x);
translateAxis(boxWithoutScroll.y, scroll_1.y);
}
}
return boxWithoutScroll;
};
ProjectionNode.prototype.applyTransform = function (box, transformOnly) {
if (transformOnly === void 0) { transformOnly = false; }
var withTransforms = createBox();
copyBoxInto(withTransforms, box);
for (var i = 0; i < this.path.length; i++) {
var node = this.path[i];
if (!transformOnly &&
node.options.layoutScroll &&
node.scroll &&
node !== node.root) {
transformBox(withTransforms, {
x: -node.scroll.x,
y: -node.scroll.y,
});
}
if (!hasTransform(node.latestValues))
continue;
transformBox(withTransforms, node.latestValues);
}
if (hasTransform(this.latestValues)) {
transformBox(withTransforms, this.latestValues);
}
return withTransforms;
};
ProjectionNode.prototype.removeTransform = function (box) {
var _a;
var boxWithoutTransform = createBox();
copyBoxInto(boxWithoutTransform, box);
for (var i = 0; i < this.path.length; i++) {
var node = this.path[i];
if (!node.instance)
continue;
if (!hasTransform(node.latestValues))
continue;
hasScale(node.latestValues) && node.updateSnapshot();
var sourceBox = createBox();
var nodeBox = node.measure();
copyBoxInto(sourceBox, nodeBox);
removeBoxTransforms(boxWithoutTransform, node.latestValues, (_a = node.snapshot) === null || _a === void 0 ? void 0 : _a.layout, sourceBox);
}
if (hasTransform(this.latestValues)) {
removeBoxTransforms(boxWithoutTransform, this.latestValues);
}
return boxWithoutTransform;
};
/**
*
*/
ProjectionNode.prototype.setTargetDelta = function (delta) {
this.targetDelta = delta;
this.root.scheduleUpdateProjection();
};
ProjectionNode.prototype.setOptions = function (options) {
var _a;
this.options = tslib.__assign(tslib.__assign(tslib.__assign({}, this.options), options), { crossfade: (_a = options.crossfade) !== null && _a !== void 0 ? _a : true });
};
ProjectionNode.prototype.clearMeasurements = function () {
this.scroll = undefined;
this.layout = undefined;
this.snapshot = undefined;
this.prevTransformTemplateValue = undefined;
this.targetDelta = undefined;
this.target = undefined;
this.isLayoutDirty = false;
};
/**
* Frame calculations
*/
ProjectionNode.prototype.resolveTargetDelta = function () {
var _a;
var _b = this.options, layout = _b.layout, layoutId = _b.layoutId;
/**
* If we have no layout, we can't perform projection, so early return
*/
if (!this.layout || !(layout || layoutId))
return;
/**
* If we don't have a targetDelta but do have a layout, we can attempt to resolve
* a relativeParent. This will allow a component to perform scale correction
* even if no animation has started.
*/
// TODO If this is unsuccessful this currently happens every frame
if (!this.targetDelta && !this.relativeTarget) {
// TODO: This is a semi-repetition of further down this function, make DRY
this.relativeParent = this.getClosestProjectingParent();
if (this.relativeParent && this.relativeParent.layout) {
this.relativeTarget = createBox();
this.relativeTargetOrigin = createBox();
calcRelativePosition(this.relativeTargetOrigin, this.layout.actual, this.relativeParent.layout.actual);
copyBoxInto(this.relativeTarget, this.relativeTargetOrigin);
}
}
/**
* If we have no relative target or no target delta our target isn't valid
* for this frame.
*/
if (!this.relativeTarget && !this.targetDelta)
return;
/**
* Lazy-init target data structure
*/
if (!this.target) {
this.target = createBox();
this.targetWithTransforms = createBox();
}
/**
* If we've got a relative box for this component, resolve it into a target relative to the parent.
*/
if (this.relativeTarget &&
this.relativeTargetOrigin &&
((_a = this.relativeParent) === null || _a === void 0 ? void 0 : _a.target)) {
calcRelativeBox(this.target, this.relativeTarget, this.relativeParent.target);
/**
* If we've only got a targetDelta, resolve it into a target
*/
}
else if (this.targetDelta) {
if (Boolean(this.resumingFrom)) {
// TODO: This is creating a new object every frame
this.target = this.applyTransform(this.layout.actual);
}
else {
copyBoxInto(this.target, this.layout.actual);
}
applyBoxDelta(this.target, this.targetDelta);
}
else {
/**
* If no target, use own layout as target
*/
copyBoxInto(this.target, this.layout.actual);
}
/**
* If we've been told to attempt to resolve a relative target, do so.
*/
if (this.attemptToResolveRelativeTarget) {
this.attemptToResolveRelativeTarget = false;
this.relativeParent = this.getClosestProjectingParent();
if (this.relativeParent &&
Boolean(this.relativeParent.resumingFrom) ===
Boolean(this.resumingFrom) &&
!this.relativeParent.options.layoutScroll &&
this.relativeParent.target) {
this.relativeTarget = createBox();
this.relativeTargetOrigin = createBox();
calcRelativePosition(this.relativeTargetOrigin, this.target, this.relativeParent.target);
copyBoxInto(this.relativeTarget, this.relativeTargetOrigin);
}
}
};
ProjectionNode.prototype.getClosestProjectingParent = function () {
if (!this.parent || hasTransform(this.parent.latestValues))
return undefined;
if ((this.parent.relativeTarget || this.parent.targetDelta) &&
this.parent.layout) {
return this.parent;
}
else {
return this.parent.getClosestProjectingParent();
}
};
ProjectionNode.prototype.calcProjection = function () {
var _a;
var _b = this.options, layout = _b.layout, layoutId = _b.layoutId;
/**
* If this section of the tree isn't animating we can
* delete our target sources for the following frame.
*/
this.isTreeAnimating = Boolean(((_a = this.parent) === null || _a === void 0 ? void 0 : _a.isTreeAnimating) ||
this.currentAnimation ||
this.pendingAnimation);
if (!this.isTreeAnimating) {
this.targetDelta = this.relativeTarget = undefined;
}
if (!this.layout || !(layout || layoutId))
return;
var lead = this.getLead();
/**
* Reset the corrected box with the latest values from box, as we're then going
* to perform mutative operations on it.
*/
copyBoxInto(this.layoutCorrected, this.layout.actual);
/**
* Apply all the parent deltas to this box to produce the corrected box. This
* is the layout box, as it will appear on screen as a result of the transforms of its parents.
*/
applyTreeDeltas(this.layoutCorrected, this.treeScale, this.path, Boolean(this.resumingFrom) || this !== lead);
var target = lead.target;
if (!target)
return;
if (!this.projectionDelta) {
this.projectionDelta = createDelta();
this.projectionDeltaWithTransform = createDelta();
}
var prevTreeScaleX = this.treeScale.x;
var prevTreeScaleY = this.treeScale.y;
var prevProjectionTransform = this.projectionTransform;
/**
* Update the delta between the corrected box and the target box before user-set transforms were applied.
* This will allow us to calculate the corrected borderRadius and boxShadow to compensate
* for our layout reprojection, but still allow them to be scaled correctly by the user.
* It might be that to simplify this we may want to accept that user-set scale is also corrected
* and we wouldn't have to keep and calc both deltas, OR we could support a user setting
* to allow people to choose whether these styles are corrected based on just the
* layout reprojection or the final bounding box.
*/
calcBoxDelta(this.projectionDelta, this.layoutCorrected, target, this.latestValues);
this.projectionTransform = buildProjectionTransform(this.projectionDelta, this.treeScale);
if (this.projectionTransform !== prevProjectionTransform ||
this.treeScale.x !== prevTreeScaleX ||
this.treeScale.y !== prevTreeScaleY) {
this.hasProjected = true;
this.scheduleRender();
this.notifyListeners("projectionUpdate", target);
}
};
ProjectionNode.prototype.hide = function () {
this.isVisible = false;
// TODO: Schedule render
};
ProjectionNode.prototype.show = function () {
this.isVisible = true;
// TODO: Schedule render
};
ProjectionNode.prototype.scheduleRender = function (notifyAll) {
var _a, _b, _c;
if (notifyAll === void 0) { notifyAll = true; }
(_b = (_a = this.options).scheduleRender) === null || _b === void 0 ? void 0 : _b.call(_a);
notifyAll && ((_c = this.getStack()) === null || _c === void 0 ? void 0 : _c.scheduleRender());
if (this.resumingFrom && !this.resumingFrom.instance) {
this.resumingFrom = undefined;
}
};
ProjectionNode.prototype.setAnimationOrigin = function (delta, hasOnlyRelativeTargetChanged) {
var _this = this;
var _a;
if (hasOnlyRelativeTargetChanged === void 0) { hasOnlyRelativeTargetChanged = false; }
var snapshot = this.snapshot;
var snapshotLatestValues = (snapshot === null || snapshot === void 0 ? void 0 : snapshot.latestValues) || {};
var mixedValues = tslib.__assign({}, this.latestValues);
var targetDelta = createDelta();
this.relativeTarget = this.relativeTargetOrigin = undefined;
this.attemptToResolveRelativeTarget = !hasOnlyRelativeTargetChanged;
var relativeLayout = createBox();
var isSharedLayoutAnimation = snapshot === null || snapshot === void 0 ? void 0 : snapshot.isShared;
var isOnlyMember = (((_a = this.getStack()) === null || _a === void 0 ? void 0 : _a.members.length) || 0) <= 1;
var shouldCrossfadeOpacity = Boolean(isSharedLayoutAnimation &&
!isOnlyMember &&
this.options.crossfade === true &&
!this.path.some(hasOpacityCrossfade));
this.animationProgress = 0;
this.mixTargetDelta = function (latest) {
var _a;
var progress = latest / 1000;
mixAxisDelta(targetDelta.x, delta.x, progress);
mixAxisDelta(targetDelta.y, delta.y, progress);
_this.setTargetDelta(targetDelta);
if (_this.relativeTarget &&
_this.relativeTargetOrigin &&
_this.layout &&
((_a = _this.relativeParent) === null || _a === void 0 ? void 0 : _a.layout)) {
calcRelativePosition(relativeLayout, _this.layout.actual, _this.relativeParent.layout.actual);
mixBox(_this.relativeTarget, _this.relativeTargetOrigin, relativeLayout, progress);
}
if (isSharedLayoutAnimation) {
_this.animationValues = mixedValues;
mixValues(mixedValues, snapshotLatestValues, _this.latestValues, progress, shouldCrossfadeOpacity, isOnlyMember);
}
_this.root.scheduleUpdateProjection();
_this.scheduleRender();
_this.animationProgress = progress;
};
this.mixTargetDelta(0);
};
ProjectionNode.prototype.startAnimation = function (options) {
var _this = this;
var _a, _b;
this.notifyListeners("animationStart");
(_a = this.currentAnimation) === null || _a === void 0 ? void 0 : _a.stop();
if (this.resumingFrom) {
(_b = this.resumingFrom.currentAnimation) === null || _b === void 0 ? void 0 : _b.stop();
}
if (this.pendingAnimation) {
sync.cancelSync.update(this.pendingAnimation);
this.pendingAnimation = undefined;
}
/**
* Start the animation in the next frame to have a frame with progress 0,
* where the target is the same as when the animation started, so we can
* calculate the relative positions correctly for instant transitions.
*/
this.pendingAnimation = sync__default["default"].update(function () {
globalProjectionState.hasAnimatedSinceResize = true;
_this.currentAnimation = animate(0, animationTarget, tslib.__assign(tslib.__assign({}, options), { onUpdate: function (latest) {
var _a;
_this.mixTargetDelta(latest);
(_a = options.onUpdate) === null || _a === void 0 ? void 0 : _a.call(options, latest);
}, onComplete: function () {
var _a;
(_a = options.onComplete) === null || _a === void 0 ? void 0 : _a.call(options);
_this.completeAnimation();
} }));
if (_this.resumingFrom) {
_this.resumingFrom.currentAnimation = _this.currentAnimation;
}
_this.pendingAnimation = undefined;
});
};
ProjectionNode.prototype.completeAnimation = function () {
var _a;
if (this.resumingFrom) {
this.resumingFrom.currentAnimation = undefined;
this.resumingFrom.preserveOpacity = undefined;
}
(_a = this.getStack()) === null || _a === void 0 ? void 0 : _a.exitAnimationComplete();
this.resumingFrom =
this.currentAnimation =
this.animationValues =
undefined;
this.notifyListeners("animationComplete");
};
ProjectionNode.prototype.finishAnimation = function () {
var _a;
if (this.currentAnimation) {
(_a = this.mixTargetDelta) === null || _a === void 0 ? void 0 : _a.call(this, animationTarget);
this.currentAnimation.stop();
}
this.completeAnimation();
};
ProjectionNode.prototype.applyTransformsToTarget = function () {
var _a = this.getLead(), targetWithTransforms = _a.targetWithTransforms, target = _a.target, layout = _a.layout, latestValues = _a.latestValues;
if (!targetWithTransforms || !target || !layout)
return;
copyBoxInto(targetWithTransforms, target);
/**
* Apply the latest user-set transforms to the targetBox to produce the targetBoxFinal.
* This is the final box that we will then project into by calculating a transform delta and
* applying it to the corrected box.
*/
transformBox(targetWithTransforms, latestValues);
/**
* Update the delta between the corrected box and the final target box, after
* user-set transforms are applied to it. This will be used by the renderer to
* create a transform style that will reproject the element from its actual layout
* into the desired bounding box.
*/
calcBoxDelta(this.projectionDeltaWithTransform, this.layoutCorrected, targetWithTransforms, latestValues);
};
ProjectionNode.prototype.registerSharedNode = function (layoutId, node) {
var _a, _b, _c;
if (!this.sharedNodes.has(layoutId)) {
this.sharedNodes.set(layoutId, new NodeStack());
}
var stack = this.sharedNodes.get(layoutId);
stack.add(node);
node.promote({
transition: (_a = node.options.initialPromotionConfig) === null || _a === void 0 ? void 0 : _a.transition,
preserveFollowOpacity: (_c = (_b = node.options.initialPromotionConfig) === null || _b === void 0 ? void 0 : _b.shouldPreserveFollowOpacity) === null || _c === void 0 ? void 0 : _c.call(_b, node),
});
};
ProjectionNode.prototype.isLead = function () {
var stack = this.getStack();
return stack ? stack.lead === this : true;
};
ProjectionNode.prototype.getLead = function () {
var _a;
var layoutId = this.options.layoutId;
return layoutId ? ((_a = this.getStack()) === null || _a === void 0 ? void 0 : _a.lead) || this : this;
};
ProjectionNode.prototype.getPrevLead = function () {
var _a;
var layoutId = this.options.layoutId;
return layoutId ? (_a = this.getStack()) === null || _a === void 0 ? void 0 : _a.prevLead : undefined;
};
ProjectionNode.prototype.getStack = function () {
var layoutId = this.options.layoutId;
if (layoutId)
return this.root.sharedNodes.get(layoutId);
};
ProjectionNode.prototype.promote = function (_a) {
var _b = _a === void 0 ? {} : _a, needsReset = _b.needsReset, transition = _b.transition, preserveFollowOpacity = _b.preserveFollowOpacity;
var stack = this.getStack();
if (stack)
stack.promote(this, preserveFollowOpacity);
if (needsReset) {
this.projectionDelta = undefined;
this.needsReset = true;
}
if (transition)
this.setOptions({ transition: transition });
};
ProjectionNode.prototype.relegate = function () {
var stack = this.getStack();
if (stack) {
return stack.relegate(this);
}
else {
return false;
}
};
ProjectionNode.prototype.resetRotation = function () {
var visualElement = this.options.visualElement;
if (!visualElement)
return;
// If there's no detected rotation values, we can early return without a forced render.
var hasRotate = false;
// Keep a record of all the values we've reset
var resetValues = {};
// Check the rotate value of all axes and reset to 0
for (var i = 0; i < transformAxes.length; i++) {
var axis = transformAxes[i];
var key = "rotate" + axis;
// If this rotation doesn't exist as a motion value, then we don't
// need to reset it
if (!visualElement.getStaticValue(key)) {
continue;
}
hasRotate = true;
// Record the rotation and then temporarily set it to 0
resetValues[key] = visualElement.getStaticValue(key);
visualElement.setStaticValue(key, 0);
}
// If there's no rotation values, we don't need to do any more.
if (!hasRotate)
return;
// Force a render of this element to apply the transform with all rotations
// set to 0.
visualElement === null || visualElement === void 0 ? void 0 : visualElement.syncRender();
// Put back all the values we reset
for (var key in resetValues) {
visualElement.setStaticValue(key, resetValues[key]);
}
// Schedule a render for the next frame. This ensures we won't visually
// see the element with the reset rotate value applied.
visualElement.scheduleRender();
};
ProjectionNode.prototype.getProjectionStyles = function (styleProp) {
var _a, _b, _c, _d, _e, _f;
if (styleProp === void 0) { styleProp = {}; }
// TODO: Return lifecycle-persistent object
var styles = {};
if (!this.instance || this.isSVG)
return styles;
if (!this.isVisible) {
return { visibility: "hidden" };
}
else {
styles.visibility = "";
}
var transformTemplate = (_a = this.options.visualElement) === null || _a === void 0 ? void 0 : _a.getProps().transformTemplate;
if (this.needsReset) {
this.needsReset = false;
styles.opacity = "";
styles.pointerEvents =
resolveMotionValue(styleProp.pointerEvents) || "";
styles.transform = transformTemplate
? transformTemplate(this.latestValues, "")
: "none";
return styles;
}
var lead = this.getLead();
if (!this.projectionDelta || !this.layout || !lead.target) {
var emptyStyles = {};
if (this.options.layoutId) {
emptyStyles.opacity = (_b = this.latestValues.opacity) !== null && _b !== void 0 ? _b : 1;
emptyStyles.pointerEvents =
resolveMotionValue(styleProp.pointerEvents) || "";
}
if (this.hasProjected && !hasTransform(this.latestValues)) {
emptyStyles.transform = transformTemplate
? transformTemplate({}, "")
: "none";
this.hasProjected = false;
}
return emptyStyles;
}
var valuesToRender = lead.animationValues || lead.latestValues;
this.applyTransformsToTarget();
styles.transform = buildProjectionTransform(this.projectionDeltaWithTransform, this.treeScale, valuesToRender);
if (transformTemplate) {
styles.transform = transformTemplate(valuesToRender, styles.transform);
}
var _g = this.projectionDelta, x = _g.x, y = _g.y;
styles.transformOrigin = "".concat(x.origin * 100, "% ").concat(y.origin * 100, "% 0");
if (lead.animationValues) {
/**
* If the lead component is animating, assign this either the entering/leaving
* opacity
*/
styles.opacity =
lead === this
? (_d = (_c = valuesToRender.opacity) !== null && _c !== void 0 ? _c : this.latestValues.opacity) !== null && _d !== void 0 ? _d : 1
: this.preserveOpacity
? this.latestValues.opacity
: valuesToRender.opacityExit;
}
else {
/**
* Or we're not animating at all, set the lead component to its actual
* opacity and other components to hidden.
*/
styles.opacity =
lead === this
? (_e = valuesToRender.opacity) !== null && _e !== void 0 ? _e : ""
: (_f = valuesToRender.opacityExit) !== null && _f !== void 0 ? _f : 0;
}
/**
* Apply scale correction
*/
for (var key in scaleCorrectors) {
if (valuesToRender[key] === undefined)
continue;
var _h = scaleCorrectors[key], correct = _h.correct, applyTo = _h.applyTo;
var corrected = correct(valuesToRender[key], lead);
if (applyTo) {
var num = applyTo.length;
for (var i = 0; i < num; i++) {
styles[applyTo[i]] = corrected;
}
}
else {
styles[key] = corrected;
}
}
/**
* Disable pointer events on follow components. This is to ensure
* that if a follow component covers a lead component it doesn't block
* pointer events on the lead.
*/
if (this.options.layoutId) {
styles.pointerEvents =
lead === this
? resolveMotionValue(styleProp.pointerEvents) || ""
: "none";
}
return styles;
};
ProjectionNode.prototype.clearSnapshot = function () {
this.resumeFrom = this.snapshot = undefined;
};
// Only run on root
ProjectionNode.prototype.resetTree = function () {
this.root.nodes.forEach(function (node) { var _a; return (_a = node.currentAnimation) === null || _a === void 0 ? void 0 : _a.stop(); });
this.root.nodes.forEach(clearMeasurements);
this.root.sharedNodes.clear();
};
return ProjectionNode;
}());
}
function updateLayout(node) {
node.updateLayout();
}
function notifyLayoutUpdate(node) {
var _a, _b, _c, _d;
var snapshot = (_b = (_a = node.resumeFrom) === null || _a === void 0 ? void 0 : _a.snapshot) !== null && _b !== void 0 ? _b : node.snapshot;
if (node.isLead() &&
node.layout &&
snapshot &&
node.hasListeners("didUpdate")) {
var _e = node.layout, layout_1 = _e.actual, measuredLayout = _e.measured;
// TODO Maybe we want to also resize the layout snapshot so we don't trigger
// animations for instance if layout="size" and an element has only changed position
if (node.options.animationType === "size") {
eachAxis(function (axis) {
var axisSnapshot = snapshot.isShared
? snapshot.measured[axis]
: snapshot.layout[axis];
var length = calcLength(axisSnapshot);
axisSnapshot.min = layout_1[axis].min;
axisSnapshot.max = axisSnapshot.min + length;
});
}
else if (node.options.animationType === "position") {
eachAxis(function (axis) {
var axisSnapshot = snapshot.isShared
? snapshot.measured[axis]
: snapshot.layout[axis];
var length = calcLength(layout_1[axis]);
axisSnapshot.max = axisSnapshot.min + length;
});
}
var layoutDelta = createDelta();
calcBoxDelta(layoutDelta, layout_1, snapshot.layout);
var visualDelta = createDelta();
if (snapshot.isShared) {
calcBoxDelta(visualDelta, node.applyTransform(measuredLayout, true), snapshot.measured);
}
else {
calcBoxDelta(visualDelta, layout_1, snapshot.layout);
}
var hasLayoutChanged = !isDeltaZero(layoutDelta);
var hasRelativeTargetChanged = false;
if (!node.resumeFrom) {
node.relativeParent = node.getClosestProjectingParent();
/**
* If the relativeParent is itself resuming from a different element then
* the relative snapshot is not relavent
*/
if (node.relativeParent && !node.relativeParent.resumeFrom) {
var _f = node.relativeParent, parentSnapshot = _f.snapshot, parentLayout = _f.layout;
if (parentSnapshot && parentLayout) {
var relativeSnapshot = createBox();
calcRelativePosition(relativeSnapshot, snapshot.layout, parentSnapshot.layout);
var relativeLayout = createBox();
calcRelativePosition(relativeLayout, layout_1, parentLayout.actual);
if (!boxEquals(relativeSnapshot, relativeLayout)) {
hasRelativeTargetChanged = true;
}
}
}
}
node.notifyListeners("didUpdate", {
layout: layout_1,
snapshot: snapshot,
delta: visualDelta,
layoutDelta: layoutDelta,
hasLayoutChanged: hasLayoutChanged,
hasRelativeTargetChanged: hasRelativeTargetChanged,
});
}
else if (node.isLead()) {
(_d = (_c = node.options).onExitComplete) === null || _d === void 0 ? void 0 : _d.call(_c);
}
/**
* Clearing transition
* TODO: Investigate why this transition is being passed in as {type: false } from Framer
* and why we need it at all
*/
node.options.transition = undefined;
}
function clearSnapshot(node) {
node.clearSnapshot();
}
function clearMeasurements(node) {
node.clearMeasurements();
}
function resetTransformStyle(node) {
var visualElement = node.options.visualElement;
if (visualElement === null || visualElement === void 0 ? void 0 : visualElement.getProps().onBeforeLayoutMeasure) {
visualElement.notifyBeforeLayoutMeasure();
}
node.resetTransform();
}
function finishAnimation(node) {
node.finishAnimation();
node.targetDelta = node.relativeTarget = node.target = undefined;
}
function resolveTargetDelta(node) {
node.resolveTargetDelta();
}
function calcProjection(node) {
node.calcProjection();
}
function resetRotation(node) {
node.resetRotation();
}
function removeLeadSnapshots(stack) {
stack.removeLeadSnapshot();
}
function mixAxisDelta(output, delta, p) {
output.translate = popmotion.mix(delta.translate, 0, p);
output.scale = popmotion.mix(delta.scale, 1, p);
output.origin = delta.origin;
output.originPoint = delta.originPoint;
}
function mixAxis(output, from, to, p) {
output.min = popmotion.mix(from.min, to.min, p);
output.max = popmotion.mix(from.max, to.max, p);
}
function mixBox(output, from, to, p) {
mixAxis(output.x, from.x, to.x, p);
mixAxis(output.y, from.y, to.y, p);
}
function hasOpacityCrossfade(node) {
return (node.animationValues && node.animationValues.opacityExit !== undefined);
}
var defaultLayoutTransition = {
duration: 0.45,
ease: [0.4, 0, 0.1, 1],
};
function mountNodeEarly(node, id) {
/**
* Rather than searching the DOM from document we can search the
* path for the deepest mounted ancestor and search from there
*/
var searchNode = node.root;
for (var i = node.path.length - 1; i >= 0; i--) {
if (Boolean(node.path[i].instance)) {
searchNode = node.path[i];
break;
}
}
var searchElement = searchNode && searchNode !== node.root ? searchNode.instance : document;
var element = searchElement.querySelector("[data-projection-id=\"".concat(id, "\"]"));
if (element)
node.mount(element, true);
}
function roundAxis(axis) {
axis.min = Math.round(axis.min);
axis.max = Math.round(axis.max);
}
function roundBox(box) {
roundAxis(box.x);
roundAxis(box.y);
}
var DocumentProjectionNode = createProjectionNode({
attachResizeListener: function (ref, notify) { return addDomEvent(ref, "resize", notify); },
measureScroll: function () { return ({
x: document.documentElement.scrollLeft || document.body.scrollLeft,
y: document.documentElement.scrollTop || document.body.scrollTop,
}); },
checkIsScrollRoot: function () { return true; },
});
var rootProjectionNode = {
current: undefined,
};
var HTMLProjectionNode = createProjectionNode({
measureScroll: function (instance) { return ({
x: instance.scrollLeft,
y: instance.scrollTop,
}); },
defaultParent: function () {
if (!rootProjectionNode.current) {
var documentNode = new DocumentProjectionNode(0, {});
documentNode.mount(window);
documentNode.setOptions({ layoutScroll: true });
rootProjectionNode.current = documentNode;
}
return rootProjectionNode.current;
},
resetTransform: function (instance, value) {
instance.style.transform = value !== null && value !== void 0 ? value : "none";
},
checkIsScrollRoot: function (instance) {
return Boolean(window.getComputedStyle(instance).position === "fixed");
},
});
var featureBundle = tslib.__assign(tslib.__assign(tslib.__assign(tslib.__assign({}, animations), gestureAnimations), drag), layoutFeatures);
/**
* HTML & SVG components, optimised for use with gestures and animation. These can be used as
* drop-in replacements for any HTML & SVG component, all CSS & SVG properties are supported.
*
* @public
*/
var motion = /*@__PURE__*/ createMotionProxy(function (Component, config) {
return createDomMotionConfig(Component, config, featureBundle, createDomVisualElement, HTMLProjectionNode);
});
/**
* Create a DOM `motion` component with the provided string. This is primarily intended
* as a full alternative to `motion` for consumers who have to support environments that don't
* support `Proxy`.
*
* ```javascript
* import { createDomMotionComponent } from "framer-motion"
*
* const motion = {
* div: createDomMotionComponent('div')
* }
* ```
*
* @public
*/
function createDomMotionComponent(key) {
return createMotionComponent(createDomMotionConfig(key, { forwardMotionProps: false }, featureBundle, createDomVisualElement, HTMLProjectionNode));
}
/**
* @public
*/
var m = createMotionProxy(createDomMotionConfig);
function useIsMounted() {
var isMounted = React.useRef(false);
useIsomorphicLayoutEffect(function () {
isMounted.current = true;
return function () {
isMounted.current = false;
};
}, []);
return isMounted;
}
function useForceUpdate() {
var isMounted = useIsMounted();
var _a = tslib.__read(React.useState(0), 2), forcedRenderCount = _a[0], setForcedRenderCount = _a[1];
var forceRender = React.useCallback(function () {
isMounted.current && setForcedRenderCount(forcedRenderCount + 1);
}, [forcedRenderCount]);
/**
* Defer this to the end of the next animation frame in case there are multiple
* synchronous calls.
*/
var deferredForceRender = React.useCallback(function () { return sync__default["default"].postRender(forceRender); }, [forceRender]);
return [deferredForceRender, forcedRenderCount];
}
var PresenceChild = function (_a) {
var children = _a.children, initial = _a.initial, isPresent = _a.isPresent, onExitComplete = _a.onExitComplete, custom = _a.custom, presenceAffectsLayout = _a.presenceAffectsLayout;
var presenceChildren = useConstant(newChildrenMap);
var id = useId();
var context = React.useMemo(function () { return ({
id: id,
initial: initial,
isPresent: isPresent,
custom: custom,
onExitComplete: function (childId) {
var e_1, _a;
presenceChildren.set(childId, true);
try {
for (var _b = tslib.__values(presenceChildren.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
var isComplete = _c.value;
if (!isComplete)
return; // can stop searching when any is incomplete
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_1) throw e_1.error; }
}
onExitComplete === null || onExitComplete === void 0 ? void 0 : onExitComplete();
},
register: function (childId) {
presenceChildren.set(childId, false);
return function () { return presenceChildren.delete(childId); };
},
}); },
/**
* If the presence of a child affects the layout of the components around it,
* we want to make a new context value to ensure they get re-rendered
* so they can detect that layout change.
*/
presenceAffectsLayout ? undefined : [isPresent]);
React.useMemo(function () {
presenceChildren.forEach(function (_, key) { return presenceChildren.set(key, false); });
}, [isPresent]);
/**
* If there's no `motion` components to fire exit animations, we want to remove this
* component immediately.
*/
React__namespace.useEffect(function () {
!isPresent && !presenceChildren.size && (onExitComplete === null || onExitComplete === void 0 ? void 0 : onExitComplete());
}, [isPresent]);
return (React__namespace.createElement(PresenceContext.Provider, { value: context }, children));
};
function newChildrenMap() {
return new Map();
}
var getChildKey = function (child) { return child.key || ""; };
function updateChildLookup(children, allChildren) {
children.forEach(function (child) {
var key = getChildKey(child);
allChildren.set(key, child);
});
}
function onlyElements(children) {
var filtered = [];
// We use forEach here instead of map as map mutates the component key by preprending `.$`
React.Children.forEach(children, function (child) {
if (React.isValidElement(child))
filtered.push(child);
});
return filtered;
}
/**
* `AnimatePresence` enables the animation of components that have been removed from the tree.
*
* When adding/removing more than a single child, every child **must** be given a unique `key` prop.
*
* Any `motion` components that have an `exit` property defined will animate out when removed from
* the tree.
*
* ```jsx
* import { motion, AnimatePresence } from 'framer-motion'
*
* export const Items = ({ items }) => (
* <AnimatePresence>
* {items.map(item => (
* <motion.div
* key={item.id}
* initial={{ opacity: 0 }}
* animate={{ opacity: 1 }}
* exit={{ opacity: 0 }}
* />
* ))}
* </AnimatePresence>
* )
* ```
*
* You can sequence exit animations throughout a tree using variants.
*
* If a child contains multiple `motion` components with `exit` props, it will only unmount the child
* once all `motion` components have finished animating out. Likewise, any components using
* `usePresence` all need to call `safeToRemove`.
*
* @public
*/
var AnimatePresence = function (_a) {
var children = _a.children, custom = _a.custom, _b = _a.initial, initial = _b === void 0 ? true : _b, onExitComplete = _a.onExitComplete, exitBeforeEnter = _a.exitBeforeEnter, _c = _a.presenceAffectsLayout, presenceAffectsLayout = _c === void 0 ? true : _c;
// We want to force a re-render once all exiting animations have finished. We
// either use a local forceRender function, or one from a parent context if it exists.
var _d = tslib.__read(useForceUpdate(), 1), forceRender = _d[0];
var forceRenderLayoutGroup = React.useContext(LayoutGroupContext).forceRender;
if (forceRenderLayoutGroup)
forceRender = forceRenderLayoutGroup;
var isMounted = useIsMounted();
// Filter out any children that aren't ReactElements. We can only track ReactElements with a props.key
var filteredChildren = onlyElements(children);
var childrenToRender = filteredChildren;
var exiting = new Set();
// Keep a living record of the children we're actually rendering so we
// can diff to figure out which are entering and exiting
var presentChildren = React.useRef(childrenToRender);
// A lookup table to quickly reference components by key
var allChildren = React.useRef(new Map()).current;
// If this is the initial component render, just deal with logic surrounding whether
// we play onMount animations or not.
var isInitialRender = React.useRef(true);
useIsomorphicLayoutEffect(function () {
isInitialRender.current = false;
updateChildLookup(filteredChildren, allChildren);
presentChildren.current = childrenToRender;
});
useUnmountEffect(function () {
isInitialRender.current = true;
allChildren.clear();
exiting.clear();
});
if (isInitialRender.current) {
return (React__namespace.createElement(React__namespace.Fragment, null, childrenToRender.map(function (child) { return (React__namespace.createElement(PresenceChild, { key: getChildKey(child), isPresent: true, initial: initial ? undefined : false, presenceAffectsLayout: presenceAffectsLayout }, child)); })));
}
// If this is a subsequent render, deal with entering and exiting children
childrenToRender = tslib.__spreadArray([], tslib.__read(childrenToRender), false);
// Diff the keys of the currently-present and target children to update our
// exiting list.
var presentKeys = presentChildren.current.map(getChildKey);
var targetKeys = filteredChildren.map(getChildKey);
// Diff the present children with our target children and mark those that are exiting
var numPresent = presentKeys.length;
for (var i = 0; i < numPresent; i++) {
var key = presentKeys[i];
if (targetKeys.indexOf(key) === -1) {
exiting.add(key);
}
}
// If we currently have exiting children, and we're deferring rendering incoming children
// until after all current children have exiting, empty the childrenToRender array
if (exitBeforeEnter && exiting.size) {
childrenToRender = [];
}
// Loop through all currently exiting components and clone them to overwrite `animate`
// with any `exit` prop they might have defined.
exiting.forEach(function (key) {
// If this component is actually entering again, early return
if (targetKeys.indexOf(key) !== -1)
return;
var child = allChildren.get(key);
if (!child)
return;
var insertionIndex = presentKeys.indexOf(key);
var onExit = function () {
allChildren.delete(key);
exiting.delete(key);
// Remove this child from the present children
var removeIndex = presentChildren.current.findIndex(function (presentChild) { return presentChild.key === key; });
presentChildren.current.splice(removeIndex, 1);
// Defer re-rendering until all exiting children have indeed left
if (!exiting.size) {
presentChildren.current = filteredChildren;
if (isMounted.current === false)
return;
forceRender();
onExitComplete && onExitComplete();
}
};
childrenToRender.splice(insertionIndex, 0, React__namespace.createElement(PresenceChild, { key: getChildKey(child), isPresent: false, onExitComplete: onExit, custom: custom, presenceAffectsLayout: presenceAffectsLayout }, child));
});
// Add `MotionContext` even to children that don't need it to ensure we're rendering
// the same tree between renders
childrenToRender = childrenToRender.map(function (child) {
var key = child.key;
return exiting.has(key) ? (child) : (React__namespace.createElement(PresenceChild, { key: getChildKey(child), isPresent: true, presenceAffectsLayout: presenceAffectsLayout }, child));
});
if (env !== "production" &&
exitBeforeEnter &&
childrenToRender.length > 1) {
console.warn("You're attempting to animate multiple children within AnimatePresence, but its exitBeforeEnter prop is set to true. This will lead to odd visual behaviour.");
}
return (React__namespace.createElement(React__namespace.Fragment, null, exiting.size
? childrenToRender
: childrenToRender.map(function (child) { return React.cloneElement(child); })));
};
/**
* @deprecated
*/
var DeprecatedLayoutGroupContext = React.createContext(null);
var notify = function (node) {
return !node.isLayoutDirty && node.willUpdate(false);
};
function nodeGroup() {
var nodes = new Set();
var subscriptions = new WeakMap();
var dirtyAll = function () { return nodes.forEach(notify); };
return {
add: function (node) {
nodes.add(node);
subscriptions.set(node, node.addEventListener("willUpdate", dirtyAll));
},
remove: function (node) {
var _a;
nodes.delete(node);
(_a = subscriptions.get(node)) === null || _a === void 0 ? void 0 : _a();
subscriptions.delete(node);
dirtyAll();
},
dirty: dirtyAll,
};
}
var shouldInheritGroup = function (inherit) { return inherit === true; };
var shouldInheritId = function (inherit) {
return shouldInheritGroup(inherit === true) || inherit === "id";
};
var LayoutGroup = function (_a) {
var _b, _c;
var children = _a.children, id = _a.id, inheritId = _a.inheritId, _d = _a.inherit, inherit = _d === void 0 ? true : _d;
// Maintain backwards-compatibility with inheritId until 7.0
if (inheritId !== undefined)
inherit = inheritId;
var layoutGroupContext = React.useContext(LayoutGroupContext);
var deprecatedLayoutGroupContext = React.useContext(DeprecatedLayoutGroupContext);
var _e = tslib.__read(useForceUpdate(), 2), forceRender = _e[0], key = _e[1];
var context = React.useRef(null);
var upstreamId = (_b = layoutGroupContext.id) !== null && _b !== void 0 ? _b : deprecatedLayoutGroupContext;
if (context.current === null) {
if (shouldInheritId(inherit) && upstreamId) {
id = id ? upstreamId + "-" + id : upstreamId;
}
context.current = {
id: id,
group: shouldInheritGroup(inherit)
? (_c = layoutGroupContext === null || layoutGroupContext === void 0 ? void 0 : layoutGroupContext.group) !== null && _c !== void 0 ? _c : nodeGroup()
: nodeGroup(),
};
}
var memoizedContext = React.useMemo(function () { return (tslib.__assign(tslib.__assign({}, context.current), { forceRender: forceRender })); }, [key]);
return (React__namespace.createElement(LayoutGroupContext.Provider, { value: memoizedContext }, children));
};
var id = 0;
var AnimateSharedLayout = function (_a) {
var children = _a.children;
React__namespace.useEffect(function () {
heyListen.warning(false, "AnimateSharedLayout is deprecated: https://www.framer.com/docs/guide-upgrade/##shared-layout-animations");
}, []);
return (React__namespace.createElement(LayoutGroup, { id: useConstant(function () { return "asl-".concat(id++); }) }, children));
};
/**
* `MotionConfig` is used to set configuration options for all children `motion` components.
*
* ```jsx
* import { motion, MotionConfig } from "framer-motion"
*
* export function App() {
* return (
* <MotionConfig transition={{ type: "spring" }}>
* <motion.div animate={{ x: 100 }} />
* </MotionConfig>
* )
* }
* ```
*
* @public
*/
function MotionConfig(_a) {
var children = _a.children, isValidProp = _a.isValidProp, config = tslib.__rest(_a, ["children", "isValidProp"]);
isValidProp && loadExternalIsValidProp(isValidProp);
/**
* Inherit props from any parent MotionConfig components
*/
config = tslib.__assign(tslib.__assign({}, React.useContext(MotionConfigContext)), config);
/**
* Don't allow isStatic to change between renders as it affects how many hooks
* motion components fire.
*/
config.isStatic = useConstant(function () { return config.isStatic; });
/**
* Creating a new config context object will re-render every `motion` component
* every time it renders. So we only want to create a new one sparingly.
*/
var context = React.useMemo(function () { return config; }, [JSON.stringify(config.transition), config.transformPagePoint, config.reducedMotion]);
return (React__namespace.createElement(MotionConfigContext.Provider, { value: context }, children));
}
/**
* Used in conjunction with the `m` component to reduce bundle size.
*
* `m` is a version of the `motion` component that only loads functionality
* critical for the initial render.
*
* `LazyMotion` can then be used to either synchronously or asynchronously
* load animation and gesture support.
*
* ```jsx
* // Synchronous loading
* import { LazyMotion, m, domAnimations } from "framer-motion"
*
* function App() {
* return (
* <LazyMotion features={domAnimations}>
* <m.div animate={{ scale: 2 }} />
* </LazyMotion>
* )
* }
*
* // Asynchronous loading
* import { LazyMotion, m } from "framer-motion"
*
* function App() {
* return (
* <LazyMotion features={() => import('./path/to/domAnimations')}>
* <m.div animate={{ scale: 2 }} />
* </LazyMotion>
* )
* }
* ```
*
* @public
*/
function LazyMotion(_a) {
var children = _a.children, features = _a.features, _b = _a.strict, strict = _b === void 0 ? false : _b;
var _c = tslib.__read(React.useState(!isLazyBundle(features)), 2), setIsLoaded = _c[1];
var loadedRenderer = React.useRef(undefined);
/**
* If this is a synchronous load, load features immediately
*/
if (!isLazyBundle(features)) {
var renderer = features.renderer, loadedFeatures = tslib.__rest(features, ["renderer"]);
loadedRenderer.current = renderer;
loadFeatures(loadedFeatures);
}
React.useEffect(function () {
if (isLazyBundle(features)) {
features().then(function (_a) {
var renderer = _a.renderer, loadedFeatures = tslib.__rest(_a, ["renderer"]);
loadFeatures(loadedFeatures);
loadedRenderer.current = renderer;
setIsLoaded(true);
});
}
}, []);
return (React__namespace.createElement(LazyContext.Provider, { value: { renderer: loadedRenderer.current, strict: strict } }, children));
}
function isLazyBundle(features) {
return typeof features === "function";
}
var ReorderContext = React.createContext(null);
function checkReorder(order, value, offset, velocity) {
if (!velocity)
return order;
var index = order.findIndex(function (item) { return item.value === value; });
if (index === -1)
return order;
var nextOffset = velocity > 0 ? 1 : -1;
var nextItem = order[index + nextOffset];
if (!nextItem)
return order;
var item = order[index];
var nextLayout = nextItem.layout;
var nextItemCenter = popmotion.mix(nextLayout.min, nextLayout.max, 0.5);
if ((nextOffset === 1 && item.layout.max + offset > nextItemCenter) ||
(nextOffset === -1 && item.layout.min + offset < nextItemCenter)) {
return moveItem(order, index, index + nextOffset);
}
return order;
}
function ReorderGroup(_a, externalRef) {
var children = _a.children, _b = _a.as, as = _b === void 0 ? "ul" : _b, _c = _a.axis, axis = _c === void 0 ? "y" : _c, onReorder = _a.onReorder, values = _a.values, props = tslib.__rest(_a, ["children", "as", "axis", "onReorder", "values"]);
var Component = useConstant(function () { return motion(as); });
var order = [];
var isReordering = React.useRef(false);
heyListen.invariant(Boolean(values), "Reorder.Group must be provided a values prop");
var context = {
axis: axis,
registerItem: function (value, layout) {
/**
* Ensure entries can't add themselves more than once
*/
if (layout &&
order.findIndex(function (entry) { return value === entry.value; }) === -1) {
order.push({ value: value, layout: layout[axis] });
order.sort(compareMin);
}
},
updateOrder: function (id, offset, velocity) {
if (isReordering.current)
return;
var newOrder = checkReorder(order, id, offset, velocity);
if (order !== newOrder) {
isReordering.current = true;
onReorder(newOrder
.map(getValue)
.filter(function (value) { return values.indexOf(value) !== -1; }));
}
},
};
React.useEffect(function () {
isReordering.current = false;
});
return (React__namespace.createElement(Component, tslib.__assign({}, props, { ref: externalRef }),
React__namespace.createElement(ReorderContext.Provider, { value: context }, children)));
}
var Group = React.forwardRef(ReorderGroup);
function getValue(item) {
return item.value;
}
function compareMin(a, b) {
return a.layout.min - b.layout.min;
}
/**
* Creates a `MotionValue` to track the state and velocity of a value.
*
* Usually, these are created automatically. For advanced use-cases, like use with `useTransform`, you can create `MotionValue`s externally and pass them into the animated component via the `style` prop.
*
* ```jsx
* export const MyComponent = () => {
* const scale = useMotionValue(1)
*
* return <motion.div style={{ scale }} />
* }
* ```
*
* @param initial - The initial state.
*
* @public
*/
function useMotionValue(initial) {
var value = useConstant(function () { return motionValue(initial); });
/**
* If this motion value is being used in static mode, like on
* the Framer canvas, force components to rerender when the motion
* value is updated.
*/
var isStatic = React.useContext(MotionConfigContext).isStatic;
if (isStatic) {
var _a = tslib.__read(React.useState(initial), 2), setLatest_1 = _a[1];
React.useEffect(function () { return value.onChange(setLatest_1); }, []);
}
return value;
}
var isCustomValueType = function (v) {
return typeof v === "object" && v.mix;
};
var getMixer = function (v) { return (isCustomValueType(v) ? v.mix : undefined); };
function transform() {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var useImmediate = !Array.isArray(args[0]);
var argOffset = useImmediate ? 0 : -1;
var inputValue = args[0 + argOffset];
var inputRange = args[1 + argOffset];
var outputRange = args[2 + argOffset];
var options = args[3 + argOffset];
var interpolator = popmotion.interpolate(inputRange, outputRange, tslib.__assign({ mixer: getMixer(outputRange[0]) }, options));
return useImmediate ? interpolator(inputValue) : interpolator;
}
function useOnChange(value, callback) {
useIsomorphicLayoutEffect(function () {
if (isMotionValue(value))
return value.onChange(callback);
}, [callback]);
}
function useMultiOnChange(values, handler) {
useIsomorphicLayoutEffect(function () {
var subscriptions = values.map(function (value) { return value.onChange(handler); });
return function () { return subscriptions.forEach(function (unsubscribe) { return unsubscribe(); }); };
});
}
function useCombineMotionValues(values, combineValues) {
/**
* Initialise the returned motion value. This remains the same between renders.
*/
var value = useMotionValue(combineValues());
/**
* Create a function that will update the template motion value with the latest values.
* This is pre-bound so whenever a motion value updates it can schedule its
* execution in Framesync. If it's already been scheduled it won't be fired twice
* in a single frame.
*/
var updateValue = function () { return value.set(combineValues()); };
/**
* Synchronously update the motion value with the latest values during the render.
* This ensures that within a React render, the styles applied to the DOM are up-to-date.
*/
updateValue();
/**
* Subscribe to all motion values found within the template. Whenever any of them change,
* schedule an update.
*/
useMultiOnChange(values, function () { return sync__default["default"].update(updateValue, false, true); });
return value;
}
function useTransform(input, inputRangeOrTransformer, outputRange, options) {
var transformer = typeof inputRangeOrTransformer === "function"
? inputRangeOrTransformer
: transform(inputRangeOrTransformer, outputRange, options);
return Array.isArray(input)
? useListTransform(input, transformer)
: useListTransform([input], function (_a) {
var _b = tslib.__read(_a, 1), latest = _b[0];
return transformer(latest);
});
}
function useListTransform(values, transformer) {
var latest = useConstant(function () { return []; });
return useCombineMotionValues(values, function () {
latest.length = 0;
var numValues = values.length;
for (var i = 0; i < numValues; i++) {
latest[i] = values[i].get();
}
return transformer(latest);
});
}
function useDefaultMotionValue(value, defaultValue) {
if (defaultValue === void 0) { defaultValue = 0; }
return isMotionValue(value) ? value : useMotionValue(defaultValue);
}
function ReorderItem(_a, externalRef) {
var children = _a.children, style = _a.style, value = _a.value, _b = _a.as, as = _b === void 0 ? "li" : _b, onDrag = _a.onDrag, _c = _a.layout, layout = _c === void 0 ? true : _c, props = tslib.__rest(_a, ["children", "style", "value", "as", "onDrag", "layout"]);
var Component = useConstant(function () { return motion(as); });
var context = React.useContext(ReorderContext);
var point = {
x: useDefaultMotionValue(style === null || style === void 0 ? void 0 : style.x),
y: useDefaultMotionValue(style === null || style === void 0 ? void 0 : style.y),
};
var zIndex = useTransform([point.x, point.y], function (_a) {
var _b = tslib.__read(_a, 2), latestX = _b[0], latestY = _b[1];
return latestX || latestY ? 1 : "unset";
});
var measuredLayout = React.useRef(null);
heyListen.invariant(Boolean(context), "Reorder.Item must be a child of Reorder.Group");
var _d = context, axis = _d.axis, registerItem = _d.registerItem, updateOrder = _d.updateOrder;
React.useEffect(function () {
registerItem(value, measuredLayout.current);
}, [context]);
return (React__namespace.createElement(Component, tslib.__assign({ drag: axis }, props, { dragSnapToOrigin: true, style: tslib.__assign(tslib.__assign({}, style), { x: point.x, y: point.y, zIndex: zIndex }), layout: layout, onDrag: function (event, gesturePoint) {
var velocity = gesturePoint.velocity;
velocity[axis] &&
updateOrder(value, point[axis].get(), velocity[axis]);
onDrag === null || onDrag === void 0 ? void 0 : onDrag(event, gesturePoint);
}, onLayoutMeasure: function (measured) {
measuredLayout.current = measured;
}, ref: externalRef }), children));
}
var Item = React.forwardRef(ReorderItem);
var Reorder = {
Group: Group,
Item: Item,
};
/**
* @public
*/
var domAnimation = tslib.__assign(tslib.__assign({ renderer: createDomVisualElement }, animations), gestureAnimations);
/**
* @public
*/
var domMax = tslib.__assign(tslib.__assign(tslib.__assign(tslib.__assign({}, domAnimation), drag), layoutFeatures), { projectionNodeConstructor: HTMLProjectionNode });
/**
* Combine multiple motion values into a new one using a string template literal.
*
* ```jsx
* import {
* motion,
* useSpring,
* useMotionValue,
* useMotionTemplate
* } from "framer-motion"
*
* function Component() {
* const shadowX = useSpring(0)
* const shadowY = useMotionValue(0)
* const shadow = useMotionTemplate`drop-shadow(${shadowX}px ${shadowY}px 20px rgba(0,0,0,0.3))`
*
* return <motion.div style={{ filter: shadow }} />
* }
* ```
*
* @public
*/
function useMotionTemplate(fragments) {
var values = [];
for (var _i = 1; _i < arguments.length; _i++) {
values[_i - 1] = arguments[_i];
}
/**
* Create a function that will build a string from the latest motion values.
*/
var numFragments = fragments.length;
function buildValue() {
var output = "";
for (var i = 0; i < numFragments; i++) {
output += fragments[i];
var value = values[i];
if (value)
output += values[i].get();
}
return output;
}
return useCombineMotionValues(values, buildValue);
}
/**
* Creates a `MotionValue` that, when `set`, will use a spring animation to animate to its new state.
*
* It can either work as a stand-alone `MotionValue` by initialising it with a value, or as a subscriber
* to another `MotionValue`.
*
* @remarks
*
* ```jsx
* const x = useSpring(0, { stiffness: 300 })
* const y = useSpring(x, { damping: 10 })
* ```
*
* @param inputValue - `MotionValue` or number. If provided a `MotionValue`, when the input `MotionValue` changes, the created `MotionValue` will spring towards that value.
* @param springConfig - Configuration options for the spring.
* @returns `MotionValue`
*
* @public
*/
function useSpring(source, config) {
if (config === void 0) { config = {}; }
var isStatic = React.useContext(MotionConfigContext).isStatic;
var activeSpringAnimation = React.useRef(null);
var value = useMotionValue(isMotionValue(source) ? source.get() : source);
React.useMemo(function () {
return value.attach(function (v, set) {
/**
* A more hollistic approach to this might be to use isStatic to fix VisualElement animations
* at that level, but this will work for now
*/
if (isStatic)
return set(v);
if (activeSpringAnimation.current) {
activeSpringAnimation.current.stop();
}
activeSpringAnimation.current = popmotion.animate(tslib.__assign(tslib.__assign({ from: value.get(), to: v, velocity: value.getVelocity() }, config), { onUpdate: set }));
return value.get();
});
}, [JSON.stringify(config)]);
useOnChange(source, function (v) { return value.set(parseFloat(v)); });
return value;
}
/**
* Creates a `MotionValue` that updates when the velocity of the provided `MotionValue` changes.
*
* ```javascript
* const x = useMotionValue(0)
* const xVelocity = useVelocity(x)
* const xAcceleration = useVelocity(xVelocity)
* ```
*
* @public
*/
function useVelocity(value) {
var velocity = useMotionValue(value.getVelocity());
React.useEffect(function () {
return value.velocityUpdateSubscribers.add(function (newVelocity) {
velocity.set(newVelocity);
});
}, [value]);
return velocity;
}
var createScrollMotionValues = function () { return ({
scrollX: motionValue(0),
scrollY: motionValue(0),
scrollXProgress: motionValue(0),
scrollYProgress: motionValue(0),
}); };
function useScroll(_a) {
if (_a === void 0) { _a = {}; }
var container = _a.container, target = _a.target, options = tslib.__rest(_a, ["container", "target"]);
var values = useConstant(createScrollMotionValues);
useIsomorphicLayoutEffect(function () {
return dom.scroll(function (_a) {
var x = _a.x, y = _a.y;
values.scrollX.set(x.current);
values.scrollXProgress.set(x.progress);
values.scrollY.set(y.current);
values.scrollYProgress.set(y.progress);
}, tslib.__assign(tslib.__assign({}, options), { container: (container === null || container === void 0 ? void 0 : container.current) || undefined, target: (target === null || target === void 0 ? void 0 : target.current) || undefined }));
}, []);
return values;
}
function useElementScroll(ref) {
warnOnce(false, "useElementScroll is deprecated. Convert to useScroll({ container: ref }).");
return useScroll({ container: ref });
}
function useViewportScroll() {
warnOnce(false, "useViewportScroll is deprecated. Convert to useScroll().");
return useScroll();
}
var getCurrentTime = typeof performance !== "undefined"
? function () { return performance.now(); }
: function () { return Date.now(); };
function useAnimationFrame(callback) {
var initialTimestamp = useConstant(getCurrentTime);
var isStatic = React.useContext(MotionConfigContext).isStatic;
React.useEffect(function () {
if (isStatic)
return;
var provideTimeSinceStart = function (_a) {
var timestamp = _a.timestamp;
callback(timestamp - initialTimestamp);
};
sync__default["default"].update(provideTimeSinceStart, true);
return function () { return sync.cancelSync.update(provideTimeSinceStart); };
}, [callback]);
}
function useTime() {
var time = useMotionValue(0);
useAnimationFrame(function (t) { return time.set(t); });
return time;
}
/**
* @public
*/
function animationControls() {
/**
* Track whether the host component has mounted.
*/
var hasMounted = false;
/**
* Pending animations that are started before a component is mounted.
* TODO: Remove this as animations should only run in effects
*/
var pendingAnimations = [];
/**
* A collection of linked component animation controls.
*/
var subscribers = new Set();
var controls = {
subscribe: function (visualElement) {
subscribers.add(visualElement);
return function () { return void subscribers.delete(visualElement); };
},
start: function (definition, transitionOverride) {
/**
* TODO: We only perform this hasMounted check because in Framer we used to
* encourage the ability to start an animation within the render phase. This
* isn't behaviour concurrent-safe so when we make Framer concurrent-safe
* we can ditch this.
*/
if (hasMounted) {
var animations_1 = [];
subscribers.forEach(function (visualElement) {
animations_1.push(animateVisualElement(visualElement, definition, {
transitionOverride: transitionOverride,
}));
});
return Promise.all(animations_1);
}
else {
return new Promise(function (resolve) {
pendingAnimations.push({
animation: [definition, transitionOverride],
resolve: resolve,
});
});
}
},
set: function (definition) {
heyListen.invariant(hasMounted, "controls.set() should only be called after a component has mounted. Consider calling within a useEffect hook.");
return subscribers.forEach(function (visualElement) {
setValues(visualElement, definition);
});
},
stop: function () {
subscribers.forEach(function (visualElement) {
stopAnimation(visualElement);
});
},
mount: function () {
hasMounted = true;
pendingAnimations.forEach(function (_a) {
var animation = _a.animation, resolve = _a.resolve;
controls.start.apply(controls, tslib.__spreadArray([], tslib.__read(animation), false)).then(resolve);
});
return function () {
hasMounted = false;
controls.stop();
};
},
};
return controls;
}
/**
* Creates `AnimationControls`, which can be used to manually start, stop
* and sequence animations on one or more components.
*
* The returned `AnimationControls` should be passed to the `animate` property
* of the components you want to animate.
*
* These components can then be animated with the `start` method.
*
* ```jsx
* import * as React from 'react'
* import { motion, useAnimation } from 'framer-motion'
*
* export function MyComponent(props) {
* const controls = useAnimation()
*
* controls.start({
* x: 100,
* transition: { duration: 0.5 },
* })
*
* return <motion.div animate={controls} />
* }
* ```
*
* @returns Animation controller with `start` and `stop` methods
*
* @public
*/
function useAnimationControls() {
var controls = useConstant(animationControls);
React.useEffect(controls.mount, []);
return controls;
}
var useAnimation = useAnimationControls;
/**
* Cycles through a series of visual properties. Can be used to toggle between or cycle through animations. It works similar to `useState` in React. It is provided an initial array of possible states, and returns an array of two arguments.
*
* An index value can be passed to the returned `cycle` function to cycle to a specific index.
*
* ```jsx
* import * as React from "react"
* import { motion, useCycle } from "framer-motion"
*
* export const MyComponent = () => {
* const [x, cycleX] = useCycle(0, 50, 100)
*
* return (
* <motion.div
* animate={{ x: x }}
* onTap={() => cycleX()}
* />
* )
* }
* ```
*
* @param items - items to cycle through
* @returns [currentState, cycleState]
*
* @public
*/
function useCycle() {
var items = [];
for (var _i = 0; _i < arguments.length; _i++) {
items[_i] = arguments[_i];
}
var index = React.useRef(0);
var _a = tslib.__read(React.useState(items[index.current]), 2), item = _a[0], setItem = _a[1];
var runCycle = React.useCallback(function (next) {
index.current =
typeof next !== "number"
? popmotion.wrap(0, items.length, index.current + 1)
: next;
setItem(items[index.current]);
}, tslib.__spreadArray([items.length], tslib.__read(items), false));
return [item, runCycle];
}
function useInView(ref, _a) {
var _b = _a === void 0 ? {} : _a, root = _b.root, margin = _b.margin, amount = _b.amount, _c = _b.once, once = _c === void 0 ? false : _c;
var _d = tslib.__read(React.useState(false), 2), isInView = _d[0], setInView = _d[1];
React.useEffect(function () {
var _a;
if (!ref.current || (once && isInView))
return;
var onEnter = function () {
setInView(true);
return once ? undefined : function () { return setInView(false); };
};
var options = {
root: (_a = root === null || root === void 0 ? void 0 : root.current) !== null && _a !== void 0 ? _a : undefined,
margin: margin,
amount: amount === "some" ? "any" : amount,
};
return dom.inView(ref.current, onEnter, options);
}, [root, ref, margin, once]);
return isInView;
}
/**
* Can manually trigger a drag gesture on one or more `drag`-enabled `motion` components.
*
* ```jsx
* const dragControls = useDragControls()
*
* function startDrag(event) {
* dragControls.start(event, { snapToCursor: true })
* }
*
* return (
* <>
* <div onPointerDown={startDrag} />
* <motion.div drag="x" dragControls={dragControls} />
* </>
* )
* ```
*
* @public
*/
var DragControls = /** @class */ (function () {
function DragControls() {
this.componentControls = new Set();
}
/**
* Subscribe a component's internal `VisualElementDragControls` to the user-facing API.
*
* @internal
*/
DragControls.prototype.subscribe = function (controls) {
var _this = this;
this.componentControls.add(controls);
return function () { return _this.componentControls.delete(controls); };
};
/**
* Start a drag gesture on every `motion` component that has this set of drag controls
* passed into it via the `dragControls` prop.
*
* ```jsx
* dragControls.start(e, {
* snapToCursor: true
* })
* ```
*
* @param event - PointerEvent
* @param options - Options
*
* @public
*/
DragControls.prototype.start = function (event, options) {
this.componentControls.forEach(function (controls) {
controls.start(event.nativeEvent || event, options);
});
};
return DragControls;
}());
var createDragControls = function () { return new DragControls(); };
/**
* Usually, dragging is initiated by pressing down on a `motion` component with a `drag` prop
* and moving it. For some use-cases, for instance clicking at an arbitrary point on a video scrubber, we
* might want to initiate that dragging from a different component than the draggable one.
*
* By creating a `dragControls` using the `useDragControls` hook, we can pass this into
* the draggable component's `dragControls` prop. It exposes a `start` method
* that can start dragging from pointer events on other components.
*
* ```jsx
* const dragControls = useDragControls()
*
* function startDrag(event) {
* dragControls.start(event, { snapToCursor: true })
* }
*
* return (
* <>
* <div onPointerDown={startDrag} />
* <motion.div drag="x" dragControls={dragControls} />
* </>
* )
* ```
*
* @public
*/
function useDragControls() {
return useConstant(createDragControls);
}
function useInstantLayoutTransition() {
return startTransition;
}
function startTransition(cb) {
if (!rootProjectionNode.current)
return;
rootProjectionNode.current.isUpdating = false;
rootProjectionNode.current.blockUpdate();
cb === null || cb === void 0 ? void 0 : cb();
}
function useInstantTransition() {
var _a = tslib.__read(useForceUpdate(), 2), forceUpdate = _a[0], forcedRenderCount = _a[1];
var startInstantLayoutTransition = useInstantLayoutTransition();
React.useEffect(function () {
/**
* Unblock after two animation frames, otherwise this will unblock too soon.
*/
sync__default["default"].postRender(function () {
return sync__default["default"].postRender(function () { return (instantAnimationState.current = false); });
});
}, [forcedRenderCount]);
return function (callback) {
startInstantLayoutTransition(function () {
instantAnimationState.current = true;
forceUpdate();
callback();
});
};
}
function useResetProjection() {
var reset = React__namespace.useCallback(function () {
var root = rootProjectionNode.current;
if (!root)
return;
root.resetTree();
}, []);
return reset;
}
var createObject = function () { return ({}); };
var stateVisualElement = visualElement({
build: function () { },
measureViewportBox: createBox,
resetTransform: function () { },
restoreTransform: function () { },
removeValueFromRenderState: function () { },
render: function () { },
scrapeMotionValuesFromProps: createObject,
readValueFromInstance: function (_state, key, options) {
return options.initialState[key] || 0;
},
makeTargetAnimatable: function (element, _a) {
var transition = _a.transition, transitionEnd = _a.transitionEnd, target = tslib.__rest(_a, ["transition", "transitionEnd"]);
var origin = getOrigin(target, transition || {}, element);
checkTargetForNewValues(element, target, origin);
return tslib.__assign({ transition: transition, transitionEnd: transitionEnd }, target);
},
});
var useVisualState = makeUseVisualState({
scrapeMotionValuesFromProps: createObject,
createRenderState: createObject,
});
/**
* This is not an officially supported API and may be removed
* on any version.
*/
function useAnimatedState(initialState) {
var _a = tslib.__read(React.useState(initialState), 2), animationState = _a[0], setAnimationState = _a[1];
var visualState = useVisualState({}, false);
var element = useConstant(function () {
return stateVisualElement({ props: {}, visualState: visualState }, { initialState: initialState });
});
React.useEffect(function () {
element.mount({});
return element.unmount;
}, [element]);
React.useEffect(function () {
element.setProps({
onUpdate: function (v) {
setAnimationState(tslib.__assign({}, v));
},
});
}, [setAnimationState, element]);
var startAnimation = useConstant(function () { return function (animationDefinition) {
return animateVisualElement(element, animationDefinition);
}; });
return [animationState, startAnimation];
}
// Keep things reasonable and avoid scale: Infinity. In practise we might need
// to add another value, opacity, that could interpolate scaleX/Y [0,0.01] => [0,1]
// to simply hide content at unreasonable scales.
var maxScale = 100000;
var invertScale = function (scale) {
return scale > 0.001 ? 1 / scale : maxScale;
};
var hasWarned = false;
/**
* Returns a `MotionValue` each for `scaleX` and `scaleY` that update with the inverse
* of their respective parent scales.
*
* This is useful for undoing the distortion of content when scaling a parent component.
*
* By default, `useInvertedScale` will automatically fetch `scaleX` and `scaleY` from the nearest parent.
* By passing other `MotionValue`s in as `useInvertedScale({ scaleX, scaleY })`, it will invert the output
* of those instead.
*
* ```jsx
* const MyComponent = () => {
* const { scaleX, scaleY } = useInvertedScale()
* return <motion.div style={{ scaleX, scaleY }} />
* }
* ```
*
* @deprecated
*/
function useInvertedScale(scale) {
var parentScaleX = useMotionValue(1);
var parentScaleY = useMotionValue(1);
var visualElement = useVisualElementContext();
heyListen.invariant(!!(scale || visualElement), "If no scale values are provided, useInvertedScale must be used within a child of another motion component.");
heyListen.warning(hasWarned, "useInvertedScale is deprecated and will be removed in 3.0. Use the layout prop instead.");
hasWarned = true;
if (scale) {
parentScaleX = scale.scaleX || parentScaleX;
parentScaleY = scale.scaleY || parentScaleY;
}
else if (visualElement) {
parentScaleX = visualElement.getValue("scaleX", 1);
parentScaleY = visualElement.getValue("scaleY", 1);
}
var scaleX = useTransform(parentScaleX, invertScale);
var scaleY = useTransform(parentScaleY, invertScale);
return { scaleX: scaleX, scaleY: scaleY };
}
exports.AnimatePresence = AnimatePresence;
exports.AnimateSharedLayout = AnimateSharedLayout;
exports.DeprecatedLayoutGroupContext = DeprecatedLayoutGroupContext;
exports.DragControls = DragControls;
exports.FlatTree = FlatTree;
exports.LayoutGroup = LayoutGroup;
exports.LayoutGroupContext = LayoutGroupContext;
exports.LazyMotion = LazyMotion;
exports.MotionConfig = MotionConfig;
exports.MotionConfigContext = MotionConfigContext;
exports.MotionContext = MotionContext;
exports.MotionValue = MotionValue;
exports.PresenceContext = PresenceContext;
exports.Reorder = Reorder;
exports.SwitchLayoutGroupContext = SwitchLayoutGroupContext;
exports.addPointerEvent = addPointerEvent;
exports.addScaleCorrector = addScaleCorrector;
exports.animate = animate;
exports.animateVisualElement = animateVisualElement;
exports.animationControls = animationControls;
exports.animations = animations;
exports.calcLength = calcLength;
exports.checkTargetForNewValues = checkTargetForNewValues;
exports.createBox = createBox;
exports.createDomMotionComponent = createDomMotionComponent;
exports.createMotionComponent = createMotionComponent;
exports.domAnimation = domAnimation;
exports.domMax = domMax;
exports.filterProps = filterProps;
exports.isBrowser = isBrowser;
exports.isDragActive = isDragActive;
exports.isMotionValue = isMotionValue;
exports.isValidMotionProp = isValidMotionProp;
exports.m = m;
exports.makeUseVisualState = makeUseVisualState;
exports.motion = motion;
exports.motionValue = motionValue;
exports.resolveMotionValue = resolveMotionValue;
exports.transform = transform;
exports.useAnimation = useAnimation;
exports.useAnimationControls = useAnimationControls;
exports.useAnimationFrame = useAnimationFrame;
exports.useCycle = useCycle;
exports.useDeprecatedAnimatedState = useAnimatedState;
exports.useDeprecatedInvertedScale = useInvertedScale;
exports.useDomEvent = useDomEvent;
exports.useDragControls = useDragControls;
exports.useElementScroll = useElementScroll;
exports.useForceUpdate = useForceUpdate;
exports.useInView = useInView;
exports.useInstantLayoutTransition = useInstantLayoutTransition;
exports.useInstantTransition = useInstantTransition;
exports.useIsPresent = useIsPresent;
exports.useIsomorphicLayoutEffect = useIsomorphicLayoutEffect;
exports.useMotionTemplate = useMotionTemplate;
exports.useMotionValue = useMotionValue;
exports.usePresence = usePresence;
exports.useReducedMotion = useReducedMotion;
exports.useReducedMotionConfig = useReducedMotionConfig;
exports.useResetProjection = useResetProjection;
exports.useScroll = useScroll;
exports.useSpring = useSpring;
exports.useTime = useTime;
exports.useTransform = useTransform;
exports.useUnmountEffect = useUnmountEffect;
exports.useVelocity = useVelocity;
exports.useViewportScroll = useViewportScroll;
exports.useVisualElementContext = useVisualElementContext;
exports.visualElement = visualElement;
exports.wrapHandler = wrapHandler;