import { warning } from '../../utils/errors.mjs'; import { secondsToMilliseconds } from '../../utils/time-conversion.mjs'; import { instantAnimationState } from '../../utils/use-instant-transition-state.mjs'; import { createAcceleratedAnimation } from '../animators/waapi/create-accelerated-animation.mjs'; import { createInstantAnimation } from '../animators/instant.mjs'; import { getDefaultTransition } from '../utils/default-transitions.mjs'; import { isAnimatable } from '../utils/is-animatable.mjs'; import { getKeyframes } from '../utils/keyframes.mjs'; import { getValueTransition, isTransitionDefined } from '../utils/transitions.mjs'; import { animateValue } from '../animators/js/index.mjs'; import { MotionGlobalConfig } from '../../utils/GlobalConfig.mjs'; const animateMotionValue = (valueName, value, target, transition = {}) => { return (onComplete) => { const valueTransition = getValueTransition(transition, valueName) || {}; /** * Most transition values are currently completely overwritten by value-specific * transitions. In the future it'd be nicer to blend these transitions. But for now * delay actually does inherit from the root transition if not value-specific. */ const delay = valueTransition.delay || transition.delay || 0; /** * Elapsed isn't a public transition option but can be passed through from * optimized appear effects in milliseconds. */ let { elapsed = 0 } = transition; elapsed = elapsed - secondsToMilliseconds(delay); const keyframes = getKeyframes(value, valueName, target, valueTransition); /** * Check if we're able to animate between the start and end keyframes, * and throw a warning if we're attempting to animate between one that's * animatable and another that isn't. */ const originKeyframe = keyframes[0]; const targetKeyframe = keyframes[keyframes.length - 1]; const isOriginAnimatable = isAnimatable(valueName, originKeyframe); const isTargetAnimatable = isAnimatable(valueName, targetKeyframe); warning(isOriginAnimatable === isTargetAnimatable, `You are trying to animate ${valueName} from "${originKeyframe}" to "${targetKeyframe}". ${originKeyframe} is not an animatable value - to enable this animation set ${originKeyframe} to a value animatable to ${targetKeyframe} via the \`style\` property.`); let options = { keyframes, velocity: value.getVelocity(), ease: "easeOut", ...valueTransition, delay: -elapsed, onUpdate: (v) => { value.set(v); valueTransition.onUpdate && valueTransition.onUpdate(v); }, onComplete: () => { onComplete(); valueTransition.onComplete && valueTransition.onComplete(); }, }; /** * If there's no transition defined for this value, we can generate * unqiue transition settings for this value. */ if (!isTransitionDefined(valueTransition)) { options = { ...options, ...getDefaultTransition(valueName, options), }; } /** * Both WAAPI and our internal animation functions use durations * as defined by milliseconds, while our external API defines them * as seconds. */ if (options.duration) { options.duration = secondsToMilliseconds(options.duration); } if (options.repeatDelay) { options.repeatDelay = secondsToMilliseconds(options.repeatDelay); } if (!isOriginAnimatable || !isTargetAnimatable || instantAnimationState.current || valueTransition.type === false || MotionGlobalConfig.skipAnimations) { /** * If we can't animate this value, or the global instant animation flag is set, * or this is simply defined as an instant transition, return an instant transition. */ return createInstantAnimation(instantAnimationState.current ? { ...options, delay: 0 } : options); } /** * Animate via WAAPI if possible. */ if ( /** * If this is a handoff animation, the optimised animation will be running via * WAAPI. Therefore, this animation must be JS to ensure it runs "under" the * optimised animation. */ !transition.isHandoff && value.owner && value.owner.current instanceof HTMLElement && /** * If we're outputting values to onUpdate then we can't use WAAPI as there's * no way to read the value from WAAPI every frame. */ !value.owner.getProps().onUpdate) { const acceleratedAnimation = createAcceleratedAnimation(value, valueName, options); if (acceleratedAnimation) return acceleratedAnimation; } /** * If we didn't create an accelerated animation, create a JS animation */ return animateValue(options); }; }; export { animateMotionValue };