UNPKG

framer-motion

Version:

A simple and powerful React animation library

1,368 lines (1,296 loc) • 417 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react')) : typeof define === 'function' && define.amd ? define(['exports', 'react'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Motion = {}, global.React)); })(this, (function (exports, React) { 'use strict'; 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__default = /*#__PURE__*/_interopDefaultLegacy(React); var React__namespace = /*#__PURE__*/_interopNamespace(React); /** * @public */ const MotionConfigContext = React.createContext({ transformPagePoint: (p) => p, isStatic: false, reducedMotion: "never", }); const MotionContext = React.createContext({}); function useVisualElementContext() { return React.useContext(MotionContext).visualElement; } /** * @public */ const PresenceContext = React.createContext(null); const isBrowser = typeof document !== "undefined"; const useIsomorphicLayoutEffect = isBrowser ? React.useLayoutEffect : React.useEffect; const LazyContext = React.createContext({ strict: false }); function useVisualElement(Component, visualState, props, createVisualElement) { const parent = useVisualElementContext(); const lazyContext = React.useContext(LazyContext); const presenceContext = React.useContext(PresenceContext); const reducedMotionConfig = React.useContext(MotionConfigContext).reducedMotion; const visualElementRef = React.useRef(undefined); /** * If we haven't preloaded a renderer, check to see if we have one lazy-loaded */ createVisualElement = createVisualElement || lazyContext.renderer; if (!visualElementRef.current && createVisualElement) { visualElementRef.current = createVisualElement(Component, { visualState, parent, props, presenceId: presenceContext ? presenceContext.id : undefined, blockInitialAnimation: presenceContext ? presenceContext.initial === false : false, reducedMotionConfig, }); } const visualElement = visualElementRef.current; useIsomorphicLayoutEffect(() => { visualElement && visualElement.syncRender(); }); React.useEffect(() => { if (visualElement && visualElement.animationState) { visualElement.animationState.animateChanges(); } }); useIsomorphicLayoutEffect(() => () => visualElement && 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((instance) => { instance && visualState.mount && visualState.mount(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 variant label */ function isVariantLabel(v) { return typeof v === "string" || Array.isArray(v); } function isAnimationControls(v) { return typeof v === "object" && typeof v.start === "function"; } const variantProps$1 = [ "initial", "animate", "exit", "whileHover", "whileDrag", "whileTap", "whileFocus", "whileInView", ]; function isControllingVariants(props) { return (isAnimationControls(props.animate) || variantProps$1.some((name) => isVariantLabel(props[name]))); } function isVariantNode(props) { return Boolean(isControllingVariants(props) || props.variants); } function getCurrentTreeVariants(props, context) { if (isControllingVariants(props)) { const { initial, animate } = props; return { initial: initial === false || isVariantLabel(initial) ? initial : undefined, animate: isVariantLabel(animate) ? animate : undefined, }; } return props.inherit !== false ? context : {}; } function useCreateMotionContext(props) { const { initial, animate } = getCurrentTreeVariants(props, React.useContext(MotionContext)); return React.useMemo(() => ({ initial, animate }), [variantLabelsAsDependency(initial), variantLabelsAsDependency(animate)]); } function variantLabelsAsDependency(prop) { return Array.isArray(prop) ? prop.join(" ") : prop; } const createDefinition = (propNames) => ({ isEnabled: (props) => propNames.some((name) => !!props[name]), }); const 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 (const key in features) { if (key === "projectionNodeConstructor") { featureDefinitions.projectionNodeConstructor = features[key]; } else { featureDefinitions[key].Component = features[key]; } } } /** * 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) { const 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. */ const 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, }; let id$1 = 1; function useProjectionId() { return useConstant(() => { if (globalProjectionState.hasEverUpdated) { return id$1++; } }); } const LayoutGroupContext = React.createContext({}); class VisualElementHandler extends React__default["default"].Component { /** * Update visual element props as soon as we know this update is going to be commited. */ getSnapshotBeforeUpdate() { const { visualElement, props } = this.props; if (visualElement) visualElement.setProps(props); return null; } componentDidUpdate() { } render() { return this.props.children; } } /** * Internal, exported only for usage in Framer */ const SwitchLayoutGroupContext = React.createContext({}); const motionComponentSymbol = Symbol.for("motionComponentSymbol"); /** * 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({ preloadedFeatures, createVisualElement, projectionNodeConstructor, useRender, useVisualState, Component, }) { preloadedFeatures && loadFeatures(preloadedFeatures); function MotionComponent(props, externalRef) { const configAndProps = { ...React.useContext(MotionConfigContext), ...props, layoutId: useLayoutId(props), }; const { isStatic } = configAndProps; let features = null; const 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. */ const projectionId = isStatic ? undefined : useProjectionId(); /** * */ const visualState = useVisualState(props, isStatic); if (!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, configAndProps, createVisualElement); /** * Load Motion gesture and animation features. These are rendered as renderless * components so each feature can optionally make use of React lifecycle methods. */ const lazyStrictMode = React.useContext(LazyContext).strict; const initialLayoutGroupConfig = React.useContext(SwitchLayoutGroupContext); if (context.visualElement) { features = context.visualElement.loadFeatures(props, lazyStrictMode, preloadedFeatures, projectionId, projectionNodeConstructor || featureDefinitions.projectionNodeConstructor, initialLayoutGroupConfig); } } /** * 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: configAndProps }, features, React__namespace.createElement(MotionContext.Provider, { value: context }, useRender(Component, props, projectionId, useMotionRef(visualState, context.visualElement, externalRef), visualState, isStatic, context.visualElement)))); } const ForwardRefComponent = React.forwardRef(MotionComponent); ForwardRefComponent[motionComponentSymbol] = Component; return ForwardRefComponent; } function useLayoutId({ layoutId }) { const layoutGroupId = React.useContext(LayoutGroupContext).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 = {}) { 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. */ const 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: (_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 */ const 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; } const scaleCorrectors = {}; function addScaleCorrector(correctors) { Object.assign(scaleCorrectors, correctors); } /** * Generate a list of every possible transform key. */ const transformPropOrder = [ "transformPerspective", "x", "y", "z", "translateX", "translateY", "translateZ", "scale", "scaleX", "scaleY", "rotate", "rotateX", "rotateY", "rotateZ", "skew", "skewX", "skewY", ]; /** * A quick lookup for transform props. */ const transformProps = new Set(transformPropOrder); function isForcedMotionValue(key, { layout, layoutId }) { return (transformProps.has(key) || key.startsWith("origin") || ((layout || layoutId !== undefined) && (!!scaleCorrectors[key] || key === "opacity"))); } const isMotionValue = (value) => value === undefined ? false : !!value.getVelocity; const translateAlias = { x: "translateX", y: "translateY", z: "translateZ", transformPerspective: "perspective", }; /** * A function to use with Array.sort to sort transform keys by their default order. */ const sortTransformProps = (a, b) => transformPropOrder.indexOf(a) - transformPropOrder.indexOf(b); /** * 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({ transform, transformKeys }, { enableHardwareAcceleration = true, allowTransformNone = true, }, transformIsDefault, transformTemplate) { // The transform string we're going to build into. let transformString = ""; // Transform keys into their default order - this will determine the output order. transformKeys.sort(sortTransformProps); // Loop over each transform and build them into transformString for (const key of transformKeys) { transformString += `${translateAlias[key] || key}(${transform[key]}) `; } if (enableHardwareAcceleration && !transform.z) { transformString += "translateZ(0)"; } 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; } /** * 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. */ const getValueAsType = (value, type) => { return type && typeof value === "number" ? type.transform(value) : value; }; const clamp$2 = (min, max) => (v) => Math.max(Math.min(v, max), min); const sanitize = (v) => (v % 1 ? Number(v.toFixed(5)) : v); const floatRegex = /(-)?([\d]*\.?[\d])+/g; const colorRegex = /(#[0-9a-f]{6}|#[0-9a-f]{3}|#(?:[0-9a-f]{2}){2,4}|(rgb|hsl)a?\((-?[\d\.]+%?[,\s]+){2}(-?[\d\.]+%?)\s*[\,\/]?\s*[\d\.]*%?\))/gi; const singleColorRegex = /^(#[0-9a-f]{3}|#(?:[0-9a-f]{2}){2,4}|(rgb|hsl)a?\((-?[\d\.]+%?[,\s]+){2}(-?[\d\.]+%?)\s*[\,\/]?\s*[\d\.]*%?\))$/i; function isString$1(v) { return typeof v === 'string'; } const number = { test: (v) => typeof v === 'number', parse: parseFloat, transform: (v) => v, }; const alpha = Object.assign(Object.assign({}, number), { transform: clamp$2(0, 1) }); const scale = Object.assign(Object.assign({}, number), { default: 1 }); const createUnitType = (unit) => ({ test: (v) => isString$1(v) && v.endsWith(unit) && v.split(' ').length === 1, parse: parseFloat, transform: (v) => `${v}${unit}`, }); const degrees = createUnitType('deg'); const percent = createUnitType('%'); const px = createUnitType('px'); const vh = createUnitType('vh'); const vw = createUnitType('vw'); const progressPercentage = Object.assign(Object.assign({}, percent), { parse: (v) => percent.parse(v) / 100, transform: (v) => percent.transform(v * 100) }); const isColorString = (type, testProp) => (v) => { return Boolean((isString$1(v) && singleColorRegex.test(v) && v.startsWith(type)) || (testProp && Object.prototype.hasOwnProperty.call(v, testProp))); }; const splitColor = (aName, bName, cName) => (v) => { if (!isString$1(v)) return v; const [a, b, c, alpha] = v.match(floatRegex); return { [aName]: parseFloat(a), [bName]: parseFloat(b), [cName]: parseFloat(c), alpha: alpha !== undefined ? parseFloat(alpha) : 1, }; }; const hsla = { test: isColorString('hsl', 'hue'), parse: splitColor('hue', 'saturation', 'lightness'), transform: ({ hue, saturation, lightness, alpha: alpha$1 = 1 }) => { return ('hsla(' + Math.round(hue) + ', ' + percent.transform(sanitize(saturation)) + ', ' + percent.transform(sanitize(lightness)) + ', ' + sanitize(alpha.transform(alpha$1)) + ')'); }, }; const clampRgbUnit = clamp$2(0, 255); const rgbUnit = Object.assign(Object.assign({}, number), { transform: (v) => Math.round(clampRgbUnit(v)) }); const rgba = { test: isColorString('rgb', 'red'), parse: splitColor('red', 'green', 'blue'), transform: ({ red, green, blue, alpha: alpha$1 = 1 }) => 'rgba(' + rgbUnit.transform(red) + ', ' + rgbUnit.transform(green) + ', ' + rgbUnit.transform(blue) + ', ' + sanitize(alpha.transform(alpha$1)) + ')', }; function parseHex(v) { let r = ''; let g = ''; let b = ''; let a = ''; if (v.length > 5) { r = v.substr(1, 2); g = v.substr(3, 2); b = v.substr(5, 2); a = v.substr(7, 2); } else { r = v.substr(1, 1); g = v.substr(2, 1); b = v.substr(3, 1); a = v.substr(4, 1); r += r; g += g; b += b; a += a; } return { red: parseInt(r, 16), green: parseInt(g, 16), blue: parseInt(b, 16), alpha: a ? parseInt(a, 16) / 255 : 1, }; } const hex = { test: isColorString('#'), parse: parseHex, transform: rgba.transform, }; const color = { test: (v) => rgba.test(v) || hex.test(v) || hsla.test(v), parse: (v) => { if (rgba.test(v)) { return rgba.parse(v); } else if (hsla.test(v)) { return hsla.parse(v); } else { return hex.parse(v); } }, transform: (v) => { return isString$1(v) ? v : v.hasOwnProperty('red') ? rgba.transform(v) : hsla.transform(v); }, }; const colorToken = '${c}'; const numberToken = '${n}'; function test(v) { var _a, _b, _c, _d; return (isNaN(v) && isString$1(v) && ((_b = (_a = v.match(floatRegex)) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) + ((_d = (_c = v.match(colorRegex)) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) > 0); } function analyse$1(v) { if (typeof v === 'number') v = `${v}`; const values = []; let numColors = 0; const colors = v.match(colorRegex); if (colors) { numColors = colors.length; v = v.replace(colorRegex, colorToken); values.push(...colors.map(color.parse)); } const numbers = v.match(floatRegex); if (numbers) { v = v.replace(floatRegex, numberToken); values.push(...numbers.map(number.parse)); } return { values, numColors, tokenised: v }; } function parse(v) { return analyse$1(v).values; } function createTransformer(v) { const { values, numColors, tokenised } = analyse$1(v); const numValues = values.length; return (v) => { let output = tokenised; for (let i = 0; i < numValues; i++) { output = output.replace(i < numColors ? colorToken : numberToken, i < numColors ? color.transform(v[i]) : sanitize(v[i])); } return output; }; } const convertNumbersToZero = (v) => typeof v === 'number' ? 0 : v; function getAnimatableNone$1(v) { const parsed = parse(v); const transformer = createTransformer(v); return transformer(parsed.map(convertNumbersToZero)); } const complex = { test, parse, createTransformer, getAnimatableNone: getAnimatableNone$1 }; const maxDefaults = new Set(['brightness', 'contrast', 'saturate', 'opacity']); function applyDefaultFilter(v) { let [name, value] = v.slice(0, -1).split('('); if (name === 'drop-shadow') return v; const [number] = value.match(floatRegex) || []; if (!number) return v; const unit = value.replace(number, ''); let defaultValue = maxDefaults.has(name) ? 1 : 0; if (number !== value) defaultValue *= 100; return name + '(' + defaultValue + unit + ')'; } const functionRegex = /([a-z-]*)\(.*?\)/g; const filter = Object.assign(Object.assign({}, complex), { getAnimatableNone: (v) => { const functions = v.match(functionRegex); return functions ? functions.map(applyDefaultFilter).join(' ') : v; } }); const int = { ...number, transform: Math.round, }; const numberValueTypes = { // Border props borderWidth: px, borderTopWidth: px, borderRightWidth: px, borderBottomWidth: px, borderLeftWidth: px, borderRadius: px, radius: px, borderTopLeftRadius: px, borderTopRightRadius: px, borderBottomRightRadius: px, borderBottomLeftRadius: px, // Positioning props width: px, maxWidth: px, height: px, maxHeight: px, size: px, top: px, right: px, bottom: px, left: px, // Spacing props padding: px, paddingTop: px, paddingRight: px, paddingBottom: px, paddingLeft: px, margin: px, marginTop: px, marginRight: px, marginBottom: px, marginLeft: px, // Transform props rotate: degrees, rotateX: degrees, rotateY: degrees, rotateZ: degrees, scale, scaleX: scale, scaleY: scale, scaleZ: scale, skew: degrees, skewX: degrees, skewY: degrees, distance: px, translateX: px, translateY: px, translateZ: px, x: px, y: px, z: px, perspective: px, transformPerspective: px, opacity: alpha, originX: progressPercentage, originY: progressPercentage, originZ: px, // Misc zIndex: int, // SVG fillOpacity: alpha, strokeOpacity: alpha, numOctaves: int, }; function buildHTMLStyles(state, latestValues, options, transformTemplate) { const { style, vars, transform, transformKeys, transformOrigin } = state; transformKeys.length = 0; // Track whether we encounter any transform or transformOrigin values. let hasTransform = false; let hasTransformOrigin = false; // Does the calculated transform essentially equal "none"? let 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 (const key in latestValues) { const 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" const valueType = numberValueTypes[key]; const valueAsType = getValueAsType(value, valueType); if (transformProps.has(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 !== (valueType.default || 0)) transformIsNone = false; } else if (key.startsWith("origin")) { // If this is a transform origin, flag and enable further transform-origin processing hasTransformOrigin = true; transformOrigin[key] = valueAsType; } else { style[key] = valueAsType; } } if (hasTransform || transformTemplate) { style.transform = buildTransform(state, options, transformIsNone, transformTemplate); } else if (!latestValues.transform && style.transform) { /** * If we have previously created a transform but currently don't have any, * reset transform style to none. */ style.transform = "none"; } /** * Build a transformOrigin style. Uses the same defaults as the browser for * undefined origins. */ if (hasTransformOrigin) { const { originX = "50%", originY = "50%", originZ = 0, } = transformOrigin; style.transformOrigin = `${originX} ${originY} ${originZ}`; } } const createHtmlRenderState = () => ({ style: {}, transform: {}, transformKeys: [], transformOrigin: {}, vars: {}, }); function copyRawValuesOnly(target, source, props) { for (const key in source) { if (!isMotionValue(source[key]) && !isForcedMotionValue(key, props)) { target[key] = source[key]; } } } function useInitialMotionValues({ transformTemplate }, visualState, isStatic) { return React.useMemo(() => { const state = createHtmlRenderState(); buildHTMLStyles(state, visualState, { enableHardwareAcceleration: !isStatic }, transformTemplate); return Object.assign({}, state.vars, state.style); }, [visualState]); } function useStyle(props, visualState, isStatic) { const styleProp = props.style || {}; const style = {}; /** * Copy non-Motion Values straight into style */ copyRawValuesOnly(style, styleProp, props); Object.assign(style, useInitialMotionValues(props, visualState, isStatic)); return props.transformValues ? props.transformValues(style) : style; } function useHTMLProps(props, visualState, isStatic) { // The `any` isn't ideal but it is the type of createElement props argument const htmlProps = {}; const style = useStyle(props, visualState, isStatic); if (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-${props.drag === "x" ? "y" : "x"}`; } htmlProps.style = style; return htmlProps; } const animationProps = [ "animate", "exit", "variants", "whileHover", "whileTap", "whileFocus", "whileDrag", "whileInView", ]; const tapProps = ["whileTap", "onTap", "onTapStart", "onTapCancel"]; const panProps = ["onPan", "onPanStart", "onPanSessionStart", "onPanEnd"]; const inViewProps = [ "whileInView", "onViewportEnter", "onViewportLeave", "viewport", ]; /** * A list of all valid MotionProps. * * @privateRemarks * This doesn't throw if a `MotionProp` name is missing - it should. */ const validMotionProps = new Set([ "initial", "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", "onHoverStart", "onHoverEnd", "layoutScroll", ...inViewProps, ...tapProps, ...animationProps, ...panProps, ]); /** * 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); } let shouldForward = (key) => !isValidMotionProp(key); function loadExternalIsValidProp(isValidProp) { if (!isValidProp) return; // Explicitly filter our events shouldForward = (key) => 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) { const filteredProps = {}; for (const 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 : 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) { const pxOriginX = calcOrigin$1(originX, dimensions.x, dimensions.width); const pxOriginY = calcOrigin$1(originY, dimensions.y, dimensions.height); return `${pxOriginX} ${pxOriginY}`; } const dashKeys = { offset: "stroke-dashoffset", array: "stroke-dasharray", }; const 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 = 1, offset = 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. const keys = useDashCase ? dashKeys : camelKeys; // Build the dash offset attrs[keys.offset] = px.transform(-offset); // Build the dash array const pathLength = px.transform(length); const pathSpacing = px.transform(spacing); attrs[keys.array] = `${pathLength} ${pathSpacing}`; } /** * Build SVG visual attrbutes, like cx and style.transform */ function buildSVGAttrs(state, { attrX, attrY, originX, originY, pathLength, pathSpacing = 1, pathOffset = 0, // This is object creation, which we try to avoid per-frame. ...latest }, options, transformTemplate) { buildHTMLStyles(state, latest, options, transformTemplate); state.attrs = state.style; state.style = {}; const { attrs, style, dimensions } = state; /** * 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); } } const createSvgRenderState = () => ({ ...createHtmlRenderState(), attrs: {}, }); function useSVGProps(props, visualState) { const visualProps = React.useMemo(() => { const state = createSvgRenderState(); buildSVGAttrs(state, visualState, { enableHardwareAcceleration: false }, props.transformTemplate); return { ...state.attrs, style: { ...state.style }, }; }, [visualState]); if (props.style) { const rawStyles = {}; copyRawValuesOnly(rawStyles, props.style, props); visualProps.style = { ...rawStyles, ...visualProps.style }; } return visualProps; } function createUseRender(forwardMotionProps = false) { const useRender = (Component, props, projectionId, ref, { latestValues }, isStatic) => { const useVisualProps = isSVGComponent(Component) ? useSVGProps : useHTMLProps; const visualProps = useVisualProps(props, latestValues, isStatic); const filteredProps = filterProps(props, typeof Component === "string", forwardMotionProps); const elementProps = { ...filteredProps, ...visualProps, ref, }; if (projectionId) { elementProps["data-projection-id"] = projectionId; } return React.createElement(Component, elementProps); }; return useRender; } /** * Convert camelCase to dash-case properties. */ const camelToDash = (str) => str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); function renderHTML(element, { style, vars }, styleProp, projection) { Object.assign(element.style, style, projection && projection.getProjectionStyles(styleProp)); // Loop over any CSS variables and assign those. for (const key in vars) { element.style.setProperty(key, vars[key]); } } /** * A set of attribute names that are always read/written as camel case. */ const 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 (const key in renderState.attrs) { element.setAttribute(!camelCaseAttributes.has(key) ? camelToDash(key) : key, renderState.attrs[key]); } } function scrapeMotionValuesFromProps$1(props) { const { style } = props; const newValues = {}; for (const key in style) { if (isMotionValue(style[key]) || isForcedMotionValue(key, props)) { newValues[key] = style[key]; } } return newValues; } function scrapeMotionValuesFromProps(props) { const newValues = scrapeMotionValuesFromProps$1(props); for (const key in props) { if (isMotionValue(props[key])) { const targetKey = key === "x" || key === "y" ? "attr" + key.toUpperCase() : key; newValues[targetKey] = props[key]; } } return newValues; } function resolveVariantFromProps(props, definition, custom, currentValues = {}, currentVelocity = {}) { /** * If the variant definition is a function, resolve. */ if (typeof definition === "function") { definition = definition(custom !== undefined ? 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 = props.variants && props.variants[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 !== undefined ? custom : props.custom, currentValues, currentVelocity); } return definition; } const isKeyframesTarget = (v) => { return Array.isArray(v); }; const isCustomValue = (v) => { return Boolean(v && typeof v === "object" && v.mix && v.toValue); }; const resolveFinalValueInKeyframes = (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) { const unwrappedValue = isMotionValue(value) ? value.get() : value; return isCustomValue(unwrappedValue) ? unwrappedValue.toValue() : unwrappedValue; } function makeState({ scrapeMotionValuesFromProps, createRenderState, onMount, }, props, context, presenceContext) { const state = { latestValues: makeLatestValues(props, context, presenceContext, scrapeMotionValuesFromProps), renderState: createRenderState(), }; if (onMount) { state.mount = (instance) => onMount(props, instance, state); } return state; } const makeUseVisualState = (config) => (props, isStatic) => { const context = React.useContext(MotionContext); const presenceContext = React.useContext(PresenceContext); const make = () => makeState(config, props, context, presenceContext); return isStatic ? make() : useConstant(make); }; function makeLatestValues(props, context, presenceContext, scrapeMotionValues) { const values = {}; const motionValues = scrapeMotionValues(props); for (const key in motionValues) { values[key] = resolveMotionValue(motionValues[key]); } let { initial, animate } = props; const isControllingVariants$1 = isControllingVariants(props); const isVariantNode$1 = isVariantNode(props); if (context && isVariantNode$1 && !isControllingVariants$1 && props.inherit !== false) { if (initial === undefined) initial = context.initial; if (animate === undefined) animate = context.animate; } let isInitialAnimationBlocked = presenceContext ? presenceContext.initial === false : false; isInitialAnimationBlocked = isInitialAnimationBlocked || initial === false; const variantToSet = isInitialAnimationBlocked ? animate : initial; if (variantToSet && typeof variantToSet !== "boolean" && !isAnimationControls(variantToSet)) { const list = Array.isArray(variantToSet) ? variantToSet : [variantToSet]; list.forEach((definition) => { const resolved = resolveVariantFromProps(props, definition); if (!resolved) return; const { transitionEnd, transition, ...target } = resolved; for (const key in target) { let valueTarget = target[key]; if (Array.isArray(valueTarget)) {