UNPKG

framer-motion

Version:

A simple and powerful JavaScript animation library

102 lines (99 loc) 4.96 kB
"use client"; import { jsxs, jsx } from 'react/jsx-runtime'; import { warning, invariant } from 'motion-utils'; import { forwardRef, useContext } from 'react'; import { LayoutGroupContext } from '../context/LayoutGroupContext.mjs'; import { LazyContext } from '../context/LazyContext.mjs'; import { MotionConfigContext } from '../context/MotionConfigContext.mjs'; import { MotionContext } from '../context/MotionContext/index.mjs'; import { useCreateMotionContext } from '../context/MotionContext/create.mjs'; import { isBrowser } from '../utils/is-browser.mjs'; import { featureDefinitions } from './features/definitions.mjs'; import { loadFeatures } from './features/load-features.mjs'; import { motionComponentSymbol } from './utils/symbol.mjs'; import { useMotionRef } from './utils/use-motion-ref.mjs'; import { useVisualElement } from './utils/use-visual-element.mjs'; /** * 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 createRendererMotionComponent({ preloadedFeatures, createVisualElement, useRender, useVisualState, Component, }) { preloadedFeatures && loadFeatures(preloadedFeatures); function MotionComponent(props, externalRef) { /** * If we need to measure the element we load this functionality in a * separate class component in order to gain access to getSnapshotBeforeUpdate. */ let MeasureLayout; const configAndProps = { ...useContext(MotionConfigContext), ...props, layoutId: useLayoutId(props), }; const { isStatic } = configAndProps; const context = useCreateMotionContext(props); const visualState = useVisualState(props, isStatic); if (!isStatic && isBrowser) { useStrictMode(configAndProps, preloadedFeatures); const layoutProjection = getProjectionFunctionality(configAndProps); MeasureLayout = layoutProjection.MeasureLayout; /** * 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, layoutProjection.ProjectionNode); } /** * The mount order and hierarchy is specific to ensure our element ref * is hydrated by the time features fire their effects. */ return (jsxs(MotionContext.Provider, { value: context, children: [MeasureLayout && context.visualElement ? (jsx(MeasureLayout, { visualElement: context.visualElement, ...configAndProps })) : null, useRender(Component, props, useMotionRef(visualState, context.visualElement, externalRef), visualState, isStatic, context.visualElement)] })); } MotionComponent.displayName = `motion.${typeof Component === "string" ? Component : `create(${Component.displayName ?? Component.name ?? ""})`}`; const ForwardRefMotionComponent = forwardRef(MotionComponent); ForwardRefMotionComponent[motionComponentSymbol] = Component; return ForwardRefMotionComponent; } function useLayoutId({ layoutId }) { const layoutGroupId = useContext(LayoutGroupContext).id; return layoutGroupId && layoutId !== undefined ? layoutGroupId + "-" + layoutId : layoutId; } function useStrictMode(configAndProps, preloadedFeatures) { const isStrict = useContext(LazyContext).strict; /** * If we're in development mode, check to make sure we're not rendering a motion component * as a child of LazyMotion, as this will break the file-size benefits of using it. */ if (process.env.NODE_ENV !== "production" && preloadedFeatures && isStrict) { const strictMessage = "You have rendered a `motion` component within a `LazyMotion` component. This will break tree shaking. Import and render a `m` component instead."; configAndProps.ignoreStrict ? warning(false, strictMessage) : invariant(false, strictMessage); } } function getProjectionFunctionality(props) { const { drag, layout } = featureDefinitions; if (!drag && !layout) return {}; const combined = { ...drag, ...layout }; return { MeasureLayout: drag?.isEnabled(props) || layout?.isEnabled(props) ? combined.MeasureLayout : undefined, ProjectionNode: combined.ProjectionNode, }; } export { createRendererMotionComponent };