80 lines
4.0 KiB
JavaScript
80 lines
4.0 KiB
JavaScript
import * as React from 'react';
|
|
import { forwardRef, useContext } from 'react';
|
|
import { MotionConfigContext } from '../context/MotionConfigContext.mjs';
|
|
import { MotionContext } from '../context/MotionContext/index.mjs';
|
|
import { useVisualElement } from './utils/use-visual-element.mjs';
|
|
import { useMotionRef } from './utils/use-motion-ref.mjs';
|
|
import { useCreateMotionContext } from '../context/MotionContext/create.mjs';
|
|
import { loadFeatures } from './features/load-features.mjs';
|
|
import { isBrowser } from '../utils/is-browser.mjs';
|
|
import { LayoutGroupContext } from '../context/LayoutGroupContext.mjs';
|
|
import { LazyContext } from '../context/LazyContext.mjs';
|
|
import { SwitchLayoutGroupContext } from '../context/SwitchLayoutGroupContext.mjs';
|
|
import { motionComponentSymbol } from './utils/symbol.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 createMotionComponent({ 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) {
|
|
/**
|
|
* 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 initialLayoutGroupConfig = useContext(SwitchLayoutGroupContext);
|
|
const isStrict = useContext(LazyContext).strict;
|
|
if (context.visualElement) {
|
|
MeasureLayout = context.visualElement.loadFeatures(
|
|
// Note: Pass the full new combined props to correctly re-render dynamic feature components.
|
|
configAndProps, isStrict, preloadedFeatures, initialLayoutGroupConfig);
|
|
}
|
|
}
|
|
/**
|
|
* The mount order and hierarchy is specific to ensure our element ref
|
|
* is hydrated by the time features fire their effects.
|
|
*/
|
|
return (React.createElement(MotionContext.Provider, { value: context },
|
|
MeasureLayout && context.visualElement ? (React.createElement(MeasureLayout, { visualElement: context.visualElement, ...configAndProps })) : null,
|
|
useRender(Component, props, useMotionRef(visualState, context.visualElement, externalRef), visualState, isStatic, context.visualElement)));
|
|
}
|
|
const ForwardRefComponent = forwardRef(MotionComponent);
|
|
ForwardRefComponent[motionComponentSymbol] = Component;
|
|
return ForwardRefComponent;
|
|
}
|
|
function useLayoutId({ layoutId }) {
|
|
const layoutGroupId = useContext(LayoutGroupContext).id;
|
|
return layoutGroupId && layoutId !== undefined
|
|
? layoutGroupId + "-" + layoutId
|
|
: layoutId;
|
|
}
|
|
|
|
export { createMotionComponent };
|